mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-08-11 03:43:38 +00:00
Compare commits
433 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d52e427238 | |||
| b37a8f3217 | |||
| 1202187e2d | |||
| 66d913e4bf | |||
| 02d30fa2b4 | |||
| c287a96c7f | |||
| 50305f3d6f | |||
| 1129e34e01 | |||
| 76dfcf751c | |||
| 4227186383 | |||
| 9d3341ab9f | |||
| fd432dc0d1 | |||
| ee12dc0a7d | |||
| 6f940fbf46 | |||
| 5fbdeb7a78 | |||
| 64f278e2e7 | |||
| 6fcc3ef1e2 | |||
| 0e073f3f8b | |||
| b3b021550c | |||
| 42e05aa28a | |||
| fd726f1da8 | |||
| 44786c3ea4 | |||
| 12d69c057a | |||
| 4841a06c9f | |||
| 1ee03dfe8c | |||
| c1ae6be799 | |||
| e689fe0366 | |||
| 94fced4af0 | |||
| 5223a4beba | |||
| 3c7a90e2a2 | |||
| da2d56752d | |||
| b26fb5ac19 | |||
| 3bb8f35f63 | |||
| 3ca27c4852 | |||
| 32421bdb6a | |||
| 06076f4154 | |||
| 322e079486 | |||
| f2af1b1921 | |||
| c5d3db0bed | |||
| 66c6b8c2e4 | |||
| 9b72254ccc | |||
| 5afd5cae27 | |||
| 4537784ad9 | |||
| c2f6940c5e | |||
| a7011e33fa | |||
| 5686403f7f | |||
| 33859c2b31 | |||
| 8472a37cd8 | |||
| ed49348155 | |||
| ae36353aab | |||
| c30d9f3472 | |||
| 853989bfc9 | |||
| df2dbed441 | |||
| 7f0f7cee0c | |||
| 14cf4edc39 | |||
| c1144b063b | |||
| cd81911bfb | |||
| d2962005ee | |||
| c2d4f179ae | |||
| a51aeab811 | |||
| dbc8e8be67 | |||
| 1c93f06b9b | |||
| 422e111857 | |||
| c8d2104c39 | |||
| b2a903e077 | |||
| 74bd2c1cfc | |||
| 4ea9c8508d | |||
| f9e5785f52 | |||
| 25e58393ea | |||
| 3653199bfc | |||
| 443a37a9c5 | |||
| 78ef53fbea | |||
| 5e63deb717 | |||
| 5d8b0d150a | |||
| 0440982c83 | |||
| b683dd5352 | |||
| ac975e7e86 | |||
| 2745c6c1ca | |||
| 918337f202 | |||
| 5a2779c698 | |||
| 7429982bef | |||
| 4222c4d945 | |||
| 62bfdcff8f | |||
| 06213ab583 | |||
| 1a8f9b4aa2 | |||
| 7c07df19bf | |||
| d2629263c3 | |||
| 11fb5500f3 | |||
| 31697030ed | |||
| 54e799d3f6 | |||
| ca2ae9657c | |||
| efaf7e856c | |||
| eeb5555400 | |||
| d72b33551e | |||
| 5a6a9d1aed | |||
| 2810acc1f4 | |||
| 08e4b7baeb | |||
| 8d722c2ccc | |||
| afe5df922a | |||
| 93a0b0e19e | |||
| d52867c541 | |||
| f554642505 | |||
| a0ecf58c7f | |||
| 0ab54268e9 | |||
| fdf2605367 | |||
| 3548cea999 | |||
| 7f3ebe4f48 | |||
| f22837103b | |||
| 7465fd065f | |||
| 61d3035c76 | |||
| 92402fc0ca | |||
| 6f1861f057 | |||
| 94259ab75a | |||
| 2dbfe1d4ee | |||
| d06d23cba2 | |||
| 8b9e49cfc6 | |||
| 618b4d7ded | |||
| b409b8dfc1 | |||
| efb514e97f | |||
| 81977aa04d | |||
| 3e5b87411e | |||
| 717ef02c44 | |||
| c4b8e4a36c | |||
| 4ae4f76ce0 | |||
| c0a9d51f98 | |||
| d9f7be4f25 | |||
| 891235032a | |||
| dda74c471f | |||
| f5c419919c | |||
| a95f3f2fa1 | |||
| 0e78a11fd6 | |||
| 130e089d1c | |||
| e675c991c4 | |||
| fc79188fe2 | |||
| 56ec7f122f | |||
| e1d09bdd8b | |||
| 1ae2171885 | |||
| c2257729d5 | |||
| 4dd8e53c1c | |||
| 4991fc9cb4 | |||
| 1371445e94 | |||
| 8823b96b5f | |||
| 9cb551e6fa | |||
| b9fc1df2e4 | |||
| 3bc387c1c7 | |||
| fb85b94d2d | |||
| 3409030e1a | |||
| c0b816fd40 | |||
| 9f14ed7a5c | |||
| e147a4c604 | |||
| fd7827c264 | |||
| 6c0b6b3b95 | |||
| 04fb7f7684 | |||
| fa9adaf046 | |||
| 6305459056 | |||
| a477fc78bc | |||
| 781ebc7a42 | |||
| 59603e3027 | |||
| dc7c50b97b | |||
| 2eb4e87d32 | |||
| fd730206a6 | |||
| 7d9e721506 | |||
| 5f416b3271 | |||
| 589335e420 | |||
| 2214584e59 | |||
| 090d0e79f9 | |||
| ae3d1f6b2a | |||
| 9ea52fce28 | |||
| dc3350a1c8 | |||
| 905095add9 | |||
| ce4353d701 | |||
| 929f99d527 | |||
| 541db97a74 | |||
| cc567cf342 | |||
| 8ed6f8b193 | |||
| 1dec76d96d | |||
| 75216793f9 | |||
| 173e68676c | |||
| e67d5a6c9f | |||
| 47e4777df0 | |||
| e1c602583c | |||
| 8925bc11ca | |||
| 6e97433ec4 | |||
| bddc40ddb8 | |||
| 7d697dc584 | |||
| 02c63850a1 | |||
| 2d3ef543b3 | |||
| 78ccbd733b | |||
| 99ab12ed99 | |||
| fd7469b5f0 | |||
| 014b07ffc3 | |||
| 165c569a99 | |||
| 6cb59a8f39 | |||
| 2e49b72ab4 | |||
| 0da53c794e | |||
| c54a0968b9 | |||
| 5ba5dd5fd8 | |||
| 004591f8ad | |||
| b7543f24e8 | |||
| eb8853ddf4 | |||
| 3a0e154daa | |||
| e004da9f00 | |||
| ecd76dec7d | |||
| 29995e1584 | |||
| b97b197219 | |||
| 87b75b4269 | |||
| 2b96071884 | |||
| 4ee3d8fb5c | |||
| 30c21086ba | |||
| 5fda8c1c1b | |||
| 4bb96a7730 | |||
| 4ec757d87a | |||
| 745da36e78 | |||
| 3a2effb2dc | |||
| f45eef2dd2 | |||
| d3825ccda1 | |||
| aaabf5220e | |||
| 43034ed0ad | |||
| 50ee8865ed | |||
| 74a4b84b78 | |||
| 56feca1451 | |||
| d5cdb9ec34 | |||
| 37d2602f2d | |||
| fe7df16ac0 | |||
| 1c1f74e1ad | |||
| 33d86bd081 | |||
| e509eb3d35 | |||
| ebc080b452 | |||
| 25ddf06ad7 | |||
| 5c23b102df | |||
| 3e9ab6834e | |||
| d9bfc65862 | |||
| c661491772 | |||
| 0b2f6b4f3d | |||
| 75114effc1 | |||
| b513b2a049 | |||
| 82c606edc1 | |||
| 0550ed1566 | |||
| 9dad6fb9b9 | |||
| 5d4fb7c1ba | |||
| 2e15341f6b | |||
| 8be023a5ee | |||
| 6b66b0d567 | |||
| 53bde229c3 | |||
| 4eaff505df | |||
| bff55e29ea | |||
| 3950008d05 | |||
| bbc1da3c48 | |||
| a4c6a67d33 | |||
| dbb1fa05d7 | |||
| 859b33fa08 | |||
| abf8601f67 | |||
| cd33677de4 | |||
| 68fff65088 | |||
| 99827bce47 | |||
| 2583c5c175 | |||
| 61dafa7aa7 | |||
| abbe53c7fa | |||
| 5b57873dee | |||
| 1d9b114c95 | |||
| 88e7302838 | |||
| b9cb19e8e0 | |||
| 3e69143cfd | |||
| 7a53439408 | |||
| 6a6c7a3236 | |||
| f9a49ab7e7 | |||
| 5ad927d20c | |||
| 75a7f05761 | |||
| 70d8fa23fb | |||
| a1e6ee7a90 | |||
| b3fe6fe363 | |||
| e978ff69bb | |||
| 5fe9cb24ac | |||
| cc5db0ef6b | |||
| 3d1f0cc376 | |||
| 751cd8c75c | |||
| 26bb6471dc | |||
| 1822df778d | |||
| 57af94ef7b | |||
| f79ec32c2b | |||
| 8c91c753ee | |||
| f975fa8eae | |||
| d0d58b3f24 | |||
| 986459151f | |||
| 5cf97ac802 | |||
| a33c8219e1 | |||
| f5ec6c1cb4 | |||
| 6f1afa2de9 | |||
| 7e9042074d | |||
| 164bcc5557 | |||
| 50b61ea094 | |||
| b737da9c51 | |||
| b41e539b6e | |||
| a2f35d816c | |||
| b12d9e9a5d | |||
| 5ba8979d94 | |||
| 880fae604d | |||
| 0db24006e1 | |||
| 05b87659f4 | |||
| 3b58e1f127 | |||
| c5705e8b1a | |||
| 91dc8a9453 | |||
| 16ed06a10b | |||
| 4fb28b1bf5 | |||
| 7c2ad7e8d0 | |||
| a79b4ba3a9 | |||
| 7b92053277 | |||
| 9e742b1620 | |||
| b3be71c75b | |||
| 010ca59281 | |||
| 1293679401 | |||
| f1f3d69584 | |||
| 8a00a31d5b | |||
| f96a554a84 | |||
| 70f49a9600 | |||
| f968472c00 | |||
| c9f9ac7b0e | |||
| 6a3c0b00a1 | |||
| f194db0309 | |||
| b0342c048d | |||
| 6ae074cd10 | |||
| d37a16f3ef | |||
| 7efd2a3053 | |||
| 5c8a3f695e | |||
| fff30c21cd | |||
| 6eafba3032 | |||
| b96cd1909a | |||
| aa294cd9c6 | |||
| 8880bf8587 | |||
| b0b8d81e5c | |||
| de2f7148ee | |||
| 69c8d52f52 | |||
| aae67268df | |||
| 417e88cc6b | |||
| 6411b77284 | |||
| 367c67f269 | |||
| a3067740a3 | |||
| af3bf7dece | |||
| 553f14d2ea | |||
| 36e923cac7 | |||
| eb14fdc84b | |||
| 361854390f | |||
| c6fc6ab017 | |||
| 254dd92cd8 | |||
| 93af911891 | |||
| 1ecbf22f0a | |||
| 3531b6a3e6 | |||
| 91035064fb | |||
| 11944dab2f | |||
| 15cbdabbeb | |||
| 677a733767 | |||
| 657e3578e0 | |||
| f9389f4aed | |||
| c263221236 | |||
| c0a1df4682 | |||
| aae8ef8053 | |||
| d9d246e194 | |||
| 2d4520995a | |||
| 786e1ea784 | |||
| 9e2f312126 | |||
| 9797d47fbf | |||
| 594afc5b8e | |||
| d530ba5c3e | |||
| 94bbb8a9a4 | |||
| aade1eebf1 | |||
| f16db1d4e5 | |||
| 17372a16c6 | |||
| 059bc05527 | |||
| 296a7a808a | |||
| db3ab439d0 | |||
| a26c9f423c | |||
| d1e4295178 | |||
| 043e1dc74b | |||
| 10154a6e4e | |||
| 1e1e0758f8 | |||
| f069523ba6 | |||
| 6c846380c0 | |||
| c172c6d6d6 | |||
| acd0ec187f | |||
| 965ae51223 | |||
| 65557db444 | |||
| 414196afc6 | |||
| eb37f9b18c | |||
| 6f7f20f7a8 | |||
| 2533651215 | |||
| aa4e5d0e7b | |||
| aef5f0c11a | |||
| a43769981d | |||
| b0a67686e3 | |||
| 4622affe70 | |||
| 6b55f0e5dc | |||
| 92a75d488c | |||
| c76703207a | |||
| 0418dfaa32 | |||
| 15d7c36d8f | |||
| 305a738926 | |||
| ba1b42f209 | |||
| b45b3c9804 | |||
| fa048311ca | |||
| 23bffd8c1d | |||
| 731517dcf2 | |||
| 6ac1194746 | |||
| 9b2ed229b2 | |||
| 8ad430347c | |||
| 9c344eff6e | |||
| 557a652f73 | |||
| 1ce7ecf64e | |||
| 9c84e995ac | |||
| 93936a07d0 | |||
| 7a000b02ce | |||
| fbff95ed29 | |||
| 7a79d49659 | |||
| 614a1cc906 | |||
| 1e1335e2ed | |||
| 70bca0d142 | |||
| eb3037c913 | |||
| 8e92b8045b | |||
| b795d2fd09 | |||
| a73faca149 | |||
| 96a7a963e6 | |||
| e206fcd5d5 | |||
| bbc353a861 | |||
| ba1857950e | |||
| 43193cf7f6 | |||
| 43c95b6ab5 | |||
| da44f6ef22 | |||
| 6113455520 | |||
| 1150c81ab9 | |||
| 5aabdb7593 | |||
| 0a8f83b0fc | |||
| b0adfc6bb5 | |||
| e959b40805 | |||
| 274771b212 |
@@ -0,0 +1,87 @@
|
||||
version: 2.4.a.{build}
|
||||
shallow_clone: true
|
||||
skip_branch_with_pr: false
|
||||
skip_commits:
|
||||
message: /!nobuild/
|
||||
skip_tags: false
|
||||
|
||||
environment:
|
||||
access_token_documentation:
|
||||
secure: JVBVVL8uJUcLXN+48eRdELEeCGOGCCaMzCqutsUqNuaZ/KblG5ZTt7+LV4UKv/0f
|
||||
LUAROCKS_VER: 2.4.1
|
||||
LUA_VER: 5.1.5
|
||||
LUA: lua5.3
|
||||
matrix:
|
||||
- LUA_VER: 5.1.5
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
|
||||
init:
|
||||
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
|
||||
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
|
||||
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
|
||||
throw "There are newer queued builds for this pull request, failing early." }
|
||||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
|
||||
install:
|
||||
- cmd:
|
||||
# Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache.
|
||||
call choco install 7zip.commandline
|
||||
call choco install lua51
|
||||
call choco install luarocks
|
||||
call refreshenv
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
|
||||
cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin
|
||||
cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua
|
||||
cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll
|
||||
call luarocks install luasrcdiet
|
||||
call luarocks install checks
|
||||
call luarocks install luadocumentor
|
||||
call luarocks install luacheck
|
||||
|
||||
|
||||
cache:
|
||||
C:\ProgramData\chocolatey\lib
|
||||
C:\ProgramData\chocolatey\bin
|
||||
|
||||
|
||||
|
||||
build_script:
|
||||
- ps: |
|
||||
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
|
||||
{
|
||||
echo "Hello World!"
|
||||
$apiUrl = 'https://ci.appveyor.com/api'
|
||||
$token = 'v2.6hcv3ige78kg3yvg4ge8'
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $token"
|
||||
"Content-type" = "application/json"
|
||||
}
|
||||
$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: |
|
||||
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
|
||||
{
|
||||
$apiUrl = 'https://ci.appveyor.com/api'
|
||||
$token = 'v2.6hcv3ige78kg3yvg4ge8'
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $token"
|
||||
"Content-type" = "application/json"
|
||||
}
|
||||
$RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-docs'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json
|
||||
# get project with last build details
|
||||
$project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody
|
||||
}
|
||||
|
||||
|
||||
test: off
|
||||
# test_script:
|
||||
# - cmd: luacheck "Moose Development\Moose\moose.lua" "Moose Mission Setup\moose.lua"
|
||||
|
||||
|
||||
on_finish:
|
||||
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
@@ -15,7 +15,3 @@
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
# Avoid Windows line endings on shell scripts
|
||||
# Needed for dockerfile builds
|
||||
*.sh text eol=lf
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
name: Moose-Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master-ng
|
||||
- develop
|
||||
paths:
|
||||
- 'Moose Setup/**/*.lua'
|
||||
- 'Moose Development/**/*.lua'
|
||||
- 'Moose Development/**/*.py'
|
||||
- 'Moose Development/**/*.html'
|
||||
- '.github/workflows/build-docs.yml'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
|
||||
id: extract_branch
|
||||
|
||||
- name: Build informations
|
||||
run: |
|
||||
echo "Triggered by: ${{ github.event_name }}"
|
||||
echo "Running on: ${{ runner.os }}"
|
||||
echo "Ref: ${{ github.ref }}"
|
||||
echo "Branch name: ${{ steps.extract_branch.outputs.branch }}"
|
||||
echo "Repository: ${{ github.repository }}"
|
||||
echo "Commit-Id: ${{ github.sha }}"
|
||||
echo "Owner: ${{ github.repository_owner }}"
|
||||
echo "FORCE_PUSH: ${{ vars.FORCE_PUSH }}"
|
||||
|
||||
#########################################################################
|
||||
# Prepare build environment
|
||||
#########################################################################
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare build output folders
|
||||
run: |
|
||||
mkdir -p build/tools
|
||||
mkdir -p build/doc
|
||||
|
||||
- name: Checkout FlightControls modified luadocumentor
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: Applevangelist/luadocumentor
|
||||
path: './build/tools/luadocumentor'
|
||||
ref: 'patch-1'
|
||||
token: ${{ secrets.BOT_TOKEN }}
|
||||
|
||||
- name: Update apt-get (needed for act docker image)
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get -qq update
|
||||
|
||||
- name: Install tree
|
||||
run: |
|
||||
sudo apt-get -qq install tree
|
||||
|
||||
#########################################################################
|
||||
# Install all prerequisites for LuaDocumentor
|
||||
#########################################################################
|
||||
- name: Install Lua
|
||||
run: |
|
||||
sudo apt-get -qq install lua5.1
|
||||
|
||||
- name: Install LuaRocks
|
||||
run: |
|
||||
sudo apt-get -qq install luarocks -y
|
||||
|
||||
- name: Install markdown (prereq for LuaDocumentor)
|
||||
run: |
|
||||
sudo luarocks install markdown 0.33-1
|
||||
|
||||
- name: Install penlight (prereq for LuaDocumentor)
|
||||
run: |
|
||||
sudo luarocks install penlight 1.14.0-3
|
||||
|
||||
- name: Install metalua-compiler (prereq for LuaDocumentor)
|
||||
run: |
|
||||
sudo luarocks install metalua-compiler 0.7.3-1
|
||||
|
||||
- name: Install metalua-parser (prereq for LuaDocumentor)
|
||||
run: |
|
||||
sudo luarocks install metalua-parser 0.7.3-2
|
||||
|
||||
- name: Install checks (prereq for LuaDocumentor)
|
||||
run: |
|
||||
sudo luarocks install checks
|
||||
|
||||
#########################################################################
|
||||
# Run LuaDocumentor
|
||||
#########################################################################
|
||||
- name: Run LuaDocumentor
|
||||
run: |
|
||||
lua luadocumentor.lua -d ${{ github.workspace }}/build/doc "${{ github.workspace }}/Moose Development/Moose"
|
||||
working-directory: ${{ github.workspace }}/build/tools/luadocumentor
|
||||
|
||||
#########################################################################
|
||||
# Replace <head> tag
|
||||
#########################################################################
|
||||
- name: Replace head tag
|
||||
run: |
|
||||
python3 "${{ github.workspace }}/Moose Development/docs-header.py"
|
||||
working-directory: ${{ github.workspace }}/build/doc
|
||||
|
||||
- name: Check replacement of head tag
|
||||
run: |
|
||||
head -10 ${{ github.workspace }}/build/doc/Core.Menu.html
|
||||
|
||||
#########################################################################
|
||||
# Push to MOOSE_DOCS
|
||||
#########################################################################
|
||||
- name: Set docs repo for branch
|
||||
shell: bash
|
||||
id: set_doc_repo
|
||||
run: |
|
||||
if [[ $GITHUB_REF == 'refs/heads/master-ng' ]]; then
|
||||
echo "docrepo=MOOSE_DOCS" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "docrepo=MOOSE_DOCS_DEVELOP" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Checkout ${{ steps.set_doc_repo.outputs.docrepo }} to folder MOOSE_DOCS
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ github.repository_owner }}/${{ steps.set_doc_repo.outputs.docrepo }}
|
||||
path: './build/MOOSE_DOCS'
|
||||
fetch-depth: 0
|
||||
ref: 'master-ng'
|
||||
token: ${{ secrets.BOT_TOKEN }}
|
||||
|
||||
- name: Delete folder to remove deleted files
|
||||
run: |
|
||||
rm -rf ./build/MOOSE_DOCS/Documentation/
|
||||
|
||||
- name: Create target folder
|
||||
run: mkdir -p build/MOOSE_DOCS/Documentation
|
||||
|
||||
- name: Copy build result to MOOSE_DOCS
|
||||
run: |
|
||||
cp ./build/doc/*.* ./build/MOOSE_DOCS/Documentation/
|
||||
|
||||
- name: Push result to docs repository
|
||||
if: ${{ vars.FORCE_PUSH == 'true' }}
|
||||
run: |
|
||||
git config user.name "MooseBotter"
|
||||
git config user.email "MooseBotter@users.noreply.github.com"
|
||||
git add .
|
||||
git commit --allow-empty -m "Auto commit by GitHub Actions Workflow"
|
||||
git push --set-upstream origin 'master-ng'
|
||||
|
||||
working-directory: ${{ github.workspace }}/build/MOOSE_DOCS
|
||||
|
||||
#########################################################################
|
||||
# Show the results
|
||||
#########################################################################
|
||||
- name: List files in the repository
|
||||
run: |
|
||||
tree ${{ github.workspace }}/build
|
||||
|
||||
- run: echo "This job's status is ${{ job.status }}."
|
||||
@@ -1,151 +0,0 @@
|
||||
name: Moose-Includes
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master-ng
|
||||
- develop
|
||||
- Apple/Develop
|
||||
|
||||
paths:
|
||||
- 'Moose Setup/**/*.lua'
|
||||
- 'Moose Development/**/*.lua'
|
||||
- '.github/workflows/build-includes.yml'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
|
||||
id: extract_branch
|
||||
|
||||
- name: Build informations
|
||||
run: |
|
||||
echo "Triggered by: ${{ github.event_name }}"
|
||||
echo "Running on: ${{ runner.os }}"
|
||||
echo "Ref: ${{ github.ref }}"
|
||||
echo "Branch name: ${{ steps.extract_branch.outputs.branch }}"
|
||||
echo "Repository: ${{ github.repository }}"
|
||||
echo "Commit-Id: ${{ github.sha }}"
|
||||
echo "Owner: ${{ github.repository_owner }}"
|
||||
echo "FORCE_PUSH: ${{ vars.FORCE_PUSH }}"
|
||||
|
||||
#########################################################################
|
||||
# Prepare build environment
|
||||
#########################################################################
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare build output folders
|
||||
run: |
|
||||
mkdir -p build/result/Moose_Include_Dynamic
|
||||
mkdir -p build/result/Moose_Include_Static
|
||||
|
||||
- name: Update apt-get (needed for act docker image)
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get -qq update
|
||||
|
||||
- name: Install tree
|
||||
run: |
|
||||
sudo apt-get -qq install tree
|
||||
|
||||
#########################################################################
|
||||
# Install all prerequisites
|
||||
#########################################################################
|
||||
- name: Install Lua 5.3
|
||||
run: |
|
||||
sudo apt-get -qq install lua5.3 -y
|
||||
- name: Check Lua version
|
||||
run: |
|
||||
lua -v
|
||||
|
||||
- name: Install LuaRocks
|
||||
run: |
|
||||
sudo apt-get -qq install luarocks -y
|
||||
- name: Check LuaRocks version
|
||||
run: |
|
||||
luarocks --version
|
||||
|
||||
- name: Install Lua 5.3 Dev for prerequisites for LuaSrcDiet
|
||||
run: |
|
||||
sudo apt-get -qq install liblua5.3-dev -y
|
||||
|
||||
- name: Install LuaSrcDiet
|
||||
run: |
|
||||
sudo luarocks install luasrcdiet
|
||||
|
||||
- name: Install LuaCheck
|
||||
run: |
|
||||
sudo luarocks install luacheck
|
||||
|
||||
#########################################################################
|
||||
# Build Include files
|
||||
#########################################################################
|
||||
- name: Build Include Static
|
||||
run: |
|
||||
export COMMIT_TIME=$(git show -s --format=%cd ${{ github.sha }} --date=iso-strict)
|
||||
lua5.3 "./Moose Setup/Moose_Create.lua" S "$COMMIT_TIME-${{ github.sha }}" "./Moose Development/Moose" "./Moose Setup" "./build/result/Moose_Include_Static"
|
||||
|
||||
- name: Build Includes Dynamic
|
||||
run: |
|
||||
export COMMIT_TIME=$(git show -s --format=%cd ${{ github.sha }} --date=iso-strict)
|
||||
lua5.3 "./Moose Setup/Moose_Create.lua" D "$COMMIT_TIME-${{ github.sha }}" "./Moose Development/Moose" "./Moose Setup" "./build/result/Moose_Include_Dynamic"
|
||||
|
||||
#########################################################################
|
||||
# Run LuaCheck
|
||||
#########################################################################
|
||||
- name: Run LuaCheck
|
||||
if: ${{ vars.SKIP_LUACHECK != true }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
luacheck --std=lua51c --config=.luacheckrc -guras "Moose Development/Moose"
|
||||
|
||||
- name: Run LuaSrcDiet
|
||||
run: |
|
||||
luasrcdiet --basic --opt-emptylines ./build/result/Moose_Include_Static/Moose.lua -o ./build/result/Moose_Include_Static/Moose_.lua
|
||||
|
||||
#########################################################################
|
||||
# Push to MOOSE_INCLUDE
|
||||
#########################################################################
|
||||
- name: Checkout MOOSE_INCLUDE
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ github.repository_owner }}/MOOSE_INCLUDE
|
||||
path: './build/MOOSE_INCLUDE'
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.extract_branch.outputs.branch }}
|
||||
token: ${{ secrets.BOT_TOKEN }}
|
||||
|
||||
- name: Create target folder (needed if checkout is deactivated)
|
||||
run: mkdir -p build/MOOSE_INCLUDE
|
||||
|
||||
- name: Copy build reseult to MOOSE_INCLUDE
|
||||
run: |
|
||||
cp -r ./build/result/* ./build/MOOSE_INCLUDE/
|
||||
|
||||
- name: Push result to MOOSE_INCLUDE repository
|
||||
if: ${{ vars.FORCE_PUSH == 'true' }}
|
||||
run: |
|
||||
git config user.name "MooseBotter"
|
||||
git config user.email "MooseBotter@users.noreply.github.com"
|
||||
git add .
|
||||
git commit --allow-empty -m "Auto commit by GitHub Actions Workflow"
|
||||
git push --set-upstream origin ${{ steps.extract_branch.outputs.branch }}
|
||||
|
||||
working-directory: ${{ github.workspace }}/build/MOOSE_INCLUDE
|
||||
|
||||
#########################################################################
|
||||
# Show the results
|
||||
#########################################################################
|
||||
- name: List files in the repository
|
||||
run: |
|
||||
tree ${{ github.workspace }}/build
|
||||
|
||||
- run: echo "This job's status is ${{ job.status }}."
|
||||
@@ -1,78 +0,0 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
||||
name: Deploy Jekyll site to Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master-ng"]
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/workflows/gh-pages.yml'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.1' # Not needed with a .ruby-version file
|
||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||
cache-version: 0 # Increment this number if you need to re-download cached gems
|
||||
working-directory: docs/
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Build with Jekyll
|
||||
# Outputs to the './_site' directory by default
|
||||
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
||||
env:
|
||||
JEKYLL_ENV: production
|
||||
working-directory: docs/
|
||||
- name: Upload artifact
|
||||
# Automatically uploads an artifact from the './_site' directory by default
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: docs/_site/
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
steps:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v5
|
||||
- run: npm install linkinator
|
||||
- run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip 'https:\/\/flightcontrol-master\.github\.io\/MOOSE_DOCS_DEVELOP\/Documentation\/(?:AI|Tasking|Cargo|MissileTrainer)\.[^\/\s]+\.html$' --status-code "404:skip" --retry-errors --retry-errors-count 3 --retry-errors-jitter
|
||||
+5
-11
@@ -42,8 +42,6 @@ local.properties
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
.vscode
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
@@ -228,16 +226,12 @@ pip-log.txt
|
||||
#Goodsync
|
||||
_gsdata_/
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
||||
#GITHUB
|
||||
.gitattributes
|
||||
.gitignore
|
||||
Moose Test Missions/MOOSE_Test_Template.miz
|
||||
Moose Development/Moose/.vscode/launch.json
|
||||
MooseCodeWS.code-workspace
|
||||
|
||||
# Excludes for act (https://github.com/nektos/act)
|
||||
.secrets
|
||||
.env
|
||||
.actrc
|
||||
.vars
|
||||
.gitignore
|
||||
.gitignore
|
||||
/.gitignore
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"ExpandedNodes": [
|
||||
"",
|
||||
"\\Moose Development",
|
||||
"\\Moose Development\\Moose",
|
||||
"\\Moose Development\\Moose\\Ops"
|
||||
],
|
||||
"SelectedNode": "\\Moose Development\\Moose\\Ops\\Airboss.lua",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' )
|
||||
|
||||
local base = _G
|
||||
|
||||
__Moose = {}
|
||||
|
||||
__Moose.Include = function( IncludeFile )
|
||||
if not __Moose.Includes[ IncludeFile ] then
|
||||
__Moose.Includes[IncludeFile] = IncludeFile
|
||||
local f = assert( base.loadfile( IncludeFile ) )
|
||||
if f == nil then
|
||||
error ("Moose: Could not load Moose file " .. IncludeFile )
|
||||
else
|
||||
env.info( "Moose: " .. IncludeFile .. " dynamically loaded." )
|
||||
return f()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
__Moose.Includes = {}
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Modules.lua' )
|
||||
BASE:TraceOnOff( true )
|
||||
env.info( '*** MOOSE INCLUDE END *** ' )
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1953,7 +1953,7 @@ local function refct_from_id(id) -- refct = refct_from_id(CTypeID)
|
||||
unsigned = refct.unsigned,
|
||||
size = bit.band(bit.rshift(ctype.info, 16), 127),
|
||||
}
|
||||
refct.bool, refct.const, refct.volatile, refct.unsigned = nil, nil, nil, nil
|
||||
refct.bool, refct.const, refct.volatile, refct.unsigned = nil
|
||||
end
|
||||
|
||||
if CT[4] then -- Merge sibling attributes onto this type.
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
# Repository: https://github.com/CppCXY/EmmyLuaCodeStyle
|
||||
# English documentation: https://github.com/CppCXY/EmmyLuaCodeStyle/blob/master/README_EN.md
|
||||
[*.lua]
|
||||
# [basic]
|
||||
|
||||
# optional space/tab
|
||||
indent_style = space
|
||||
# if indent_style is space, this is valid
|
||||
indent_size = 4
|
||||
# if indent_style is tab, this is valid
|
||||
tab_width = 4
|
||||
# none/single/double
|
||||
quote_style = none
|
||||
|
||||
# only support number
|
||||
continuation_indent_size = 0
|
||||
|
||||
# optional crlf/lf/cr/auto, if it is 'auto', in windows it is crlf other platforms are lf
|
||||
end_of_line = auto
|
||||
|
||||
detect_end_of_line = false
|
||||
|
||||
# this mean utf8 length , if this is 'unset' then the line width is no longer checked
|
||||
# this option decides when to chopdown the code
|
||||
max_line_length = 9999
|
||||
|
||||
# this will check text end with new line
|
||||
insert_final_newline = true
|
||||
|
||||
# [function]
|
||||
|
||||
# function call expression's args will align to first arg
|
||||
# optional true/false/only_after_more_indention_statement/only_not_exist_cross_row_expression
|
||||
align_call_args = false
|
||||
|
||||
# if true, all function define params will align to first param
|
||||
align_function_define_params = true
|
||||
|
||||
remove_expression_list_finish_comma = true
|
||||
|
||||
# keep/remove/remove_table_only/remove_string_only/unambiguous_remove_string_only
|
||||
call_arg_parentheses = keep
|
||||
|
||||
# [table]
|
||||
|
||||
#optional none/comma/semicolon
|
||||
table_separator_style = none
|
||||
|
||||
#optional keep/never/always/smart
|
||||
trailing_table_separator = keep
|
||||
|
||||
# see document for detail
|
||||
continuous_assign_table_field_align_to_equal_sign = true
|
||||
|
||||
# if true, format like this "local t = { 1, 2, 3 }"
|
||||
keep_one_space_between_table_and_bracket = true
|
||||
|
||||
# if indent_style is tab, this option is invalid
|
||||
align_table_field_to_first_field = true
|
||||
|
||||
# [statement]
|
||||
|
||||
align_chained_expression_statement = false
|
||||
|
||||
# continous line distance
|
||||
max_continuous_line_distance = 1
|
||||
|
||||
# see document for detail
|
||||
continuous_assign_statement_align_to_equal_sign = true
|
||||
|
||||
# if statement will align like switch case
|
||||
if_condition_align_with_each_other = false
|
||||
|
||||
# if true, continuation_indent_size for local or assign statement is invalid
|
||||
# however, if the expression list has cross row expression, it will not be aligned to the first expression
|
||||
local_assign_continuation_align_to_first_expression = false
|
||||
|
||||
statement_inline_comment_space = 1
|
||||
|
||||
# [indentation]
|
||||
|
||||
# if true, the label loses its current indentation
|
||||
label_no_indent = false
|
||||
# if true, there will be no indentation in the do statement
|
||||
do_statement_no_indent = false
|
||||
# if true, the conditional expression of the if statement will not be a continuation line indent
|
||||
if_condition_no_continuation_indent = false
|
||||
|
||||
if_branch_comments_after_block_no_indent = false
|
||||
|
||||
# [space]
|
||||
|
||||
# if true, t[#t+1] will not space wrapper '+'
|
||||
table_append_expression_no_space = false
|
||||
|
||||
long_chain_expression_allow_one_space_after_colon = false
|
||||
|
||||
remove_empty_header_and_footer_lines_in_function = true
|
||||
|
||||
space_before_function_open_parenthesis = false
|
||||
|
||||
space_inside_function_call_parentheses = false
|
||||
|
||||
space_inside_function_param_list_parentheses = false
|
||||
|
||||
space_before_open_square_bracket = false
|
||||
|
||||
space_inside_square_brackets = false
|
||||
|
||||
# if true, ormat like this "local t <const> = 1"
|
||||
keep_one_space_between_namedef_and_attribute = true
|
||||
|
||||
# [row_layout]
|
||||
# The following configuration supports four expressions
|
||||
# minLine:${n}
|
||||
# keepLine
|
||||
# keepLine:${n}
|
||||
# maxLine:${n}
|
||||
|
||||
keep_line_after_if_statement = minLine:0
|
||||
|
||||
keep_line_after_do_statement = minLine:0
|
||||
|
||||
keep_line_after_while_statement = minLine:0
|
||||
|
||||
keep_line_after_repeat_statement = minLine:0
|
||||
|
||||
keep_line_after_for_statement = minLine:0
|
||||
|
||||
keep_line_after_local_or_assign_statement = keepLine
|
||||
|
||||
keep_line_after_function_define_statement = keepLine:1
|
||||
|
||||
keep_line_after_expression_statement = keepLine
|
||||
|
||||
# [diagnostic]
|
||||
|
||||
# the following is code diagnostic options
|
||||
enable_check_codestyle = true
|
||||
|
||||
# [diagnostic.name_style]
|
||||
enable_name_style_check = false
|
||||
# the following is name style check rule
|
||||
# base option off/camel_case/snake_case/upper_snake_case/pascal_case/same(filename/first_param/'<const string>', snake_case/pascal_case/camel_case)
|
||||
# all option can use '|' represent or
|
||||
# for example:
|
||||
# snake_case | upper_snake_case
|
||||
# same(first_param, snake_case)
|
||||
# same('m')
|
||||
local_name_define_style = snake_case
|
||||
|
||||
function_param_name_style = snake_case
|
||||
|
||||
function_name_define_style = snake_case
|
||||
|
||||
local_function_name_define_style = snake_case
|
||||
|
||||
table_field_name_define_style = snake_case
|
||||
|
||||
global_variable_name_define_style = snake_case|upper_snake_case
|
||||
|
||||
module_name_define_style = same('m')|same(filename, snake_case)
|
||||
|
||||
require_module_name_style = same(first_param, snake_case)
|
||||
|
||||
class_name_define_style = same(filename, snake_case)
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"Lua.workspace.preloadFileSize": 10000,
|
||||
"Lua.diagnostics.disable": [
|
||||
"undefined-doc-name",
|
||||
"duplicate-set-field",
|
||||
"trailing-space",
|
||||
"need-check-nil",
|
||||
"ambiguity-1",
|
||||
"undefined-doc-param",
|
||||
"redundant-parameter",
|
||||
"param-type-mismatch",
|
||||
"deprecated",
|
||||
"undefined-global",
|
||||
"lowercase-global"
|
||||
],
|
||||
"Lua.diagnostics.globals": [
|
||||
"BASE",
|
||||
"lfs",
|
||||
"__Moose",
|
||||
"trigger",
|
||||
"coord",
|
||||
"missionCommands"
|
||||
],
|
||||
"Lua.completion.displayContext": 5,
|
||||
"Lua.runtime.version": "Lua 5.1",
|
||||
"Lua.completion.callSnippet": "Both"
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
--- **AI** -- (R2.2) - Models the process of Combat Air Patrol (CAP) for airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2A_Cap
|
||||
-- @image AI_Combat_Air_Patrol.JPG
|
||||
|
||||
--- @type AI_A2A_CAP
|
||||
-- @extends AI.AI_Air_Patrol#AI_AIR_PATROL
|
||||
-- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE
|
||||
|
||||
|
||||
--- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
|
||||
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_A2A_CAP is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_A2A_CAP constructor
|
||||
--
|
||||
-- * @{#AI_A2A_CAP.New}(): Creates a new AI_A2A_CAP object.
|
||||
--
|
||||
-- ## 2. AI_A2A_CAP is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1 AI_A2A_CAP States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2 AI_A2A_CAP Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_A2A_CAP.Engage}**: Let the AI engage the bogeys.
|
||||
-- * **@{#AI_A2A_CAP.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_CAP#AI_A2A_CAP.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_A2A_CAP.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2A_CAP
|
||||
AI_A2A_CAP = {
|
||||
ClassName = "AI_A2A_CAP",
|
||||
}
|
||||
|
||||
--- Creates a new AI_A2A_CAP object
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_A2A_CAP
|
||||
function AI_A2A_CAP:New2( AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
|
||||
|
||||
-- Multiple inheritance ... :-)
|
||||
local AI_Air = AI_AIR:New( AICap )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage ) --#AI_A2A_CAP
|
||||
|
||||
self:SetFuelThreshold( .2, 60 )
|
||||
self:SetDamageThreshold( 0.4 )
|
||||
self:SetDisengageRadius( 70000 )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new AI_A2A_CAP object
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2A_CAP
|
||||
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
|
||||
|
||||
end
|
||||
|
||||
--- onafter State Transition for Event Patrol.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_CAP:onafterStart( AICap, From, Event, To )
|
||||
|
||||
self:GetParent( self, AI_A2A_CAP ).onafterStart( self, AICap, From, Event, To )
|
||||
AICap:HandleEvent( EVENTS.Takeoff, nil, self )
|
||||
|
||||
end
|
||||
|
||||
--- Set the Engage Zone which defines where the AI will engage bogies.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP.
|
||||
-- @return #AI_A2A_CAP self
|
||||
function AI_A2A_CAP:SetEngageZone( EngageZone )
|
||||
self:F2()
|
||||
|
||||
if EngageZone then
|
||||
self.EngageZone = EngageZone
|
||||
else
|
||||
self.EngageZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the Engage Range when the AI will engage with airborne enemies.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param #number EngageRange The Engage Range.
|
||||
-- @return #AI_A2A_CAP self
|
||||
function AI_A2A_CAP:SetEngageRange( EngageRange )
|
||||
self:F2()
|
||||
|
||||
if EngageRange then
|
||||
self.EngageRange = EngageRange
|
||||
else
|
||||
self.EngageRange = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2A_CAP self
|
||||
function AI_A2A_CAP:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
|
||||
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
|
||||
if AttackUnit and AttackUnit:IsAlive() and AttackUnit:IsAir() then
|
||||
-- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition()
|
||||
-- Maybe the detected set also contains
|
||||
self:T( { "Attacking Task:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit )
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
--- **AI** -- (R2.2) - Models the process of Ground Controlled Interception (GCI) for airplanes.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2A_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2A_GCI
|
||||
-- @image AI_Ground_Control_Intercept.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_A2A_GCI
|
||||
-- @extends AI.AI_A2A#AI_A2A
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_A2A_GCI is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_A2A_GCI constructor
|
||||
--
|
||||
-- * @{#AI_A2A_GCI.New}(): Creates a new AI_A2A_GCI object.
|
||||
--
|
||||
-- ## 2. AI_A2A_GCI is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1 AI_A2A_GCI States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2 AI_A2A_GCI Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_A2A_GCI.Engage}**: Let the AI engage the bogeys.
|
||||
-- * **@{#AI_A2A_GCI.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_GCI#AI_A2A_GCI.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_A2A_GCI.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2A_GCI
|
||||
AI_A2A_GCI = {
|
||||
ClassName = "AI_A2A_GCI",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_A2A_GCI object
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_A2A_GCI
|
||||
function AI_A2A_GCI:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIIntercept )
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air, AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage ) -- #AI_A2A_GCI
|
||||
|
||||
self:SetFuelThreshold( .2, 60 )
|
||||
self:SetDamageThreshold( 0.4 )
|
||||
self:SetDisengageRadius( 70000 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new AI_A2A_GCI object
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_A2A_GCI
|
||||
function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
|
||||
return self:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
end
|
||||
|
||||
--- onafter State Transition for Event Patrol.
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_GCI:onafterStart( AIIntercept, From, Event, To )
|
||||
|
||||
self:GetParent( self, AI_A2A_GCI ).onafterStart( self, AIIntercept, From, Event, To )
|
||||
end
|
||||
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2A_GCI self
|
||||
function AI_A2A_GCI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
|
||||
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
|
||||
self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
|
||||
-- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition()
|
||||
-- Maybe the detected set also contains
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit )
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
@@ -0,0 +1,404 @@
|
||||
--- **AI** -- (R2.2) - Models the process of air patrol of airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2A_Patrol
|
||||
-- @image AI_Air_Patrolling.JPG
|
||||
|
||||
|
||||
--- @type AI_A2A_PATROL
|
||||
-- @extends AI.AI_A2A#AI_A2A
|
||||
|
||||
--- Implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_A2A_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
---- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or
|
||||
-- use derived AI_ classes to model AI offensive or defensive behaviour.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_A2A_PATROL constructor
|
||||
--
|
||||
-- * @{#AI_A2A_PATROL.New}(): Creates a new AI_A2A_PATROL object.
|
||||
--
|
||||
-- ## 2. AI_A2A_PATROL is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_A2A_PATROL States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base.
|
||||
-- * **Stopped** ( Group ): The process is stopped.
|
||||
-- * **Crashed** ( Group ): The AI has crashed or is dead.
|
||||
--
|
||||
-- ### 2.2. AI_A2A_PATROL Events
|
||||
--
|
||||
-- * **Start** ( Group ): Start the process.
|
||||
-- * **Stop** ( Group ): Stop the process.
|
||||
-- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **RTB** ( Group ): Route the AI to the home base.
|
||||
-- * **Detect** ( Group ): The AI is detecting targets.
|
||||
-- * **Detected** ( Group ): The AI has detected new targets.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set or Get the AI controllable
|
||||
--
|
||||
-- * @{#AI_A2A_PATROL.SetControllable}(): Set the AIControllable.
|
||||
-- * @{#AI_A2A_PATROL.GetControllable}(): Get the AIControllable.
|
||||
--
|
||||
-- ## 4. Set the Speed and Altitude boundaries of the AI controllable
|
||||
--
|
||||
-- * @{#AI_A2A_PATROL.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol.
|
||||
-- * @{#AI_A2A_PATROL.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol.
|
||||
--
|
||||
-- ## 5. Manage the detection process of the AI controllable
|
||||
--
|
||||
-- The detection process of the AI controllable can be manipulated.
|
||||
-- Detection requires an amount of CPU power, which has an impact on your mission performance.
|
||||
-- Only put detection on when absolutely necessary, and the frequency of the detection can also be set.
|
||||
--
|
||||
-- * @{#AI_A2A_PATROL.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
|
||||
-- * @{#AI_A2A_PATROL.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
|
||||
--
|
||||
-- The detection frequency can be set with @{#AI_A2A_PATROL.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
|
||||
-- Use the method @{#AI_A2A_PATROL.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI.
|
||||
--
|
||||
-- The detection can be filtered to potential targets in a specific zone.
|
||||
-- Use the method @{#AI_A2A_PATROL.SetDetectionZone}() to set the zone where targets need to be detected.
|
||||
-- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected
|
||||
-- according the weather conditions.
|
||||
--
|
||||
-- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL
|
||||
--
|
||||
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- while a new AI is targetted to the AI_A2A_PATROL.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place.
|
||||
--
|
||||
-- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL
|
||||
--
|
||||
-- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on.
|
||||
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2A_PATROL
|
||||
AI_A2A_PATROL = {
|
||||
ClassName = "AI_A2A_PATROL",
|
||||
}
|
||||
|
||||
--- Creates a new AI_A2A_PATROL object
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The patrol group object.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to BARO
|
||||
-- @return #AI_A2A_PATROL self
|
||||
-- @usage
|
||||
-- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol a Group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
|
||||
-- PatrolZone = ZONE:New( 'PatrolZone' )
|
||||
-- PatrolSpawn = SPAWN:New( 'Patrol Group' )
|
||||
-- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 )
|
||||
function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIPatrol )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Patrol ) -- #AI_A2A_PATROL
|
||||
|
||||
self:SetFuelThreshold( .2, 60 )
|
||||
self:SetDamageThreshold( 0.4 )
|
||||
self:SetDisengageRadius( 70000 )
|
||||
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
|
||||
-- defafult PatrolAltType to "BARO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "BARO"
|
||||
|
||||
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
|
||||
|
||||
--- OnBefore Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnBeforePatrol
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnAfterPatrol
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_A2A_PATROL] Patrol
|
||||
-- @param #AI_A2A_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_A2A_PATROL] __Patrol
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnLeavePatrolling
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnEnterPatrolling
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL.
|
||||
|
||||
--- OnBefore Transition Handler for Event Route.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnBeforeRoute
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Route.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnAfterRoute
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Route.
|
||||
-- @function [parent=#AI_A2A_PATROL] Route
|
||||
-- @param #AI_A2A_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Route.
|
||||
-- @function [parent=#AI_A2A_PATROL] __Route
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
|
||||
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum speed of the patrol.
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @return #AI_A2A_PATROL self
|
||||
function AI_A2A_PATROL:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
|
||||
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
|
||||
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sets the floor and ceiling altitude of the patrol.
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @return #AI_A2A_PATROL self
|
||||
function AI_A2A_PATROL:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
|
||||
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
end
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @return #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_PATROL:onafterPatrol( AIPatrol, From, Event, To )
|
||||
self:F2()
|
||||
|
||||
self:ClearTargetDistance()
|
||||
|
||||
self:__Route( 1 )
|
||||
|
||||
AIPatrol:OnReSpawn(
|
||||
function( PatrolGroup )
|
||||
self:__Reset( 1 )
|
||||
self:__Route( 5 )
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
--- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
|
||||
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The AI group.
|
||||
-- @param #AI_A2A_PATROL Fsm The FSM.
|
||||
function AI_A2A_PATROL.PatrolRoute( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:F( { "AI_A2A_PATROL.PatrolRoute:", AIPatrol:GetName() } )
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
Fsm:Route()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
|
||||
self:F2()
|
||||
|
||||
-- When RTB, don't allow anymore the routing.
|
||||
if From == "RTB" then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local CurrentCoord = AIPatrol:GetCoordinate()
|
||||
|
||||
-- Random altitude.
|
||||
local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude)
|
||||
|
||||
-- Random speed in km/h.
|
||||
local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed)
|
||||
|
||||
-- First waypoint is current position.
|
||||
PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current")
|
||||
|
||||
if self.racetrack then
|
||||
|
||||
-- Random heading.
|
||||
local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax)
|
||||
|
||||
-- Random leg length.
|
||||
local leg=math.random(self.racetracklegmin, self.racetracklegmax)
|
||||
|
||||
-- Random duration if any.
|
||||
local duration = self.racetrackdurationmin
|
||||
if self.racetrackdurationmax then
|
||||
duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax)
|
||||
end
|
||||
|
||||
-- CAP coordinate.
|
||||
local c0=self.PatrolZone:GetRandomCoordinate()
|
||||
if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then
|
||||
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
|
||||
end
|
||||
|
||||
-- Race track points.
|
||||
local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE
|
||||
local c2=c1:Translate(leg, heading):SetAltitude(altitude)
|
||||
|
||||
self:SetTargetDistance(c0) -- For RTB status check
|
||||
|
||||
-- Debug:
|
||||
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration)))
|
||||
--c1:MarkToAll("Race track c1")
|
||||
--c2:MarkToAll("Race track c2")
|
||||
|
||||
-- Task to orbit.
|
||||
local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2)
|
||||
|
||||
-- Task function to redo the patrol at other random position.
|
||||
local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self)
|
||||
|
||||
-- Controlled task with task condition.
|
||||
local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil)
|
||||
local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond)
|
||||
|
||||
-- Second waypoint
|
||||
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit")
|
||||
|
||||
else
|
||||
|
||||
-- Target coordinate.
|
||||
local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() --Core.Point#COORDINATE
|
||||
ToTargetCoord:SetAltitude(altitude)
|
||||
|
||||
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
|
||||
|
||||
local taskReRoute=AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
|
||||
|
||||
PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskReRoute}, "Patrol Point")
|
||||
|
||||
end
|
||||
|
||||
-- ROE
|
||||
AIPatrol:OptionROEReturnFire()
|
||||
AIPatrol:OptionROTEvadeFire()
|
||||
|
||||
-- Patrol.
|
||||
AIPatrol:Route( PatrolRoute, 0.5)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
--- **AI** -- Models the process of air to ground BAI engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2G_BAI
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_A2G_BAI
|
||||
-- @extends AI.AI_A2A_Engage#AI_A2A_Engage
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2G_BAI
|
||||
AI_A2G_BAI = {
|
||||
ClassName = "AI_A2G_BAI",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_BAI object
|
||||
-- @param #AI_A2G_BAI self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_BAI
|
||||
function AI_A2G_BAI:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIGroup )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_BAI object
|
||||
-- @param #AI_A2G_BAI self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_BAI
|
||||
function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType)
|
||||
end
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2G_BAI self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2G_BAI self
|
||||
function AI_A2G_BAI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
|
||||
for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do
|
||||
if AttackUnit then
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
|
||||
self:T( { "BAI Unit:", AttackUnit:GetName() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
--- **AI** -- Models the process of air to ground engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2G_CAS
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_A2G_CAS
|
||||
-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2G_CAS
|
||||
AI_A2G_CAS = {
|
||||
ClassName = "AI_A2G_CAS",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_CAS object
|
||||
-- @param #AI_A2G_CAS self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_CAS
|
||||
function AI_A2G_CAS:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIGroup )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_CAS object
|
||||
-- @param #AI_A2G_CAS self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_CAS
|
||||
function AI_A2G_CAS:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType)
|
||||
end
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2G_CAS self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2G_CAS self
|
||||
function AI_A2G_CAS:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
|
||||
for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do
|
||||
if AttackUnit then
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
|
||||
self:T( { "CAS Unit:", AttackUnit:GetName() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
||||
--- **AI** -- Models the process of air to ground SEAD engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2G_SEAD
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_A2G_SEAD
|
||||
-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL
|
||||
|
||||
|
||||
--- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_A2G_SEAD is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_SEAD process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_A2G_SEAD constructor
|
||||
--
|
||||
-- * @{#AI_A2G_SEAD.New}(): Creates a new AI_A2G_SEAD object.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_GCI#AI_A2G_SEAD.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_A2G_SEAD.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2G_SEAD
|
||||
AI_A2G_SEAD = {
|
||||
ClassName = "AI_A2G_SEAD",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_SEAD object
|
||||
-- @param #AI_A2G_SEAD self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_SEAD
|
||||
function AI_A2G_SEAD:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIGroup )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_SEAD object
|
||||
-- @param #AI_A2G_SEAD self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_SEAD
|
||||
function AI_A2G_SEAD:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
end
|
||||
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2G_SEAD self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2G_SEAD self
|
||||
function AI_A2G_SEAD:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
|
||||
for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do
|
||||
if AttackUnit then
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
|
||||
local HasRadar = AttackUnit:HasSEAD()
|
||||
if HasRadar then
|
||||
self:F( { "SEAD Unit:", AttackUnit:GetName() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
|
||||
@@ -0,0 +1,812 @@
|
||||
--- **AI** - Models the process of AI air operations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- @type AI_AIR
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
--- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_AIR constructor
|
||||
--
|
||||
-- * @{#AI_AIR.New}(): Creates a new AI_AIR object.
|
||||
--
|
||||
-- # 2) AI_AIR is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
--
|
||||
-- ## 2.1) AI_AIR States.
|
||||
--
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_AIR Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- @field #AI_AIR
|
||||
AI_AIR = {
|
||||
ClassName = "AI_AIR",
|
||||
}
|
||||
|
||||
AI_AIR.TaskDelay = 0.5 -- The delay of each task given to the AI.
|
||||
|
||||
--- Creates a new AI_AIR process.
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process.
|
||||
-- @return #AI_AIR
|
||||
function AI_AIR:New( AIGroup )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_AIR
|
||||
|
||||
self:SetControllable( AIGroup )
|
||||
|
||||
self:SetStartState( "Stopped" )
|
||||
|
||||
self:AddTransition( "*", "Queue", "Queued" )
|
||||
|
||||
self:AddTransition( "*", "Start", "Started" )
|
||||
|
||||
--- Start Handler OnBefore for AI_AIR
|
||||
-- @function [parent=#AI_AIR] OnBeforeStart
|
||||
-- @param #AI_AIR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #boolean
|
||||
|
||||
--- Start Handler OnAfter for AI_AIR
|
||||
-- @function [parent=#AI_AIR] OnAfterStart
|
||||
-- @param #AI_AIR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Start Trigger for AI_AIR
|
||||
-- @function [parent=#AI_AIR] Start
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Start Asynchronous Trigger for AI_AIR
|
||||
-- @function [parent=#AI_AIR] __Start
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay
|
||||
|
||||
self:AddTransition( "*", "Stop", "Stopped" )
|
||||
|
||||
--- OnLeave Transition Handler for State Stopped.
|
||||
-- @function [parent=#AI_AIR] OnLeaveStopped
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Stopped.
|
||||
-- @function [parent=#AI_AIR] OnEnterStopped
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- OnBefore Transition Handler for Event Stop.
|
||||
-- @function [parent=#AI_AIR] OnBeforeStop
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Stop.
|
||||
-- @function [parent=#AI_AIR] OnAfterStop
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Stop.
|
||||
-- @function [parent=#AI_AIR] Stop
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Stop.
|
||||
-- @function [parent=#AI_AIR] __Stop
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
|
||||
|
||||
--- OnBefore Transition Handler for Event Status.
|
||||
-- @function [parent=#AI_AIR] OnBeforeStatus
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Status.
|
||||
-- @function [parent=#AI_AIR] OnAfterStatus
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Status.
|
||||
-- @function [parent=#AI_AIR] Status
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Status.
|
||||
-- @function [parent=#AI_AIR] __Status
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "RTB", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
|
||||
|
||||
--- OnBefore Transition Handler for Event RTB.
|
||||
-- @function [parent=#AI_AIR] OnBeforeRTB
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event RTB.
|
||||
-- @function [parent=#AI_AIR] OnAfterRTB
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event RTB.
|
||||
-- @function [parent=#AI_AIR] RTB
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Asynchronous Event Trigger for Event RTB.
|
||||
-- @function [parent=#AI_AIR] __RTB
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Returning.
|
||||
-- @function [parent=#AI_AIR] OnLeaveReturning
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Returning.
|
||||
-- @function [parent=#AI_AIR] OnEnterReturning
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Patrolling", "Refuel", "Refuelling" )
|
||||
|
||||
--- Refuel Handler OnBefore for AI_AIR
|
||||
-- @function [parent=#AI_AIR] OnBeforeRefuel
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #boolean
|
||||
|
||||
--- Refuel Handler OnAfter for AI_AIR
|
||||
-- @function [parent=#AI_AIR] OnAfterRefuel
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Refuel Trigger for AI_AIR
|
||||
-- @function [parent=#AI_AIR] Refuel
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Refuel Asynchronous Trigger for AI_AIR
|
||||
-- @function [parent=#AI_AIR] __Refuel
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay
|
||||
|
||||
self:AddTransition( "*", "Takeoff", "Airborne" )
|
||||
self:AddTransition( "*", "Return", "Returning" )
|
||||
self:AddTransition( "*", "Hold", "Holding" )
|
||||
self:AddTransition( "*", "Home", "Home" )
|
||||
self:AddTransition( "*", "LostControl", "LostControl" )
|
||||
self:AddTransition( "*", "Fuel", "Fuel" )
|
||||
self:AddTransition( "*", "Damaged", "Damaged" )
|
||||
self:AddTransition( "*", "Eject", "*" )
|
||||
self:AddTransition( "*", "Crash", "Crashed" )
|
||||
self:AddTransition( "*", "PilotDead", "*" )
|
||||
|
||||
self.IdleCount = 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function GROUP:OnEventTakeoff( EventData, Fsm )
|
||||
Fsm:Takeoff()
|
||||
self:UnHandleEvent( EVENTS.Takeoff )
|
||||
end
|
||||
|
||||
|
||||
|
||||
function AI_AIR:SetDispatcher( Dispatcher )
|
||||
self.Dispatcher = Dispatcher
|
||||
end
|
||||
|
||||
function AI_AIR:GetDispatcher()
|
||||
return self.Dispatcher
|
||||
end
|
||||
|
||||
function AI_AIR:SetTargetDistance( Coordinate )
|
||||
|
||||
local CurrentCoord = self.Controllable:GetCoordinate()
|
||||
self.TargetDistance = CurrentCoord:Get2DDistance( Coordinate )
|
||||
|
||||
self.ClosestTargetDistance = ( not self.ClosestTargetDistance or self.ClosestTargetDistance > self.TargetDistance ) and self.TargetDistance or self.ClosestTargetDistance
|
||||
end
|
||||
|
||||
|
||||
function AI_AIR:ClearTargetDistance()
|
||||
|
||||
self.TargetDistance = nil
|
||||
self.ClosestTargetDistance = nil
|
||||
end
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum speed of the patrol.
|
||||
-- @param #AI_AIR self
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
|
||||
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
|
||||
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum RTB speed of the patrol.
|
||||
-- @param #AI_AIR self
|
||||
-- @param DCS#Speed RTBMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed RTBMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetRTBSpeed( RTBMinSpeed, RTBMaxSpeed )
|
||||
self:F( { RTBMinSpeed, RTBMaxSpeed } )
|
||||
|
||||
self.RTBMinSpeed = RTBMinSpeed
|
||||
self.RTBMaxSpeed = RTBMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
--- Sets the floor and ceiling altitude of the patrol.
|
||||
-- @param #AI_AIR self
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
|
||||
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
end
|
||||
|
||||
|
||||
--- Sets the home airbase.
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Airbase#AIRBASE HomeAirbase
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetHomeAirbase( HomeAirbase )
|
||||
self:F2( { HomeAirbase } )
|
||||
|
||||
self.HomeAirbase = HomeAirbase
|
||||
end
|
||||
|
||||
--- Sets to refuel at the given tanker.
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetTanker( TankerName )
|
||||
self:F2( { TankerName } )
|
||||
|
||||
self.TankerName = TankerName
|
||||
end
|
||||
|
||||
|
||||
--- Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number DisengageRadius The disengage range.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetDisengageRadius( DisengageRadius )
|
||||
self:F2( { DisengageRadius } )
|
||||
|
||||
self.DisengageRadius = DisengageRadius
|
||||
end
|
||||
|
||||
--- Set the status checking off.
|
||||
-- @param #AI_AIR self
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetStatusOff()
|
||||
self:F2()
|
||||
|
||||
self.CheckStatus = false
|
||||
end
|
||||
|
||||
|
||||
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number FuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
|
||||
|
||||
self.FuelThresholdPercentage = FuelThresholdPercentage
|
||||
self.OutOfFuelOrbitTime = OutOfFuelOrbitTime
|
||||
|
||||
self.Controllable:OptionRTBBingoFuel( false )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.
|
||||
-- However, damage cannot be foreseen early on.
|
||||
-- Therefore, when the damage treshold is reached,
|
||||
-- the AI will return immediately to the home base (RTB).
|
||||
-- Note that for groups, the average damage of the complete group will be calculated.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
|
||||
|
||||
self.PatrolManageDamage = true
|
||||
self.PatrolDamageThreshold = PatrolDamageThreshold
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
|
||||
-- @param #AI_AIR self
|
||||
-- @return #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR:onafterStart( Controllable, From, Event, To )
|
||||
|
||||
self:__Status( 10 ) -- Check status status every 30 seconds.
|
||||
|
||||
self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnCrash )
|
||||
self:HandleEvent( EVENTS.Ejection, self.OnEjection )
|
||||
|
||||
Controllable:OptionROEHoldFire()
|
||||
Controllable:OptionROTVertical()
|
||||
end
|
||||
|
||||
--- Coordinates the approriate returning action.
|
||||
-- @param #AI_AIR self
|
||||
-- @return #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR:onafterReturn( Controllable, From, Event, To )
|
||||
|
||||
self:__RTB( self.TaskDelay )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
function AI_AIR:onbeforeStatus()
|
||||
|
||||
return self.CheckStatus
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
function AI_AIR:onafterStatus()
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
local RTB = false
|
||||
|
||||
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
|
||||
|
||||
if not self:Is( "Holding" ) and not self:Is( "Returning" ) then
|
||||
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
|
||||
|
||||
if DistanceFromHomeBase > self.DisengageRadius then
|
||||
self:I( self.Controllable:GetName() .. " is too far from home base, RTB!" )
|
||||
self:Hold( 300 )
|
||||
RTB = false
|
||||
end
|
||||
end
|
||||
|
||||
-- I think this code is not requirement anymore after release 2.5.
|
||||
-- if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then
|
||||
-- if DistanceFromHomeBase < 5000 then
|
||||
-- self:E( self.Controllable:GetName() .. " is near the home base, RTB!" )
|
||||
-- self:Home( "Destroy" )
|
||||
-- end
|
||||
-- end
|
||||
|
||||
|
||||
if not self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" )then
|
||||
|
||||
local Fuel = self.Controllable:GetFuelMin()
|
||||
|
||||
-- If the fuel in the controllable is below the treshold percentage,
|
||||
-- then send for refuel in case of a tanker, otherwise RTB.
|
||||
if Fuel < self.FuelThresholdPercentage then
|
||||
|
||||
if self.TankerName then
|
||||
self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
|
||||
self:Refuel()
|
||||
else
|
||||
self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
|
||||
local OldAIControllable = self.Controllable
|
||||
|
||||
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
|
||||
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.OutOfFuelOrbitTime,nil ) )
|
||||
OldAIControllable:SetTask( TimedOrbitTask, 10 )
|
||||
|
||||
self:Fuel()
|
||||
RTB = true
|
||||
end
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
if self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" ) then
|
||||
RTB = true
|
||||
end
|
||||
|
||||
-- TODO: Check GROUP damage function.
|
||||
local Damage = self.Controllable:GetLife()
|
||||
local InitialLife = self.Controllable:GetLife0()
|
||||
|
||||
-- If the group is damaged, then RTB.
|
||||
-- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue.
|
||||
-- The damaged unit will RTB due to DCS logic, and the others will continue to engage.
|
||||
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
|
||||
self:I( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
|
||||
self:Damaged()
|
||||
RTB = true
|
||||
self:SetStatusOff()
|
||||
end
|
||||
|
||||
-- Check if planes went RTB and are out of control.
|
||||
-- We only check if planes are out of control, when they are in duty.
|
||||
if self.Controllable:HasTask() == false then
|
||||
if not self:Is( "Started" ) and
|
||||
not self:Is( "Stopped" ) and
|
||||
not self:Is( "Fuel" ) and
|
||||
not self:Is( "Damaged" ) and
|
||||
not self:Is( "Home" ) then
|
||||
if self.IdleCount >= 10 then
|
||||
if Damage ~= InitialLife then
|
||||
self:Damaged()
|
||||
else
|
||||
self:I( self.Controllable:GetName() .. " control lost! " )
|
||||
|
||||
self:LostControl()
|
||||
end
|
||||
else
|
||||
self.IdleCount = self.IdleCount + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
self.IdleCount = 0
|
||||
end
|
||||
|
||||
if RTB == true then
|
||||
self:__RTB( self.TaskDelay )
|
||||
end
|
||||
|
||||
if not self:Is("Home") then
|
||||
self:__Status( 10 )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR.RTBRoute( AIGroup, Fsm )
|
||||
|
||||
AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } )
|
||||
|
||||
if AIGroup:IsAlive() then
|
||||
Fsm:RTB()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR.RTBHold( AIGroup, Fsm )
|
||||
|
||||
AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } )
|
||||
if AIGroup:IsAlive() then
|
||||
Fsm:__RTB( Fsm.TaskDelay )
|
||||
Fsm:Return()
|
||||
local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
|
||||
AIGroup:SetTask( Task )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR:onafterRTB( AIGroup, From, Event, To )
|
||||
self:F( { AIGroup, From, Event, To } )
|
||||
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
|
||||
self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
|
||||
|
||||
self:ClearTargetDistance()
|
||||
--AIGroup:ClearTasks()
|
||||
|
||||
AIGroup:OptionProhibitAfterburner(true)
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromCoord = AIGroup:GetCoordinate()
|
||||
local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!)
|
||||
local ToTargetVec3 = ToTargetCoord:GetVec3()
|
||||
ToTargetVec3.y = ToTargetCoord:GetLandHeight()+1000 -- let's set this 1000m/3000 feet above ground
|
||||
local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 )
|
||||
|
||||
if not self.RTBMinSpeed or not self.RTBMaxSpeed then
|
||||
local RTBSpeedMax = AIGroup:GetSpeedMax()
|
||||
self:SetRTBSpeed( RTBSpeedMax * 0.5, RTBSpeedMax * 0.6 )
|
||||
end
|
||||
|
||||
local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
|
||||
--local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) )
|
||||
|
||||
local Distance = FromCoord:Get2DDistance( ToTargetCoord2 )
|
||||
|
||||
--local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
|
||||
local ToAirbaseCoord = ToTargetCoord2
|
||||
|
||||
if Distance < 5000 then
|
||||
self:I( "RTB and near the airbase!" )
|
||||
self:Home()
|
||||
return
|
||||
end
|
||||
|
||||
if not AIGroup:InAir() == true then
|
||||
self:I( "Not anymore in the air, considered Home." )
|
||||
self:Home()
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
--- Create a route point of type air.
|
||||
local FromRTBRoutePoint = FromCoord:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
RTBSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
RTBSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = FromRTBRoutePoint
|
||||
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_AIR.RTBRoute", self )
|
||||
|
||||
EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks )
|
||||
|
||||
AIGroup:OptionROEHoldFire()
|
||||
AIGroup:OptionROTEvadeFire()
|
||||
|
||||
--- NOW ROUTE THE GROUP!
|
||||
AIGroup:Route( EngageRoute, self.TaskDelay )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR:onafterHome( AIGroup, From, Event, To )
|
||||
self:F( { AIGroup, From, Event, To } )
|
||||
|
||||
self:I( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime )
|
||||
self:F( { AIGroup, From, Event, To } )
|
||||
|
||||
self:I( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
|
||||
local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) )
|
||||
|
||||
local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self )
|
||||
|
||||
local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
|
||||
|
||||
--AIGroup:SetState( AIGroup, "AI_AIR", self )
|
||||
|
||||
AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR.Resume( AIGroup, Fsm )
|
||||
|
||||
AIGroup:I( { "AI_AIR.Resume:", AIGroup:GetName() } )
|
||||
if AIGroup:IsAlive() then
|
||||
Fsm:__RTB( Fsm.TaskDelay )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR:onafterRefuel( AIGroup, From, Event, To )
|
||||
self:F( { AIGroup, From, Event, To } )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
|
||||
-- Get tanker group.
|
||||
local Tanker = GROUP:FindByName( self.TankerName )
|
||||
|
||||
if Tanker and Tanker:IsAlive() and Tanker:IsAirPlane() then
|
||||
|
||||
self:I( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName )
|
||||
|
||||
local RefuelRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromRefuelCoord = AIGroup:GetCoordinate()
|
||||
local ToRefuelCoord = Tanker:GetCoordinate()
|
||||
local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true)
|
||||
|
||||
--- Create a route point of type air. NOT used!
|
||||
local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true)
|
||||
|
||||
self:F( { ToRefuelSpeed = ToRefuelSpeed } )
|
||||
|
||||
RefuelRoute[#RefuelRoute+1] = FromRefuelRoutePoint
|
||||
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
|
||||
|
||||
AIGroup:OptionROEHoldFire()
|
||||
AIGroup:OptionROTEvadeFire()
|
||||
|
||||
-- Get Class name for .Resume function
|
||||
local classname=self:GetClassName()
|
||||
|
||||
-- AI_A2A_CAP can call this function but does not have a .Resume function. Try to fix.
|
||||
if classname=="AI_A2A_CAP" then
|
||||
classname="AI_AIR_PATROL"
|
||||
end
|
||||
|
||||
env.info("FF refueling classname="..classname)
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = AIGroup:TaskRefueling()
|
||||
Tasks[#Tasks+1] = AIGroup:TaskFunction( classname .. ".Resume", self )
|
||||
RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks )
|
||||
|
||||
AIGroup:Route( RefuelRoute, self.TaskDelay )
|
||||
|
||||
else
|
||||
|
||||
-- No tanker defined ==> RTB!
|
||||
self:RTB()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
function AI_AIR:onafterDead()
|
||||
self:SetStatusOff()
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR:OnCrash( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
if #self.Controllable:GetUnits() == 1 then
|
||||
self:__Crash( self.TaskDelay, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR:OnEjection( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
self:__Eject( self.TaskDelay, EventData )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR:OnPilotDead( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
self:__PilotDead( self.TaskDelay, EventData )
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,603 @@
|
||||
--- **AI** -- Models the process of air to ground engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air_Engage
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_AIR_ENGAGE
|
||||
-- @extends AI.AI_AIR#AI_AIR
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_AIR_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_ENGAGE process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_AIR_ENGAGE constructor
|
||||
--
|
||||
-- * @{#AI_AIR_ENGAGE.New}(): Creates a new AI_AIR_ENGAGE object.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_GCI#AI_AIR_ENGAGE.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_AIR_ENGAGE.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_AIR_ENGAGE
|
||||
AI_AIR_ENGAGE = {
|
||||
ClassName = "AI_AIR_ENGAGE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_AIR_ENGAGE object
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM.
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI group.
|
||||
-- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_AIR_ENGAGE
|
||||
function AI_AIR_ENGAGE:New( AI_Air, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_ENGAGE
|
||||
|
||||
self.Accomplished = false
|
||||
self.Engaging = false
|
||||
|
||||
local SpeedMax = AIGroup:GetSpeedMax()
|
||||
|
||||
self.EngageMinSpeed = EngageMinSpeed or SpeedMax * 0.5
|
||||
self.EngageMaxSpeed = EngageMaxSpeed or SpeedMax * 0.75
|
||||
self.EngageFloorAltitude = EngageFloorAltitude or 1000
|
||||
self.EngageCeilingAltitude = EngageCeilingAltitude or 1500
|
||||
self.EngageAltType = EngageAltType or "RADIO"
|
||||
|
||||
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "EngageRoute", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterEngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] EngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __EngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterEngage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Engage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Engage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeFired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterFired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Fired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Fired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeDestroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterDestroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Destroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Destroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeAbort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterAbort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Abort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Abort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeAccomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterAccomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Accomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Accomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- onafter event handler for Start event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterStart( AIGroup, From, Event, To )
|
||||
|
||||
self:GetParent( self, AI_AIR_ENGAGE ).onafterStart( self, AIGroup, From, Event, To )
|
||||
|
||||
AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- onafter event handler for Engage event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterEngage( AIGroup, From, Event, To )
|
||||
-- TODO: This function is overwritten below!
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
end
|
||||
|
||||
-- todo: need to fix this global function
|
||||
|
||||
|
||||
--- onbefore event handler for Engage event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onbeforeEngage( AIGroup, From, Event, To )
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- onafter event handler for Abort event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterAbort( AIGroup, From, Event, To )
|
||||
AIGroup:ClearTasks()
|
||||
self:Return()
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterAccomplish( AIGroup, From, Event, To )
|
||||
self.Accomplished = true
|
||||
--self:SetDetectionOff()
|
||||
end
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.AttackUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR_ENGAGE:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
|
||||
self:__Destroy( self.TaskDelay, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIControllable
|
||||
function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit )
|
||||
Fsm:I(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName())))
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
Fsm:__EngageRoute( Fsm.TaskDelay or 0.1, AttackSetUnit )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:I( { DefenderGroup, From, Event, To, AttackSetUnit } )
|
||||
|
||||
local DefenderGroupName = DefenderGroup:GetName()
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
if DefenderGroup:IsAlive() then
|
||||
|
||||
local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
|
||||
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
|
||||
|
||||
-- Determine the distance to the target.
|
||||
-- If it is less than 10km, then attack without a route.
|
||||
-- Otherwise perform a route attack.
|
||||
|
||||
local DefenderCoord = DefenderGroup:GetPointVec3()
|
||||
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
|
||||
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
|
||||
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
|
||||
|
||||
-- TODO: A factor of * 3 is way too close. This causes the AI not to engange until merged sometimes!
|
||||
if TargetDistance <= EngageDistance * 9 then
|
||||
|
||||
self:I(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000))
|
||||
self:__Engage( 0.1, AttackSetUnit )
|
||||
|
||||
else
|
||||
|
||||
self:I(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000))
|
||||
|
||||
local EngageRoute = {}
|
||||
local AttackTasks = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = FromWP
|
||||
|
||||
self:SetTargetDistance( TargetCoord ) -- For RTB status check
|
||||
|
||||
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
|
||||
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
|
||||
|
||||
local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToWP
|
||||
|
||||
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___EngageRoute", self, AttackSetUnit )
|
||||
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
|
||||
|
||||
DefenderGroup:OptionROEReturnFire()
|
||||
DefenderGroup:OptionROTEvadeFire()
|
||||
|
||||
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
|
||||
self:I( DefenderGroupName .. ": No targets found -> Going RTB")
|
||||
self:Return()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIControllable
|
||||
function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit )
|
||||
|
||||
Fsm:I(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName())))
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
local delay=Fsm.TaskDelay or 0.1
|
||||
Fsm:__Engage(delay, AttackSetUnit)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
|
||||
|
||||
local DefenderGroupName = DefenderGroup:GetName()
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
self:T({AttackCount = AttackCount})
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
if DefenderGroup and DefenderGroup:IsAlive() then
|
||||
|
||||
local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 )
|
||||
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
|
||||
|
||||
local DefenderCoord = DefenderGroup:GetPointVec3()
|
||||
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
|
||||
if not TargetCoord then
|
||||
self:Return()
|
||||
return
|
||||
end
|
||||
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
|
||||
|
||||
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
|
||||
|
||||
local EngageRoute = {}
|
||||
local AttackTasks = {}
|
||||
|
||||
local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
EngageRoute[#EngageRoute+1] = FromWP
|
||||
|
||||
self:SetTargetDistance( TargetCoord ) -- For RTB status check
|
||||
|
||||
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
|
||||
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
|
||||
|
||||
local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
EngageRoute[#EngageRoute+1] = ToWP
|
||||
|
||||
-- TODO: A factor of * 3 this way too low. This causes the AI NOT to engage until very close or even merged sometimes. Some A2A missiles have a much longer range! Needs more frequent updates of the task!
|
||||
if TargetDistance <= EngageDistance * 9 then
|
||||
|
||||
local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic
|
||||
|
||||
if #AttackUnitTasks == 0 then
|
||||
self:I( DefenderGroupName .. ": No valid targets found -> Going RTB")
|
||||
self:Return()
|
||||
return
|
||||
else
|
||||
local text=string.format("%s: Engaging targets at distance %.2f NM", DefenderGroupName, UTILS.MetersToNM(TargetDistance))
|
||||
self:I(text)
|
||||
DefenderGroup:OptionROEOpenFire()
|
||||
DefenderGroup:OptionROTEvadeFire()
|
||||
DefenderGroup:OptionKeepWeaponsOnThreat()
|
||||
|
||||
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks )
|
||||
end
|
||||
end
|
||||
|
||||
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___Engage", self, AttackSetUnit )
|
||||
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
|
||||
|
||||
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
|
||||
|
||||
end
|
||||
else
|
||||
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
|
||||
self:I( DefenderGroupName .. ": No targets found -> returning.")
|
||||
self:Return()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIEngage
|
||||
function AI_AIR_ENGAGE.Resume( AIEngage, Fsm )
|
||||
|
||||
AIEngage:F( { "Resume:", AIEngage:GetName() } )
|
||||
if AIEngage and AIEngage:IsAlive() then
|
||||
Fsm:__Reset( Fsm.TaskDelay or 0.1 )
|
||||
Fsm:__EngageRoute( Fsm.TaskDelay or 0.2, Fsm.AttackSetUnit )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,398 @@
|
||||
--- **AI** -- Models the process of A2G patrolling and engaging ground targets for airplanes and helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air_Patrol
|
||||
-- @image AI_Air_To_Ground_Patrol.JPG
|
||||
|
||||
--- @type AI_AIR_PATROL
|
||||
-- @extends AI.AI_Air#AI_AIR
|
||||
|
||||
|
||||
--- The AI_AIR_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
|
||||
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_AIR_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_PATROL process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_AIR_PATROL constructor
|
||||
--
|
||||
-- * @{#AI_AIR_PATROL.New}(): Creates a new AI_AIR_PATROL object.
|
||||
--
|
||||
-- ## 2. AI_AIR_PATROL is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1 AI_AIR_PATROL States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2 AI_AIR_PATROL Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.PatrolRoute}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_AIR_PATROL.Engage}**: Let the AI engage the bogeys.
|
||||
-- * **@{#AI_AIR_PATROL.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_CAP#AI_AIR_PATROL.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_AIR_PATROL.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_AIR_PATROL
|
||||
AI_AIR_PATROL = {
|
||||
ClassName = "AI_AIR_PATROL",
|
||||
}
|
||||
|
||||
--- Creates a new AI_AIR_PATROL object
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM.
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI group.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO.
|
||||
-- @return #AI_AIR_PATROL
|
||||
function AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_PATROL
|
||||
|
||||
local SpeedMax = AIGroup:GetSpeedMax()
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude or 1000
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude or 1500
|
||||
self.PatrolMinSpeed = PatrolMinSpeed or SpeedMax * 0.5
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed or SpeedMax * 0.75
|
||||
|
||||
-- defafult PatrolAltType to "RADIO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "RADIO"
|
||||
|
||||
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
|
||||
|
||||
--- OnBefore Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnBeforePatrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnAfterPatrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] Patrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] __Patrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnLeavePatrolling
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnEnterPatrolling
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Patrolling", "PatrolRoute", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL.
|
||||
|
||||
--- OnBefore Transition Handler for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnBeforePatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnAfterPatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] PatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] __PatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Engage Range when the AI will engage with airborne enemies.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number EngageRange The Engage Range.
|
||||
-- @return #AI_AIR_PATROL self
|
||||
function AI_AIR_PATROL:SetEngageRange( EngageRange )
|
||||
self:F2()
|
||||
|
||||
if EngageRange then
|
||||
self.EngageRange = EngageRange
|
||||
else
|
||||
self.EngageRange = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m.
|
||||
-- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m.
|
||||
-- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North.
|
||||
-- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North.
|
||||
-- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage.
|
||||
-- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage.
|
||||
-- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading.
|
||||
-- @return #AI_AIR_PATROL self
|
||||
function AI_AIR_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates)
|
||||
|
||||
self.racetrack=true
|
||||
self.racetracklegmin=LegMin or 10000
|
||||
self.racetracklegmax=LegMax or 15000
|
||||
self.racetrackheadingmin=HeadingMin or 0
|
||||
self.racetrackheadingmax=HeadingMax or 180
|
||||
self.racetrackdurationmin=DurationMin
|
||||
self.racetrackdurationmax=DurationMax
|
||||
|
||||
if self.racetrackdurationmax and not self.racetrackdurationmin then
|
||||
self.racetrackdurationmin=self.racetrackdurationmax
|
||||
end
|
||||
|
||||
self.racetrackcapcoordinates=CapCoordinates
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @return #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_PATROL:onafterPatrol( AIPatrol, From, Event, To )
|
||||
self:F2()
|
||||
|
||||
self:ClearTargetDistance()
|
||||
|
||||
self:__PatrolRoute( self.TaskDelay )
|
||||
|
||||
AIPatrol:OnReSpawn(
|
||||
function( PatrolGroup )
|
||||
self:__Reset( self.TaskDelay )
|
||||
self:__PatrolRoute( self.TaskDelay )
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
--- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
|
||||
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The AI group.
|
||||
-- @param #AI_AIR_PATROL Fsm The FSM.
|
||||
function AI_AIR_PATROL.___PatrolRoute( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:F( { "AI_AIR_PATROL.___PatrolRoute:", AIPatrol:GetName() } )
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
Fsm:PatrolRoute()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To )
|
||||
|
||||
self:F2()
|
||||
|
||||
-- When RTB, don't allow anymore the routing.
|
||||
if From == "RTB" then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local CurrentCoord = AIPatrol:GetCoordinate()
|
||||
|
||||
local altitude= math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude )
|
||||
|
||||
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
|
||||
ToTargetCoord:SetAlt( altitude )
|
||||
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
|
||||
|
||||
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
local speedkmh=ToTargetSpeed
|
||||
|
||||
local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true)
|
||||
PatrolRoute[#PatrolRoute+1] = FromWP
|
||||
|
||||
if self.racetrack then
|
||||
|
||||
-- Random heading.
|
||||
local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax)
|
||||
|
||||
-- Random leg length.
|
||||
local leg=math.random(self.racetracklegmin, self.racetracklegmax)
|
||||
|
||||
-- Random duration if any.
|
||||
local duration = self.racetrackdurationmin
|
||||
if self.racetrackdurationmax then
|
||||
duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax)
|
||||
end
|
||||
|
||||
-- CAP coordinate.
|
||||
local c0=self.PatrolZone:GetRandomCoordinate()
|
||||
if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then
|
||||
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
|
||||
end
|
||||
|
||||
-- Race track points.
|
||||
local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE
|
||||
local c2=c1:Translate(leg, heading):SetAltitude(altitude)
|
||||
|
||||
self:SetTargetDistance(c0) -- For RTB status check
|
||||
|
||||
-- Debug:
|
||||
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration)))
|
||||
--c1:MarkToAll("Race track c1")
|
||||
--c2:MarkToAll("Race track c2")
|
||||
|
||||
-- Task to orbit.
|
||||
local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2)
|
||||
|
||||
-- Task function to redo the patrol at other random position.
|
||||
local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self)
|
||||
|
||||
-- Controlled task with task condition.
|
||||
local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil)
|
||||
local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond)
|
||||
|
||||
-- Second waypoint
|
||||
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit")
|
||||
|
||||
else
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true)
|
||||
PatrolRoute[#PatrolRoute+1] = ToWP
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self)
|
||||
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
|
||||
|
||||
end
|
||||
|
||||
AIPatrol:OptionROEReturnFire()
|
||||
AIPatrol:OptionROTEvadeFire()
|
||||
|
||||
AIPatrol:Route( PatrolRoute, self.TaskDelay )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIPatrol
|
||||
function AI_AIR_PATROL.Resume( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:F( { "AI_AIR_PATROL.Resume:", AIPatrol:GetName() } )
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
Fsm:__Reset( Fsm.TaskDelay )
|
||||
Fsm:__PatrolRoute( Fsm.TaskDelay )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,289 @@
|
||||
--- **AI** - Models squadrons for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_Air_Dispatcher} and derived dispatcher classes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air_Squadron
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_AIR_SQUADRON
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Implements the core functions modeling squadrons for airplanes and helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_AIR_SQUADRON
|
||||
AI_AIR_SQUADRON = {
|
||||
ClassName = "AI_AIR_SQUADRON",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_AIR_SQUADRON object
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #AI_AIR_SQUADRON
|
||||
function AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount )
|
||||
|
||||
self:I( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } )
|
||||
|
||||
local AI_Air_Squadron = BASE:New() -- #AI_AIR_SQUADRON
|
||||
|
||||
AI_Air_Squadron.Name = SquadronName
|
||||
AI_Air_Squadron.Airbase = AIRBASE:FindByName( AirbaseName )
|
||||
AI_Air_Squadron.AirbaseName = AI_Air_Squadron.Airbase:GetName()
|
||||
if not AI_Air_Squadron.Airbase then
|
||||
error( "Cannot find airbase with name:" .. AirbaseName )
|
||||
end
|
||||
|
||||
AI_Air_Squadron.Spawn = {}
|
||||
if type( TemplatePrefixes ) == "string" then
|
||||
local SpawnTemplate = TemplatePrefixes
|
||||
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
|
||||
AI_Air_Squadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate]
|
||||
else
|
||||
for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do
|
||||
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
|
||||
AI_Air_Squadron.Spawn[#AI_Air_Squadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate]
|
||||
end
|
||||
end
|
||||
AI_Air_Squadron.ResourceCount = ResourceCount
|
||||
AI_Air_Squadron.TemplatePrefixes = TemplatePrefixes
|
||||
AI_Air_Squadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured.
|
||||
|
||||
self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default.
|
||||
|
||||
return AI_Air_Squadron
|
||||
end
|
||||
|
||||
--- Set the Name of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #string Name The Squadron Name.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetName( Name )
|
||||
|
||||
self.Name = Name
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Name of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #string The Squadron Name.
|
||||
function AI_AIR_SQUADRON:GetName()
|
||||
|
||||
return self.Name
|
||||
end
|
||||
|
||||
--- Set the ResourceCount of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number ResourceCount The Squadron ResourceCount.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetResourceCount( ResourceCount )
|
||||
|
||||
self.ResourceCount = ResourceCount
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the ResourceCount of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron ResourceCount.
|
||||
function AI_AIR_SQUADRON:GetResourceCount()
|
||||
|
||||
return self.ResourceCount
|
||||
end
|
||||
|
||||
--- Add Resources to the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Resources The Resources to be added.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:AddResources( Resources )
|
||||
|
||||
self.ResourceCount = self.ResourceCount + Resources
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove Resources to the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Resources The Resources to be removed.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:RemoveResources( Resources )
|
||||
|
||||
self.ResourceCount = self.ResourceCount - Resources
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Overhead of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Overhead The Squadron Overhead.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetOverhead( Overhead )
|
||||
|
||||
self.Overhead = Overhead
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Overhead of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Overhead.
|
||||
function AI_AIR_SQUADRON:GetOverhead()
|
||||
|
||||
return self.Overhead
|
||||
end
|
||||
|
||||
--- Set the Grouping of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Grouping The Squadron Grouping.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetGrouping( Grouping )
|
||||
|
||||
self.Grouping = Grouping
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Grouping of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Grouping.
|
||||
function AI_AIR_SQUADRON:GetGrouping()
|
||||
|
||||
return self.Grouping
|
||||
end
|
||||
|
||||
--- Set the FuelThreshold of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number FuelThreshold The Squadron FuelThreshold.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetFuelThreshold( FuelThreshold )
|
||||
|
||||
self.FuelThreshold = FuelThreshold
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the FuelThreshold of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron FuelThreshold.
|
||||
function AI_AIR_SQUADRON:GetFuelThreshold()
|
||||
|
||||
return self.FuelThreshold
|
||||
end
|
||||
|
||||
--- Set the EngageProbability of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number EngageProbability The Squadron EngageProbability.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetEngageProbability( EngageProbability )
|
||||
|
||||
self.EngageProbability = EngageProbability
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the EngageProbability of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron EngageProbability.
|
||||
function AI_AIR_SQUADRON:GetEngageProbability()
|
||||
|
||||
return self.EngageProbability
|
||||
end
|
||||
|
||||
--- Set the Takeoff of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Takeoff The Squadron Takeoff.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetTakeoff( Takeoff )
|
||||
|
||||
self.Takeoff = Takeoff
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Takeoff of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Takeoff.
|
||||
function AI_AIR_SQUADRON:GetTakeoff()
|
||||
|
||||
return self.Takeoff
|
||||
end
|
||||
|
||||
--- Set the Landing of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Landing The Squadron Landing.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetLanding( Landing )
|
||||
|
||||
self.Landing = Landing
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Landing of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Landing.
|
||||
function AI_AIR_SQUADRON:GetLanding()
|
||||
|
||||
return self.Landing
|
||||
end
|
||||
|
||||
--- Set the TankerName of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #string TankerName The Squadron Tanker Name.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetTankerName( TankerName )
|
||||
|
||||
self.TankerName = TankerName
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Tanker Name of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #string The Squadron Tanker Name.
|
||||
function AI_AIR_SQUADRON:GetTankerName()
|
||||
|
||||
return self.TankerName
|
||||
end
|
||||
|
||||
|
||||
--- Set the Radio of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number RadioFrequency The frequency of communication.
|
||||
-- @param #number RadioModulation The modulation of communication.
|
||||
-- @param #number RadioPower The power in Watts of communication.
|
||||
-- @param #string Language The language of the radio speech.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetRadio( RadioFrequency, RadioModulation, RadioPower, Language )
|
||||
|
||||
self.RadioFrequency = RadioFrequency
|
||||
self.RadioModulation = RadioModulation or radio.modulation.AM
|
||||
self.RadioPower = RadioPower or 100
|
||||
|
||||
if self.RadioSpeech then
|
||||
self.RadioSpeech:Stop()
|
||||
end
|
||||
|
||||
self.RadioSpeech = nil
|
||||
|
||||
self.RadioSpeech = RADIOSPEECH:New( RadioFrequency, RadioModulation )
|
||||
self.RadioSpeech.power = RadioPower
|
||||
self.RadioSpeech:Start( 0.5 )
|
||||
|
||||
self.RadioSpeech:SetLanguage( Language )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,647 @@
|
||||
--- **AI** -- Peform Battlefield Area Interdiction (BAI) within an engagement zone.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Hold and standby within a patrol zone.
|
||||
-- * Engage upon command the assigned targets within an engagement zone.
|
||||
-- * Loop the zone until all targets are eliminated.
|
||||
-- * Trigger different events upon the results achieved.
|
||||
-- * After combat, return to the patrol zone and hold.
|
||||
-- * RTB when commanded or after out of fuel.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/BAI%20-%20Battlefield%20Air%20Interdiction)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Bai
|
||||
-- @image AI_Battlefield_Air_Interdiction.JPG
|
||||
|
||||
|
||||
--- AI_BAI_ZONE class
|
||||
-- @type AI_BAI_ZONE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
|
||||
|
||||
--- Implements the core functions to provide BattleGround Air Interdiction in an Engage @{Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
|
||||
--
|
||||
-- The AI_BAI_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_BAI_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_BAI_ZONE process can be started through the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
|
||||
-- using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
-- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When the AI is commanded to provide BattleGround Air Interdiction (through the event **Engage**), the AI will fly towards the Engage Zone.
|
||||
-- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will detect the targets and will only destroy the targets within the Engage Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Every target that is destroyed, is reported< by the AI.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party:
|
||||
--
|
||||
-- * a FAC
|
||||
-- * a timed event
|
||||
-- * a menu option selected by a human
|
||||
-- * a condition
|
||||
-- * others ...
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When the AI has accomplished the Bombing, it will fly back to the Patrol Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone.
|
||||
-- It can be notified to go RTB through the **RTB** event.
|
||||
--
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # 1. AI_BAI_ZONE constructor
|
||||
--
|
||||
-- * @{#AI_BAI_ZONE.New}(): Creates a new AI_BAI_ZONE object.
|
||||
--
|
||||
-- ## 2. AI_BAI_ZONE is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_BAI_ZONE States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing BOMB.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2. AI_BAI_ZONE Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_BAI_ZONE.Engage}**: Engage the AI to provide BOMB in the Engage Zone, destroying any target it finds.
|
||||
-- * **@{#AI_BAI_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
|
||||
-- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object**
|
||||
--
|
||||
-- Use the method @{#AI_BAI_ZONE.SearchOff}() to specify that the EngageZone is not to be searched for potential targets (UNITs), but that the center of the zone
|
||||
-- is the point where a map object is to be destroyed (like a bridge).
|
||||
--
|
||||
-- Example:
|
||||
--
|
||||
-- -- Tell the BAI not to search for potential targets in the BAIEngagementZone, but rather use the center of the BAIEngagementZone as the bombing location.
|
||||
-- AIBAIZone:SearchOff()
|
||||
--
|
||||
-- Searching can be switched back on with the method @{#AI_BAI_ZONE.SearchOn}(). Use the method @{#AI_BAI_ZONE.SearchOnOff}() to flexibily switch searching on or off.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_BAI_ZONE
|
||||
AI_BAI_ZONE = {
|
||||
ClassName = "AI_BAI_ZONE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_BAI_ZONE object
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_BAI_ZONE self
|
||||
function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_BAI_ZONE
|
||||
|
||||
self.EngageZone = EngageZone
|
||||
self.Accomplished = false
|
||||
|
||||
self:SetDetectionZone( self.EngageZone )
|
||||
self:SearchOn()
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeEngage
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterEngage
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_BAI_ZONE] Engage
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
|
||||
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Engage
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
|
||||
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnLeaveEngaging
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnEnterEngaging
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeFired
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterFired
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_BAI_ZONE] Fired
|
||||
-- @param #AI_BAI_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Fired
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeDestroy
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterDestroy
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_BAI_ZONE] Destroy
|
||||
-- @param #AI_BAI_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Destroy
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeAbort
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterAbort
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_BAI_ZONE] Abort
|
||||
-- @param #AI_BAI_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Abort
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeAccomplish
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterAccomplish
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_BAI_ZONE] Accomplish
|
||||
-- @param #AI_BAI_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Accomplish
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Engage Zone where the AI is performing BOMB. Note that if the EngageZone is changed, the AI needs to re-detect targets.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing BOMB.
|
||||
-- @return #AI_BAI_ZONE self
|
||||
function AI_BAI_ZONE:SetEngageZone( EngageZone )
|
||||
self:F2()
|
||||
|
||||
if EngageZone then
|
||||
self.EngageZone = EngageZone
|
||||
else
|
||||
self.EngageZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Specifies whether to search for potential targets in the zone, or let the center of the zone be the bombing coordinate.
|
||||
-- AI_BAI_ZONE will search for potential targets by default.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @return #AI_BAI_ZONE
|
||||
function AI_BAI_ZONE:SearchOnOff( Search )
|
||||
|
||||
self.Search = Search
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- If Search is Off, the current zone coordinate will be the center of the bombing.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @return #AI_BAI_ZONE
|
||||
function AI_BAI_ZONE:SearchOff()
|
||||
|
||||
self:SearchOnOff( false )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- If Search is On, BAI will search for potential targets in the zone.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @return #AI_BAI_ZONE
|
||||
function AI_BAI_ZONE:SearchOn()
|
||||
|
||||
self:SearchOnOff( true )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- onafter State Transition for Event Start.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onafterStart( Controllable, From, Event, To )
|
||||
|
||||
-- Call the parent Start event handler
|
||||
self:GetParent(self).onafterStart( self, Controllable, From, Event, To )
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
|
||||
self:SetDetectionDeactivated() -- When not engaging, set the detection off.
|
||||
end
|
||||
|
||||
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
|
||||
function _NewEngageRoute( AIControllable )
|
||||
|
||||
AIControllable:T( "NewEngageRoute" )
|
||||
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_BAI#AI_BAI_ZONE
|
||||
EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection )
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onbeforeEngage( Controllable, From, Event, To )
|
||||
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onafterTarget( Controllable, From, Event, To )
|
||||
self:F({"onafterTarget",self.Search,Controllable:IsAlive()})
|
||||
|
||||
|
||||
|
||||
if Controllable:IsAlive() then
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
if self.Search == true then
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
if DetectedUnit:IsAlive() then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
if Detected == true then
|
||||
self:F( {"Target: ", DetectedUnit } )
|
||||
self.DetectedUnits[DetectedUnit] = false
|
||||
local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil )
|
||||
self.Controllable:PushTask( AttackTask, 1 )
|
||||
end
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
self:F("Attack zone")
|
||||
local AttackTask = Controllable:TaskAttackMapObject(
|
||||
self.EngageZone:GetPointVec2():GetVec2(),
|
||||
true,
|
||||
self.EngageWeaponExpend,
|
||||
self.EngageAttackQty,
|
||||
self.EngageDirection,
|
||||
self.EngageAltitude
|
||||
)
|
||||
self.Controllable:PushTask( AttackTask, 1 )
|
||||
end
|
||||
|
||||
self:__Target( -10 )
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onafterAbort( Controllable, From, Event, To )
|
||||
Controllable:ClearTasks()
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
EngageSpeed,
|
||||
EngageAltitude,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection )
|
||||
|
||||
self:F("onafterEngage")
|
||||
|
||||
self.EngageSpeed = EngageSpeed or 400
|
||||
self.EngageAltitude = EngageAltitude or 2000
|
||||
self.EngageWeaponExpend = EngageWeaponExpend
|
||||
self.EngageAttackQty = EngageAttackQty
|
||||
self.EngageDirection = EngageDirection
|
||||
|
||||
if Controllable:IsAlive() then
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
self.EngageSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = CurrentRoutePoint
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
if self.Search == true then
|
||||
|
||||
for DetectedUnitID, DetectedUnitData in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT
|
||||
self:T( DetectedUnit )
|
||||
if DetectedUnit:IsAlive() then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
self:F( {"Engaging ", DetectedUnit } )
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskBombing(
|
||||
DetectedUnit:GetPointVec2():GetVec2(),
|
||||
true,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection,
|
||||
EngageAltitude
|
||||
)
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
self:F("Attack zone")
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackMapObject(
|
||||
self.EngageZone:GetPointVec2():GetVec2(),
|
||||
true,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection,
|
||||
EngageAltitude
|
||||
)
|
||||
end
|
||||
|
||||
EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks )
|
||||
|
||||
--- Define a random point in the @{Zone}. The AI will fly to that point within the zone.
|
||||
|
||||
--- Find a random 2D point in EngageZone.
|
||||
local ToTargetVec2 = self.EngageZone:GetRandomVec2()
|
||||
self:T2( ToTargetVec2 )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
self.EngageSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToTargetRoutePoint
|
||||
|
||||
Controllable:OptionROEOpenFire()
|
||||
Controllable:OptionROTVertical()
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
|
||||
Controllable:WayPointInitialize( EngageRoute )
|
||||
|
||||
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
|
||||
Controllable:SetState( Controllable, "EngageZone", self )
|
||||
|
||||
Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageRoute" )
|
||||
|
||||
--- NOW ROUTE THE GROUP!
|
||||
Controllable:WayPointExecute( 1 )
|
||||
|
||||
self:SetRefreshTimeInterval( 2 )
|
||||
self:SetDetectionActivated()
|
||||
self:__Target( -2 ) -- Start Targetting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onafterAccomplish( Controllable, From, Event, To )
|
||||
self.Accomplished = true
|
||||
self:SetDetectionDeactivated()
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_BAI_ZONE:onafterDestroy( Controllable, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.DetectedUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_BAI_ZONE:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then
|
||||
self:__Destroy( 1, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
--- **AI** -- Balance player slots with AI to create an engaging simulation environment, independent of the amount of players.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Automatically spawn AI as a replacement of free player slots for a coalition.
|
||||
-- * Make the AI to perform tasks.
|
||||
-- * Define a maximum amount of AI to be active at the same time.
|
||||
-- * Configure the behaviour of AI when a human joins a slot for which an AI is active.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Balancer
|
||||
-- @image AI_Balancing.JPG
|
||||
|
||||
--- @type AI_BALANCER
|
||||
-- @field Core.Set#SET_CLIENT SetClient
|
||||
-- @field Core.Spawn#SPAWN SpawnAI
|
||||
-- @field Wrapper.Group#GROUP Test
|
||||
-- @extends Core.Fsm#FSM_SET
|
||||
|
||||
|
||||
--- Monitors and manages as many replacement AI groups as there are
|
||||
-- CLIENTS in a SET\_CLIENT collection, which are not occupied by human players.
|
||||
-- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
|
||||
--
|
||||
-- The parent class @{Core.Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
|
||||
-- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
|
||||
-- An explanation about state and event transition methods can be found in the @{FSM} module documentation.
|
||||
--
|
||||
-- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following:
|
||||
--
|
||||
-- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned.
|
||||
--
|
||||
-- ## 1. AI_BALANCER construction
|
||||
--
|
||||
-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method:
|
||||
--
|
||||
-- ## 2. AI_BALANCER is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_BALANCER States
|
||||
--
|
||||
-- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients.
|
||||
-- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference.
|
||||
-- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
|
||||
-- * **Destroying** ( Set, AIGroup ): The AI is being destroyed.
|
||||
-- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any.
|
||||
--
|
||||
-- ### 2.2. AI_BALANCER Events
|
||||
--
|
||||
-- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set.
|
||||
-- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference.
|
||||
-- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
|
||||
-- * **Destroy** ( Set, AIGroup ): The AI is being destroyed.
|
||||
-- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods.
|
||||
--
|
||||
-- ## 3. AI_BALANCER spawn interval for replacement AI
|
||||
--
|
||||
-- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned.
|
||||
--
|
||||
-- ## 4. AI_BALANCER returns AI to Airbases
|
||||
--
|
||||
-- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default.
|
||||
-- However, there are 2 additional options that you can use to customize the destroy behaviour.
|
||||
-- When a human player joins a slot, you can configure to let the AI return to:
|
||||
--
|
||||
-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Wrapper.Airbase#AIRBASE}.
|
||||
-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Wrapper.Airbase#AIRBASE}.
|
||||
--
|
||||
-- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return,
|
||||
-- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed.
|
||||
--
|
||||
-- @field #AI_BALANCER
|
||||
AI_BALANCER = {
|
||||
ClassName = "AI_BALANCER",
|
||||
PatrolZones = {},
|
||||
AIGroups = {},
|
||||
Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds.
|
||||
Latest = 60, -- Latest a new AI can be spawned is in 60 seconds.
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_BALANCER object
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
|
||||
-- @param Core.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
|
||||
-- @return #AI_BALANCER
|
||||
function AI_BALANCER:New( SetClient, SpawnAI )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER
|
||||
|
||||
-- TODO: Define the OnAfterSpawned event
|
||||
self:SetStartState( "None" )
|
||||
self:AddTransition( "*", "Monitor", "Monitoring" )
|
||||
self:AddTransition( "*", "Spawn", "Spawning" )
|
||||
self:AddTransition( "Spawning", "Spawned", "Spawned" )
|
||||
self:AddTransition( "*", "Destroy", "Destroying" )
|
||||
self:AddTransition( "*", "Return", "Returning" )
|
||||
|
||||
self.SetClient = SetClient
|
||||
self.SetClient:FilterOnce()
|
||||
self.SpawnAI = SpawnAI
|
||||
|
||||
self.SpawnQueue = {}
|
||||
|
||||
self.ToNearestAirbase = false
|
||||
self.ToHomeAirbase = false
|
||||
|
||||
self:__Monitor( 1 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI.
|
||||
-- Provide 2 identical seconds if the interval should be a fixed amount of seconds.
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param #number Earliest The earliest a new AI can be spawned in seconds.
|
||||
-- @param #number Latest The latest a new AI can be spawned in seconds.
|
||||
-- @return self
|
||||
function AI_BALANCER:InitSpawnInterval( Earliest, Latest )
|
||||
|
||||
self.Earliest = Earliest
|
||||
self.Latest = Latest
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}.
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}.
|
||||
-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Core.Set#SET_AIRBASE}s to evaluate where to return to.
|
||||
function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet )
|
||||
|
||||
self.ToNearestAirbase = true
|
||||
self.ReturnThresholdRange = ReturnThresholdRange
|
||||
self.ReturnAirbaseSet = ReturnAirbaseSet
|
||||
end
|
||||
|
||||
--- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}.
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}.
|
||||
function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange )
|
||||
|
||||
self.ToHomeAirbase = true
|
||||
self.ReturnThresholdRange = ReturnThresholdRange
|
||||
end
|
||||
|
||||
--- @param #AI_BALANCER self
|
||||
-- @param Core.Set#SET_GROUP SetGroup
|
||||
-- @param #string ClientName
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName )
|
||||
|
||||
-- OK, Spawn a new group from the default SpawnAI object provided.
|
||||
local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP
|
||||
if AIGroup then
|
||||
AIGroup:T( { "Spawning new AIGroup", ClientName = ClientName } )
|
||||
--TODO: need to rework UnitName thing ...
|
||||
|
||||
SetGroup:Remove( ClientName ) -- Ensure that the previously allocated AIGroup to ClientName is removed in the Set.
|
||||
SetGroup:Add( ClientName, AIGroup )
|
||||
self.SpawnQueue[ClientName] = nil
|
||||
|
||||
-- Fire the Spawned event. The first parameter is the AIGroup just Spawned.
|
||||
-- Mission designers can catch this event to bind further actions to the AIGroup.
|
||||
self:Spawned( AIGroup )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_BALANCER self
|
||||
-- @param Core.Set#SET_GROUP SetGroup
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup )
|
||||
|
||||
AIGroup:Destroy()
|
||||
SetGroup:Flush( self )
|
||||
SetGroup:Remove( ClientName )
|
||||
SetGroup:Flush( self )
|
||||
end
|
||||
|
||||
--- RTB
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param Core.Set#SET_GROUP SetGroup
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
|
||||
|
||||
local AIGroupTemplate = AIGroup:GetTemplate()
|
||||
if self.ToHomeAirbase == true then
|
||||
local WayPointCount = #AIGroupTemplate.route.points
|
||||
local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
|
||||
AIGroup:SetCommand( SwitchWayPointCommand )
|
||||
AIGroup:MessageToRed( "Returning to home base ...", 30 )
|
||||
else
|
||||
-- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
|
||||
--TODO: i need to rework the POINT_VEC2 thing.
|
||||
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
|
||||
local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
|
||||
self:T( ClosestAirbase.AirbaseName )
|
||||
--[[
|
||||
AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
|
||||
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
|
||||
AIGroupTemplate.route = RTBRoute
|
||||
AIGroup:Respawn( AIGroupTemplate )
|
||||
]]
|
||||
AIGroup:RouteRTB(ClosestAirbase)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BALANCER self
|
||||
function AI_BALANCER:onenterMonitoring( SetGroup )
|
||||
|
||||
self:T2( { self.SetClient:Count() } )
|
||||
--self.SetClient:Flush()
|
||||
|
||||
self.SetClient:ForEachClient(
|
||||
--- @param Wrapper.Client#CLIENT Client
|
||||
function( Client )
|
||||
self:T3(Client.ClientName)
|
||||
|
||||
local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP
|
||||
if AIGroup then self:T( { AIGroup = AIGroup:GetName(), IsAlive = AIGroup:IsAlive() } ) end
|
||||
if Client:IsAlive() == true then
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() == true then
|
||||
|
||||
if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
|
||||
self:Destroy( Client.UnitName, AIGroup )
|
||||
else
|
||||
-- We test if there is no other CLIENT within the self.ReturnThresholdRange of the first unit of the AI group.
|
||||
-- If there is a CLIENT, the AI stays engaged and will not return.
|
||||
-- If there is no CLIENT within the self.ReturnThresholdRange, then the unit will return to the Airbase return method selected.
|
||||
|
||||
local PlayerInRange = { Value = false }
|
||||
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnThresholdRange )
|
||||
|
||||
self:T2( RangeZone )
|
||||
|
||||
_DATABASE:ForEachPlayerUnit(
|
||||
--- @param Wrapper.Unit#UNIT RangeTestUnit
|
||||
function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
|
||||
self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
|
||||
if RangeTestUnit:IsInZone( RangeZone ) == true then
|
||||
self:T2( "in zone" )
|
||||
if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then
|
||||
self:T2( "in range" )
|
||||
PlayerInRange.Value = true
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
--- @param Core.Zone#ZONE_RADIUS RangeZone
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function( RangeZone, AIGroup, PlayerInRange )
|
||||
if PlayerInRange.Value == false then
|
||||
self:Return( AIGroup )
|
||||
end
|
||||
end
|
||||
, RangeZone, AIGroup, PlayerInRange
|
||||
)
|
||||
|
||||
end
|
||||
self.Set:Remove( Client.UnitName )
|
||||
end
|
||||
else
|
||||
if not AIGroup or not AIGroup:IsAlive() == true then
|
||||
self:T( "Client " .. Client.UnitName .. " not alive." )
|
||||
self:T( { Queue = self.SpawnQueue[Client.UnitName] } )
|
||||
if not self.SpawnQueue[Client.UnitName] then
|
||||
-- Spawn a new AI taking into account the spawn interval Earliest, Latest
|
||||
self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName )
|
||||
self.SpawnQueue[Client.UnitName] = true
|
||||
self:T( "New AI Spawned for Client " .. Client.UnitName )
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
)
|
||||
|
||||
self:__Monitor( 10 )
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,551 @@
|
||||
--- **AI** -- Perform Combat Air Patrolling (CAP) for airplanes.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel treshold to RTB on time.
|
||||
-- * Engage the enemy when detected.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAP%20-%20Combat%20Air%20Patrol)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1YCyPxJgoZn-CfhwyeW65L)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **[Quax](https://forums.eagle.ru/member.php?u=90530)**: Concept, Advice & Testing.
|
||||
-- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Concept, Advice & Testing.
|
||||
-- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision.
|
||||
-- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing.
|
||||
-- * **[Delta99](https://forums.eagle.ru/member.php?u=125166): Testing.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cap
|
||||
-- @image AI_Combat_Air_Patrol.JPG
|
||||
|
||||
|
||||
--- @type AI_CAP_ZONE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
|
||||
|
||||
|
||||
--- Implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}
|
||||
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_CAP_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_CAP_ZONE constructor
|
||||
--
|
||||
-- * @{#AI_CAP_ZONE.New}(): Creates a new AI_CAP_ZONE object.
|
||||
--
|
||||
-- ## 2. AI_CAP_ZONE is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1 AI_CAP_ZONE States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2 AI_CAP_ZONE Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_CAP_ZONE.Engage}**: Let the AI engage the bogeys.
|
||||
-- * **@{#AI_CAP_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_CAP#AI_CAP_ZONE.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_CAP_ZONE.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CAP_ZONE
|
||||
AI_CAP_ZONE = {
|
||||
ClassName = "AI_CAP_ZONE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_CAP_ZONE object
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_CAP_ZONE self
|
||||
function AI_CAP_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAP_ZONE
|
||||
|
||||
self.Accomplished = false
|
||||
self.Engaging = false
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeEngage
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterEngage
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_CAP_ZONE] Engage
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Engage
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnLeaveEngaging
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnEnterEngaging
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeFired
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterFired
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_CAP_ZONE] Fired
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Fired
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeDestroy
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterDestroy
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_CAP_ZONE] Destroy
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Destroy
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeAbort
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterAbort
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_CAP_ZONE] Abort
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Abort
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeAccomplish
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterAccomplish
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_CAP_ZONE] Accomplish
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Accomplish
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Engage Zone which defines where the AI will engage bogies.
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP.
|
||||
-- @return #AI_CAP_ZONE self
|
||||
function AI_CAP_ZONE:SetEngageZone( EngageZone )
|
||||
self:F2()
|
||||
|
||||
if EngageZone then
|
||||
self.EngageZone = EngageZone
|
||||
else
|
||||
self.EngageZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the Engage Range when the AI will engage with airborne enemies.
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number EngageRange The Engage Range.
|
||||
-- @return #AI_CAP_ZONE self
|
||||
function AI_CAP_ZONE:SetEngageRange( EngageRange )
|
||||
self:F2()
|
||||
|
||||
if EngageRange then
|
||||
self.EngageRange = EngageRange
|
||||
else
|
||||
self.EngageRange = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- onafter State Transition for Event Start.
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To )
|
||||
|
||||
-- Call the parent Start event handler
|
||||
self:GetParent(self).onafterStart( self, Controllable, From, Event, To )
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param AI.AI_CAP#AI_CAP_ZONE
|
||||
-- @param Wrapper.Group#GROUP EngageGroup
|
||||
function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm )
|
||||
|
||||
EngageGroup:F( { "AI_CAP_ZONE.EngageRoute:", EngageGroup:GetName() } )
|
||||
|
||||
if EngageGroup:IsAlive() then
|
||||
Fsm:__Engage( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onbeforeEngage( Controllable, From, Event, To )
|
||||
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterDetected( Controllable, From, Event, To )
|
||||
|
||||
if From ~= "Engaging" then
|
||||
|
||||
local Engage = false
|
||||
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
self:T( DetectedUnit )
|
||||
if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then
|
||||
Engage = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if Engage == true then
|
||||
self:F( 'Detected -> Engaging' )
|
||||
self:__Engage( 1 )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterAbort( Controllable, From, Event, To )
|
||||
Controllable:ClearTasks()
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
|
||||
|
||||
if Controllable and Controllable:IsAlive() then
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
if not CurrentVec2 then return self end
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToEngageZoneSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = CurrentRoutePoint
|
||||
|
||||
|
||||
--- Find a random 2D point in PatrolZone.
|
||||
local ToTargetVec2 = self.PatrolZone:GetRandomVec2()
|
||||
self:T2( ToTargetVec2 )
|
||||
|
||||
--- Define Speed and Altitude.
|
||||
local ToTargetAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
|
||||
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToTargetSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
|
||||
|
||||
Controllable:OptionROEOpenFire()
|
||||
Controllable:OptionROTEvadeFire()
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
self:T( { DetectedUnit, DetectedUnit:IsAlive(), DetectedUnit:IsAir() } )
|
||||
if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then
|
||||
if self.EngageZone then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
self:F( {"Within Zone and Engaging ", DetectedUnit } )
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
|
||||
end
|
||||
else
|
||||
if self.EngageRange then
|
||||
if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3() ) <= self.EngageRange then
|
||||
self:F( {"Within Range and Engaging", DetectedUnit } )
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
|
||||
end
|
||||
else
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
|
||||
end
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if #AttackTasks == 0 then
|
||||
self:F("No targets found -> Going back to Patrolling")
|
||||
self:__Abort( 1 )
|
||||
self:__Route( 1 )
|
||||
self:SetDetectionActivated()
|
||||
else
|
||||
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAP_ZONE.EngageRoute", self )
|
||||
EngageRoute[1].task = Controllable:TaskCombo( AttackTasks )
|
||||
|
||||
self:SetDetectionDeactivated()
|
||||
end
|
||||
|
||||
Controllable:Route( EngageRoute, 0.5 )
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterAccomplish( Controllable, From, Event, To )
|
||||
self.Accomplished = true
|
||||
self:SetDetectionOff()
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_CAP_ZONE:onafterDestroy( Controllable, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.DetectedUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_CAP_ZONE:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then
|
||||
self:__Destroy( 1, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,565 @@
|
||||
--- **AI** -- Perform Close Air Support (CAS) near friendlies.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Hold and standby within a patrol zone.
|
||||
-- * Engage upon command the enemies within an engagement zone.
|
||||
-- * Loop the zone until all enemies are eliminated.
|
||||
-- * Trigger different events upon the results achieved.
|
||||
-- * After combat, return to the patrol zone and hold.
|
||||
-- * RTB when commanded or after fuel.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAS%20-%20Close%20Air%20Support)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3JBO1WDqqpyYRRmIkR2ir2)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **[Quax](https://forums.eagle.ru/member.php?u=90530)**: Concept, Advice & Testing.
|
||||
-- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Concept, Advice & Testing.
|
||||
-- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cas
|
||||
-- @image AI_Close_Air_Support.JPG
|
||||
|
||||
--- AI_CAS_ZONE class
|
||||
-- @type AI_CAS_ZONE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
|
||||
|
||||
--- Implements the core functions to provide Close Air Support in an Engage @{Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
|
||||
-- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_CAS_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
|
||||
-- using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
-- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When the AI is commanded to provide Close Air Support (through the event **Engage**), the AI will fly towards the Engage Zone.
|
||||
-- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will detect the targets and will only destroy the targets within the Engage Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Every target that is destroyed, is reported< by the AI.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party:
|
||||
--
|
||||
-- * a FAC
|
||||
-- * a timed event
|
||||
-- * a menu option selected by a human
|
||||
-- * a condition
|
||||
-- * others ...
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When the AI has accomplished the CAS, it will fly back to the Patrol Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone.
|
||||
-- It can be notified to go RTB through the **RTB** event.
|
||||
--
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## AI_CAS_ZONE constructor
|
||||
--
|
||||
-- * @{#AI_CAS_ZONE.New}(): Creates a new AI_CAS_ZONE object.
|
||||
--
|
||||
-- ## AI_CAS_ZONE is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_CAS_ZONE States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing CAS.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2. AI_CAS_ZONE Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_CAS_ZONE.Engage}**: Engage the AI to provide CAS in the Engage Zone, destroying any target it finds.
|
||||
-- * **@{#AI_CAS_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
|
||||
-- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CAS_ZONE
|
||||
AI_CAS_ZONE = {
|
||||
ClassName = "AI_CAS_ZONE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_CAS_ZONE object
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_CAS_ZONE self
|
||||
function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAS_ZONE
|
||||
|
||||
self.EngageZone = EngageZone
|
||||
self.Accomplished = false
|
||||
|
||||
self:SetDetectionZone( self.EngageZone )
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeEngage
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterEngage
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_CAS_ZONE] Engage
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
|
||||
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Engage
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
|
||||
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnEnterEngaging
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeFired
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterFired
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_CAS_ZONE] Fired
|
||||
-- @param #AI_CAS_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Fired
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeDestroy
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterDestroy
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_CAS_ZONE] Destroy
|
||||
-- @param #AI_CAS_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Destroy
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeAbort
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterAbort
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_CAS_ZONE] Abort
|
||||
-- @param #AI_CAS_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Abort
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeAccomplish
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterAccomplish
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_CAS_ZONE] Accomplish
|
||||
-- @param #AI_CAS_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Accomplish
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Engage Zone where the AI is performing CAS. Note that if the EngageZone is changed, the AI needs to re-detect targets.
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAS.
|
||||
-- @return #AI_CAS_ZONE self
|
||||
function AI_CAS_ZONE:SetEngageZone( EngageZone )
|
||||
self:F2()
|
||||
|
||||
if EngageZone then
|
||||
self.EngageZone = EngageZone
|
||||
else
|
||||
self.EngageZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- onafter State Transition for Event Start.
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To )
|
||||
|
||||
-- Call the parent Start event handler
|
||||
self:GetParent(self).onafterStart( self, Controllable, From, Event, To )
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
|
||||
self:SetDetectionDeactivated() -- When not engaging, set the detection off.
|
||||
end
|
||||
|
||||
--- @param AI.AI_CAS#AI_CAS_ZONE
|
||||
-- @param Wrapper.Group#GROUP EngageGroup
|
||||
function AI_CAS_ZONE.EngageRoute( EngageGroup, Fsm )
|
||||
|
||||
EngageGroup:F( { "AI_CAS_ZONE.EngageRoute:", EngageGroup:GetName() } )
|
||||
|
||||
if EngageGroup:IsAlive() then
|
||||
Fsm:__Engage( 1, Fsm.EngageSpeed, Fsm.EngageAltitude, Fsm.EngageWeaponExpend, Fsm.EngageAttackQty, Fsm.EngageDirection )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onbeforeEngage( Controllable, From, Event, To )
|
||||
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To )
|
||||
|
||||
if Controllable:IsAlive() then
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
if DetectedUnit:IsAlive() then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
if Detected == true then
|
||||
self:F( {"Target: ", DetectedUnit } )
|
||||
self.DetectedUnits[DetectedUnit] = false
|
||||
local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil )
|
||||
self.Controllable:PushTask( AttackTask, 1 )
|
||||
end
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
self:__Target( -10 )
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onafterAbort( Controllable, From, Event, To )
|
||||
Controllable:ClearTasks()
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
EngageSpeed,
|
||||
EngageAltitude,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection )
|
||||
self:F("onafterEngage")
|
||||
|
||||
self.EngageSpeed = EngageSpeed or 400
|
||||
self.EngageAltitude = EngageAltitude or 2000
|
||||
self.EngageWeaponExpend = EngageWeaponExpend
|
||||
self.EngageAttackQty = EngageAttackQty
|
||||
self.EngageDirection = EngageDirection
|
||||
|
||||
if Controllable:IsAlive() then
|
||||
|
||||
Controllable:OptionROEOpenFire()
|
||||
Controllable:OptionROTVertical()
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
self.EngageSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = CurrentRoutePoint
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
self:T( DetectedUnit )
|
||||
if DetectedUnit:IsAlive() then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
self:F( {"Engaging ", DetectedUnit } )
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit,
|
||||
true,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection
|
||||
)
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAS_ZONE.EngageRoute", self )
|
||||
EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks )
|
||||
|
||||
--- Define a random point in the @{Zone}. The AI will fly to that point within the zone.
|
||||
|
||||
--- Find a random 2D point in EngageZone.
|
||||
local ToTargetVec2 = self.EngageZone:GetRandomVec2()
|
||||
self:T2( ToTargetVec2 )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
self.EngageSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToTargetRoutePoint
|
||||
|
||||
Controllable:Route( EngageRoute, 0.5 )
|
||||
|
||||
self:SetRefreshTimeInterval( 2 )
|
||||
self:SetDetectionActivated()
|
||||
self:__Target( -2 ) -- Start Targetting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onafterAccomplish( Controllable, From, Event, To )
|
||||
self.Accomplished = true
|
||||
self:SetDetectionDeactivated()
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_CAS_ZONE:onafterDestroy( Controllable, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.DetectedUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_CAS_ZONE:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then
|
||||
self:__Destroy( 1, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,584 @@
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo
|
||||
-- @image Cargo.JPG
|
||||
|
||||
--- @type AI_CARGO
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
|
||||
--- Base class for the dynamic cargo handling capability for AI groups.
|
||||
--
|
||||
-- Carriers can be mobilized to intelligently transport infantry and other cargo within the simulation.
|
||||
-- The AI_CARGO module uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- CARGO derived objects must be declared within the mission to make the AI_CARGO object recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- The derived classes from this module are:
|
||||
--
|
||||
-- * @{AI.AI_Cargo_APC} - Cargo transportation using APCs and other vehicles between zones.
|
||||
-- * @{AI.AI_Cargo_Helicopter} - Cargo transportation using helicopters between zones.
|
||||
-- * @{AI.AI_Cargo_Airplane} - Cargo transportation using airplanes to and from airbases.
|
||||
--
|
||||
-- @field #AI_CARGO
|
||||
AI_CARGO = {
|
||||
ClassName = "AI_CARGO",
|
||||
Coordinate = nil, -- Core.Point#COORDINATE,
|
||||
Carrier_Cargo = {},
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO object.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier Cargo carrier group.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport.
|
||||
-- @return #AI_CARGO self
|
||||
function AI_CARGO:New( Carrier, CargoSet )
|
||||
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO
|
||||
|
||||
self.CargoSet = CargoSet -- Core.Set#SET_CARGO
|
||||
self.CargoCarrier = Carrier -- Wrapper.Group#GROUP
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
|
||||
-- Board
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Reload", "*" )
|
||||
self:AddTransition( "*", "Board", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
-- Unload
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unboard", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnBeforePickup
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
-- @return #boolean
|
||||
|
||||
--- Pickup Handler OnAfter for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnAfterPickup
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Pickup Trigger for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] Pickup
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] __Pickup
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #number Delay
|
||||
-- @param Core.Point#COORDINATE Coordinate Pickup place. If not given, loading starts at the current location.
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Deploy Handler OnBefore for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnBeforeDeploy
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
-- @return #boolean
|
||||
|
||||
--- Deploy Handler OnAfter for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnAfterDeploy
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Deploy Trigger for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] Deploy
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] __Deploy
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #number Delay
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
|
||||
--- Loaded Handler OnAfter for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnAfterLoaded
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Unloaded Handler OnAfter for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnAfterUnloaded
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @function [parent=#AI_CARGO] OnAfterDeployed
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
-- @param #boolean Defend Defend for APCs.
|
||||
|
||||
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
CarrierUnit:SetCargoBayWeightLimit()
|
||||
end
|
||||
|
||||
self.Transporting = false
|
||||
self.Relocating = false
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
function AI_CARGO:IsTransporting()
|
||||
|
||||
return self.Transporting == true
|
||||
end
|
||||
|
||||
function AI_CARGO:IsRelocating()
|
||||
|
||||
return self.Relocating == true
|
||||
end
|
||||
|
||||
|
||||
--- On after Pickup event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
self.Transporting = false
|
||||
self.Relocating = true
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Deploy place.
|
||||
-- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
self.Relocating = false
|
||||
self.Transporting = true
|
||||
|
||||
end
|
||||
|
||||
--- On before Load event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone )
|
||||
self:F( { Carrier, From, Event, To } )
|
||||
|
||||
local Boarding = false
|
||||
|
||||
local LoadInterval = 2
|
||||
local LoadDelay = 1
|
||||
local Carrier_List = {}
|
||||
local Carrier_Weight = {}
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
self.Carrier_Cargo = {}
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
|
||||
local CargoBayFreeWeight = CarrierUnit:GetCargoBayFreeWeight()
|
||||
self:F({CargoBayFreeWeight=CargoBayFreeWeight})
|
||||
|
||||
Carrier_List[#Carrier_List+1] = CarrierUnit
|
||||
Carrier_Weight[CarrierUnit] = CargoBayFreeWeight
|
||||
end
|
||||
|
||||
local Carrier_Count = #Carrier_List
|
||||
local Carrier_Index = 1
|
||||
|
||||
local Loaded = false
|
||||
|
||||
for _, Cargo in UTILS.spairs( self.CargoSet:GetSet(), function( t, a, b ) return t[a]:GetWeight() > t[b]:GetWeight() end ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
|
||||
self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } )
|
||||
|
||||
-- Try all Carriers, but start from the one according the Carrier_Index
|
||||
for Carrier_Loop = 1, #Carrier_List do
|
||||
|
||||
local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT
|
||||
|
||||
-- This counters loop through the available Carriers.
|
||||
Carrier_Index = Carrier_Index + 1
|
||||
if Carrier_Index > Carrier_Count then
|
||||
Carrier_Index = 1
|
||||
end
|
||||
|
||||
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
|
||||
if Cargo:IsInLoadRadius( CarrierUnit:GetCoordinate() ) then
|
||||
self:F( { "In radius", CarrierUnit:GetName() } )
|
||||
|
||||
local CargoWeight = Cargo:GetWeight()
|
||||
local CarrierSpace=Carrier_Weight[CarrierUnit]
|
||||
|
||||
-- Only when there is space within the bay to load the next cargo item!
|
||||
if CarrierSpace > CargoWeight then
|
||||
Carrier:RouteStop()
|
||||
--Cargo:Ungroup()
|
||||
Cargo:__Board( -LoadDelay, CarrierUnit )
|
||||
self:__Board( LoadDelay, Cargo, CarrierUnit, PickupZone )
|
||||
|
||||
LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval
|
||||
|
||||
-- So now this CarrierUnit has Cargo that is being loaded.
|
||||
-- This will be used further in the logic to follow and to check cargo status.
|
||||
self.Carrier_Cargo[Cargo] = CarrierUnit
|
||||
Boarding = true
|
||||
Carrier_Weight[CarrierUnit] = Carrier_Weight[CarrierUnit] - CargoWeight
|
||||
Loaded = true
|
||||
|
||||
-- Ok, we loaded a cargo, now we can stop the loop.
|
||||
break
|
||||
else
|
||||
self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space", tostring(CarrierUnit:GetName()), CargoWeight, CarrierSpace))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if not Loaded == true then
|
||||
-- No loading happened, so we need to pickup something else.
|
||||
self.Relocating = false
|
||||
end
|
||||
end
|
||||
|
||||
return Boarding
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On before Reload event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onbeforeReload( Carrier, From, Event, To )
|
||||
self:F( { Carrier, From, Event, To } )
|
||||
|
||||
local Boarding = false
|
||||
|
||||
local LoadInterval = 2
|
||||
local LoadDelay = 1
|
||||
local Carrier_List = {}
|
||||
local Carrier_Weight = {}
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
|
||||
Carrier_List[#Carrier_List+1] = CarrierUnit
|
||||
end
|
||||
|
||||
local Carrier_Count = #Carrier_List
|
||||
local Carrier_Index = 1
|
||||
|
||||
local Loaded = false
|
||||
|
||||
for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
|
||||
self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } )
|
||||
|
||||
-- Try all Carriers, but start from the one according the Carrier_Index
|
||||
for Carrier_Loop = 1, #Carrier_List do
|
||||
|
||||
local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT
|
||||
|
||||
-- This counters loop through the available Carriers.
|
||||
Carrier_Index = Carrier_Index + 1
|
||||
if Carrier_Index > Carrier_Count then
|
||||
Carrier_Index = 1
|
||||
end
|
||||
|
||||
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
|
||||
Carrier:RouteStop()
|
||||
Cargo:__Board( -LoadDelay, CarrierUnit )
|
||||
self:__Board( LoadDelay, Cargo, CarrierUnit )
|
||||
|
||||
LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval
|
||||
|
||||
-- So now this CarrierUnit has Cargo that is being loaded.
|
||||
-- This will be used further in the logic to follow and to check cargo status.
|
||||
self.Carrier_Cargo[Cargo] = CarrierUnit
|
||||
Boarding = true
|
||||
Loaded = true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if not Loaded == true then
|
||||
-- No loading happened, so we need to pickup something else.
|
||||
self.Relocating = false
|
||||
end
|
||||
end
|
||||
|
||||
return Boarding
|
||||
|
||||
end
|
||||
|
||||
--- On after Board event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param Wrapper.Unit#UNIT CarrierUnit
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onafterBoard( Carrier, From, Event, To, Cargo, CarrierUnit, PickupZone )
|
||||
self:F( { Carrier, From, Event, To, Cargo, CarrierUnit:GetName() } )
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } )
|
||||
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
|
||||
self:__Board( -10, Cargo, CarrierUnit, PickupZone )
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self:__Loaded( 0.1, Cargo, CarrierUnit, PickupZone )
|
||||
|
||||
end
|
||||
|
||||
--- On after Loaded event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @return #boolean Cargo loaded.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onafterLoaded( Carrier, From, Event, To, Cargo, PickupZone )
|
||||
self:F( { Carrier, From, Event, To } )
|
||||
|
||||
local Loaded = true
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), Carrier:GetName() } )
|
||||
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
|
||||
Loaded = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Loaded then
|
||||
self:__PickedUp( 0.1, PickupZone )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after PickedUp event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onafterPickedUp( Carrier, From, Event, To, PickupZone )
|
||||
self:F( { Carrier, From, Event, To } )
|
||||
|
||||
Carrier:RouteResume()
|
||||
|
||||
local HasCargo = false
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do
|
||||
HasCargo = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self.Relocating = false
|
||||
if HasCargo then
|
||||
self:F( "Transporting" )
|
||||
self.Transporting = true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- On after Unload event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO:onafterUnload( Carrier, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, DeployZone, Defend = Defend } )
|
||||
|
||||
local UnboardInterval = 5
|
||||
local UnboardDelay = 5
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
Carrier:RouteStop()
|
||||
for _, Cargo in pairs( CarrierUnit:GetCargo() ) do
|
||||
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
|
||||
if Cargo:IsLoaded() then
|
||||
Cargo:__UnBoard( UnboardDelay )
|
||||
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
|
||||
self:__Unboard( UnboardDelay, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
if not Defend == true then
|
||||
Cargo:SetDeployed( true )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after Unboard event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
if not Cargo:IsUnLoaded() then
|
||||
self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self:Unloaded( Cargo, CarrierUnit, DeployZone, Defend )
|
||||
|
||||
end
|
||||
|
||||
--- On after Unloaded event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
local AllUnloaded = true
|
||||
|
||||
--Cargo:Regroup()
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
local IsEmpty = CarrierUnit:IsCargoEmpty()
|
||||
self:I({ IsEmpty = IsEmpty })
|
||||
if not IsEmpty then
|
||||
AllUnloaded = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if AllUnloaded == true then
|
||||
if DeployZone == true then
|
||||
self.Carrier_Cargo = {}
|
||||
end
|
||||
self.CargoCarrier = Carrier
|
||||
end
|
||||
end
|
||||
|
||||
if AllUnloaded == true then
|
||||
self:__Deployed( 5, DeployZone, Defend )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
-- @param #boolean Defend Defend for APCs.
|
||||
function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
if not Defend == true then
|
||||
self.Transporting = false
|
||||
else
|
||||
self:F( "Defending" )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,602 @@
|
||||
--- **AI** - Models the intelligent transportation of cargo using ground vehicles.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_APC
|
||||
-- @image AI_Cargo_Dispatching_For_APC.JPG
|
||||
|
||||
--- @type AI_CARGO_APC
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI vehicle group.
|
||||
--
|
||||
-- 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 class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_APC object recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo loading.
|
||||
--
|
||||
-- The module will load automatically cargo when the APCs are within boarding or loading radius.
|
||||
-- The boarding or loading radius is specified when the cargo is created in the simulation, and therefore, this radius depends on the type of cargo
|
||||
-- and the specified boarding radius.
|
||||
--
|
||||
-- ## **Defending** the APCs when enemies nearby.
|
||||
--
|
||||
-- Cargo will defend the carrier with its available arms, and to avoid cargo being lost within the battlefield.
|
||||
--
|
||||
-- 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 radius needs to be specified in meters at the @{#AI_CARGO_APC.New}() method.
|
||||
-- This combat radius will trigger the unboarding of troops when enemies are within the combat radius around the APCs.
|
||||
-- During my tests, I've noticed that there is a balance between ensuring that the infantry is within sufficient hit radius (effectiveness) versus
|
||||
-- vulnerability of the infantry. It all depends on the kind of enemies that are expected to be encountered.
|
||||
-- A combat radius of 350 meters to 500 meters has been proven to be the most effective and efficient.
|
||||
--
|
||||
-- However, when the defense of the carrier, is not required, it must be switched off.
|
||||
-- This is done by disabling the defense of the carrier using the method @{#AI_CARGO_APC.SetCombatRadius}(), and providing a combat radius of 0 meters.
|
||||
-- It can be switched on later when required by reenabling the defense using the method and providing a combat radius larger than 0.
|
||||
--
|
||||
-- ## Infantry or cargo **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,
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_APC object.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC The carrier APC group.
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported.
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When the combat radius is 0, no defense will happen of the carrier.
|
||||
-- @return #AI_CARGO_APC
|
||||
function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( APC, CargoSet ) ) -- #AI_CARGO_APC
|
||||
|
||||
self:AddTransition( "*", "Monitor", "*" )
|
||||
self:AddTransition( "*", "Follow", "Following" )
|
||||
self:AddTransition( "*", "Guard", "Unloaded" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
self:AddTransition( "*", "Reload", "Boarding" )
|
||||
self:AddTransition( "*", "Deployed", "*" )
|
||||
self:AddTransition( "*", "PickedUp", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
self:SetCombatRadius( CombatRadius )
|
||||
|
||||
self:SetCarrier( APC )
|
||||
|
||||
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 )
|
||||
|
||||
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 radius. Unload the CargoCarrier.
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- CargoCarrier:HandleEvent( EVENTS.Hit )
|
||||
--
|
||||
-- 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 radius. 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
|
||||
|
||||
--- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Find a free Carrier within a radius.
|
||||
-- @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
|
||||
|
||||
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
|
||||
-- This is only valid for APCs and trucks etc, thus ground vehicles.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
|
||||
-- When the combat radius is 0, no defense will happen of the carrier.
|
||||
-- When the combat radius is not provided, no defense will happen!
|
||||
-- @return #AI_CARGO_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- Disembark the infantry when the carrier is under attack.
|
||||
-- AICargoAPC:SetCombatRadius( true )
|
||||
--
|
||||
-- -- Keep the cargo in the carrier when the carrier is under attack.
|
||||
-- AICargoAPC:SetCombatRadius( false )
|
||||
function AI_CARGO_APC:SetCombatRadius( CombatRadius )
|
||||
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
|
||||
return self
|
||||
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
|
||||
|
||||
|
||||
--- On after Monitor event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function AI_CARGO_APC:onafterMonitor( APC, From, Event, To )
|
||||
self:F( { APC, From, Event, To, IsTransporting = self:IsTransporting() } )
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
if APC and APC:IsAlive() then
|
||||
if self.CarrierCoordinate then
|
||||
if self:IsTransporting() == true then
|
||||
local Coordinate = APC:GetCoordinate()
|
||||
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
if self:Is( "Unloaded" ) then
|
||||
-- There are no enemies within combat radius. Reload the CargoCarrier.
|
||||
self:Reload()
|
||||
end
|
||||
else
|
||||
if self:Is( "Loaded" ) then
|
||||
-- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
|
||||
else
|
||||
if self:Is( "Unloaded" ) then
|
||||
--self:Follow()
|
||||
end
|
||||
self:F( "I am here" .. self:GetCurrentState() )
|
||||
if self:Is( "Following" ) then
|
||||
for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
|
||||
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
|
||||
|
||||
end
|
||||
self.CarrierCoordinate = APC:GetCoordinate()
|
||||
end
|
||||
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Follow event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
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 Cargo, APCUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
if Cargo:IsUnLoaded() then
|
||||
self:FollowToCarrier( self, APCUnit, Cargo )
|
||||
APCUnit:RouteResume()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Pickup task function. Triggers Load event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC sel `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param #number Speed Speed (not used).
|
||||
-- @param Core.Zone#ZONE PickupZone Pickup zone.
|
||||
function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
|
||||
|
||||
if APC:IsAlive() then
|
||||
self:Load( PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- Deploy task function. Triggers Unload event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC self `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param Core.Zone#ZONE DeployZone Deploy zone.
|
||||
function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
|
||||
|
||||
if APC:IsAlive() then
|
||||
self:Unload( DeployZone )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- On after Pickup event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if APC and APC:IsAlive() then
|
||||
|
||||
if Coordinate then
|
||||
self.RoutePickup = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.pickupOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone )
|
||||
|
||||
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, Coordinate, Speed, PickupZone )
|
||||
end
|
||||
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterPickup( self, APC, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Deploy place.
|
||||
-- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if APC and APC:IsAlive() then
|
||||
|
||||
self.RouteDeploy = true
|
||||
|
||||
-- Set speed in km/h.
|
||||
local speedmax=APC:GetSpeedMax()
|
||||
local _speed=Speed or speedmax*0.5
|
||||
_speed=math.min(_speed, speedmax)
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.deployOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
-- Task function
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
-- Last waypoint
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
|
||||
-- Set task function
|
||||
APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Route group
|
||||
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
|
||||
-- Call parent function.
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after Unloaded event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_APC:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterUnloaded( self, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
|
||||
-- If Defend == true then we need to scan for possible enemies within combat zone and engage only ground forces.
|
||||
if Defend == true then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if not self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
-- OK, enemies nearby, now find the enemies and attack them.
|
||||
local AttackUnits = self.Zone:GetScannedUnits() -- #list<DCS#Unit>
|
||||
local Move = {}
|
||||
local CargoGroup = Cargo.CargoObject -- Wrapper.Group#GROUP
|
||||
Move[#Move+1] = CargoGroup:GetCoordinate():WaypointGround( 70, "Custom" )
|
||||
for UnitId, AttackUnit in pairs( AttackUnits ) do
|
||||
local MooseUnit = UNIT:Find( AttackUnit )
|
||||
if MooseUnit:GetCoalition() ~= CargoGroup:GetCoalition() then
|
||||
Move[#Move+1] = MooseUnit:GetCoordinate():WaypointGround( 70, "Line abreast" )
|
||||
--MoveTo.Task = CargoGroup:TaskCombo( CargoGroup:TaskAttackUnit( MooseUnit, true ) )
|
||||
self:F( { MooseUnit = MooseUnit:GetName(), CargoGroup = CargoGroup:GetName() } )
|
||||
end
|
||||
end
|
||||
CargoGroup:RoutePush( Move, 0.1 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_APC:onafterDeployed( APC, From, Event, To, DeployZone, Defend )
|
||||
self:F( { APC, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
self:__Guard( 0.1 )
|
||||
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterDeployed( self, APC, From, Event, To, DeployZone, Defend )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Home event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate. This parameter is ignored for APCs.
|
||||
function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
|
||||
if APC and APC:IsAlive() ~= nil then
|
||||
|
||||
self.RouteHome = true
|
||||
|
||||
Speed = Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, Speed, "Line abreast", true )
|
||||
|
||||
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
|
||||
@@ -0,0 +1,505 @@
|
||||
--- **AI** - Models the intelligent transportation of cargo using airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Airplane
|
||||
-- @image AI_Cargo_Dispatching_For_Airplanes.JPG
|
||||
|
||||
--- @type AI_CARGO_AIRPLANE
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI airplane group.
|
||||
--
|
||||
-- Airplane carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation between airbases.
|
||||
--
|
||||
-- The AI_CARGO_AIRPLANE module uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission to make AI_CARGO_AIRPLANE recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_AIRPLANE.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate.
|
||||
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_AIRPLANE.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
|
||||
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
|
||||
--
|
||||
-- ## 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, 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 ...
|
||||
--
|
||||
--
|
||||
-- @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 Plane used for transportation of cargo.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Cargo set to be transported.
|
||||
-- @return #AI_CARGO_AIRPLANE
|
||||
function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Airplane, CargoSet ) ) -- #AI_CARGO_AIRPLANE
|
||||
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Home" , "*" )
|
||||
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforePickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up.
|
||||
-- @param #number Speed in km/h for travelling to pickup base.
|
||||
-- @return #boolean
|
||||
|
||||
--- Pickup Handler OnAfter for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
|
||||
--- Pickup Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] Pickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] __Pickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
|
||||
--- Deploy Handler OnBefore for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed.
|
||||
-- @param #number Speed Speed in km/h for travelling to deploy base.
|
||||
-- @return #boolean
|
||||
|
||||
--- Deploy Handler OnAfter for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
|
||||
--- Deploy Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] Deploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] __Deploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
|
||||
--- On after Loaded event, i.e. triggered when the cargo is inside the carrier.
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterLoaded
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeployed
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed.
|
||||
|
||||
-- Set carrier.
|
||||
self:SetCarrier( Airplane )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Carrier (controllable). Also initializes events for carrier and defines the coalition.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Transport plane.
|
||||
-- @return #AI_CARGO_AIRPLANE self
|
||||
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.Relocating = false
|
||||
AICargo:Landed( self.Airplane )
|
||||
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
|
||||
|
||||
--- On after "Landed" event. Called on engine shutdown and initiates the pickup mission or unloading event.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To )
|
||||
|
||||
self:F({Airplane, From, Event, To})
|
||||
|
||||
if Airplane and Airplane:IsAlive()~=nil then
|
||||
|
||||
-- Aircraft was sent to this airbase to pickup troops. Initiate loadling.
|
||||
if self.RoutePickup == true then
|
||||
self:Load( self.PickupZone )
|
||||
end
|
||||
|
||||
-- Aircraft was send to this airbase to deploy troops. Initiate unloading.
|
||||
if self.RouteDeploy == true then
|
||||
self:Unload()
|
||||
self.RouteDeploy = false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after "Pickup" event. Routes transport to pickup airbase.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if Airplane and Airplane:IsAlive() then
|
||||
|
||||
local airbasepickup=Coordinate:GetClosestAirbase()
|
||||
|
||||
self.PickupZone = PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName())
|
||||
|
||||
-- Get closest airbase of current position.
|
||||
local ClosestAirbase, DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase()
|
||||
|
||||
-- Two cases. Aircraft spawned in air or at an airbase.
|
||||
if Airplane:InAir() then
|
||||
self.Airbase=nil --> route will start in air
|
||||
else
|
||||
self.Airbase=ClosestAirbase
|
||||
end
|
||||
|
||||
-- Set pickup airbase.
|
||||
local Airbase = self.PickupZone:GetAirbase()
|
||||
|
||||
-- Distance from closest to pickup airbase ==> we need to know if we are already at the pickup airbase.
|
||||
local Dist = Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate())
|
||||
|
||||
if Airplane:InAir() or Dist>500 then
|
||||
|
||||
-- Route aircraft to pickup airbase.
|
||||
self:Route( Airplane, Airbase, Speed, Height )
|
||||
|
||||
-- Set airbase as starting point in the next Route() call.
|
||||
self.Airbase = Airbase
|
||||
|
||||
-- Aircraft is on a pickup mission.
|
||||
self.RoutePickup = true
|
||||
|
||||
else
|
||||
|
||||
-- We are already at the right airbase ==> Landed ==> triggers loading of troops. Is usually called at engine shutdown event.
|
||||
self.RoutePickup=true
|
||||
self:Landed()
|
||||
|
||||
end
|
||||
|
||||
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterPickup( self, Airplane, From, Event, To, Coordinate, Speed, Height, self.PickupZone )
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- On after Depoly event. Routes plane to the airbase where the troops are deployed.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if Airplane and Airplane:IsAlive()~=nil then
|
||||
|
||||
local Airbase = Coordinate:GetClosestAirbase()
|
||||
|
||||
if DeployZone then
|
||||
Airbase=DeployZone:GetAirbase()
|
||||
end
|
||||
|
||||
-- Activate uncontrolled airplane.
|
||||
if Airplane:IsAlive()==false then
|
||||
Airplane:SetCommand({id = 'Start', params = {}})
|
||||
end
|
||||
|
||||
-- Route to destination airbase.
|
||||
self:Route( Airplane, Airbase, Speed, Height )
|
||||
|
||||
-- Aircraft is on a depoly mission.
|
||||
self.RouteDeploy = true
|
||||
|
||||
-- Set destination airbase for next :Route() command.
|
||||
self.Airbase = Airbase
|
||||
|
||||
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterDeploy( self, Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Unload event. Cargo is beeing unloaded, i.e. the unboarding process is started.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone )
|
||||
|
||||
local UnboardInterval = 10
|
||||
local UnboardDelay = 10
|
||||
|
||||
if Airplane and Airplane:IsAlive() then
|
||||
for _, AirplaneUnit in pairs( Airplane:GetUnits() ) do
|
||||
local Cargos = AirplaneUnit:GetCargo()
|
||||
for CargoID, Cargo in pairs( Cargos ) do
|
||||
|
||||
local Angle = 180
|
||||
local CargoCarrierHeading = Airplane:GetHeading() -- Get Heading of object in degrees.
|
||||
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
|
||||
self:T( { CargoCarrierHeading, CargoDeployHeading } )
|
||||
local CargoDeployCoordinate = Airplane:GetPointVec2():Translate( 150, CargoDeployHeading )
|
||||
|
||||
Cargo:__UnBoard( UnboardDelay, CargoDeployCoordinate )
|
||||
UnboardDelay = UnboardDelay + UnboardInterval
|
||||
Cargo:SetDeployed( true )
|
||||
self:__Unboard( UnboardDelay, Cargo, AirplaneUnit, DeployZone )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Route the airplane from one airport or it's current position to another airbase.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed.
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase.
|
||||
-- @param #number Speed Speed in km/h. Default is 80% of max possible speed the group can do.
|
||||
-- @param #number Height Height in meters to move to the Airbase.
|
||||
-- @param #boolean Uncontrolled If true, spawn group in uncontrolled state.
|
||||
function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled )
|
||||
|
||||
if Airplane and Airplane:IsAlive() then
|
||||
|
||||
-- Set takeoff type.
|
||||
local Takeoff = SPAWN.Takeoff.Cold
|
||||
|
||||
-- Get template of group.
|
||||
local Template = Airplane:GetTemplate()
|
||||
|
||||
-- Nil check
|
||||
if Template==nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Waypoints of the route.
|
||||
local Points={}
|
||||
|
||||
-- To point.
|
||||
local AirbasePointVec2 = Airbase:GetPointVec2()
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase)
|
||||
|
||||
--ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
--ToWaypoint["speed_locked"] = true
|
||||
|
||||
|
||||
-- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned.
|
||||
if self.Airbase then
|
||||
|
||||
-- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine.
|
||||
Template.route.points[2] = ToWaypoint
|
||||
|
||||
-- Respawn group at the current airbase.
|
||||
Airplane:RespawnAtCurrentAirbase(Template, Takeoff, Uncontrolled)
|
||||
|
||||
else
|
||||
|
||||
-- From point.
|
||||
local GroupPoint = Airplane:GetVec2()
|
||||
local FromWaypoint = {}
|
||||
FromWaypoint.x = GroupPoint.x
|
||||
FromWaypoint.y = GroupPoint.y
|
||||
FromWaypoint.type = "Turning Point"
|
||||
FromWaypoint.action = "Turning Point"
|
||||
FromWaypoint.speed = Airplane:GetSpeedMax()*0.8
|
||||
|
||||
-- The two route points.
|
||||
Points[1] = FromWaypoint
|
||||
Points[2] = ToWaypoint
|
||||
|
||||
local PointVec3 = Airplane:GetPointVec3()
|
||||
Template.x = PointVec3.x
|
||||
Template.y = PointVec3.z
|
||||
|
||||
Template.route.points = Points
|
||||
|
||||
local GroupSpawned = Airplane:Respawn(Template)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Home event. Aircraft will be routed to their home base.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane The cargo plane.
|
||||
-- @param From From state.
|
||||
-- @param Event Event.
|
||||
-- @param To To State.
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place (not used).
|
||||
-- @param #number Speed Speed in km/h to fly to the home airbase (zone). Default is 80% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE HomeZone The home airbase (zone) where the plane should return to.
|
||||
function AI_CARGO_AIRPLANE:onafterHome(Airplane, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
if Airplane and Airplane:IsAlive() then
|
||||
|
||||
-- We are going home!
|
||||
self.RouteHome = true
|
||||
|
||||
-- Home Base.
|
||||
local HomeBase=HomeZone:GetAirbase()
|
||||
self.Airbase=HomeBase
|
||||
|
||||
-- Now route the airplane home
|
||||
self:Route( Airplane, HomeBase, Speed, Height )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,258 @@
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Quickly transport cargo to various deploy zones using ground vehicles (APCs, trucks ...).
|
||||
-- * Various @{Cargo.Cargo#CARGO} types can be transported. These are infantry groups and crates.
|
||||
-- * Define a list of deploy zones of various types to transport the cargo to.
|
||||
-- * The vehicles follow the roads to ensure the fastest possible cargo transportation over the ground.
|
||||
-- * Multiple vehicles can transport multiple cargo as one vehicle group.
|
||||
-- * Multiple vehicle groups can be enabled as one collaborating transportation process.
|
||||
-- * Infantry loaded as cargo, will unboard in case enemies are nearby and will help defending the vehicles.
|
||||
-- * Different ranges can be setup for enemy defenses.
|
||||
-- * Different options can be setup to tweak the cargo transporation behaviour.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- Test missions can be located on the main GITHUB site.
|
||||
--
|
||||
-- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/]
|
||||
-- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_APC
|
||||
-- @image AI_Cargo_Dispatching_For_APC.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_APC
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo transportation 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 is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_APC class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
|
||||
--
|
||||
-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful!
|
||||
--
|
||||
-- On top, the AI_CARGO_DISPATCHER_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_APC constructor.
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.New}(): Creates a new AI_CARGO_DISPATCHER_APC object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_APC is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 3) Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.SetPickupRadius}(): Sets or randomizes the pickup location for the APC around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
--
|
||||
-- # 4) Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.SetDeployRadius}(): Sets or randomizes the deploy location for the APC around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
--
|
||||
-- # 5) Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the APCs will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_APC.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the APCs will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @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 APCSet The set of @{Wrapper.Group#GROUP} objects of vehicles, trucks, APCs that will transport the cargo.
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the APCs.
|
||||
-- @param DCS#Distance CombatRadius The cargo will be unloaded from the APC and engage the enemy if the enemy is within CombatRadius range. The radius is in meters, the default value is 500 meters.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a vehicle squadron, moving infantry from pickup zones to deploy zones.
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherAPC = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargoInfantry, nil, SetDeployZones )
|
||||
-- AICargoDispatcherAPC:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet, CombatRadius )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_APC
|
||||
|
||||
self:SetDeploySpeed( 120, 70 )
|
||||
self:SetPickupSpeed( 120, 70 )
|
||||
self:SetPickupRadius( 0, 0 )
|
||||
self:SetDeployRadius( 0, 0 )
|
||||
|
||||
self:SetPickupHeight()
|
||||
self:SetDeployHeight()
|
||||
|
||||
self:SetCombatRadius( CombatRadius )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- AI cargo
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param Wrapper.Group#GROUP APC The APC carrier.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Cargo set.
|
||||
-- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object.
|
||||
function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet )
|
||||
|
||||
local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius)
|
||||
|
||||
aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation)
|
||||
aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation)
|
||||
|
||||
return aicargoapc
|
||||
end
|
||||
|
||||
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
|
||||
-- This is only valid for APCs and trucks etc, thus ground vehicles.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
|
||||
-- When the combat radius is 0 (default), no defense will happen of the carrier.
|
||||
-- When the combat radius is not provided, no defense will happen!
|
||||
-- @return #AI_CARGO_DISPATCHER_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- Disembark the infantry when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( 500 )
|
||||
--
|
||||
-- -- Keep the cargo in the carrier when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( 0 )
|
||||
function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -0,0 +1,164 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo using Planes.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * The airplanes will fly towards the pickup airbases to pickup the cargo.
|
||||
-- * The airplanes will fly towards the deploy airbases to deploy the cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- Test missions can be located on the main GITHUB site.
|
||||
--
|
||||
-- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/]
|
||||
-- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Airplane
|
||||
-- @image AI_Cargo_Dispatching_For_Airplanes.JPG
|
||||
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_AIRPLANE
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- 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 is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_AIRPLANE class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!**
|
||||
--
|
||||
-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful!
|
||||
--
|
||||
-- On top, the AI_CARGO_DISPATCHER_AIRPLANE class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo.
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_AIRPLANE constructor.
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_AIRPLANE.New}(): Creates a new AI_CARGO_DISPATCHER_AIRPLANE object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_AIRPLANE is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @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 AirplaneSet The set of @{Wrapper.Group#GROUP} objects of airplanes that will transport the cargo.
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Zone#SET_ZONE PickupZoneSet The set of zone airbases where the cargo has to be picked up.
|
||||
-- @param Core.Zone#SET_ZONE DeployZoneSet The set of zone airbases where the cargo is deployed. Choice for each cargo is random.
|
||||
-- @return #AI_CARGO_DISPATCHER_AIRPLANE self
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for an airplane squadron, moving infantry and vehicles from pickup airbases to deploy airbases.
|
||||
--
|
||||
-- local CargoInfantrySet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local AirplanesSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart()
|
||||
-- local PickupZoneSet = SET_ZONE:New()
|
||||
-- local DeployZoneSet = SET_ZONE:New()
|
||||
--
|
||||
-- PickupZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Gudauta ) )
|
||||
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Sochi_Adler ) )
|
||||
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Maykop_Khanskaya ) )
|
||||
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Mineralnye_Vody ) )
|
||||
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Vaziani ) )
|
||||
--
|
||||
-- AICargoDispatcherAirplanes = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplanesSet, CargoInfantrySet, PickupZoneSet, DeployZoneSet )
|
||||
-- AICargoDispatcherAirplanes:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_AIRPLANE:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE
|
||||
|
||||
self:SetPickupSpeed( 1200, 600 )
|
||||
self:SetDeploySpeed( 1200, 600 )
|
||||
|
||||
self:SetPickupRadius( 0, 0 )
|
||||
self:SetDeployRadius( 0, 0 )
|
||||
|
||||
self:SetPickupHeight( 8000, 6000 )
|
||||
self:SetDeployHeight( 8000, 6000 )
|
||||
|
||||
self:SetMonitorTimeInterval( 600 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_AIRPLANE:AICargo( Airplane, CargoSet )
|
||||
|
||||
return AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
|
||||
end
|
||||
@@ -0,0 +1,194 @@
|
||||
--- **AI** -- (2.4) - Models the intelligent transportation of infantry and other cargo using Helicopters.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * The helicopters will fly towards the pickup locations to pickup the cargo.
|
||||
-- * The helicopters will fly towards the deploy zones to deploy the cargo.
|
||||
-- * Precision deployment as well as randomized deployment within the deploy zones are possible.
|
||||
-- * Helicopters will orbit the deploy zones when there is no space for landing until the deploy zone is free.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- Test missions can be located on the main GITHUB site.
|
||||
--
|
||||
-- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/]
|
||||
-- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Helicopter
|
||||
-- @image AI_Cargo_Dispatching_For_Helicopters.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_HELICOPTER
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo handling capability for AI helicopter groups.
|
||||
--
|
||||
-- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation.
|
||||
--
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_HELICOPTER module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_HELICOPTER class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!**
|
||||
--
|
||||
-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful!
|
||||
--
|
||||
-- On top, the AI_CARGO_DISPATCHER_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 1. AI\_CARGO\_DISPATCHER\_HELICOPTER constructor.
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.New}(): Creates a new AI\_CARGO\_DISPATCHER\_HELICOPTER object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2. AI\_CARGO\_DISPATCHER\_HELICOPTER is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## 3. Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupRadius}(): Sets or randomizes the pickup location for the helicopter around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupHeight}(): Set the height or randomizes the height in meters to pickup the cargo.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## 4. Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployRadius}(): Sets or randomizes the deploy location for the helicopter around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployHeight}(): Set the height or randomizes the height in meters to deploy the cargo.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## 5. Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the Helicopters will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_HELICOPTER.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the helicopters will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @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 HelicopterSet The set of @{Wrapper.Group#GROUP} objects of helicopters that will transport the cargo.
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the Helicopters.
|
||||
-- @return #AI_CARGO_DISPATCHER_HELICOPTER
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a helicopter squadron, moving infantry from pickup zones to deploy zones.
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart()
|
||||
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones )
|
||||
-- AICargoDispatcherHelicopter:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER
|
||||
|
||||
self:SetPickupSpeed( 350, 150 )
|
||||
self:SetDeploySpeed( 350, 150 )
|
||||
|
||||
self:SetPickupRadius( 40, 12 )
|
||||
self:SetDeployRadius( 40, 12 )
|
||||
|
||||
self:SetPickupHeight( 500, 200 )
|
||||
self:SetDeployHeight( 500, 200 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet )
|
||||
|
||||
local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
dispatcher:SetLandingSpeedAndHeight(27, 6)
|
||||
return dispatcher
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
--- **AI** -- (2.5.1) - Models the intelligent transportation of infantry and other cargo using Ships
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Transport cargo to various deploy zones using naval vehicles.
|
||||
-- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates.
|
||||
-- * Define a deploy zone of various types to determine the destination of the cargo.
|
||||
-- * Ships will follow shipping lanes as defined in the Mission Editor.
|
||||
-- * Multiple ships can transport multiple cargo as a single group.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- NEED TO DO
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_SHIP
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo transportation capability for AI groups.
|
||||
--
|
||||
-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
|
||||
--
|
||||
-- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framwork.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_SHIP constructor.
|
||||
--
|
||||
-- * @{AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 3) Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
--
|
||||
-- # 4) Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
--
|
||||
-- # 5) Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CARGO_DISPATCHER_SHIP
|
||||
AI_CARGO_DISPATCHER_SHIP = {
|
||||
ClassName = "AI_CARGO_DISPATCHER_SHIP"
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
-- @param #AI_CARGO_DISPATCHER_SHIP self
|
||||
-- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_DISPATCHER_SHIP
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart()
|
||||
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
-- NEED MORE THOUGHT - ShippingLane is part of Warehouse.......
|
||||
-- local ShippingLane = GROUP:New():FilterPrefixes( "ShippingLane" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane )
|
||||
-- AICargoDispatcherShip:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) )
|
||||
|
||||
self:SetPickupSpeed( 60, 10 )
|
||||
self:SetDeploySpeed( 60, 10 )
|
||||
|
||||
self:SetPickupRadius( 500, 6000 )
|
||||
self:SetDeployRadius( 500, 6000 )
|
||||
|
||||
self:SetPickupHeight( 0, 0 )
|
||||
self:SetDeployHeight( 0, 0 )
|
||||
|
||||
self:SetShippingLane( ShippingLane )
|
||||
|
||||
self:SetMonitorTimeInterval( 600 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet )
|
||||
|
||||
return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane )
|
||||
end
|
||||
@@ -0,0 +1,656 @@
|
||||
--- **AI** - Models the intelligent transportation of cargo using helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Helicopter
|
||||
-- @image AI_Cargo_Dispatching_For_Helicopters.JPG
|
||||
|
||||
--- @type AI_CARGO_HELICOPTER
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI helicopter group.
|
||||
--
|
||||
-- Helicopter carriers can be mobilized to intelligently transport infantry and other cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_HELICOPTER object recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_HELICOPTER.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate.
|
||||
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_HELICOPTER.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
|
||||
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
|
||||
--
|
||||
-- ## Infantry health.
|
||||
--
|
||||
-- When infantry is unboarded from the helicopters, 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, 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 ...
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @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
|
||||
-- @return #AI_CARGO_HELICOPTER
|
||||
function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Helicopter, CargoSet ) ) -- #AI_CARGO_HELICOPTER
|
||||
|
||||
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
-- Boarding
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
-- Unboarding
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
self:AddTransition( "*", "Destroyed", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
-- RTB
|
||||
self:AddTransition( "*", "Home" , "*" )
|
||||
|
||||
--- 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
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object.
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Pickup Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to go to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- 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 Place at which cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @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
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deployed Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Deploy Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Home Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Home
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
--- Home Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Home
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
-- We need to capture the Crash events for the helicopters.
|
||||
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
|
||||
-- So, we need to unlock this when the helo is not anymore ...
|
||||
Helicopter:HandleEvent( EVENTS.Crash,
|
||||
function( Helicopter, EventData )
|
||||
AI_CARGO_QUEUE[Helicopter] = nil
|
||||
end
|
||||
)
|
||||
|
||||
-- We need to capture the Land events for the helicopters.
|
||||
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
|
||||
-- So, we need to unlock this when the helo has landed, which can be anywhere ...
|
||||
-- But only free the landing coordinate after 1 minute, to ensure that all helos have left.
|
||||
Helicopter:HandleEvent( EVENTS.Land,
|
||||
function( Helicopter, EventData )
|
||||
self:ScheduleOnce( 60,
|
||||
function( Helicopter )
|
||||
AI_CARGO_QUEUE[Helicopter] = nil
|
||||
end, Helicopter
|
||||
)
|
||||
end
|
||||
)
|
||||
|
||||
self:SetCarrier( Helicopter )
|
||||
|
||||
self.landingspeed = 15 -- kph
|
||||
self.landingheight = 5.5 -- meter
|
||||
|
||||
return self
|
||||
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:OnEventLand( EventData )
|
||||
AICargo:Landed()
|
||||
end
|
||||
|
||||
self.Coalition = self.Helicopter:GetCoalition()
|
||||
|
||||
self:SetControllable( Helicopter )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #number speed Landing speed in kph(!), e.g. 15
|
||||
-- @param #number height Landing height in meters(!), e.g. 5.5
|
||||
-- @return #AI_CARGO_HELICOPTER self
|
||||
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
|
||||
--
|
||||
-- BASE:TraceOn()
|
||||
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
|
||||
--
|
||||
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
|
||||
-- Adjust if necessary.
|
||||
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
|
||||
local _speed = speed or 15
|
||||
local _height = height or 5.5
|
||||
self.landingheight = _height
|
||||
self.landingspeed = _speed
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
self:F({From, Event, To})
|
||||
Helicopter:F( { Name = Helicopter:GetName() } )
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
|
||||
-- S_EVENT_LAND is directly called in two situations:
|
||||
-- 1 - When the helo lands normally on the ground.
|
||||
-- 2 - when the helo is hit and goes RTB or even when it is destroyed.
|
||||
-- For point 2, this is an issue, the infantry may not unload in this case!
|
||||
-- So we check if the helo is on the ground, and velocity< 15.
|
||||
-- Only then the infantry can unload (and load too, for consistency)!
|
||||
|
||||
self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
|
||||
if self.RoutePickup == true then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
--self:Load( Helicopter:GetPointVec2() )
|
||||
self:Load( self.PickupZone )
|
||||
self.RoutePickup = false
|
||||
end
|
||||
end
|
||||
|
||||
if self.RouteDeploy == true then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
self:Unload( self.DeployZone )
|
||||
self.RouteDeploy = false
|
||||
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:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone )
|
||||
self:F({From, Event, To, Coordinate, Speed, DeployZone})
|
||||
local HelicopterInZone = false
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() == true then
|
||||
|
||||
local Distance = Coordinate:DistanceFromPointVec2( Helicopter:GetCoordinate() )
|
||||
|
||||
if Distance > 2000 then
|
||||
self:__Queue( -10, Coordinate, Speed, DeployZone )
|
||||
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 landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
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 )
|
||||
|
||||
-- Keep the DeployZone, because when the helo has landed, we want to provide the DeployZone to the mission designer as part of the Unloaded event.
|
||||
self.DeployZone = DeployZone
|
||||
|
||||
else
|
||||
self:__Queue( -10, Coordinate, Speed, DeployZone )
|
||||
end
|
||||
end
|
||||
else
|
||||
AI_CARGO_QUEUE[Helicopter] = nil
|
||||
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 )
|
||||
self:F({From, Event, To, Coordinate})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
|
||||
local Route = {}
|
||||
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
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 ), 150, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
|
||||
Route[#Route].task = Helicopter:TaskCombo( Tasks )
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @return #boolean True if all cargo has been unloaded.
|
||||
function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone )
|
||||
self:F( { From, Event, To, DeployZone = DeployZone } )
|
||||
|
||||
self:Orbit( Helicopter:GetCoordinate(), 50 )
|
||||
|
||||
-- Free the coordinate zone after 30 seconds, so that the original helicopter can fly away first.
|
||||
self:ScheduleOnce( 30,
|
||||
function( Helicopter )
|
||||
AI_CARGO_QUEUE[Helicopter] = nil
|
||||
end, Helicopter
|
||||
)
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone )
|
||||
|
||||
end
|
||||
|
||||
--- On after Pickup event.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Pickup place.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
self:F({Coordinate, Speed, Height, PickupZone })
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
Helicopter:Activate()
|
||||
|
||||
self.RoutePickup = true
|
||||
Coordinate.y = Height
|
||||
|
||||
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
local Route = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
|
||||
--- Create a route point of type air.
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, 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.PickupZone = PickupZone
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterPickup( self, Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Depoloy function and queue.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP AICargoHelicopter
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate
|
||||
function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate, DeployZone )
|
||||
AICargoHelicopter:__Queue( -10, Coordinate, 100, DeployZone )
|
||||
end
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter Transport helicopter.
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate.
|
||||
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
self:F({From, Event, To, Coordinate, Speed, Height, DeployZone})
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteDeploy = true
|
||||
|
||||
|
||||
local Route = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
Coordinate.y = Height
|
||||
|
||||
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, 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 = {}
|
||||
|
||||
-- The _Deploy function does not exist.
|
||||
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, 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, 0 )
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeploy( self, Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Home event.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
self:F({From, Event, To, Coordinate, Speed, Height})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteHome = true
|
||||
|
||||
local Route = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
--Coordinate.y = Height
|
||||
Height = Height or 50
|
||||
|
||||
Speed = Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + Height -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, 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
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
--- **AI** -- (R2.5.1) - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_SHIP
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI naval group.
|
||||
--
|
||||
-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes.
|
||||
-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo loading.
|
||||
--
|
||||
-- The module will automatically load cargo when the Ship is within boarding or loading radius.
|
||||
-- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of
|
||||
-- cargo and the specified boarding radius.
|
||||
--
|
||||
-- ## Defending the Ship when enemies are nearby
|
||||
-- This is not supported for naval cargo because most tanks don't float. Protect your transports...
|
||||
--
|
||||
-- ## Infantry or cargo **health**.
|
||||
-- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding cargo 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 newly spawned units as a parameter.
|
||||
-- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for
|
||||
-- naval cargo due to the lack of support for defending the Ship mentioned above). 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. Given the relatively short duration of DCS missions and the somewhat
|
||||
-- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting
|
||||
-- between warehouses.
|
||||
--
|
||||
-- ## Control the Ships on the map.
|
||||
--
|
||||
-- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission
|
||||
-- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module.
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the
|
||||
-- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified
|
||||
-- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
--
|
||||
-- @field #AI_CARGO_SHIP
|
||||
AI_CARGO_SHIP = {
|
||||
ClassName = "AI_CARGO_SHIP",
|
||||
Coordinate = nil -- Core.Point#COORDINATE
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_SHIP object.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship The carrier Ship group
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP
|
||||
|
||||
self:AddTransition( "*", "Monitor", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
|
||||
self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0
|
||||
self:SetShippingLane ( ShippingLane )
|
||||
|
||||
self:SetCarrier( Ship )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:SetCarrier( CargoCarrier )
|
||||
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP
|
||||
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self )
|
||||
|
||||
CargoCarrier:HandleEvent( EVENTS.Dead )
|
||||
|
||||
function CargoCarrier:OnEventDead( EventData )
|
||||
self:F({"dead"})
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" )
|
||||
self:F({AICargoTroops=AICargoTroops})
|
||||
if AICargoTroops then
|
||||
self:F({})
|
||||
if not AICargoTroops:Is( "Loaded" ) then
|
||||
-- Better hope they can swim!
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
|
||||
self.Coalition = self.CargoCarrier:GetCoalition()
|
||||
|
||||
self:SetControllable( CargoCarrier )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- FInd a free Carrier within a radius
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Radius
|
||||
-- @return Wrapper.Group#GROUP NewCarrier
|
||||
function AI_CARGO_SHIP: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_SHIP" ) then
|
||||
local Attributes = NearUnit:GetDesc()
|
||||
self:F({Desc=Attributes})
|
||||
if NearUnit:HasAttributes( "Trucks" ) then
|
||||
return NearUnit:GetGroup()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetCombatRadius( CombatRadius )
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Follow Infantry to the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param #AI_CARGO_SHIP Me
|
||||
-- @param Wrapper.Unit#UNIT ShipUnit
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup )
|
||||
|
||||
local InfantryGroup = CargoGroup:GetGroup()
|
||||
|
||||
self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
|
||||
|
||||
if ShipUnit:IsAlive() then
|
||||
-- Check if the Cargo is near the CargoCarrier
|
||||
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then
|
||||
|
||||
-- Cargo does not need to navigate to Carrier
|
||||
Me:Guard()
|
||||
else
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
if InfantryGroup:IsAlive() then
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
local Waypoints = {}
|
||||
|
||||
-- Calculate new route
|
||||
local FromCoord = InfantryGroup:GetCoordinate()
|
||||
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({FromGround=FromGround})
|
||||
table.insert( Waypoints, FromGround )
|
||||
|
||||
local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
|
||||
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({ToGround=ToGround})
|
||||
table.insert( Waypoints, ToGround )
|
||||
|
||||
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, 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 number of seconds to the Route. See Route method for details
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To )
|
||||
self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } )
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
-- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0.
|
||||
-- This is to avoid Unloading the Ship in the middle of the sea.
|
||||
if Ship and Ship:IsAlive() then
|
||||
if self.CarrierCoordinate then
|
||||
if self:IsTransporting() == true then
|
||||
local Coordinate = Ship:GetCoordinate()
|
||||
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
if self:Is( "Unloaded" ) then
|
||||
-- There are no enemies within combat radius. Reload the CargoCarrier.
|
||||
self:Reload()
|
||||
end
|
||||
else
|
||||
if self:Is( "Loaded" ) then
|
||||
-- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
|
||||
else
|
||||
if self:Is( "Unloaded" ) then
|
||||
--self:Follow()
|
||||
end
|
||||
self:F( "I am here" .. self:GetCurrentState() )
|
||||
if self:Is( "Following" ) then
|
||||
for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
if Cargo:IsAlive() then
|
||||
if not Cargo:IsNear( ShipUnit, 40 ) then
|
||||
ShipUnit:RouteStop()
|
||||
self.CarrierStopped = true
|
||||
else
|
||||
if self.CarrierStopped then
|
||||
if Cargo:IsNear( ShipUnit, 25 ) then
|
||||
ShipUnit:RouteResume()
|
||||
self.CarrierStopped = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.CarrierCoordinate = Ship:GetCoordinate()
|
||||
end
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Load event
|
||||
-- @param Wrapper.Group#Group Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
|
||||
Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Load( PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone )
|
||||
Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Unload( DeployZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- on after Pickup event.
|
||||
-- @param AI_CARGO_SHIP Ship
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point
|
||||
-- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided
|
||||
function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP SHIP
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point
|
||||
-- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
for i=1, #lane do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone )
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:SetTaskWaypoint( Waypoint, TaskFunction )
|
||||
Ship:Route(Waypoints, 1)
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Unload event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } )
|
||||
|
||||
local UnboardInterval = 5
|
||||
local UnboardDelay = 5
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
for _, ShipUnit in pairs( Ship:GetUnits() ) do
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
Ship:RouteStop()
|
||||
for _, Cargo in pairs( ShipUnit:GetCargo() ) do
|
||||
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
|
||||
if Cargo:IsLoaded() then
|
||||
local unboardCoord = DeployZone:GetRandomPointVec2()
|
||||
Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000)
|
||||
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
|
||||
self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend )
|
||||
if not Defend == true then
|
||||
Cargo:SetDeployed( true )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
self.RouteHome = true
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
-- Need to find a more generalized way to do this instead of reversing the shipping lane.
|
||||
-- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1
|
||||
for i=#lane, 1, -1 do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:Route(Waypoints, 1)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,185 @@
|
||||
--- **AI** - Models the automatic assignment of AI escorts to player flights.
|
||||
--
|
||||
-- ## Features:
|
||||
-- --
|
||||
-- * Provides the facilities to trigger escorts when players join flight slots.
|
||||
-- *
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Escort_Dispatcher
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
--- @type AI_ESCORT_DISPATCHER
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models the automatic assignment of AI escorts to player flights.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_ESCORT_DISPATCHER
|
||||
AI_ESCORT_DISPATCHER = {
|
||||
ClassName = "AI_ESCORT_DISPATCHER",
|
||||
}
|
||||
|
||||
--- @field #list
|
||||
AI_ESCORT_DISPATCHER.AI_Escorts = {}
|
||||
|
||||
|
||||
--- Creates a new AI_ESCORT_DISPATCHER object.
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are spawned in.
|
||||
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts.
|
||||
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned.
|
||||
-- @param #string EscortName Name of the escort, which will also be the name of the escort menu.
|
||||
-- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #AI_ESCORT_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Create a new escort when a player joins an SU-25T plane.
|
||||
-- Create a carrier set, which contains the player slots that can be joined by the players, for which escorts will be defined.
|
||||
-- local Red_SU25T_CarrierSet = SET_GROUP:New():FilterPrefixes( "Red A2G Player Su-25T" ):FilterStart()
|
||||
--
|
||||
-- -- Create a spawn object that will spawn in the escorts, once the player has joined the player slot.
|
||||
-- local Red_SU25T_EscortSpawn = SPAWN:NewWithAlias( "Red A2G Su-25 Escort", "Red AI A2G SU-25 Escort" ):InitLimit( 10, 10 )
|
||||
--
|
||||
-- -- Create an airbase object, where the escorts will be spawned.
|
||||
-- local Red_SU25T_Airbase = AIRBASE:FindByName( AIRBASE.Caucasus.Maykop_Khanskaya )
|
||||
--
|
||||
-- -- Park the airplanes at the airbase, visible before start.
|
||||
-- Red_SU25T_EscortSpawn:ParkAtAirbase( Red_SU25T_Airbase, AIRBASE.TerminalType.OpenMedOrBig )
|
||||
--
|
||||
-- -- New create the escort dispatcher, using the carrier set, the escort spawn object at the escort airbase.
|
||||
-- -- Provide a name of the escort, which will be also the name appearing on the radio menu for the group.
|
||||
-- -- And a briefing to appear when the player joins the player slot.
|
||||
-- Red_SU25T_EscortDispatcher = AI_ESCORT_DISPATCHER:New( Red_SU25T_CarrierSet, Red_SU25T_EscortSpawn, Red_SU25T_Airbase, "Escort Su-25", "You Su-25T is escorted by one Su-25. Use the radio menu to control the escorts." )
|
||||
--
|
||||
-- -- The dispatcher needs to be started using the :Start() method.
|
||||
-- Red_SU25T_EscortDispatcher:Start()
|
||||
function AI_ESCORT_DISPATCHER:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER
|
||||
|
||||
self.CarrierSet = CarrierSet
|
||||
self.EscortSpawn = EscortSpawn
|
||||
self.EscortAirbase = EscortAirbase
|
||||
self.EscortName = EscortName
|
||||
self.EscortBriefing = EscortBriefing
|
||||
|
||||
self:SetStartState( "Idle" )
|
||||
|
||||
self:AddTransition( "Monitoring", "Monitor", "Monitoring" )
|
||||
|
||||
self:AddTransition( "Idle", "Start", "Monitoring" )
|
||||
self:AddTransition( "Monitoring", "Stop", "Idle" )
|
||||
|
||||
-- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset.
|
||||
function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier )
|
||||
self:F( { Carrier = Carrier:GetName() } )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_ESCORT_DISPATCHER:onafterStart( From, Event, To )
|
||||
|
||||
self:HandleEvent( EVENTS.Birth )
|
||||
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventExit )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER:OnEventExit( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
self:I({EscortAirbase= self.EscortAirbase } )
|
||||
self:I({PlayerGroupName = PlayerGroupName } )
|
||||
self:I({PlayerGroup = PlayerGroup})
|
||||
self:I({FirstGroup = self.CarrierSet:GetFirst()})
|
||||
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if self.AI_Escorts[PlayerGroupName] then
|
||||
self.AI_Escorts[PlayerGroupName]:Stop()
|
||||
self.AI_Escorts[PlayerGroupName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER:OnEventBirth( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
self:I({EscortAirbase= self.EscortAirbase } )
|
||||
self:I({PlayerGroupName = PlayerGroupName } )
|
||||
self:I({PlayerGroup = PlayerGroup})
|
||||
self:I({FirstGroup = self.CarrierSet:GetFirst()})
|
||||
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if not self.AI_Escorts[PlayerGroupName] then
|
||||
local LeaderUnit = PlayerUnit
|
||||
local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot )
|
||||
self:I({EscortGroup = EscortGroup})
|
||||
|
||||
self:ScheduleOnce( 1,
|
||||
function( EscortGroup )
|
||||
local EscortSet = SET_GROUP:New()
|
||||
EscortSet:AddGroup( EscortGroup )
|
||||
self.AI_Escorts[PlayerGroupName] = AI_ESCORT:New( LeaderUnit, EscortSet, self.EscortName, self.EscortBriefing )
|
||||
self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 )
|
||||
if EscortGroup:IsHelicopter() then
|
||||
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
|
||||
else
|
||||
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
|
||||
end
|
||||
self.AI_Escorts[PlayerGroupName]:__Start( 0.1 )
|
||||
end, EscortGroup
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Start Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] Start
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
|
||||
--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] __Start
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param #number Delay
|
||||
|
||||
--- Stop Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
|
||||
--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] __Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param #number Delay
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
--- **AI** - Models the assignment of AI escorts to player flights upon request using the radio menu.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provides the facilities to trigger escorts when players join flight units.
|
||||
-- * Provide a menu for which escorts can be requested.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
--- @type AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models the assignment of AI escorts to player flights upon request using the radio menu.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_ESCORT_DISPATCHER_REQUEST
|
||||
AI_ESCORT_DISPATCHER_REQUEST = {
|
||||
ClassName = "AI_ESCORT_DISPATCHER_REQUEST",
|
||||
}
|
||||
|
||||
--- @field #list
|
||||
AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts = {}
|
||||
|
||||
|
||||
--- Creates a new AI_ESCORT_DISPATCHER_REQUEST object.
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are requested.
|
||||
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts.
|
||||
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned.
|
||||
-- @param #string EscortName Name of the escort, which will also be the name of the escort menu.
|
||||
-- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #AI_ESCORT_DISPATCHER_REQUEST
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER_REQUEST
|
||||
|
||||
self.CarrierSet = CarrierSet
|
||||
self.EscortSpawn = EscortSpawn
|
||||
self.EscortAirbase = EscortAirbase
|
||||
self.EscortName = EscortName
|
||||
self.EscortBriefing = EscortBriefing
|
||||
|
||||
self:SetStartState( "Idle" )
|
||||
|
||||
self:AddTransition( "Monitoring", "Monitor", "Monitoring" )
|
||||
|
||||
self:AddTransition( "Idle", "Start", "Monitoring" )
|
||||
self:AddTransition( "Monitoring", "Stop", "Idle" )
|
||||
|
||||
-- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset.
|
||||
function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier )
|
||||
self:F( { Carrier = Carrier:GetName() } )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:onafterStart( From, Event, To )
|
||||
|
||||
self:HandleEvent( EVENTS.Birth )
|
||||
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventExit )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if self.AI_Escorts[PlayerGroupName] then
|
||||
self.AI_Escorts[PlayerGroupName]:Stop()
|
||||
self.AI_Escorts[PlayerGroupName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if not self.AI_Escorts[PlayerGroupName] then
|
||||
local LeaderUnit = PlayerUnit
|
||||
self:ScheduleOnce( 0.1,
|
||||
function()
|
||||
self.AI_Escorts[PlayerGroupName] = AI_ESCORT_REQUEST:New( LeaderUnit, self.EscortSpawn, self.EscortAirbase, self.EscortName, self.EscortBriefing )
|
||||
self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 )
|
||||
if PlayerGroup:IsHelicopter() then
|
||||
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
|
||||
else
|
||||
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
|
||||
end
|
||||
self.AI_Escorts[PlayerGroupName]:__Start( 0.1 )
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Start Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Start
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
|
||||
--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Start
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param #number Delay
|
||||
|
||||
--- Stop Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
|
||||
--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param #number Delay
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
--- **Functional** -- Taking the lead of AI escorting your flight or of other AI, upon request using the menu.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Escort navigation commands.
|
||||
-- * Escort hold at position commands.
|
||||
-- * Escorts reporting detected targets.
|
||||
-- * Escorts scanning targets in advance.
|
||||
-- * Escorts attacking specific targets.
|
||||
-- * Request assistance from other groups for attack.
|
||||
-- * Manage rule of engagement of escorts.
|
||||
-- * Manage the allowed evasion techniques of escorts.
|
||||
-- * Make escort to execute a defined mission or path.
|
||||
-- * Escort tactical situation reporting.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/ESC%20-%20Escorting)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- Allows you to interact with escorting AI on your flight and take the lead.
|
||||
--
|
||||
-- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10).
|
||||
--
|
||||
-- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes.
|
||||
-- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts.
|
||||
--
|
||||
-- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval.
|
||||
-- Once targets are reported, each escort has these targets as menu options to command the attack of these targets.
|
||||
-- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered.
|
||||
--
|
||||
-- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available.
|
||||
-- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target.
|
||||
--
|
||||
-- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location.
|
||||
-- In this way, you can spread out the escorts over the battle field before a coordinated attack.
|
||||
--
|
||||
-- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight.
|
||||
--
|
||||
-- ## Leading the flight
|
||||
--
|
||||
-- When leading the flight, you are expected to guide the escorts towards the target areas,
|
||||
-- and carefully coordinate the attack based on the threat levels reported, and the available weapons
|
||||
-- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack.
|
||||
--
|
||||
-- ## Following the flight
|
||||
--
|
||||
-- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead.
|
||||
-- You as a player, are following the escorts, and are commanding them to progress the mission while
|
||||
-- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets
|
||||
-- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target
|
||||
-- type. Once the attack is finished, the escort will resume the mission it was assigned.
|
||||
-- In other words, you can use the escorts for reconnaissance, and for guiding the attack.
|
||||
-- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are
|
||||
-- following. You are in control. The ka-50s detect targets, report them, and you command how the attack
|
||||
-- will commence and from where. You can control where the escorts are holding position and which targets
|
||||
-- are attacked first. You are in control how the ka-50s will follow their mission path.
|
||||
--
|
||||
-- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control.
|
||||
-- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the
|
||||
-- attack is well coordinated.
|
||||
--
|
||||
-- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism
|
||||
-- it brings in your DCS world simulations.
|
||||
--
|
||||
-- # RADIO MENUs that can be created:
|
||||
--
|
||||
-- Find a summary below of the current available commands:
|
||||
--
|
||||
-- ## Navigation ...:
|
||||
--
|
||||
-- Escort group navigation functions:
|
||||
--
|
||||
-- * **"Join-Up":** The escort group fill follow you in the assigned formation.
|
||||
-- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color.
|
||||
-- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
|
||||
--
|
||||
-- ## Hold position ...:
|
||||
--
|
||||
-- Escort group navigation functions:
|
||||
--
|
||||
-- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter.
|
||||
-- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter.
|
||||
--
|
||||
-- ## Report targets ...:
|
||||
--
|
||||
-- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below).
|
||||
--
|
||||
-- * **"Report now":** Will report the current detected targets.
|
||||
-- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list.
|
||||
-- * **"Report targets off":** Will stop detecting targets.
|
||||
--
|
||||
-- ## Attack targets ...:
|
||||
--
|
||||
-- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
|
||||
-- This menu will be available in Flight menu or in each Escort menu.
|
||||
--
|
||||
-- ## Scan targets ...:
|
||||
--
|
||||
-- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation.
|
||||
--
|
||||
-- * **"Scan targets 30 seconds":** Scan 30 seconds for targets.
|
||||
-- * **"Scan targets 60 seconds":** Scan 60 seconds for targets.
|
||||
--
|
||||
-- ## Request assistance from ...:
|
||||
--
|
||||
-- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**.
|
||||
-- This menu item allows to request attack support from other ground based escorts supporting the current escort.
|
||||
-- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles.
|
||||
-- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area.
|
||||
--
|
||||
-- ## ROE ...:
|
||||
--
|
||||
-- Sets the Rules of Engagement (ROE) of the escort group when in flight.
|
||||
--
|
||||
-- * **"Hold Fire":** The escort group will hold fire.
|
||||
-- * **"Return Fire":** The escort group will return fire.
|
||||
-- * **"Open Fire":** The escort group will open fire on designated targets.
|
||||
-- * **"Weapon Free":** The escort group will engage with any target.
|
||||
--
|
||||
-- ## Evasion ...:
|
||||
--
|
||||
-- Will define the evasion techniques that the escort group will perform during flight or combat.
|
||||
--
|
||||
-- * **"Fight until death":** The escort group will have no reaction to threats.
|
||||
-- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed.
|
||||
-- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing.
|
||||
-- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres.
|
||||
--
|
||||
-- ## Resume Mission ...:
|
||||
--
|
||||
-- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
|
||||
-- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Escort
|
||||
-- @image Escorting.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_ESCORT_REQUEST
|
||||
-- @extends AI.AI_Escort#AI_ESCORT
|
||||
|
||||
--- AI_ESCORT_REQUEST class
|
||||
--
|
||||
-- # AI_ESCORT_REQUEST construction methods.
|
||||
--
|
||||
-- Create a new AI_ESCORT_REQUEST object with the @{#AI_ESCORT_REQUEST.New} method:
|
||||
--
|
||||
-- * @{#AI_ESCORT_REQUEST.New}: Creates a new AI_ESCORT_REQUEST object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text.
|
||||
--
|
||||
-- @usage
|
||||
-- -- Declare a new EscortPlanes object as follows:
|
||||
--
|
||||
-- -- First find the GROUP object and the CLIENT object.
|
||||
-- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor.
|
||||
-- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client.
|
||||
--
|
||||
-- -- Now use these 2 objects to construct the new EscortPlanes object.
|
||||
-- EscortPlanes = AI_ESCORT_REQUEST:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." )
|
||||
--
|
||||
-- @field #AI_ESCORT_REQUEST
|
||||
AI_ESCORT_REQUEST = {
|
||||
ClassName = "AI_ESCORT_REQUEST",
|
||||
}
|
||||
|
||||
--- AI_ESCORT_REQUEST.Mode class
|
||||
-- @type AI_ESCORT_REQUEST.MODE
|
||||
-- @field #number FOLLOW
|
||||
-- @field #number MISSION
|
||||
|
||||
--- MENUPARAM type
|
||||
-- @type MENUPARAM
|
||||
-- @field #AI_ESCORT_REQUEST ParamSelf
|
||||
-- @field #Distance ParamDistance
|
||||
-- @field #function ParamFunction
|
||||
-- @field #string ParamMessage
|
||||
|
||||
--- AI_ESCORT_REQUEST class constructor for an AI group
|
||||
-- @param #AI_ESCORT_REQUEST self
|
||||
-- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup.
|
||||
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object of AI, escorting the EscortUnit.
|
||||
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where escorts will be spawned once requested.
|
||||
-- @param #string EscortName Name of the escort.
|
||||
-- @param #string EscortBriefing A text showing the AI_ESCORT_REQUEST briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #AI_ESCORT_REQUEST
|
||||
-- @usage
|
||||
-- EscortSpawn = SPAWN:NewWithAlias( "Red A2G Escort Template", "Red A2G Escort AI" ):InitLimit( 10, 10 )
|
||||
-- EscortSpawn:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Sochi_Adler ), AIRBASE.TerminalType.OpenBig )
|
||||
--
|
||||
-- local EscortUnit = UNIT:FindByName( "Red A2G Pilot" )
|
||||
--
|
||||
-- Escort = AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, AIRBASE:FindByName(AIRBASE.Caucasus.Sochi_Adler), "A2G", "Briefing" )
|
||||
-- Escort:FormationTrail( 50, 100, 100 )
|
||||
-- Escort:Menus()
|
||||
-- Escort:__Start( 5 )
|
||||
function AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
|
||||
|
||||
local EscortGroupSet = SET_GROUP:New():FilterDeads():FilterCrashes()
|
||||
local self = BASE:Inherit( self, AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT_REQUEST
|
||||
|
||||
self.EscortGroupSet = EscortGroupSet
|
||||
self.EscortSpawn = EscortSpawn
|
||||
self.EscortAirbase = EscortAirbase
|
||||
|
||||
self.LeaderGroup = self.PlayerUnit:GetGroup()
|
||||
|
||||
self.Detection = DETECTION_AREAS:New( self.EscortGroupSet, 5000 )
|
||||
self.Detection:__Start( 30 )
|
||||
|
||||
self.SpawnMode = self.__Enum.Mode.Mission
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_REQUEST self
|
||||
function AI_ESCORT_REQUEST:SpawnEscort()
|
||||
|
||||
local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot )
|
||||
|
||||
self:ScheduleOnce( 0.1,
|
||||
function( EscortGroup )
|
||||
|
||||
EscortGroup:OptionROTVertical()
|
||||
EscortGroup:OptionROEHoldFire()
|
||||
|
||||
self.EscortGroupSet:AddGroup( EscortGroup )
|
||||
|
||||
local LeaderEscort = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP
|
||||
local Report = REPORT:New()
|
||||
Report:Add( "Joining Up " .. self.EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.EscortUnit ) )
|
||||
LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit )
|
||||
|
||||
self:SetFlightModeFormation( EscortGroup )
|
||||
self:FormationTrail()
|
||||
|
||||
self:_InitFlightMenus()
|
||||
self:_InitEscortMenus( EscortGroup )
|
||||
self:_InitEscortRoute( EscortGroup )
|
||||
|
||||
--- @param #AI_ESCORT self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function EscortGroup:OnEventDeadOrCrash( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
self.EscortMenu:Remove()
|
||||
end
|
||||
|
||||
EscortGroup:HandleEvent( EVENTS.Dead, EscortGroup.OnEventDeadOrCrash )
|
||||
EscortGroup:HandleEvent( EVENTS.Crash, EscortGroup.OnEventDeadOrCrash )
|
||||
|
||||
end, EscortGroup
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_REQUEST self
|
||||
-- @param Core.Set#SET_GROUP EscortGroupSet
|
||||
function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet )
|
||||
|
||||
self:F()
|
||||
|
||||
if not self.MenuRequestEscort then
|
||||
self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName )
|
||||
self.MenuRequestEscort = MENU_GROUP_COMMAND:New( self.LeaderGroup, "Request new escort ", self.MainMenu,
|
||||
function()
|
||||
self:SpawnEscort()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
self:GetParent( self ).onafterStart( self, EscortGroupSet )
|
||||
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventDeadOrCrash )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_REQUEST self
|
||||
-- @param Core.Set#SET_GROUP EscortGroupSet
|
||||
function AI_ESCORT_REQUEST:onafterStop( EscortGroupSet )
|
||||
|
||||
self:F()
|
||||
|
||||
EscortGroupSet:ForEachGroup(
|
||||
--- @param Core.Group#GROUP EscortGroup
|
||||
function( EscortGroup )
|
||||
EscortGroup:WayPointInitialize()
|
||||
|
||||
EscortGroup:OptionROTVertical()
|
||||
EscortGroup:OptionROEOpenFire()
|
||||
end
|
||||
)
|
||||
|
||||
self.Detection:Stop()
|
||||
|
||||
self.MainMenu:Remove()
|
||||
|
||||
end
|
||||
|
||||
--- Set the spawn mode to be mission execution.
|
||||
-- @param #AI_ESCORT_REQUEST self
|
||||
function AI_ESCORT_REQUEST:SetEscortSpawnMission()
|
||||
|
||||
self.SpawnMode = self.__Enum.Mode.Mission
|
||||
|
||||
end
|
||||
+631
-347
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,934 @@
|
||||
--- **AI** -- Perform Air Patrolling for airplanes.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel treshold to RTB on time.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- AI PATROL classes makes AI Controllables execute an Patrol.
|
||||
--
|
||||
-- There are the following types of PATROL classes defined:
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE}: Perform a PATROL in a zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/PAT%20-%20Patrolling)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl35HvYZKA6G22WMt7iI3zky)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
|
||||
-- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Testing and API concept review.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Patrol
|
||||
-- @image AI_Air_Patrolling.JPG
|
||||
|
||||
--- AI_PATROL_ZONE class
|
||||
-- @type AI_PATROL_ZONE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
|
||||
-- @field Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @field DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @field DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @field DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @field DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @field Core.Spawn#SPAWN CoordTest
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
--- Implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_PATROL_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
---- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or
|
||||
-- use derived AI_ classes to model AI offensive or defensive behaviour.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_PATROL_ZONE constructor
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE.New}(): Creates a new AI_PATROL_ZONE object.
|
||||
--
|
||||
-- ## 2. AI_PATROL_ZONE is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_PATROL_ZONE States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base.
|
||||
-- * **Stopped** ( Group ): The process is stopped.
|
||||
-- * **Crashed** ( Group ): The AI has crashed or is dead.
|
||||
--
|
||||
-- ### 2.2. AI_PATROL_ZONE Events
|
||||
--
|
||||
-- * **Start** ( Group ): Start the process.
|
||||
-- * **Stop** ( Group ): Stop the process.
|
||||
-- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **RTB** ( Group ): Route the AI to the home base.
|
||||
-- * **Detect** ( Group ): The AI is detecting targets.
|
||||
-- * **Detected** ( Group ): The AI has detected new targets.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set or Get the AI controllable
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE.SetControllable}(): Set the AIControllable.
|
||||
-- * @{#AI_PATROL_ZONE.GetControllable}(): Get the AIControllable.
|
||||
--
|
||||
-- ## 4. Set the Speed and Altitude boundaries of the AI controllable
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol.
|
||||
-- * @{#AI_PATROL_ZONE.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol.
|
||||
--
|
||||
-- ## 5. Manage the detection process of the AI controllable
|
||||
--
|
||||
-- The detection process of the AI controllable can be manipulated.
|
||||
-- Detection requires an amount of CPU power, which has an impact on your mission performance.
|
||||
-- Only put detection on when absolutely necessary, and the frequency of the detection can also be set.
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
|
||||
-- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
|
||||
--
|
||||
-- The detection frequency can be set with @{#AI_PATROL_ZONE.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
|
||||
-- Use the method @{#AI_PATROL_ZONE.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI.
|
||||
--
|
||||
-- The detection can be filtered to potential targets in a specific zone.
|
||||
-- Use the method @{#AI_PATROL_ZONE.SetDetectionZone}() to set the zone where targets need to be detected.
|
||||
-- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected
|
||||
-- according the weather conditions.
|
||||
--
|
||||
-- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE
|
||||
--
|
||||
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- while a new AI is targetted to the AI_PATROL_ZONE.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place.
|
||||
--
|
||||
-- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE
|
||||
--
|
||||
-- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
|
||||
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this proces in place.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_PATROL_ZONE
|
||||
AI_PATROL_ZONE = {
|
||||
ClassName = "AI_PATROL_ZONE",
|
||||
}
|
||||
|
||||
--- Creates a new AI_PATROL_ZONE object
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
-- @usage
|
||||
-- -- Define a new AI_PATROL_ZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
|
||||
-- PatrolZone = ZONE:New( 'PatrolZone' )
|
||||
-- PatrolSpawn = SPAWN:New( 'Patrol Group' )
|
||||
-- PatrolArea = AI_PATROL_ZONE:New( PatrolZone, 3000, 6000, 600, 900 )
|
||||
function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_PATROL_ZONE
|
||||
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
|
||||
-- defafult PatrolAltType to "BARO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "BARO"
|
||||
|
||||
self:SetRefreshTimeInterval( 30 )
|
||||
|
||||
self.CheckStatus = true
|
||||
|
||||
self:ManageFuel( .2, 60 )
|
||||
self:ManageDamage( 1 )
|
||||
|
||||
|
||||
self.DetectedUnits = {} -- This table contains the targets detected during patrol.
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
self:AddTransition( "*", "Stop", "Stopped" )
|
||||
|
||||
--- OnLeave Transition Handler for State Stopped.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnLeaveStopped
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Stopped.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnEnterStopped
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- OnBefore Transition Handler for Event Stop.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeStop
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Stop.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterStop
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Stop.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Stop
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Stop.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Stop
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "None", "Start", "Patrolling" )
|
||||
|
||||
--- OnBefore Transition Handler for Event Start.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeStart
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Start.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterStart
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Start.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Start
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Start.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Start
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnLeavePatrolling
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnEnterPatrolling
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Route.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeRoute
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Route.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterRoute
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Route.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Route
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Route.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Route
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Status.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeStatus
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Status.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterStatus
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Status.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Status
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Status.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Status
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Detect", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Detect.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeDetect
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Detect.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterDetect
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Detect.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Detect
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Detect.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Detect
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Detected", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Detected.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeDetected
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Detected.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterDetected
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Detected.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Detected
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Detected.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Detected
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "RTB", "Returning" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event RTB.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeRTB
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event RTB.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterRTB
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event RTB.
|
||||
-- @function [parent=#AI_PATROL_ZONE] RTB
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event RTB.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __RTB
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Returning.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnLeaveReturning
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Returning.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnEnterReturning
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
self:AddTransition( "*", "Eject", "*" )
|
||||
self:AddTransition( "*", "Crash", "Crashed" )
|
||||
self:AddTransition( "*", "PilotDead", "*" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum speed of the patrol.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
|
||||
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
|
||||
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sets the floor and ceiling altitude of the patrol.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
|
||||
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
end
|
||||
|
||||
-- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
|
||||
-- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
|
||||
|
||||
--- Set the detection on. The AI will detect for targets.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionOn()
|
||||
self:F2()
|
||||
|
||||
self.DetectOn = true
|
||||
end
|
||||
|
||||
--- Set the detection off. The AI will NOT detect for targets.
|
||||
-- However, the list of already detected targets will be kept and can be enquired!
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionOff()
|
||||
self:F2()
|
||||
|
||||
self.DetectOn = false
|
||||
end
|
||||
|
||||
--- Set the status checking off.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetStatusOff()
|
||||
self:F2()
|
||||
|
||||
self.CheckStatus = false
|
||||
end
|
||||
|
||||
--- Activate the detection. The AI will detect for targets if the Detection is switched On.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionActivated()
|
||||
self:F2()
|
||||
|
||||
self:ClearDetectedUnits()
|
||||
self.DetectActivated = true
|
||||
self:__Detect( -self.DetectInterval )
|
||||
end
|
||||
|
||||
--- Deactivate the detection. The AI will NOT detect for targets.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionDeactivated()
|
||||
self:F2()
|
||||
|
||||
self:ClearDetectedUnits()
|
||||
self.DetectActivated = false
|
||||
end
|
||||
|
||||
--- Set the interval in seconds between each detection executed by the AI.
|
||||
-- The list of already detected targets will be kept and updated.
|
||||
-- Newly detected targets will be added, but already detected targets that were
|
||||
-- not detected in this cycle, will NOT be removed!
|
||||
-- The default interval is 30 seconds.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Seconds The interval in seconds.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetRefreshTimeInterval( Seconds )
|
||||
self:F2()
|
||||
|
||||
if Seconds then
|
||||
self.DetectInterval = Seconds
|
||||
else
|
||||
self.DetectInterval = 30
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the detection zone where the AI is detecting targets.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Zone#ZONE DetectionZone The zone where to detect targets.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionZone( DetectionZone )
|
||||
self:F2()
|
||||
|
||||
if DetectionZone then
|
||||
self.DetectZone = DetectionZone
|
||||
else
|
||||
self.DetectZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Gets a list of @{Wrapper.Unit#UNIT}s that were detected by the AI.
|
||||
-- No filtering is applied, so, ANY detected UNIT can be in this list.
|
||||
-- It is up to the mission designer to use the @{Wrapper.Unit} class and methods to filter the targets.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #table The list of @{Wrapper.Unit#UNIT}s
|
||||
function AI_PATROL_ZONE:GetDetectedUnits()
|
||||
self:F2()
|
||||
|
||||
return self.DetectedUnits
|
||||
end
|
||||
|
||||
--- Clears the list of @{Wrapper.Unit#UNIT}s that were detected by the AI.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ClearDetectedUnits()
|
||||
self:F2()
|
||||
self.DetectedUnits = {}
|
||||
end
|
||||
|
||||
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
|
||||
|
||||
self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage
|
||||
self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.
|
||||
-- However, damage cannot be foreseen early on.
|
||||
-- Therefore, when the damage treshold is reached,
|
||||
-- the AI will return immediately to the home base (RTB).
|
||||
-- Note that for groups, the average damage of the complete group will be calculated.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold )
|
||||
|
||||
self.PatrolManageDamage = true
|
||||
self.PatrolDamageThreshold = PatrolDamageThreshold
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To )
|
||||
self:F2()
|
||||
|
||||
self:__Route( 1 ) -- Route to the patrol point. The asynchronous trigger is important, because a spawned group and units takes at least one second to come live.
|
||||
self:__Status( 60 ) -- Check status status every 30 seconds.
|
||||
self:SetDetectionActivated()
|
||||
|
||||
self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnCrash )
|
||||
self:HandleEvent( EVENTS.Ejection, self.OnEjection )
|
||||
|
||||
Controllable:OptionROEHoldFire()
|
||||
Controllable:OptionROTVertical()
|
||||
|
||||
self.Controllable:OnReSpawn(
|
||||
function( PatrolGroup )
|
||||
self:T( "ReSpawn" )
|
||||
self:__Reset( 1 )
|
||||
self:__Route( 5 )
|
||||
end
|
||||
)
|
||||
|
||||
self:SetDetectionOn()
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
--- @param Wrapper.Controllable#CONTROLLABLE Controllable
|
||||
function AI_PATROL_ZONE:onbeforeDetect( Controllable, From, Event, To )
|
||||
|
||||
return self.DetectOn and self.DetectActivated
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
--- @param Wrapper.Controllable#CONTROLLABLE Controllable
|
||||
function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To )
|
||||
|
||||
local Detected = false
|
||||
|
||||
local DetectedTargets = Controllable:GetDetectedTargets()
|
||||
for TargetID, Target in pairs( DetectedTargets or {} ) do
|
||||
local TargetObject = Target.object
|
||||
|
||||
if TargetObject and TargetObject:isExist() and TargetObject.id_ < 50000000 then
|
||||
|
||||
local TargetUnit = UNIT:Find( TargetObject )
|
||||
|
||||
-- Check that target is alive due to issue https://github.com/FlightControl-Master/MOOSE/issues/1234
|
||||
if TargetUnit and TargetUnit:IsAlive() then
|
||||
|
||||
local TargetUnitName = TargetUnit:GetName()
|
||||
|
||||
if self.DetectionZone then
|
||||
if TargetUnit:IsInZone( self.DetectionZone ) then
|
||||
self:T( {"Detected ", TargetUnit } )
|
||||
if self.DetectedUnits[TargetUnit] == nil then
|
||||
self.DetectedUnits[TargetUnit] = true
|
||||
end
|
||||
Detected = true
|
||||
end
|
||||
else
|
||||
if self.DetectedUnits[TargetUnit] == nil then
|
||||
self.DetectedUnits[TargetUnit] = true
|
||||
end
|
||||
Detected = true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:__Detect( -self.DetectInterval )
|
||||
|
||||
if Detected == true then
|
||||
self:__Detected( 1.5 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
|
||||
-- This statis method is called from the route path within the last task at the last waaypoint of the Controllable.
|
||||
-- Note that this method is required, as triggers the next route when patrolling for the Controllable.
|
||||
function AI_PATROL_ZONE:_NewPatrolRoute( AIControllable )
|
||||
|
||||
local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROL_ZONE
|
||||
PatrolZone:__Route( 1 )
|
||||
end
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
|
||||
self:F2()
|
||||
|
||||
-- When RTB, don't allow anymore the routing.
|
||||
if From == "RTB" then
|
||||
return
|
||||
end
|
||||
|
||||
local life = self.Controllable:GetLife() or 0
|
||||
if self.Controllable:IsAlive() and life > 1 then
|
||||
-- Determine if the AIControllable is within the PatrolZone.
|
||||
-- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point.
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
-- Calculate the current route point of the controllable as the start point of the route.
|
||||
-- However, when the controllable is not in the air,
|
||||
-- the controllable current waypoint is probably the airbase...
|
||||
-- Thus, if we would take the current waypoint as the startpoint, upon take-off, the controllable flies
|
||||
-- immediately back to the airbase, and this is not correct.
|
||||
-- Therefore, when on a runway, get as the current route point a random point within the PatrolZone.
|
||||
-- This will make the plane fly immediately to the patrol zone.
|
||||
|
||||
if self.Controllable:InAir() == false then
|
||||
self:T( "Not in the air, finding route path within PatrolZone" )
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
if not CurrentVec2 then return end
|
||||
--Done: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TakeOffParking,
|
||||
POINT_VEC3.RoutePointAction.FromParkingArea,
|
||||
ToPatrolZoneSpeed,
|
||||
true
|
||||
)
|
||||
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
|
||||
else
|
||||
self:T( "In the air, finding route path within PatrolZone" )
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
if not CurrentVec2 then return end
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToPatrolZoneSpeed,
|
||||
true
|
||||
)
|
||||
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
|
||||
end
|
||||
|
||||
|
||||
--- Define a random point in the @{Zone}. The AI will fly to that point within the zone.
|
||||
|
||||
--- Find a random 2D point in PatrolZone.
|
||||
local ToTargetVec2 = self.PatrolZone:GetRandomVec2()
|
||||
self:T2( ToTargetVec2 )
|
||||
|
||||
--- Define Speed and Altitude.
|
||||
local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude )
|
||||
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToTargetSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
--self.CoordTest:SpawnFromVec3( ToTargetPointVec3:GetVec3() )
|
||||
|
||||
--ToTargetPointVec3:SmokeRed()
|
||||
|
||||
PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
|
||||
self.Controllable:WayPointInitialize( PatrolRoute )
|
||||
|
||||
--- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ...
|
||||
self.Controllable:SetState( self.Controllable, "PatrolZone", self )
|
||||
self.Controllable:WayPointFunction( #PatrolRoute, 1, "AI_PATROL_ZONE:_NewPatrolRoute" )
|
||||
|
||||
--- NOW ROUTE THE GROUP!
|
||||
self.Controllable:WayPointExecute( 1, 2 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:onbeforeStatus()
|
||||
|
||||
return self.CheckStatus
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:onafterStatus()
|
||||
self:F2()
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
local RTB = false
|
||||
|
||||
local Fuel = self.Controllable:GetFuelMin()
|
||||
if Fuel < self.PatrolFuelThresholdPercentage then
|
||||
self:I( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
|
||||
local OldAIControllable = self.Controllable
|
||||
|
||||
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
|
||||
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )
|
||||
OldAIControllable:SetTask( TimedOrbitTask, 10 )
|
||||
|
||||
RTB = true
|
||||
else
|
||||
end
|
||||
|
||||
-- TODO: Check GROUP damage function.
|
||||
local Damage = self.Controllable:GetLife()
|
||||
if Damage <= self.PatrolDamageThreshold then
|
||||
self:I( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
|
||||
RTB = true
|
||||
end
|
||||
|
||||
if RTB == true then
|
||||
self:RTB()
|
||||
else
|
||||
self:__Status( 60 ) -- Execute the Patrol event after 30 seconds.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:onafterRTB()
|
||||
self:F2()
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
self:SetDetectionOff()
|
||||
self.CheckStatus = false
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
if not CurrentVec2 then return end
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
--local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToPatrolZoneSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
|
||||
self.Controllable:WayPointInitialize( PatrolRoute )
|
||||
|
||||
--- NOW ROUTE THE GROUP!
|
||||
self.Controllable:WayPointExecute( 1, 1 )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:onafterDead()
|
||||
self:SetDetectionOff()
|
||||
self:SetStatusOff()
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_PATROL_ZONE:OnCrash( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
if #self.Controllable:GetUnits() == 1 then
|
||||
self:__Crash( 1, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_PATROL_ZONE:OnEjection( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
self:__Eject( 1, EventData )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_PATROL_ZONE:OnPilotDead( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
self:__PilotDead( 1, EventData )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,309 @@
|
||||
--- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occuring on @{Wrapper.Unit}s.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Actions.Account
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
do -- ACT_ACCOUNT
|
||||
|
||||
--- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS}
|
||||
--
|
||||
-- ## ACT_ACCOUNT state machine:
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
-- ### ACT_ACCOUNT States
|
||||
--
|
||||
-- * **Asigned**: The player is assigned.
|
||||
-- * **Waiting**: Waiting for an event.
|
||||
-- * **Report**: Reporting.
|
||||
-- * **Account**: Account for an event.
|
||||
-- * **Accounted**: All events have been accounted for, end of the process.
|
||||
-- * **Failed**: Failed the process.
|
||||
--
|
||||
-- ### ACT_ACCOUNT Events
|
||||
--
|
||||
-- * **Start**: Start the process.
|
||||
-- * **Wait**: Wait for an event.
|
||||
-- * **Report**: Report the status of the accounting.
|
||||
-- * **Event**: An event happened, process the event.
|
||||
-- * **More**: More targets.
|
||||
-- * **NoMore (*)**: No more targets.
|
||||
-- * **Fail (*)**: The action process has failed.
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
-- ### ACT_ACCOUNT state transition methods:
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
-- @type ACT_ACCOUNT
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ACCOUNT = {
|
||||
ClassName = "ACT_ACCOUNT",
|
||||
TargetSetUnit = nil,
|
||||
}
|
||||
|
||||
--- Creates a new DESTROY process.
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @return #ACT_ACCOUNT
|
||||
function ACT_ACCOUNT:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
self:AddTransition( "Assigned", "Start", "Waiting" )
|
||||
self:AddTransition( "*", "Wait", "Waiting" )
|
||||
self:AddTransition( "*", "Report", "Report" )
|
||||
self:AddTransition( "*", "Event", "Account" )
|
||||
self:AddTransition( "Account", "Player", "AccountForPlayer" )
|
||||
self:AddTransition( "Account", "Other", "AccountForOther" )
|
||||
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "More", "Wait" )
|
||||
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "NoMore", "Accounted" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
|
||||
self:AddEndState( "Failed" )
|
||||
|
||||
self:SetStartState( "Assigned" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Process Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT:onafterStart( ProcessUnit, From, Event, To )
|
||||
|
||||
self:HandleEvent( EVENTS.Dead, self.onfuncEventDead )
|
||||
self:HandleEvent( EVENTS.Crash, self.onfuncEventCrash )
|
||||
self:HandleEvent( EVENTS.Hit )
|
||||
|
||||
self:__Wait( 1 )
|
||||
end
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT:onenterWaiting( ProcessUnit, From, Event, To )
|
||||
|
||||
if self.DisplayCount >= self.DisplayInterval then
|
||||
self:Report()
|
||||
self.DisplayCount = 1
|
||||
else
|
||||
self.DisplayCount = self.DisplayCount + 1
|
||||
end
|
||||
|
||||
return true -- Process always the event.
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event )
|
||||
|
||||
self:__NoMore( 1 )
|
||||
end
|
||||
|
||||
end -- ACT_ACCOUNT
|
||||
|
||||
do -- ACT_ACCOUNT_DEADS
|
||||
|
||||
--- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Core.Fsm.Account#ACT_ACCOUNT}
|
||||
--
|
||||
-- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units.
|
||||
-- The process is given a @{Set} of units that will be tracked upon successful destruction.
|
||||
-- The process will end after each target has been successfully destroyed.
|
||||
-- Each successful dead will trigger an Account state transition that can be scored, modified or administered.
|
||||
--
|
||||
--
|
||||
-- ## ACT_ACCOUNT_DEADS constructor:
|
||||
--
|
||||
-- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object.
|
||||
--
|
||||
-- @type ACT_ACCOUNT_DEADS
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @extends #ACT_ACCOUNT
|
||||
ACT_ACCOUNT_DEADS = {
|
||||
ClassName = "ACT_ACCOUNT_DEADS",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new DESTROY process.
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param #string TaskName
|
||||
function ACT_ACCOUNT_DEADS:New()
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
self.DisplayCategory = "HQ" -- Targets is the default display category
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ACCOUNT_DEADS:Init( FsmAccount )
|
||||
|
||||
self.Task = self:GetTask()
|
||||
self.TaskName = self.Task:GetName()
|
||||
end
|
||||
|
||||
--- Process Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
local MessageText = "Your group with assigned " .. self.TaskName .. " task has " .. Task.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed."
|
||||
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
end
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param Tasking.Task#TASK Task
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Task, From, Event, To, EventData )
|
||||
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
|
||||
|
||||
if Task.TargetSetUnit:FindUnit( EventData.IniUnitName ) then
|
||||
local PlayerName = ProcessUnit:GetPlayerName()
|
||||
local PlayerHit = self.PlayerHits and self.PlayerHits[EventData.IniUnitName]
|
||||
if PlayerHit == PlayerName then
|
||||
self:Player( EventData )
|
||||
else
|
||||
self:Other( EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param Tasking.Task#TASK Task
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessUnit, Task, From, Event, To, EventData )
|
||||
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
|
||||
|
||||
local TaskGroup = ProcessUnit:GetGroup()
|
||||
|
||||
Task.TargetSetUnit:Remove( EventData.IniUnitName )
|
||||
|
||||
local MessageText = "You have destroyed a target.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
|
||||
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
|
||||
local PlayerName = ProcessUnit:GetPlayerName()
|
||||
Task:AddProgress( PlayerName, "Destroyed " .. EventData.IniTypeName, timer.getTime(), 1 )
|
||||
|
||||
if Task.TargetSetUnit:Count() > 0 then
|
||||
self:__More( 1 )
|
||||
else
|
||||
self:__NoMore( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param Tasking.Task#TASK Task
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessUnit, Task, From, Event, To, EventData )
|
||||
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
|
||||
|
||||
local TaskGroup = ProcessUnit:GetGroup()
|
||||
Task.TargetSetUnit:Remove( EventData.IniUnitName )
|
||||
|
||||
local MessageText = "One of the task targets has been destroyed.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
|
||||
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
|
||||
if Task.TargetSetUnit:Count() > 0 then
|
||||
self:__More( 1 )
|
||||
else
|
||||
self:__NoMore( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- DCS Events
|
||||
|
||||
--- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:OnEventHit( EventData )
|
||||
self:T( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniPlayerName and EventData.TgtDCSUnitName then
|
||||
self.PlayerHits = self.PlayerHits or {}
|
||||
self.PlayerHits[EventData.TgtDCSUnitName] = EventData.IniPlayerName
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData )
|
||||
self:T( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
self:Event( EventData )
|
||||
end
|
||||
end
|
||||
|
||||
--- DCS Events
|
||||
|
||||
--- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData )
|
||||
self:T( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
self:Event( EventData )
|
||||
end
|
||||
end
|
||||
|
||||
end -- ACT_ACCOUNT DEADS
|
||||
@@ -0,0 +1,287 @@
|
||||
--- (SP) (MP) (FSM) Accept or reject process for player (task) assignments.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS}
|
||||
--
|
||||
-- ## ACT_ASSIGN state machine:
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
-- ### ACT_ASSIGN **Events**:
|
||||
--
|
||||
-- These are the events defined in this class:
|
||||
--
|
||||
-- * **Start**: Start the tasking acceptance process.
|
||||
-- * **Assign**: Assign the task.
|
||||
-- * **Reject**: Reject the task..
|
||||
--
|
||||
-- ### ACT_ASSIGN **Event methods**:
|
||||
--
|
||||
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
|
||||
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
|
||||
--
|
||||
-- * **Immediate**: The event method has exactly the name of the event.
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- ### ACT_ASSIGN **States**:
|
||||
--
|
||||
-- * **UnAssigned**: The player has not accepted the task.
|
||||
-- * **Assigned (*)**: The player has accepted the task.
|
||||
-- * **Rejected (*)**: The player has not accepted the task.
|
||||
-- * **Waiting**: The process is awaiting player feedback.
|
||||
-- * **Failed (*)**: The process has failed.
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
-- ### ACT_ASSIGN state transition methods:
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Core.Fsm.Assign#ACT_ASSIGN}
|
||||
--
|
||||
-- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task.
|
||||
--
|
||||
-- ## 1.1) ACT_ASSIGN_ACCEPT constructor:
|
||||
--
|
||||
-- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Core.Fsm.Assign#ACT_ASSIGN}
|
||||
--
|
||||
-- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option.
|
||||
-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task.
|
||||
-- The assignment type also allows to reject the task.
|
||||
--
|
||||
-- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor:
|
||||
-- -----------------------------------------
|
||||
--
|
||||
-- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Actions.Assign
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
do -- ACT_ASSIGN
|
||||
|
||||
--- ACT_ASSIGN class
|
||||
-- @type ACT_ASSIGN
|
||||
-- @field Tasking.Task#TASK Task
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ASSIGN = {
|
||||
ClassName = "ACT_ASSIGN",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted.
|
||||
-- @param #ACT_ASSIGN self
|
||||
-- @return #ACT_ASSIGN The task acceptance process.
|
||||
function ACT_ASSIGN:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
self:AddTransition( "UnAssigned", "Start", "Waiting" )
|
||||
self:AddTransition( "Waiting", "Assign", "Assigned" )
|
||||
self:AddTransition( "Waiting", "Reject", "Rejected" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
|
||||
self:AddEndState( "Assigned" )
|
||||
self:AddEndState( "Rejected" )
|
||||
self:AddEndState( "Failed" )
|
||||
|
||||
self:SetStartState( "UnAssigned" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end -- ACT_ASSIGN
|
||||
|
||||
|
||||
|
||||
do -- ACT_ASSIGN_ACCEPT
|
||||
|
||||
--- ACT_ASSIGN_ACCEPT class
|
||||
-- @type ACT_ASSIGN_ACCEPT
|
||||
-- @field Tasking.Task#TASK Task
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends #ACT_ASSIGN
|
||||
ACT_ASSIGN_ACCEPT = {
|
||||
ClassName = "ACT_ASSIGN_ACCEPT",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted.
|
||||
-- @param #ACT_ASSIGN_ACCEPT self
|
||||
-- @param #string TaskBriefing
|
||||
function ACT_ASSIGN_ACCEPT:New( TaskBriefing )
|
||||
|
||||
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT
|
||||
|
||||
self.TaskBriefing = TaskBriefing
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ASSIGN_ACCEPT:Init( FsmAssign )
|
||||
|
||||
self.TaskBriefing = FsmAssign.TaskBriefing
|
||||
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_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
self:__Assign( 1 )
|
||||
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_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
|
||||
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
|
||||
end
|
||||
|
||||
end -- ACT_ASSIGN_ACCEPT
|
||||
|
||||
|
||||
do -- ACT_ASSIGN_MENU_ACCEPT
|
||||
|
||||
--- ACT_ASSIGN_MENU_ACCEPT class
|
||||
-- @type ACT_ASSIGN_MENU_ACCEPT
|
||||
-- @field Tasking.Task#TASK Task
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends #ACT_ASSIGN
|
||||
ACT_ASSIGN_MENU_ACCEPT = {
|
||||
ClassName = "ACT_ASSIGN_MENU_ACCEPT",
|
||||
}
|
||||
|
||||
--- Init.
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param #string TaskBriefing
|
||||
-- @return #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:New( TaskBriefing )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT
|
||||
|
||||
self.TaskBriefing = TaskBriefing
|
||||
|
||||
return self
|
||||
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 TaskBriefing
|
||||
-- @return #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:Init( TaskBriefing )
|
||||
|
||||
self.TaskBriefing = TaskBriefing
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
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 TaskGroup = ProcessUnit:GetGroup()
|
||||
|
||||
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( TaskGroup )
|
||||
|
||||
self:__Assign( -1, TaskGroup )
|
||||
end
|
||||
|
||||
--- Menu function.
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:MenuReject( TaskGroup )
|
||||
|
||||
self:__Reject( -1, TaskGroup )
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
|
||||
self.Menu:Remove()
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string 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
|
||||
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
|
||||
@@ -0,0 +1,214 @@
|
||||
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
|
||||
--
|
||||
-- ## ACT_ASSIST state machine:
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
-- ### ACT_ASSIST **Events**:
|
||||
--
|
||||
-- These are the events defined in this class:
|
||||
--
|
||||
-- * **Start**: The process is started.
|
||||
-- * **Next**: The process is smoking the targets in the given zone.
|
||||
--
|
||||
-- ### ACT_ASSIST **Event methods**:
|
||||
--
|
||||
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
|
||||
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
|
||||
--
|
||||
-- * **Immediate**: The event method has exactly the name of the event.
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- ### ACT_ASSIST **States**:
|
||||
--
|
||||
-- * **None**: The controllable did not receive route commands.
|
||||
-- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone.
|
||||
-- * **Smoking (*)**: The process is smoking the targets in the zone.
|
||||
-- * **Failed (*)**: The process has failed.
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
-- ### ACT_ASSIST state transition methods:
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Core.Fsm.Route#ACT_ASSIST}
|
||||
--
|
||||
-- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}.
|
||||
-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour.
|
||||
-- At random intervals, a new target is smoked.
|
||||
--
|
||||
-- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor:
|
||||
--
|
||||
-- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Actions.Assist
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
do -- ACT_ASSIST
|
||||
|
||||
--- ACT_ASSIST class
|
||||
-- @type ACT_ASSIST
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ASSIST = {
|
||||
ClassName = "ACT_ASSIST",
|
||||
}
|
||||
|
||||
--- Creates a new target smoking 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_ASSIST self
|
||||
-- @return #ACT_ASSIST
|
||||
function ACT_ASSIST:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
self:AddTransition( "None", "Start", "AwaitSmoke" )
|
||||
self:AddTransition( "AwaitSmoke", "Next", "Smoking" )
|
||||
self:AddTransition( "Smoking", "Next", "AwaitSmoke" )
|
||||
self:AddTransition( "*", "Stop", "Success" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
|
||||
self:AddEndState( "Failed" )
|
||||
self:AddEndState( "Success" )
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Task Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIST self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIST:onafterStart( ProcessUnit, From, Event, To )
|
||||
|
||||
local ProcessGroup = ProcessUnit:GetGroup()
|
||||
local MissionMenu = self:GetMission():GetMenu( ProcessGroup )
|
||||
|
||||
local function MenuSmoke( MenuParam )
|
||||
local self = MenuParam.self
|
||||
local SmokeColor = MenuParam.SmokeColor
|
||||
self.SmokeColor = SmokeColor
|
||||
self:__Next( 1 )
|
||||
end
|
||||
|
||||
self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu )
|
||||
self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } )
|
||||
self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } )
|
||||
self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } )
|
||||
self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } )
|
||||
self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } )
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIST self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIST:onafterStop( ProcessUnit, From, Event, To )
|
||||
|
||||
self.Menu:Remove() -- When stopped, remove the menus
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
|
||||
|
||||
--- ACT_ASSIST_SMOKE_TARGETS_ZONE class
|
||||
-- @type ACT_ASSIST_SMOKE_TARGETS_ZONE
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends #ACT_ASSIST
|
||||
ACT_ASSIST_SMOKE_TARGETS_ZONE = {
|
||||
ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE",
|
||||
}
|
||||
|
||||
-- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor()
|
||||
-- self:E("_Destructor")
|
||||
--
|
||||
-- self.Menu:Remove()
|
||||
-- self:EventRemoveAll()
|
||||
-- end
|
||||
|
||||
--- Creates a new target smoking 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_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param Core.Zone#ZONE_BASE TargetZone
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone )
|
||||
local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
self.TargetZone = TargetZone
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke )
|
||||
|
||||
self.TargetSetUnit = FsmSmoke.TargetSetUnit
|
||||
self.TargetZone = FsmSmoke.TargetZone
|
||||
end
|
||||
|
||||
--- Creates a new target smoking 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_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param Core.Zone#ZONE_BASE TargetZone
|
||||
-- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone )
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
self.TargetZone = TargetZone
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, From, Event, To )
|
||||
|
||||
self.TargetSetUnit:ForEachUnit(
|
||||
--- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
function( SmokeUnit )
|
||||
if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then
|
||||
SCHEDULER:New( self,
|
||||
function()
|
||||
if SmokeUnit:IsAlive() then
|
||||
SmokeUnit:Smoke( self.SmokeColor, 150 )
|
||||
end
|
||||
end, {}, math.random( 10, 60 )
|
||||
)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,478 @@
|
||||
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS}
|
||||
--
|
||||
-- ## ACT_ROUTE state machine:
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
-- ### ACT_ROUTE **Events**:
|
||||
--
|
||||
-- These are the events defined in this class:
|
||||
--
|
||||
-- * **Start**: The process is started. The process will go into the Report state.
|
||||
-- * **Report**: The process is reporting to the player the route to be followed.
|
||||
-- * **Route**: The process is routing the controllable.
|
||||
-- * **Pause**: The process is pausing the route of the controllable.
|
||||
-- * **Arrive**: The controllable has arrived at a route point.
|
||||
-- * **More**: There are more route points that need to be followed. The process will go back into the Report state.
|
||||
-- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state.
|
||||
--
|
||||
-- ### ACT_ROUTE **Event methods**:
|
||||
--
|
||||
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
|
||||
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
|
||||
--
|
||||
-- * **Immediate**: The event method has exactly the name of the event.
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- ### ACT_ROUTE **States**:
|
||||
--
|
||||
-- * **None**: The controllable did not receive route commands.
|
||||
-- * **Arrived (*)**: The controllable has arrived at a route point.
|
||||
-- * **Aborted (*)**: The controllable has aborted the route path.
|
||||
-- * **Routing**: The controllable is understay to the route point.
|
||||
-- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around.
|
||||
-- * **Success (*)**: All route points were reached.
|
||||
-- * **Failed (*)**: The process has failed.
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
-- ### ACT_ROUTE state transition methods:
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Core.Fsm.Route#ACT_ROUTE}
|
||||
--
|
||||
-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Wrapper.Controllable} player @{Wrapper.Unit} to a @{Zone}.
|
||||
-- The player receives on perioding times messages with the coordinates of the route to follow.
|
||||
-- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended.
|
||||
--
|
||||
-- # 1.1) ACT_ROUTE_ZONE constructor:
|
||||
--
|
||||
-- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Actions.Route
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
do -- ACT_ROUTE
|
||||
|
||||
--- ACT_ROUTE class
|
||||
-- @type ACT_ROUTE
|
||||
-- @field Tasking.Task#TASK TASK
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE Zone
|
||||
-- @field Core.Point#COORDINATE Coordinate
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ROUTE = {
|
||||
ClassName = "ACT_ROUTE",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @return #ACT_ROUTE self
|
||||
function ACT_ROUTE:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
self:AddTransition( "*", "Reset", "None" )
|
||||
self:AddTransition( "None", "Start", "Routing" )
|
||||
self:AddTransition( "*", "Report", "*" )
|
||||
self:AddTransition( "Routing", "Route", "Routing" )
|
||||
self:AddTransition( "Routing", "Pause", "Pausing" )
|
||||
self:AddTransition( "Routing", "Arrive", "Arrived" )
|
||||
self:AddTransition( "*", "Cancel", "Cancelled" )
|
||||
self:AddTransition( "Arrived", "Success", "Success" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
self:AddTransition( "", "", "" )
|
||||
self:AddTransition( "", "", "" )
|
||||
|
||||
self:AddEndState( "Arrived" )
|
||||
self:AddEndState( "Failed" )
|
||||
self:AddEndState( "Cancelled" )
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
self:SetRouteMode( "C" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set a Cancel Menu item.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @return #ACT_ROUTE
|
||||
function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime, MenuTag )
|
||||
|
||||
self.CancelMenuGroupCommand = MENU_GROUP_COMMAND:New(
|
||||
MenuGroup,
|
||||
MenuText,
|
||||
ParentMenu,
|
||||
self.MenuCancel,
|
||||
self
|
||||
):SetTime( MenuTime ):SetTag( MenuTag )
|
||||
|
||||
ParentMenu:SetTime( MenuTime )
|
||||
|
||||
ParentMenu:Remove( MenuTime, MenuTag )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the route mode.
|
||||
-- There are 2 route modes supported:
|
||||
--
|
||||
-- * SetRouteMode( "B" ): Route mode is Bearing and Range.
|
||||
-- * SetRouteMode( "C" ): Route mode is LL or MGRS according coordinate system setup.
|
||||
--
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @return #ACT_ROUTE
|
||||
function ACT_ROUTE:SetRouteMode( RouteMode )
|
||||
|
||||
self.RouteMode = RouteMode
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the routing text to be displayed.
|
||||
-- The route mode determines the text displayed.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT Controllable
|
||||
-- @return #string
|
||||
function ACT_ROUTE:GetRouteText( Controllable )
|
||||
|
||||
local RouteText = ""
|
||||
|
||||
local Coordinate = nil -- Core.Point#COORDINATE
|
||||
|
||||
if self.Coordinate then
|
||||
Coordinate = self.Coordinate
|
||||
end
|
||||
|
||||
if self.Zone then
|
||||
Coordinate = self.Zone:GetPointVec3( self.Altitude )
|
||||
Coordinate:SetHeading( self.Heading )
|
||||
end
|
||||
|
||||
|
||||
local Task = self:GetTask() -- This is to dermine that the coordinates are for a specific task mode (A2A or A2G).
|
||||
local CC = self:GetTask():GetMission():GetCommandCenter()
|
||||
if CC then
|
||||
if CC:IsModeWWII() then
|
||||
-- Find closest reference point to the target.
|
||||
local ShortestDistance = 0
|
||||
local ShortestReferencePoint = nil
|
||||
local ShortestReferenceName = ""
|
||||
self:F( { CC.ReferencePoints } )
|
||||
for ZoneName, Zone in pairs( CC.ReferencePoints ) do
|
||||
self:F( { ZoneName = ZoneName } )
|
||||
local Zone = Zone -- Core.Zone#ZONE
|
||||
local ZoneCoord = Zone:GetCoordinate()
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate )
|
||||
self:F( { ShortestDistance, ShortestReferenceName } )
|
||||
if ShortestDistance == 0 or ZoneDistance < ShortestDistance then
|
||||
ShortestDistance = ZoneDistance
|
||||
ShortestReferencePoint = ZoneCoord
|
||||
ShortestReferenceName = CC.ReferenceNames[ZoneName]
|
||||
end
|
||||
end
|
||||
if ShortestReferencePoint then
|
||||
RouteText = Coordinate:ToStringFromRP( ShortestReferencePoint, ShortestReferenceName, Controllable )
|
||||
end
|
||||
else
|
||||
RouteText = Coordinate:ToString( Controllable, nil, Task )
|
||||
end
|
||||
end
|
||||
|
||||
return RouteText
|
||||
end
|
||||
|
||||
|
||||
function ACT_ROUTE:MenuCancel()
|
||||
self:F("Cancelled")
|
||||
self.CancelMenuGroupCommand:Remove()
|
||||
self:__Cancel( 1 )
|
||||
end
|
||||
|
||||
--- Task Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE:onafterStart( ProcessUnit, From, Event, To )
|
||||
|
||||
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
--- Check if the controllable has arrived.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @return #boolean
|
||||
function ACT_ROUTE:onfuncHasArrived( ProcessUnit )
|
||||
return false
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To )
|
||||
|
||||
if ProcessUnit:IsAlive() then
|
||||
local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic
|
||||
if self.DisplayCount >= self.DisplayInterval then
|
||||
self:T( { HasArrived = HasArrived } )
|
||||
if not HasArrived then
|
||||
self:Report()
|
||||
end
|
||||
self.DisplayCount = 1
|
||||
else
|
||||
self.DisplayCount = self.DisplayCount + 1
|
||||
end
|
||||
|
||||
if HasArrived then
|
||||
self:__Arrive( 1 )
|
||||
else
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
return HasArrived -- if false, then the event will not be executed...
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
end -- ACT_ROUTE
|
||||
|
||||
|
||||
do -- ACT_ROUTE_POINT
|
||||
|
||||
--- ACT_ROUTE_POINT class
|
||||
-- @type ACT_ROUTE_POINT
|
||||
-- @field Tasking.Task#TASK TASK
|
||||
-- @extends #ACT_ROUTE
|
||||
ACT_ROUTE_POINT = {
|
||||
ClassName = "ACT_ROUTE_POINT",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new routing state machine.
|
||||
-- The task will route a controllable to a Coordinate until the controllable is within the Range.
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Core.Point#COORDINATE The Coordinate to Target.
|
||||
-- @param #number Range The Distance to Target.
|
||||
-- @param Core.Zone#ZONE_BASE Zone
|
||||
function ACT_ROUTE_POINT:New( Coordinate, Range )
|
||||
local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_POINT
|
||||
|
||||
self.Coordinate = Coordinate
|
||||
self.Range = Range or 0
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new routing state machine.
|
||||
-- The task will route a controllable to a Coordinate until the controllable is within the Range.
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
function ACT_ROUTE_POINT:Init( FsmRoute )
|
||||
|
||||
self.Coordinate = FsmRoute.Coordinate
|
||||
self.Range = FsmRoute.Range or 0
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
self:SetStartState("None")
|
||||
end
|
||||
|
||||
--- Set Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Core.Point#COORDINATE Coordinate The Coordinate to route to.
|
||||
function ACT_ROUTE_POINT:SetCoordinate( Coordinate )
|
||||
self:F2( { Coordinate } )
|
||||
self.Coordinate = Coordinate
|
||||
end
|
||||
|
||||
--- Get Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @return Core.Point#COORDINATE Coordinate The Coordinate to route to.
|
||||
function ACT_ROUTE_POINT:GetCoordinate()
|
||||
self:F2( { self.Coordinate } )
|
||||
return self.Coordinate
|
||||
end
|
||||
|
||||
--- Set Range around Coordinate
|
||||
-- @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( { Range } )
|
||||
self.Range = Range or 10000
|
||||
end
|
||||
|
||||
--- Get Range around Coordinate
|
||||
-- @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
|
||||
|
||||
--- Method override to check if the controllable has arrived.
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @return #boolean
|
||||
function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit )
|
||||
|
||||
if ProcessUnit:IsAlive() then
|
||||
local Distance = self.Coordinate:Get2DDistance( ProcessUnit:GetCoordinate() )
|
||||
|
||||
if Distance <= self.Range then
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived."
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Task Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE_POINT:onafterReport( ProcessUnit, From, Event, To )
|
||||
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit )
|
||||
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
|
||||
end
|
||||
|
||||
end -- ACT_ROUTE_POINT
|
||||
|
||||
|
||||
do -- ACT_ROUTE_ZONE
|
||||
|
||||
--- ACT_ROUTE_ZONE class
|
||||
-- @type ACT_ROUTE_ZONE
|
||||
-- @field Tasking.Task#TASK TASK
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE Zone
|
||||
-- @extends #ACT_ROUTE
|
||||
ACT_ROUTE_ZONE = {
|
||||
ClassName = "ACT_ROUTE_ZONE",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE.
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE Zone
|
||||
function ACT_ROUTE_ZONE:New( Zone )
|
||||
local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE
|
||||
|
||||
self.Zone = Zone
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ROUTE_ZONE:Init( FsmRoute )
|
||||
|
||||
self.Zone = FsmRoute.Zone
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
end
|
||||
|
||||
--- Set Zone
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE Zone The Zone object where to route to.
|
||||
-- @param #number Altitude
|
||||
-- @param #number Heading
|
||||
function ACT_ROUTE_ZONE:SetZone( Zone, Altitude, Heading ) -- R2.2 Added altitude and heading
|
||||
self.Zone = Zone
|
||||
self.Altitude = Altitude
|
||||
self.Heading = Heading
|
||||
end
|
||||
|
||||
--- Get Zone
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @return Core.Zone#ZONE_BASE Zone The Zone object where to route to.
|
||||
function ACT_ROUTE_ZONE:GetZone()
|
||||
return self.Zone
|
||||
end
|
||||
|
||||
--- Method override to check if the controllable has arrived.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @return #boolean
|
||||
function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit )
|
||||
|
||||
if ProcessUnit:IsInZone( self.Zone ) then
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived within the zone."
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
end
|
||||
|
||||
return ProcessUnit:IsInZone( self.Zone )
|
||||
end
|
||||
|
||||
--- Task Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE_ZONE:onafterReport( ProcessUnit, From, Event, To )
|
||||
self:F( { ProcessUnit = ProcessUnit } )
|
||||
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit )
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
|
||||
end
|
||||
|
||||
end -- ACT_ROUTE_ZONE
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,332 @@
|
||||
--- **Cargo** -- Management of single cargo crates, which are based on a @{Static} object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions]()
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Cargo.CargoCrate
|
||||
-- @image Cargo_Crates.JPG
|
||||
|
||||
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
|
||||
|
||||
--- 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.
|
||||
--
|
||||
-- The above cargo classes are used by the following 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} module.
|
||||
-- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter} module.
|
||||
-- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane} module.
|
||||
-- * 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.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @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.RemoveUnit, self.OnEventCargoDead )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
|
||||
|
||||
self:SetEventPriority( 4 )
|
||||
|
||||
self.NearRadius = NearRadius or 25
|
||||
|
||||
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( false ) -- Do not generate a remove unit event, because we want to keep the template for later respawn in the database.
|
||||
--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 the cargo can be sling loaded.
|
||||
-- @param #CARGO_CRATE self
|
||||
function CARGO_CRATE:CanSlingload()
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if Cargo Crate is in the radius for the Cargo to be reported.
|
||||
-- @param #CARGO_CRATE 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_CRATE 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
|
||||
|
||||
@@ -0,0 +1,768 @@
|
||||
--- **Cargo** - Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions]()
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Cargo.CargoGroup
|
||||
-- @image Cargo_Groups.JPG
|
||||
|
||||
|
||||
do -- CARGO_GROUP
|
||||
|
||||
--- @type CARGO_GROUP
|
||||
-- @field Core.Set#SET_CARGO CargoSet The collection of derived CARGO objects.
|
||||
-- @field #string GroupName The name of the CargoGroup.
|
||||
-- @extends Cargo.Cargo#CARGO_REPORTABLE
|
||||
|
||||
--- Defines a cargo that is represented by a @{Wrapper.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 following 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} module.
|
||||
-- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter} module.
|
||||
-- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane} module.
|
||||
-- * 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.
|
||||
--
|
||||
-- @field #CARGO_GROUP CARGO_GROUP
|
||||
--
|
||||
CARGO_GROUP = {
|
||||
ClassName = "CARGO_GROUP",
|
||||
}
|
||||
|
||||
--- CARGO_GROUP constructor.
|
||||
-- This make a new CARGO_GROUP from a @{Wrapper.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 Group to be transported as cargo.
|
||||
-- @param #string Type Cargo type, e.g. "Infantry". This is the type used in SET_CARGO:New():FilterTypes("Infantry") to define the valid cargo groups of the set.
|
||||
-- @param #string Name A user defined name of the cargo group. This name CAN be the same as the group object but can also have a different name. This name MUST be unique!
|
||||
-- @param #number LoadRadius (optional) Distance in meters until which a cargo is loaded into the carrier. Cargo outside this radius has to be routed by other means to within the radius to be loaded.
|
||||
-- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene.
|
||||
-- @return #CARGO_GROUP Cargo group object.
|
||||
function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CAROG_REPORTABLE
|
||||
local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP
|
||||
self:F( { Type, Name, LoadRadius } )
|
||||
|
||||
self.CargoSet = SET_CARGO:New()
|
||||
self.CargoGroup = CargoGroup
|
||||
self.Grouped = true
|
||||
self.CargoUnitTemplate = {}
|
||||
|
||||
self.NearRadius = NearRadius
|
||||
|
||||
self:SetDeployed( false )
|
||||
|
||||
local WeightGroup = 0
|
||||
local VolumeGroup = 0
|
||||
|
||||
self.CargoGroup:Destroy() -- destroy and generate a unit removal event, so that the database gets cleaned, and the linked sets get properly cleaned.
|
||||
|
||||
local GroupName = CargoGroup:GetName()
|
||||
self.CargoName = Name
|
||||
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
|
||||
|
||||
-- Deactivate late activation.
|
||||
self.CargoTemplate.lateActivation=false
|
||||
|
||||
self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
|
||||
self.GroupTemplate.name = self.CargoName .. "#CARGO"
|
||||
self.GroupTemplate.groupId = nil
|
||||
|
||||
self.GroupTemplate.units = {}
|
||||
|
||||
for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do
|
||||
UnitTemplate.name = UnitTemplate.name .. "#CARGO"
|
||||
local CargoUnitName = UnitTemplate.name
|
||||
self.CargoUnitTemplate[CargoUnitName] = UnitTemplate
|
||||
|
||||
self.GroupTemplate.units[#self.GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName]
|
||||
self.GroupTemplate.units[#self.GroupTemplate.units].unitId = nil
|
||||
|
||||
-- And we register the spawned unit as part of the CargoSet.
|
||||
local Unit = UNIT:Register( CargoUnitName )
|
||||
|
||||
end
|
||||
|
||||
-- Then we register the new group in the database
|
||||
self.CargoGroup = GROUP:NewTemplate( self.GroupTemplate, self.GroupTemplate.CoalitionID, self.GroupTemplate.CategoryID, self.GroupTemplate.CountryID )
|
||||
|
||||
-- Now we spawn the new group based on the template created.
|
||||
self.CargoObject = _DATABASE:Spawn( self.GroupTemplate )
|
||||
|
||||
for CargoUnitID, CargoUnit in pairs( self.CargoObject:GetUnits() ) do
|
||||
|
||||
|
||||
local CargoUnitName = CargoUnit:GetName()
|
||||
|
||||
local Cargo = CARGO_UNIT:New( CargoUnit, Type, CargoUnitName, LoadRadius, NearRadius )
|
||||
self.CargoSet:Add( CargoUnitName, Cargo )
|
||||
|
||||
WeightGroup = WeightGroup + Cargo:GetWeight()
|
||||
|
||||
end
|
||||
|
||||
self:SetWeight( WeightGroup )
|
||||
|
||||
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.RemoveUnit, self.OnEventCargoDead )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
|
||||
|
||||
self:SetEventPriority( 4 )
|
||||
|
||||
return self
|
||||
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#CARGO
|
||||
Cargo:Destroy() -- Destroy the cargo and generate a remove unit event to update the sets.
|
||||
Cargo:SetStartState( "UnLoaded" )
|
||||
end
|
||||
|
||||
-- Now we spawn the new group based on the template created.
|
||||
_DATABASE:Spawn( self.GroupTemplate )
|
||||
|
||||
for CargoUnitID, CargoUnit in pairs( self.CargoObject:GetUnits() ) do
|
||||
|
||||
local CargoUnitName = CargoUnit:GetName()
|
||||
|
||||
local Cargo = CARGO_UNIT:New( CargoUnit, self.Type, CargoUnitName, self.LoadRadius )
|
||||
self.CargoSet:Add( CargoUnitName, Cargo )
|
||||
|
||||
end
|
||||
|
||||
self:SetDeployed( false )
|
||||
self:SetStartState( "UnLoaded" )
|
||||
|
||||
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
|
||||
|
||||
self.CargoObject = nil
|
||||
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.
|
||||
self.CargoObject = _DATABASE:Spawn( GroupTemplate )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #CARGO_GROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function CARGO_GROUP:OnEventCargoDead( EventData )
|
||||
|
||||
self:E(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.Cargo#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
|
||||
|
||||
--- After Board Event.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT CargoCarrier
|
||||
-- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier.
|
||||
function CARGO_GROUP:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... )
|
||||
self:F( { CargoCarrier.UnitName, From, Event, To, NearRadius = NearRadius } )
|
||||
|
||||
NearRadius = NearRadius or self.NearRadius
|
||||
|
||||
-- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2
|
||||
self.CargoSet:ForEach(
|
||||
function( Cargo, ... )
|
||||
self:F( { "Board Unit", Cargo:GetName( ), Cargo:IsDestroyed(), Cargo.CargoObject:IsAlive() } )
|
||||
local CargoGroup = Cargo.CargoObject --Wrapper.Group#GROUP
|
||||
CargoGroup:OptionAlarmStateGreen()
|
||||
Cargo:__Board( 1, CargoCarrier, NearRadius, ... )
|
||||
end, ...
|
||||
)
|
||||
|
||||
self:__Boarding( -1, CargoCarrier, NearRadius, ... )
|
||||
|
||||
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:onafterLoad( 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
|
||||
if not Cargo:IsDestroyed() then
|
||||
Cargo:Load( CargoCarrier )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--self.CargoObject:Destroy()
|
||||
self.CargoCarrier = CargoCarrier
|
||||
self.CargoCarrier:AddCargo( self )
|
||||
|
||||
end
|
||||
|
||||
--- Leave Boarding State.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT CargoCarrier
|
||||
-- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier.
|
||||
function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
|
||||
--self:F( { CargoCarrier.UnitName, From, Event, To } )
|
||||
|
||||
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( -5, 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 #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
-- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier.
|
||||
function CARGO_GROUP:onafterUnBoard( 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
|
||||
local ToVec=nil
|
||||
if ToPointVec2==nil then
|
||||
ToVec=self.CargoCarrier:GetPointVec2():GetRandomPointVec2InRadius(2*NearRadius, NearRadius)
|
||||
else
|
||||
ToVec=ToPointVec2
|
||||
end
|
||||
Cargo:__UnBoard( Timer, ToVec, NearRadius )
|
||||
Timer = Timer + 1
|
||||
end
|
||||
end, { NearRadius }
|
||||
)
|
||||
|
||||
|
||||
self:__UnBoarding( 1, ToPointVec2, NearRadius, ... )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Leave UnBoarding State.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
-- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier.
|
||||
function CARGO_GROUP:onafterUnBoarding( 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
|
||||
self:__UnLoad( 1, ToPointVec2, ... )
|
||||
else
|
||||
self:__UnBoarding( 1, ToPointVec2, NearRadius, ... )
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Enter UnLoaded State.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
function CARGO_GROUP:onafterUnLoad( 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=nil
|
||||
if ToPointVec2 then
|
||||
RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10)
|
||||
end
|
||||
Cargo:UnBoard( 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()
|
||||
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 or #nil if 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
|
||||
local CargoCoordinate
|
||||
if Cargo:IsLoaded() then
|
||||
CargoCoordinate = Cargo.CargoCarrier:GetCoordinate()
|
||||
else
|
||||
CargoCoordinate = Cargo.CargoObject:GetCoordinate()
|
||||
end
|
||||
|
||||
-- FF check if coordinate could be obtained. This was commented out for some (unknown) reason. But the check seems valid!
|
||||
if CargoCoordinate then
|
||||
Distance = Coordinate:Get2DDistance( CargoCoordinate )
|
||||
else
|
||||
return false
|
||||
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
|
||||
|
||||
|
||||
--- 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.Cargo#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.Cargo#CARGO
|
||||
|
||||
if Cargo then
|
||||
Cargo:Smoke( SmokeColor, Radius )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if the first element of the CargoGroup is the given @{Zone}.
|
||||
-- @param #CARGO_GROUP 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.Cargo#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
|
||||
@@ -0,0 +1,270 @@
|
||||
--- **Cargo** -- Management of single cargo crates, which are based on a @{Static} object. The cargo can only be slingloaded.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions]()
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Cargo.CargoSlingload
|
||||
-- @image Cargo_Slingload.JPG
|
||||
|
||||
|
||||
do -- CARGO_SLINGLOAD
|
||||
|
||||
--- Models the behaviour of cargo crates, which can only be slingloaded.
|
||||
-- @type CARGO_SLINGLOAD
|
||||
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
|
||||
|
||||
--- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @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.RemoveUnit, self.OnEventCargoDead )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
|
||||
|
||||
self:SetEventPriority( 4 )
|
||||
|
||||
self.NearRadius = NearRadius or 25
|
||||
|
||||
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_SLINGLOAD self
|
||||
function CARGO_SLINGLOAD: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
|
||||
@@ -0,0 +1,390 @@
|
||||
--- **Cargo** - Management of single cargo logistics, which are based on a @{Wrapper.Unit} object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions]()
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Cargo.CargoUnit
|
||||
-- @image Cargo_Units.JPG
|
||||
|
||||
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
|
||||
|
||||
--- 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.
|
||||
-- Note that ground forces behave in a group, and thus, act in formation, regardless if one unit is commanded to move.
|
||||
--
|
||||
-- This class is used in CARGO_GROUP, and is not meant to be used by mission designers individually.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @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, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CARGO_REPRESENTABLE.
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
|
||||
|
||||
-- Debug info.
|
||||
self:T({Type=Type, Name=Name, LoadRadius=LoadRadius, NearRadius=NearRadius})
|
||||
|
||||
-- Set cargo object.
|
||||
self.CargoObject = CargoUnit
|
||||
|
||||
-- Set event prio.
|
||||
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
|
||||
-- @param #number NearRadius (optional) Defaut 25 m.
|
||||
function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius )
|
||||
self:F( { From, Event, To, ToPointVec2, NearRadius } )
|
||||
|
||||
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
|
||||
if CargoCarrier:IsShip() then
|
||||
-- If CargoCarrier is a ship, we don't want to spawn the units in the water next to the boat. Use destination coord instead.
|
||||
self.CargoObject:ReSpawnAt( ToPointVec2, CargoDeployHeading )
|
||||
else
|
||||
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
|
||||
end
|
||||
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
|
||||
-- @param #number NearRadius (optional) Defaut 100 m.
|
||||
function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius )
|
||||
self:F( { From, Event, To, ToPointVec2, NearRadius } )
|
||||
|
||||
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
|
||||
-- @param #number NearRadius (optional) Defaut 100 m.
|
||||
function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius )
|
||||
self:F( { From, Event, To, ToPointVec2, NearRadius } )
|
||||
|
||||
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
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @param #number NearRadius
|
||||
function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... )
|
||||
self:F( { From, Event, To, CargoCarrier, NearRadius = NearRadius } )
|
||||
|
||||
self.CargoInAir = self.CargoObject:InAir()
|
||||
|
||||
local Desc = self.CargoObject:GetDesc()
|
||||
local MaxSpeed = Desc.speedMaxOffRoad
|
||||
local TypeName = Desc.typeName
|
||||
|
||||
--self:F({Unit=self.CargoObject:GetName()})
|
||||
|
||||
-- A cargo unit can only be boarded if it is not dead
|
||||
|
||||
-- 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 NearRadius is given, then use the given NearRadius, otherwise calculate the NearRadius
|
||||
-- based upon the Carrier bounding radius, which is calculated from the bounding rectangle on the Y axis.
|
||||
local NearRadius = NearRadius or CargoCarrier:GetBoundingRadius() + 5
|
||||
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 = 0
|
||||
|
||||
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( -5, 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 Default 25 m.
|
||||
function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
|
||||
self:F( { From, Event, To, CargoCarrier:GetName(), NearRadius = NearRadius } )
|
||||
|
||||
self:F( { IsAlive=self.CargoObject:IsAlive() } )
|
||||
|
||||
if CargoCarrier and CargoCarrier:IsAlive() then -- and self.CargoObject and self.CargoObject:IsAlive() then
|
||||
if (CargoCarrier:IsAir() and not CargoCarrier:InAir()) or true then
|
||||
local NearRadius = NearRadius or CargoCarrier:GetBoundingRadius( NearRadius ) + 5
|
||||
if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then
|
||||
self:__Load( -1, CargoCarrier, ... )
|
||||
else
|
||||
if self:IsNear( CargoCarrier:GetPointVec2(), 20 ) then
|
||||
self:__Boarding( -1, CargoCarrier, NearRadius, ... )
|
||||
self.RunCount = self.RunCount + 1
|
||||
else
|
||||
self:__Boarding( -2, CargoCarrier, NearRadius, ... )
|
||||
self.RunCount = self.RunCount + 2
|
||||
end
|
||||
if self.RunCount >= 40 then
|
||||
self.RunCount = 0
|
||||
local Speed = 90
|
||||
local Angle = 180
|
||||
local Distance = 0
|
||||
|
||||
--self:F({Unit=self.CargoObject:GetName()})
|
||||
|
||||
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
|
||||
|
||||
|
||||
--- 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
|
||||
|
||||
--self:F({Unit=self.CargoObject:GetName()})
|
||||
|
||||
-- Only destroy the CargoObject if there is a CargoObject (packages don't have CargoObjects).
|
||||
if self.CargoObject then
|
||||
self.CargoObject:Destroy( false )
|
||||
--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
|
||||
@@ -112,7 +112,7 @@
|
||||
--
|
||||
-- # Calculate the Path
|
||||
--
|
||||
-- Finally, we have to calculate the path. This is done by the @{#GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which
|
||||
-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which
|
||||
-- describe the optimal path from the start node to the end node.
|
||||
--
|
||||
-- By default, the start and end node are include in the table that is returned.
|
||||
@@ -320,12 +320,10 @@ end
|
||||
|
||||
--- Set valid neighbours to be in a certain distance.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number MaxDistance (Optional) Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourDistance(MaxDistance)
|
||||
|
||||
MaxDistance = MaxDistance or 2000
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance)
|
||||
|
||||
return self
|
||||
@@ -333,12 +331,10 @@ end
|
||||
|
||||
--- Set valid neighbours to be in a certain distance.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number MaxDistance (Optional) Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourRoad(MaxDistance)
|
||||
|
||||
MaxDistance = MaxDistance or 2000
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.Road, MaxDistance)
|
||||
|
||||
return self
|
||||
@@ -401,10 +397,10 @@ end
|
||||
-- The coordinate system is oriented along the line between start and end point.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed.
|
||||
-- @param #number BoxHY (Optional) Box "height" in meters along the y-coordinate. Default 40000 meters (40 km).
|
||||
-- @param #number SpaceX (Optional) Additional space in meters before start and after end coordinate. Default 10000 meters (10 km).
|
||||
-- @param #number deltaX (Optional) Increment in the direction of start to end coordinate in meters. Default 2000 meters.
|
||||
-- @param #number deltaY (Optional) Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX.
|
||||
-- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km).
|
||||
-- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km).
|
||||
-- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters.
|
||||
-- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX.
|
||||
-- @param #boolean MarkGrid If true, create F10 map markers at grid nodes.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid)
|
||||
@@ -551,7 +547,7 @@ end
|
||||
--- Function to check if distance between two nodes is less than a threshold distance.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @param #number distmax (Optional) Max distance in meters. Default is 2000 m.
|
||||
-- @param #number distmax Max distance in meters. Default is 2000 m.
|
||||
-- @return #boolean If true, distance between the two nodes is below threshold.
|
||||
function ASTAR.DistMax(nodeA, nodeB, distmax)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,10 +8,6 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Beacon)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Beacon
|
||||
@@ -20,31 +16,29 @@
|
||||
--- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon
|
||||
--
|
||||
-- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
|
||||
-- There are two types of BEACONs available : the (aircraft) TACAN Beacon and the general purpose Radio Beacon.
|
||||
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very useful to simulate the battery time if your BEACON is
|
||||
-- attach to a cargo crate, for example.
|
||||
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
|
||||
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
|
||||
-- attach to a cargo crate, for exemple.
|
||||
--
|
||||
-- ## Aircraft TACAN Beacon usage
|
||||
-- ## AA TACAN Beacon usage
|
||||
--
|
||||
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON.ActivateTACAN}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON.StopRadioBeacon}() to stop it.
|
||||
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
|
||||
-- Use @#BEACON:StopAATACAN}() to stop it.
|
||||
--
|
||||
-- ## General Purpose Radio Beacon usage
|
||||
--
|
||||
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
|
||||
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON.RadioBeacon}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON.StopRadioBeacon}() to stop it.
|
||||
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- @type BEACON
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{Wrapper.Controllable#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @field #number UniqueName Counter to make the unique naming work.
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name = nil,
|
||||
UniqueName = 0,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
@@ -78,12 +72,12 @@ BEACON.Type={
|
||||
TACAN = 4,
|
||||
VORTAC = 5,
|
||||
RSBN = 128,
|
||||
BROADCAST_STATION = 1024,
|
||||
BROADCAST_STATION = 1024,
|
||||
HOMER = 8,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
ILS_FAR_HOMER = 16408,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_LOCALIZER = 16640,
|
||||
ILS_GLIDESLOPE = 16896,
|
||||
PRMG_LOCALIZER = 33024,
|
||||
@@ -103,24 +97,24 @@ BEACON.Type={
|
||||
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
|
||||
-- @field #number VOR Very High Frequency Omni-Directional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glide slope.
|
||||
-- @field #number ILS_GLIDESLOPE ILS glideslope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glide slope.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
|
||||
-- @field #number BROADCAST_STATION Broadcast station.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omni-directional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
|
||||
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
|
||||
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
|
||||
-- @field #number ICLS_LOCALIZER Carrier landing system.
|
||||
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
|
||||
BEACON.System={
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
TACAN_TANKER_X = 4,
|
||||
TACAN_TANKER_Y = 5,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
ILS_GLIDESLOPE = 8,
|
||||
PRMG_LOCALIZER = 9,
|
||||
PRMG_GLIDESLOPE = 10,
|
||||
@@ -136,16 +130,16 @@ BEACON.System={
|
||||
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
|
||||
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
|
||||
-- @param #BEACON self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Wrapper.Positionable} that will receive radio capabilities.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
|
||||
function BEACON:New(Positionable)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#BEACON
|
||||
|
||||
|
||||
-- Debug.
|
||||
self:F(Positionable)
|
||||
|
||||
|
||||
-- Set positionable.
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
@@ -153,11 +147,12 @@ function BEACON:New(Positionable)
|
||||
self:I(string.format("New BEACON %s", tostring(self.name)))
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
|
||||
@@ -174,28 +169,28 @@ end
|
||||
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
|
||||
|
||||
|
||||
Mode=Mode or "Y"
|
||||
|
||||
|
||||
-- Get frequency.
|
||||
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
|
||||
|
||||
|
||||
-- Check.
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-- Beacon type.
|
||||
local Type=BEACON.Type.TACAN
|
||||
|
||||
|
||||
-- Beacon system.
|
||||
local System=BEACON.System.TACAN
|
||||
|
||||
|
||||
-- Check if unit is an aircraft and set system accordingly.
|
||||
local AA=self.Positionable:IsAir()
|
||||
|
||||
|
||||
|
||||
|
||||
if AA then
|
||||
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
|
||||
-- Check if "Y" mode is selected for aircraft.
|
||||
@@ -206,21 +201,21 @@ function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
System=BEACON.System.TACAN_TANKER_Y
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
|
||||
-- Debug.
|
||||
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
|
||||
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop scheduler.
|
||||
|
||||
-- Stop sheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -232,52 +227,25 @@ end
|
||||
-- @return #BEACON self
|
||||
function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
|
||||
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
|
||||
-- Debug
|
||||
self:T2({"ICLS BEACON started!"})
|
||||
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
|
||||
|
||||
-- Stop scheduler
|
||||
|
||||
-- Stop sheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates a LINK4 BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Frequency LINK4 FRequency in MHz, eg 336.
|
||||
-- @param #string Morse The ID that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
function BEACON:ActivateLink4(Frequency, Morse, Duration)
|
||||
self:F({Frequency=Frequency, Morse=Morse, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2({"LINK4 BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateLink4(Frequency,UnitID,Morse)
|
||||
|
||||
-- Stop sheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:CommandDeactivateLink4(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- DEPRECATED: Please use @{#BEACON.ActivateTACAN}() instead.
|
||||
-- Activates a TACAN BEACON on an Aircraft.
|
||||
--- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
@@ -292,21 +260,20 @@ end
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
self:E("This method is DEPRECATED! Please use ActivateTACAN() instead.")
|
||||
|
||||
|
||||
local IsValid = true
|
||||
|
||||
|
||||
if not self.Positionable:IsAir() then
|
||||
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
|
||||
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
|
||||
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing or 14 (TACAN_AA_MODE_Y) if it does not
|
||||
local System
|
||||
if Bearing then
|
||||
@@ -314,7 +281,7 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
else
|
||||
System = BEACON.System.TACAN_AA_MODE_Y
|
||||
end
|
||||
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
@@ -329,7 +296,7 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
modeChannel = "Y",
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New(nil,
|
||||
function()
|
||||
@@ -337,7 +304,7 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -357,10 +324,11 @@ function BEACON:StopAATACAN()
|
||||
end
|
||||
end
|
||||
|
||||
--- Activates a general purpose Radio Beacon
|
||||
|
||||
--- Activates a general pupose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only a few DCS Modules can home on radio beacons at the time of writing, i.e. the Mi-8, Huey, Gazelle etc.
|
||||
-- The following e.g. can home in on these specific frequencies :
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
@@ -387,8 +355,6 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
Modulation = Modulation or radio.modulation.AM
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
@@ -399,53 +365,50 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension? ", FileName})
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
|
||||
-- Check the Frequency
|
||||
if type(Frequency) ~= "number" and IsValid then
|
||||
self:E({"Frequency invalid. ", Frequency})
|
||||
IsValid = false
|
||||
end
|
||||
Frequency = Frequency * 1000000 -- Conversion to Hz
|
||||
|
||||
|
||||
-- Check the modulation
|
||||
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
|
||||
-- Check the Power
|
||||
if type(Power) ~= "number" and IsValid then
|
||||
self:E({"Power is invalid. ", Power})
|
||||
IsValid = false
|
||||
end
|
||||
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
|
||||
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
BEACON.UniqueName = BEACON.UniqueName + 1
|
||||
self.BeaconName = "MooseBeacon"..tostring(BEACON.UniqueName)
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, self.BeaconName)
|
||||
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the Radio Beacon
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(self.BeaconName)
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -463,16 +426,16 @@ function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
return nil -- error in arguments
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
|
||||
-- I have no idea what it does but it seems to work
|
||||
local A = 1151 -- 'X', channel >= 64
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
@@ -483,6 +446,6 @@ function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
end
|
||||
@@ -1,934 +0,0 @@
|
||||
--- **Core** - Client Menu Management.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * For complex, non-static menu structures
|
||||
-- * Lightweigt implementation as alternative to MENU
|
||||
-- * Separation of menu tree creation from menu on the clients's side
|
||||
-- * Works with a SET_CLIENT set of clients
|
||||
-- * Allow manipulation of the shadow tree in various ways
|
||||
-- * Push to all or only one client
|
||||
-- * Change entries' menu text
|
||||
-- * Option to make an entry usable once only across all clients
|
||||
-- * Auto appends GROUP and CLIENT objects to menu calls
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.ClientMenu
|
||||
-- @image Core_Menu.JPG
|
||||
-- last change: Sept 2025
|
||||
|
||||
-- TODO
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
-- CLIENTMENU
|
||||
--
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
---
|
||||
-- @type CLIENTMENU
|
||||
-- @field #string ClassName Class Name
|
||||
-- @field #string lid Lid for log entries
|
||||
-- @field #string version Version string
|
||||
-- @field #string name Name
|
||||
-- @field #string groupname Group name
|
||||
-- @field #table path
|
||||
-- @field #table parentpath
|
||||
-- @field #CLIENTMENU Parent
|
||||
-- @field Wrapper.Client#CLIENT client
|
||||
-- @field #number GroupID Group ID
|
||||
-- @field #number ID Entry ID
|
||||
-- @field Wrapper.Group#GROUP group
|
||||
-- @field #string UUID Unique ID based on path+name
|
||||
-- @field #string Function
|
||||
-- @field #table Functionargs
|
||||
-- @field #table Children
|
||||
-- @field #boolean Once
|
||||
-- @field #boolean Generic
|
||||
-- @field #boolean debug
|
||||
-- @field #CLIENTMENUMANAGER Controller
|
||||
-- @field #active boolean
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
---
|
||||
-- @field #CLIENTMENU
|
||||
CLIENTMENU = {
|
||||
ClassName = "CLIENTMENU",
|
||||
lid = "",
|
||||
version = "0.1.3",
|
||||
name = nil,
|
||||
path = nil,
|
||||
group = nil,
|
||||
client = nil,
|
||||
GroupID = nil,
|
||||
Children = {},
|
||||
Once = false,
|
||||
Generic = false,
|
||||
debug = false,
|
||||
Controller = nil,
|
||||
groupname = nil,
|
||||
active = false,
|
||||
}
|
||||
|
||||
---
|
||||
-- @field #CLIENTMENU_ID
|
||||
CLIENTMENU_ID = 0
|
||||
|
||||
--- Create an new CLIENTMENU object.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param Wrapper.Client#CLIENT Client The client for whom this entry is. Leave as nil for a generic entry.
|
||||
-- @param #string Text Text of the F10 menu entry.
|
||||
-- @param #CLIENTMENU Parent The parent menu entry.
|
||||
-- @param #string Function (optional) Function to call when the entry is used.
|
||||
-- @param ... (optional) Arguments for the Function, comma separated
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #CLIENTMENU
|
||||
CLIENTMENU_ID = CLIENTMENU_ID + 1
|
||||
self.ID = CLIENTMENU_ID
|
||||
if Client then
|
||||
self.group = Client:GetGroup()
|
||||
self.client = Client
|
||||
self.GroupID = self.group:GetID()
|
||||
self.groupname = self.group:GetName() or "Unknown Groupname"
|
||||
else
|
||||
self.Generic = true
|
||||
end
|
||||
self.name = Text or "unknown entry"
|
||||
if Parent then
|
||||
if Parent:IsInstanceOf("MENU_BASE") then
|
||||
self.parentpath = Parent.MenuPath
|
||||
else
|
||||
self.parentpath = Parent:GetPath()
|
||||
Parent:AddChild(self)
|
||||
end
|
||||
end
|
||||
self.Parent = Parent
|
||||
self.Function = Function
|
||||
self.Functionargs = arg or {}
|
||||
table.insert(self.Functionargs,self.group)
|
||||
table.insert(self.Functionargs,self.client)
|
||||
if self.Functionargs and self.debug then
|
||||
self:T({"Functionargs",self.Functionargs})
|
||||
end
|
||||
if not self.Generic and self.active == false then
|
||||
if Function ~= nil then
|
||||
local ErrorHandler = function( errmsg )
|
||||
env.info( "MOOSE Error in CLIENTMENU COMMAND function: " .. errmsg )
|
||||
if BASE.Debug ~= nil then
|
||||
env.info( BASE.Debug.traceback() )
|
||||
end
|
||||
return errmsg
|
||||
end
|
||||
self.CallHandler = function()
|
||||
local function MenuFunction()
|
||||
return self.Function( unpack( self.Functionargs ) )
|
||||
end
|
||||
local Status, Result = xpcall( MenuFunction, ErrorHandler)
|
||||
if self.Once == true then
|
||||
self:Clear()
|
||||
end
|
||||
end
|
||||
self.path = missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath, self.CallHandler)
|
||||
self.active = true
|
||||
else
|
||||
self.path = missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath)
|
||||
self.active = true
|
||||
end
|
||||
else
|
||||
if self.parentpath then
|
||||
self.path = UTILS.DeepCopy(self.parentpath)
|
||||
else
|
||||
self.path = {}
|
||||
end
|
||||
self.path[#self.path+1] = Text
|
||||
end
|
||||
self.UUID = table.concat(self.path,";")
|
||||
self:T({self.UUID})
|
||||
self.Once = false
|
||||
-- Log id.
|
||||
self.lid=string.format("CLIENTMENU %s | %s | ", self.ID, self.name)
|
||||
self:T(self.lid.."Created")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a UUID
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param #CLIENTMENU Parent The parent object if any
|
||||
-- @param #string Text The menu entry text
|
||||
-- @return #string UUID
|
||||
function CLIENTMENU:CreateUUID(Parent,Text)
|
||||
local path = {}
|
||||
if Parent and Parent.path then
|
||||
path = Parent.path
|
||||
end
|
||||
path[#path+1] = Text
|
||||
local UUID = table.concat(path,";")
|
||||
return UUID
|
||||
end
|
||||
|
||||
--- Set the CLIENTMENUMANAGER for this entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param #CLIENTMENUMANAGER Controller The controlling object.
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:SetController(Controller)
|
||||
self.Controller = Controller
|
||||
return self
|
||||
end
|
||||
|
||||
--- The entry will be deleted after being used used - for menu entries with functions only.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:SetOnce()
|
||||
self:T(self.lid.."SetOnce")
|
||||
self.Once = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove the entry from the F10 menu.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:RemoveF10()
|
||||
self:T(self.lid.."RemoveF10")
|
||||
if self.GroupID then
|
||||
--self:I(self.lid.."Removing "..table.concat(self.path,";"))
|
||||
local function RemoveFunction()
|
||||
return missionCommands.removeItemForGroup(self.GroupID , self.path )
|
||||
end
|
||||
local status, err = pcall(RemoveFunction)
|
||||
if not status then
|
||||
self:I(string.format("**** Error Removing Menu Entry %s for %s!",tostring(self.name),self.groupname))
|
||||
end
|
||||
self.active = false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the menu path table.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #table Path
|
||||
function CLIENTMENU:GetPath()
|
||||
self:T(self.lid.."GetPath")
|
||||
return self.path
|
||||
end
|
||||
|
||||
--- Get the UUID.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #string UUID
|
||||
function CLIENTMENU:GetUUID()
|
||||
self:T(self.lid.."GetUUID")
|
||||
return self.UUID
|
||||
end
|
||||
|
||||
--- Link a child entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param #CLIENTMENU Child The entry to link as a child.
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:AddChild(Child)
|
||||
self:T(self.lid.."AddChild "..Child.ID)
|
||||
table.insert(self.Children,Child.ID,Child)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove a child entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param #CLIENTMENU Child The entry to remove from the children.
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:RemoveChild(Child)
|
||||
self:T(self.lid.."RemoveChild "..Child.ID)
|
||||
table.remove(self.Children,Child.ID)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove all subentries (children) from this entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:RemoveSubEntries()
|
||||
self:T(self.lid.."RemoveSubEntries")
|
||||
self:T({self.Children})
|
||||
for _id,_entry in pairs(self.Children) do
|
||||
self:T("Removing ".._id)
|
||||
if _entry then
|
||||
_entry:RemoveSubEntries()
|
||||
_entry:RemoveF10()
|
||||
if _entry.Parent then
|
||||
_entry.Parent:RemoveChild(self)
|
||||
end
|
||||
--if self.Controller then
|
||||
--self.Controller:_RemoveByID(_entry.ID)
|
||||
--end
|
||||
--_entry = nil
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove this entry and all subentries (children) from this entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:Clear()
|
||||
self:T(self.lid.."Clear")
|
||||
for _id,_entry in pairs(self.Children) do
|
||||
if _entry then
|
||||
_entry:RemoveSubEntries()
|
||||
_entry = nil
|
||||
end
|
||||
end
|
||||
self:RemoveF10()
|
||||
if self.Parent then
|
||||
self.Parent:RemoveChild(self)
|
||||
end
|
||||
--if self.Controller then
|
||||
--self.Controller:_RemoveByID(self.ID)
|
||||
--end
|
||||
return self
|
||||
end
|
||||
|
||||
-- TODO
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
-- CLIENTMENUMANAGER
|
||||
--
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
--- Class CLIENTMENUMANAGER
|
||||
-- @type CLIENTMENUMANAGER
|
||||
-- @field #string ClassName Class Name
|
||||
-- @field #string lid Lid for log entries
|
||||
-- @field #string version Version string
|
||||
-- @field #string name Name
|
||||
-- @field Core.Set#SET_CLIENT clientset The set of clients this menu manager is for
|
||||
-- @field #table flattree
|
||||
-- @field #table rootentries
|
||||
-- @field #table menutree
|
||||
-- @field #number entrycount
|
||||
-- @field #boolean debug
|
||||
-- @field #table PlayerMenu
|
||||
-- @field #number Coalition
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *As a child my family's menu consisted of two choices: take it, or leave it.*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## CLIENTMENU and CLIENTMENUMANAGER
|
||||
--
|
||||
-- Manage menu structures for a SET_CLIENT of clients.
|
||||
--
|
||||
-- ## Concept
|
||||
--
|
||||
-- Separate creation of a menu tree structure from pushing it to each client. Create a shadow "reference" menu structure tree for your client pilot's in a mission.
|
||||
-- This can then be propagated to all clients. Manipulate the entries in the structure with removing, clearing or changing single entries, create replacement sub-structures
|
||||
-- for entries etc, push to one or all clients.
|
||||
--
|
||||
-- Many functions can either change the tree for one client or for all clients.
|
||||
--
|
||||
-- ## Conceptual remarks
|
||||
--
|
||||
-- There's a couple of things to fully understand:
|
||||
--
|
||||
-- 1) **CLIENTMENUMANAGER** manages a set of entries from **CLIENTMENU**, it's main purpose is to administer the *shadow menu tree*, ie. a menu structure which is not
|
||||
-- (yet) visible to any client
|
||||
-- 2) The entries are **CLIENTMENU** objects, which are linked in a tree form. There's two ways to create them:
|
||||
-- A) in the manager with ":NewEntry()" which initially
|
||||
-- adds it to the shadow menu **only**
|
||||
-- B) stand-alone directly as `CLIENTMENU:NewEntry()` - here it depends on whether or not you gave a CLIENT object if the entry is created as generic entry or pushed
|
||||
-- a **specific** client. **Be aware** though that the entries are not managed by the CLIENTMANAGER before the next step!
|
||||
-- A generic entry can be added to the manager (and the shadow tree) with `:AddEntry()` - this will also push it to all clients(!) if no client is given, or a specific client only.
|
||||
-- 3) Pushing only works for alive clients.
|
||||
-- 4) Live and shadow tree entries are managed via the CLIENTMENUMANAGER object.
|
||||
-- 5) `Propagate()`refreshes the menu tree for all, or a single client.
|
||||
--
|
||||
-- ## Create a base reference tree and send to all clients
|
||||
--
|
||||
-- local clientset = SET_CLIENT:New():FilterStart()
|
||||
--
|
||||
-- local menumgr = CLIENTMENUMANAGER:New(clientset,"Dayshift")
|
||||
-- local mymenu = menumgr:NewEntry("Top")
|
||||
-- local mymenu_lv1a = menumgr:NewEntry("Level 1 a",mymenu)
|
||||
-- local mymenu_lv1b = menumgr:NewEntry("Level 1 b",mymenu)
|
||||
-- -- next one is a command menu entry, which can only be used once
|
||||
-- local mymenu_lv1c = menumgr:NewEntry("Action Level 1 c",mymenu, testfunction, "testtext"):SetOnce()
|
||||
--
|
||||
-- local mymenu_lv2a = menumgr:NewEntry("Go here",mymenu_lv1a)
|
||||
-- local mymenu_lv2b = menumgr:NewEntry("Level 2 ab",mymenu_lv1a)
|
||||
-- local mymenu_lv2c = menumgr:NewEntry("Level 2 ac",mymenu_lv1a)
|
||||
--
|
||||
-- local mymenu_lv2ba = menumgr:NewEntry("Level 2 ba",mymenu_lv1b)
|
||||
-- local mymenu_lv2bb = menumgr:NewEntry("Level 2 bb",mymenu_lv1b)
|
||||
-- local mymenu_lv2bc = menumgr:NewEntry("Level 2 bc",mymenu_lv1b)
|
||||
--
|
||||
-- local mymenu_lv3a = menumgr:NewEntry("Level 3 aaa",mymenu_lv2a)
|
||||
-- local mymenu_lv3b = menumgr:NewEntry("Level 3 aab",mymenu_lv2a)
|
||||
-- local mymenu_lv3c = menumgr:NewEntry("Level 3 aac",mymenu_lv2a)
|
||||
--
|
||||
-- menumgr:Propagate() -- propagate **once** to all clients in the SET_CLIENT
|
||||
--
|
||||
-- ## Remove a single entry's subtree
|
||||
--
|
||||
-- menumgr:RemoveSubEntries(mymenu_lv3a)
|
||||
--
|
||||
-- ## Remove a single entry and also it's subtree
|
||||
--
|
||||
-- menumgr:DeleteEntry(mymenu_lv3a)
|
||||
--
|
||||
-- ## Add a single entry
|
||||
--
|
||||
-- local baimenu = menumgr:NewEntry("BAI",mymenu_lv1b)
|
||||
--
|
||||
-- menumgr:AddEntry(baimenu)
|
||||
--
|
||||
-- ## Add an entry with a function
|
||||
--
|
||||
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, Argument1, Argument1)
|
||||
--
|
||||
-- Now, the class will **automatically append the call with GROUP and CLIENT objects**, as this is can only be done when pushing the entry to the clients. So, the actual function implementation needs to look like this:
|
||||
--
|
||||
-- function TestFunction( Argument1, Argument2, Group, Client)
|
||||
--
|
||||
-- **Caveat is**, that you need to ensure your arguments are not **nil** or **false**, as LUA will optimize those away. You would end up having Group and Client in wrong places in the function call. Hence,
|
||||
-- if you need/ want to send **nil** or **false**, send a place holder instead and ensure your function can handle this, e.g.
|
||||
--
|
||||
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, "nil", Argument1)
|
||||
--
|
||||
-- ## Change the text of a leaf entry in the menu tree
|
||||
--
|
||||
-- menumgr:ChangeEntryTextForAll(mymenu_lv1b,"Attack")
|
||||
--
|
||||
-- ## Reset a single clients menu tree
|
||||
--
|
||||
-- menumgr:ResetMenu(client)
|
||||
--
|
||||
-- ## Reset all and clear the reference tree
|
||||
--
|
||||
-- menumgr:ResetMenuComplete()
|
||||
--
|
||||
-- ## Set to auto-propagate for CLIENTs joining the SET_CLIENT **after** the script is loaded - handy if you have a single menu tree.
|
||||
--
|
||||
-- menumgr:InitAutoPropagation()
|
||||
--
|
||||
-- @field #CLIENTMENUMANAGER
|
||||
CLIENTMENUMANAGER = {
|
||||
ClassName = "CLIENTMENUMANAGER",
|
||||
lid = "",
|
||||
version = "0.1.7",
|
||||
name = nil,
|
||||
clientset = nil,
|
||||
menutree = {},
|
||||
flattree = {},
|
||||
playertree = {},
|
||||
entrycount = 0,
|
||||
rootentries = {},
|
||||
debug = true,
|
||||
PlayerMenu = {},
|
||||
Coalition = nil,
|
||||
}
|
||||
|
||||
--- Create a new ClientManager instance.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param Core.Set#SET_CLIENT ClientSet The set of clients to manage.
|
||||
-- @param #string Alias The name of this manager.
|
||||
-- @param #number Coalition (Optional) Coalition of this Manager, defaults to coalition.side.BLUE
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:New(ClientSet, Alias, Coalition)
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #CLIENTMENUMANAGER
|
||||
self.clientset = ClientSet
|
||||
self.PlayerMenu = {}
|
||||
self.name = Alias or "Nightshift"
|
||||
self.Coalition = Coalition or coalition.side.BLUE
|
||||
-- Log id.
|
||||
self.lid=string.format("CLIENTMENUMANAGER %s | %s | ", self.version, self.name)
|
||||
if self.debug then
|
||||
self:I(self.lid.."Created")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Event handling
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:_EventHandler(EventData,Retry)
|
||||
self:T(self.lid.."_EventHandler: "..EventData.id)
|
||||
--self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName))
|
||||
if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then
|
||||
self:T(self.lid.."Leave event for player: "..tostring(EventData.IniPlayerName))
|
||||
local Client = _DATABASE:FindClient( EventData.IniUnitName )
|
||||
if Client then
|
||||
self:ResetMenu(Client)
|
||||
end
|
||||
elseif (EventData.id == EVENTS.PlayerEnterAircraft) and EventData.IniCoalition == self.Coalition then
|
||||
if EventData.IniPlayerName and EventData.IniGroup then
|
||||
if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then
|
||||
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName)
|
||||
if not Retry then
|
||||
-- try again in 2 secs
|
||||
self:ScheduleOnce(2,CLIENTMENUMANAGER._EventHandler,self,EventData,true)
|
||||
end
|
||||
return self
|
||||
end
|
||||
--self:I(self.lid.."Join event for player: "..EventData.IniPlayerName)
|
||||
local player = _DATABASE:FindClient( EventData.IniUnitName )
|
||||
self:Propagate(player)
|
||||
end
|
||||
elseif EventData.id == EVENTS.PlayerEnterUnit then
|
||||
-- special for CA slots
|
||||
local grp = GROUP:FindByName(EventData.IniGroupName)
|
||||
if grp:IsGround() then
|
||||
self:T(string.format("Player %s entered GROUND unit %s!",EventData.IniPlayerName,EventData.IniUnitName))
|
||||
local IsPlayer = EventData.IniDCSUnit:getPlayerName()
|
||||
if IsPlayer then
|
||||
|
||||
local client=_DATABASE.CLIENTS[EventData.IniDCSUnitName] --Wrapper.Client#CLIENT
|
||||
|
||||
-- Add client in case it does not exist already.
|
||||
if not client then
|
||||
|
||||
-- Debug info.
|
||||
self:I(string.format("Player '%s' joined ground unit '%s' of group '%s'", tostring(EventData.IniPlayerName), tostring(EventData.IniDCSUnitName), tostring(EventData.IniDCSGroupName)))
|
||||
|
||||
client=_DATABASE:AddClient(EventData.IniDCSUnitName)
|
||||
|
||||
-- Add player.
|
||||
client:AddPlayer(EventData.IniPlayerName)
|
||||
|
||||
-- Add player.
|
||||
if not _DATABASE.PLAYERS[EventData.IniPlayerName] then
|
||||
_DATABASE:AddPlayer( EventData.IniUnitName, EventData.IniPlayerName )
|
||||
end
|
||||
|
||||
-- Player settings.
|
||||
local Settings = SETTINGS:Set( EventData.IniPlayerName )
|
||||
Settings:SetPlayerMenu(EventData.IniUnit)
|
||||
end
|
||||
--local player = _DATABASE:FindClient( EventData.IniPlayerName )
|
||||
self:Propagate(client)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this Client Manager to auto-propagate menus **once** to newly joined players. Useful if you have **one** menu structure only. Does not automatically push follow-up changes to the client(s).
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:InitAutoPropagation()
|
||||
-- Player Events
|
||||
self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.Ejection, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.Crash, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler)
|
||||
self:SetEventPriority(6)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new entry in the **generic** structure.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string Text Text of the F10 menu entry.
|
||||
-- @param #CLIENTMENU Parent The parent menu entry.
|
||||
-- @param #string Function (optional) Function to call when the entry is used.
|
||||
-- @param ... (optional) Arguments for the Function, comma separated.
|
||||
-- @return #CLIENTMENU Entry
|
||||
function CLIENTMENUMANAGER:NewEntry(Text,Parent,Function,...)
|
||||
self:T(self.lid.."NewEntry "..Text or "None")
|
||||
self.entrycount = self.entrycount + 1
|
||||
local entry = CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg))
|
||||
if not Parent then
|
||||
self.rootentries[self.entrycount] = entry
|
||||
end
|
||||
local depth = #entry.path
|
||||
if not self.menutree[depth] then self.menutree[depth] = {} end
|
||||
table.insert(self.menutree[depth],entry.UUID)
|
||||
self.flattree[entry.UUID] = entry
|
||||
return entry
|
||||
end
|
||||
|
||||
--- Check matching entry in the generic structure by UUID.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string UUID UUID of the menu entry.
|
||||
-- @return #boolean Exists
|
||||
function CLIENTMENUMANAGER:EntryUUIDExists(UUID)
|
||||
local exists = self.flattree[UUID] and true or false
|
||||
return exists
|
||||
end
|
||||
|
||||
--- Find matching entry in the generic structure by UUID.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string UUID UUID of the menu entry.
|
||||
-- @return #CLIENTMENU Entry The #CLIENTMENU object found or nil.
|
||||
function CLIENTMENUMANAGER:FindEntryByUUID(UUID)
|
||||
self:T(self.lid.."FindEntryByUUID "..UUID or "None")
|
||||
local entry = nil
|
||||
for _gid,_entry in pairs(self.flattree) do
|
||||
local Entry = _entry -- #CLIENTMENU
|
||||
if Entry and Entry.UUID == UUID then
|
||||
entry = Entry
|
||||
end
|
||||
end
|
||||
return entry
|
||||
end
|
||||
|
||||
--- Find matching entries by text in the generic structure by UUID.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string Text Text or partial text of the menu entry to find.
|
||||
-- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
|
||||
-- @return #table Table of matching UUIDs of #CLIENTMENU objects
|
||||
-- @return #table Table of matching #CLIENTMENU objects
|
||||
-- @return #number Number of matches
|
||||
function CLIENTMENUMANAGER:FindUUIDsByText(Text,Parent)
|
||||
self:T(self.lid.."FindUUIDsByText "..Text or "None")
|
||||
local matches = {}
|
||||
local entries = {}
|
||||
local n = 0
|
||||
for _uuid,_entry in pairs(self.flattree) do
|
||||
local Entry = _entry -- #CLIENTMENU
|
||||
if Parent then
|
||||
if Entry and string.find(Entry.name,Text,1,true) and string.find(Entry.UUID,Parent.UUID,1,true) then
|
||||
table.insert(matches,_uuid)
|
||||
table.insert(entries,Entry )
|
||||
n=n+1
|
||||
end
|
||||
else
|
||||
if Entry and string.find(Entry.name,Text,1,true) then
|
||||
table.insert(matches,_uuid)
|
||||
table.insert(entries,Entry )
|
||||
n=n+1
|
||||
end
|
||||
end
|
||||
end
|
||||
return matches, entries, n
|
||||
end
|
||||
|
||||
--- Find matching entries in the generic structure by the menu text.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string Text Text or partial text of the F10 menu entry.
|
||||
-- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
|
||||
-- @return #table Table of matching #CLIENTMENU objects.
|
||||
-- @return #number Number of matches
|
||||
function CLIENTMENUMANAGER:FindEntriesByText(Text,Parent)
|
||||
self:T(self.lid.."FindEntriesByText "..Text or "None")
|
||||
local matches, objects, number = self:FindUUIDsByText(Text, Parent)
|
||||
return objects, number
|
||||
end
|
||||
|
||||
--- Find matching entries under a parent in the generic structure by UUID.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Parent Find entries under this parent entry.
|
||||
-- @return #table Table of matching UUIDs of #CLIENTMENU objects
|
||||
-- @return #table Table of matching #CLIENTMENU objects
|
||||
-- @return #number Number of matches
|
||||
function CLIENTMENUMANAGER:FindUUIDsByParent(Parent)
|
||||
self:T(self.lid.."FindUUIDsByParent")
|
||||
local matches = {}
|
||||
local entries = {}
|
||||
local n = 0
|
||||
for _uuid,_entry in pairs(self.flattree) do
|
||||
local Entry = _entry -- #CLIENTMENU
|
||||
if Parent then
|
||||
if Entry and string.find(Entry.UUID,Parent.UUID,1,true) then
|
||||
table.insert(matches,_uuid)
|
||||
table.insert(entries,Entry )
|
||||
n=n+1
|
||||
end
|
||||
end
|
||||
end
|
||||
return matches, entries, n
|
||||
end
|
||||
|
||||
--- Find matching entries in the generic structure under a parent.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Parent Find entries under this parent entry.
|
||||
-- @return #table Table of matching #CLIENTMENU objects.
|
||||
-- @return #number Number of matches
|
||||
function CLIENTMENUMANAGER:FindEntriesByParent(Parent)
|
||||
self:T(self.lid.."FindEntriesByParent")
|
||||
local matches, objects, number = self:FindUUIDsByParent(Parent)
|
||||
return objects, number
|
||||
end
|
||||
|
||||
--- Alter the text of a leaf entry in the generic structure and push to one specific client's F10 menu.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The menu entry.
|
||||
-- @param #string Text New Text of the F10 menu entry.
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) The client for whom to alter the entry, if nil done for all clients.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:ChangeEntryText(Entry, Text, Client)
|
||||
self:T(self.lid.."ChangeEntryText "..Text or "None")
|
||||
local newentry = CLIENTMENU:NewEntry(nil,Text,Entry.Parent,Entry.Function,unpack(Entry.Functionargs))
|
||||
self:DeleteF10Entry(Entry,Client)
|
||||
self:DeleteGenericEntry(Entry)
|
||||
if not Entry.Parent then
|
||||
self.rootentries[self.entrycount] = newentry
|
||||
end
|
||||
local depth = #newentry.path
|
||||
if not self.menutree[depth] then self.menutree[depth] = {} end
|
||||
table.insert(self.menutree[depth],newentry.UUID)
|
||||
self.flattree[newentry.UUID] = newentry
|
||||
self:AddEntry(newentry,Client)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Push the complete menu structure to each of the clients in the set - refresh the menu tree of the clients.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, propagate only for this client.
|
||||
-- @return #CLIENTMENU Entry
|
||||
function CLIENTMENUMANAGER:Propagate(Client)
|
||||
self:T(self.lid.."Propagate")
|
||||
--self:I(UTILS.PrintTableToLog(Client,1))
|
||||
local knownunits = {} -- track so we can ID multi seated
|
||||
local Set = self.clientset.Set
|
||||
if Client then
|
||||
Set = {Client}
|
||||
end
|
||||
self:ResetMenu(Client)
|
||||
for _,_client in pairs(Set) do
|
||||
local client = _client -- Wrapper.Client#CLIENT
|
||||
if client and client:IsAlive() then
|
||||
local playerunit = client:GetName()
|
||||
--local playergroup = client:GetGroup()
|
||||
local playername = client:GetPlayerName() or "none"
|
||||
if not knownunits[playerunit] then
|
||||
knownunits[playerunit] = true
|
||||
else
|
||||
self:I("Player in multi seat unit: "..playername)
|
||||
break -- multi seat already build
|
||||
end
|
||||
if not self.playertree[playername] then
|
||||
self.playertree[playername] = {}
|
||||
end
|
||||
for level,branch in pairs (self.menutree) do
|
||||
self:T("Building branch:" .. level)
|
||||
for _,leaf in pairs(branch) do
|
||||
self:T("Building leaf:" .. leaf)
|
||||
local entry = self:FindEntryByUUID(leaf)
|
||||
if entry then
|
||||
self:T("Found generic entry:" .. entry.UUID)
|
||||
local parent = nil
|
||||
if entry.Parent and entry.Parent.UUID then
|
||||
parent = self.playertree[playername][entry.Parent.UUID] or self:FindEntryByUUID(entry.Parent.UUID)
|
||||
end
|
||||
self.playertree[playername][entry.UUID] = CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs))
|
||||
self.playertree[playername][entry.UUID].Once = entry.Once
|
||||
else
|
||||
self:T("NO generic entry for:" .. leaf)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Push a single previously created entry into the F10 menu structure of all clients.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry to add.
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:AddEntry(Entry,Client)
|
||||
self:T(self.lid.."AddEntry")
|
||||
local Set = self.clientset.Set
|
||||
local knownunits = {}
|
||||
if Client then
|
||||
Set = {Client}
|
||||
end
|
||||
for _,_client in pairs(Set) do
|
||||
local client = _client -- Wrapper.Client#CLIENT
|
||||
if client and client:IsAlive() then
|
||||
local playername = client:GetPlayerName() or "None"
|
||||
local unitname = client:GetName()
|
||||
if not knownunits[unitname] then
|
||||
knownunits[unitname] = true
|
||||
else
|
||||
self:I("Player in multi seat unit: "..playername)
|
||||
break
|
||||
end
|
||||
if Entry then
|
||||
self:T("Adding generic entry:" .. Entry.UUID)
|
||||
local parent = nil
|
||||
if not self.playertree[playername] then
|
||||
self.playertree[playername] = {}
|
||||
end
|
||||
if Entry.Parent and Entry.Parent.UUID then
|
||||
parent = self.playertree[playername][Entry.Parent.UUID] or self:FindEntryByUUID(Entry.Parent.UUID)
|
||||
end
|
||||
self.playertree[playername][Entry.UUID] = CLIENTMENU:NewEntry(client,Entry.name,parent,Entry.Function,unpack(Entry.Functionargs))
|
||||
self.playertree[playername][Entry.UUID].Once = Entry.Once
|
||||
else
|
||||
self:T("NO generic entry given")
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Blank out the menu - remove **all root entries** and all entries below from the client's F10 menus, leaving the generic structure untouched.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, remove only for this client.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:ResetMenu(Client)
|
||||
self:T(self.lid.."ResetMenu")
|
||||
for _,_entry in pairs(self.rootentries) do
|
||||
--local RootEntry = self.structure.generic[_entry]
|
||||
if _entry then
|
||||
self:DeleteF10Entry(_entry,Client)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Blank out the menu - remove **all root entries** and all entries below from all clients' F10 menus, and **delete** the generic structure.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:ResetMenuComplete()
|
||||
self:T(self.lid.."ResetMenuComplete")
|
||||
for _,_entry in pairs(self.rootentries) do
|
||||
--local RootEntry = self.structure.generic[_entry]
|
||||
if _entry then
|
||||
self:DeleteF10Entry(_entry)
|
||||
end
|
||||
end
|
||||
self.playertree = nil
|
||||
self.playertree = {}
|
||||
self.rootentries = nil
|
||||
self.rootentries = {}
|
||||
self.menutree = nil
|
||||
self.menutree = {}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove the entry and all entries below the given entry from the client's F10 menus.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry to remove
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:DeleteEntry(Entry,Client)
|
||||
self:T(self.lid.."DeleteEntry")
|
||||
return self:DeleteF10Entry(Entry,Client)
|
||||
end
|
||||
|
||||
--- Remove the entry and all entries below the given entry from the client's F10 menus.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry to remove
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client)
|
||||
self:T(self.lid.."DeleteF10Entry")
|
||||
local Set = self.clientset.Set
|
||||
if Client then
|
||||
Set = {Client}
|
||||
end
|
||||
for _,_client in pairs(Set) do
|
||||
if _client and _client:IsAlive() then
|
||||
local playername = _client:GetPlayerName()
|
||||
if self.playertree[playername] then
|
||||
local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
|
||||
if centry then
|
||||
--self:I("Match for "..Entry.UUID)
|
||||
centry:Clear()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove the entry and all entries below the given entry from the generic tree.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry to remove
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:DeleteGenericEntry(Entry)
|
||||
self:T(self.lid.."DeleteGenericEntry")
|
||||
|
||||
if Entry.Children and #Entry.Children > 0 then
|
||||
self:RemoveGenericSubEntries(Entry)
|
||||
end
|
||||
|
||||
local depth = #Entry.path
|
||||
local uuid = Entry.UUID
|
||||
|
||||
local tbl = UTILS.DeepCopy(self.menutree)
|
||||
|
||||
if tbl[depth] then
|
||||
for i=depth,#tbl do
|
||||
--self:I("Level = "..i)
|
||||
for _id,_uuid in pairs(tbl[i]) do
|
||||
self:T(_uuid)
|
||||
if string.find(_uuid,uuid,1,true) or _uuid == uuid then
|
||||
--self:I("Match for ".._uuid)
|
||||
self.menutree[i][_id] = nil
|
||||
self.flattree[_uuid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove all entries below the given entry from the generic tree.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry where to start. This entry stays.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:RemoveGenericSubEntries(Entry)
|
||||
self:T(self.lid.."RemoveGenericSubEntries")
|
||||
|
||||
local depth = #Entry.path + 1
|
||||
local uuid = Entry.UUID
|
||||
|
||||
local tbl = UTILS.DeepCopy(self.menutree)
|
||||
|
||||
if tbl[depth] then
|
||||
for i=depth,#tbl do
|
||||
self:T("Level = "..i)
|
||||
for _id,_uuid in pairs(tbl[i]) do
|
||||
self:T(_uuid)
|
||||
if string.find(_uuid,uuid,1,true) then
|
||||
self:T("Match for ".._uuid)
|
||||
self.menutree[i][_id] = nil
|
||||
self.flattree[_uuid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Remove all entries below the given entry from the client's F10 menus.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry where to start. This entry stays.
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:RemoveF10SubEntries(Entry,Client)
|
||||
self:T(self.lid.."RemoveSubEntries")
|
||||
local Set = self.clientset.Set
|
||||
if Client then
|
||||
Set = {Client}
|
||||
end
|
||||
for _,_client in pairs(Set) do
|
||||
if _client and _client:IsAlive() then
|
||||
local playername = _client:GetPlayerName()
|
||||
if self.playertree[playername] then
|
||||
local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
|
||||
centry:RemoveSubEntries()
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
-- End ClientMenu
|
||||
--
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
@@ -1,464 +0,0 @@
|
||||
--- **Core** - Define any or all conditions to be evaluated.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Add arbitrary numbers of conditon functions
|
||||
-- * Evaluate *any* or *all* conditions
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Core/Condition).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Core.Condition
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- CONDITON class.
|
||||
-- @type CONDITION
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #string name Name of the condition.
|
||||
-- @field #boolean isAny General functions are evaluated as any condition.
|
||||
-- @field #boolean negateResult Negate result of evaluation.
|
||||
-- @field #boolean noneResult Boolean that is returned if no condition functions at all were specified.
|
||||
-- @field #table functionsGen General condition functions.
|
||||
-- @field #table functionsAny Any condition functions.
|
||||
-- @field #table functionsAll All condition functions.
|
||||
-- @field #number functionCounter Running number to determine the unique ID of condition functions.
|
||||
-- @field #boolean defaultPersist Default persistence of condition functions.
|
||||
--
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *Better three hours too soon than a minute too late.* - William Shakespeare
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The CONDITION Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #CONDITION
|
||||
CONDITION = {
|
||||
ClassName = "CONDITION",
|
||||
lid = nil,
|
||||
functionsGen = {},
|
||||
functionsAny = {},
|
||||
functionsAll = {},
|
||||
functionCounter = 0,
|
||||
defaultPersist = false,
|
||||
}
|
||||
|
||||
--- Condition function.
|
||||
-- @type CONDITION.Function
|
||||
-- @field #number uid Unique ID of the condition function.
|
||||
-- @field #string type Type of the condition function: "gen", "any", "all".
|
||||
-- @field #boolean persistence If `true`, this is persistent.
|
||||
-- @field #function func Callback function to check for a condition. Must return a `#boolean`.
|
||||
-- @field #table arg (Optional) Arguments passed to the condition callback function if any.
|
||||
|
||||
--- CONDITION class version.
|
||||
-- @field #string version
|
||||
CONDITION.version="0.3.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Make FSM. No sure if really necessary.
|
||||
-- DONE: Option to remove condition functions.
|
||||
-- DONE: Persistence option for condition functions.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new CONDITION object.
|
||||
-- @param #CONDITION self
|
||||
-- @param #string Name (Optional) Name used in the logs.
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:New(Name)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#CONDITION
|
||||
|
||||
self.name=Name or "Condition X"
|
||||
|
||||
self:SetNoneResult(false)
|
||||
|
||||
self.lid=string.format("%s | ", self.name)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set that general condition functions return `true` if `any` function returns `true`. Default is that *all* functions must return `true`.
|
||||
-- @param #CONDITION self
|
||||
-- @param #boolean Any If `true`, *any* condition can be true. Else *all* conditions must result `true`.
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:SetAny(Any)
|
||||
self.isAny=Any
|
||||
return self
|
||||
end
|
||||
|
||||
--- Negate result.
|
||||
-- @param #CONDITION self
|
||||
-- @param #boolean Negate If `true`, result is negated else not.
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:SetNegateResult(Negate)
|
||||
self.negateResult=Negate
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether `true` or `false` is returned, if no conditions at all were specified. By default `false` is returned.
|
||||
-- @param #CONDITION self
|
||||
-- @param #boolean ReturnValue Returns this boolean.
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:SetNoneResult(ReturnValue)
|
||||
if not ReturnValue then
|
||||
self.noneResult=false
|
||||
else
|
||||
self.noneResult=true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether condition functions are persistent, *i.e.* are removed.
|
||||
-- @param #CONDITION self
|
||||
-- @param #boolean IsPersistent If `true`, condition functions are persistent.
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:SetDefaultPersistence(IsPersistent)
|
||||
self.defaultPersist=IsPersistent
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
|
||||
-- @param #CONDITION self
|
||||
-- @param #function Function The function to call.
|
||||
-- @param ... (Optional) Parameters passed to the function (if any).
|
||||
--
|
||||
-- @usage
|
||||
-- local function isAequalB(a, b)
|
||||
-- return a==b
|
||||
-- end
|
||||
--
|
||||
-- myCondition:AddFunction(isAequalB, a, b)
|
||||
--
|
||||
-- @return #CONDITION.Function Condition function table.
|
||||
function CONDITION:AddFunction(Function, ...)
|
||||
|
||||
-- Condition function.
|
||||
local condition=self:_CreateCondition(0, Function, ...)
|
||||
|
||||
-- Add to table.
|
||||
table.insert(self.functionsGen, condition)
|
||||
|
||||
return condition
|
||||
end
|
||||
|
||||
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
|
||||
-- @param #CONDITION self
|
||||
-- @param #function Function The function to call.
|
||||
-- @param ... (Optional) Parameters passed to the function (if any).
|
||||
-- @return #CONDITION.Function Condition function table.
|
||||
function CONDITION:AddFunctionAny(Function, ...)
|
||||
|
||||
-- Condition function.
|
||||
local condition=self:_CreateCondition(1, Function, ...)
|
||||
|
||||
-- Add to table.
|
||||
table.insert(self.functionsAny, condition)
|
||||
|
||||
return condition
|
||||
end
|
||||
|
||||
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
|
||||
-- @param #CONDITION self
|
||||
-- @param #function Function The function to call.
|
||||
-- @param ... (Optional) Parameters passed to the function (if any).
|
||||
-- @return #CONDITION.Function Condition function table.
|
||||
function CONDITION:AddFunctionAll(Function, ...)
|
||||
|
||||
-- Condition function.
|
||||
local condition=self:_CreateCondition(2, Function, ...)
|
||||
|
||||
-- Add to table.
|
||||
table.insert(self.functionsAll, condition)
|
||||
|
||||
return condition
|
||||
end
|
||||
|
||||
--- Remove a condition function.
|
||||
-- @param #CONDITION self
|
||||
-- @param #CONDITION.Function ConditionFunction The condition function to be removed.
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:RemoveFunction(ConditionFunction)
|
||||
|
||||
if ConditionFunction then
|
||||
|
||||
local data=nil
|
||||
if ConditionFunction.type==0 then
|
||||
data=self.functionsGen
|
||||
elseif ConditionFunction.type==1 then
|
||||
data=self.functionsAny
|
||||
elseif ConditionFunction.type==2 then
|
||||
data=self.functionsAll
|
||||
end
|
||||
|
||||
if data then
|
||||
for i=#data,1,-1 do
|
||||
local cf=data[i] --#CONDITION.Function
|
||||
if cf.uid==ConditionFunction.uid then
|
||||
self:T(self.lid..string.format("Removed ConditionFunction UID=%d", cf.uid))
|
||||
table.remove(data, i)
|
||||
return self
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove all non-persistant condition functions.
|
||||
-- @param #CONDITION self
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:RemoveNonPersistant()
|
||||
|
||||
for i=#self.functionsGen,1,-1 do
|
||||
local cf=self.functionsGen[i] --#CONDITION.Function
|
||||
if not cf.persistence then
|
||||
table.remove(self.functionsGen, i)
|
||||
end
|
||||
end
|
||||
|
||||
for i=#self.functionsAll,1,-1 do
|
||||
local cf=self.functionsAll[i] --#CONDITION.Function
|
||||
if not cf.persistence then
|
||||
table.remove(self.functionsAll, i)
|
||||
end
|
||||
end
|
||||
|
||||
for i=#self.functionsAny,1,-1 do
|
||||
local cf=self.functionsAny[i] --#CONDITION.Function
|
||||
if not cf.persistence then
|
||||
table.remove(self.functionsAny, i)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Evaluate conditon functions.
|
||||
-- @param #CONDITION self
|
||||
-- @param #boolean AnyTrue (Optional) If `true`, evaluation return `true` if *any* condition function returns `true`. By default, *all* condition functions must return true.
|
||||
-- @return #boolean Result of condition functions.
|
||||
function CONDITION:Evaluate(AnyTrue)
|
||||
|
||||
-- Check if at least one function was given.
|
||||
if #self.functionsAll + #self.functionsAny + #self.functionsAll == 0 then
|
||||
return self.noneResult
|
||||
end
|
||||
|
||||
-- Any condition for gen.
|
||||
local evalAny=self.isAny
|
||||
if AnyTrue~=nil then
|
||||
evalAny=AnyTrue
|
||||
end
|
||||
|
||||
local isGen=nil
|
||||
if evalAny then
|
||||
isGen=self:_EvalConditionsAny(self.functionsGen)
|
||||
else
|
||||
isGen=self:_EvalConditionsAll(self.functionsGen)
|
||||
end
|
||||
|
||||
-- Is any?
|
||||
local isAny=self:_EvalConditionsAny(self.functionsAny)
|
||||
|
||||
-- Is all?
|
||||
local isAll=self:_EvalConditionsAll(self.functionsAll)
|
||||
|
||||
-- Result.
|
||||
local result=isGen and isAny and isAll
|
||||
|
||||
-- Negate result.
|
||||
if self.negateResult then
|
||||
result=not result
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
self:T(self.lid..string.format("Evaluate: isGen=%s, isAny=%s, isAll=%s (negate=%s) ==> result=%s", tostring(isGen), tostring(isAny), tostring(isAll), tostring(self.negateResult), tostring(result)))
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Check if all given condition are true.
|
||||
-- @param #CONDITION self
|
||||
-- @param #table functions Functions to evaluate.
|
||||
-- @return #boolean If true, all conditions were true (or functions was empty/nil). Returns false if at least one condition returned false.
|
||||
function CONDITION:_EvalConditionsAll(functions)
|
||||
|
||||
-- At least one condition?
|
||||
local gotone=false
|
||||
|
||||
|
||||
-- Any stop condition must be true.
|
||||
for _,_condition in pairs(functions or {}) do
|
||||
local condition=_condition --#CONDITION.Function
|
||||
|
||||
-- At least one condition was defined.
|
||||
gotone=true
|
||||
|
||||
-- Call function.
|
||||
local istrue=condition.func(unpack(condition.arg))
|
||||
|
||||
-- Any false will return false.
|
||||
if not istrue then
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- All conditions were true.
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--- Check if any of the given conditions is true.
|
||||
-- @param #CONDITION self
|
||||
-- @param #table functions Functions to evaluate.
|
||||
-- @return #boolean If true, at least one condition is true (or functions was emtpy/nil).
|
||||
function CONDITION:_EvalConditionsAny(functions)
|
||||
|
||||
-- At least one condition?
|
||||
local gotone=false
|
||||
|
||||
-- Any stop condition must be true.
|
||||
for _,_condition in pairs(functions or {}) do
|
||||
local condition=_condition --#CONDITION.Function
|
||||
|
||||
-- At least one condition was defined.
|
||||
gotone=true
|
||||
|
||||
-- Call function.
|
||||
local istrue=condition.func(unpack(condition.arg))
|
||||
|
||||
-- Any true will return true.
|
||||
if istrue then
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- No condition was true.
|
||||
if gotone then
|
||||
return false
|
||||
else
|
||||
-- No functions passed.
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--- Create conditon function object.
|
||||
-- @param #CONDITION self
|
||||
-- @param #number Ftype Function type: 0=Gen, 1=All, 2=Any.
|
||||
-- @param #function Function The function to call.
|
||||
-- @param ... (Optional) Parameters passed to the function (if any).
|
||||
-- @return #CONDITION.Function Condition function.
|
||||
function CONDITION:_CreateCondition(Ftype, Function, ...)
|
||||
|
||||
-- Increase counter.
|
||||
self.functionCounter=self.functionCounter+1
|
||||
|
||||
local condition={} --#CONDITION.Function
|
||||
|
||||
condition.uid=self.functionCounter
|
||||
condition.type=Ftype or 0
|
||||
condition.persistence=self.defaultPersist
|
||||
condition.func=Function
|
||||
condition.arg={}
|
||||
if arg then
|
||||
condition.arg=arg
|
||||
end
|
||||
|
||||
return condition
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Global Condition Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Condition to check if time is greater than a given threshold time.
|
||||
-- @param #number Time Time in seconds.
|
||||
-- @param #boolean Absolute (Optional) If `true`, abs. mission time from `timer.getAbsTime()` is checked. Default is relative mission time from `timer.getTime()`.
|
||||
-- @return #boolean Returns `true` if time is greater than give the time.
|
||||
function CONDITION.IsTimeGreater(Time, Absolute)
|
||||
|
||||
local Tnow=nil
|
||||
|
||||
if Absolute then
|
||||
Tnow=timer.getAbsTime()
|
||||
else
|
||||
Tnow=timer.getTime()
|
||||
end
|
||||
|
||||
if Tnow>Time then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Function that returns `true` (success) with a certain probability. For example, if you specify `Probability=80` there is an 80% chance that `true` is returned.
|
||||
-- Technically, a random number between 0 and 100 is created. If the given success probability is less then this number, `true` is returned.
|
||||
-- @param #number Probability (Optional) Success probability in percent. Default 50 %.
|
||||
-- @return #boolean Returns `true` for success and `false` otherwise.
|
||||
function CONDITION.IsRandomSuccess(Probability)
|
||||
|
||||
Probability=Probability or 50
|
||||
|
||||
-- Create some randomness.
|
||||
math.random()
|
||||
math.random()
|
||||
math.random()
|
||||
|
||||
-- Number between 0 and 100.
|
||||
local N=math.random()*100
|
||||
|
||||
if N<Probability then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Function that returns always `true`
|
||||
-- @return #boolean Returns `true` unconditionally.
|
||||
function CONDITION.ReturnTrue()
|
||||
return true
|
||||
end
|
||||
|
||||
--- Function that returns always `false`
|
||||
-- @return #boolean Returns `false` unconditionally.
|
||||
function CONDITION.ReturnFalse()
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
||||
-- 
|
||||
--
|
||||
-- Within a running mission, various DCS events occur. Units are dynamically created, crash, die, shoot stuff, get hit etc.
|
||||
-- This module provides a mechanism to dispatch those events occurring within your running mission, to the different objects orchestrating your mission.
|
||||
-- This module provides a mechanism to dispatch those events occuring within your running mission, to the different objects orchestrating your mission.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -32,11 +32,11 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- There are 5 types/levels of objects that the _EVENTDISPATCHER services:
|
||||
-- There are 5 levels of kind of objects that the _EVENTDISPATCHER services:
|
||||
--
|
||||
-- * _DATABASE object: The core of the MOOSE objects. Any object that is created, deleted or updated, is done in this database.
|
||||
-- * SET_ derived classes: These are subsets of the global _DATABASE object (an instance of @{Core.Database#DATABASE}). These subsets are updated by the _EVENTDISPATCHER as the second priority.
|
||||
-- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed UNIT object.
|
||||
-- * SET_ derived classes: Subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority.
|
||||
-- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to teh subscribed UNIT object.
|
||||
-- * GROUP objects: GROUP objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed GROUP object.
|
||||
-- * Any other object: Various other objects can subscribe to DCS events. Each DCS event triggered will be published to each subscribed object.
|
||||
--
|
||||
@@ -52,7 +52,7 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The actual event subscribing and handling is not facilitated through the _EVENTDISPATCHER, but it is done through the @{Core.Base#BASE} class, @{Wrapper.Unit#UNIT} class and @{Wrapper.Group#GROUP} class.
|
||||
-- The actual event subscribing and handling is not facilitated through the _EVENTDISPATCHER, but it is done through the @{BASE} class, @{UNIT} class and @{GROUP} class.
|
||||
-- The _EVENTDISPATCHER is a component that is quietly working in the background of MOOSE.
|
||||
--
|
||||
-- 
|
||||
@@ -173,8 +173,7 @@
|
||||
-- @image Core_Event.JPG
|
||||
|
||||
|
||||
---
|
||||
-- @type EVENT
|
||||
--- @type EVENT
|
||||
-- @field #EVENT.Events Events
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
@@ -184,20 +183,16 @@ EVENT = {
|
||||
ClassName = "EVENT",
|
||||
ClassID = 0,
|
||||
MissionEnd = false,
|
||||
CreateMarkCoordinateOnEvent = false,
|
||||
}
|
||||
|
||||
world.event.S_EVENT_NEW_ZONE = world.event.S_EVENT_MAX + 1000
|
||||
world.event.S_EVENT_DELETE_ZONE = world.event.S_EVENT_MAX + 1001
|
||||
world.event.S_EVENT_NEW_ZONE_GOAL = world.event.S_EVENT_MAX + 1002
|
||||
world.event.S_EVENT_DELETE_ZONE_GOAL = world.event.S_EVENT_MAX + 1003
|
||||
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1004
|
||||
world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT = world.event.S_EVENT_MAX + 1005
|
||||
-- dynamic cargo
|
||||
world.event.S_EVENT_NEW_DYNAMIC_CARGO = world.event.S_EVENT_MAX + 1006
|
||||
world.event.S_EVENT_DYNAMIC_CARGO_LOADED = world.event.S_EVENT_MAX + 1007
|
||||
world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED = world.event.S_EVENT_MAX + 1008
|
||||
world.event.S_EVENT_DYNAMIC_CARGO_REMOVED = world.event.S_EVENT_MAX + 1009
|
||||
world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
|
||||
world.event.S_EVENT_DELETE_CARGO = world.event.S_EVENT_MAX + 1001
|
||||
world.event.S_EVENT_NEW_ZONE = world.event.S_EVENT_MAX + 1002
|
||||
world.event.S_EVENT_DELETE_ZONE = world.event.S_EVENT_MAX + 1003
|
||||
world.event.S_EVENT_NEW_ZONE_GOAL = world.event.S_EVENT_MAX + 1004
|
||||
world.event.S_EVENT_DELETE_ZONE_GOAL = world.event.S_EVENT_MAX + 1005
|
||||
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1006
|
||||
world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT = world.event.S_EVENT_MAX + 1007
|
||||
|
||||
|
||||
--- The different types of events supported by MOOSE.
|
||||
@@ -232,6 +227,8 @@ EVENTS = {
|
||||
MarkChange = world.event.S_EVENT_MARK_CHANGE,
|
||||
MarkRemoved = world.event.S_EVENT_MARK_REMOVED,
|
||||
-- Moose Events
|
||||
NewCargo = world.event.S_EVENT_NEW_CARGO,
|
||||
DeleteCargo = world.event.S_EVENT_DELETE_CARGO,
|
||||
NewZone = world.event.S_EVENT_NEW_ZONE,
|
||||
DeleteZone = world.event.S_EVENT_DELETE_ZONE,
|
||||
NewZoneGoal = world.event.S_EVENT_NEW_ZONE_GOAL,
|
||||
@@ -251,66 +248,29 @@ EVENTS = {
|
||||
TriggerZone = world.event.S_EVENT_TRIGGER_ZONE or -1,
|
||||
LandingQualityMark = world.event.S_EVENT_LANDING_QUALITY_MARK or -1,
|
||||
BDA = world.event.S_EVENT_BDA or -1,
|
||||
-- Added with DCS 2.8.0
|
||||
AIAbortMission = world.event.S_EVENT_AI_ABORT_MISSION or -1,
|
||||
DayNight = world.event.S_EVENT_DAYNIGHT or -1,
|
||||
FlightTime = world.event.S_EVENT_FLIGHT_TIME or -1,
|
||||
SelfKillPilot = world.event.S_EVENT_PLAYER_SELF_KILL_PILOT or -1,
|
||||
PlayerCaptureAirfield = world.event.S_EVENT_PLAYER_CAPTURE_AIRFIELD or -1,
|
||||
EmergencyLanding = world.event.S_EVENT_EMERGENCY_LANDING or -1,
|
||||
UnitCreateTask = world.event.S_EVENT_UNIT_CREATE_TASK or -1,
|
||||
UnitDeleteTask = world.event.S_EVENT_UNIT_DELETE_TASK or -1,
|
||||
SimulationStart = world.event.S_EVENT_SIMULATION_START or -1,
|
||||
WeaponRearm = world.event.S_EVENT_WEAPON_REARM or -1,
|
||||
WeaponDrop = world.event.S_EVENT_WEAPON_DROP or -1,
|
||||
-- Added with DCS 2.9.x
|
||||
--UnitTaskTimeout = world.event.S_EVENT_UNIT_TASK_TIMEOUT or -1,
|
||||
UnitTaskComplete = world.event.S_EVENT_UNIT_TASK_COMPLETE or -1,
|
||||
UnitTaskStage = world.event.S_EVENT_UNIT_TASK_STAGE or -1,
|
||||
--MacSubtaskScore = world.event.S_EVENT_MAC_SUBTASK_SCORE or -1,
|
||||
MacExtraScore = world.event.S_EVENT_MAC_EXTRA_SCORE or -1,
|
||||
MissionRestart = world.event.S_EVENT_MISSION_RESTART or -1,
|
||||
MissionWinner = world.event.S_EVENT_MISSION_WINNER or -1,
|
||||
RunwayTakeoff = world.event.S_EVENT_RUNWAY_TAKEOFF or -1,
|
||||
RunwayTouch = world.event.S_EVENT_RUNWAY_TOUCH or -1,
|
||||
MacLMSRestart = world.event.S_EVENT_MAC_LMS_RESTART or -1,
|
||||
SimulationFreeze = world.event.S_EVENT_SIMULATION_FREEZE or -1,
|
||||
SimulationUnfreeze = world.event.S_EVENT_SIMULATION_UNFREEZE or -1,
|
||||
HumanAircraftRepairStart = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_START or -1,
|
||||
HumanAircraftRepairFinish = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or -1,
|
||||
GroupChangeOption = world.event.S_EVENT_GROUP_CHANGE_OPTION or -1,
|
||||
-- dynamic cargo
|
||||
NewDynamicCargo = world.event.S_EVENT_NEW_DYNAMIC_CARGO or -1,
|
||||
DynamicCargoLoaded = world.event.S_EVENT_DYNAMIC_CARGO_LOADED or -1,
|
||||
DynamicCargoUnloaded = world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED or -1,
|
||||
DynamicCargoRemoved = world.event.S_EVENT_DYNAMIC_CARGO_REMOVED or -1,
|
||||
}
|
||||
|
||||
|
||||
--- The Event structure
|
||||
-- Note that at the beginning of each field description, there is an indication which field will be populated depending on the object type involved in the Event:
|
||||
--
|
||||
-- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event.
|
||||
-- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.
|
||||
-- * A GROUP : A DCS Group object is involved in the Event.
|
||||
--
|
||||
-- @type EVENTDATA
|
||||
-- @field #number id The identifier of the event.
|
||||
--
|
||||
-- @field DCS#Unit initiator (UNIT/STATIC/SCENERY/GROUP) The initiating @{DCS#Unit} or @{DCS#StaticObject}. For `S_EVENT_GROUP_CHANGE_OPTION`, this is a DCS Group.
|
||||
-- @field DCS#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{DCS#Unit} or @{DCS#StaticObject}.
|
||||
-- @field DCS#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ).
|
||||
-- @field DCS#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCS#Unit} or @{DCS#StaticObject}.
|
||||
-- @field #string IniDCSUnitName (UNIT/STATIC) The initiating Unit name.
|
||||
-- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Wrapper.Unit#UNIT} of the initiator Unit object.
|
||||
-- @field #string IniUnitName (UNIT/STATIC) The initiating UNIT name (same as IniDCSUnitName).
|
||||
-- @field DCS#Group IniDCSGroup (UNIT/GROUP) The initiating @{DCS#Group}.
|
||||
-- @field #string IniDCSGroupName (UNIT/GROUP) The initiating Group name.
|
||||
-- @field Wrapper.Group#GROUP IniGroup (UNIT/GROUP) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object.
|
||||
-- @field #string IniGroupName (UNIT/GROUP) The initiating GROUP name (same as IniDCSGroupName).
|
||||
-- @field DCS#Group.Category IniGroupCategory (GROUP) The category of the initiating group.
|
||||
-- @field DCS#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}.
|
||||
-- @field #string IniDCSGroupName (UNIT) The initiating Group name.
|
||||
-- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object.
|
||||
-- @field #string IniGroupName UNIT) The initiating GROUP name (same as IniDCSGroupName).
|
||||
-- @field #string IniPlayerName (UNIT) The name of the initiating player in case the Unit is a client or player slot.
|
||||
-- @field #string IniPlayerUCID (UNIT) The UCID of the initiating player in case the Unit is a client or player slot and on a multi-player server.
|
||||
-- @field DCS#coalition.side IniCoalition (UNIT/GROUP) The coalition of the initiator.
|
||||
-- @field DCS#coalition.side IniCoalition (UNIT) The coalition of the initiator.
|
||||
-- @field DCS#Unit.Category IniCategory (UNIT) The category of the initiator.
|
||||
-- @field #string IniTypeName (UNIT) The type name of the initiator.
|
||||
--
|
||||
@@ -325,7 +285,6 @@ EVENTS = {
|
||||
-- @field Wrapper.Group#GROUP TgtGroup (UNIT) The target MOOSE wrapper @{Wrapper.Group#GROUP} of the target Group object.
|
||||
-- @field #string TgtGroupName (UNIT) The target GROUP name (same as TgtDCSGroupName).
|
||||
-- @field #string TgtPlayerName (UNIT) The name of the target player in case the Unit is a client or player slot.
|
||||
-- @field #string TgtPlayerUCID (UNIT) The UCID of the target player in case the Unit is a client or player slot and on a multi-player server.
|
||||
-- @field DCS#coalition.side TgtCoalition (UNIT) The coalition of the target.
|
||||
-- @field DCS#Unit.Category TgtCategory (UNIT) The category of the target.
|
||||
-- @field #string TgtTypeName (UNIT) The type name of the target.
|
||||
@@ -334,23 +293,16 @@ EVENTS = {
|
||||
-- @field Wrapper.Airbase#AIRBASE Place The MOOSE airbase object.
|
||||
-- @field #string PlaceName The name of the airbase.
|
||||
--
|
||||
-- @field DCS#Weapon weapon The weapon used during the event.
|
||||
-- @field DCS#Weapon Weapon The weapon used during the event.
|
||||
-- @field #table weapon The weapon used during the event.
|
||||
-- @field #table Weapon
|
||||
-- @field #string WeaponName Name of the weapon.
|
||||
-- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon.
|
||||
--
|
||||
-- @field Core.Zone#ZONE Zone The zone object.
|
||||
-- @field Cargo.Cargo#CARGO Cargo The cargo object.
|
||||
-- @field #string CargoName The name of the cargo object.
|
||||
--
|
||||
-- @field Core.ZONE#ZONE Zone The zone object.
|
||||
-- @field #string ZoneName The name of the zone.
|
||||
--
|
||||
-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object.
|
||||
-- @field #string IniDynamicCargoName The dynamic cargo unit name.
|
||||
--
|
||||
-- @field #number MarkCoalition Coalition of a marker (if any)
|
||||
-- @field DCS#Vec3 MarkVec3 Position of a marker
|
||||
-- @field #string MarkText Text content of a marker, if any
|
||||
-- @field #number MarkID ID of the marker, for deletion
|
||||
-- @field #number MarkGroupID Group ID of the group that created the marker
|
||||
-- @field Core.Point#COORDINATE Coordinate object of the marker, only filled if EVENT.CreateMarkCoordinateOnEvent is set to true (off by default)
|
||||
|
||||
|
||||
|
||||
@@ -511,6 +463,16 @@ local _EVENTMETA = {
|
||||
Event = "OnEventMarkRemoved",
|
||||
Text = "S_EVENT_MARK_REMOVED"
|
||||
},
|
||||
[EVENTS.NewCargo] = {
|
||||
Order = 1,
|
||||
Event = "OnEventNewCargo",
|
||||
Text = "S_EVENT_NEW_CARGO"
|
||||
},
|
||||
[EVENTS.DeleteCargo] = {
|
||||
Order = 1,
|
||||
Event = "OnEventDeleteCargo",
|
||||
Text = "S_EVENT_DELETE_CARGO"
|
||||
},
|
||||
[EVENTS.NewZone] = {
|
||||
Order = 1,
|
||||
Event = "OnEventNewZone",
|
||||
@@ -598,177 +560,6 @@ local _EVENTMETA = {
|
||||
Event = "OnEventBDA",
|
||||
Text = "S_EVENT_BDA"
|
||||
},
|
||||
-- Added with DCS 2.8
|
||||
[EVENTS.AIAbortMission] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventAIAbortMission",
|
||||
Text = "S_EVENT_AI_ABORT_MISSION"
|
||||
},
|
||||
[EVENTS.DayNight] = {
|
||||
Order = 1,
|
||||
Event = "OnEventDayNight",
|
||||
Text = "S_EVENT_DAYNIGHT"
|
||||
},
|
||||
[EVENTS.FlightTime] = {
|
||||
Order = 1,
|
||||
Event = "OnEventFlightTime",
|
||||
Text = "S_EVENT_FLIGHT_TIME"
|
||||
},
|
||||
[EVENTS.SelfKillPilot] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventSelfKillPilot",
|
||||
Text = "S_EVENT_PLAYER_SELF_KILL_PILOT"
|
||||
},
|
||||
[EVENTS.PlayerCaptureAirfield] = {
|
||||
Order = 1,
|
||||
Event = "OnEventPlayerCaptureAirfield",
|
||||
Text = "S_EVENT_PLAYER_CAPTURE_AIRFIELD"
|
||||
},
|
||||
[EVENTS.EmergencyLanding] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventEmergencyLanding",
|
||||
Text = "S_EVENT_EMERGENCY_LANDING"
|
||||
},
|
||||
[EVENTS.UnitCreateTask] = {
|
||||
Order = 1,
|
||||
Event = "OnEventUnitCreateTask",
|
||||
Text = "S_EVENT_UNIT_CREATE_TASK"
|
||||
},
|
||||
[EVENTS.UnitDeleteTask] = {
|
||||
Order = 1,
|
||||
Event = "OnEventUnitDeleteTask",
|
||||
Text = "S_EVENT_UNIT_DELETE_TASK"
|
||||
},
|
||||
[EVENTS.SimulationStart] = {
|
||||
Order = 1,
|
||||
Event = "OnEventSimulationStart",
|
||||
Text = "S_EVENT_SIMULATION_START"
|
||||
},
|
||||
[EVENTS.WeaponRearm] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventWeaponRearm",
|
||||
Text = "S_EVENT_WEAPON_REARM"
|
||||
},
|
||||
[EVENTS.WeaponDrop] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventWeaponDrop",
|
||||
Text = "S_EVENT_WEAPON_DROP"
|
||||
},
|
||||
-- DCS 2.9
|
||||
--[EVENTS.UnitTaskTimeout] = {
|
||||
-- Order = 1,
|
||||
-- Side = "I",
|
||||
-- Event = "OnEventUnitTaskTimeout",
|
||||
-- Text = "S_EVENT_UNIT_TASK_TIMEOUT "
|
||||
--},
|
||||
[EVENTS.UnitTaskStage] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventUnitTaskStage",
|
||||
Text = "S_EVENT_UNIT_TASK_STAGE "
|
||||
},
|
||||
--[EVENTS.MacSubtaskScore] = {
|
||||
-- Order = 1,
|
||||
--Side = "I",
|
||||
--Event = "OnEventMacSubtaskScore",
|
||||
--Text = "S_EVENT_MAC_SUBTASK_SCORE"
|
||||
--},
|
||||
[EVENTS.MacExtraScore] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMacExtraScore",
|
||||
Text = "S_EVENT_MAC_EXTRA_SCOREP"
|
||||
},
|
||||
[EVENTS.MissionRestart] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMissionRestart",
|
||||
Text = "S_EVENT_MISSION_RESTART"
|
||||
},
|
||||
[EVENTS.MissionWinner] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMissionWinner",
|
||||
Text = "S_EVENT_MISSION_WINNER"
|
||||
},
|
||||
[EVENTS.RunwayTakeoff] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventRunwayTakeoff",
|
||||
Text = "S_EVENT_RUNWAY_TAKEOFF"
|
||||
},
|
||||
[EVENTS.RunwayTouch] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventRunwayTouch",
|
||||
Text = "S_EVENT_RUNWAY_TOUCH"
|
||||
},
|
||||
[EVENTS.MacLMSRestart] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMacLMSRestart",
|
||||
Text = "S_EVENT_MAC_LMS_RESTART"
|
||||
},
|
||||
[EVENTS.SimulationFreeze] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventSimulationFreeze",
|
||||
Text = "S_EVENT_SIMULATION_FREEZE"
|
||||
},
|
||||
[EVENTS.SimulationUnfreeze] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventSimulationUnfreeze",
|
||||
Text = "S_EVENT_SIMULATION_UNFREEZE"
|
||||
},
|
||||
[EVENTS.HumanAircraftRepairStart] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventHumanAircraftRepairStart",
|
||||
Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_START"
|
||||
},
|
||||
[EVENTS.HumanAircraftRepairFinish] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventHumanAircraftRepairFinish",
|
||||
Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH"
|
||||
},
|
||||
[EVENTS.GroupChangeOption] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventGroupChangeOption",
|
||||
Text = "S_EVENT_GROUP_CHANGE_OPTION"
|
||||
},
|
||||
-- dynamic cargo
|
||||
[EVENTS.NewDynamicCargo] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventNewDynamicCargo",
|
||||
Text = "S_EVENT_NEW_DYNAMIC_CARGO"
|
||||
},
|
||||
[EVENTS.DynamicCargoLoaded] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventDynamicCargoLoaded",
|
||||
Text = "S_EVENT_DYNAMIC_CARGO_LOADED"
|
||||
},
|
||||
[EVENTS.DynamicCargoUnloaded] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventDynamicCargoUnloaded",
|
||||
Text = "S_EVENT_DYNAMIC_CARGO_UNLOADED"
|
||||
},
|
||||
[EVENTS.DynamicCargoRemoved] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventDynamicCargoRemoved",
|
||||
Text = "S_EVENT_DYNAMIC_CARGO_REMOVED"
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1063,6 +854,36 @@ end
|
||||
|
||||
do -- Event Creation
|
||||
|
||||
--- Creation of a New Cargo Event.
|
||||
-- @param #EVENT self
|
||||
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
|
||||
function EVENT:CreateEventNewCargo( Cargo )
|
||||
self:F( { Cargo } )
|
||||
|
||||
local Event = {
|
||||
id = EVENTS.NewCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a Cargo Deletion Event.
|
||||
-- @param #EVENT self
|
||||
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
|
||||
function EVENT:CreateEventDeleteCargo( Cargo )
|
||||
self:F( { Cargo } )
|
||||
|
||||
local Event = {
|
||||
id = EVENTS.DeleteCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a New Zone Event.
|
||||
-- @param #EVENT self
|
||||
-- @param Core.Zone#ZONE_BASE Zone The Zone created.
|
||||
@@ -1095,7 +916,7 @@ do -- Event Creation
|
||||
|
||||
--- Creation of a New ZoneGoal Event.
|
||||
-- @param #EVENT self
|
||||
-- @param Functional.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal created.
|
||||
-- @param Core.Functional#ZONE_GOAL ZoneGoal The ZoneGoal created.
|
||||
function EVENT:CreateEventNewZoneGoal( ZoneGoal )
|
||||
self:F( { ZoneGoal } )
|
||||
|
||||
@@ -1111,7 +932,7 @@ do -- Event Creation
|
||||
|
||||
--- Creation of a ZoneGoal Deletion Event.
|
||||
-- @param #EVENT self
|
||||
-- @param Functional.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal created.
|
||||
-- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal created.
|
||||
function EVENT:CreateEventDeleteZoneGoal( ZoneGoal )
|
||||
self:F( { ZoneGoal } )
|
||||
|
||||
@@ -1154,63 +975,7 @@ do -- Event Creation
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventNewDynamicCargo(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.NewDynamicCargo,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_LOADED event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventDynamicCargoLoaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoLoaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_UNLOADED event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventDynamicCargoUnloaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoUnloaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_REMOVED event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventDynamicCargoRemoved(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoRemoved,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Main event function.
|
||||
@@ -1243,18 +1008,9 @@ function EVENT:onEvent( Event )
|
||||
|
||||
if Event.initiator then
|
||||
|
||||
if Event.id == EVENTS.GroupChangeOption then
|
||||
Event.IniDCSGroup = Event.initiator
|
||||
Event.IniDCSGroupName = Group.getName(Event.initiator)
|
||||
Event.IniGroupName = Event.IniDCSGroupName
|
||||
Event.IniGroup = GROUP:FindByName(Event.IniDCSGroupName)
|
||||
Event.IniCoalition = Group.getCoalition(Event.initiator)
|
||||
Event.IniGroupCategory = Group.getCategory(Event.initiator)
|
||||
else
|
||||
Event.IniObjectCategory = Object.getCategory(Event.initiator)
|
||||
end
|
||||
Event.IniObjectCategory = Event.initiator:getCategory()
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.STATIC then
|
||||
if Event.IniObjectCategory == Object.Category.STATIC then
|
||||
---
|
||||
-- Static
|
||||
---
|
||||
@@ -1290,9 +1046,10 @@ function EVENT:onEvent( Event )
|
||||
local Unit=UNIT:FindByName(Event.IniDCSUnitName)
|
||||
if Unit then
|
||||
Event.IniObjectCategory = Object.Category.UNIT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif Event.IniObjectCategory == Object.Category.UNIT then
|
||||
if Event.IniObjectCategory == Object.Category.UNIT then
|
||||
---
|
||||
-- Unit
|
||||
---
|
||||
@@ -1301,14 +1058,13 @@ function EVENT:onEvent( Event )
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniDCSGroup = Event.IniDCSUnit:getGroup()
|
||||
Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName )
|
||||
|
||||
|
||||
if not Event.IniUnit then
|
||||
-- Unit can be a CLIENT. Most likely this will be the case ...
|
||||
Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true )
|
||||
end
|
||||
|
||||
Event.IniDCSGroupName = Event.IniUnit and Event.IniUnit.GroupName or ""
|
||||
Event.IniGroupName=Event.IniDCSGroupName --At least set the group name because group might not exist any more
|
||||
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
|
||||
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
|
||||
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
|
||||
@@ -1316,51 +1072,38 @@ function EVENT:onEvent( Event )
|
||||
end
|
||||
|
||||
Event.IniPlayerName = Event.IniDCSUnit:getPlayerName()
|
||||
if Event.IniPlayerName then
|
||||
-- get UUCID
|
||||
local PID = NET.GetPlayerIDByName(nil,Event.IniPlayerName)
|
||||
if PID then
|
||||
Event.IniPlayerUCID = net.get_player_info(tonumber(PID), 'ucid')
|
||||
--env.info("Event.IniPlayerUCID="..tostring(Event.IniPlayerUCID),false)
|
||||
end
|
||||
end
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
end
|
||||
|
||||
elseif Event.IniObjectCategory == Object.Category.CARGO then
|
||||
if Event.IniObjectCategory == Object.Category.CARGO then
|
||||
---
|
||||
-- Cargo
|
||||
---
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
if string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then
|
||||
Event.IniDynamicCargo = DYNAMICCARGO:FindByName(Event.IniUnitName)
|
||||
Event.IniDynamicCargoName = Event.IniUnitName
|
||||
Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+")
|
||||
else
|
||||
Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false )
|
||||
end
|
||||
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
end
|
||||
|
||||
elseif Event.IniObjectCategory == Object.Category.SCENERY then
|
||||
if Event.IniObjectCategory == Object.Category.SCENERY then
|
||||
---
|
||||
-- Scenery
|
||||
---
|
||||
---
|
||||
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = ( Event.IniDCSUnit and Event.IniDCSUnit.getName ) and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000)
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
local ID = (Event.IniDCSUnit and Event.IniDCSUnit.getID) and Event.IniDCSUnit:getID() or Event.IniDCSUnitName
|
||||
Event.IniUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil
|
||||
--Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
|
||||
Event.IniCategory = (Event.IniDCSUnit and Event.IniDCSUnit.getDesc ) and Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = (Event.initiator and Event.initiator.isExist
|
||||
and Event.initiator:isExist() and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName) and Event.IniDCSUnit:getTypeName() or "SCENERY"
|
||||
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY"
|
||||
end
|
||||
|
||||
elseif Event.IniObjectCategory == Object.Category.BASE then
|
||||
if Event.IniObjectCategory == Object.Category.BASE then
|
||||
---
|
||||
-- Base Object
|
||||
---
|
||||
@@ -1371,12 +1114,6 @@ function EVENT:onEvent( Event )
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
|
||||
-- If the airbase does not exist in the DB, we add it (e.g. when FARPS are spawned).
|
||||
if not Event.IniUnit then
|
||||
_DATABASE:_RegisterAirbase(Event.initiator)
|
||||
Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1387,12 +1124,9 @@ function EVENT:onEvent( Event )
|
||||
---
|
||||
|
||||
-- Target category.
|
||||
Event.TgtObjectCategory = Object.getCategory(Event.target)
|
||||
Event.TgtObjectCategory = Event.target:getCategory()
|
||||
|
||||
if Event.TgtObjectCategory == Object.Category.UNIT then
|
||||
---
|
||||
-- UNIT
|
||||
---
|
||||
Event.TgtDCSUnit = Event.target
|
||||
Event.TgtDCSGroup = Event.TgtDCSUnit:getGroup()
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
@@ -1405,33 +1139,21 @@ function EVENT:onEvent( Event )
|
||||
Event.TgtGroupName = Event.TgtDCSGroupName
|
||||
end
|
||||
Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName()
|
||||
if Event.TgtPlayerName then
|
||||
-- get UUCID
|
||||
local PID = NET.GetPlayerIDByName(nil,Event.TgtPlayerName)
|
||||
if PID then
|
||||
Event.TgtPlayerUCID = net.get_player_info(tonumber(PID), 'ucid')
|
||||
--env.info("Event.TgtPlayerUCID="..tostring(Event.TgtPlayerUCID),false)
|
||||
end
|
||||
end
|
||||
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
end
|
||||
|
||||
elseif Event.TgtObjectCategory == Object.Category.STATIC then
|
||||
---
|
||||
-- STATIC
|
||||
---
|
||||
if Event.TgtObjectCategory == Object.Category.STATIC then
|
||||
-- get base data
|
||||
Event.TgtDCSUnit = Event.target
|
||||
if Event.target.isExist and Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object, check that isExist exists (Kiowa Hellfire issue, Special K)
|
||||
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
-- Workaround for borked target info on cruise missiles
|
||||
if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
|
||||
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
end
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
|
||||
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
else
|
||||
Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id))
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
@@ -1450,39 +1172,27 @@ function EVENT:onEvent( Event )
|
||||
Event.TgtTypeName = "Static"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif Event.TgtObjectCategory == Object.Category.SCENERY then
|
||||
---
|
||||
-- SCENERY
|
||||
---
|
||||
if Event.TgtObjectCategory == Object.Category.SCENERY then
|
||||
Event.TgtDCSUnit = Event.target
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or nil
|
||||
if Event.TgtDCSUnitName~=nil then
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
local ID = (Event.TgtDCSUnit and Event.TgtDCSUnit.getID) and Event.TgtDCSUnit:getID() or Event.TgtDCSUnitName
|
||||
--Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
Event.TgtUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
end
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
end
|
||||
end
|
||||
|
||||
-- Weapon.
|
||||
if Event.weapon and type(Event.weapon) == "table" and Event.weapon.isExist and Event.weapon:isExist() then
|
||||
if Event.weapon then
|
||||
Event.Weapon = Event.weapon
|
||||
if Event.weapon_name == "ZELL Booster" then
|
||||
Event.WeaponName = "ZELL Booster"
|
||||
else
|
||||
Event.WeaponName = Event.weapon:isExist() and Event.weapon.getTypeName and Event.weapon:getTypeName() or "Unknown Weapon"
|
||||
end
|
||||
if Event.weapon_name == "ZELL Booster" then Event.WeaponName = "ZELL Booster" end
|
||||
Event.WeaponName = Event.Weapon:getTypeName()
|
||||
Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit!
|
||||
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName()
|
||||
--Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
|
||||
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon.getCoalition and Event.Weapon:getCoalition()
|
||||
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon.getDesc and Event.Weapon:getDesc().category
|
||||
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon.getTypeName and Event.Weapon:getTypeName()
|
||||
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
|
||||
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition()
|
||||
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category
|
||||
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName()
|
||||
--Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
|
||||
end
|
||||
|
||||
@@ -1493,13 +1203,9 @@ function EVENT:onEvent( Event )
|
||||
--local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :(
|
||||
-- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground.
|
||||
--Event.Place=UNIT:Find(Event.place)
|
||||
else
|
||||
if Event.place:isExist() and Object.getCategory(Event.place) ~= Object.Category.SCENERY then
|
||||
Event.Place=AIRBASE:Find(Event.place)
|
||||
if Event.Place then
|
||||
Event.PlaceName=Event.Place:GetName()
|
||||
end
|
||||
end
|
||||
else
|
||||
Event.Place=AIRBASE:Find(Event.place)
|
||||
Event.PlaceName=Event.Place:GetName()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1507,22 +1213,16 @@ function EVENT:onEvent( Event )
|
||||
if Event.idx then
|
||||
Event.MarkID=Event.idx
|
||||
Event.MarkVec3=Event.pos
|
||||
if self.CreateMarkCoordinateOnEvent == true then
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
end
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
Event.MarkText=Event.text
|
||||
Event.MarkCoalition=Event.coalition
|
||||
Event.IniCoalition=Event.coalition
|
||||
Event.MarkGroupID = Event.groupID
|
||||
end
|
||||
|
||||
-- Dynamic cargo Object
|
||||
if Event.dynamiccargo then
|
||||
Event.IniDynamicCargo = Event.dynamiccargo
|
||||
Event.IniDynamicCargoName = Event.IniDynamicCargo.StaticName
|
||||
if Event.IniDynamicCargo.Owner or Event.IniUnitName then
|
||||
Event.IniPlayerName = Event.IniDynamicCargo.Owner or string.match(Event.IniUnitName or "None|00:00|PKG00","^(.+)|%d%d:%d%d|PKG%d+")
|
||||
end
|
||||
-- Cargo object.
|
||||
if Event.cargo then
|
||||
Event.Cargo = Event.cargo
|
||||
Event.CargoName = Event.cargo.Name
|
||||
end
|
||||
|
||||
-- Zone object.
|
||||
@@ -1677,6 +1377,15 @@ function EVENT:onEvent( Event )
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,87 +1,89 @@
|
||||
--- **Core** - Models the process to achieve goal(s).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Define the goal.
|
||||
-- * Monitor the goal achievement.
|
||||
-- * Manage goal contribution by players.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Classes that implement a goal achievement, will derive from GOAL to implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Core.Goal
|
||||
-- @image Core_Goal.JPG
|
||||
|
||||
|
||||
do -- Goal
|
||||
|
||||
-- @type GOAL
|
||||
--- @type GOAL
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- # 1. GOAL constructor
|
||||
--
|
||||
--
|
||||
-- * @{#GOAL.New}(): Creates a new GOAL object.
|
||||
--
|
||||
--
|
||||
-- # 2. GOAL is a finite state machine (FSM).
|
||||
--
|
||||
--
|
||||
-- ## 2.1. GOAL States
|
||||
--
|
||||
--
|
||||
-- * **Pending**: The goal object is in progress.
|
||||
-- * **Achieved**: The goal objective is Achieved.
|
||||
--
|
||||
--
|
||||
-- ## 2.2. GOAL Events
|
||||
--
|
||||
--
|
||||
-- * **Achieved**: Set the goal objective to Achieved.
|
||||
--
|
||||
--
|
||||
-- # 3. Player contributions.
|
||||
--
|
||||
--
|
||||
-- Goals are most of the time achieved by players. These player achievements can be registered as part of the goal achievement.
|
||||
-- Use @{#GOAL.AddPlayerContribution}() to add a player contribution to the goal.
|
||||
-- The player contributions are based on a points system, an internal counter per player.
|
||||
-- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(),
|
||||
-- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(),
|
||||
-- that retrieves all contributions done by the players. For one player, the contribution can be queried using @{#GOAL.GetPlayerContribution}().
|
||||
-- The total amount of player contributions can be queried using @{#GOAL.GetTotalContributions}().
|
||||
--
|
||||
--
|
||||
-- # 4. Goal achievement.
|
||||
--
|
||||
--
|
||||
-- Once the goal is achieved, the mission designer will need to trigger the goal achievement using the **Achieved** event.
|
||||
-- The underlying 2 examples will achieve the goals for the `Goal` object:
|
||||
--
|
||||
--
|
||||
-- Goal:Achieved() -- Achieve the goal immediately.
|
||||
-- Goal:__Achieved( 30 ) -- Achieve the goal within 30 seconds.
|
||||
--
|
||||
--
|
||||
-- # 5. Check goal achievement.
|
||||
--
|
||||
--
|
||||
-- The method @{#GOAL.IsAchieved}() will return true if the goal is achieved (the trigger **Achieved** was executed).
|
||||
-- You can use this method to check asynchronously if a goal has been achieved, for example using a scheduler.
|
||||
--
|
||||
--
|
||||
-- @field #GOAL
|
||||
GOAL = {
|
||||
ClassName = "GOAL",
|
||||
}
|
||||
|
||||
-- @field #table GOAL.Players
|
||||
|
||||
--- @field #table GOAL.Players
|
||||
GOAL.Players = {}
|
||||
|
||||
-- @field #number GOAL.TotalContributions
|
||||
--- @field #number GOAL.TotalContributions
|
||||
GOAL.TotalContributions = 0
|
||||
|
||||
|
||||
--- GOAL Constructor.
|
||||
-- @param #GOAL self
|
||||
-- @return #GOAL
|
||||
function GOAL:New()
|
||||
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #GOAL
|
||||
self:F( {} )
|
||||
|
||||
@@ -102,10 +104,11 @@ do -- Goal
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
|
||||
|
||||
self:SetStartState( "Pending" )
|
||||
self:AddTransition( "*", "Achieved", "Achieved" )
|
||||
|
||||
self:AddTransition( "*", "Achieved", "Achieved" )
|
||||
|
||||
--- Achieved Handler OnBefore for GOAL
|
||||
-- @function [parent=#GOAL] OnBeforeAchieved
|
||||
-- @param #GOAL self
|
||||
@@ -113,44 +116,47 @@ do -- Goal
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #boolean
|
||||
|
||||
|
||||
--- Achieved Handler OnAfter for GOAL
|
||||
-- @function [parent=#GOAL] OnAfterAchieved
|
||||
-- @param #GOAL self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
|
||||
--- Achieved Trigger for GOAL
|
||||
-- @function [parent=#GOAL] Achieved
|
||||
-- @param #GOAL self
|
||||
|
||||
|
||||
--- Achieved Asynchronous Trigger for GOAL
|
||||
-- @function [parent=#GOAL] __Achieved
|
||||
-- @param #GOAL self
|
||||
-- @param #number Delay
|
||||
|
||||
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Add a new contribution by a player.
|
||||
-- @param #GOAL self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
function GOAL:AddPlayerContribution( PlayerName )
|
||||
self:F( { PlayerName } )
|
||||
self:F({PlayerName})
|
||||
self.Players[PlayerName] = self.Players[PlayerName] or 0
|
||||
self.Players[PlayerName] = self.Players[PlayerName] + 1
|
||||
self.TotalContributions = self.TotalContributions + 1
|
||||
end
|
||||
|
||||
-- @param #GOAL self
|
||||
|
||||
|
||||
--- @param #GOAL self
|
||||
-- @param #number Player contribution.
|
||||
function GOAL:GetPlayerContribution( PlayerName )
|
||||
return self.Players[PlayerName] or 0
|
||||
return self.Players[PlayerName] or 0
|
||||
end
|
||||
|
||||
|
||||
--- Get the players who contributed to achieve the goal.
|
||||
-- The result is a list of players, sorted by the name of the players.
|
||||
-- @param #GOAL self
|
||||
@@ -159,6 +165,7 @@ do -- Goal
|
||||
return self.Players or {}
|
||||
end
|
||||
|
||||
|
||||
--- Gets the total contributions that happened to achieve the goal.
|
||||
-- The result is a number.
|
||||
-- @param #GOAL self
|
||||
@@ -166,7 +173,9 @@ do -- Goal
|
||||
function GOAL:GetTotalContributions()
|
||||
return self.TotalContributions or 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Validates if the goal is achieved.
|
||||
-- @param #GOAL self
|
||||
-- @return #boolean true if the goal is achieved.
|
||||
@@ -174,4 +183,4 @@ do -- Goal
|
||||
return self:Is( "Achieved" )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1,23 +1,16 @@
|
||||
--- **Core** - Tap into markers added to the F10 map by users.
|
||||
--- **Core** - MarkerOps_Base.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Create an easy way to tap into markers added to the F10 map by users.
|
||||
-- * Recognize own tag and list of keywords.
|
||||
-- * Matched keywords are handed down to functions.
|
||||
-- ##Listen for your tag
|
||||
-- myMarker = MARKEROPS_BASE:New("tag", {}, false)
|
||||
-- function myMarker:OnAfterMarkChanged(From, Event, To, Text, Keywords, Coord, idx)
|
||||
--
|
||||
-- end
|
||||
-- Make sure to use the "MarkChanged" event as "MarkAdded" comes in right after the user places a blank marker and your callback will never be called.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- Date: 5 May 2021
|
||||
-- Last Update: Mar 2023
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
@@ -34,7 +27,6 @@
|
||||
-- @field #string Tag Tag to identify commands.
|
||||
-- @field #table Keywords Table of keywords to recognize.
|
||||
-- @field #string version Version of #MARKEROPS_BASE.
|
||||
-- @field #boolean Casesensitive Enforce case when identifying the Tag, i.e. tag ~= Tag
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Fiat lux.* -- Latin proverb.
|
||||
@@ -50,9 +42,8 @@ MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.1.5",
|
||||
version = "0.0.1",
|
||||
debug = false,
|
||||
Casesensitive = true,
|
||||
}
|
||||
|
||||
--- Function to instantiate a new #MARKEROPS_BASE object.
|
||||
@@ -70,8 +61,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords)
|
||||
self.Tag = Tagname or "mytag"-- #string
|
||||
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
|
||||
self.debug = false
|
||||
self.Casesensitive = true
|
||||
|
||||
|
||||
-----------------------
|
||||
--- FSM Transitions ---
|
||||
-----------------------
|
||||
@@ -103,31 +93,23 @@ function MARKEROPS_BASE:New(Tagname,Keywords)
|
||||
--- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state.
|
||||
-- @param #string Event The Event called.
|
||||
-- @param #string To The To state.
|
||||
-- @param #string Text The text on the marker.
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text.
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
-- @param #number MarkerID Id of this marker.
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator.
|
||||
-- @param #string PlayerName Name of the player creating/changing the mark. nil if it cannot be obtained.
|
||||
-- @param Core.Event#EVENTDATA EventData the event data table.
|
||||
|
||||
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state.
|
||||
-- @param #string Event The Event called.
|
||||
-- @param #string To The To state.
|
||||
-- @param #string Text The text on the marker.
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text.
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
-- @param #number MarkerID Id of this marker.
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator.
|
||||
-- @param #string PlayerName Name of the player creating/changing the mark. nil if it cannot be obtained.
|
||||
-- @param Core.Event#EVENTDATA EventData the event data table
|
||||
|
||||
|
||||
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
|
||||
-- @param #MARKEROPS_BASE self
|
||||
@@ -135,13 +117,13 @@ function MARKEROPS_BASE:New(Tagname,Keywords)
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
|
||||
--- "Stop" trigger. Used to stop the function an unhandle events
|
||||
--- "Stop" trigger. Used to stop the function an unhandle events
|
||||
-- @function [parent=#MARKEROPS_BASE] Stop
|
||||
|
||||
end
|
||||
|
||||
--- (Internal) Handle events.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
--- (internal) Handle events.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function MARKEROPS_BASE:OnEventMark(Event)
|
||||
self:T({Event})
|
||||
@@ -149,43 +131,37 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
||||
self:E("Skipping onEvent. Event or Event.idx unknown.")
|
||||
return true
|
||||
end
|
||||
|
||||
local coalition = Event.MarkCoalition
|
||||
--position
|
||||
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
|
||||
local coord=COORDINATE:NewFromVec3(vec3)
|
||||
if self.debug then
|
||||
local coordtext = coord:ToStringLLDDM()
|
||||
local text = tostring(Event.text)
|
||||
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
|
||||
end
|
||||
-- decision
|
||||
if Event.id==world.event.S_EVENT_MARK_ADDED then
|
||||
self:T({event="S_EVENT_MARK_ADDED", carrier=Event.IniGroupName, vec3=Event.pos})
|
||||
self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z})
|
||||
if self.debug then
|
||||
local coordtext = coord:ToStringLLDDM()
|
||||
local text = tostring(Event.text)
|
||||
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
|
||||
end
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkAdded(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
|
||||
self:T({event="S_EVENT_MARK_CHANGE", carrier=Event.IniGroupName, vec3=Event.pos})
|
||||
self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z})
|
||||
if self.debug then
|
||||
local coordtext = coord:ToStringLLDDM()
|
||||
local text = tostring(Event.text)
|
||||
local m = MESSAGE:New(string.format("Mark changed at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
|
||||
end
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=Event.IniGroupName, vec3=Event.pos})
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Hande event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
@@ -196,30 +172,21 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) Match tag.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
--- (internal) Match tag.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #boolean
|
||||
function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||
local matches = false
|
||||
--self:I(self.lid .. "Casesensitive "..tostring(self.Casesensitive))
|
||||
if self.Casesensitive == false then
|
||||
--self:I(self.lid .. "Marker non-casesensitive "..Eventtext)
|
||||
local type = string.lower(self.Tag) -- #string
|
||||
if string.find(string.lower(Eventtext),type,1,true) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
else
|
||||
local type = self.Tag -- #string
|
||||
if string.find(Eventtext,type) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
local type = string.lower(self.Tag) -- #string
|
||||
if string.find(string.lower(Eventtext),type) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
return matches
|
||||
end
|
||||
|
||||
--- (Internal) Match keywords table.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
--- (internal) Match keywords table.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #table
|
||||
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
|
||||
@@ -240,10 +207,8 @@ end
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber)
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
@@ -254,10 +219,8 @@ end
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber)
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
@@ -283,22 +246,6 @@ function MARKEROPS_BASE:onenterStopped(From,Event,To)
|
||||
self:UnHandleEvent(EVENTS.MarkRemoved)
|
||||
end
|
||||
|
||||
--- Switch off case sensitive matching
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @return self
|
||||
function MARKEROPS_BASE:SwitchCaseSensitiveOff()
|
||||
self.Casesensitive = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch on case sensitive matching
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @return self
|
||||
function MARKEROPS_BASE:SwitchCaseSensitiveOn()
|
||||
self.Casesensitive = true
|
||||
return self
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition End.
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
--- **Core** - Informs the players using messages during a simulation.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * A more advanced messaging system using the DCS message system.
|
||||
-- * Time messages.
|
||||
-- * Send messages based on a message type, which has a pre-defined duration that can be tweaked in SETTINGS.
|
||||
@@ -13,7 +13,7 @@
|
||||
-- * Send messages to a specific unit or client.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Core.Message
|
||||
-- @image Core_Message.JPG
|
||||
|
||||
@@ -24,43 +24,43 @@
|
||||
--- Message System to display Messages to Clients, Coalitions or All.
|
||||
-- Messages are shown on the display panel for an amount of seconds, and will then disappear.
|
||||
-- Messages can contain a category which is indicating the category of the message.
|
||||
--
|
||||
--
|
||||
-- ## MESSAGE construction
|
||||
--
|
||||
--
|
||||
-- Messages are created with @{#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet.
|
||||
-- To send messages, you need to use the To functions.
|
||||
--
|
||||
--
|
||||
-- ## Send messages to an audience
|
||||
--
|
||||
--
|
||||
-- Messages are sent:
|
||||
--
|
||||
-- * To a @{Wrapper.Client} using @{#MESSAGE.ToClient}().
|
||||
-- * To a @{Client} using @{#MESSAGE.ToClient}().
|
||||
-- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}()
|
||||
-- * To a @{Wrapper.Unit} using @{#MESSAGE.ToUnit}()
|
||||
-- * To a coalition using @{#MESSAGE.ToCoalition}().
|
||||
-- * To the red coalition using @{#MESSAGE.ToRed}().
|
||||
-- * To the blue coalition using @{#MESSAGE.ToBlue}().
|
||||
-- * To all Players using @{#MESSAGE.ToAll}().
|
||||
--
|
||||
--
|
||||
-- ## Send conditionally to an audience
|
||||
--
|
||||
--
|
||||
-- Messages can be sent conditionally to an audience (when a condition is true):
|
||||
--
|
||||
--
|
||||
-- * To all players using @{#MESSAGE.ToAllIf}().
|
||||
-- * To a coalition using @{#MESSAGE.ToCoalitionIf}().
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **Applevangelist**
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @field #MESSAGE
|
||||
MESSAGE = {
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
}
|
||||
|
||||
--- Message Types
|
||||
@@ -70,199 +70,183 @@ MESSAGE.Type = {
|
||||
Information = "Information",
|
||||
Briefing = "Briefing Report",
|
||||
Overview = "Overview Report",
|
||||
Detailed = "Detailed Report",
|
||||
Detailed = "Detailed Report"
|
||||
}
|
||||
|
||||
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{#MESSAGE.ToClient} or @{#MESSAGE.ToCoalition} or @{#MESSAGE.ToAll} to send these Messages to the respective recipients.
|
||||
-- @param self
|
||||
-- @param #string Text is the text of the Message.
|
||||
-- @param #number Duration Duration in seconds how long the message text is shown.
|
||||
-- @param #string Category (Optional) String expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ".
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages if true.
|
||||
-- @return #MESSAGE self
|
||||
-- @usage
|
||||
--
|
||||
-- -- Create a series of new Messages.
|
||||
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
|
||||
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" )
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
|
||||
--
|
||||
function MESSAGE:New( Text, Duration, Category, ClearScreen )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
|
||||
self:F( { Text, Duration, Category } )
|
||||
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
|
||||
-- @param self
|
||||
-- @param #string MessageText is the text of the Message.
|
||||
-- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel.
|
||||
-- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ".
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages if true.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Create a series of new Messages.
|
||||
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
|
||||
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" )
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
|
||||
function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText, MessageDuration, MessageCategory } )
|
||||
|
||||
|
||||
self.MessageType = nil
|
||||
|
||||
-- When no MessageCategory is given, we don't show it as a title...
|
||||
if Category and Category ~= "" then
|
||||
if Category:sub( -1 ) ~= "\n" then
|
||||
self.MessageCategory = Category .. ": "
|
||||
|
||||
-- When no MessageCategory is given, we don't show it as a title...
|
||||
if MessageCategory and MessageCategory ~= "" then
|
||||
if MessageCategory:sub(-1) ~= "\n" then
|
||||
self.MessageCategory = MessageCategory .. ": "
|
||||
else
|
||||
self.MessageCategory = Category:sub( 1, -2 ) .. ":\n"
|
||||
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
|
||||
end
|
||||
else
|
||||
self.MessageCategory = ""
|
||||
end
|
||||
|
||||
self.ClearScreen = false
|
||||
if ClearScreen ~= nil then
|
||||
self.ClearScreen = ClearScreen
|
||||
|
||||
self.ClearScreen=false
|
||||
if ClearScreen~=nil then
|
||||
self.ClearScreen=ClearScreen
|
||||
end
|
||||
|
||||
self.MessageDuration = Duration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = Text:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
|
||||
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new MESSAGE object of a certain type.
|
||||
-- Note that these MESSAGE objects are not yet displayed on the display panel.
|
||||
-- You must use the functions @{Core.Message#ToClient} or @{Core.Message#ToCoalition} or @{Core.Message#ToAll} to send these Messages to the respective recipients.
|
||||
-- The message display times are automatically defined based on the timing settings in the @{Core.Settings} menu.
|
||||
|
||||
--- Creates a new MESSAGE object of a certain type.
|
||||
-- Note that these MESSAGE objects are not yet displayed on the display panel.
|
||||
-- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
|
||||
-- The message display times are automatically defined based on the timing settings in the @{Settings} menu.
|
||||
-- @param self
|
||||
-- @param #string MessageText is the text of the Message.
|
||||
-- @param #MESSAGE.Type MessageType The type of the message.
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information )
|
||||
-- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information )
|
||||
-- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update )
|
||||
-- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update )
|
||||
--
|
||||
function MESSAGE:NewType( MessageText, MessageType, ClearScreen )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText } )
|
||||
|
||||
|
||||
self.MessageType = MessageType
|
||||
|
||||
self.ClearScreen = false
|
||||
if ClearScreen ~= nil then
|
||||
self.ClearScreen = ClearScreen
|
||||
|
||||
self.ClearScreen=false
|
||||
if ClearScreen~=nil then
|
||||
self.ClearScreen=ClearScreen
|
||||
end
|
||||
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
|
||||
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc.
|
||||
|
||||
|
||||
--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
function MESSAGE:Clear()
|
||||
self:F()
|
||||
self.ClearScreen = true
|
||||
self.ClearScreen=true
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player".
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Client#CLIENT Client is the Group of the Client.
|
||||
-- @param Core.Settings#SETTINGS Settings used to display the message.
|
||||
-- @param Core.Settings#SETTINGS Settings Settings used to display the message.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send the 2 messages created with the @{New} method to the Client Group.
|
||||
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
|
||||
-- ClientGroup = Group.getByName( "ClientGroup" )
|
||||
--
|
||||
-- -- Send the 2 messages created with the @{New} method to the Client Group.
|
||||
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
|
||||
-- Client = CLIENT:FindByName("NameOfClientUnit")
|
||||
--
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ):ToClient( Client )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score" ):ToClient( Client )
|
||||
-- or
|
||||
-- MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score"):ToClient( Client )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score"):ToClient( Client )
|
||||
-- or
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score")
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
|
||||
-- MessageClient1:ToClient( Client )
|
||||
-- MessageClient2:ToClient( Client )
|
||||
--
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
|
||||
-- MessageClient1:ToClient( ClientGroup )
|
||||
-- MessageClient2:ToClient( ClientGroup )
|
||||
function MESSAGE:ToClient( Client, Settings )
|
||||
self:F( Client )
|
||||
self:ToUnit(Client,Settings)
|
||||
return self
|
||||
end
|
||||
self:F( Client )
|
||||
|
||||
--- Sends a MESSAGE to a SET_GROUP, SET_UNIT, or SET_CLIENT.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Core.Set#SET_GROUP Set The set to send to.
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return self
|
||||
function MESSAGE:ToSet(Set, Settings)
|
||||
for _,_obj in pairs (Set:GetSetObjects() or {}) do
|
||||
if _obj and _obj:IsAlive() then
|
||||
if _obj:IsInstanceOf("SET_GROUP") then
|
||||
self:ToGroup(_obj, Settings)
|
||||
elseif _obj:IsInstanceOf("SET_CLIENT") or _obj:IsInstanceOf("SET_UNIT") then
|
||||
self:ToUnit(_obj, Settings)
|
||||
end
|
||||
if Client and Client:GetClientGroupID() then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
end
|
||||
return self
|
||||
|
||||
local Unit = Client:GetClient()
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
local ClientGroupID = Client:GetClientGroupID()
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
--trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
|
||||
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a SET_GROUP, SET_UNIT, or SET_CLIENT if a condition is true.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Core.Set#SET_GROUP Set The set to send to.
|
||||
-- @param #boolean Condition The condition which needs to be true.
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return self
|
||||
function MESSAGE:ToSetIf(Set, Condition, Settings)
|
||||
if Set and Condition == true then
|
||||
self:ToSet(Set, Settings)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Group.
|
||||
--- Sends a MESSAGE to a Group.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Group#GROUP Group to which the message is displayed.
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE Message object.
|
||||
function MESSAGE:ToGroup( Group, Settings )
|
||||
self:F( Group.GroupName )
|
||||
|
||||
if Group and Group:IsAlive() then
|
||||
|
||||
if Group then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Unit.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Unit#UNIT Unit to which the message is displayed.
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE Message object.
|
||||
function MESSAGE:ToUnit( Unit, Settings )
|
||||
self:F( Unit.IdentifiableName )
|
||||
|
||||
if Unit and Unit:IsAlive() then
|
||||
if Unit then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
@@ -272,7 +256,6 @@ function MESSAGE:ToUnit( Unit, Settings )
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
local ID = Unit:GetID()
|
||||
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
@@ -280,100 +263,59 @@ function MESSAGE:ToUnit( Unit, Settings )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Country.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #number Country to which the message is displayed, e.g. country.id.GERMANY. For all country numbers see here: [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_country)
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE Message object.
|
||||
function MESSAGE:ToCountry( Country, Settings )
|
||||
self:F(Country )
|
||||
if Country then
|
||||
if self.MessageType then
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForCountry( Country, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Country.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #number Country to which the message is displayed, , e.g. country.id.GERMANY. For all country numbers see here: [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_country)
|
||||
-- @param #boolean Condition Sends the message only if the condition is true.
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE Message object.
|
||||
function MESSAGE:ToCountryIf( Country, Condition, Settings )
|
||||
self:F(Country )
|
||||
if Country and Condition == true then
|
||||
self:ToCountry( Country, Settings )
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to the Blue coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the BLUE coalition.
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToBlue()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToBlue()
|
||||
-- or
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", 25, "Penalty")
|
||||
-- MessageBLUE:ToBlue()
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the BLUE coalition.
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageBLUE:ToBlue()
|
||||
function MESSAGE:ToBlue()
|
||||
self:F()
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
|
||||
return self
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to the Red Coalition.
|
||||
--- Sends a MESSAGE to the Red Coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToRed()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToRed()
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty")
|
||||
-- MessageRED:ToRed()
|
||||
--
|
||||
function MESSAGE:ToRed()
|
||||
self:F()
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToRed()
|
||||
function MESSAGE:ToRed( )
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.RED )
|
||||
|
||||
return self
|
||||
self:ToCoalition( coalition.side.RED )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Coalition.
|
||||
--- Sends a MESSAGE to a Coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @param DCS#coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed.
|
||||
-- @param #DCS.coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed.
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE Message object.
|
||||
-- @usage
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty")
|
||||
-- MessageRED:ToCoalition( coalition.side.RED )
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToCoalition( coalition.side.RED )
|
||||
function MESSAGE:ToCoalition( CoalitionSide, Settings )
|
||||
self:F( CoalitionSide )
|
||||
self:F( CoalitionSide )
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
@@ -381,22 +323,20 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if CoalitionSide then
|
||||
if CoalitionSide then
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
self.CoalitionSide = CoalitionSide
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Coalition if the given Condition is true.
|
||||
--- Sends a MESSAGE to a Coalition if the given Condition is true.
|
||||
-- @param #MESSAGE self
|
||||
-- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{#DCS.coalition.side}.
|
||||
-- @param #boolean Condition Sends the message only if the condition is true.
|
||||
-- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}.
|
||||
-- @param #boolean Condition Sends the message only if the condition is true.
|
||||
-- @return #MESSAGE self
|
||||
function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
|
||||
self:F( CoalitionSide )
|
||||
@@ -404,51 +344,42 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
|
||||
if Condition and Condition == true then
|
||||
self:ToCoalition( CoalitionSide )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to all players.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @param #number Delay (Optional) Delay in seconds before the message is send. Default instantly (`nil`).
|
||||
-- @return #MESSAGE self
|
||||
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- -- Send a message created to all players.
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission"):ToAll()
|
||||
-- or
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission"):ToAll()
|
||||
-- or
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission")
|
||||
-- MessageAll:ToAll()
|
||||
--
|
||||
function MESSAGE:ToAll( Settings, Delay )
|
||||
-- -- Send a message created to all players.
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
|
||||
-- MessageAll:ToAll()
|
||||
function MESSAGE:ToAll(Settings)
|
||||
self:F()
|
||||
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, MESSAGE.ToAll, self, Settings, 0)
|
||||
else
|
||||
if self.MessageType then
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Sends a MESSAGE to all players if the given Condition is true.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #boolean Condition
|
||||
-- @return #MESSAGE
|
||||
function MESSAGE:ToAllIf( Condition )
|
||||
|
||||
@@ -456,204 +387,5 @@ function MESSAGE:ToAllIf( Condition )
|
||||
self:ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to DCS log file.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE self
|
||||
function MESSAGE:ToLog()
|
||||
|
||||
env.info(self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to DCS log file if the given Condition is true.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE self
|
||||
function MESSAGE:ToLogIf( Condition )
|
||||
|
||||
if Condition and Condition == true then
|
||||
env.info(self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
_MESSAGESRS = {}
|
||||
|
||||
--- Set up MESSAGE generally to allow Text-To-Speech via SRS and TTS functions. `SetMSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
|
||||
-- @param #string PathToSRS (optional) Path to SRS TTS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio" or your configuration file setting.
|
||||
-- @param #number Port Port (optional) number of SRS, defaults to 5002 or your configuration file setting.
|
||||
-- @param #string PathToCredentials (optional) Path to credentials file for Google.
|
||||
-- @param #number Frequency Frequency in MHz. Can also be given as a #table of frequencies.
|
||||
-- @param #number Modulation Modulation, i.e. radio.modulation.AM or radio.modulation.FM. Can also be given as a #table of modulations.
|
||||
-- @param #string Gender (optional) Gender, i.e. "male" or "female", defaults to "female" or your configuration file setting.
|
||||
-- @param #string Culture (optional) Culture, e.g. "en-US", defaults to "en-GB" or your configuration file setting.
|
||||
-- @param #string Voice (optional) Voice. Will override gender and culture settings, e.g. MSRS.Voices.Microsoft.Hazel or MSRS.Voices.Google.Standard.de_DE_Standard_D. Hint on Microsoft voices - working voices are limited to Hedda, Hazel, David, Zira and Hortense. **Must** be installed on your Desktop or Server!
|
||||
-- @param #number Coalition (optional) Coalition, can be coalition.side.RED, coalition.side.BLUE or coalition.side.NEUTRAL. Defaults to coalition.side.NEUTRAL.
|
||||
-- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest).
|
||||
-- @param #string Label (optional) Label, defaults to "MESSAGE" or the Message Category set.
|
||||
-- @param Core.Point#COORDINATE Coordinate (optional) Coordinate this messages originates from.
|
||||
-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC or MSRS.Backend.HOUND etc
|
||||
-- @param #string Provider (optional) Provider to be used, can be MSRS.Provider.WINDOWS or MSRS.Provider.GOOGLE or MSRS.Provider.PIPER etc
|
||||
-- @param #string Speaker (optional) Speaker to be used. Only for select provider PIPER TTS Voices, requires HOUND backend.
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS()
|
||||
--
|
||||
function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend,Provider,Speaker)
|
||||
|
||||
_MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
|
||||
|
||||
_MESSAGESRS.frequency = Frequency or MSRS.frequencies or 243
|
||||
_MESSAGESRS.modulation = Modulation or MSRS.modulations or radio.modulation.AM
|
||||
|
||||
_MESSAGESRS.MSRS = MSRS:New(_MESSAGESRS.PathToSRS,_MESSAGESRS.frequency, _MESSAGESRS.modulation)
|
||||
|
||||
_MESSAGESRS.coalition = Coalition or MSRS.coalition or coalition.side.NEUTRAL
|
||||
_MESSAGESRS.MSRS:SetCoalition(_MESSAGESRS.coalition)
|
||||
|
||||
_MESSAGESRS.coordinate = Coordinate
|
||||
|
||||
if Coordinate then
|
||||
_MESSAGESRS.MSRS:SetCoordinate(Coordinate)
|
||||
end
|
||||
|
||||
if Backend then
|
||||
_MESSAGESRS.MSRS:SetBackend(Backend)
|
||||
end
|
||||
|
||||
_MESSAGESRS.Culture = Culture or MSRS.culture or "en-GB"
|
||||
_MESSAGESRS.MSRS:SetCulture(Culture)
|
||||
|
||||
_MESSAGESRS.Gender = Gender or MSRS.gender or "female"
|
||||
_MESSAGESRS.MSRS:SetGender(Gender)
|
||||
|
||||
if PathToCredentials then
|
||||
_MESSAGESRS.MSRS:SetProviderOptionsGoogle(PathToCredentials)
|
||||
_MESSAGESRS.MSRS:SetProvider(MSRS.Provider.GOOGLE)
|
||||
end
|
||||
|
||||
if Provider then
|
||||
_MESSAGESRS.MSRS:SetProvider(Provider)
|
||||
end
|
||||
|
||||
_MESSAGESRS.label = Label or MSRS.Label or "MESSAGE"
|
||||
_MESSAGESRS.MSRS:SetLabel(_MESSAGESRS.label)
|
||||
|
||||
_MESSAGESRS.port = Port or MSRS.port or 5002
|
||||
_MESSAGESRS.MSRS:SetPort(_MESSAGESRS.port)
|
||||
|
||||
_MESSAGESRS.volume = Volume or MSRS.volume or 1
|
||||
_MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume)
|
||||
|
||||
if Voice then _MESSAGESRS.MSRS:SetVoice(Voice) end
|
||||
if Speaker then _MESSAGESRS.MSRS:SetSpeakerPiper(Speaker) end
|
||||
|
||||
_MESSAGESRS.voice = Voice or MSRS.voice --or MSRS.Voices.Microsoft.Hedda
|
||||
|
||||
_MESSAGESRS.SRSQ = MSRSQUEUE:New(_MESSAGESRS.label)
|
||||
end
|
||||
|
||||
--- Sends a message via SRS. `ToSRS()` will try to use as many attributes configured with @{Core.Message#MESSAGE.SetMSRS}() and @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #number frequency (optional) Frequency in MHz. Can also be given as a #table of frequencies. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
|
||||
-- @param #number modulation (optional) Modulation, i.e. radio.modulation.AM or radio.modulation.FM. Can also be given as a #table of modulations. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
|
||||
-- @param #string gender (optional) Gender, i.e. "male" or "female". Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #string culture (optional) Culture, e.g. "en-US". Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #string voice (optional) Voice. Will override gender and culture settings. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #number coalition (optional) Coalition, can be coalition.side.RED, coalition.side.BLUE or coalition.side.NEUTRAL. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #number volume (optional) Volume, can be between 0.0 and 1.0 (loudest). Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param Core.Point#COORDINATE coordinate (optional) Coordinate this messages originates from. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @return #MESSAGE self
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS()
|
||||
--
|
||||
function MESSAGE:ToSRS(frequency,modulation,gender,culture,voice,coalition,volume,coordinate)
|
||||
local tgender = gender or _MESSAGESRS.Gender
|
||||
if _MESSAGESRS.SRSQ then
|
||||
if voice then
|
||||
_MESSAGESRS.MSRS:SetVoice(voice or _MESSAGESRS.voice)
|
||||
end
|
||||
if coordinate then
|
||||
_MESSAGESRS.MSRS:SetCoordinate(coordinate)
|
||||
end
|
||||
local category = string.gsub(self.MessageCategory,":","")
|
||||
_MESSAGESRS.SRSQ:NewTransmission(self.MessageText,nil,_MESSAGESRS.MSRS,0.5,1,nil,nil,nil,frequency or _MESSAGESRS.frequency,modulation or _MESSAGESRS.modulation, gender or _MESSAGESRS.Gender,culture or _MESSAGESRS.Culture,nil,volume or _MESSAGESRS.volume,category,coordinate or _MESSAGESRS.coordinate)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a message via SRS on the blue coalition side.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #number frequency (optional) Frequency in MHz. Can also be given as a #table of frequencies. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
|
||||
-- @param #number modulation (optional) Modulation, i.e. radio.modulation.AM or radio.modulation.FM. Can also be given as a #table of modulations. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
|
||||
-- @param #string gender (optional) Gender, i.e. "male" or "female". Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #string culture (optional) Culture, e.g. "en-US. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #string voice (optional) Voice. Will override gender and culture settings. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #number volume (optional) Volume, can be between 0.0 and 1.0 (loudest). Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param Core.Point#COORDINATE coordinate (optional) Coordinate this messages originates from. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @return #MESSAGE self
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSBlue()
|
||||
--
|
||||
function MESSAGE:ToSRSBlue(frequency,modulation,gender,culture,voice,volume,coordinate)
|
||||
self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.BLUE,volume,coordinate)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a message via SRS on the red coalition side.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #number frequency (optional) Frequency in MHz. Can also be given as a #table of frequencies. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
|
||||
-- @param #number modulation (optional) Modulation, i.e. radio.modulation.AM or radio.modulation.FM. Can also be given as a #table of modulations. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
|
||||
-- @param #string gender (optional) Gender, i.e. "male" or "female". Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #string culture (optional) Culture, e.g. "en-US. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #string voice (optional) Voice. Will override gender and culture settings. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #number volume (optional) Volume, can be between 0.0 and 1.0 (loudest). Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param Core.Point#COORDINATE coordinate (optional) Coordinate this messages originates from. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @return #MESSAGE self
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSRed()
|
||||
--
|
||||
function MESSAGE:ToSRSRed(frequency,modulation,gender,culture,voice,volume,coordinate)
|
||||
self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.RED,volume,coordinate)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a message via SRS to all - via the neutral coalition side.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #number frequency (optional) Frequency in MHz. Can also be given as a #table of frequencies. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
|
||||
-- @param #number modulation (optional) Modulation, i.e. radio.modulation.AM or radio.modulation.FM. Can also be given as a #table of modulations. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
|
||||
-- @param #string gender (optional) Gender, i.e. "male" or "female". Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #string culture (optional) Culture, e.g. "en-US. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #string voice (optional) Voice. Will override gender and culture settings. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param #number volume (optional) Volume, can be between 0.0 and 1.0 (loudest). Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @param Core.Point#COORDINATE coordinate (optional) Coordinate this messages originates from. Only needed if you want to change defaults set with `MESSAGE.SetMSRS()`.
|
||||
-- @return #MESSAGE self
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSAll()
|
||||
--
|
||||
function MESSAGE:ToSRSAll(frequency,modulation,gender,culture,voice,volume,coordinate)
|
||||
self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.NEUTRAL,volume,coordinate)
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1,446 +0,0 @@
|
||||
--- **Core** - Path from A to B.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Path from A to B
|
||||
-- * Arbitrary number of points
|
||||
-- * Automatically from lines drawtool
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Core.Pathline
|
||||
-- @image CORE_Pathline.png
|
||||
|
||||
|
||||
--- PATHLINE class.
|
||||
-- @type PATHLINE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #string name Name of the path line.
|
||||
-- @field #table points List of 3D points defining the path.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *The shortest distance between two points is a straight line.* -- Archimedes
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The PATHLINE Concept
|
||||
--
|
||||
-- List of points defining a path from A to B. The pathline can consist of multiple points. Each point holds the information of its position, the surface type, the land height
|
||||
-- and the water depth (if over sea).
|
||||
--
|
||||
-- Line drawings created in the mission editor are automatically registered as pathlines and stored in the MOOSE database.
|
||||
-- They can be accessed with the @{#PATHLINE.FindByName) function.
|
||||
--
|
||||
-- # Constructor
|
||||
--
|
||||
-- The @{PATHLINE.New) function creates a new PATHLINE object. This does not hold any points. Points can be added with the @{#PATHLINE.AddPointFromVec2} and @{#PATHLINE.AddPointFromVec3}
|
||||
--
|
||||
-- For a given table of 2D or 3D positions, a new PATHLINE object can be created with the @{#PATHLINE.NewFromVec2Array} or @{#PATHLINE.NewFromVec3Array}, respectively.
|
||||
--
|
||||
-- # Line Drawings
|
||||
--
|
||||
-- The most convenient way to create a pathline is the draw panel feature in the DCS mission editor. You can select "Line" and then "Segments", "Segment" or "Free" to draw your lines.
|
||||
-- These line drawings are then automatically added to the MOOSE database as PATHLINE objects and can be retrieved with the @{#PATHLINE.FindByName) function, where the name is the one
|
||||
-- you specify in the draw panel.
|
||||
--
|
||||
-- # Mark on F10 map
|
||||
--
|
||||
-- The points of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
|
||||
-- water depth.
|
||||
--
|
||||
-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`).
|
||||
--
|
||||
-- @field #PATHLINE
|
||||
PATHLINE = {
|
||||
ClassName = "PATHLINE",
|
||||
lid = nil,
|
||||
points = {},
|
||||
}
|
||||
|
||||
--- Point of line.
|
||||
-- @type PATHLINE.Point
|
||||
-- @field DCS#Vec3 vec3 3D position.
|
||||
-- @field DCS#Vec2 vec2 2D position.
|
||||
-- @field #number surfaceType Surface type.
|
||||
-- @field #number landHeight Land height in meters.
|
||||
-- @field #number depth Water depth in meters.
|
||||
-- @field #number markerID Marker ID.
|
||||
-- @field #number lineID Line marker ID.
|
||||
|
||||
|
||||
--- PATHLINE class version.
|
||||
-- @field #string version
|
||||
PATHLINE.version="0.2.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new PATHLINE object. Points need to be added later.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #string Name Name of the path.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:New(Name)
|
||||
|
||||
-- Inherit everything from INTEL class.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#PATHLINE
|
||||
|
||||
self.name=Name or "Unknown Path"
|
||||
|
||||
self.lid=string.format("PATHLINE %s | ", Name)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new PATHLINE object from a given list of 2D points.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #string Name Name of the pathline.
|
||||
-- @param #table Vec2Array List of DCS#Vec2 points.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:NewFromVec2Array(Name, Vec2Array)
|
||||
|
||||
local self=PATHLINE:New(Name)
|
||||
|
||||
for i=1,#Vec2Array do
|
||||
self:AddPointFromVec2(Vec2Array[i])
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new PATHLINE object from a given list of 3D points.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #string Name Name of the pathline.
|
||||
-- @param #table Vec3Array List of DCS#Vec3 points.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:NewFromVec3Array(Name, Vec3Array)
|
||||
|
||||
local self=PATHLINE:New(Name)
|
||||
|
||||
for i=1,#Vec3Array do
|
||||
self:AddPointFromVec3(Vec3Array[i])
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Find a pathline in the database.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #string Name The name of the pathline.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:FindByName(Name)
|
||||
local pathline = _DATABASE:FindPathline(Name)
|
||||
return pathline
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Add a point to the path from a given 2D position. The third dimension is determined from the land height.
|
||||
-- @param #PATHLINE self
|
||||
-- @param DCS#Vec2 Vec2 The 2D vector (x,y) to add.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:AddPointFromVec2(Vec2)
|
||||
|
||||
if Vec2 then
|
||||
|
||||
local point=self:_CreatePoint(Vec2)
|
||||
|
||||
table.insert(self.points, point)
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a point to the path from a given 3D position.
|
||||
-- @param #PATHLINE self
|
||||
-- @param DCS#Vec3 Vec3 The 3D vector (x,y) to add.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:AddPointFromVec3(Vec3)
|
||||
|
||||
if Vec3 then
|
||||
|
||||
local point=self:_CreatePoint(Vec3)
|
||||
|
||||
table.insert(self.points, point)
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get name of pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @return #string Name of the pathline.
|
||||
function PATHLINE:GetName()
|
||||
return self.name
|
||||
end
|
||||
|
||||
--- Get number of points.
|
||||
-- @param #PATHLINE self
|
||||
-- @return #number Number of points.
|
||||
function PATHLINE:GetNumberOfPoints()
|
||||
local N=#self.points
|
||||
return N
|
||||
end
|
||||
|
||||
--- Get points of pathline. Not that points are tables, that contain more information as just the 2D or 3D position but also the surface type etc.
|
||||
-- @param #PATHLINE self
|
||||
-- @return #list <#PATHLINE.Point> List of points.
|
||||
function PATHLINE:GetPoints()
|
||||
return self.points
|
||||
end
|
||||
|
||||
--- Get 3D points of pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @return <DCS#Vec3> List of DCS#Vec3 points.
|
||||
function PATHLINE:GetPoints3D()
|
||||
|
||||
local vecs={}
|
||||
|
||||
for _,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
table.insert(vecs, point.vec3)
|
||||
end
|
||||
|
||||
return vecs
|
||||
end
|
||||
|
||||
--- Get 2D points of pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @return <DCS#Vec2> List of DCS#Vec2 points.
|
||||
function PATHLINE:GetPoints2D()
|
||||
|
||||
local vecs={}
|
||||
|
||||
for _,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
table.insert(vecs, point.vec2)
|
||||
end
|
||||
|
||||
return vecs
|
||||
end
|
||||
|
||||
--- Get COORDINATES of pathline. Note that COORDINATE objects are created when calling this function. That does involve deep copy calls and can have an impact on performance if done too often.
|
||||
-- @param #PATHLINE self
|
||||
-- @return <Core.Point#COORDINATE> List of COORDINATES points.
|
||||
function PATHLINE:GetCoordinates()
|
||||
|
||||
local vecs={}
|
||||
|
||||
for _,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
local coord=COORDINATE:NewFromVec3(point.vec3)
|
||||
table.insert(vecs,coord)
|
||||
end
|
||||
|
||||
return vecs
|
||||
end
|
||||
|
||||
--- Get the n-th point of the pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #number n (optional) The index of the point. Default is the first point.
|
||||
-- @return #PATHLINE.Point Point.
|
||||
function PATHLINE:GetPointFromIndex(n)
|
||||
|
||||
local N=self:GetNumberOfPoints()
|
||||
|
||||
n=n or 1
|
||||
|
||||
local point=nil --#PATHLINE.Point
|
||||
|
||||
if n>=1 and n<=N then
|
||||
point=self.points[n]
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: No point in pathline for N=%s", tostring(n)))
|
||||
end
|
||||
|
||||
return point
|
||||
end
|
||||
|
||||
--- Get the 3D position of the n-th point.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #number n The n-th point.
|
||||
-- @return DCS#Vec3 Position in 3D.
|
||||
function PATHLINE:GetPoint3DFromIndex(n)
|
||||
|
||||
local point=self:GetPointFromIndex(n)
|
||||
|
||||
if point then
|
||||
return point.vec3
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get the 2D position of the n-th point.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #number n The n-th point.
|
||||
-- @return DCS#Vec2 Position in 3D.
|
||||
function PATHLINE:GetPoint2DFromIndex(n)
|
||||
|
||||
local point=self:GetPointFromIndex(n)
|
||||
|
||||
if point then
|
||||
return point.vec2
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Calculate the length of the line.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #boolean Project2D Calculate 2D distance between points.
|
||||
-- @return #number Length in meters.
|
||||
function PATHLINE:GetLength(Project2D)
|
||||
local l=0
|
||||
|
||||
local np=#self.points
|
||||
|
||||
for i=1,np-1 do
|
||||
local p1=self.points[i] --#PATHLINE.Point
|
||||
local p2=self.points[i+1] --#PATHLINE.Point
|
||||
|
||||
if Project2D then
|
||||
l=l+UTILS.VecDist2D(p1.vec2, p2.vec2)
|
||||
else
|
||||
l=l+UTILS.VecDist3D(p1.vec3, p2.vec3)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return l
|
||||
end
|
||||
|
||||
|
||||
--- Mark points on F10 map.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #boolean Switch If `true` or nil, set marks. If `false`, remove marks.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:MarkPoints(Switch)
|
||||
for i,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
if Switch==false then
|
||||
|
||||
if point.markerID then
|
||||
UTILS.RemoveMark(point.markerID)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if point.markerID then
|
||||
UTILS.RemoveMark(point.markerID)
|
||||
end
|
||||
|
||||
point.markerID=UTILS.GetMarkID()
|
||||
|
||||
local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m", self.name, i, point.surfaceType, point.landHeight, point.depth)
|
||||
|
||||
trigger.action.markToAll(point.markerID, text, point.vec3, "")
|
||||
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Draw line on F10 map.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #number Recipient Recipent of the line: -1=All.
|
||||
-- @param #table Color (optional) Color as RGB table plus alpha value. Default {1, 0, 0, 1.0}.
|
||||
-- @param #number LineType (optional) Line type: 1=Solid (default).
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:DrawLine(Recipient, Color, LineType)
|
||||
|
||||
-- Input
|
||||
Recipient= Recipient or -1
|
||||
Color= Color or {1,0,0, 1.0}
|
||||
LineType=LineType or 1
|
||||
local ReadOnly=false
|
||||
|
||||
|
||||
local np=#self.points
|
||||
|
||||
for i=1,np-1 do
|
||||
local p1=self.points[i] --#PATHLINE.Point
|
||||
local p2=self.points[i+1] --#PATHLINE.Point
|
||||
|
||||
p1.lineID = UTILS.GetMarkID()
|
||||
|
||||
trigger.action.lineToAll(Recipient, p1.lineID, p1.vec3, p2.vec3, Color, LineType, ReadOnly, "")
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove line on F10 map.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #number Delay Delay in seconds before line is removed.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:UnDrawLine(Delay)
|
||||
|
||||
|
||||
local np=#self.points
|
||||
|
||||
for _,_point in pairs(self.points) do
|
||||
local p=_point --#PATHLINE.Point
|
||||
if p.lineID then
|
||||
UTILS.RemoveMark(p.lineID, Delay)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get 3D points of pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @param DCS#Vec3 Vec Position vector. Can also be a DCS#Vec2 in which case the altitude at landheight is taken.
|
||||
-- @return #PATHLINE.Point
|
||||
function PATHLINE:_CreatePoint(Vec)
|
||||
|
||||
local point={} --#PATHLINE.Point
|
||||
|
||||
if Vec.z then
|
||||
-- Given vec is 3D
|
||||
point.vec3=UTILS.DeepCopy(Vec)
|
||||
point.vec2={x=Vec.x, y=Vec.z}
|
||||
else
|
||||
-- Given vec is 2D
|
||||
point.vec2=UTILS.DeepCopy(Vec)
|
||||
point.vec3={x=Vec.x, y=land.getHeight(Vec), z=Vec.y}
|
||||
end
|
||||
|
||||
-- Get surface type.
|
||||
point.surfaceType=land.getSurfaceType(point.vec2)
|
||||
|
||||
-- Get land height and depth.
|
||||
point.landHeight, point.depth=land.getSurfaceHeightWithSeabed(point.vec2)
|
||||
|
||||
point.markerID=nil
|
||||
|
||||
return point
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
+724
-1166
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
||||
--- **Core** - Provides a handy means to create messages and reports.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Create text blocks that are formatted.
|
||||
-- * Create automatic indents.
|
||||
-- * Variate the delimiters between reporting lines.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: FlightControl : Design & Programming
|
||||
@@ -15,8 +15,8 @@
|
||||
-- @module Core.Report
|
||||
-- @image Core_Report.JPG
|
||||
|
||||
---
|
||||
-- @type REPORT
|
||||
|
||||
--- @type REPORT
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Provides a handy means to create messages and reports.
|
||||
@@ -36,7 +36,7 @@ function REPORT:New( Title )
|
||||
|
||||
self.Report = {}
|
||||
|
||||
self:SetTitle( Title or "" )
|
||||
self:SetTitle( Title or "" )
|
||||
self:SetIndent( 3 )
|
||||
|
||||
return self
|
||||
@@ -45,26 +45,28 @@ end
|
||||
--- Has the REPORT Text?
|
||||
-- @param #REPORT self
|
||||
-- @return #boolean
|
||||
function REPORT:HasText() -- R2.1
|
||||
|
||||
function REPORT:HasText() --R2.1
|
||||
|
||||
return #self.Report > 0
|
||||
end
|
||||
|
||||
|
||||
--- Set indent of a REPORT.
|
||||
-- @param #REPORT self
|
||||
-- @param #number Indent
|
||||
-- @return #REPORT
|
||||
function REPORT:SetIndent( Indent ) -- R2.1
|
||||
function REPORT:SetIndent( Indent ) --R2.1
|
||||
self.Indent = Indent
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Add a new line to a REPORT.
|
||||
-- @param #REPORT self
|
||||
-- @param #string Text
|
||||
-- @return #REPORT
|
||||
function REPORT:Add( Text )
|
||||
self.Report[#self.Report + 1] = Text
|
||||
self.Report[#self.Report+1] = Text
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -74,17 +76,17 @@ end
|
||||
-- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best.
|
||||
-- @return #REPORT
|
||||
function REPORT:AddIndent( Text, Separator )
|
||||
self.Report[#self.Report + 1] = ((Separator and Separator .. string.rep( " ", self.Indent - 1 )) or string.rep( " ", self.Indent )) .. Text:gsub( "\n", "\n" .. string.rep( " ", self.Indent ) )
|
||||
self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Produces the text of the report, taking into account an optional delimiter, which is \n by default.
|
||||
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default.
|
||||
-- @param #REPORT self
|
||||
-- @param #string Delimiter (optional) A delimiter text.
|
||||
-- @return #string The report text.
|
||||
function REPORT:Text( Delimiter )
|
||||
Delimiter = Delimiter or "\n"
|
||||
local ReportText = (self.Title ~= "" and self.Title .. Delimiter or self.Title) .. table.concat( self.Report, Delimiter ) or ""
|
||||
local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or ""
|
||||
return ReportText
|
||||
end
|
||||
|
||||
@@ -93,7 +95,7 @@ end
|
||||
-- @param #string Title The title of the report.
|
||||
-- @return #REPORT
|
||||
function REPORT:SetTitle( Title )
|
||||
self.Title = Title
|
||||
self.Title = Title
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
---- **Core** - SCHEDULEDISPATCHER dispatches the different schedules.
|
||||
--
|
||||
--- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects.
|
||||
--
|
||||
--
|
||||
-- This class is tricky and needs some thorough explanation.
|
||||
-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects.
|
||||
-- The SCHEDULEDISPATCHER class ensures that:
|
||||
--
|
||||
--
|
||||
-- - Scheduled functions are planned according the SCHEDULER object parameters.
|
||||
-- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters.
|
||||
-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters.
|
||||
--
|
||||
--
|
||||
-- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection:
|
||||
--
|
||||
--
|
||||
-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory.
|
||||
-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection!
|
||||
--
|
||||
-- The non-persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collected when the parent object is destroyed, or set to nil and garbage collected.
|
||||
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
|
||||
--
|
||||
-- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected.
|
||||
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
|
||||
-- these will not be executed anymore when the SCHEDULER object has been destroyed.
|
||||
--
|
||||
--
|
||||
-- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object.
|
||||
-- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER.
|
||||
-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method.
|
||||
-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method.
|
||||
-- The Schedule() method returns the CallID that is the reference ID for each planned schedule.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Contributions: -
|
||||
-- ### Authors: FlightControl : Design & Programming
|
||||
--
|
||||
--
|
||||
-- @module Core.ScheduleDispatcher
|
||||
-- @image Core_Schedule_Dispatcher.JPG
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number CallID Call ID counter.
|
||||
-- @field #table PersistentSchedulers Persistent schedulers.
|
||||
-- @field #table PersistentSchedulers Persistant schedulers.
|
||||
-- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists.
|
||||
-- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ).
|
||||
-- @extends Core.Base#BASE
|
||||
@@ -46,11 +46,11 @@
|
||||
--- The SCHEDULEDISPATCHER structure
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
SCHEDULEDISPATCHER = {
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
}
|
||||
|
||||
--- Player data table holding all important parameters of each player.
|
||||
@@ -58,7 +58,7 @@ SCHEDULEDISPATCHER = {
|
||||
-- @field #function Function The schedule function to be called.
|
||||
-- @field #table Arguments Schedule function arguments.
|
||||
-- @field #number Start Start time in seconds.
|
||||
-- @field #number Repeat Repeat time interval in seconds.
|
||||
-- @field #number Repeat Repeat time intervall in seconds.
|
||||
-- @field #number Randomize Randomization factor [0,1].
|
||||
-- @field #number Stop Stop time in seconds.
|
||||
-- @field #number StartTime Time in seconds when the scheduler is created.
|
||||
@@ -77,7 +77,7 @@ end
|
||||
|
||||
--- Add a Schedule to the ScheduleDispatcher.
|
||||
-- The development of this method was really tidy.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified.
|
||||
-- Nothing of this code should be modified without testing it thoroughly.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
@@ -85,7 +85,7 @@ end
|
||||
-- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction.
|
||||
-- @param #number Start Start time in seconds.
|
||||
-- @param #number Repeat Repeat interval in seconds.
|
||||
-- @param #number Randomize Randomization factor [0,1].
|
||||
-- @param #number Randomize Radomization factor [0,1].
|
||||
-- @param #number Stop Stop time in seconds.
|
||||
-- @param #number TraceLevel Trace level [0,3].
|
||||
-- @param Core.Fsm#FSM Fsm Finite state model.
|
||||
@@ -95,32 +95,32 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
-- Increase counter.
|
||||
self.CallID = self.CallID + 1
|
||||
|
||||
|
||||
-- Create ID.
|
||||
local CallID = self.CallID .. "#" .. (Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "") or ""
|
||||
|
||||
self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) )
|
||||
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
|
||||
|
||||
self:T2(string.format("Adding schedule #%d CallID=%s", self.CallID, CallID))
|
||||
|
||||
-- Initialize PersistentSchedulers
|
||||
self.PersistentSchedulers = self.PersistentSchedulers or {}
|
||||
|
||||
-- Initialize the ObjectSchedulers array, which is a weakly coupled table.
|
||||
-- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array.
|
||||
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
|
||||
|
||||
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
|
||||
|
||||
if Scheduler.MasterObject then
|
||||
--env.info("FF Object Scheduler")
|
||||
self.ObjectSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, ObjectScheduler = tostring( self.ObjectSchedulers[CallID] ), MasterObject = tostring( Scheduler.MasterObject ) } )
|
||||
self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
|
||||
else
|
||||
--env.info("FF Persistent Scheduler")
|
||||
self.PersistentSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
|
||||
end
|
||||
|
||||
|
||||
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
|
||||
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
|
||||
self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
self.Schedule[Scheduler][CallID] = {} --#SCHEDULEDISPATCHER.ScheduleData
|
||||
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
|
||||
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
|
||||
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
|
||||
@@ -128,7 +128,8 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
|
||||
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
|
||||
self.Schedule[Scheduler][CallID].Stop = Stop
|
||||
|
||||
|
||||
|
||||
-- This section handles the tracing of the scheduled calls.
|
||||
-- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated.
|
||||
-- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
|
||||
@@ -150,10 +151,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
-- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result.
|
||||
-- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level.
|
||||
-- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher.
|
||||
-- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often.
|
||||
-- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often.
|
||||
|
||||
local Info = {}
|
||||
|
||||
|
||||
if debug then
|
||||
TraceLevel = TraceLevel or 2
|
||||
Info = debug.getinfo( TraceLevel, "nlS" )
|
||||
@@ -167,7 +168,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
--- Function passed to the DCS timer.scheduleFunction()
|
||||
self.Schedule[Scheduler][CallID].CallHandler = function( Params )
|
||||
|
||||
|
||||
local CallID = Params.CallID
|
||||
local Info = Params.Info or {}
|
||||
local Source = Info.source or "?"
|
||||
@@ -175,33 +176,33 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
local Name = Info.name or "?"
|
||||
|
||||
local ErrorHandler = function( errmsg )
|
||||
env.info( "Error in timer function: " .. errmsg or "" )
|
||||
env.info( "Error in timer function: " .. errmsg )
|
||||
if BASE.Debug ~= nil then
|
||||
env.info( BASE.Debug.traceback() )
|
||||
end
|
||||
return errmsg
|
||||
end
|
||||
|
||||
-- Get object or persistent scheduler object.
|
||||
local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER
|
||||
|
||||
-- Get object or persistant scheduler object.
|
||||
local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER
|
||||
if not Scheduler then
|
||||
Scheduler = self.PersistentSchedulers[CallID]
|
||||
end
|
||||
|
||||
-- self:T3( { Scheduler = Scheduler } )
|
||||
|
||||
|
||||
--self:T3( { Scheduler = Scheduler } )
|
||||
|
||||
if Scheduler then
|
||||
|
||||
local MasterObject = tostring( Scheduler.MasterObject )
|
||||
|
||||
local MasterObject = tostring(Scheduler.MasterObject)
|
||||
|
||||
-- Schedule object.
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
--self:T3( { Schedule = Schedule } )
|
||||
|
||||
-- self:T3( { Schedule = Schedule } )
|
||||
|
||||
local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object?
|
||||
local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object?
|
||||
local ShowTrace = Scheduler.ShowTrace
|
||||
|
||||
|
||||
local ScheduleFunction = Schedule.Function
|
||||
local ScheduleArguments = Schedule.Arguments or {}
|
||||
local Start = Schedule.Start
|
||||
@@ -209,11 +210,12 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
local Randomize = Schedule.Randomize or 0
|
||||
local Stop = Schedule.Stop or 0
|
||||
local ScheduleID = Schedule.ScheduleID
|
||||
|
||||
local Prefix = (Repeat == 0) and "--->" or "+++>"
|
||||
|
||||
|
||||
|
||||
local Prefix = ( Repeat == 0 ) and "--->" or "+++>"
|
||||
|
||||
local Status, Result
|
||||
-- self:E( { SchedulerObject = SchedulerObject } )
|
||||
--self:E( { SchedulerObject = SchedulerObject } )
|
||||
if SchedulerObject then
|
||||
local function Timer()
|
||||
if ShowTrace then
|
||||
@@ -228,39 +230,40 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
if ShowTrace then
|
||||
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
end
|
||||
Status, Result = xpcall( Timer, ErrorHandler )
|
||||
end
|
||||
|
||||
|
||||
local CurrentTime = timer.getTime()
|
||||
local StartTime = Schedule.StartTime
|
||||
|
||||
-- Debug info.
|
||||
self:F3( { CallID = CallID, ScheduleID = ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
|
||||
if Status and ((Result == nil) or (Result and Result ~= false)) then
|
||||
|
||||
if Repeat ~= 0 and ((Stop == 0) or (Stop ~= 0 and CurrentTime <= StartTime + Stop)) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random( -(Randomize * Repeat / 2), (Randomize * Repeat / 2) ) + 0.0001 -- Accuracy
|
||||
-- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
self:F3( { CallID=CallID, ScheduleID=ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
|
||||
|
||||
if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
|
||||
|
||||
if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random(- ( Randomize * Repeat / 2 ), ( Randomize * Repeat / 2 )) + 0.0001 -- Accuracy
|
||||
--self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
return ScheduleTime -- returns the next time the function needs to be called.
|
||||
else
|
||||
self:Stop( Scheduler, CallID )
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
self:Stop( Scheduler, CallID )
|
||||
end
|
||||
else
|
||||
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
self:Start( Scheduler, CallID, Info )
|
||||
|
||||
|
||||
return CallID
|
||||
end
|
||||
|
||||
@@ -284,33 +287,33 @@ end
|
||||
-- @param #string Info (Optional) Debug info.
|
||||
function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
|
||||
self:F2( { Start = CallID, Scheduler = Scheduler } )
|
||||
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only start when there is no ScheduleID defined!
|
||||
-- This prevents to "Start" the scheduler twice with the same CallID...
|
||||
if not Schedule.ScheduleID then
|
||||
|
||||
|
||||
-- Current time in seconds.
|
||||
local Tnow = timer.getTime()
|
||||
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
|
||||
local Tnow=timer.getTime()
|
||||
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
|
||||
-- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction
|
||||
Schedule.ScheduleID = timer.scheduleFunction( Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start )
|
||||
|
||||
self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
Schedule.ScheduleID = timer.scheduleFunction(Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start)
|
||||
|
||||
self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
-- Recursive.
|
||||
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
||||
self:Start( Scheduler, CallID, Info ) -- Recursive
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -322,29 +325,29 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
|
||||
self:F2( { Stop = CallID, Scheduler = Scheduler } )
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing.
|
||||
if Schedule and Schedule.ScheduleID then
|
||||
|
||||
self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
|
||||
if Schedule.ScheduleID then
|
||||
|
||||
self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
|
||||
-- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction
|
||||
timer.removeFunction( Schedule.ScheduleID )
|
||||
|
||||
timer.removeFunction(Schedule.ScheduleID)
|
||||
|
||||
Schedule.ScheduleID = nil
|
||||
|
||||
|
||||
else
|
||||
self:T( string.format( "Error no ScheduleID for CallID=%s", tostring( CallID ) ) )
|
||||
self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID)))
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
||||
self:Stop( Scheduler, CallID ) -- Recursive
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -359,7 +362,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler )
|
||||
end
|
||||
end
|
||||
|
||||
--- Show tracing info.
|
||||
--- Shopw tracing info.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
function SCHEDULEDISPATCHER:ShowTrace( Scheduler )
|
||||
@@ -375,13 +378,3 @@ function SCHEDULEDISPATCHER:NoTrace( Scheduler )
|
||||
Scheduler.ShowTrace = false
|
||||
end
|
||||
|
||||
--- Helper for memory cleanup for self stopping schedulers
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
-- @param #string CallID (Optional) Scheduler Call ID.
|
||||
function SCHEDULEDISPATCHER:_Reclaim( Scheduler, CallID )
|
||||
self:Stop( Scheduler, CallID ) -- remove DCS timer, nil ScheduleID
|
||||
if self.Schedule[Scheduler] then self.Schedule[Scheduler][CallID] = nil end
|
||||
self.ObjectSchedulers[CallID] = nil
|
||||
self.PersistentSchedulers[CallID] = nil
|
||||
end
|
||||
|
||||
@@ -3,35 +3,39 @@
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Schedule functions over time,
|
||||
-- * optionally in an optional specified time interval,
|
||||
-- * optionally **repeating** with a specified time repeat interval,
|
||||
-- * optionally **randomizing** with a specified time interval randomization factor,
|
||||
-- * optionally **stop** the repeating after a specified time interval.
|
||||
-- * optionally in an optional specified time interval,
|
||||
-- * optionally **repeating** with a specified time repeat interval,
|
||||
-- * optionally **randomizing** with a specified time interval randomization factor,
|
||||
-- * optionally **stop** the repeating after a specified time interval.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ### [SCHEDULER Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Core/Scheduler)
|
||||
-- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SCH%20-%20Scheduler)
|
||||
--
|
||||
-- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler)
|
||||
--
|
||||
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
-- ### None
|
||||
--
|
||||
--
|
||||
-- ### [SCHEDULER YouTube Channel (none)]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * FlightControl : Concept & Testing
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
-- * FlightControl : Design & Programming
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.Scheduler
|
||||
@@ -44,61 +48,62 @@
|
||||
-- @field #boolean ShowTrace Trace info if true.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization.
|
||||
--
|
||||
--
|
||||
-- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**.
|
||||
-- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called.
|
||||
-- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{#SCHEDULER.Start}() and @{#SCHEDULER.Stop}(),
|
||||
-- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(),
|
||||
-- which can start and stop specific repeating schedules respectively within a SCHEDULER object.
|
||||
--
|
||||
-- ## SCHEDULER constructor
|
||||
--
|
||||
--
|
||||
-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters:
|
||||
--
|
||||
--
|
||||
-- The @{#SCHEDULER.New}() method returns 2 variables:
|
||||
--
|
||||
--
|
||||
-- 1. The SCHEDULER object reference.
|
||||
-- 2. The first schedule planned in the SCHEDULER object.
|
||||
--
|
||||
--
|
||||
-- To clarify the different appliances, lets have a look at the following examples:
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a persistent schedule.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection.
|
||||
--
|
||||
--
|
||||
-- MasterObject = SCHEDULER:New()
|
||||
-- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, but does not schedule anything.
|
||||
-- A separate schedule is created by using the MasterObject using the method :Schedule..., which returns a ScheduleID
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence...
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is set to nil or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE.
|
||||
-- A separate schedule is created by using the MasterObject using the method :Schedule()..., which returns a ScheduleID
|
||||
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
|
||||
-- As a result, the MasterObject will cancel any planned schedule.
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object with a persistent schedule.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
|
||||
--
|
||||
--
|
||||
-- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence...
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
@@ -107,13 +112,13 @@
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and schedules a method call (ScheduleFunction),
|
||||
-- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject).
|
||||
-- Both a MasterObject and a SchedulerID variable are returned.
|
||||
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
|
||||
-- As a result, the MasterObject will cancel the planned schedule.
|
||||
--
|
||||
--
|
||||
-- ## SCHEDULER timer stopping and (re-)starting.
|
||||
--
|
||||
-- The SCHEDULER can be stopped and restarted with the following methods:
|
||||
@@ -128,70 +133,70 @@
|
||||
-- MasterObject:Stop( SchedulerID )
|
||||
-- ...
|
||||
-- MasterObject:Start( SchedulerID )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
|
||||
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
|
||||
--
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
|
||||
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
|
||||
--
|
||||
-- ## Create a new schedule
|
||||
--
|
||||
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
|
||||
--
|
||||
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
|
||||
-- This method is used by the :New() constructor when a new schedule is planned.
|
||||
--
|
||||
--
|
||||
-- Consider the following code fragment of the SCHEDULER object creation.
|
||||
--
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
--
|
||||
-- Several parameters can be specified that influence the behavior of a Schedule.
|
||||
--
|
||||
--
|
||||
-- Several parameters can be specified that influence the behaviour of a Schedule.
|
||||
--
|
||||
-- ### A single schedule, immediately executed
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ...
|
||||
--
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ...
|
||||
--
|
||||
-- ### A single schedule, planned over time
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 )
|
||||
--
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ...
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- and repeating 60 every seconds ...
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- and repeating 60 seconds, with a 50% time interval randomization ...
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- which is in this example between **30** and **90** seconds.
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- The schedule will repeat every 60 seconds.
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- which is in this example between **30** and **90** seconds.
|
||||
-- The schedule will stop after **300** seconds.
|
||||
--
|
||||
--
|
||||
-- @field #SCHEDULER
|
||||
SCHEDULER = {
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
}
|
||||
|
||||
--- SCHEDULER constructor.
|
||||
@@ -204,17 +209,17 @@ SCHEDULER = {
|
||||
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
|
||||
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
|
||||
-- @return #SCHEDULER self.
|
||||
-- @return #string The ScheduleID of the planned schedule.
|
||||
-- @return #table The ScheduleID of the planned schedule.
|
||||
function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
|
||||
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER
|
||||
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
local ScheduleID = nil
|
||||
|
||||
|
||||
self.MasterObject = MasterObject
|
||||
self.ShowTrace = false
|
||||
|
||||
|
||||
if SchedulerFunction then
|
||||
ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 )
|
||||
end
|
||||
@@ -230,8 +235,8 @@ end
|
||||
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
|
||||
-- @param #number Repeat Specifies the time interval in seconds when the scheduler will call the event function.
|
||||
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stopped.
|
||||
-- @param #number TraceLevel (Optional) Trace level [0,3]. Default 3.
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stoppe.
|
||||
-- @param #number TraceLevel Trace level [0,3]. Default 3.
|
||||
-- @param Core.Fsm#FSM Fsm Finite state model.
|
||||
-- @return #string The Schedule ID of the planned schedule.
|
||||
function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm )
|
||||
@@ -240,27 +245,28 @@ function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments
|
||||
|
||||
-- Debug info.
|
||||
local ObjectName = "-"
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
ObjectName = MasterObject.ClassName .. MasterObject.ClassID
|
||||
end
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
-- Set master object.
|
||||
self.MasterObject = MasterObject
|
||||
|
||||
|
||||
-- Add schedule.
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules + 1] = ScheduleID
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
|
||||
self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules+1] = ScheduleID
|
||||
|
||||
return ScheduleID
|
||||
end
|
||||
@@ -270,7 +276,7 @@ end
|
||||
-- @param #string ScheduleID (Optional) The Schedule ID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Start( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID)))
|
||||
_SCHEDULEDISPATCHER:Start( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -279,7 +285,7 @@ end
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Stop( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID)))
|
||||
_SCHEDULEDISPATCHER:Stop( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -288,15 +294,15 @@ end
|
||||
-- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Remove( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T( string.format( "Removing scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID)))
|
||||
_SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID )
|
||||
end
|
||||
|
||||
--- Clears all pending schedules.
|
||||
-- @param #SCHEDULER self
|
||||
function SCHEDULER:Clear()
|
||||
self:F3()
|
||||
self:T( string.format( "Clearing scheduler" ) )
|
||||
self:F3( )
|
||||
self:T(string.format("Clearing scheduler"))
|
||||
_SCHEDULEDISPATCHER:Clear( self )
|
||||
end
|
||||
|
||||
|
||||
+2062
-4072
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
--- **Core** - Manages various settings for missions, providing a menu for players to tweak settings in running missions.
|
||||
--- **Core** - Manages various settings for running missions, consumed by moose classes and provides a menu system for players to tweak settings in running missions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -30,15 +30,14 @@
|
||||
-- @image Core_Settings.JPG
|
||||
|
||||
|
||||
---
|
||||
-- @type SETTINGS
|
||||
--- @type SETTINGS
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
--- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- The SETTINGS class takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
-- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
|
||||
-- SETTINGS can work on 2 levels:
|
||||
--
|
||||
-- - **Default settings**: A running mission has **Default settings**.
|
||||
@@ -60,7 +59,7 @@
|
||||
--
|
||||
-- A menu is created automatically per Command Center that allows to modify the **Default** settings.
|
||||
-- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**!
|
||||
-- Note that the **Default settings** will only be used when a player has not chosen its own settings.
|
||||
-- Note that the **Default settings** will only be used when a player has not choosen its own settings.
|
||||
--
|
||||
-- ## 2.2) Player settings menu
|
||||
--
|
||||
@@ -70,7 +69,7 @@
|
||||
--
|
||||
-- ## 2.3) Show or Hide the Player Setting menus
|
||||
--
|
||||
-- Of course, it may be required not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
|
||||
-- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
|
||||
-- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus.
|
||||
-- Note that when this method is used, any player already in a slot will not have its menus visibility changed.
|
||||
-- The option will only have effect when a player enters a new slot or changes a slot.
|
||||
@@ -93,10 +92,10 @@
|
||||
--
|
||||
-- Will customize which display format is used to indicate A2G coordinates in text as part of the Command Center communications.
|
||||
--
|
||||
-- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_\(navigation\)).
|
||||
-- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)).
|
||||
-- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted.
|
||||
-- - A2G LL DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2G LL DDM: Latitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
-- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
--
|
||||
-- ### 3.1.2) A2G coordinates setting **menu**
|
||||
--
|
||||
@@ -107,9 +106,9 @@
|
||||
-- There are different methods that can be used to change the **System settings** using the \_SETTINGS object.
|
||||
--
|
||||
-- - @{#SETTINGS.SetA2G_BR}(): Enable the BR display formatting by default.
|
||||
-- - @{#SETTINGS.SetA2G_MGRS}(): Enable the MGRS display formatting by default. Use @{#SETTINGS.SetMGRS_Accuracy}() to adapt the accuracy of the MGRS formatting.
|
||||
-- - @{#SETTINGS.SetA2G_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{#SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
|
||||
-- - @{#SETTINGS.SetA2G_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{#SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
|
||||
-- - @{#SETTINGS.SetA2G_MGRS}(): Enable the MGRS display formatting by default. Use @{SETTINGS.SetMGRS_Accuracy}() to adapt the accuracy of the MGRS formatting.
|
||||
-- - @{#SETTINGS.SetA2G_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
|
||||
-- - @{#SETTINGS.SetA2G_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
|
||||
--
|
||||
-- ### 3.1.4) A2G coordinates setting - additional notes
|
||||
--
|
||||
@@ -122,7 +121,7 @@
|
||||
--
|
||||
-- Will customize which display format is used to indicate A2A coordinates in text as part of the Command Center communications.
|
||||
--
|
||||
-- - A2A BRAA: [Bearing Range Altitude Aspect](https://en.wikipedia.org/wiki/Bearing_\(navigation\)).
|
||||
-- - A2A BRAA: [Bearing Range Altitude Aspect](https://en.wikipedia.org/wiki/Bearing_(navigation)).
|
||||
-- - A2A MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted.
|
||||
-- - A2A LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2A LL DDM: Lattitude Longitude [Decimal Degrees and Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
@@ -137,9 +136,9 @@
|
||||
-- There are different methods that can be used to change the **System settings** using the \_SETTINGS object.
|
||||
--
|
||||
-- - @{#SETTINGS.SetA2A_BRAA}(): Enable the BR display formatting by default.
|
||||
-- - @{#SETTINGS.SetA2A_MGRS}(): Enable the MGRS display formatting by default. Use @{#SETTINGS.SetMGRS_Accuracy}() to adapt the accuracy of the MGRS formatting.
|
||||
-- - @{#SETTINGS.SetA2A_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{#SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
|
||||
-- - @{#SETTINGS.SetA2A_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{#SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
|
||||
-- - @{#SETTINGS.SetA2A_MGRS}(): Enable the MGRS display formatting by default. Use @{SETTINGS.SetMGRS_Accuracy}() to adapt the accuracy of the MGRS formatting.
|
||||
-- - @{#SETTINGS.SetA2A_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
|
||||
-- - @{#SETTINGS.SetA2A_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
|
||||
-- - @{#SETTINGS.SetA2A_BULLS}(): Enable the BULLSeye display formatting by default.
|
||||
--
|
||||
-- ### 3.2.4) A2A coordinates settings - additional notes
|
||||
@@ -184,7 +183,7 @@
|
||||
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
|
||||
--
|
||||
-- Each Message Type has specific timings that will be applied when the message is displayed.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be chosen.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen.
|
||||
-- So the player can choose its own amount of seconds how long a message should be displayed of a certain type.
|
||||
-- Note that **Update** messages can be chosen not to be displayed at all!
|
||||
--
|
||||
@@ -192,12 +191,12 @@
|
||||
--
|
||||
-- There are different methods that can be used to change the **System settings** using the \_SETTINGS object.
|
||||
--
|
||||
-- - @{#SETTINGS.SetMessageTime}(): Define for a specific @{Core.Message#MESSAGE.MessageType} the duration to be displayed in seconds.
|
||||
-- - @{#SETTINGS.GetMessageTime}(): Retrieves for a specific @{Core.Message#MESSAGE.MessageType} the duration to be displayed in seconds.
|
||||
-- - @{#SETTINGS.SetMessageTime}(): Define for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds.
|
||||
-- - @{#SETTINGS.GetMessageTime}(): Retrieves for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds.
|
||||
--
|
||||
-- ## 3.5) **Era** of the battle
|
||||
--
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greater threat in WWII than on modern warfare.
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare.
|
||||
-- Therefore, there are 4 era that are defined within the settings:
|
||||
--
|
||||
-- - **WWII** era: Use for warfare with equipment during the world war II time.
|
||||
@@ -214,14 +213,13 @@
|
||||
SETTINGS = {
|
||||
ClassName = "SETTINGS",
|
||||
ShowPlayerMenu = true,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
}
|
||||
|
||||
SETTINGS.__Enum = {}
|
||||
|
||||
---
|
||||
-- @type SETTINGS.__Enum.Era
|
||||
--- @type SETTINGS.__Enum.Era
|
||||
-- @field #number WWII
|
||||
-- @field #number Korea
|
||||
-- @field #number Cold
|
||||
@@ -233,6 +231,7 @@ SETTINGS.__Enum.Era = {
|
||||
Modern = 4,
|
||||
}
|
||||
|
||||
|
||||
do -- SETTINGS
|
||||
|
||||
--- SETTINGS constructor.
|
||||
@@ -270,14 +269,14 @@ do -- SETTINGS
|
||||
-- Short text are better suited for, e.g., VR.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default).
|
||||
function SETTINGS:SetMenutextShort( onoff )
|
||||
function SETTINGS:SetMenutextShort(onoff)
|
||||
_SETTINGS.MenuShort = onoff
|
||||
end
|
||||
|
||||
--- Set menu to be static.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default).
|
||||
function SETTINGS:SetMenuStatic( onoff )
|
||||
function SETTINGS:SetMenuStatic(onoff)
|
||||
_SETTINGS.MenuStatic = onoff
|
||||
end
|
||||
|
||||
@@ -286,26 +285,26 @@ do -- SETTINGS
|
||||
function SETTINGS:SetMetric()
|
||||
self.Metric = true
|
||||
end
|
||||
|
||||
|
||||
--- Sets the SETTINGS default text locale.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #string Locale
|
||||
function SETTINGS:SetLocale(Locale)
|
||||
self.Locale = Locale or "en"
|
||||
end
|
||||
|
||||
|
||||
--- Gets the SETTINGS text locale.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #string
|
||||
function SETTINGS:GetLocale()
|
||||
return self.Locale or _SETTINGS:GetLocale()
|
||||
end
|
||||
|
||||
|
||||
--- Gets if the SETTINGS is metric.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if metric.
|
||||
function SETTINGS:IsMetric()
|
||||
return (self.Metric ~= nil and self.Metric == true) or (self.Metric == nil and _SETTINGS:IsMetric())
|
||||
return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS imperial.
|
||||
@@ -318,7 +317,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if imperial.
|
||||
function SETTINGS:IsImperial()
|
||||
return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsImperial())
|
||||
return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS LL accuracy.
|
||||
@@ -338,7 +337,7 @@ do -- SETTINGS
|
||||
|
||||
--- Sets the SETTINGS MGRS accuracy.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #number MGRS_Accuracy 0 to 5
|
||||
-- @param #number MGRS_Accuracy
|
||||
-- @return #SETTINGS
|
||||
function SETTINGS:SetMGRS_Accuracy( MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
@@ -360,12 +359,13 @@ do -- SETTINGS
|
||||
self.MessageTypeTimings[MessageType] = MessageTime
|
||||
end
|
||||
|
||||
|
||||
--- Gets the SETTINGS Message Display Timing of a MessageType
|
||||
-- @param #SETTINGS self
|
||||
-- @param Core.Message#MESSAGE MessageType The type of the message.
|
||||
-- @return #number
|
||||
function SETTINGS:GetMessageTime( MessageType )
|
||||
return (self.MessageTypeTimings and self.MessageTypeTimings[MessageType]) or _SETTINGS:GetMessageTime( MessageType )
|
||||
return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType )
|
||||
end
|
||||
|
||||
--- Sets A2G LL DMS
|
||||
@@ -386,14 +386,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2G_LL_DMS()
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DMS") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS())
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() )
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2G_LL_DDM()
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DDM") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM())
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() )
|
||||
end
|
||||
|
||||
--- Sets A2G MGRS
|
||||
@@ -407,7 +407,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2G_MGRS()
|
||||
return (self.A2GSystem and self.A2GSystem == "MGRS") or (not self.A2GSystem and _SETTINGS:IsA2G_MGRS())
|
||||
return ( self.A2GSystem and self.A2GSystem == "MGRS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_MGRS() )
|
||||
end
|
||||
|
||||
--- Sets A2G BRA
|
||||
@@ -421,7 +421,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2G_BR()
|
||||
return (self.A2GSystem and self.A2GSystem == "BR") or (not self.A2GSystem and _SETTINGS:IsA2G_BR())
|
||||
return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() )
|
||||
end
|
||||
|
||||
--- Sets A2A BRA
|
||||
@@ -435,7 +435,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2A_BRAA()
|
||||
return (self.A2ASystem and self.A2ASystem == "BRAA") or (not self.A2ASystem and _SETTINGS:IsA2A_BRAA())
|
||||
return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() )
|
||||
end
|
||||
|
||||
--- Sets A2A BULLS
|
||||
@@ -449,7 +449,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BULLS
|
||||
function SETTINGS:IsA2A_BULLS()
|
||||
return (self.A2ASystem and self.A2ASystem == "BULLS") or (not self.A2ASystem and _SETTINGS:IsA2A_BULLS())
|
||||
return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() )
|
||||
end
|
||||
|
||||
--- Sets A2A LL DMS
|
||||
@@ -470,14 +470,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2A_LL_DMS()
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DMS") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS())
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() )
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2A_LL_DDM()
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DDM") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM())
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() )
|
||||
end
|
||||
|
||||
--- Sets A2A MGRS
|
||||
@@ -491,7 +491,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2A_MGRS()
|
||||
return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
|
||||
return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
@@ -510,37 +510,37 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
-------
|
||||
|
||||
local text = "A2G Coordinate System"
|
||||
local text="A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G Coordinates"
|
||||
text="A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
-- Set LL DMS
|
||||
if not self:IsA2G_LL_DMS() then
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "LL DMS"
|
||||
text="LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DDM
|
||||
if not self:IsA2G_LL_DDM() then
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "LL DDM"
|
||||
text="LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DMS accuracy.
|
||||
if self:IsA2G_LL_DDM() then
|
||||
local text1 = "LL DDM Accuracy 1"
|
||||
local text2 = "LL DDM Accuracy 2"
|
||||
local text3 = "LL DDM Accuracy 3"
|
||||
local text1="LL DDM Accuracy 1"
|
||||
local text2="LL DDM Accuracy 2"
|
||||
local text3="LL DDM Accuracy 3"
|
||||
if _SETTINGS.MenuShort then
|
||||
text1 = "LL DDM"
|
||||
text1="LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
|
||||
@@ -549,18 +549,18 @@ do -- SETTINGS
|
||||
|
||||
-- Set BR.
|
||||
if not self:IsA2G_BR() then
|
||||
local text = "Bearing, Range (BR)"
|
||||
local text="Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "BR"
|
||||
text="BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set MGRS.
|
||||
if not self:IsA2G_MGRS() then
|
||||
local text = "Military Grid (MGRS)"
|
||||
local text="Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "MGRS"
|
||||
text="MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -578,24 +578,24 @@ do -- SETTINGS
|
||||
-- A2A Coordinate System
|
||||
-------
|
||||
|
||||
local text = "A2A Coordinate System"
|
||||
local text="A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A Coordinates"
|
||||
text="A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if not self:IsA2A_LL_DMS() then
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "LL DMS"
|
||||
text="LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() then
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "LL DDM"
|
||||
text="LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -608,25 +608,25 @@ do -- SETTINGS
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() then
|
||||
local text = "Bullseye (BULLS)"
|
||||
local text="Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Bulls"
|
||||
text="Bulls"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() then
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "BRAA"
|
||||
text="BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() then
|
||||
local text = "Military Grid (MGRS)"
|
||||
local text="Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "MGRS"
|
||||
text="MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -639,31 +639,31 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text = "Measures and Weights System"
|
||||
local text="Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Unit System"
|
||||
text="Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if self:IsMetric() then
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
local text="Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Imperial"
|
||||
text="Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if self:IsImperial() then
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Metric"
|
||||
text="Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text = "Messages and Reports"
|
||||
local text="Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Messages & Reports"
|
||||
text="Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
@@ -704,6 +704,7 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
|
||||
|
||||
|
||||
SettingsMenu:Remove( MenuTime )
|
||||
|
||||
return self
|
||||
@@ -740,18 +741,18 @@ do -- SETTINGS
|
||||
if _SETTINGS.ShowPlayerMenu == true then
|
||||
|
||||
local PlayerGroup = PlayerUnit:GetGroup()
|
||||
local PlayerName = PlayerUnit:GetPlayerName() or "None"
|
||||
--local PlayerNames = PlayerGroup:GetPlayerNames()
|
||||
local PlayerName = PlayerUnit:GetPlayerName()
|
||||
local PlayerNames = PlayerGroup:GetPlayerNames()
|
||||
|
||||
local PlayerMenu = MENU_GROUP:New( PlayerGroup, 'Settings "' .. PlayerName .. '"' )
|
||||
|
||||
self.PlayerMenu = PlayerMenu
|
||||
|
||||
self:T( string.format( "Setting menu for player %s", tostring( PlayerName ) ) )
|
||||
self:I(string.format("Setting menu for player %s", tostring(PlayerName)))
|
||||
|
||||
local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 0 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 2 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 3 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 4 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
|
||||
@@ -768,40 +769,40 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
------
|
||||
|
||||
local text = "A2G Coordinate System"
|
||||
local text="A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G Coordinates"
|
||||
text="A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if not self:IsA2G_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G LL DMS"
|
||||
text="A2G LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G LL DDM"
|
||||
text="A2G LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_BR() or _SETTINGS.MenuStatic then
|
||||
local text = "Bearing, Range (BR)"
|
||||
local text="Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G BR"
|
||||
text="A2G BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text = "Military Grid (MGRS)"
|
||||
local text="Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G MGRS"
|
||||
text="A2G MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -810,48 +811,49 @@ do -- SETTINGS
|
||||
-- A2A Coordinates Menu
|
||||
------
|
||||
|
||||
local text = "A2A Coordinate System"
|
||||
local text="A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A Coordinates"
|
||||
text="A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
|
||||
if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A LL DMS"
|
||||
text="A2A LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A LL DDM"
|
||||
text="A2A LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then
|
||||
local text = "Bullseye (BULLS)"
|
||||
local text="Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A BULLS"
|
||||
text="A2A BULLS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() or _SETTINGS.MenuStatic then
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A BRAA"
|
||||
text="A2A BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text = "Military Grid (MGRS)"
|
||||
local text="Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A MGRS"
|
||||
text="A2A MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -860,24 +862,24 @@ do -- SETTINGS
|
||||
-- Unit system
|
||||
---
|
||||
|
||||
local text = "Measures and Weights System"
|
||||
local text="Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Unit System"
|
||||
text="Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if self:IsMetric() or _SETTINGS.MenuStatic then
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
local text="Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Imperial"
|
||||
text="Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
|
||||
end
|
||||
|
||||
if self:IsImperial() or _SETTINGS.MenuStatic then
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Metric"
|
||||
text="Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
|
||||
end
|
||||
@@ -886,9 +888,9 @@ do -- SETTINGS
|
||||
-- Messages and Reports
|
||||
---
|
||||
|
||||
local text = "Messages and Reports"
|
||||
local text="Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Messages & Reports"
|
||||
text="Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
@@ -948,38 +950,39 @@ do -- SETTINGS
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
@@ -992,12 +995,12 @@ do -- SETTINGS
|
||||
do
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
|
||||
--BASE:E( {PlayerUnit:GetName(), A2GSystem } )
|
||||
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1005,9 +1008,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1015,9 +1018,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1025,9 +1028,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1035,9 +1038,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1067,6 +1070,7 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Cold war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1076,6 +1080,7 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Modern war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1085,4 +1090,7 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
+1658
-2441
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,36 @@
|
||||
--- **Core** - Spawn statics.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Spawn new statics from a static already defined in the mission editor.
|
||||
-- * Spawn new statics from a given template.
|
||||
-- * Spawn new statics from a given type.
|
||||
-- * Spawn with a custom heading and location.
|
||||
-- * Spawn within a zone.
|
||||
-- * Spawn statics linked to units, .e.g on aircraft carriers.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPS%20-%20Spawning%20Statics)
|
||||
--
|
||||
-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/SpawnStatic)
|
||||
--
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
-- ## No videos yet!
|
||||
--
|
||||
--
|
||||
-- ## [SPAWNSTATIC YouTube Channel]() [No videos yet!]
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Core.SpawnStatic
|
||||
-- @image Core_Spawnstatic.JPG
|
||||
|
||||
@@ -57,38 +57,38 @@
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Allows to spawn dynamically new @{Wrapper.Static}s into your mission.
|
||||
--
|
||||
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc),
|
||||
--- Allows to spawn dynamically new @{Static}s into your mission.
|
||||
--
|
||||
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc),
|
||||
-- and "copy" these properties to create a new static object and place it at the desired coordinate.
|
||||
--
|
||||
-- New spawned @{Wrapper.Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
|
||||
-- By default, spawned @{Wrapper.Static}s will follow a naming convention at run-time:
|
||||
--
|
||||
-- * Spawned @{Wrapper.Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**, and _nnn_ is a **counter from 0 to 99999**.
|
||||
--
|
||||
--
|
||||
-- New spawned @{Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
|
||||
-- By default, spawned @{Static}s will follow a naming convention at run-time:
|
||||
--
|
||||
-- * Spawned @{Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**, and _nnn_ is a **counter from 0 to 99999**.
|
||||
--
|
||||
-- # SPAWNSTATIC Constructors
|
||||
--
|
||||
--
|
||||
-- Firstly, we need to create a SPAWNSTATIC object that will be used to spawn new statics into the mission. There are three ways to do this.
|
||||
--
|
||||
--
|
||||
-- ## Use another Static
|
||||
--
|
||||
--
|
||||
-- A new SPAWNSTATIC object can be created using another static by the @{#SPAWNSTATIC.NewFromStatic}() function. All parameters such as position, heading, country will be initialized
|
||||
-- from the static.
|
||||
--
|
||||
--
|
||||
-- ## From a Template
|
||||
--
|
||||
--
|
||||
-- A SPAWNSTATIC object can also be created from a template table using the @{#SPAWNSTATIC.NewFromTemplate}(SpawnTemplate, CountryID) function. All parameters are taken from the template.
|
||||
--
|
||||
--
|
||||
-- ## From a Type
|
||||
--
|
||||
--
|
||||
-- A very basic method is to create a SPAWNSTATIC object by just giving the type of the static. All parameters must be initialized from the InitXYZ functions described below. Otherwise default values
|
||||
-- are used. For example, if no spawn coordinate is given, the static will be created at the origin of the map.
|
||||
--
|
||||
--
|
||||
-- # Setting Parameters
|
||||
--
|
||||
--
|
||||
-- Parameters such as the spawn position, heading, country etc. can be set via :Init*XYZ* functions. Note that these functions must be given before the actual spawn command!
|
||||
--
|
||||
--
|
||||
-- * @{#SPAWNSTATIC.InitCoordinate}(Coordinate) Sets the coordinate where the static is spawned. Statics are always spawnd on the ground.
|
||||
-- * @{#SPAWNSTATIC.InitHeading}(Heading) sets the orientation of the static.
|
||||
-- * @{#SPAWNSTATIC.InitLivery}(LiveryName) sets the livery of the static. Not all statics support this.
|
||||
@@ -99,17 +99,17 @@
|
||||
-- * @{#SPAWNSTATIC.InitLinkToUnit}(Unit, OffsetX, OffsetY, OffsetAngle) links the static to a unit, e.g. to an aircraft carrier.
|
||||
--
|
||||
-- # Spawning the Statics
|
||||
--
|
||||
--
|
||||
-- Once the SPAWNSTATIC object is created and parameters are initialized, the spawn command can be given. There are different methods where some can be used to directly set parameters
|
||||
-- such as position and heading.
|
||||
--
|
||||
--
|
||||
-- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a COORDINATE coordinate. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Core.Zone}. Optionally, heading and name can be given. The name **must be unique**!
|
||||
--
|
||||
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Zone}. Optionally, heading and name can be given. The name **must be unique**!
|
||||
--
|
||||
-- @field #SPAWNSTATIC SPAWNSTATIC
|
||||
--
|
||||
--
|
||||
SPAWNSTATIC = {
|
||||
ClassName = "SPAWNSTATIC",
|
||||
SpawnIndex = 0,
|
||||
@@ -131,7 +131,7 @@ SPAWNSTATIC = {
|
||||
-- @field #number mass Cargo mass in kg.
|
||||
-- @field #boolean canCargo Static can be a cargo.
|
||||
|
||||
--- Creates the main object to spawn a @{Wrapper.Static} defined in the mission editor (ME).
|
||||
--- Creates the main object to spawn a @{Static} defined in the mission editor (ME).
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string SpawnTemplateName Name of the static object in the ME. Each new static will have the name starting with this prefix.
|
||||
-- @param DCS#country.id SpawnCountryID (Optional) The ID of the country.
|
||||
@@ -139,9 +139,9 @@ SPAWNSTATIC = {
|
||||
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
|
||||
|
||||
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
|
||||
|
||||
|
||||
if TemplateStatic then
|
||||
self.SpawnTemplatePrefix = SpawnTemplateName
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
|
||||
@@ -149,7 +149,6 @@ function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
|
||||
self.CategoryID = CategoryID
|
||||
self.CoalitionID = CoalitionID
|
||||
self.SpawnIndex = 0
|
||||
self.StaticCopyFrom = SpawnTemplateName
|
||||
else
|
||||
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
|
||||
end
|
||||
@@ -159,28 +158,28 @@ function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates the main object to spawn a @{Wrapper.Static} given a template table.
|
||||
--- Creates the main object to spawn a @{Static} given a template table.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #table SpawnTemplate Template used for spawning.
|
||||
-- @param DCS#country.id CountryID (Optional) The ID of the country. Default `country.id.USA`.
|
||||
-- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID)
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
|
||||
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate)
|
||||
self.SpawnTemplatePrefix = SpawnTemplate.name
|
||||
self.CountryID = CountryID or country.id.USA
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates the main object to spawn a @{Wrapper.Static} from a given type.
|
||||
--- Creates the main object to spawn a @{Static} from a given type.
|
||||
-- NOTE that you have to init many other parameters as spawn coordinate etc.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string StaticType Type of the static.
|
||||
-- @param #string StaticCategory Category of the static, e.g. "Planes".
|
||||
-- @param DCS#country.id CountryID (Optional) The ID of the country. Default `country.id.USA`.
|
||||
-- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID)
|
||||
|
||||
@@ -190,69 +189,13 @@ function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID)
|
||||
self.InitStaticCategory=StaticCategory
|
||||
self.CountryID=CountryID or country.id.USA
|
||||
self.SpawnTemplatePrefix=self.InitStaticType
|
||||
self.TemplateStaticUnit = {}
|
||||
|
||||
|
||||
self.InitStaticCoordinate=COORDINATE:New(0, 0, 0)
|
||||
self.InitStaticHeading=0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal/Cargo) Init the resource table for STATIC object that should be spawned containing storage objects.
|
||||
-- NOTE that you have to init many other parameters as the resources.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number CombinedWeight (Optional) The weight this cargo object should have (some have fixed weights!), defaults to 1kg.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:_InitResourceTable(CombinedWeight)
|
||||
if not self.TemplateStaticUnit.resourcePayload then
|
||||
self.TemplateStaticUnit.resourcePayload = {
|
||||
["weapons"] = {},
|
||||
["aircrafts"] = {},
|
||||
["gasoline"] = 0,
|
||||
["diesel"] = 0,
|
||||
["methanol_mixture"] = 0,
|
||||
["jet_fuel"] = 0,
|
||||
}
|
||||
end
|
||||
self:InitCargo(true)
|
||||
self:InitCargoMass(CombinedWeight or 1)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User/Cargo) Add to resource table for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string Type Type of cargo. Known types are: STORAGE.Type.WEAPONS, STORAGE.Type.LIQUIDS, STORAGE.Type.AIRCRAFT. Liquids are fuel.
|
||||
-- @param #string Name Name of the cargo type. Liquids can be STORAGE.LiquidName.JETFUEL, STORAGE.LiquidName.GASOLINE, STORAGE.LiquidName.MW50 and STORAGE.LiquidName.DIESEL. The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`. Aircraft go by their typename.
|
||||
-- @param #number Amount of tons (liquids) or number (everything else) to add.
|
||||
-- @param #number CombinedWeight Combined weight to be set to this static cargo object. NOTE - some static cargo objects have fixed weights!
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:AddCargoResource(Type,Name,Amount,CombinedWeight)
|
||||
if not self.TemplateStaticUnit.resourcePayload then
|
||||
self:_InitResourceTable(CombinedWeight)
|
||||
end
|
||||
if Type == STORAGE.Type.LIQUIDS and type(Name) == "string" then
|
||||
self.TemplateStaticUnit.resourcePayload[Name] = Amount
|
||||
else
|
||||
self.TemplateStaticUnit.resourcePayload[Type] = {
|
||||
[Name] = {
|
||||
["amount"] = Amount,
|
||||
}
|
||||
}
|
||||
end
|
||||
UTILS.PrintTableToLog(self.TemplateStaticUnit)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User/Cargo) Resets resource table to zero for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters.
|
||||
-- Handy if you spawn from cargo statics which have resources already set.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:ResetCargoResources()
|
||||
self.TemplateStaticUnit.resourcePayload = nil
|
||||
self:_InitResourceTable()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize heading of the spawned static.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Point#COORDINATE Coordinate Position where the static is spawned.
|
||||
@@ -300,19 +243,15 @@ end
|
||||
|
||||
--- Initialize parameters for spawning FARPs.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number CallsignID (Optional) Callsign ID. Default 1 (="London").
|
||||
-- @param #number Frequency (Optional) Frequency in MHz. Default 127.5 MHz.
|
||||
-- @param #number Modulation (Optional) Modulation 0=AM, 1=FM. Defaults to 0
|
||||
-- @param #boolean DynamicSpawns If true, allow Dynamic Spawns
|
||||
-- @param #boolean DynamicHotStarts If true, and DynamicSpawns is true, then allow Dynamic Spawns with hot starts.
|
||||
-- @param #number CallsignID Callsign ID. Default 1 (="London").
|
||||
-- @param #number Frequency Frequency in MHz. Default 127.5 MHz.
|
||||
-- @param #number Modulation Modulation 0=AM, 1=FM.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation, DynamicSpawns,DynamicHotStarts)
|
||||
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation)
|
||||
self.InitFarp=true
|
||||
self.InitFarpCallsignID=CallsignID or 1
|
||||
self.InitFarpFreq=Frequency or 127.5
|
||||
self.InitFarpModu=Modulation or 0
|
||||
self.InitFarpDynamicSpawns = DynamicSpawns
|
||||
self.InitFarpDynamicHotStarts = (DynamicSpawns == true and DynamicHotStarts == true) and true or nil
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -334,34 +273,9 @@ function SPAWNSTATIC:InitCargo(IsCargo)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hide the static on the map view (visible to game master slots!).
|
||||
-- @param #SPAWN self
|
||||
-- @param #boolean OnOff Defaults to true
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWNSTATIC:InitHiddenOnMap(OnOff)
|
||||
self.SpawnHiddenOnMap = OnOff == false and false or true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hide the static on MFDs (visible to game master slots!).
|
||||
-- @param #SPAWN self
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWNSTATIC:InitHiddenOnMFD()
|
||||
self.SpawnHiddenOnMFD = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hide the static on planner (visible to game master slots!).
|
||||
-- @param #SPAWN self
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWNSTATIC:InitHiddenOnPlanner()
|
||||
self.SpawnHiddenOnPlanner = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize as dead.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #boolean IsDead If true, this static is dead.
|
||||
-- @param #boolean IsCargo If true, this static is dead.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitDead(IsDead)
|
||||
self.InitStaticDead=IsDead
|
||||
@@ -377,7 +291,7 @@ function SPAWNSTATIC:InitCountry(CountryID)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc.
|
||||
--- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name.
|
||||
-- @return #SPAWNSTATIC self
|
||||
@@ -403,39 +317,6 @@ function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Uses Disposition and other fallback logic to find a better and valid ground spawn position.
|
||||
--- NOTE: This is not a spawn randomizer.
|
||||
--- It will try to a find clear ground location avoiding trees, water, roads, runways, map scenery, other statics and other units in the area.
|
||||
--- Uses the initial position if it's a valid location.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #boolean OnOff Enable/disable the feature.
|
||||
-- @param #number MaxRadius (Optional) Max radius to search for a valid ground location in meters. Default is 10 times the max radius of the static.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff, MaxRadius)
|
||||
self.ValidateAndRepositionStatic = OnOff
|
||||
self.ValidateAndRepositionStaticMaxRadius = MaxRadius
|
||||
return self
|
||||
end
|
||||
|
||||
--- Allows to place a CallFunction hook when a new static spawns.
|
||||
-- The provided method will be called when a new group is spawned, including its given parameters.
|
||||
-- The first parameter of the SpawnFunction is the @{Wrapper.Static#STATIC} that was spawned.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #function SpawnCallBackFunction The function to be called when a group spawns.
|
||||
-- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:OnSpawnStatic( SpawnCallBackFunction, ... )
|
||||
self:F( "OnSpawnStatic" )
|
||||
|
||||
self.SpawnFunctionHook = SpawnCallBackFunction
|
||||
self.SpawnFunctionArguments = {}
|
||||
if arg then
|
||||
self.SpawnFunctionArguments = arg
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Spawn a new STATIC object.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number Heading (Optional) The heading of the static, which is a number in degrees from 0 to 360. Default is the heading of the template.
|
||||
@@ -446,18 +327,18 @@ function SPAWNSTATIC:Spawn(Heading, NewName)
|
||||
if Heading then
|
||||
self.InitStaticHeading=Heading
|
||||
end
|
||||
|
||||
|
||||
if NewName then
|
||||
self.InitStaticName=NewName
|
||||
end
|
||||
|
||||
return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID)
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Creates a new @{Wrapper.Static} from a COORDINATE.
|
||||
--- Creates a new @{Static} from a POINT_VEC2.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Point#COORDINATE PointVec2 The 2D coordinate where to spawn the static.
|
||||
-- @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 NewName (Optional) The name of the new static.
|
||||
-- @return Wrapper.Static#STATIC The static spawned.
|
||||
@@ -466,12 +347,12 @@ function SPAWNSTATIC:SpawnFromPointVec2(PointVec2, Heading, NewName)
|
||||
local vec2={x=PointVec2:GetX(), y=PointVec2:GetY()}
|
||||
|
||||
local Coordinate=COORDINATE:NewFromVec2(vec2)
|
||||
|
||||
|
||||
return self:SpawnFromCoordinate(Coordinate, Heading, NewName)
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new @{Wrapper.Static} from a COORDINATE.
|
||||
--- Creates a new @{Static} from a COORDINATE.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Point#COORDINATE Coordinate The 3D coordinate where to spawn the static.
|
||||
-- @param #number Heading (Optional) Heading The heading of the static in degrees. Default is 0 degrees.
|
||||
@@ -481,11 +362,11 @@ function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName)
|
||||
|
||||
-- Set up coordinate.
|
||||
self.InitStaticCoordinate=Coordinate
|
||||
|
||||
|
||||
if Heading then
|
||||
self.InitStaticHeading=Heading
|
||||
end
|
||||
|
||||
|
||||
if NewName then
|
||||
self.InitStaticName=NewName
|
||||
end
|
||||
@@ -494,7 +375,7 @@ function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName)
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new @{Wrapper.Static} from a @{Core.Zone}.
|
||||
--- Creates a new @{Static} from a @{Zone}.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static.
|
||||
-- @param #number Heading (Optional)The heading of the static in degrees. Default is the heading of the template.
|
||||
@@ -503,9 +384,8 @@ end
|
||||
function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName)
|
||||
|
||||
-- Spawn the new static at the center of the zone.
|
||||
--local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
|
||||
local Static = self:SpawnFromCoordinate(Zone:GetCoordinate(), Heading, NewName)
|
||||
|
||||
local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
|
||||
|
||||
return Static
|
||||
end
|
||||
|
||||
@@ -519,58 +399,45 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
Template=Template or {}
|
||||
|
||||
local CountryID=CountryID or self.CountryID
|
||||
|
||||
|
||||
if self.InitStaticType then
|
||||
Template.type=self.InitStaticType
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticCategory then
|
||||
Template.category=self.InitStaticCategory
|
||||
end
|
||||
|
||||
if self.InitStaticCoordinate then
|
||||
Template.x = self.InitStaticCoordinate.x
|
||||
|
||||
if self.InitStaticCoordinate then
|
||||
Template.x = self.InitStaticCoordinate.x
|
||||
Template.y = self.InitStaticCoordinate.z
|
||||
Template.alt = self.InitStaticCoordinate.y
|
||||
Template.alt = self.InitStaticCoordinate.y
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticHeading then
|
||||
Template.heading = math.rad(self.InitStaticHeading)
|
||||
Template.heading = math.rad(self.InitStaticHeading)
|
||||
end
|
||||
|
||||
if self.InitStaticShape then
|
||||
Template.shape_name=self.InitStaticShape
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticLivery then
|
||||
Template.livery_id=self.InitStaticLivery
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticDead~=nil then
|
||||
Template.dead=self.InitStaticDead
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticCargo~=nil then
|
||||
Template.canCargo=self.InitStaticCargo
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticCargoMass~=nil then
|
||||
Template.mass=self.InitStaticCargoMass
|
||||
end
|
||||
|
||||
-- hiding options
|
||||
if self.SpawnHiddenOnPlanner then
|
||||
Template.hiddenOnPlanner=true
|
||||
end
|
||||
|
||||
if self.SpawnHiddenOnMFD then
|
||||
Template.hiddenOnMFD=true
|
||||
end
|
||||
|
||||
if self.SpawnHiddenOnMap then
|
||||
Template.hidden=self.SpawnHiddenOnMap
|
||||
end
|
||||
|
||||
|
||||
if self.InitLinkUnit then
|
||||
Template.linkUnit=self.InitLinkUnit:GetID()
|
||||
Template.linkOffset=true
|
||||
@@ -579,107 +446,52 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
Template.offsets.x=self.InitOffsetX
|
||||
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0
|
||||
end
|
||||
|
||||
|
||||
if self.InitFarp then
|
||||
Template.heliport_callsign_id = self.InitFarpCallsignID
|
||||
Template.heliport_frequency = self.InitFarpFreq
|
||||
Template.heliport_modulation = self.InitFarpModu
|
||||
Template.unitId=nil
|
||||
end
|
||||
|
||||
|
||||
-- Increase spawn index counter.
|
||||
self.SpawnIndex = self.SpawnIndex + 1
|
||||
|
||||
|
||||
-- Name of the spawned static.
|
||||
Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex)
|
||||
|
||||
-- Add and register the new static.
|
||||
local mystatic=_DATABASE:AddStatic(Template.name)
|
||||
|
||||
-- Debug output.
|
||||
self:T(Template)
|
||||
|
||||
-- Add static to the game.
|
||||
local Static=nil --DCS#StaticObject
|
||||
|
||||
if self.ValidateAndRepositionStatic then
|
||||
local validPos = UTILS.ValidateAndRepositionStatic(CountryID, Template.category, Template.type, Template, Template.shape_name, self.ValidateAndRepositionStaticMaxRadius)
|
||||
if validPos then
|
||||
Template.x = validPos.x
|
||||
Template.y = validPos.y
|
||||
end
|
||||
end
|
||||
|
||||
local Static=nil
|
||||
|
||||
if self.InitFarp then
|
||||
|
||||
local TemplateGroup={}
|
||||
|
||||
local TemplateGroup={}
|
||||
TemplateGroup.units={}
|
||||
TemplateGroup.units[1]=Template
|
||||
|
||||
|
||||
TemplateGroup.visible=true
|
||||
TemplateGroup.hidden=false
|
||||
TemplateGroup.x=Template.x
|
||||
TemplateGroup.y=Template.y
|
||||
TemplateGroup.name=Template.name
|
||||
|
||||
if self.InitFarpDynamicSpawns == true then
|
||||
TemplateGroup.units[1].dynamicSpawn = true
|
||||
if self.InitFarpDynamicHotStarts == true then
|
||||
TemplateGroup.units[1].allowHotStart = true
|
||||
end
|
||||
end
|
||||
|
||||
self:T("Spawning FARP")
|
||||
self:T("Spawning FARP")
|
||||
self:T({Template=Template})
|
||||
self:T({TemplateGroup=TemplateGroup})
|
||||
|
||||
|
||||
-- ED's dirty way to spawn FARPS.
|
||||
Static=coalition.addGroup(CountryID, -1, TemplateGroup)
|
||||
--Static=coalition.addStaticObject(CountryID, Template)
|
||||
|
||||
-- Currently DCS 2.8 does not trigger birth events if FARPS are spawned!
|
||||
-- We create such an event. The airbase is registered in Core.Event
|
||||
local Event = {
|
||||
id = EVENTS.Birth,
|
||||
time = timer.getTime(),
|
||||
initiator = Static
|
||||
}
|
||||
-- Create BIRTH event.
|
||||
world.onEvent(Event)
|
||||
|
||||
else
|
||||
self:T("Spawning Static")
|
||||
self:T2({Template=Template})
|
||||
self:T("Spawning Static")
|
||||
self:T2({Template=Template})
|
||||
Static=coalition.addStaticObject(CountryID, Template)
|
||||
end
|
||||
|
||||
if Static then
|
||||
self:T(string.format("Succesfully spawned static object \"%s\" ID=%d", Static:getName(), Static:getID()))
|
||||
--[[
|
||||
local static=StaticObject.getByName(Static:getName())
|
||||
if static then
|
||||
env.info(string.format("FF got static from StaticObject.getByName"))
|
||||
else
|
||||
env.error(string.format("FF error did NOT get static from StaticObject.getByName"))
|
||||
end ]]
|
||||
else
|
||||
self:E(string.format("ERROR: DCS static object \"%s\" is nil!", tostring(Template.name)))
|
||||
end
|
||||
end
|
||||
|
||||
-- Add and register the new static.
|
||||
local mystatic=_DATABASE:AddStatic(Template.name)
|
||||
|
||||
-- If there is a SpawnFunction hook defined, call it.
|
||||
if self.SpawnFunctionHook then
|
||||
-- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group.
|
||||
self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments))
|
||||
end
|
||||
|
||||
if self.StaticCopyFrom ~= nil then
|
||||
mystatic.StaticCopyFrom = self.StaticCopyFrom
|
||||
end
|
||||
|
||||
local TemplateGroup={}
|
||||
TemplateGroup.units={}
|
||||
TemplateGroup.units[1]=Template
|
||||
TemplateGroup.x=Template.x
|
||||
TemplateGroup.y=Template.y
|
||||
TemplateGroup.name=Template.name
|
||||
_DATABASE:_RegisterStaticTemplate( TemplateGroup, self.CoalitionID, self.CategoryID, CountryID )
|
||||
|
||||
return mystatic
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
-- SPOT implements the DCS Spot class functionality, but adds additional luxury to be able to:
|
||||
--
|
||||
-- * Spot for a defined duration.
|
||||
-- * Updates of laser spot position every 0.2 seconds for moving targets.
|
||||
-- * Updates of laer spot position every 0.2 seconds for moving targets.
|
||||
-- * Wiggle the spot at the target.
|
||||
-- * Provide a @{Wrapper.Unit} as a target, instead of a point.
|
||||
-- * Implement a status machine, LaseOn, LaseOff.
|
||||
@@ -13,17 +13,27 @@
|
||||
-- ===
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ### [SPOT Demo Missions source code]()
|
||||
--
|
||||
-- ### [SPOT Demo Missions, only for beta testers]()
|
||||
--
|
||||
-- ### [Demo Missions on GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS)
|
||||
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
-- ### [SPOT YouTube Channel]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Ciribob**: Showing the way how to lase targets + how laser codes work!!! Explained the autolase script.
|
||||
-- * **EasyEB**: Ideas and Beta Testing
|
||||
-- * **Wingthor**: Beta Testing
|
||||
-- * [**Ciribob**](https://forums.eagle.ru/member.php?u=112175): Showing the way how to lase targets + how laser codes work!!! Explained the autolase script.
|
||||
-- * [**EasyEB**](https://forums.eagle.ru/member.php?u=112055): Ideas and Beta Testing
|
||||
-- * [**Wingthor**](https://forums.eagle.ru/member.php?u=123698): Beta Testing
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -33,22 +43,21 @@
|
||||
|
||||
do
|
||||
|
||||
---
|
||||
-- @type SPOT
|
||||
--- @type SPOT
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Implements the target spotting or marking functionality, but adds additional luxury to be able to:
|
||||
--
|
||||
-- * Mark targets for a defined duration.
|
||||
-- * Updates of laser spot position every 0.25 seconds for moving targets.
|
||||
-- * Updates of laer spot position every 0.2 seconds for moving targets.
|
||||
-- * Wiggle the spot at the target.
|
||||
-- * Provide a @{Wrapper.Unit} as a target, instead of a point.
|
||||
-- * Implement a status machine, LaseOn, LaseOff.
|
||||
--
|
||||
-- ## 1. SPOT constructor
|
||||
--
|
||||
-- * @{#SPOT.New}(): Creates a new SPOT object.
|
||||
-- * @{#SPOT.New}(..\Presentations\SPOT\Dia2.JPG): Creates a new SPOT object.
|
||||
--
|
||||
-- ## 2. SPOT is a FSM
|
||||
--
|
||||
@@ -208,8 +217,6 @@ do
|
||||
|
||||
|
||||
self.Recce = Recce
|
||||
|
||||
self.RecceName = self.Recce:GetName()
|
||||
|
||||
self.LaseScheduler = SCHEDULER:New( self )
|
||||
|
||||
@@ -229,27 +236,21 @@ do
|
||||
-- @param #number LaserCode Laser code.
|
||||
-- @param #number Duration Duration of lasing in seconds.
|
||||
function SPOT:onafterLaseOn( From, Event, To, Target, LaserCode, Duration )
|
||||
self:T({From, Event, To})
|
||||
self:T2( { "LaseOn", Target, LaserCode, Duration } )
|
||||
self:F( { "LaseOn", Target, LaserCode, Duration } )
|
||||
|
||||
local function StopLase( self )
|
||||
self:LaseOff()
|
||||
end
|
||||
|
||||
self.Target = Target
|
||||
|
||||
self.TargetName = Target:GetName()
|
||||
|
||||
self.LaserCode = LaserCode
|
||||
|
||||
self.Lasing = true
|
||||
|
||||
local RecceDcsUnit = self.Recce:GetDCSObject()
|
||||
|
||||
local relativespot = self.relstartpos or { x = 0, y = 2, z = 0 }
|
||||
|
||||
self.SpotIR = Spot.createInfraRed( RecceDcsUnit, relativespot, Target:GetPointVec3():AddY(1):GetVec3() )
|
||||
self.SpotLaser = Spot.createLaser( RecceDcsUnit, relativespot, Target:GetPointVec3():AddY(1):GetVec3(), LaserCode )
|
||||
self.SpotIR = Spot.createInfraRed( RecceDcsUnit, { x = 0, y = 2, z = 0 }, Target:GetPointVec3():AddY(1):GetVec3() )
|
||||
self.SpotLaser = Spot.createLaser( RecceDcsUnit, { x = 0, y = 2, z = 0 }, Target:GetPointVec3():AddY(1):GetVec3(), LaserCode )
|
||||
|
||||
if Duration then
|
||||
self.ScheduleID = self.LaseScheduler:Schedule( self, StopLase, {self}, Duration )
|
||||
@@ -258,8 +259,6 @@ do
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
|
||||
self:__Lasing( -1 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -272,7 +271,7 @@ do
|
||||
-- @param #number LaserCode Laser code.
|
||||
-- @param #number Duration Duration of lasing in seconds.
|
||||
function SPOT:onafterLaseOnCoordinate(From, Event, To, Coordinate, LaserCode, Duration)
|
||||
self:T2( { "LaseOnCoordinate", Coordinate, LaserCode, Duration } )
|
||||
self:F( { "LaseOnCoordinate", Coordinate, LaserCode, Duration } )
|
||||
|
||||
local function StopLase( self )
|
||||
self:LaseOff()
|
||||
@@ -294,72 +293,55 @@ do
|
||||
end
|
||||
|
||||
self:__Lasing(-1)
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #SPOT self
|
||||
|
||||
--- @param #SPOT self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SPOT:OnEventDead(EventData)
|
||||
self:T2( { Dead = EventData.IniDCSUnitName, Target = self.Target } )
|
||||
self:F( { Dead = EventData.IniDCSUnitName, Target = self.Target } )
|
||||
if self.Target then
|
||||
if EventData.IniDCSUnitName == self.TargetName then
|
||||
self:F( {"Target dead ", self.TargetName } )
|
||||
if EventData.IniDCSUnitName == self.Target:GetName() then
|
||||
self:F( {"Target dead ", self.Target:GetName() } )
|
||||
self:Destroyed()
|
||||
self:LaseOff()
|
||||
end
|
||||
end
|
||||
if self.Recce then
|
||||
if EventData.IniDCSUnitName == self.RecceName then
|
||||
self:F( {"Recce dead ", self.RecceName } )
|
||||
self:LaseOff()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #SPOT self
|
||||
--- @param #SPOT self
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
function SPOT:onafterLasing( From, Event, To )
|
||||
self:T({From, Event, To})
|
||||
|
||||
if self.Lasing then
|
||||
if self.Target and self.Target:IsAlive() then
|
||||
|
||||
self.SpotIR:setPoint( self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/200):AddX(math.random(-100,100)/200):GetVec3() )
|
||||
self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() )
|
||||
|
||||
self:__Lasing(0.2)
|
||||
elseif self.TargetCoord then
|
||||
|
||||
-- Wiggle the IR spot a bit.
|
||||
local irvec3={x=self.TargetCoord.x+math.random(-100,100)/200, y=self.TargetCoord.y+math.random(-100,100)/200, z=self.TargetCoord.z} --#DCS.Vec3
|
||||
local lsvec3={x=self.TargetCoord.x, y=self.TargetCoord.y, z=self.TargetCoord.z} --#DCS.Vec3
|
||||
|
||||
self.SpotIR:setPoint(irvec3)
|
||||
self.SpotLaser:setPoint(lsvec3)
|
||||
|
||||
self:__Lasing(0.2)
|
||||
else
|
||||
self:F( { "Target is not alive", self.Target:IsAlive() } )
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #SPOT self
|
||||
if self.Target and self.Target:IsAlive() then
|
||||
self.SpotIR:setPoint( self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3() )
|
||||
self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() )
|
||||
self:__Lasing( -0.2 )
|
||||
elseif self.TargetCoord then
|
||||
|
||||
-- Wiggle the IR spot a bit.
|
||||
local irvec3={x=self.TargetCoord.x+math.random(-100,100)/100, y=self.TargetCoord.y+math.random(-100,100)/100, z=self.TargetCoord.z} --#DCS.Vec3
|
||||
local lsvec3={x=self.TargetCoord.x, y=self.TargetCoord.y, z=self.TargetCoord.z} --#DCS.Vec3
|
||||
|
||||
self.SpotIR:setPoint(irvec3)
|
||||
self.SpotLaser:setPoint(lsvec3)
|
||||
|
||||
self:__Lasing(-0.25)
|
||||
else
|
||||
self:F( { "Target is not alive", self.Target:IsAlive() } )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #SPOT self
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @return #SPOT
|
||||
function SPOT:onafterLaseOff( From, Event, To )
|
||||
self:T({From, Event, To})
|
||||
|
||||
self:T2( {"Stopped lasing for ", self.Target and self.Target:GetName() or "coord", SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
|
||||
|
||||
self:F( {"Stopped lasing for ", self.Target and self.Target:GetName() or "coord", SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
|
||||
|
||||
self.Lasing = false
|
||||
|
||||
@@ -386,16 +368,4 @@ do
|
||||
return self.Lasing
|
||||
end
|
||||
|
||||
--- Set laser start position relative to the lasing unit.
|
||||
-- @param #SPOT self
|
||||
-- @param #table position (Optional) Start position of the laser relative to the lasing unit. Default is { x = 0, y = 2, z = 0 }
|
||||
-- @return #SPOT self
|
||||
-- @usage
|
||||
-- -- Set lasing position to be the position of the optics of the Gazelle M:
|
||||
-- myspot:SetRelativeStartPosition({ x = 1.7, y = 1.2, z = 0 })
|
||||
function SPOT:SetRelativeStartPosition(position)
|
||||
self.relstartpos = position or { x = 0, y = 2, z = 0 }
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Core** - A Moose GetText system.
|
||||
--- **Core** - TEXTANDSOUND (MOOSE gettext) system
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
|
||||
@@ -107,7 +107,7 @@ _TIMERID=0
|
||||
|
||||
--- TIMER class version.
|
||||
-- @field #string version
|
||||
TIMER.version="0.2.0"
|
||||
TIMER.version="0.1.2"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -155,7 +155,7 @@ function TIMER:New(Function, ...)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Start TIMER object.
|
||||
--- Create a new TIMER object.
|
||||
-- @param #TIMER self
|
||||
-- @param #number Tstart Relative start time in seconds.
|
||||
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
|
||||
@@ -167,7 +167,7 @@ function TIMER:Start(Tstart, dT, Duration)
|
||||
local Tnow=timer.getTime()
|
||||
|
||||
-- Start time in sec.
|
||||
self.Tstart=Tstart and Tnow+math.max(Tstart, 0.001) or Tnow+0.001 -- one millisecond delay if Tstart=nil
|
||||
self.Tstart=Tstart and Tnow+Tstart or Tnow+0.001 -- one millisecond delay if Tstart=nil
|
||||
|
||||
-- Set time interval.
|
||||
self.dT=dT
|
||||
@@ -192,20 +192,6 @@ function TIMER:Start(Tstart, dT, Duration)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Start TIMER object if a condition is met. Useful for e.g. debugging.
|
||||
-- @param #TIMER self
|
||||
-- @param #boolean Condition Must be true for the TIMER to start
|
||||
-- @param #number Tstart Relative start time in seconds.
|
||||
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
|
||||
-- @param #number Duration Time in seconds for how long the timer is running. If not specified `nil`, the timer runs forever or until stopped manually by the `TIMER:Stop()` function.
|
||||
-- @return #TIMER self
|
||||
function TIMER:StartIf(Condition,Tstart, dT, Duration)
|
||||
if Condition then
|
||||
self:Start(Tstart, dT, Duration)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stop the timer by removing the timer function.
|
||||
-- @param #TIMER self
|
||||
-- @param #number Delay (Optional) Delay in seconds, before the timer is stopped.
|
||||
@@ -222,20 +208,7 @@ function TIMER:Stop(Delay)
|
||||
|
||||
-- Remove timer function.
|
||||
self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls))
|
||||
|
||||
-- We use a pcall here because if the DCS timer does not exist any more, it crashes the whole script!
|
||||
local status=pcall(
|
||||
function ()
|
||||
timer.removeFunction(self.tid)
|
||||
end
|
||||
)
|
||||
|
||||
-- Debug messages.
|
||||
if status then
|
||||
self:T2(self.lid..string.format("Stopped timer!"))
|
||||
else
|
||||
self:E(self.lid..string.format("WARNING: Could not remove timer function! isrunning=%s", tostring(self.isrunning)))
|
||||
end
|
||||
timer.removeFunction(self.tid)
|
||||
|
||||
-- Not running any more.
|
||||
self.isrunning=false
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
do -- UserFlag
|
||||
|
||||
-- @type USERFLAG
|
||||
--- @type USERFLAG
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string UserFlagName Name of the flag.
|
||||
-- @extends Core.Base#BASE
|
||||
@@ -35,13 +35,13 @@ do -- UserFlag
|
||||
ClassName = "USERFLAG",
|
||||
UserFlagName = nil,
|
||||
}
|
||||
|
||||
|
||||
--- USERFLAG Constructor.
|
||||
-- @param #USERFLAG self
|
||||
-- @param #string UserFlagName The name of the userflag, which is a free text string.
|
||||
-- @return #USERFLAG
|
||||
function USERFLAG:New( UserFlagName ) --R2.3
|
||||
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #USERFLAG
|
||||
|
||||
self.UserFlagName = UserFlagName
|
||||
@@ -52,13 +52,13 @@ do -- UserFlag
|
||||
--- Get the userflag name.
|
||||
-- @param #USERFLAG self
|
||||
-- @return #string Name of the user flag.
|
||||
function USERFLAG:GetName()
|
||||
function USERFLAG:GetName()
|
||||
return self.UserFlagName
|
||||
end
|
||||
|
||||
--- Set the userflag to a given Number.
|
||||
-- @param #USERFLAG self
|
||||
-- @param #number Number The number value to set the flag to.
|
||||
-- @param #number Number The number value to be checked if it is the same as the userflag.
|
||||
-- @param #number Delay Delay in seconds, before the flag is set.
|
||||
-- @return #USERFLAG The userflag instance.
|
||||
-- @usage
|
||||
@@ -66,17 +66,18 @@ do -- UserFlag
|
||||
-- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100.
|
||||
--
|
||||
function USERFLAG:Set( Number, Delay ) --R2.3
|
||||
|
||||
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, USERFLAG.Set, self, Number)
|
||||
else
|
||||
--env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime()))
|
||||
trigger.action.setUserFlag( self.UserFlagName, Number )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Get the userflag Number.
|
||||
-- @param #USERFLAG self
|
||||
-- @return #number Number The number value to be checked if it is the same as the userflag.
|
||||
@@ -85,10 +86,12 @@ do -- UserFlag
|
||||
-- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value.
|
||||
--
|
||||
function USERFLAG:Get() --R2.3
|
||||
|
||||
|
||||
return trigger.misc.getUserFlag( self.UserFlagName )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Check if the userflag has a value of Number.
|
||||
-- @param #USERFLAG self
|
||||
-- @param #number Number The number value to be checked if it is the same as the userflag.
|
||||
@@ -99,9 +102,9 @@ do -- UserFlag
|
||||
-- return "Blue has won"
|
||||
-- end
|
||||
function USERFLAG:Is( Number ) --R2.3
|
||||
|
||||
|
||||
return trigger.misc.getUserFlag( self.UserFlagName ) == Number
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@
|
||||
|
||||
do -- Velocity
|
||||
|
||||
-- @type VELOCITY
|
||||
--- @type VELOCITY
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ do -- Velocity
|
||||
self.Velocity = VelocityMps
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get the velocity in Mps (meters per second).
|
||||
-- @param #VELOCITY self
|
||||
-- @return #number The velocity in meters per second.
|
||||
@@ -70,12 +70,12 @@ do -- Velocity
|
||||
self.Velocity = UTILS.KmphToMps( VelocityKmph )
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get the velocity in Kmph (kilometers per hour).
|
||||
-- @param #VELOCITY self
|
||||
-- @return #number The velocity in kilometers per hour.
|
||||
function VELOCITY:GetKmph()
|
||||
|
||||
|
||||
return UTILS.MpsToKmph( self.Velocity )
|
||||
end
|
||||
|
||||
@@ -87,7 +87,7 @@ do -- Velocity
|
||||
self.Velocity = UTILS.MiphToMps( VelocityMiph )
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get the velocity in Miph (miles per hour).
|
||||
-- @param #VELOCITY self
|
||||
-- @return #number The velocity in miles per hour.
|
||||
@@ -95,7 +95,8 @@ do -- Velocity
|
||||
return UTILS.MpsToMiph( self.Velocity )
|
||||
end
|
||||
|
||||
--- Get the velocity in text, according the player @{Core.Settings}.
|
||||
|
||||
--- Get the velocity in text, according the player @{Settings}.
|
||||
-- @param #VELOCITY self
|
||||
-- @param Core.Settings#SETTINGS Settings
|
||||
-- @return #string The velocity in text.
|
||||
@@ -112,11 +113,11 @@ do -- Velocity
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the velocity in text, according the player or default @{Core.Settings}.
|
||||
--- Get the velocity in text, according the player or default @{Settings}.
|
||||
-- @param #VELOCITY self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
|
||||
-- @param Core.Settings#SETTINGS Settings
|
||||
-- @return #string The velocity in text according the player or default @{Core.Settings}
|
||||
-- @return #string The velocity in text according the player or default @{Settings}
|
||||
function VELOCITY:ToString( VelocityGroup, Settings ) -- R2.3
|
||||
self:F( { Group = VelocityGroup and VelocityGroup:GetName() } )
|
||||
local Settings = Settings or ( VelocityGroup and _DATABASE:GetPlayerSettings( VelocityGroup:GetPlayerName() ) ) or _SETTINGS
|
||||
@@ -127,13 +128,13 @@ end
|
||||
|
||||
do -- VELOCITY_POSITIONABLE
|
||||
|
||||
-- @type VELOCITY_POSITIONABLE
|
||||
--- @type VELOCITY_POSITIONABLE
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- # VELOCITY_POSITIONABLE class, extends @{Core.Base#BASE}
|
||||
--
|
||||
-- @{#VELOCITY_POSITIONABLE} monitors the speed of a @{Wrapper.Positionable#POSITIONABLE} in the simulation, which can be expressed in various formats according the Settings.
|
||||
-- VELOCITY_POSITIONABLE monitors the speed of an @{Positionable} in the simulation, which can be expressed in various formats according the Settings.
|
||||
--
|
||||
-- ## 1. VELOCITY_POSITIONABLE constructor
|
||||
--
|
||||
@@ -166,7 +167,7 @@ do -- VELOCITY_POSITIONABLE
|
||||
-- @param #VELOCITY_POSITIONABLE self
|
||||
-- @return #number The velocity in kilometers per hour.
|
||||
function VELOCITY_POSITIONABLE:GetKmph()
|
||||
|
||||
|
||||
return UTILS.MpsToKmph( self.Positionable:GetVelocityMPS() or 0)
|
||||
end
|
||||
|
||||
@@ -177,9 +178,9 @@ do -- VELOCITY_POSITIONABLE
|
||||
return UTILS.MpsToMiph( self.Positionable:GetVelocityMPS() or 0 )
|
||||
end
|
||||
|
||||
--- Get the velocity in text, according the player or default @{Core.Settings}.
|
||||
--- Get the velocity in text, according the player or default @{Settings}.
|
||||
-- @param #VELOCITY_POSITIONABLE self
|
||||
-- @return #string The velocity in text according the player or default @{Core.Settings}
|
||||
-- @return #string The velocity in text according the player or default @{Settings}
|
||||
function VELOCITY_POSITIONABLE:ToString() -- R2.3
|
||||
self:F( { Group = self.Positionable and self.Positionable:GetName() } )
|
||||
local Settings = Settings or ( self.Positionable and _DATABASE:GetPlayerSettings( self.Positionable:GetPlayerName() ) ) or _SETTINGS
|
||||
|
||||
+323
-2211
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,5 @@
|
||||
--- **Core** - The ZONE_DETECTION class, defined by a zone name, a detection object and a radius.
|
||||
-- @module Core.Zone_Detection
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
---
|
||||
--- The ZONE_DETECTION class, defined by a zone name, a detection object and a radius.
|
||||
-- @type ZONE_DETECTION
|
||||
-- @field DCS#Vec2 Vec2 The current location of the zone.
|
||||
-- @field DCS#Distance Radius The radius of the zone.
|
||||
@@ -32,7 +29,7 @@ function ZONE_DETECTION:New( ZoneName, Detection, Radius )
|
||||
|
||||
self.Detection = Detection
|
||||
self.Radius = Radius
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -51,14 +48,15 @@ function ZONE_DETECTION:BoundZone( Points, CountryID, UnBound )
|
||||
|
||||
local Angle
|
||||
local RadialBase = math.pi*2
|
||||
|
||||
|
||||
--
|
||||
for Angle = 0, 360, (360 / Points ) do
|
||||
local Radial = Angle * RadialBase / 360
|
||||
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
|
||||
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
|
||||
|
||||
|
||||
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
|
||||
|
||||
|
||||
local Tire = {
|
||||
["country"] = CountryName,
|
||||
["category"] = "Fortifications",
|
||||
@@ -107,7 +105,7 @@ function ZONE_DETECTION:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset )
|
||||
local Radial = ( Angle + AngleOffset ) * RadialBase / 360
|
||||
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
|
||||
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
|
||||
COORDINATE:New( Point.x, AddHeight, Point.y):Smoke( SmokeColor )
|
||||
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor )
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -138,7 +136,7 @@ function ZONE_DETECTION:FlareZone( FlareColor, Points, Azimuth, AddHeight )
|
||||
local Radial = Angle * RadialBase / 360
|
||||
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
|
||||
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
|
||||
COORDINATE:New( Point.x, AddHeight, Point.y ):Flare( FlareColor, Azimuth )
|
||||
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth )
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -202,3 +200,4 @@ function ZONE_DETECTION:IsVec3InZone( Vec3 )
|
||||
|
||||
return InZone
|
||||
end
|
||||
|
||||
|
||||
+133
-426
@@ -1,6 +1,4 @@
|
||||
--- **DCS API** Prototypes.
|
||||
--
|
||||
-- ===
|
||||
--- **DCS API** Prototypes
|
||||
--
|
||||
-- See the [Simulator Scripting Engine Documentation](https://wiki.hoggitworld.com/view/Simulator_Scripting_Engine_Documentation) on Hoggit for further explanation and examples.
|
||||
--
|
||||
@@ -14,7 +12,6 @@ do -- world
|
||||
-- @field #world.event event [https://wiki.hoggitworld.com/view/DCS_enum_world](https://wiki.hoggitworld.com/view/DCS_enum_world)
|
||||
-- @field #world.BirthPlace BirthPlace The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events.
|
||||
-- @field #world.VolumeType VolumeType The volumeType enumerator defines the types of 3d geometery used within the [world.searchObjects](https://wiki.hoggitworld.com/view/DCS_func_searchObjects) function.
|
||||
-- @field #world.weather weather Weather functions for fog etc.
|
||||
|
||||
--- The world singleton contains functions centered around two different but extremely useful functions.
|
||||
-- * Events and event handlers are all governed within world.
|
||||
@@ -26,69 +23,38 @@ do -- world
|
||||
|
||||
--- [https://wiki.hoggitworld.com/view/DCS_enum_world](https://wiki.hoggitworld.com/view/DCS_enum_world)
|
||||
-- @type world.event
|
||||
-- @field S_EVENT_INVALID = 0
|
||||
-- @field S_EVENT_SHOT = 1
|
||||
-- @field S_EVENT_HIT = 2
|
||||
-- @field S_EVENT_TAKEOFF = 3
|
||||
-- @field S_EVENT_LAND = 4
|
||||
-- @field S_EVENT_CRASH = 5
|
||||
-- @field S_EVENT_EJECTION = 6
|
||||
-- @field S_EVENT_REFUELING = 7
|
||||
-- @field S_EVENT_DEAD = 8
|
||||
-- @field S_EVENT_PILOT_DEAD = 9
|
||||
-- @field S_EVENT_BASE_CAPTURED = 10
|
||||
-- @field S_EVENT_MISSION_START = 11
|
||||
-- @field S_EVENT_MISSION_END = 12
|
||||
-- @field S_EVENT_TOOK_CONTROL = 13
|
||||
-- @field S_EVENT_REFUELING_STOP = 14
|
||||
-- @field S_EVENT_BIRTH = 15
|
||||
-- @field S_EVENT_HUMAN_FAILURE = 16
|
||||
-- @field S_EVENT_DETAILED_FAILURE = 17
|
||||
-- @field S_EVENT_ENGINE_STARTUP = 18
|
||||
-- @field S_EVENT_ENGINE_SHUTDOWN = 19
|
||||
-- @field S_EVENT_PLAYER_ENTER_UNIT = 20
|
||||
-- @field S_EVENT_PLAYER_LEAVE_UNIT = 21
|
||||
-- @field S_EVENT_PLAYER_COMMENT = 22
|
||||
-- @field S_EVENT_SHOOTING_START = 23
|
||||
-- @field S_EVENT_SHOOTING_END = 24
|
||||
-- @field S_EVENT_MARK_ADDED = 25
|
||||
-- @field S_EVENT_MARK_CHANGE = 26
|
||||
-- @field S_EVENT_MARK_REMOVED = 27
|
||||
-- @field S_EVENT_KILL = 28
|
||||
-- @field S_EVENT_SCORE = 29
|
||||
-- @field S_EVENT_UNIT_LOST = 30
|
||||
-- @field S_EVENT_LANDING_AFTER_EJECTION = 31
|
||||
-- @field S_EVENT_PARATROOPER_LENDING = 32 -- who's lending whom what? ;)
|
||||
-- @field S_EVENT_DISCARD_CHAIR_AFTER_EJECTION = 33
|
||||
-- @field S_EVENT_WEAPON_ADD = 34
|
||||
-- @field S_EVENT_TRIGGER_ZONE = 35
|
||||
-- @field S_EVENT_LANDING_QUALITY_MARK = 36
|
||||
-- @field S_EVENT_BDA = 37 -- battle damage assessment
|
||||
-- @field S_EVENT_AI_ABORT_MISSION = 38
|
||||
-- @field S_EVENT_DAYNIGHT = 39
|
||||
-- @field S_EVENT_FLIGHT_TIME = 40
|
||||
-- @field S_EVENT_PLAYER_SELF_KILL_PILOT = 41
|
||||
-- @field S_EVENT_PLAYER_CAPTURE_AIRFIELD = 42
|
||||
-- @field S_EVENT_EMERGENCY_LANDING = 43
|
||||
-- @field S_EVENT_UNIT_CREATE_TASK = 44
|
||||
-- @field S_EVENT_UNIT_DELETE_TASK = 45
|
||||
-- @field S_EVENT_SIMULATION_START = 46
|
||||
-- @field S_EVENT_WEAPON_REARM = 47
|
||||
-- @field S_EVENT_WEAPON_DROP = 48
|
||||
-- @field S_EVENT_UNIT_TASK_COMPLETE = 49
|
||||
-- @field S_EVENT_UNIT_TASK_STAGE = 50
|
||||
-- @field S_EVENT_MAC_EXTRA_SCORE= 51 -- not sure what this is
|
||||
-- @field S_EVENT_MISSION_RESTART= 52
|
||||
-- @field S_EVENT_MISSION_WINNER = 53
|
||||
-- @field S_EVENT_RUNWAY_TAKEOFF= 54
|
||||
-- @field S_EVENT_RUNWAY_TOUCH= 55
|
||||
-- @field S_EVENT_MAC_LMS_RESTART= 56 -- not sure what this is
|
||||
-- @field S_EVENT_SIMULATION_FREEZE = 57
|
||||
-- @field S_EVENT_SIMULATION_UNFREEZE = 58
|
||||
-- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_START = 59
|
||||
-- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH = 60
|
||||
-- @field S_EVENT_GROUP_CHANGE_OPTION = 61
|
||||
-- @field S_EVENT_MAX = 62
|
||||
-- @field S_EVENT_INVALID
|
||||
-- @field S_EVENT_SHOT [https://wiki.hoggitworld.com/view/DCS_event_shot](https://wiki.hoggitworld.com/view/DCS_event_shot)
|
||||
-- @field S_EVENT_HIT [https://wiki.hoggitworld.com/view/DCS_event_hit](https://wiki.hoggitworld.com/view/DCS_event_hit)
|
||||
-- @field S_EVENT_TAKEOFF [https://wiki.hoggitworld.com/view/DCS_event_takeoff](https://wiki.hoggitworld.com/view/DCS_event_takeoff)
|
||||
-- @field S_EVENT_LAND [https://wiki.hoggitworld.com/view/DCS_event_land](https://wiki.hoggitworld.com/view/DCS_event_land)
|
||||
-- @field S_EVENT_CRASH [https://wiki.hoggitworld.com/view/DCS_event_crash](https://wiki.hoggitworld.com/view/DCS_event_crash)
|
||||
-- @field S_EVENT_EJECTION [https://wiki.hoggitworld.com/view/DCS_event_ejection](https://wiki.hoggitworld.com/view/DCS_event_ejection)
|
||||
-- @field S_EVENT_REFUELING [https://wiki.hoggitworld.com/view/DCS_event_refueling](https://wiki.hoggitworld.com/view/DCS_event_refueling)
|
||||
-- @field S_EVENT_DEAD [https://wiki.hoggitworld.com/view/DCS_event_dead](https://wiki.hoggitworld.com/view/DCS_event_dead)
|
||||
-- @field S_EVENT_PILOT_DEAD [https://wiki.hoggitworld.com/view/DCS_event_pilot_dead](https://wiki.hoggitworld.com/view/DCS_event_pilot_dead)
|
||||
-- @field S_EVENT_BASE_CAPTURED [https://wiki.hoggitworld.com/view/DCS_event_base_captured](https://wiki.hoggitworld.com/view/DCS_event_base_captured)
|
||||
-- @field S_EVENT_MISSION_START [https://wiki.hoggitworld.com/view/DCS_event_mission_start](https://wiki.hoggitworld.com/view/DCS_event_mission_start)
|
||||
-- @field S_EVENT_MISSION_END [https://wiki.hoggitworld.com/view/DCS_event_mission_end](https://wiki.hoggitworld.com/view/DCS_event_mission_end)
|
||||
-- @field S_EVENT_TOOK_CONTROL
|
||||
-- @field S_EVENT_REFUELING_STOP [https://wiki.hoggitworld.com/view/DCS_event_refueling_stop](https://wiki.hoggitworld.com/view/DCS_event_refueling_stop)
|
||||
-- @field S_EVENT_BIRTH [https://wiki.hoggitworld.com/view/DCS_event_birth](https://wiki.hoggitworld.com/view/DCS_event_birth)
|
||||
-- @field S_EVENT_HUMAN_FAILURE [https://wiki.hoggitworld.com/view/DCS_event_human_failure](https://wiki.hoggitworld.com/view/DCS_event_human_failure)
|
||||
-- @field S_EVENT_ENGINE_STARTUP [https://wiki.hoggitworld.com/view/DCS_event_engine_startup](https://wiki.hoggitworld.com/view/DCS_event_engine_startup)
|
||||
-- @field S_EVENT_ENGINE_SHUTDOWN [https://wiki.hoggitworld.com/view/DCS_event_engine_shutdown](https://wiki.hoggitworld.com/view/DCS_event_engine_shutdown)
|
||||
-- @field S_EVENT_PLAYER_ENTER_UNIT [https://wiki.hoggitworld.com/view/DCS_event_player_enter_unit](https://wiki.hoggitworld.com/view/DCS_event_player_enter_unit)
|
||||
-- @field S_EVENT_PLAYER_LEAVE_UNIT [https://wiki.hoggitworld.com/view/DCS_event_player_leave_unit](https://wiki.hoggitworld.com/view/DCS_event_player_leave_unit)
|
||||
-- @field S_EVENT_PLAYER_COMMENT
|
||||
-- @field S_EVENT_SHOOTING_START [https://wiki.hoggitworld.com/view/DCS_event_shooting_start](https://wiki.hoggitworld.com/view/DCS_event_shooting_start)
|
||||
-- @field S_EVENT_SHOOTING_END [https://wiki.hoggitworld.com/view/DCS_event_shooting_end](https://wiki.hoggitworld.com/view/DCS_event_shooting_end)
|
||||
-- @field S_EVENT_MARK ADDED [https://wiki.hoggitworld.com/view/DCS_event_mark_added](https://wiki.hoggitworld.com/view/DCS_event_mark_added) DCS>=2.5.1
|
||||
-- @field S_EVENT_MARK CHANGE [https://wiki.hoggitworld.com/view/DCS_event_mark_change](https://wiki.hoggitworld.com/view/DCS_event_mark_change) DCS>=2.5.1
|
||||
-- @field S_EVENT_MARK REMOVE [https://wiki.hoggitworld.com/view/DCS_event_mark_remove](https://wiki.hoggitworld.com/view/DCS_event_mark_remove) DCS>=2.5.1
|
||||
-- @field S_EVENT_KILL [https://wiki.hoggitworld.com/view/DCS_event_kill](https://wiki.hoggitworld.com/view/DCS_event_kill) DCS>=2.5.6
|
||||
-- @field S_EVENT_SCORE [https://wiki.hoggitworld.com/view/DCS_event_score](https://wiki.hoggitworld.com/view/DCS_event_score) DCS>=2.5.6
|
||||
-- @field S_EVENT_UNIT_LOST [https://wiki.hoggitworld.com/view/DCS_event_unit_lost](https://wiki.hoggitworld.com/view/DCS_event_unit_lost) DCS>=2.5.6
|
||||
-- @field S_EVENT_LANDING_AFTER_EJECTION [https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection](https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection) DCS>=2.5.6
|
||||
-- @field S_EVENT_MAX
|
||||
|
||||
--- The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events.
|
||||
-- @type world.BirthPlace
|
||||
@@ -132,38 +98,8 @@ do -- world
|
||||
|
||||
--- Returns a table of DCS airbase objects.
|
||||
-- @function [parent=#world] getAirbases
|
||||
-- @param #number coalitionId (Optional) The coalition side number ID. Default is all airbases are returned.
|
||||
-- @param #number coalitionId The coalition side number ID. Default is all airbases are returned.
|
||||
-- @return #table Table of DCS airbase objects.
|
||||
|
||||
|
||||
--- Weather functions.
|
||||
-- @type world.weather
|
||||
|
||||
--- Fog animation data structure.
|
||||
-- @type world.FogAnimation
|
||||
-- @field #number time
|
||||
-- @field #number visibility
|
||||
-- @field #number thickness
|
||||
|
||||
--- Returns the current fog thickness.
|
||||
-- @function [parent=#world.weather] getFogThickness Returns the fog thickness.
|
||||
-- @return #number Fog thickness in meters. If there is no fog, zero is returned.
|
||||
|
||||
--- Sets the fog thickness instantly. Any current fog animation is discarded.
|
||||
-- @function [parent=#world.weather] setFogThickness
|
||||
-- @param #number thickness Fog thickness in meters. Set to zero to disable fog.
|
||||
|
||||
--- Returns the current fog visibility distance.
|
||||
-- @function [parent=#world.weather] getFogVisibilityDistance Returns the current maximum visibility distance in meters. Returns zero if fog is not present.
|
||||
|
||||
--- Instantly sets the maximum visibility distance of fog at sea level when looking at the horizon. Any current fog animation is discarded. Set zero to disable the fog.
|
||||
-- @function [parent=#world.weather] setFogVisibilityDistance
|
||||
-- @param #number visibility Max fog visibility in meters. Set to zero to disable fog.
|
||||
|
||||
--- Sets fog animation keys. Time is set in seconds and relative to the current simulation time, where time=0 is the current moment.
|
||||
-- Time must be increasing. Previous animation is always discarded despite the data being correct.
|
||||
-- @function [parent=#world.weather] setFogAnimation
|
||||
-- @param #world.FogAnimation animation List of fog animations
|
||||
|
||||
end -- world
|
||||
|
||||
@@ -197,22 +133,6 @@ do -- env
|
||||
|
||||
end -- env
|
||||
|
||||
do -- radio
|
||||
|
||||
--@type radio
|
||||
-- @field #radio.modulation modulation
|
||||
|
||||
---
|
||||
-- @type radio.modulation
|
||||
-- @field AM
|
||||
-- @field FM
|
||||
|
||||
radio = {}
|
||||
radio.modulation = {}
|
||||
radio.modulation.AM = 0
|
||||
radio.modulation.FM = 1
|
||||
|
||||
end
|
||||
|
||||
do -- timer
|
||||
|
||||
@@ -271,29 +191,21 @@ do -- land
|
||||
|
||||
--- [Type of surface enumerator](https://wiki.hoggitworld.com/view/DCS_singleton_land)
|
||||
-- @type land.SurfaceType
|
||||
-- @field LAND Land=1
|
||||
-- @field SHALLOW_WATER Shallow water=2
|
||||
-- @field WATER Water=3
|
||||
-- @field ROAD Road=4
|
||||
-- @field RUNWAY Runway=5
|
||||
-- @field LAND
|
||||
-- @field SHALLOW_WATER
|
||||
-- @field WATER
|
||||
-- @field ROAD
|
||||
-- @field RUNWAY
|
||||
|
||||
--- Returns the distance from sea level (y-axis) of a given vec2 point.
|
||||
--- Returns altitude MSL of the point.
|
||||
-- @function [parent=#land] getHeight
|
||||
-- @param #Vec2 point Point on the ground.
|
||||
-- @return #number Height in meters.
|
||||
|
||||
--- Returns the surface height and depth of a point. Useful for checking if the path is deep enough to support a given ship.
|
||||
-- Both values are positive. When checked over water at sea level the first value is always zero.
|
||||
-- When checked over water at altitude, for example the reservoir of the Inguri Dam, the first value is the corresponding altitude the water level is at.
|
||||
-- @function [parent=#land] getSurfaceHeightWithSeabed
|
||||
-- @param #Vec2 point Position where to check.
|
||||
-- @return #number Height in meters.
|
||||
-- @return #number Depth in meters.
|
||||
-- @param #Vec2 point point on the ground.
|
||||
-- @return #Distance
|
||||
|
||||
--- Returns surface type at the given point.
|
||||
--- returns surface type at the given point.
|
||||
-- @function [parent=#land] getSurfaceType
|
||||
-- @param #Vec2 point Point on the land.
|
||||
-- @return #number Enumerator value from `land.SurfaceType` (LAND=1, SHALLOW_WATER=2, WATER=3, ROAD=4, RUNWAY=5)
|
||||
-- @return #land.SurfaceType
|
||||
|
||||
--- [DCS Singleton land](https://wiki.hoggitworld.com/view/DCS_singleton_land)
|
||||
land = {} --#land
|
||||
@@ -394,11 +306,6 @@ do -- country
|
||||
-- @field Argentinia
|
||||
-- @field Cyprus
|
||||
-- @field Slovenia
|
||||
-- @field BOLIVIA
|
||||
-- @field GHANA
|
||||
-- @field NIGERIA
|
||||
-- @field PERU
|
||||
-- @field ECUADOR
|
||||
|
||||
country = {} --#country
|
||||
|
||||
@@ -407,11 +314,11 @@ end -- country
|
||||
|
||||
do -- Command
|
||||
|
||||
-- @type Command
|
||||
--- @type Command
|
||||
-- @field #string id
|
||||
-- @field #Command.params params
|
||||
|
||||
-- @type Command.params
|
||||
--- @type Command.params
|
||||
|
||||
end -- Command
|
||||
|
||||
@@ -427,23 +334,9 @@ do -- coalition
|
||||
-- @field RED
|
||||
-- @field BLUE
|
||||
|
||||
--- Get country coalition.
|
||||
-- @function [parent=#coalition] getCountryCoalition
|
||||
-- @param #number countryId Country ID.
|
||||
-- @return #number coalitionId Coalition ID.
|
||||
|
||||
--- Dynamically spawns a group. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup)
|
||||
-- @function [parent=#coalition] addGroup
|
||||
-- @param #number countryId Id of the country.
|
||||
-- @param #number groupCategory Group category. Set -1 for spawning FARPS.
|
||||
-- @param #table groupData Group data table.
|
||||
-- @return DCS#Group The spawned Group object.
|
||||
|
||||
--- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addStaticObject)
|
||||
-- @function [parent=#coalition] addStaticObject
|
||||
-- @param #number countryId Id of the country.
|
||||
-- @param #table groupData Group data table.
|
||||
-- @return DCS#Static The spawned static object.
|
||||
--- @function [parent=#coalition] getCountryCoalition
|
||||
-- @param #number countryId
|
||||
-- @return #number coalitionId
|
||||
|
||||
coalition = {} -- #coalition
|
||||
|
||||
@@ -452,8 +345,7 @@ end -- coalition
|
||||
|
||||
do -- Types
|
||||
|
||||
--- Descriptors.
|
||||
-- @type Desc
|
||||
--- @type Desc
|
||||
-- @field #number speedMax0 Max speed in meters/second at zero altitude.
|
||||
-- @field #number massEmpty Empty mass in kg.
|
||||
-- @field #number tankerType Type of refueling system: 0=boom, 1=probe.
|
||||
@@ -514,8 +406,8 @@ do -- Types
|
||||
--- Vec3 type is a 3D-vector.
|
||||
-- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain.
|
||||
-- @type Vec3
|
||||
-- @field #Distance x is directed to the North
|
||||
-- @field #Distance z is directed to the East
|
||||
-- @field #Distance x is directed to the north
|
||||
-- @field #Distance z is directed to the east
|
||||
-- @field #Distance y is directed up
|
||||
|
||||
--- Vec2 is a 2D-vector for the ground plane as a reference plane.
|
||||
@@ -550,16 +442,16 @@ do -- Types
|
||||
-- @type AttributeNameArray
|
||||
-- @list <#AttributeName>
|
||||
|
||||
-- @type Zone
|
||||
--- @type Zone
|
||||
-- @field DCSVec3#Vec3 point
|
||||
-- @field #number radius
|
||||
|
||||
Zone = {}
|
||||
|
||||
-- @type ModelTime
|
||||
--- @type ModelTime
|
||||
-- @extends #number
|
||||
|
||||
-- @type Time
|
||||
--- @type Time
|
||||
-- @extends #number
|
||||
|
||||
--- A task descriptor (internal structure for DCS World). See [https://wiki.hoggitworld.com/view/Category:Tasks](https://wiki.hoggitworld.com/view/Category:Tasks).
|
||||
@@ -568,7 +460,7 @@ do -- Types
|
||||
-- @field #string id
|
||||
-- @field #Task.param param
|
||||
|
||||
-- @type Task.param
|
||||
--- @type Task.param
|
||||
|
||||
--- List of @{#Task}
|
||||
-- @type TaskArray
|
||||
@@ -615,7 +507,7 @@ do -- Object
|
||||
-- @field SCENERY
|
||||
-- @field CARGO
|
||||
|
||||
-- @type Object.Desc
|
||||
--- @type Object.Desc
|
||||
-- @extends #Desc
|
||||
-- @field #number life initial life level
|
||||
-- @field #Box3 box bounding box of collision geometry
|
||||
@@ -631,13 +523,9 @@ do -- Object
|
||||
--- @function [parent=#Object] destroy
|
||||
-- @param #Object self
|
||||
|
||||
--- Returns an enumerator of the category for the specific object.
|
||||
-- The enumerator returned is dependent on the category of the object and how the function is called.
|
||||
-- As of DCS 2.9.2 when this function is called on an Object, Unit, Weapon, or Airbase a 2nd value will be returned which details the object sub-category value.
|
||||
-- @function [parent=#Object] getCategory
|
||||
--- @function [parent=#Object] getCategory
|
||||
-- @param #Object self
|
||||
-- @return #Object.Category The object category (1=UNIT, 2=WEAPON, 3=STATIC, 4=BASE, 5=SCENERY, 6=Cargo)
|
||||
-- @return #number The subcategory of the passed object, e.g. Unit.Category if a unit object was passed.
|
||||
-- @return #Object.Category
|
||||
|
||||
--- Returns type name of the Object.
|
||||
-- @function [parent=#Object] getTypeName
|
||||
@@ -770,11 +658,10 @@ do -- Weapon
|
||||
|
||||
--- Weapon.Category enum that stores weapon categories.
|
||||
-- @type Weapon.Category
|
||||
-- @field #number SHELL Shell.
|
||||
-- @field #number MISSILE Missile
|
||||
-- @field #number ROCKET Rocket.
|
||||
-- @field #number BOMB Bomb.
|
||||
-- @field #number TORPEDO Torpedo.
|
||||
-- @field SHELL
|
||||
-- @field MISSILE
|
||||
-- @field ROCKET
|
||||
-- @field BOMB
|
||||
|
||||
|
||||
--- Weapon.GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB).
|
||||
@@ -876,118 +763,11 @@ do -- Airbase
|
||||
-- @function [parent=#Airbase] getDesc
|
||||
-- @param self
|
||||
-- @return #Airbase.Desc
|
||||
|
||||
--- Returns the warehouse object associated with the airbase object. Can then be used to call the warehouse class functions to modify the contents of the warehouse.
|
||||
-- @function [parent=#Airbase] getWarehouse
|
||||
-- @param self
|
||||
-- @return #Warehouse The DCS warehouse object of this airbase.
|
||||
|
||||
--- Enables or disables the airbase and FARP auto capture game mechanic where ownership of a base can change based on the presence of ground forces or the
|
||||
-- default setting assigned in the editor.
|
||||
-- @function [parent=#Airbase] autoCapture
|
||||
-- @param self
|
||||
-- @param #boolean setting `true` : enables autoCapture behavior, `false` : disables autoCapture behavior
|
||||
|
||||
--- Returns the current autoCapture setting for the passed base.
|
||||
-- @function [parent=#Airbase] autoCaptureIsOn
|
||||
-- @param self
|
||||
-- @return #boolean `true` if autoCapture behavior is enabled and `false` otherwise.
|
||||
|
||||
--- Changes the passed airbase object's coalition to the set value. Must be used with Airbase.autoCapture to disable auto capturing of the base,
|
||||
-- otherwise the base can revert back to a different coalition depending on the situation and built in game capture rules.
|
||||
-- @function [parent=#Airbase] setCoalition
|
||||
-- @param self
|
||||
-- @param #number coa The new owner coalition: 0=neutra, 1=red, 2=blue.
|
||||
|
||||
--- Returns the wsType of every object that exists in DCS. A wsType is a table consisting of 4 entries indexed numerically.
|
||||
-- It can be used to broadly categorize object types. The table can be broken down as: {mainCategory, subCat1, subCat2, index}
|
||||
-- @function [parent=#Airbase] getResourceMap
|
||||
-- @param self
|
||||
-- @return #table wsType of every object that exists in DCS.
|
||||
|
||||
|
||||
Airbase = {} --#Airbase
|
||||
|
||||
end -- Airbase
|
||||
|
||||
|
||||
do -- Warehouse
|
||||
|
||||
--- [DCS Class Warehouse](https://wiki.hoggitworld.com/view/DCS_Class_Warehouse)
|
||||
-- The warehouse class gives control over warehouses that exist in airbase objects. These warehouses can limit the aircraft, munitions, and fuel available to coalition aircraft.
|
||||
-- @type Warehouse
|
||||
|
||||
|
||||
--- Get a warehouse by passing its name.
|
||||
-- @function [parent=#Warehouse] getByName
|
||||
-- @param #string Name Name of the warehouse.
|
||||
-- @return #Warehouse The warehouse object.
|
||||
|
||||
--- Adds the passed amount of a given item to the warehouse.
|
||||
-- itemName is the typeName associated with the item: "weapons.missiles.AIM_54C_Mk47"
|
||||
-- A wsType table can also be used, however the last digit with wsTypes has been known to change. {4, 4, 7, 322}
|
||||
-- @function [parent=#Warehouse] addItem
|
||||
-- @param self
|
||||
-- @param #string itemName Name of the item.
|
||||
-- @param #number count Number of items to add.
|
||||
|
||||
--- Returns the number of the passed type of item currently in a warehouse object.
|
||||
-- @function [parent=#Warehouse] getItemCount
|
||||
-- @param self
|
||||
-- @param #string itemName Name of the item.
|
||||
|
||||
--- Sets the passed amount of a given item to the warehouse.
|
||||
-- @function [parent=#Warehouse] setItem
|
||||
-- @param self
|
||||
-- @param #string itemName Name of the item.
|
||||
-- @param #number count Number of items to add.
|
||||
|
||||
--- Removes the amount of the passed item from the warehouse.
|
||||
-- @function [parent=#Warehouse] removeItem
|
||||
-- @param self
|
||||
-- @param #string itemName Name of the item.
|
||||
-- @param #number count Number of items to be removed.
|
||||
|
||||
--- Adds the passed amount of a liquid fuel into the warehouse inventory.
|
||||
-- @function [parent=#Warehouse] addLiquid
|
||||
-- @param self
|
||||
-- @param #number liquidType Type of liquid to add: 0=jetfuel, 1=aviation gasoline, 2=MW50, 3=Diesel.
|
||||
-- @param #number count Amount of liquid to add.
|
||||
|
||||
--- Returns the amount of the passed liquid type within a given warehouse.
|
||||
-- @function [parent=#Warehouse] getLiquidAmount
|
||||
-- @param self
|
||||
-- @param #number liquidType Type of liquid to add: 0=jetfuel, 1=aviation gasoline, 2=MW50, 3=Diesel.
|
||||
-- @return #number Amount of liquid.
|
||||
|
||||
--- Sets the passed amount of a liquid fuel into the warehouse inventory.
|
||||
-- @function [parent=#Warehouse] setLiquidAmount
|
||||
-- @param self
|
||||
-- @param #number liquidType Type of liquid to add: 0=jetfuel, 1=aviation gasoline, 2=MW50, 3=Diesel.
|
||||
-- @param #number count Amount of liquid.
|
||||
|
||||
--- Removes the set amount of liquid from the inventory in a warehouse.
|
||||
-- @function [parent=#Warehouse] setLiquidAmount
|
||||
-- @param self
|
||||
-- @param #number liquidType Type of liquid to add: 0=jetfuel, 1=aviation gasoline, 2=MW50, 3=Diesel.
|
||||
-- @param #number count Amount of liquid.
|
||||
|
||||
--- Returns the airbase object associated with the warehouse object.
|
||||
-- @function [parent=#Warehouse] getOwner
|
||||
-- @param self
|
||||
-- @return #Airbase The airbase object owning this warehouse.
|
||||
|
||||
--- Returns a full itemized list of everything currently in a warehouse. If a category is set to unlimited then the table will be returned empty.
|
||||
-- Aircraft and weapons are indexed by strings. Liquids are indexed by number.
|
||||
-- @function [parent=#Warehouse] getInventory
|
||||
-- @param self
|
||||
-- @param #string itemName Name of the item.
|
||||
-- @return #table Itemized list of everything currently in a warehouse
|
||||
|
||||
|
||||
Warehouse = {} --#Warehouse
|
||||
|
||||
end
|
||||
|
||||
do -- Spot
|
||||
|
||||
--- [DCS Class Spot](https://wiki.hoggitworld.com/view/DCS_Class_Spot)
|
||||
@@ -1034,7 +814,7 @@ do -- Spot
|
||||
--- Sets the number that is used to define the laser code for which laser designation can track.
|
||||
-- @function [parent=#Spot] setCode
|
||||
-- @param #Spot self
|
||||
-- @param #number Code (Optional) The laser code. Default value is 1688.
|
||||
-- @param #number Code The laser code. Default value is 1688.
|
||||
|
||||
--- Destroys the spot.
|
||||
-- @function [parent=#Spot] destroy
|
||||
@@ -1050,16 +830,14 @@ do -- Spot
|
||||
end -- Spot
|
||||
|
||||
do -- Controller
|
||||
|
||||
--- Controller is an object that performs A.I.-tasks. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C.
|
||||
--- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C.
|
||||
--
|
||||
-- This class has 2 types of functions:
|
||||
--
|
||||
-- * Tasks
|
||||
-- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics.
|
||||
--
|
||||
-- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics.
|
||||
-- @type Controller
|
||||
-- @field #Controller.Detection Detection Enum contains identifiers of surface types.
|
||||
-- @field #Controller.Detection Detection Enum contains identifiers of surface types.
|
||||
|
||||
--- Enables and disables the controller.
|
||||
-- Note: Now it works only for ground / naval groups!
|
||||
@@ -1118,18 +896,18 @@ do -- Controller
|
||||
|
||||
-- Detection
|
||||
|
||||
--- Enum containing detection types.
|
||||
--- Enum contains identifiers of surface types.
|
||||
-- @type Controller.Detection
|
||||
-- @field #number VISUAL Visual detection. Numeric value 1.
|
||||
-- @field #number OPTIC Optical detection. Numeric value 2.
|
||||
-- @field #number RADAR Radar detection. Numeric value 4.
|
||||
-- @field #number IRST Infra-red search and track detection. Numeric value 8.
|
||||
-- @field #number RWR Radar Warning Receiver detection. Numeric value 16.
|
||||
-- @field #number DLINK Data link detection. Numeric value 32.
|
||||
-- @field VISUAL
|
||||
-- @field OPTIC
|
||||
-- @field RADAR
|
||||
-- @field IRST
|
||||
-- @field RWR
|
||||
-- @field DLINK
|
||||
|
||||
--- Detected target.
|
||||
-- @type Controller.DetectedTarget
|
||||
-- @field DCS#Object object The target
|
||||
-- @type DetectedTarget
|
||||
-- @field Wrapper.Object#Object object The target
|
||||
-- @field #boolean visible The target is visible
|
||||
-- @field #boolean type The target type is known
|
||||
-- @field #boolean distance Distance to the target is known
|
||||
@@ -1142,9 +920,9 @@ do -- Controller
|
||||
-- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN
|
||||
-- @return #boolean detected True if the target is detected.
|
||||
-- @return #boolean visible Has effect only if detected is true. True if the target is visible now.
|
||||
-- @return #boolean type Has effect only if detected is true. True if the target type is known.
|
||||
-- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known.
|
||||
-- @return #ModelTime lastTime Has effect only if visible is false. Last time when target was seen.
|
||||
-- @return #boolean type Has effect only if detected is true. True if the target type is known.
|
||||
-- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known.
|
||||
-- @return #Vec3 lastPos Has effect only if visible is false. Last position of the target when it was seen.
|
||||
-- @return #Vec3 lastVel Has effect only if visible is false. Last velocity of the target when it was seen.
|
||||
|
||||
@@ -1170,8 +948,7 @@ end -- Controller
|
||||
|
||||
do -- Unit
|
||||
|
||||
--- Unit.
|
||||
-- @type Unit
|
||||
--- @type Unit
|
||||
-- @extends #CoalitionObject
|
||||
-- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically.
|
||||
-- @field #Unit.Category Category
|
||||
@@ -1203,8 +980,8 @@ do -- Unit
|
||||
|
||||
--- Enum that stores aircraft refueling system types.
|
||||
-- @type Unit.RefuelingSystem
|
||||
-- @field BOOM_AND_RECEPTACLE Tanker with a boom.
|
||||
-- @field PROBE_AND_DROGUE Tanker with a probe.
|
||||
-- @field BOOM_AND_RECEPTACLE
|
||||
-- @field PROBE_AND_DROGUE
|
||||
|
||||
--- Enum that stores sensor types.
|
||||
-- @type Unit.SensorType
|
||||
@@ -1286,18 +1063,15 @@ do -- Unit
|
||||
-- @field #Distance detectionDistanceHRM detection distance for RCS=1m^2 in high-resolution mapping mode, nil if radar has no HRM
|
||||
-- @field #Unit.Radar.detectionDistanceAir detectionDistanceAir detection distance for RCS=1m^2 airborne target, nil if radar doesn't support air search
|
||||
|
||||
--- A radar.
|
||||
-- @type Unit.Radar.detectionDistanceAir
|
||||
--- @type Unit.Radar.detectionDistanceAir
|
||||
-- @field #Unit.Radar.detectionDistanceAir.upperHemisphere upperHemisphere
|
||||
-- @field #Unit.Radar.detectionDistanceAir.lowerHemisphere lowerHemisphere
|
||||
|
||||
--- A radar.
|
||||
-- @type Unit.Radar.detectionDistanceAir.upperHemisphere
|
||||
--- @type Unit.Radar.detectionDistanceAir.upperHemisphere
|
||||
-- @field #Distance headOn
|
||||
-- @field #Distance tailOn
|
||||
|
||||
--- A radar.
|
||||
-- @type Unit.Radar.detectionDistanceAir.lowerHemisphere
|
||||
--- @type Unit.Radar.detectionDistanceAir.lowerHemisphere
|
||||
-- @field #Distance headOn
|
||||
-- @field #Distance tailOn
|
||||
|
||||
@@ -1513,42 +1287,6 @@ do -- Group
|
||||
|
||||
end -- Group
|
||||
|
||||
do -- StaticObject
|
||||
|
||||
--- Represents a static object.
|
||||
-- @type StaticObject
|
||||
-- @extends DCS#Object
|
||||
|
||||
--- Returns the static object.
|
||||
-- @function [parent=#StaticObject] getByName
|
||||
-- @param #string name Name of the static object.
|
||||
-- @return #StaticObject
|
||||
|
||||
StaticObject = {} --#StaticObject
|
||||
|
||||
end
|
||||
|
||||
do --Event
|
||||
|
||||
--- Event structure. Note that present fields depend on type of event.
|
||||
-- @type Event
|
||||
-- @field #number id Event ID.
|
||||
-- @field #number time Mission time in seconds.
|
||||
-- @field DCS#Unit initiator Unit initiating the event.
|
||||
-- @field DCS#Unit target Target unit.
|
||||
-- @field DCS#Airbase place Airbase.
|
||||
-- @field number subPlace Subplace. Unknown and often just 0.
|
||||
-- @field #string weapon_name Weapoin name.
|
||||
-- @field #number idx Mark ID.
|
||||
-- @field #number coalition Coalition ID.
|
||||
-- @field #number groupID Group ID, *e.g.* of group that added mark point.
|
||||
-- @field #string text Text, *e.g.* of mark point.
|
||||
-- @field DCS#Vec3 pos Position vector, *e.g.* of mark point.
|
||||
-- @field #string comment Comment, *e.g.* LSO score.
|
||||
|
||||
Event={} --#Event
|
||||
|
||||
end
|
||||
|
||||
do -- AI
|
||||
|
||||
@@ -1599,26 +1337,22 @@ do -- AI
|
||||
-- @field IR_POINTER
|
||||
-- @field LASER
|
||||
|
||||
---
|
||||
-- @type AI.Task.WaypointType
|
||||
--- @type AI.Task.WaypointType
|
||||
-- @field TAKEOFF
|
||||
-- @field TAKEOFF_PARKING
|
||||
-- @field TURNING_POINT
|
||||
-- @field TAKEOFF_PARKING_HOT
|
||||
-- @field LAND
|
||||
|
||||
---
|
||||
-- @type AI.Task.TurnMethod
|
||||
--- @type AI.Task.TurnMethod
|
||||
-- @field FLY_OVER_POINT
|
||||
-- @field FIN_POINT
|
||||
|
||||
---
|
||||
-- @type AI.Task.AltitudeType
|
||||
--- @type AI.Task.AltitudeType
|
||||
-- @field BARO
|
||||
-- @field RADIO
|
||||
|
||||
---
|
||||
-- @type AI.Task.VehicleFormation
|
||||
--- @type AI.Task.VehicleFormation
|
||||
-- @field OFF_ROAD
|
||||
-- @field ON_ROAD
|
||||
-- @field RANK
|
||||
@@ -1628,30 +1362,25 @@ do -- AI
|
||||
-- @field ECHELON_LEFT
|
||||
-- @field ECHELON_RIGHT
|
||||
|
||||
---
|
||||
-- @type AI.Option
|
||||
--- @type AI.Option
|
||||
-- @field #AI.Option.Air Air
|
||||
-- @field #AI.Option.Ground Ground
|
||||
-- @field #AI.Option.Naval Naval
|
||||
|
||||
---
|
||||
-- @type AI.Option.Air
|
||||
--- @type AI.Option.Air
|
||||
-- @field #AI.Option.Air.id id
|
||||
-- @field #AI.Option.Air.val val
|
||||
|
||||
---
|
||||
-- @type AI.Option.Ground
|
||||
--- @type AI.Option.Ground
|
||||
-- @field #AI.Option.Ground.id id
|
||||
-- @field #AI.Option.Ground.val val
|
||||
-- @field #AI.Option.Ground.mid mid
|
||||
-- @field #AI.Option.Ground.mval mval
|
||||
--
|
||||
-- @type AI.Option.Naval
|
||||
|
||||
--- @type AI.Option.Naval
|
||||
-- @field #AI.Option.Naval.id id
|
||||
-- @field #AI.Option.Naval.val val
|
||||
|
||||
---
|
||||
-- @type AI.Option.Air.id
|
||||
|
||||
--- @type AI.Option.Air.id
|
||||
-- @field NO_OPTION
|
||||
-- @field ROE
|
||||
-- @field REACTION_ON_THREAT
|
||||
@@ -1668,123 +1397,101 @@ do -- AI
|
||||
-- @field PROHIBIT_AG
|
||||
-- @field MISSILE_ATTACK
|
||||
-- @field PROHIBIT_WP_PASS_REPORT
|
||||
-- @field OPTION_RADIO_USAGE_CONTACT
|
||||
-- @field OPTION_RADIO_USAGE_ENGAGE
|
||||
-- @field OPTION_RADIO_USAGE_KILL
|
||||
-- @field JETT_TANKS_IF_EMPTY
|
||||
-- @field FORCED_ATTACK
|
||||
-- @field PREFER_VERTICAL
|
||||
-- @field ALLOW_FORMATION_SIDE_SWAP
|
||||
-- @field AI_RUNWAY_LINE_UP
|
||||
|
||||
--- @type AI.Option.Air.id.FORMATION
|
||||
-- @field LINE_ABREAST
|
||||
-- @field TRAIL
|
||||
-- @field WEDGE
|
||||
-- @field ECHELON_RIGHT
|
||||
-- @field ECHELON_LEFT
|
||||
-- @field FINGER_FOUR
|
||||
-- @field SPREAD_FOUR
|
||||
-- @field WW2_BOMBER_ELEMENT
|
||||
-- @field WW2_BOMBER_ELEMENT_HEIGHT
|
||||
-- @field WW2_FIGHTER_VIC
|
||||
-- @field HEL_WEDGE
|
||||
-- @field HEL_ECHELON
|
||||
-- @field HEL_FRONT
|
||||
-- @field HEL_COLUMN
|
||||
-- @field COMBAT_BOX
|
||||
-- @field JAVELIN_DOWN
|
||||
|
||||
---
|
||||
-- @type AI.Option.Air.val
|
||||
|
||||
--- @type AI.Option.Air.val
|
||||
-- @field #AI.Option.Air.val.ROE ROE
|
||||
-- @field #AI.Option.Air.val.REACTION_ON_THREAT REACTION_ON_THREAT
|
||||
-- @field #AI.Option.Air.val.RADAR_USING RADAR_USING
|
||||
-- @field #AI.Option.Air.val.FLARE_USING FLARE_USING
|
||||
|
||||
---
|
||||
-- @type AI.Option.Air.val.ROE
|
||||
--- @type AI.Option.Air.val.ROE
|
||||
-- @field WEAPON_FREE
|
||||
-- @field OPEN_FIRE_WEAPON_FREE
|
||||
-- @field OPEN_FIRE
|
||||
-- @field RETURN_FIRE
|
||||
-- @field WEAPON_HOLD
|
||||
|
||||
---
|
||||
-- @type AI.Option.Air.val.REACTION_ON_THREAT
|
||||
|
||||
--- @type AI.Option.Air.val.REACTION_ON_THREAT
|
||||
-- @field NO_REACTION
|
||||
-- @field PASSIVE_DEFENCE
|
||||
-- @field EVADE_FIRE
|
||||
-- @field BYPASS_AND_ESCAPE
|
||||
-- @field ALLOW_ABORT_MISSION
|
||||
|
||||
---
|
||||
-- @type AI.Option.Air.val.RADAR_USING
|
||||
--- @type AI.Option.Air.val.RADAR_USING
|
||||
-- @field NEVER
|
||||
-- @field FOR_ATTACK_ONLY
|
||||
-- @field FOR_SEARCH_IF_REQUIRED
|
||||
-- @field FOR_CONTINUOUS_SEARCH
|
||||
|
||||
---
|
||||
-- @type AI.Option.Air.val.FLARE_USING
|
||||
--- @type AI.Option.Air.val.FLARE_USING
|
||||
-- @field NEVER
|
||||
-- @field AGAINST_FIRED_MISSILE
|
||||
-- @field WHEN_FLYING_IN_SAM_WEZ
|
||||
-- @field WHEN_FLYING_NEAR_ENEMIES
|
||||
|
||||
---
|
||||
-- @type AI.Option.Air.val.ECM_USING
|
||||
|
||||
--- @type AI.Option.Air.val.ECM_USING
|
||||
-- @field NEVER_USE
|
||||
-- @field USE_IF_ONLY_LOCK_BY_RADAR
|
||||
-- @field USE_IF_DETECTED_LOCK_BY_RADAR
|
||||
-- @field ALWAYS_USE
|
||||
|
||||
---
|
||||
-- @type AI.Option.Air.val.MISSILE_ATTACK
|
||||
|
||||
--- @type AI.Option.Air.val.MISSILE_ATTACK
|
||||
-- @field MAX_RANGE
|
||||
-- @field NEZ_RANGE
|
||||
-- @field HALF_WAY_RMAX_NEZ
|
||||
-- @field TARGET_THREAT_EST
|
||||
-- @field RANDOM_RANGE
|
||||
|
||||
---
|
||||
-- @type AI.Option.Ground.id
|
||||
|
||||
--- @type AI.Option.Ground.id
|
||||
-- @field NO_OPTION
|
||||
-- @field ROE @{#AI.Option.Ground.val.ROE}
|
||||
-- @field FORMATION
|
||||
-- @field DISPERSE_ON_ATTACK true or false
|
||||
-- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE}
|
||||
-- @field ENGAGE_AIR_WEAPONS
|
||||
-- @field AC_ENGAGEMENT_RANGE_RESTRICTION
|
||||
-- @field EVASION_OF_ARM
|
||||
|
||||
---
|
||||
-- @type AI.Option.Ground.mid -- Moose added
|
||||
-- @field RESTRICT_AAA_MIN 27
|
||||
-- @field RESTRICT_AAA_MAX 29
|
||||
-- @field RESTRICT_TARGETS @{#AI.Option.Ground.mval.ENGAGE_TARGETS} 28
|
||||
|
||||
---
|
||||
-- @type AI.Option.Ground.val
|
||||
--- @type AI.Option.Ground.val
|
||||
-- @field #AI.Option.Ground.val.ROE ROE
|
||||
-- @field #AI.Option.Ground.val.ALARM_STATE ALARM_STATE
|
||||
-- @field #AI.Option.Ground.val.ENGAGE_TARGETS RESTRICT_TARGETS
|
||||
|
||||
---
|
||||
-- @type AI.Option.Ground.val.ROE
|
||||
--- @type AI.Option.Ground.val.ROE
|
||||
-- @field OPEN_FIRE
|
||||
-- @field RETURN_FIRE
|
||||
-- @field WEAPON_HOLD
|
||||
|
||||
---
|
||||
-- @type AI.Option.Ground.mval -- Moose added
|
||||
-- @field #AI.Option.Ground.mval.ENGAGE_TARGETS ENGAGE_TARGETS
|
||||
|
||||
---
|
||||
-- @type AI.Option.Ground.mval.ENGAGE_TARGETS -- Moose added
|
||||
-- @field ANY_TARGET -- 0
|
||||
-- @field AIR_UNITS_ONLY -- 1
|
||||
-- @field GROUND_UNITS_ONLY -- 2
|
||||
|
||||
---
|
||||
-- @type AI.Option.Ground.val.ALARM_STATE
|
||||
--- @type AI.Option.Ground.val.ALARM_STATE
|
||||
-- @field AUTO
|
||||
-- @field GREEN
|
||||
-- @field RED
|
||||
|
||||
---
|
||||
-- @type AI.Option.Naval.id
|
||||
--- @type AI.Option.Naval.id
|
||||
-- @field NO_OPTION
|
||||
-- @field ROE
|
||||
|
||||
---
|
||||
-- @type AI.Option.Naval.val
|
||||
--- @type AI.Option.Naval.val
|
||||
-- @field #AI.Option.Naval.val.ROE ROE
|
||||
|
||||
---
|
||||
-- @type AI.Option.Naval.val.ROE
|
||||
--- @type AI.Option.Naval.val.ROE
|
||||
-- @field OPEN_FIRE
|
||||
-- @field RETURN_FIRE
|
||||
-- @field WEAPON_HOLD
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
--- **Functional** - AI CSAR system.
|
||||
--
|
||||
-- ===
|
||||
--- **Functional** - AI CSAR system
|
||||
--
|
||||
-- ## Features:
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * Send out helicopters to downed pilots
|
||||
-- * Rescues players and AI alike
|
||||
@@ -11,18 +9,17 @@
|
||||
-- * Dedicated MASH zone
|
||||
-- * Some FSM functions to include in your mission scripts
|
||||
-- * Limit number of available helos
|
||||
-- * SRS voice output via TTS or soundfiles
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Functional/AICSAR).
|
||||
--
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/CSR-001%20-%20Basics).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist**
|
||||
-- Last Update July 2025
|
||||
-- ### Author: **applevangelist**
|
||||
-- Last Update April 2022
|
||||
--
|
||||
-- ===
|
||||
-- @module Functional.AICSAR
|
||||
@@ -52,13 +49,6 @@
|
||||
-- @field Core.Set#SET_CLIENT playerset Track if alive heli pilots are available.
|
||||
-- @field #boolean limithelos limit available number of helos going on mission (defaults to true)
|
||||
-- @field #number helonumber number of helos available (default: 3)
|
||||
-- @field Utilities.FiFo#FIFO PilotStore
|
||||
-- @field #number Altitude Default altitude setting for the helicopter FLIGHTGROUP 1500ft.
|
||||
-- @field #number Speed Default speed setting for the helicopter FLIGHTGROUP is 100kn.
|
||||
-- @field #boolean UseEventEject In case Event LandingAfterEjection isn't working, use set this to true.
|
||||
-- @field #number Delay In case of UseEventEject wait this long until we spawn a landed pilot.
|
||||
-- @field #boolean UseRescueZone If true, use a rescue zone and not the max distance to FARP/MASH
|
||||
-- @field Core.Zone#ZONE_RADIUS RescueZone Use this zone as operational area for the AICSAR instance.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@@ -84,7 +74,7 @@
|
||||
-- -- @param #string Alias Name of this instance.
|
||||
-- -- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral"
|
||||
-- -- @param #string Pilottemplate Pilot template name.
|
||||
-- -- @param #string Helotemplate Helicopter template name. Set the template to "cold start". Hueys work best.
|
||||
-- -- @param #string Helotemplate Helicopter template name.
|
||||
-- -- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
|
||||
-- -- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
|
||||
-- local my_aicsar=AICSAR:New("Luftrettung",coalition.side.BLUE,"Downed Pilot","Rescue Helo",AIRBASE:FindByName("Test FARP"),ZONE:New("MASH"))
|
||||
@@ -95,11 +85,10 @@
|
||||
-- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m
|
||||
-- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true.
|
||||
-- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true.
|
||||
-- my_aicsar.limithelos -- limit available number of helos going on mission (defaults to true)
|
||||
-- my_aicsarlimithelos -- limit available number of helos going on mission (defaults to true)
|
||||
-- my_aicsar.helonumber -- number of helos available (default: 3)
|
||||
-- my_aicsar.verbose -- boolean, set to `true`for message output on-screen
|
||||
--
|
||||
-- ## Radio output options
|
||||
-- ## Radio options
|
||||
--
|
||||
-- Radio messages, soundfile names and (for SRS) lengths are defined in three enumerators, so you can customize, localize messages and soundfiles to your liking:
|
||||
--
|
||||
@@ -109,7 +98,7 @@
|
||||
-- EN = {
|
||||
-- INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
|
||||
-- INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
|
||||
-- PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ", -- note that this will be appended with the position in MGRS
|
||||
-- PILOTDOWN = "Pilot down at ", -- note that this will be appended with the position
|
||||
-- PILOTKIA = "Pilot KIA!",
|
||||
-- HELODOWN = "CSAR Helo Down!",
|
||||
-- PILOTRESCUED = "Pilot rescued!",
|
||||
@@ -145,31 +134,8 @@
|
||||
-- },
|
||||
-- }
|
||||
--
|
||||
-- ## Radio output via SRS and Text-To-Speech (TTS)
|
||||
--
|
||||
-- Radio output can be done via SRS and Text-To-Speech. No extra sound files required!
|
||||
-- [Initially, Have a look at the guide on setting up SRS TTS for Moose](https://github.com/FlightControl-Master/MOOSE_GUIDES/blob/master/documents/Moose%20TTS%20Setup%20Guide.pdf).
|
||||
-- The text from the `AICSAR.Messages` table above is converted on the fly to an .ogg-file, which is then played back via SRS on the selected frequency and mdulation.
|
||||
-- Hint - the small black window popping up shortly is visible in Single-Player only.
|
||||
--
|
||||
-- To set up AICSAR for SRS TTS output, add e.g. the following to your script:
|
||||
--
|
||||
-- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required)
|
||||
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\google.json")
|
||||
--
|
||||
-- -- alternatively for MS Desktop TTS (voices need to be installed locally first!)
|
||||
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female")
|
||||
--
|
||||
-- -- define a different voice for the downed pilot(s)
|
||||
-- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male")
|
||||
--
|
||||
-- -- define another voice for the operator
|
||||
-- my_aicsar:SetOperatorTTSVoice(MSRS.Voices.Google.Standard.en_GB_Standard_A,"en-GB","female")
|
||||
--
|
||||
-- ## Radio output via preproduced soundfiles
|
||||
--
|
||||
-- The easiest way to add a soundfile to your mission is to use the "Sound to..." trigger in the mission editor. This will effectively
|
||||
-- save your sound file inside of the .miz mission file. [Example soundfiles are located on github](https://github.com/FlightControl-Master/MOOSE_SOUND/tree/master/AICSAR)
|
||||
-- save your sound file inside of the .miz mission file.
|
||||
--
|
||||
-- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version:
|
||||
--
|
||||
@@ -179,7 +145,7 @@
|
||||
--
|
||||
-- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so:
|
||||
--
|
||||
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",270,radio.modulation.AM,nil,5002)
|
||||
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,5002)
|
||||
--
|
||||
-- or
|
||||
--
|
||||
@@ -193,7 +159,7 @@
|
||||
-- @field #AICSAR
|
||||
AICSAR = {
|
||||
ClassName = "AICSAR",
|
||||
version = "0.1.18",
|
||||
version = "0.0.8",
|
||||
lid = "",
|
||||
coalition = coalition.side.BLUE,
|
||||
template = "",
|
||||
@@ -205,7 +171,7 @@ AICSAR = {
|
||||
pilotqueue = {},
|
||||
pilotindex = 0,
|
||||
helos = {},
|
||||
verbose = false,
|
||||
verbose = true,
|
||||
rescuezoneradius = 200,
|
||||
rescued = {},
|
||||
autoonoff = true,
|
||||
@@ -226,20 +192,6 @@ AICSAR = {
|
||||
helonumber = 3,
|
||||
gettext = nil,
|
||||
locale ="en", -- default text language
|
||||
SRSTTSRadio = false,
|
||||
SRSGoogle = false,
|
||||
SRSQ = nil,
|
||||
SRSPilot = nil,
|
||||
SRSPilotVoice = false,
|
||||
SRSOperator = nil,
|
||||
SRSOperatorVoice = false,
|
||||
PilotStore = nil,
|
||||
Speed = 100,
|
||||
Altitude = 1500,
|
||||
UseEventEject = false,
|
||||
Delay = 100,
|
||||
UseRescueZone = false,
|
||||
RescueZone = nil,
|
||||
}
|
||||
|
||||
-- TODO Messages
|
||||
@@ -249,7 +201,7 @@ AICSAR.Messages = {
|
||||
EN = {
|
||||
INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
|
||||
INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
|
||||
PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ",
|
||||
PILOTDOWN = "Pilot down at ",
|
||||
PILOTKIA = "Pilot KIA!",
|
||||
HELODOWN = "CSAR Helo Down!",
|
||||
PILOTRESCUED = "Pilot rescued!",
|
||||
@@ -258,7 +210,7 @@ AICSAR.Messages = {
|
||||
DE = {
|
||||
INITIALOK = "Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!",
|
||||
INITIALNOTOK = "Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!",
|
||||
PILOTDOWN = "Mayday, mayday, mayday! Pilot abgestürzt: ",
|
||||
PILOTDOWN = "Pilot abgestürzt: ",
|
||||
PILOTKIA = "Pilot gefallen!",
|
||||
HELODOWN = "CSAR Hubschrauber verloren!",
|
||||
PILOTRESCUED = "Pilot gerettet!",
|
||||
@@ -308,9 +260,8 @@ AICSAR.RadioLength = {
|
||||
-- @param #string Helotemplate Helicopter template name.
|
||||
-- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
|
||||
-- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
|
||||
-- @param #number Helonumber (Optional) Max number of alive Ai Helos at the same time. Defaults to 3.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Helonumber)
|
||||
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New())
|
||||
|
||||
@@ -352,15 +303,10 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Hel
|
||||
self.farp = FARP
|
||||
self.farpzone = MASHZone
|
||||
self.playerset = SET_CLIENT:New():FilterActive(true):FilterCategories("helicopter"):FilterStart()
|
||||
self.UseEventEject = false
|
||||
self.Delay = 300
|
||||
|
||||
-- Radio
|
||||
self.SRS = nil
|
||||
self.SRSRadio = false
|
||||
self.SRSTTSRadio = false
|
||||
self.SRSGoogle = false
|
||||
self.SRSQ = nil
|
||||
self.SRSFrequency = 243
|
||||
self.SRSPath = "\\"
|
||||
self.SRSModulation = radio.modulation.AM
|
||||
@@ -378,7 +324,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Hel
|
||||
|
||||
-- limit number of available helos at the same time
|
||||
self.limithelos = true
|
||||
self.helonumber = Helonumber or 3
|
||||
self.helonumber = 3
|
||||
|
||||
-- localization
|
||||
self:InitLocalization()
|
||||
@@ -386,9 +332,6 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Hel
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
|
||||
|
||||
--Pilot Store
|
||||
self.PilotStore = FIFO:New()
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
@@ -398,15 +341,12 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Hel
|
||||
self:AddTransition("*", "Status", "*") -- CSAR status update.
|
||||
self:AddTransition("*", "PilotDown", "*") -- Pilot down
|
||||
self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo
|
||||
self:AddTransition("*", "PilotUnloaded", "*") -- Pilot Unloaded from helo
|
||||
self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued
|
||||
self:AddTransition("*", "PilotKIA", "*") -- Pilot dead
|
||||
self:AddTransition("*", "HeloDown", "*") -- Helo dead
|
||||
self:AddTransition("*", "HeloOnDuty", "*") -- Helo spawnd
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
self:HandleEvent(EVENTS.LandingAfterEjection,self._EventHandler)
|
||||
self:HandleEvent(EVENTS.Ejection,self._EjectEventHandler)
|
||||
self:HandleEvent(EVENTS.LandingAfterEjection)
|
||||
|
||||
self:__Start(math.random(2,5))
|
||||
|
||||
@@ -460,17 +400,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Hel
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string PilotName
|
||||
|
||||
--- On after "PilotUnloaded" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotUnloaded
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param Ops.OpsGroup#OPSGROUP OpsGroup
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On after "PilotKIA" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotKIA
|
||||
@@ -479,14 +409,6 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Hel
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On after "HeloOnDuty" event.
|
||||
-- @function [parent=#AICSAR] OnAfterHeloOnDuty
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Wrapper.Group#GROUP Helo Helo group object
|
||||
|
||||
--- On after "HeloDown" event.
|
||||
-- @function [parent=#AICSAR] OnAfterHeloDown
|
||||
-- @param #AICSAR self
|
||||
@@ -529,37 +451,25 @@ function AICSAR:InitLocalization()
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Use a defined zone as area of operation and not the distance to FARP.
|
||||
-- @param #AICSAR self
|
||||
-- @param Core.Zone#ZONE Zone The operational zone to use. Downed pilots in this area will be rescued. Can be any known #ZONE type.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetUsingRescueZone(Zone)
|
||||
self.UseRescueZone = true
|
||||
self.RescueZone = Zone
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Switch sound output on and use SRS output for sound files.
|
||||
--- [User] Switch sound output on and use SRS
|
||||
-- @param #AICSAR self
|
||||
-- @param #boolean OnOff Switch on (true) or off (false).
|
||||
-- @param #string Path Path to your SRS Server External Audio Component, e.g. "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\\\ExternalAudio"
|
||||
-- @param #number Frequency (Optional) Defaults to 243 (guard)
|
||||
-- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM
|
||||
-- @param #string SoundPath (Optional) Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor.
|
||||
-- @param #number Port (Optional) Port of the SRS, defaults to 5002.
|
||||
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone"
|
||||
-- @param #number Frequency Defaults to 243 (guard)
|
||||
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM
|
||||
-- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor.
|
||||
-- @param #number Port Port of the SRS, defaults to 5002.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
|
||||
self:T(self.lid .. "SetSRSRadio")
|
||||
self:T(self.lid .. "SetSRSRadio to "..tostring(OnOff))
|
||||
self.SRSRadio = OnOff and true
|
||||
self.SRSTTSRadio = false
|
||||
self.SRSFrequency = Frequency or 243
|
||||
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
|
||||
self.SRS:SetLabel("ACSR")
|
||||
self.SRS:SetCoalition(self.coalition)
|
||||
self.SRSPath = Path or "c:\\"
|
||||
self.SRSModulation = Modulation or radio.modulation.AM
|
||||
local soundpath = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT" -- defaults to "l10n/DEFAULT/", i.e. add messages by "Sound to..." in the ME
|
||||
self.SRSSoundPath = SoundPath or soundpath
|
||||
self.SRSPort = Port or MSRS.port or 5002
|
||||
self.SRSPort = Port or 5002
|
||||
if OnOff then
|
||||
self.SRS = MSRS:New(Path,Frequency,Modulation)
|
||||
self.SRS:SetPort(self.SRSPort)
|
||||
@@ -567,114 +477,11 @@ function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Switch sound output on and use SRS-TTS output. The voice will be used across all outputs, unless you define an extra voice for downed pilots and/or the operator.
|
||||
-- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()`
|
||||
-- @param #AICSAR self
|
||||
-- @param #boolean OnOff Switch on (true) or off (false).
|
||||
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio"
|
||||
-- @param #number Frequency (Optional) Defaults to 243 (guard)
|
||||
-- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM
|
||||
-- @param #number Port (Optional) Port of the SRS, defaults to 5002.
|
||||
-- @param #string Voice (Optional) The voice to be used.
|
||||
-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB"
|
||||
-- @param #string Gender (Optional) The gender to be used, defaults to "male"
|
||||
-- @param #string GoogleCredentials (Optional) Path to google credentials
|
||||
-- @param #string Provider (Optional) TTS Provider to be used.
|
||||
-- @param #string Speaker (Optional) Use a specific speaker for a voice if Piper is used as provider (only Hound-TTS backend).
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Culture,Gender,GoogleCredentials,Provider,Speaker)
|
||||
self:T(self.lid .. "SetSRSTTSRadio")
|
||||
self.SRSTTSRadio = OnOff and true
|
||||
self.SRSRadio = false
|
||||
self.SRSFrequency = Frequency or 243
|
||||
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
|
||||
self.SRSModulation = Modulation or radio.modulation.AM
|
||||
self.SRSPort = Port or MSRS.port or 5002
|
||||
if OnOff then
|
||||
self.SRS = MSRS:New(self.SRSPath,Frequency,Modulation)
|
||||
self.SRS:SetPort(self.SRSPort)
|
||||
self.SRS:SetCoalition(self.coalition)
|
||||
self.SRS:SetLabel("ACSR")
|
||||
self.SRS:SetVoice(Voice)
|
||||
if Speaker then
|
||||
self.SRS:SetSpeakerPiper(Speaker)
|
||||
end
|
||||
self.SRS:SetCulture(Culture)
|
||||
self.SRS:SetGender(Gender)
|
||||
if GoogleCredentials and not Provider then
|
||||
self.SRS:SetProviderOptionsGoogle(GoogleCredentials,GoogleCredentials)
|
||||
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
|
||||
self.SRSGoogle = true
|
||||
end
|
||||
if Provider then
|
||||
self.SRS:SetProvider(Provider)
|
||||
end
|
||||
self.SRSQ = MSRSQUEUE:New(self.alias)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set SRS TTS Voice of downed pilot. `AICSAR:SetSRSTTSRadio()` needs to be set first!
|
||||
-- @param #AICSAR self
|
||||
-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft.
|
||||
-- Specific voices override culture and gender!
|
||||
-- @param #string Culture (Optional) The culture to be used, defaults to "en-US"
|
||||
-- @param #string Gender (Optional) The gender to be used, defaults to "male"
|
||||
-- @param #string Speaker (Optional) Use a specific speaker for a voice if Piper is used as provider (only Hound-TTS backend).
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetPilotTTSVoice(Voice,Culture,Gender,Speaker)
|
||||
self:T(self.lid .. "SetPilotTTSVoice")
|
||||
self.SRSPilotVoice = true
|
||||
self.SRSPilot = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation)
|
||||
self.SRSPilot:SetCoalition(self.coalition)
|
||||
self.SRSPilot:SetVoice(Voice)
|
||||
if Speaker then
|
||||
self.SRSPilot:SetSpeakerPiper(Speaker)
|
||||
end
|
||||
self.SRSPilot:SetCulture(Culture or "en-US")
|
||||
self.SRSPilot:SetGender(Gender or "male")
|
||||
self.SRSPilot:SetLabel("PILOT")
|
||||
if self.SRSGoogle then
|
||||
local poptions = self.SRS:GetProviderOptions(MSRS.Provider.GOOGLE) -- Sound.SRS#MSRS.ProviderOptions
|
||||
self.SRSPilot:SetProviderOptionsGoogle(poptions.credentials,poptions.key)
|
||||
self.SRSPilot:SetProvider(MSRS.Provider.GOOGLE)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set SRS TTS Voice of the rescue operator. `AICSAR:SetSRSTTSRadio()` needs to be set first!
|
||||
-- @param #AICSAR self
|
||||
-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft.
|
||||
-- Specific voices override culture and gender!
|
||||
-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB"
|
||||
-- @param #string Gender (Optional) The gender to be used, defaults to "female"
|
||||
-- @param #string Speaker (Optional) Use a specific speaker for a voice if Piper is used as provider (only Hound-TTS backend).
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetOperatorTTSVoice(Voice,Culture,Gender,Speaker)
|
||||
self:T(self.lid .. "SetOperatorTTSVoice")
|
||||
self.SRSOperatorVoice = true
|
||||
self.SRSOperator = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation)
|
||||
self.SRSOperator:SetCoalition(self.coalition)
|
||||
self.SRSOperator:SetVoice(Voice)
|
||||
if Speaker then
|
||||
self.SRSOperator:SetSpeakerPiper(Speaker)
|
||||
end
|
||||
self.SRSOperator:SetCulture(Culture or "en-GB")
|
||||
self.SRSOperator:SetGender(Gender or "female")
|
||||
self.SRSOperator:SetLabel("RESCUE")
|
||||
if self.SRSGoogle then
|
||||
local poptions = self.SRS:GetProviderOptions(MSRS.Provider.GOOGLE) -- Sound.SRS#MSRS.ProviderOptions
|
||||
self.SRSOperator:SetProviderOptionsGoogle(poptions.credentials,poptions.key)
|
||||
self.SRSOperator:SetProvider(MSRS.Provider.GOOGLE)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Switch sound output on and use normale (DCS) radio
|
||||
-- @param #AICSAR self
|
||||
-- @param #boolean OnOff Switch on (true) or off (false).
|
||||
-- @param #number Frequency(Optional) Defaults to 243 (guard).
|
||||
-- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM.
|
||||
-- @param #number Frequency Defaults to 243 (guard).
|
||||
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM.
|
||||
-- @param Wrapper.Group#GROUP Group The group to use as sending station.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetDCSRadio(OnOff,Frequency,Modulation,Group)
|
||||
@@ -710,110 +517,11 @@ function AICSAR:DCSRadioBroadcast(Soundfile,Duration,Subtitle)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Catch the ejection and save the pilot name
|
||||
-- @param #AICSAR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_EjectEventHandler(EventData)
|
||||
local _event = EventData -- Core.Event#EVENTDATA
|
||||
if _event.IniPlayerName then
|
||||
self.PilotStore:Push(_event.IniPlayerName)
|
||||
self:T(self.lid.."Pilot Ejected: ".._event.IniPlayerName)
|
||||
if self.UseEventEject then
|
||||
-- get position and spawn in a template pilot
|
||||
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
|
||||
local _country = _event.initiator:getCountry()
|
||||
local _coalition = coalition.getCountryCoalition( _country )
|
||||
--local data = UTILS.DeepCopy(EventData)
|
||||
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
|
||||
self:ScheduleOnce(self.Delay,self._DelayedSpawnPilot,self,_LandingPos,_coalition)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Spawn a pilot
|
||||
-- @param #AICSAR self
|
||||
-- @param Core.Point#COORDINATE _LandingPos Landing Postion
|
||||
-- @param #number _coalition Coalition side
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition)
|
||||
|
||||
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
|
||||
if self.UseRescueZone == true and self.RescueZone ~= nil then
|
||||
if self.RescueZone:IsCoordinateInZone(_LandingPos) then
|
||||
distancetofarp = self.maxdistance - 10
|
||||
else
|
||||
distancetofarp = self.maxdistance + 10
|
||||
end
|
||||
end
|
||||
-- Mayday Message
|
||||
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
|
||||
local text = ""
|
||||
local setting = {}
|
||||
setting.MGRS_Accuracy = self.MGRS_Accuracy
|
||||
local location = _LandingPos:ToStringMGRS(setting)
|
||||
local msgtxt = Text..location.."!"
|
||||
location = string.gsub(location,"MGRS ","")
|
||||
location = string.gsub(location,"%s+","")
|
||||
location = string.gsub(location,"([%a%d])","%1;") -- "0 5 1 "
|
||||
location = string.gsub(location,"0","zero")
|
||||
location = string.gsub(location,"9","niner")
|
||||
location = "MGRS;"..location
|
||||
if self.SRSGoogle then
|
||||
location = string.format("<say-as interpret-as='characters'>%s</say-as>",location)
|
||||
end
|
||||
text = Text .. location .. "!"
|
||||
local ttstext = Text .. location .. "! Repeat! "..location
|
||||
if _coalition == self.coalition then
|
||||
if self.verbose then
|
||||
MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
-- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog()
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
elseif self.SRSTTSRadio then
|
||||
if self.SRSPilotVoice then
|
||||
self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1)
|
||||
else
|
||||
self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- further processing
|
||||
if _coalition == self.coalition and distancetofarp <= self.maxdistance then
|
||||
-- in reach
|
||||
self:T(self.lid .. "Spawning new Pilot")
|
||||
self.pilotindex = self.pilotindex + 1
|
||||
local newpilot = SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template, self.pilotindex))
|
||||
newpilot:InitDelayOff()
|
||||
newpilot:OnSpawnGroup(
|
||||
function (grp)
|
||||
self.pilotqueue[self.pilotindex] = grp
|
||||
end
|
||||
)
|
||||
newpilot:SpawnFromCoordinate(_LandingPos)
|
||||
|
||||
self:__PilotDown(2,_LandingPos,true)
|
||||
elseif _coalition == self.coalition and distancetofarp > self.maxdistance then
|
||||
-- out of reach, apologies, too far off
|
||||
self:T(self.lid .. "Pilot out of reach")
|
||||
self:__PilotDown(2,_LandingPos,false)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Catch the landing after ejection and spawn a pilot in situ.
|
||||
-- @param #AICSAR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @param #boolean FromEject
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_EventHandler(EventData, FromEject)
|
||||
function AICSAR:OnEventLandingAfterEjection(EventData)
|
||||
self:T(self.lid .. "OnEventLandingAfterEjection ID=" .. EventData.id)
|
||||
|
||||
-- autorescue on off?
|
||||
@@ -823,44 +531,25 @@ function AICSAR:_EventHandler(EventData, FromEject)
|
||||
end
|
||||
end
|
||||
|
||||
if self.UseEventEject and (not FromEject) then return self end
|
||||
|
||||
local _event = EventData -- Core.Event#EVENTDATA
|
||||
-- get position and spawn in a template pilot
|
||||
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
|
||||
local _country = _event.initiator:getCountry()
|
||||
local _coalition = coalition.getCountryCoalition( _country )
|
||||
|
||||
|
||||
-- DONE: add distance check
|
||||
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
|
||||
if self.UseRescueZone == true and self.RescueZone ~= nil then
|
||||
if self.RescueZone:IsCoordinateInZone(_LandingPos) then
|
||||
distancetofarp = self.maxdistance - 10
|
||||
else
|
||||
distancetofarp = self.maxdistance + 10
|
||||
end
|
||||
end
|
||||
|
||||
-- Mayday Message
|
||||
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
|
||||
local text = ""
|
||||
local setting = {}
|
||||
setting.MGRS_Accuracy = self.MGRS_Accuracy
|
||||
local location = _LandingPos:ToStringMGRS(setting)
|
||||
local msgtxt = Text..location.."!"
|
||||
location = string.gsub(location,"MGRS ","")
|
||||
location = string.gsub(location,"%s+","")
|
||||
location = string.gsub(location,"([%a%d])","%1;") -- "0 5 1 "
|
||||
location = string.gsub(location,"0","zero")
|
||||
location = string.gsub(location,"9","niner")
|
||||
location = "MGRS;"..location
|
||||
if self.SRSGoogle then
|
||||
location = string.format("<say-as interpret-as='characters'>%s</say-as>",location)
|
||||
end
|
||||
text = Text .. location .. "!"
|
||||
local ttstext = Text .. location .. "! Repeat! "..location
|
||||
if _coalition == self.coalition then
|
||||
if self.verbose then
|
||||
MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
local setting = {}
|
||||
setting.MGRS_Accuracy = self.MGRS_Accuracy
|
||||
local location = _LandingPos:ToStringMGRS(setting)
|
||||
text = Text .. location .. "!"
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
@@ -868,12 +557,6 @@ function AICSAR:_EventHandler(EventData, FromEject)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
elseif self.SRSTTSRadio then
|
||||
if self.SRSPilotVoice then
|
||||
self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1)
|
||||
else
|
||||
self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -910,12 +593,6 @@ function AICSAR:_GetFlight()
|
||||
local newhelo = SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000))
|
||||
:InitDelayOff()
|
||||
:InitUnControlled(true)
|
||||
:OnSpawnGroup(
|
||||
function(Group)
|
||||
Group:OptionPreferVerticalLanding()
|
||||
self:__HeloOnDuty(1,Group)
|
||||
end
|
||||
)
|
||||
:Spawn()
|
||||
|
||||
local nhelo=FLIGHTGROUP:New(newhelo)
|
||||
@@ -936,18 +613,13 @@ function AICSAR:_InitMission(Pilot,Index)
|
||||
--local pilotset = SET_GROUP:New()
|
||||
--pilotset:AddGroup(Pilot)
|
||||
|
||||
-- Cargo transport assignment.
|
||||
-- Cargo transport assignment.
|
||||
local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone)
|
||||
--opstransport:SetVerbosity(3)
|
||||
|
||||
local helo = self:_GetFlight()
|
||||
-- inject reservation
|
||||
helo.AICSARReserved = true
|
||||
|
||||
helo:SetDefaultAltitude(self.Altitude or 1500)
|
||||
|
||||
helo:SetDefaultSpeed(self.Speed or 100)
|
||||
|
||||
-- Cargo transport assignment to first Huey group.
|
||||
helo:AddOpsTransport(opstransport)
|
||||
|
||||
@@ -960,28 +632,14 @@ function AICSAR:_InitMission(Pilot,Index)
|
||||
self:__HeloDown(2,Helo,Index)
|
||||
end
|
||||
|
||||
local function AICHeloUnloaded(Helo,OpsGroup)
|
||||
self:__PilotUnloaded(2,Helo,OpsGroup)
|
||||
end
|
||||
|
||||
function helo:OnAfterLoading(From,Event,To)
|
||||
AICPickedUp(helo,helo:GetCargoGroups(),Index)
|
||||
helo:__LoadingDone(5)
|
||||
function helo:OnAfterLoadingDone(From,Event,To)
|
||||
AICPickedUp(helo,helo:GetCargoGroups(),Index)
|
||||
end
|
||||
|
||||
function helo:OnAfterDead(From,Event,To)
|
||||
AICHeloDead(helo,Index)
|
||||
end
|
||||
|
||||
function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo)
|
||||
AICHeloUnloaded(helo,OpsGroupCargo)
|
||||
helo:__UnloadingDone(5)
|
||||
end
|
||||
|
||||
function helo:OnAfterLandAtAirbase(From,Event,To,airbase)
|
||||
helo:Despawn(2)
|
||||
end
|
||||
|
||||
self.helos[Index] = helo
|
||||
|
||||
return self
|
||||
@@ -992,7 +650,7 @@ end
|
||||
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
|
||||
-- @return #boolean outcome
|
||||
function AICSAR:_CheckInMashZone(Pilot)
|
||||
self:T(self.lid .. "_CheckInMashZone")
|
||||
self:T(self.lid .. "_CheckQueue")
|
||||
if Pilot:IsInZone(self.farpzone) then
|
||||
return true
|
||||
else
|
||||
@@ -1000,26 +658,6 @@ function AICSAR:_CheckInMashZone(Pilot)
|
||||
end
|
||||
end
|
||||
|
||||
--- [User] Set default helo speed. Note - AI might have other ideas. Defaults to 100kn.
|
||||
-- @param #AICSAR self
|
||||
-- @param #number Knots Speed in knots.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetDefaultSpeed(Knots)
|
||||
self:T(self.lid .. "SetDefaultSpeed")
|
||||
self.Speed = Knots or 100
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set default helo altitudeAGL. Note - AI might have other ideas. Defaults to 1500ft.
|
||||
-- @param #AICSAR self
|
||||
-- @param #number Feet AGL set in feet.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetDefaultAltitude(Feet)
|
||||
self:T(self.lid .. "SetDefaultAltitude")
|
||||
self.Altitude = Feet or 1500
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Check helo queue
|
||||
-- @param #AICSAR self
|
||||
-- @return #AICSAR self
|
||||
@@ -1032,9 +670,7 @@ function AICSAR:_CheckHelos()
|
||||
local name = helo:GetName()
|
||||
self:T("Helo group "..name.." in state "..state)
|
||||
if state == "Arrived" then
|
||||
--helo:__Stop(5)
|
||||
helo.OnAfterDead = nil
|
||||
helo:Despawn(35)
|
||||
helo:__Stop(5)
|
||||
self.helos[_index] = nil
|
||||
end
|
||||
else
|
||||
@@ -1058,14 +694,12 @@ end
|
||||
|
||||
--- [Internal] Check pilot queue for next mission
|
||||
-- @param #AICSAR self
|
||||
-- @param Ops.OpsGroup#OPSGROUP OpsGroup
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_CheckQueue(OpsGroup)
|
||||
function AICSAR:_CheckQueue()
|
||||
self:T(self.lid .. "_CheckQueue")
|
||||
for _index, _pilot in pairs(self.pilotqueue) do
|
||||
local classname = _pilot.ClassName and _pilot.ClassName or "NONE"
|
||||
local name = _pilot.GroupName and _pilot.GroupName or "NONE"
|
||||
local playername = "John Doe"
|
||||
local helocount = self:_CountHelos()
|
||||
--self:T("Looking at " .. classname .. " " .. name)
|
||||
-- find one w/o mission
|
||||
@@ -1073,18 +707,11 @@ function AICSAR:_CheckQueue(OpsGroup)
|
||||
local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP
|
||||
-- rescued?
|
||||
if self:_CheckInMashZone(_pilot) then
|
||||
self:T("Pilot" .. _pilot.GroupName .. " rescued!")
|
||||
if OpsGroup then
|
||||
--OpsGroup:Despawn(10)
|
||||
else
|
||||
_pilot:Destroy(true,10)
|
||||
end
|
||||
self:T("Pilot" .. _pilot.GroupName .. " rescued!")
|
||||
_pilot:Destroy(false)
|
||||
self.pilotqueue[_index] = nil
|
||||
self.rescued[_index] = true
|
||||
if self.PilotStore:Count() > 0 then
|
||||
playername = self.PilotStore:Pull()
|
||||
end
|
||||
self:__PilotRescued(2,playername)
|
||||
self:__PilotRescued(2)
|
||||
if flightgroup then
|
||||
flightgroup.AICSARReserved = false
|
||||
end
|
||||
@@ -1138,7 +765,7 @@ end
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterStatus(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
--self:_CheckQueue()
|
||||
self:_CheckQueue()
|
||||
self:_CheckHelos()
|
||||
self:__Status(30)
|
||||
return self
|
||||
@@ -1185,12 +812,6 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
elseif self.SRSTTSRadio then
|
||||
if self.SRSOperatorVoice then
|
||||
self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1)
|
||||
else
|
||||
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
|
||||
end
|
||||
end
|
||||
else
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale)
|
||||
@@ -1205,15 +826,8 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
elseif self.SRSTTSRadio then
|
||||
if self.SRSOperatorVoice then
|
||||
self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1)
|
||||
else
|
||||
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
|
||||
end
|
||||
end
|
||||
end
|
||||
self:_CheckQueue()
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1235,9 +849,7 @@ function AICSAR:onafterPilotKIA(From, Event, To)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
elseif self.SRSTTSRadio then
|
||||
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1261,12 +873,6 @@ function AICSAR:onafterHeloDown(From, Event, To, Helo, Index)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
elseif self.SRSTTSRadio then
|
||||
if self.SRSOperatorVoice then
|
||||
self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1)
|
||||
else
|
||||
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
|
||||
end
|
||||
end
|
||||
local findex = 0
|
||||
local fhname = Helo:GetName()
|
||||
@@ -1306,9 +912,8 @@ end
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #string PilotName
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotRescued(From, Event, To, PilotName)
|
||||
function AICSAR:onafterPilotRescued(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTRESCUED",self.locale)
|
||||
if self.verbose then
|
||||
@@ -1320,26 +925,10 @@ function AICSAR:onafterPilotRescued(From, Event, To, PilotName)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
elseif self.SRSTTSRadio then
|
||||
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotUnloaded
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param Ops.OpsGroup#OPSGROUP OpsGroup
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotUnloaded(From, Event, To, Helo, OpsGroup)
|
||||
self:T({From, Event, To})
|
||||
self:_CheckQueue(OpsGroup)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotPickedUp
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
@@ -1361,8 +950,6 @@ function AICSAR:onafterPilotPickedUp(From, Event, To, Helo, CargoTable, Index)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
elseif self.SRSTTSRadio then
|
||||
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
|
||||
end
|
||||
local findex = 0
|
||||
local fhname = Helo:GetName()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,806 +0,0 @@
|
||||
--- **Functional** -- Send a truck to supply artillery groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **AMMOTRUCK** - Send a truck to supply artillery groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- Demo missions can be found on [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Functional/AmmoTruck)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author : **applevangelist**
|
||||
--
|
||||
-- @module Functional.AmmoTruck
|
||||
-- @image Artillery.JPG
|
||||
--
|
||||
-- Last update: July 2023
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **AMMOTRUCK** class, extends Core.Fsm#FSM
|
||||
-- @type AMMOTRUCK
|
||||
-- @field #string ClassName Class Name
|
||||
-- @field #string lid Lid for log entries
|
||||
-- @field #string version Version string
|
||||
-- @field #string alias Alias name
|
||||
-- @field #boolean debug Debug flag
|
||||
-- @field #table trucklist List of (alive) #AMMOTRUCK.data trucks
|
||||
-- @field #table targetlist List of (alive) #AMMOTRUCK.data artillery
|
||||
-- @field #number coalition Coalition this is for
|
||||
-- @field Core.Set#SET_GROUP truckset SET of trucks
|
||||
-- @field Core.Set#SET_GROUP targetset SET of artillery
|
||||
-- @field #table remunitionqueue List of (alive) #AMMOTRUCK.data artillery to be reloaded
|
||||
-- @field #table waitingtargets List of (alive) #AMMOTRUCK.data artillery waiting
|
||||
-- @field #number ammothreshold Threshold (min) ammo before sending a truck
|
||||
-- @field #number remunidist Max distance trucks will go
|
||||
-- @field #number monitor Monitor interval in seconds
|
||||
-- @field #number unloadtime Unload time in seconds
|
||||
-- @field #number waitingtime Max waiting time in seconds
|
||||
-- @field #boolean routeonroad Route truck on road if true (default)
|
||||
-- @field #number reloads Number of reloads a single truck can do before he must return home
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Amateurs talk about tactics, but professionals study logistics.* - General Robert H Barrow, USMC
|
||||
--
|
||||
-- Simple Class to re-arm your artillery with trucks.
|
||||
--
|
||||
-- #AMMOTRUCK
|
||||
--
|
||||
-- * Controls a SET\_GROUP of trucks which will re-arm a SET\_GROUP of artillery groups when they run out of ammunition.
|
||||
--
|
||||
-- ## 1 The AMMOTRUCK concept
|
||||
--
|
||||
-- A SET\_GROUP of trucks which will re-arm a SET\_GROUP of artillery groups when they run out of ammunition. They will be based on a
|
||||
-- homebase and drive from there to the artillery groups and then back home.
|
||||
-- Trucks are the **only known in-game mechanic** to re-arm artillery and other units in DCS. Working units are e.g.: M-939 (blue), Ural-375 and ZIL-135 (both red).
|
||||
--
|
||||
-- ## 2 Set-up
|
||||
--
|
||||
-- Define a set of trucks and a set of artillery:
|
||||
--
|
||||
-- local truckset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Ammo Truck"):FilterStart()
|
||||
-- local ariset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Artillery"):FilterStart()
|
||||
--
|
||||
-- Create an AMMOTRUCK object to take care of the artillery using the trucks, with a homezone:
|
||||
--
|
||||
-- local ammotruck = AMMOTRUCK:New(truckset,ariset,coalition.side.BLUE,"Logistics",ZONE:FindByName("HomeZone")
|
||||
--
|
||||
-- ## 2 Options and their default values
|
||||
--
|
||||
-- ammotruck.ammothreshold = 5 -- send a truck when down to this many rounds
|
||||
-- ammotruck.remunidist = 20000 -- 20km - send trucks max this far from home
|
||||
-- ammotruck.unloadtime = 600 -- 10 minutes - min time to unload ammunition
|
||||
-- ammotruck.waitingtime = 1800 -- 30 mintes - wait max this long until remunition is done
|
||||
-- ammotruck.monitor = -60 -- 1 minute - AMMOTRUCK checks run every one minute
|
||||
-- ammotruck.routeonroad = true -- Trucks will **try** to drive on roads
|
||||
-- ammotruck.usearmygroup = false -- If true, will make use of ARMYGROUP in the background (if used in DEV branch)
|
||||
-- ammotruck.reloads = 5 -- Maxn re-arms a truck can do before he needs to go home and restock. Set to -1 for unlimited
|
||||
--
|
||||
-- ## 3 FSM Events to shape mission
|
||||
--
|
||||
-- Truck has been sent off:
|
||||
--
|
||||
-- function ammotruck:OnAfterRouteTruck(From, Event, To, Truckdata, Aridata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- Truck has arrived:
|
||||
--
|
||||
-- function ammotruck:OnAfterTruckArrived(From, Event, To, Truckdata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- Truck is unloading:
|
||||
--
|
||||
-- function ammotruck:OnAfterTruckUnloading(From, Event, To, Truckdata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- Truck is returning home:
|
||||
--
|
||||
-- function ammotruck:OnAfterTruckReturning(From, Event, To, Truckdata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- Truck is arrived at home:
|
||||
--
|
||||
-- function ammotruck:OnAfterTruckHome(From, Event, To, Truckdata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- @field #AMMOTRUCK
|
||||
AMMOTRUCK = {
|
||||
ClassName = "AMMOTRUCK",
|
||||
lid = "",
|
||||
version = "0.0.12",
|
||||
alias = "",
|
||||
debug = false,
|
||||
trucklist = {},
|
||||
targetlist = {},
|
||||
coalition = nil,
|
||||
truckset = nil,
|
||||
targetset = nil,
|
||||
remunitionqueue = {},
|
||||
waitingtargets = {},
|
||||
ammothreshold = 5,
|
||||
remunidist = 20000,
|
||||
monitor = -60,
|
||||
unloadtime = 600,
|
||||
waitingtime = 1800,
|
||||
routeonroad = true,
|
||||
reloads = 5,
|
||||
}
|
||||
|
||||
---
|
||||
-- @type AMMOTRUCK.State
|
||||
AMMOTRUCK.State = {
|
||||
IDLE = "idle",
|
||||
DRIVING = "driving",
|
||||
ARRIVED = "arrived",
|
||||
UNLOADING = "unloading",
|
||||
RETURNING = "returning",
|
||||
WAITING = "waiting",
|
||||
RELOADING = "reloading",
|
||||
OUTOFAMMO = "outofammo",
|
||||
REQUESTED = "requested",
|
||||
}
|
||||
|
||||
---
|
||||
--@type AMMOTRUCK.data
|
||||
--@field Wrapper.Group#GROUP group
|
||||
--@field #string name
|
||||
--@field #AMMOTRUCK.State statusquo
|
||||
--@field #number timestamp
|
||||
--@field #number ammo
|
||||
--@field Core.Point#COORDINATE coordinate
|
||||
--@field #string targetname
|
||||
--@field Wrapper.Group#GROUP targetgroup
|
||||
--@field Core.Point#COORDINATE targetcoordinate
|
||||
--@field #number reloads
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param Core.Set#SET_GROUP Truckset Set of truck groups
|
||||
-- @param Core.Set#SET_GROUP Targetset Set of artillery groups
|
||||
-- @param #number Coalition Coalition
|
||||
-- @param #string Alias Alias Name
|
||||
-- @param Core.Zone#ZONE Homezone Home, return zone for trucks
|
||||
-- @return #AMMOTRUCK self
|
||||
-- @usage
|
||||
-- Define a set of trucks and a set of artillery:
|
||||
-- local truckset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Ammo Truck"):FilterStart()
|
||||
-- local ariset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Artillery"):FilterStart()
|
||||
--
|
||||
-- Create an AMMOTRUCK object to take care of the artillery using the trucks, with a homezone:
|
||||
-- local ammotruck = AMMOTRUCK:New(truckset,ariset,coalition.side.BLUE,"Logistics",ZONE:FindByName("HomeZone")
|
||||
function AMMOTRUCK:New(Truckset,Targetset,Coalition,Alias,Homezone)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #AMMOTRUCK
|
||||
|
||||
self.truckset = Truckset -- Core.Set#SET_GROUP
|
||||
self.targetset = Targetset -- Core.Set#SET_GROUP
|
||||
self.coalition = Coalition -- #number
|
||||
self.alias = Alias -- #string
|
||||
self.debug = false
|
||||
self.remunitionqueue = {}
|
||||
self.trucklist = {}
|
||||
self.targetlist = {}
|
||||
self.ammothreshold = 5
|
||||
self.remunidist = 20000
|
||||
self.homezone = Homezone -- Core.Zone#ZONE
|
||||
self.waitingtime = 1800
|
||||
self.usearmygroup = false
|
||||
self.hasarmygroup = false
|
||||
|
||||
-- Log id.
|
||||
self.lid=string.format("AMMOTRUCK %s | %s | ", self.version, self.alias)
|
||||
|
||||
self:SetStartState("Stopped")
|
||||
self:AddTransition("Stopped", "Start", "Running")
|
||||
self:AddTransition("*", "Monitor", "*")
|
||||
self:AddTransition("*", "RouteTruck", "*")
|
||||
self:AddTransition("*", "TruckArrived", "*")
|
||||
self:AddTransition("*", "TruckUnloading", "*")
|
||||
self:AddTransition("*", "TruckReturning", "*")
|
||||
self:AddTransition("*", "TruckHome", "*")
|
||||
self:AddTransition("*", "Stop", "Stopped")
|
||||
|
||||
self:__Start(math.random(5,10))
|
||||
|
||||
self:I(self.lid .. "Started")
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the AMMOTRUCK and all its event handlers.
|
||||
-- @function [parent=#AMMOTRUCK] Stop
|
||||
-- @param #AMMOTRUCK self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the AMMOTRUCK and all its event handlers.
|
||||
-- @function [parent=#AMMOTRUCK] __Stop
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- On after "RouteTruck" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterRouteTruck
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
-- @param #AMMOTRUCK.data Artillery
|
||||
|
||||
--- On after "TruckUnloading" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterTruckUnloading
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
|
||||
--- On after "TruckReturning" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterTruckReturning
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
|
||||
--- On after "RouteTruck" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterRouteTruck
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
|
||||
--- On after "TruckHome" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterTruckHome
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckDrivingTrucks(dataset)
|
||||
self:T(self.lid .. " CheckDrivingTrucks")
|
||||
local data = dataset
|
||||
for _,_data in pairs (data) do
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
-- see if we arrived at destination
|
||||
local coord = truck.group:GetCoordinate()
|
||||
local tgtcoord = truck.targetcoordinate
|
||||
local dist = coord:Get2DDistance(tgtcoord)
|
||||
if dist <= 150 then
|
||||
-- arrived
|
||||
truck.statusquo = AMMOTRUCK.State.ARRIVED
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
truck.coordinate = coord
|
||||
self:__TruckArrived(1,truck)
|
||||
end
|
||||
-- still driving?
|
||||
local Tnow = timer.getAbsTime()
|
||||
if Tnow - truck.timestamp > 30 then
|
||||
local group = truck.group
|
||||
if self.usearmygroup then
|
||||
group = truck.group:GetGroup()
|
||||
end
|
||||
local currspeed = group:GetVelocityKMH()
|
||||
if truck.lastspeed then
|
||||
if truck.lastspeed == 0 and currspeed == 0 then
|
||||
self:T(truck.group:GetName().." Is not moving!")
|
||||
-- try and move it
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
if self.routeonroad then
|
||||
group:RouteGroundOnRoad(truck.targetcoordinate,30,2,"Vee")
|
||||
else
|
||||
group:RouteGroundTo(truck.targetcoordinate,30,"Vee",2)
|
||||
end
|
||||
end
|
||||
truck.lastspeed = currspeed
|
||||
else
|
||||
truck.lastspeed = currspeed
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
end
|
||||
self:I({truck=truck.group:GetName(),currspeed=currspeed,lastspeed=truck.lastspeed})
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:GetAmmoStatus(Group)
|
||||
local ammotot, shells, rockets, bombs, missiles, narti = Group:GetAmmunition()
|
||||
return rockets+missiles+narti
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckWaitingTargets(dataset)
|
||||
self:T(self.lid .. " CheckWaitingTargets")
|
||||
local data = dataset
|
||||
for _,_data in pairs (data) do
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
-- see how long we're waiting - maybe ammo truck is dead?
|
||||
local Tnow = timer.getAbsTime()
|
||||
local Tdiff = Tnow - truck.timestamp
|
||||
if Tdiff > self.waitingtime then
|
||||
local hasammo = self:GetAmmoStatus(truck.group)
|
||||
if hasammo <= self.ammothreshold then
|
||||
truck.statusquo = AMMOTRUCK.State.OUTOFAMMO
|
||||
else
|
||||
truck.statusquo = AMMOTRUCK.State.IDLE
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckReturningTrucks(dataset)
|
||||
self:T(self.lid .. " CheckReturningTrucks")
|
||||
local data = dataset
|
||||
local tgtcoord = self.homezone:GetCoordinate()
|
||||
local radius = self.homezone:GetRadius()
|
||||
for _,_data in pairs (data) do
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
-- see if we arrived at destination
|
||||
local coord = truck.group:GetCoordinate()
|
||||
local dist = coord:Get2DDistance(tgtcoord)
|
||||
self:T({name=truck.name,radius=radius,distance=dist})
|
||||
if dist <= radius then
|
||||
-- arrived
|
||||
truck.statusquo = AMMOTRUCK.State.IDLE
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
truck.coordinate = coord
|
||||
truck.reloads = self.reloads or 5
|
||||
self:__TruckHome(1,truck)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string name Artillery group name to find
|
||||
-- @return #AMMOTRUCK.data Data
|
||||
function AMMOTRUCK:FindTarget(name)
|
||||
self:T(self.lid .. " FindTarget")
|
||||
local data = nil
|
||||
local dataset = self.targetlist
|
||||
for _,_entry in pairs(dataset) do
|
||||
local entry = _entry -- #AMMOTRUCK.data
|
||||
if entry.name == name then
|
||||
data = entry
|
||||
break
|
||||
end
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string name Truck group name to find
|
||||
-- @return #AMMOTRUCK.data Data
|
||||
function AMMOTRUCK:FindTruck(name)
|
||||
self:T(self.lid .. " FindTruck")
|
||||
local data = nil
|
||||
local dataset = self.trucklist
|
||||
for _,_entry in pairs(dataset) do
|
||||
local entry = _entry -- #AMMOTRUCK.data
|
||||
if entry.name == name then
|
||||
data = entry
|
||||
break
|
||||
end
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckArrivedTrucks(dataset)
|
||||
self:T(self.lid .. " CheckArrivedTrucks")
|
||||
local data = dataset
|
||||
for _,_data in pairs (data) do
|
||||
-- set to unloading
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
truck.statusquo = AMMOTRUCK.State.UNLOADING
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
self:__TruckUnloading(2,truck)
|
||||
-- set target to reloading
|
||||
local aridata = self:FindTarget(truck.targetname) -- #AMMOTRUCK.data
|
||||
if aridata then
|
||||
aridata.statusquo = AMMOTRUCK.State.RELOADING
|
||||
aridata.timestamp = timer.getAbsTime()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckUnloadingTrucks(dataset)
|
||||
self:T(self.lid .. " CheckUnloadingTrucks")
|
||||
local data = dataset
|
||||
for _,_data in pairs (data) do
|
||||
-- check timestamp
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
local Tnow = timer.getAbsTime()
|
||||
local Tpassed = Tnow - truck.timestamp
|
||||
local hasammo = self:GetAmmoStatus(truck.targetgroup)
|
||||
if Tpassed > self.unloadtime and hasammo > self.ammothreshold then
|
||||
truck.statusquo = AMMOTRUCK.State.RETURNING
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
self:__TruckReturning(2,truck)
|
||||
-- set target to reloaded
|
||||
local aridata = self:FindTarget(truck.targetname) -- #AMMOTRUCK.data
|
||||
if aridata then
|
||||
aridata.statusquo = AMMOTRUCK.State.IDLE
|
||||
aridata.timestamp = timer.getAbsTime()
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckTargetsAlive()
|
||||
self:T(self.lid .. " CheckTargetsAlive")
|
||||
local arilist = self.targetlist
|
||||
for _,_ari in pairs(arilist) do
|
||||
local ari = _ari -- #AMMOTRUCK.data
|
||||
if ari.group and ari.group:IsAlive() then
|
||||
-- everything fine
|
||||
else
|
||||
-- ari dead
|
||||
self.targetlist[ari.name] = nil
|
||||
end
|
||||
end
|
||||
-- new arrivals?
|
||||
local aritable = self.targetset:GetSetObjects() --#table
|
||||
for _,_ari in pairs(aritable) do
|
||||
local ari = _ari -- Wrapper.Group#GROUP
|
||||
if ari and ari:IsAlive() and not self.targetlist[ari:GetName()] then
|
||||
local name = ari:GetName()
|
||||
local newari = {} -- #AMMOTRUCK.data
|
||||
newari.name = name
|
||||
newari.group = ari
|
||||
newari.statusquo = AMMOTRUCK.State.IDLE
|
||||
newari.timestamp = timer.getAbsTime()
|
||||
newari.coordinate = ari:GetCoordinate()
|
||||
local hasammo = self:GetAmmoStatus(ari)
|
||||
--newari.ammo = ari:GetAmmunition()
|
||||
newari.ammo = hasammo
|
||||
self.targetlist[name] = newari
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckTrucksAlive()
|
||||
self:T(self.lid .. " CheckTrucksAlive")
|
||||
local trucklist = self.trucklist
|
||||
for _,_truck in pairs(trucklist) do
|
||||
local truck = _truck -- #AMMOTRUCK.data
|
||||
if truck.group and truck.group:IsAlive() then
|
||||
-- everything fine
|
||||
else
|
||||
-- truck dead
|
||||
local tgtname = truck.targetname
|
||||
local targetdata = self:FindTarget(tgtname) -- #AMMOTRUCK.data
|
||||
if targetdata then
|
||||
if targetdata.statusquo ~= AMMOTRUCK.State.IDLE then
|
||||
targetdata.statusquo = AMMOTRUCK.State.IDLE
|
||||
end
|
||||
end
|
||||
self.trucklist[truck.name] = nil
|
||||
end
|
||||
end
|
||||
-- new arrivals?
|
||||
local trucktable = self.truckset:GetSetObjects() --#table
|
||||
for _,_truck in pairs(trucktable) do
|
||||
local truck = _truck -- Wrapper.Group#GROUP
|
||||
if truck and truck:IsAlive() and not self.trucklist[truck:GetName()] then
|
||||
local name = truck:GetName()
|
||||
local newtruck = {} -- #AMMOTRUCK.data
|
||||
newtruck.name = name
|
||||
newtruck.group = truck
|
||||
if self.hasarmygroup then
|
||||
-- is (not) already ARMYGROUP?
|
||||
if truck.ClassName and truck.ClassName == "GROUP" then
|
||||
local trucker = ARMYGROUP:New(truck)
|
||||
trucker:Activate()
|
||||
newtruck.group = trucker
|
||||
end
|
||||
end
|
||||
newtruck.statusquo = AMMOTRUCK.State.IDLE
|
||||
newtruck.timestamp = timer.getAbsTime()
|
||||
newtruck.coordinate = truck:GetCoordinate()
|
||||
newtruck.reloads = self.reloads or 5
|
||||
self.trucklist[name] = newtruck
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterStart(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
if ARMYGROUP and self.usearmygroup then
|
||||
self.hasarmygroup = true
|
||||
else
|
||||
self.hasarmygroup = false
|
||||
end
|
||||
if self.debug then
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("AMMOTRUCK")
|
||||
end
|
||||
self:CheckTargetsAlive()
|
||||
self:CheckTrucksAlive()
|
||||
self:__Monitor(-30)
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterMonitor(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
self:CheckTargetsAlive()
|
||||
self:CheckTrucksAlive()
|
||||
-- update ammo state
|
||||
local remunition = false
|
||||
local remunitionqueue = {}
|
||||
local waitingtargets = {}
|
||||
for _,_ari in pairs(self.targetlist) do
|
||||
local data = _ari -- #AMMOTRUCK.data
|
||||
if data.group and data.group:IsAlive() then
|
||||
data.ammo = self:GetAmmoStatus(data.group)
|
||||
data.timestamp = timer.getAbsTime()
|
||||
local text = string.format("Ari %s | Ammo %d | State %s",data.name,data.ammo,data.statusquo)
|
||||
self:T(text)
|
||||
if data.ammo <= self.ammothreshold and (data.statusquo == AMMOTRUCK.State.IDLE or data.statusquo == AMMOTRUCK.State.OUTOFAMMO) then
|
||||
-- add to remu queue
|
||||
data.statusquo = AMMOTRUCK.State.OUTOFAMMO
|
||||
remunitionqueue[#remunitionqueue+1] = data
|
||||
remunition = true
|
||||
elseif data.statusquo == AMMOTRUCK.State.WAITING then
|
||||
waitingtargets[#waitingtargets+1] = data
|
||||
end
|
||||
else
|
||||
self.targetlist[data.name] = nil
|
||||
end
|
||||
end
|
||||
-- sort trucks in buckets
|
||||
local idletrucks = {}
|
||||
local drivingtrucks = {}
|
||||
local unloadingtrucks = {}
|
||||
local arrivedtrucks = {}
|
||||
local returningtrucks = {}
|
||||
local found = false
|
||||
for _,_truckdata in pairs(self.trucklist) do
|
||||
local data = _truckdata -- #AMMOTRUCK.data
|
||||
if data.group and data.group:IsAlive() then
|
||||
-- check state
|
||||
local text = string.format("Truck %s | State %s",data.name,data.statusquo)
|
||||
self:T(text)
|
||||
if data.statusquo == AMMOTRUCK.State.IDLE then
|
||||
idletrucks[#idletrucks+1] = data
|
||||
found = true
|
||||
elseif data.statusquo == AMMOTRUCK.State.DRIVING then
|
||||
drivingtrucks[#drivingtrucks+1] = data
|
||||
elseif data.statusquo == AMMOTRUCK.State.ARRIVED then
|
||||
arrivedtrucks[#arrivedtrucks+1] = data
|
||||
elseif data.statusquo == AMMOTRUCK.State.UNLOADING then
|
||||
unloadingtrucks[#unloadingtrucks+1] = data
|
||||
elseif data.statusquo == AMMOTRUCK.State.RETURNING then
|
||||
returningtrucks[#returningtrucks+1] = data
|
||||
if data.reloads > 0 or data.reloads == -1 then
|
||||
idletrucks[#idletrucks+1] = data
|
||||
found = true
|
||||
end
|
||||
end
|
||||
else
|
||||
self.truckset[data.name] = nil
|
||||
end
|
||||
end
|
||||
-- see if we can/need route one
|
||||
local n=0
|
||||
if found and remunition then
|
||||
-- match
|
||||
--local match = false
|
||||
for _,_truckdata in pairs(idletrucks) do
|
||||
local truckdata = _truckdata -- #AMMOTRUCK.data
|
||||
local truckcoord = truckdata.group:GetCoordinate() -- Core.Point#COORDINATE
|
||||
for _,_aridata in pairs(remunitionqueue) do
|
||||
local aridata = _aridata -- #AMMOTRUCK.data
|
||||
local aricoord = aridata.coordinate
|
||||
local distance = truckcoord:Get2DDistance(aricoord)
|
||||
if distance <= self.remunidist and aridata.statusquo == AMMOTRUCK.State.OUTOFAMMO and n <= #idletrucks then
|
||||
n = n + 1
|
||||
aridata.statusquo = AMMOTRUCK.State.REQUESTED
|
||||
self:__RouteTruck(n*5,truckdata,aridata)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check driving trucks
|
||||
if #drivingtrucks > 0 then
|
||||
self:CheckDrivingTrucks(drivingtrucks)
|
||||
end
|
||||
|
||||
-- check arrived trucks
|
||||
if #arrivedtrucks > 0 then
|
||||
self:CheckArrivedTrucks(arrivedtrucks)
|
||||
end
|
||||
|
||||
-- check unloading trucks
|
||||
if #unloadingtrucks > 0 then
|
||||
self:CheckUnloadingTrucks(unloadingtrucks)
|
||||
end
|
||||
|
||||
-- check returningtrucks trucks
|
||||
if #returningtrucks > 0 then
|
||||
self:CheckReturningTrucks(returningtrucks)
|
||||
end
|
||||
|
||||
-- check waiting targets
|
||||
if #waitingtargets > 0 then
|
||||
self:CheckWaitingTargets(waitingtargets)
|
||||
end
|
||||
|
||||
self:__Monitor(self.monitor)
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #AMMOTRUCK.data Truckdata
|
||||
-- @param #AMMOTRUCK.data Aridata
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterRouteTruck(From, Event, To, Truckdata, Aridata)
|
||||
self:T({From, Event, To, Truckdata.name, Aridata.name})
|
||||
local truckdata = Truckdata -- #AMMOTRUCK.data
|
||||
local aridata = Aridata -- #AMMOTRUCK.data
|
||||
local tgtgrp = aridata.group
|
||||
local tgtzone = ZONE_GROUP:New(aridata.name,tgtgrp,30)
|
||||
local tgtcoord = tgtzone:GetRandomCoordinate(15)
|
||||
if self.hasarmygroup then
|
||||
local mission = AUFTRAG:NewONGUARD(tgtcoord)
|
||||
local oldmission = truckdata.group:GetMissionCurrent()
|
||||
if oldmission then oldmission:Cancel() end
|
||||
mission:SetTime(5)
|
||||
mission:SetTeleport(false)
|
||||
truckdata.group:AddMission(mission)
|
||||
elseif self.routeonroad then
|
||||
truckdata.group:RouteGroundOnRoad(tgtcoord,30)
|
||||
else
|
||||
truckdata.group:RouteGroundTo(tgtcoord,30)
|
||||
end
|
||||
truckdata.statusquo = AMMOTRUCK.State.DRIVING
|
||||
truckdata.targetgroup = tgtgrp
|
||||
truckdata.targetname = aridata.name
|
||||
truckdata.targetcoordinate = tgtcoord
|
||||
aridata.statusquo = AMMOTRUCK.State.WAITING
|
||||
aridata.timestamp = timer.getAbsTime()
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #AMMOTRUCK.data Truckdata
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterTruckUnloading(From, Event, To, Truckdata)
|
||||
local m = MESSAGE:New("Truck "..Truckdata.name.." unloading!",15,"AmmoTruck"):ToCoalitionIf(self.coalition,self.debug)
|
||||
local truck = Truckdata -- Functional.AmmoTruck#AMMOTRUCK.data
|
||||
local coord = truck.group:GetCoordinate()
|
||||
local heading = truck.group:GetHeading()
|
||||
heading = heading < 180 and (360-heading) or (heading - 180)
|
||||
local cid = self.coalition == coalition.side.BLUE and country.id.USA or country.id.RUSSIA
|
||||
cid = self.coalition == coalition.side.NEUTRAL and country.id.UN_PEACEKEEPERS or cid
|
||||
|
||||
local ammo = {}
|
||||
for i=1,5 do
|
||||
ammo[i] = SPAWNSTATIC:NewFromType("ammo_cargo","Cargos",cid)
|
||||
:InitCoordinate(coord:Translate((15+((i-1)*4)),heading))
|
||||
:Spawn(0,"AmmoCrate-"..math.random(1,10000))
|
||||
end
|
||||
|
||||
local function destroyammo(ammo)
|
||||
for _,_crate in pairs(ammo) do
|
||||
_crate:Destroy(false)
|
||||
end
|
||||
end
|
||||
|
||||
local scheduler = SCHEDULER:New(nil,destroyammo,{ammo},self.waitingtime)
|
||||
|
||||
-- one reload less
|
||||
if truck.reloads ~= -1 then
|
||||
truck.reloads = truck.reloads - 1
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterTruckReturning(From, Event, To, Truck)
|
||||
self:T({From, Event, To, Truck.name})
|
||||
-- route home
|
||||
local truckdata = Truck -- #AMMOTRUCK.data
|
||||
local tgtzone = self.homezone
|
||||
local tgtcoord = tgtzone:GetRandomCoordinate()
|
||||
if self.hasarmygroup then
|
||||
local mission = AUFTRAG:NewONGUARD(tgtcoord)
|
||||
local oldmission = truckdata.group:GetMissionCurrent()
|
||||
if oldmission then oldmission:Cancel() end
|
||||
mission:SetTime(5)
|
||||
mission:SetTeleport(false)
|
||||
truckdata.group:AddMission(mission)
|
||||
elseif self.routeonroad then
|
||||
truckdata.group:RouteGroundOnRoad(tgtcoord,30,1,"Cone")
|
||||
else
|
||||
truckdata.group:RouteGroundTo(tgtcoord,30,"Cone",1)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterStop(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
return self
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,11 @@
|
||||
-- **AUOTLASE** - Autolase targets in the field.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- None yet.
|
||||
--
|
||||
-- ### [Autolase](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **Main Features:**
|
||||
@@ -74,7 +74,6 @@
|
||||
-- @image Designation.JPG
|
||||
--
|
||||
-- Date: 24 Oct 2021
|
||||
-- Last Update: Mar 2025
|
||||
--
|
||||
--- Class AUTOLASE
|
||||
-- @type AUTOLASE
|
||||
@@ -84,15 +83,6 @@
|
||||
-- @field #string alias
|
||||
-- @field #boolean debug
|
||||
-- @field #string version
|
||||
-- @field Core.Set#SET_GROUP RecceSet
|
||||
-- @field #table LaserCodes
|
||||
-- @field #table playermenus
|
||||
-- @field #boolean smokemenu
|
||||
-- @field #boolean threatmenu
|
||||
-- @field #number RoundingPrecision
|
||||
-- @field #table smokeoffset
|
||||
-- @field #boolean increasegroundawareness
|
||||
-- @field #number MonitorFrequency
|
||||
-- @extends Ops.Intel#INTEL
|
||||
|
||||
---
|
||||
@@ -103,10 +93,6 @@ AUTOLASE = {
|
||||
verbose = 0,
|
||||
alias = "",
|
||||
debug = false,
|
||||
smokemenu = true,
|
||||
RoundingPrecision = 0,
|
||||
increasegroundawareness = false,
|
||||
MonitorFrequency = 30,
|
||||
}
|
||||
|
||||
--- Laser spot info
|
||||
@@ -120,12 +106,10 @@ AUTOLASE = {
|
||||
-- @field #string unitname
|
||||
-- @field #string reccename
|
||||
-- @field #string unittype
|
||||
-- @field Core.Point#COORDINATE coordinate
|
||||
|
||||
|
||||
--- AUTOLASE class version.
|
||||
-- @field #string version
|
||||
AUTOLASE.version = "0.1.31"
|
||||
AUTOLASE.version = "0.0.11"
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Begin Functional.Autolase.lua
|
||||
@@ -198,7 +182,6 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
|
||||
self.reporttimelong = 30
|
||||
self.smoketargets = false
|
||||
self.smokecolor = SMOKECOLOR.Red
|
||||
self.smokeoffset = nil
|
||||
self.notifypilots = true
|
||||
self.targetsperrecce = {}
|
||||
self.RecceUnits = {}
|
||||
@@ -208,18 +191,6 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
|
||||
self.SRSPath = ""
|
||||
self.SRSFreq = 251
|
||||
self.SRSMod = radio.modulation.AM
|
||||
self.NoMenus = false
|
||||
self.minthreatlevel = 0
|
||||
self.blacklistattributes = {}
|
||||
self:SetLaserCodes( { 1688, 1130, 4785, 6547, 1465, 4578 } ) -- set self.LaserCodes
|
||||
self.playermenus = {}
|
||||
self.smokemenu = true
|
||||
self.threatmenu = true
|
||||
self.RoundingPrecision = 0
|
||||
self.increasegroundawareness = false
|
||||
self.MonitorFrequency = 30
|
||||
|
||||
self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)})
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
|
||||
@@ -235,14 +206,14 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
|
||||
self:AddTransition("*", "Cancel", "*") -- Stop Autolase
|
||||
|
||||
-- Menu Entry
|
||||
if PilotSet then
|
||||
if not PilotSet then
|
||||
self.Menu = MENU_COALITION_COMMAND:New(self.coalition,"Autolase",nil,self.ShowStatus,self)
|
||||
else
|
||||
self.usepilotset = true
|
||||
self.pilotset = PilotSet
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
|
||||
--self:SetPilotMenu()
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft)
|
||||
self:SetPilotMenu()
|
||||
end
|
||||
--self.SetPilotMenu()
|
||||
|
||||
|
||||
self:SetClusterAnalysis(false, false)
|
||||
|
||||
@@ -322,95 +293,19 @@ end
|
||||
-- Helper Functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
--- [User] When using Monitor, set the frequency here in which the report will appear
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number Seconds Run the report loop every number of seconds defined here.
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetMonitorFrequency(Seconds)
|
||||
self.MonitorFrequency = Seconds or 30
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set a table of possible laser codes.
|
||||
-- Each new RECCE can select a code from this table, default is { 1688, 1130, 4785, 6547, 1465, 4578 }.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #list<#number> LaserCodes
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetLaserCodes( LaserCodes )
|
||||
self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes }
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Improve ground unit detection by using a zone scan and LOS check.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:EnableImproveGroundUnitsDetection()
|
||||
self.increasegroundawareness = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Do not improve ground unit detection by using a zone scan and LOS check.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:DisableImproveGroundUnitsDetection()
|
||||
self.increasegroundawareness = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to set pilot menu.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetPilotMenu()
|
||||
if self.usepilotset then
|
||||
local pilottable = self.pilotset:GetSetObjects() or {}
|
||||
local grouptable = {}
|
||||
for _,_unit in pairs (pilottable) do
|
||||
local Unit = _unit -- Wrapper.Unit#UNIT
|
||||
if Unit and Unit:IsAlive() then
|
||||
local Group = Unit:GetGroup()
|
||||
local GroupName = Group:GetName() or "none"
|
||||
local unitname = Unit:GetName()
|
||||
if not grouptable[GroupName] == true then
|
||||
if self.playermenus[unitname] then self.playermenus[unitname]:Remove() end -- menus
|
||||
local lasetopm = MENU_GROUP:New(Group,"Autolase",nil)
|
||||
self.playermenus[unitname] = lasetopm
|
||||
local lasemenu = MENU_GROUP_COMMAND:New(Group,"Status",lasetopm,self.ShowStatus,self,Group,Unit)
|
||||
if self.smokemenu then
|
||||
local smoke = (self.smoketargets == true) and "off" or "on"
|
||||
local smoketext = string.format("Switch smoke targets to %s",smoke)
|
||||
local smokemenu = MENU_GROUP_COMMAND:New(Group,smoketext,lasetopm,self.SetSmokeTargets,self,(not self.smoketargets))
|
||||
end -- smokement
|
||||
if self.threatmenu then
|
||||
local threatmenutop = MENU_GROUP:New(Group,"Set min lasing threat",lasetopm)
|
||||
for i=0,10,2 do
|
||||
local text = "Threatlevel "..tostring(i)
|
||||
local threatmenu = MENU_GROUP_COMMAND:New(Group,text,threatmenutop,self.SetMinThreatLevel,self,i)
|
||||
end -- threatlevel
|
||||
end -- threatmenu
|
||||
for _,_grp in pairs(self.RecceSet.Set) do
|
||||
local grp = _grp -- Wrapper.Group#GROUP
|
||||
local unit = grp:GetUnit(1)
|
||||
--local name = grp:GetName()
|
||||
if unit and unit:IsAlive() then
|
||||
local name = unit:GetName()
|
||||
local mname = string.gsub(name,".%d+.%d+$","")
|
||||
local code = self:GetLaserCode(name)
|
||||
local unittop = MENU_GROUP:New(Group,"Change laser code for "..mname,lasetopm)
|
||||
for _,_code in pairs(self.LaserCodes) do
|
||||
local text = tostring(_code)
|
||||
if _code == code then text = text.."(*)" end
|
||||
local changemenu = MENU_GROUP_COMMAND:New(Group,text,unittop,self.SetRecceLaserCode,self,name,_code,true)
|
||||
end -- Codes
|
||||
end -- unit alive
|
||||
end -- Recceset
|
||||
grouptable[GroupName] = true
|
||||
end -- grouptable[GroupName]
|
||||
--lasemenu:Refresh()
|
||||
end -- unit alive
|
||||
end -- pilot loop
|
||||
else
|
||||
if not self.NoMenus then
|
||||
self.Menu = MENU_COALITION_COMMAND:New(self.coalition,"Autolase",nil,self.ShowStatus,self)
|
||||
local lasemenu = MENU_GROUP_COMMAND:New(Group,"Autolase Status",nil,self.ShowStatus,self,Group)
|
||||
lasemenu:Refresh()
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
@@ -420,47 +315,11 @@ end
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:_EventHandler(EventData)
|
||||
function AUTOLASE:OnEventPlayerEnterAircraft(EventData)
|
||||
self:SetPilotMenu()
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Set minimum threat level for target selection, can be 0 (lowest) to 10 (highest).
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number Level (Optional) Level used for filtering, defaults to 0. SAM systems and manpads have level 7 to 10, AAA level 6, MTBs and armoured vehicles level 3 to 5, APC, Artillery, Infantry and EWR level 1 to 2.
|
||||
-- @return #AUTOLASE self
|
||||
-- @usage Filter for level 3 and above:
|
||||
-- `myautolase:SetMinThreatLevel(3)`
|
||||
function AUTOLASE:SetMinThreatLevel(Level)
|
||||
local level = Level or 0
|
||||
if level < 0 or level > 10 then level = 0 end
|
||||
self.minthreatlevel = level
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Set list of #UNIT level attributes that won't be lased. For list of attributes see [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes) and [GitHub](https://github.com/mrSkortch/DCS-miscScripts/tree/master/ObjectDB)
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #table Attributes Table of #string attributes to blacklist. Can be handed over as a single #string.
|
||||
-- @return #AUTOLASE self
|
||||
-- @usage To exclude e.g. manpads from being lased:
|
||||
--
|
||||
-- `myautolase:AddBlackListAttributes("MANPADS")`
|
||||
--
|
||||
-- To exclude trucks and artillery:
|
||||
--
|
||||
-- `myautolase:AddBlackListAttributes({"Trucks","Artillery"})`
|
||||
--
|
||||
function AUTOLASE:AddBlackListAttributes(Attributes)
|
||||
local attributes = Attributes
|
||||
if type(attributes) ~= "table" then
|
||||
attributes = {attributes}
|
||||
end
|
||||
for _,_attr in pairs(attributes) do
|
||||
table.insert(self.blacklistattributes,_attr)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to get a laser code by recce name
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName Unit(!) name of the Recce
|
||||
@@ -493,59 +352,15 @@ end
|
||||
--- (User) Function enable sending messages via SRS.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff Switch usage on and off
|
||||
-- @param #string Path Path to SRS TTS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio
|
||||
-- @param #string Path Path to SRS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalon
|
||||
-- @param #number Frequency Frequency to send, e.g. 243
|
||||
-- @param #number Modulation Modulation i.e. radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #string Label (Optional) Short label to be used on the SRS Client Overlay
|
||||
-- @param #string Gender (Optional) Defaults to "male"
|
||||
-- @param #string Culture (Optional) Defaults to "en-US"
|
||||
-- @param #number Port (Optional) Defaults to 5002
|
||||
-- @param #string Voice (Optional) Use a specifc voice with the @{Sound.SRS#SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`.
|
||||
-- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS.
|
||||
-- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest).
|
||||
-- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS.
|
||||
-- @param #string Provider (Optional) TTS Provider to be used.
|
||||
-- @param #string Speaker (Optional) Use a specific speaker for a voice if Piper is used as provider (only Hound-TTS backend).
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Provider,Speaker)
|
||||
if OnOff then
|
||||
self.useSRS = true
|
||||
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
|
||||
self.SRSFreq = Frequency or 271
|
||||
self.SRSMod = Modulation or radio.modulation.AM
|
||||
self.Gender = Gender or MSRS.gender or "male"
|
||||
self.Culture = Culture or MSRS.culture or "en-US"
|
||||
self.Port = Port or MSRS.port or 5002
|
||||
self.Voice = Voice
|
||||
self.PathToGoogleKey = PathToGoogleKey
|
||||
self.Volume = Volume or 1.0
|
||||
self.Label = Label
|
||||
-- set up SRS
|
||||
self.SRS = MSRS:New(self.SRSPath,self.SRSFreq,self.SRSMod)
|
||||
self.SRS:SetCoalition(self.coalition)
|
||||
self.SRS:SetLabel(self.MenuName or self.Name)
|
||||
self.SRS:SetGender(self.Gender)
|
||||
self.SRS:SetCulture(self.Culture)
|
||||
self.SRS:SetPort(self.Port)
|
||||
self.SRS:SetVoice(self.Voice)
|
||||
if Speaker then
|
||||
self.SRS:SetSpeakerPiper(Speaker)
|
||||
end
|
||||
self.SRS:SetCoalition(self.coalition)
|
||||
self.SRS:SetVolume(self.Volume)
|
||||
if self.PathToGoogleKey and not Provider then
|
||||
self.SRS:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey)
|
||||
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
|
||||
end
|
||||
if Provider then
|
||||
self.SRS:SetProvider(Provider)
|
||||
end
|
||||
self.SRSQueue = MSRSQUEUE:New(self.alias)
|
||||
else
|
||||
self.useSRS = false
|
||||
self.SRS= nil
|
||||
self.SRSQueue = nil
|
||||
end
|
||||
function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation)
|
||||
self.useSRS = OnOff or true
|
||||
self.SRSPath = Path or "E:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
self.SRSFreq = Frequency or 271
|
||||
self.SRSMod = Modulation or radio.modulation.AM
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -571,20 +386,10 @@ end
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName (Unit!) Name of the Recce
|
||||
-- @param #number Code The lase code
|
||||
-- @param #boolean Refresh If true, refresh menu entries
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetRecceLaserCode(RecceName, Code, Refresh)
|
||||
function AUTOLASE:SetRecceLaserCode(RecceName, Code)
|
||||
local code = Code or 1688
|
||||
self.RecceLaserCode[RecceName] = code
|
||||
if Refresh then
|
||||
self:SetPilotMenu()
|
||||
if self.notifypilots then
|
||||
if string.find(RecceName,"#") then
|
||||
RecceName = string.match(RecceName,"^(.*)#")
|
||||
end
|
||||
self:NotifyPilots(string.format("Code for %s set to: %d",RecceName,Code),15)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -601,8 +406,8 @@ end
|
||||
|
||||
--- (User) Function to force laser cooldown and cool down time
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff (Optional) Switch cool down on (true) or off (false) - defaults to true
|
||||
-- @param #number Seconds (Optional) Number of seconds for cooldown - dafaults to 60 seconds
|
||||
-- @param #boolean OnOff Switch cool down on (true) or off (false) - defaults to true
|
||||
-- @param #number Seconds Number of seconds for cooldown - dafaults to 60 seconds
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetLaserCoolDown(OnOff, Seconds)
|
||||
self.forcecooldown = OnOff and true
|
||||
@@ -623,8 +428,8 @@ end
|
||||
|
||||
--- (User) Function to set lasing distance in meters and duration in seconds
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number Distance (Optional) Max distance for lasing in meters - default 5000 meters
|
||||
-- @param #number Duration (Optional) Max duration for lasing in seconds - default 300 secs
|
||||
-- @param #number Distance (Max) distance for lasing in meters - default 5000 meters
|
||||
-- @param #number Duration (Max) duration for lasing in seconds - default 300 secs
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetLasingParameters(Distance, Duration)
|
||||
self.LaseDistance = Distance or 5000
|
||||
@@ -640,57 +445,6 @@ end
|
||||
function AUTOLASE:SetSmokeTargets(OnOff,Color)
|
||||
self.smoketargets = OnOff
|
||||
self.smokecolor = Color or SMOKECOLOR.Red
|
||||
local smktxt = OnOff == true and "on" or "off"
|
||||
local Message = "Smoking targets is now "..smktxt.."!"
|
||||
self:NotifyPilots(Message,10)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set rounding precision for BR distance output.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number IDP (Optional) Rounding precision before/after the decimal sign. Defaults to zero. Positive values round right of the decimal sign, negative ones left of the decimal sign.
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetRoundingPrecsion(IDP)
|
||||
self.RoundingPrecision = IDP or 0
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Show the "Switch smoke target..." menu entry for pilots. On by default.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #table Offset (Optional) Define an offset for the smoke, i.e. not directly on the unit itself, angle is degrees and distance is meters. E.g. `autolase:EnableSmokeMenu({Angle=30,Distance=20})`
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:EnableSmokeMenu(Offset)
|
||||
self.smokemenu = true
|
||||
if Offset then
|
||||
self.smokeoffset = {}
|
||||
self.smokeoffset.Distance = Offset.Distance or math.random(10,20)
|
||||
self.smokeoffset.Angle = Offset.Angle or math.random(0,359)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Do not show the "Switch smoke target..." menu entry for pilots.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:DisableSmokeMenu()
|
||||
self.smokemenu = false
|
||||
self.smokeoffset = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Show the "Switch min threat lasing..." menu entry for pilots. On by default.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:EnableThreatLevelMenu()
|
||||
self.threatmenu = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Do not show the "Switch min threat lasing..." menu entry for pilots.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:DisableThreatLevelMenu()
|
||||
self.threatmenu = false
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -701,7 +455,7 @@ end
|
||||
function AUTOLASE:GetLosFromUnit(Unit)
|
||||
local lasedistance = self.LaseDistance
|
||||
local unitheight = Unit:GetHeight()
|
||||
local coord = Unit:GetCoord()
|
||||
local coord = Unit:GetCoordinate()
|
||||
local landheight = coord:GetLandHeight()
|
||||
local asl = unitheight - landheight
|
||||
if asl > 100 then
|
||||
@@ -733,8 +487,7 @@ function AUTOLASE:CleanCurrentLasing()
|
||||
local unit = recce:GetUnit(1)
|
||||
local name = unit:GetName()
|
||||
if not self.RecceUnits[name] then
|
||||
local isground = (unit and unit.IsGround) and unit:IsGround() or false
|
||||
self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground }
|
||||
self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime() }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -806,72 +559,35 @@ end
|
||||
--- (Internal) Function to show status.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Wrapper.Group#GROUP Group (Optional) show to a certain group
|
||||
-- @param Wrapper.Unit#UNIT Unit (Optional) show to a certain unit
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:ShowStatus(Group,Unit)
|
||||
function AUTOLASE:ShowStatus(Group)
|
||||
local report = REPORT:New("Autolase")
|
||||
local reccetable = self.RecceSet:GetSetObjects()
|
||||
for _,_recce in pairs(reccetable) do
|
||||
if _recce and _recce:IsAlive() then
|
||||
local unit = _recce:GetUnit(1)
|
||||
local name = unit:GetName()
|
||||
if string.find(name,"#") then
|
||||
name = string.match(name,"^(.*)#")
|
||||
end
|
||||
local code = self:GetLaserCode(unit:GetName())
|
||||
local code = self:GetLaserCode(name)
|
||||
report:Add(string.format("Recce %s has code %d",name,code))
|
||||
report:Add("---------------")
|
||||
end
|
||||
end
|
||||
report:Add(string.format("Lasing min threat level %d",self.minthreatlevel))
|
||||
report:Add("---------------")
|
||||
local lines = 0
|
||||
for _ind,_entry in pairs(self.CurrentLasing) do
|
||||
local entry = _entry -- #AUTOLASE.LaserSpot
|
||||
local reccename = entry.reccename
|
||||
if string.find(reccename,"#") then
|
||||
reccename = string.match(reccename,"^(.*)#")
|
||||
end
|
||||
local typename = entry.unittype
|
||||
local code = entry.lasercode
|
||||
local locationstring = entry.location
|
||||
local playername = nil
|
||||
if Unit and Unit:IsAlive() then
|
||||
playername = Unit:GetPlayerName()
|
||||
elseif Group and Group:IsAlive() then
|
||||
playername = Group:GetPlayerName()
|
||||
end
|
||||
if playername then
|
||||
local settings = _DATABASE:GetPlayerSettings(playername)
|
||||
if settings then
|
||||
self:T("Get Settings ok!")
|
||||
if settings:IsA2G_MGRS() then
|
||||
locationstring = entry.coordinate:ToStringMGRS(settings)
|
||||
elseif settings:IsA2G_LL_DMS() then
|
||||
locationstring = entry.coordinate:ToStringLLDMS(settings)
|
||||
elseif settings:IsA2G_LL_DDM() then
|
||||
locationstring = entry.coordinate:ToStringLLDDM(settings)
|
||||
elseif settings:IsA2G_BR() then
|
||||
-- attention this is the distance from the ASKING unit to target, not from RECCE to target!
|
||||
local startcoordinate = Unit:GetCoord() or Group:GetCoord()
|
||||
locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision)
|
||||
end
|
||||
end
|
||||
end
|
||||
local text = string.format("+ %s lasing %s code %d\nat %s",reccename,typename,code,locationstring)
|
||||
local text = string.format("%s lasing %s code %d\nat %s",reccename,typename,code,locationstring)
|
||||
report:Add(text)
|
||||
report:Add("---------------")
|
||||
lines = lines + 1
|
||||
end
|
||||
if lines == 0 then
|
||||
report:Add("No targets!")
|
||||
report:Add("---------------")
|
||||
end
|
||||
local reporttime = self.reporttimelong
|
||||
if lines == 0 then reporttime = self.reporttimeshort end
|
||||
if Unit and Unit:IsAlive() then
|
||||
local m = MESSAGE:New(report:Text(),reporttime,"Info"):ToUnit(Unit)
|
||||
elseif Group and Group:IsAlive() then
|
||||
if Group and Group:IsAlive() then
|
||||
local m = MESSAGE:New(report:Text(),reporttime,"Info"):ToGroup(Group)
|
||||
else
|
||||
local m = MESSAGE:New(report:Text(),reporttime,"Info"):ToCoalition(self.coalition)
|
||||
@@ -915,7 +631,20 @@ end
|
||||
-- end
|
||||
function AUTOLASE:NotifyPilotsWithSRS(Message)
|
||||
if self.useSRS then
|
||||
self.SRSQueue:NewTransmission(Message,nil,self.SRS,nil,2)
|
||||
-- Create a SOUNDTEXT object.
|
||||
if self.debug then
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SOUNDTEXT")
|
||||
BASE:TraceClass("MSRS")
|
||||
end
|
||||
local path = self.SRSPath or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
local freq = self.SRSFreq or 271
|
||||
local mod = self.SRSMod or radio.modulation.AM
|
||||
local text=SOUNDTEXT:New(Message)
|
||||
-- MOOSE SRS
|
||||
local msrs=MSRS:New(path, freq, mod)
|
||||
-- Text-to speech with default voice after 2 seconds.
|
||||
msrs:PlaySoundText(text, 2)
|
||||
end
|
||||
if self.debug then self:I(Message) end
|
||||
return self
|
||||
@@ -943,20 +672,6 @@ end
|
||||
-- @param Wrapper.Unit#UNIT Unit The lased #UNIT
|
||||
-- @return #boolean outcome True or false
|
||||
function AUTOLASE:CanLase(Recce,Unit)
|
||||
|
||||
local function HasNoBlackListAttribute(Unit)
|
||||
local nogos = self.blacklistattributes or {}
|
||||
local having = true
|
||||
local unit = Unit -- Wrapper.Unit#UNIT
|
||||
for _,_attribute in pairs (nogos) do
|
||||
if unit:HasAttribute(_attribute) then
|
||||
having = false
|
||||
break
|
||||
end
|
||||
end
|
||||
return having
|
||||
end
|
||||
|
||||
local canlase = false
|
||||
-- cooldown?
|
||||
if Recce and Recce:IsAlive() == true then
|
||||
@@ -971,78 +686,19 @@ function AUTOLASE:CanLase(Recce,Unit)
|
||||
end
|
||||
end
|
||||
-- calculate LOS
|
||||
local reccecoord = Recce:GetCoord()
|
||||
local unitcoord = Unit:GetCoord()
|
||||
local reccecoord = Recce:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local islos = reccecoord:IsLOS(unitcoord,2.5)
|
||||
-- calculate distance
|
||||
local distance = math.floor(reccecoord:Get3DDistance(unitcoord))
|
||||
local lasedistance = self:GetLosFromUnit(Recce)
|
||||
if distance <= lasedistance and islos and HasNoBlackListAttribute(Unit) then
|
||||
if distance <= lasedistance and islos then
|
||||
canlase = true
|
||||
end
|
||||
end
|
||||
return canlase
|
||||
end
|
||||
|
||||
--- (Internal) Function to do a zone check per ground Recce and make found units and statics "known".
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:_Prescient()
|
||||
-- self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground }
|
||||
for _,_data in pairs(self.RecceUnits) do
|
||||
-- ground units only
|
||||
if _data.isground and _data.unit and _data.unit:IsAlive() then
|
||||
local unit = _data.unit -- Wrapper.Unit#UNIT
|
||||
local position = unit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local needsinit = false
|
||||
if position then
|
||||
local lastposition = unit:GetProperty("lastposition")
|
||||
-- property initiated?
|
||||
if not lastposition then
|
||||
unit:SetProperty("lastposition",position)
|
||||
lastposition = position
|
||||
needsinit = true
|
||||
end
|
||||
-- has moved?
|
||||
local dist = position:Get2DDistance(lastposition)
|
||||
-- refresh?
|
||||
local TNow = timer.getAbsTime()
|
||||
-- check
|
||||
if dist > 10 or needsinit==true or TNow - _data.timestamp > 29 then
|
||||
-- init scan objects
|
||||
local hasunits,hasstatics,_,Units,Statics = position:ScanObjects(self.LaseDistance,true,true,false)
|
||||
-- loop found units
|
||||
if hasunits then
|
||||
self:T(self.lid.."Checking possibly visible UNITs for Recce "..unit:GetName())
|
||||
for _,_target in pairs(Units) do -- Wrapper.Unit#UNIT object here
|
||||
local target = _target -- Wrapper.Unit#UNIT
|
||||
if target and target:GetCoalition() ~= self.coalition then
|
||||
if unit:IsLOS(target) and (not target:IsUnitDetected(unit))then
|
||||
unit:KnowUnit(target,true,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- loop found statics
|
||||
if hasstatics then
|
||||
self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName())
|
||||
for _,_static in pairs(Statics) do -- DCS static object here
|
||||
local static = STATIC:Find(_static)
|
||||
if static and static:GetCoalition() ~= self.coalition and static:GetCoord() then
|
||||
local IsLOS = position:IsLOS(static:GetCoord())
|
||||
if IsLOS then
|
||||
unit:KnowUnit(static,true,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------
|
||||
@@ -1055,9 +711,6 @@ end
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeMonitor(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
if self.increasegroundawareness then
|
||||
self:_Prescient()
|
||||
end
|
||||
-- Check if group has detected any units.
|
||||
self:UpdateIntel()
|
||||
return self
|
||||
@@ -1077,19 +730,18 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
|
||||
self:SetPilotMenu()
|
||||
|
||||
local detecteditems = self.Contacts or {} -- #table of Ops.Intel#INTEL.Contact
|
||||
local detecteditems = self.Contacts or {} -- #table of Ops.Intelligence#INTEL.Contact
|
||||
local groupsbythreat = {}
|
||||
local report = REPORT:New("Detections")
|
||||
local lines = 0
|
||||
for _,_contact in pairs(detecteditems) do
|
||||
local contact = _contact -- Ops.Intel#INTEL.Contact
|
||||
local contact = _contact -- Ops.Intelligence#INTEL.Contact
|
||||
local grp = contact.group
|
||||
local coord = contact.position
|
||||
local reccename = contact.recce or "none"
|
||||
local threat = contact.threatlevel or 0
|
||||
local reccegrp = UNIT:FindByName(reccename)
|
||||
if reccegrp then
|
||||
local reccecoord = reccegrp:GetCoord()
|
||||
local reccecoord = reccegrp:GetCoordinate()
|
||||
local distance = math.floor(reccecoord:Get3DDistance(coord))
|
||||
local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel)
|
||||
report:Add(text)
|
||||
@@ -1098,7 +750,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
lines = lines + 1
|
||||
-- sort out groups beyond sight
|
||||
local lasedistance = self:GetLosFromUnit(reccegrp)
|
||||
if grp:IsGround() and lasedistance >= distance and threat >= self.minthreatlevel then
|
||||
if grp:IsGround() and lasedistance >= distance then
|
||||
table.insert(groupsbythreat,{contact.group,contact.threatlevel})
|
||||
self.RecceNames[contact.groupname] = contact.recce
|
||||
end
|
||||
@@ -1128,12 +780,9 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local threat = unit:GetThreatLevel()
|
||||
if threat >= self.minthreatlevel then
|
||||
local coord = unit:GetCoordinate()
|
||||
if threat > 0 then
|
||||
local unitname = unit:GetName()
|
||||
-- prefer radar units
|
||||
if unit:HasAttribute("RADAR_BAND1_FOR_ARM") or unit:HasAttribute("RADAR_BAND2_FOR_ARM") or unit:HasAttribute("Optical Tracker") then
|
||||
threat = 11
|
||||
end
|
||||
table.insert(unitsbythreat,{unit,threat})
|
||||
self.RecceUnitNames[unitname] = reccename
|
||||
end
|
||||
@@ -1183,18 +832,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
local code = self:GetLaserCode(reccename)
|
||||
local spot = SPOT:New(recce)
|
||||
spot:LaseOn(unit,code,self.LaseDuration)
|
||||
local locationstring = unit:GetCoord():ToStringLLDDM()
|
||||
if _SETTINGS:IsA2G_MGRS() then
|
||||
local precision = _SETTINGS:GetMGRS_Accuracy()
|
||||
local settings = {}
|
||||
settings.MGRS_Accuracy = precision
|
||||
locationstring = unit:GetCoord():ToStringMGRS(settings)
|
||||
elseif _SETTINGS:IsA2G_LL_DMS() then
|
||||
locationstring = unit:GetCoord():ToStringLLDMS(_SETTINGS)
|
||||
elseif _SETTINGS:IsA2G_BR() then
|
||||
locationstring = unit:GetCoord():ToStringBULLS(self.coalition,_SETTINGS)
|
||||
end
|
||||
|
||||
local locationstring = unit:GetCoordinate():ToStringLLDDM()
|
||||
local laserspot = { -- #AUTOLASE.LaserSpot
|
||||
laserspot = spot,
|
||||
lasedunit = unit,
|
||||
@@ -1205,13 +843,9 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
unitname = unitname,
|
||||
reccename = reccename,
|
||||
unittype = unit:GetTypeName(),
|
||||
coordinate = unit:GetCoord(),
|
||||
}
|
||||
if self.smoketargets then
|
||||
local coord = unit:GetCoordinate()
|
||||
if self.smokeoffset then
|
||||
coord:Translate(self.smokeoffset.Distance,self.smokeoffset.Angle,true,true)
|
||||
end
|
||||
local color = self:GetSmokeColor(reccename)
|
||||
coord:Smoke(color)
|
||||
end
|
||||
@@ -1222,8 +856,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
end
|
||||
end
|
||||
|
||||
local nextloop = -self.MonitorFrequency or -30
|
||||
self:__Monitor(nextloop)
|
||||
self:__Monitor(-30)
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1237,9 +870,6 @@ end
|
||||
function AUTOLASE:onbeforeRecceKIA(From,Event,To,RecceName)
|
||||
self:T({From, Event, To, RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
if string.find(RecceName,"#") then
|
||||
RecceName = string.match(RecceName,"^(.*)#")
|
||||
end
|
||||
local text = string.format("Recce %s KIA!",RecceName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
@@ -1274,9 +904,6 @@ end
|
||||
function AUTOLASE:onbeforeTargetLost(From,Event,To,UnitName,RecceName)
|
||||
self:T({From, Event, To, UnitName,RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
if string.find(RecceName,"#") then
|
||||
RecceName = string.match(RecceName,"^(.*)#")
|
||||
end
|
||||
local text = string.format("%s lost sight of unit %s.",RecceName,UnitName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
@@ -1294,9 +921,6 @@ end
|
||||
function AUTOLASE:onbeforeLaserTimeout(From,Event,To,UnitName,RecceName)
|
||||
self:T({From, Event, To, UnitName,RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
if string.find(RecceName,"#") then
|
||||
RecceName = string.match(RecceName,"^(.*)#")
|
||||
end
|
||||
local text = string.format("%s laser timeout on unit %s.",RecceName,UnitName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
@@ -1314,10 +938,7 @@ function AUTOLASE:onbeforeLasing(From,Event,To,LaserSpot)
|
||||
self:T({From, Event, To, LaserSpot.unittype})
|
||||
if self.notifypilots or self.debug then
|
||||
local laserspot = LaserSpot -- #AUTOLASE.LaserSpot
|
||||
local name = laserspot.reccename if string.find(name,"#") then
|
||||
name = string.match(name,"^(.*)#")
|
||||
end
|
||||
local text = string.format("%s is lasing %s code %d\nat %s",name,laserspot.unittype,laserspot.lasercode,laserspot.location)
|
||||
local text = string.format("%s is lasing %s code %d\nat %s",laserspot.reccename,laserspot.unittype,laserspot.lasercode,laserspot.location)
|
||||
self:NotifyPilots(text,self.reporttimeshort+5)
|
||||
end
|
||||
return self
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Functional** - Keep airbases clean of crashing or colliding airplanes, and kill missiles when being fired at airbases.
|
||||
--- **Functional** -- Keep airbases clean of crashing or colliding airplanes, and kill missiles when being fired at airbases.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -14,7 +14,7 @@
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- [CLA - CleanUp Airbase](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Functional/CleanUp)
|
||||
-- [CLA - CleanUp Airbase](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CLA%20-%20CleanUp%20Airbase)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -52,13 +52,11 @@
|
||||
-- @module Functional.CleanUp
|
||||
-- @image CleanUp_Airbases.JPG
|
||||
|
||||
---
|
||||
-- @type CLEANUP_AIRBASE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-)
|
||||
--- @type CLEANUP_AIRBASE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-)
|
||||
-- @field #map<#string,Wrapper.Airbase#AIRBASE> Airbases Map of Airbases.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
---
|
||||
-- @type CLEANUP_AIRBASE
|
||||
--- @type CLEANUP_AIRBASE
|
||||
-- @extends #CLEANUP_AIRBASE.__
|
||||
|
||||
--- Keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat.
|
||||
@@ -95,7 +93,7 @@ CLEANUP_AIRBASE = {
|
||||
-- @field #CLEANUP_AIRBASE.__
|
||||
CLEANUP_AIRBASE.__ = {}
|
||||
|
||||
-- @field #CLEANUP_AIRBASE.__.Airbases
|
||||
--- @field #CLEANUP_AIRBASE.__.Airbases
|
||||
CLEANUP_AIRBASE.__.Airbases = {}
|
||||
|
||||
--- Creates the main object which is handling the cleaning of the debris within the given Zone Names.
|
||||
@@ -178,7 +176,7 @@ end
|
||||
-- Note, one can also use the method @{#CLEANUP_AIRBASE.RemoveAirbase}() to remove the airbase from the control process as a whole,
|
||||
-- when an enemy unit is near. That is also an option...
|
||||
-- @param #CLEANUP_AIRBASE self
|
||||
-- @param #boolean CleanMissiles (Optional) (Default=true) If true, missiles fired are immediately destroyed. If false missiles are not controlled.
|
||||
-- @param #string CleanMissiles (Default=true) If true, missiles fired are immediately destroyed. If false missiles are not controlled.
|
||||
-- @return #CLEANUP_AIRBASE
|
||||
function CLEANUP_AIRBASE:SetCleanMissiles( CleanMissiles )
|
||||
|
||||
@@ -242,8 +240,7 @@ function CLEANUP_AIRBASE.__:DestroyMissile( MissileObject )
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #CLEANUP_AIRBASE self
|
||||
--- @param #CLEANUP_AIRBASE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function CLEANUP_AIRBASE.__:OnEventBirth( EventData )
|
||||
self:F( { EventData } )
|
||||
@@ -357,7 +354,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
|
||||
self:F({Event})
|
||||
|
||||
|
||||
if Event.IniDCSUnit and Event.IniUnit and Event.IniCategory == Object.Category.UNIT then
|
||||
if Event.IniDCSUnit and Event.IniCategory == Object.Category.UNIT then
|
||||
if self.CleanUpList[Event.IniDCSUnitName] == nil then
|
||||
if self:IsInAirbase( Event.IniUnit:GetVec2() ) then
|
||||
self:AddForCleanUp( Event.IniUnit, Event.IniDCSUnitName )
|
||||
@@ -365,7 +362,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
|
||||
end
|
||||
end
|
||||
|
||||
if Event.TgtDCSUnit and Event.TgtUnit and Event.TgtCategory == Object.Category.UNIT then
|
||||
if Event.TgtDCSUnit and Event.TgtCategory == Object.Category.UNIT then
|
||||
if self.CleanUpList[Event.TgtDCSUnitName] == nil then
|
||||
if self:IsInAirbase( Event.TgtUnit:GetVec2() ) then
|
||||
self:AddForCleanUp( Event.TgtUnit, Event.TgtDCSUnitName )
|
||||
@@ -387,7 +384,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
|
||||
local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT
|
||||
local CleanUpGroupName = CleanUpListData.CleanUpGroupName
|
||||
|
||||
if CleanUpUnit and CleanUpUnit:IsAlive() ~= nil then
|
||||
if CleanUpUnit:IsAlive() ~= nil then
|
||||
|
||||
if self:IsInAirbase( CleanUpUnit:GetVec2() ) then
|
||||
|
||||
@@ -414,7 +411,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
|
||||
end
|
||||
end
|
||||
-- Clean Units which are waiting for a very long time in the CleanUpZone.
|
||||
if CleanUpUnit and (CleanUpUnit.GetPlayerName == nil or not CleanUpUnit:GetPlayerName()) then
|
||||
if CleanUpUnit and not CleanUpUnit:GetPlayerName() then
|
||||
local CleanUpUnitVelocity = CleanUpUnit:GetVelocityKMH()
|
||||
if CleanUpUnitVelocity < 1 then
|
||||
if CleanUpListData.CleanUpMoved then
|
||||
|
||||
@@ -1,687 +0,0 @@
|
||||
--- **Functional** - Manage and track client slots easily to add your own client-based menus and modules to.
|
||||
--
|
||||
-- The @{#CLIENTWATCH} class adds a simplified way to create scripts and menus for individual clients. Instead of creating large algorithms and juggling multiple event handlers, you can simply provide one or more prefixes to the class and use the callback functions on spawn, despawn, and any aircraft related events to script to your hearts content.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Find clients by prefixes or by providing a Wrapper.CLIENT object
|
||||
-- * Trigger functions when the client spawns and despawns
|
||||
-- * Create multiple client instances without overwriting event handlers between instances
|
||||
-- * More reliable aircraft lost events for when DCS thinks the aircraft id dead but a dead event fails to trigger
|
||||
-- * Easily manage clients spawned in dynamic slots
|
||||
--
|
||||
-- ====
|
||||
--
|
||||
-- ### Author: **Statua**
|
||||
--
|
||||
-- ### Contributions: **FlightControl**: Wrapper.CLIENT
|
||||
--
|
||||
-- ====
|
||||
-- @module Functional.ClientWatch
|
||||
-- @image clientwatch.jpg
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
--- CLIENTWATCH class
|
||||
-- @type CLIENTWATCH
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Write Debug messages to DCS log file and send Debug messages to all players.
|
||||
-- @field #string lid String for DCS log file.
|
||||
-- @field #number FilterCoalition If not nil, will only activate for aircraft of the given coalition value.
|
||||
-- @field #number FilterCategory If not nil, will only activate for aircraft of the given category value.
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
--- Manage and track client slots easily to add your own client-based menus and modules to.
|
||||
--
|
||||
-- ## Creating a new instance
|
||||
--
|
||||
-- To start, you must first create a new instance of the client manager and provide it with either a Wrapper.Client#CLIENT object, a string prefix of the unit name, or a table of string prefixes for unit names. These are used to capture the client unit when it spawns and apply your scripted functions to it. Only fixed wing and rotary wing aircraft controlled by players can be used by this class.
|
||||
-- **This will not work if the client aircraft is alive!**
|
||||
--
|
||||
-- ### Examples
|
||||
--
|
||||
-- -- Create an instance with a Wrapper.Client#CLIENT object
|
||||
-- local heliClient = CLIENT:FindByName('Rotary1-1')
|
||||
-- local clientInstance = CLIENTWATCH:New(heliClient)
|
||||
--
|
||||
-- -- Create an instance with part of the unit name in the Mission Editor
|
||||
-- local clientInstance = CLIENTWATCH:New("Rotary")
|
||||
--
|
||||
-- -- Create an instance using prefixes for a few units as well as a FARP name for any dynamic spawns coming out of it
|
||||
-- local clientInstance = CLIENTWATCH:New({"Rescue","UH-1H","FARP ALPHA"})
|
||||
--
|
||||
-- ## Applying functions and methods to client aircraft when they spawn
|
||||
--
|
||||
-- Once the instance is created, it will watch for birth events. If the unit name of the client aircraft matches the one provided in the instance, the callback method @{#CLIENTWATCH:OnAfterSpawn}() can be used to apply functions and methods to the client object.
|
||||
--
|
||||
-- In the OnAfterSpawn() callback method are four values. From, Event, To, and ClientObject. From,Event,To are standard FSM strings for the state changes. ClientObject is where the magic happens. This is a special object which you can use to access all the data of the client aircraft. The following entries in ClientObject are available for you to use:
|
||||
--
|
||||
-- * **ClientObject.Unit**: The Moose @{Wrapper.Unit#UNIT} of the client aircraft
|
||||
-- * **ClientObject.Group**: The Moose @{Wrapper.Group#GRUP} of the client aircraft
|
||||
-- * **ClientObject.Client**: The Moose @{Wrapper.Client#CLIENT} of the client aircraft
|
||||
-- * **ClientObject.PlayerName**: A #string of the player controlling the aircraft
|
||||
-- * **ClientObject.UnitName**: A #string of the client aircraft unit.
|
||||
-- * **ClientObject.GroupName**: A #string of the client aircraft group.
|
||||
--
|
||||
-- ### Examples
|
||||
--
|
||||
-- -- Create an instance with a client unit prefix and send them a message when they spawn
|
||||
-- local clientInstance = CLIENTWATCH:New("Rotary")
|
||||
-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject,EventData)
|
||||
-- MESSAGE:New("Welcome to your aircraft!",10):ToUnit(ClientObject.Unit)
|
||||
-- end
|
||||
--
|
||||
-- ## Using event callbacks
|
||||
--
|
||||
-- In a normal setting, you can only use a callback function for a specific option in one location. If you have multiple scripts that rely on the same callback from the same object, this can get quite messy. With the ClientWatch module, these callbacks are isolated t the instances and therefore open the possibility to use many instances with the same callback doing different things. ClientWatch instances subscribe to all events that are applicable to player controlled aircraft and provides callbacks for each, forwarding the EventData in the callback function.
|
||||
--
|
||||
-- The following event callbacks can be used inside the OnAfterSpawn() callback:
|
||||
--
|
||||
-- * **:OnAfterDespawn(From,Event,To)**: Triggers whenever DCS no longer sees the aircraft as 'alive'. No event data is given in this callback as it is derived from other events
|
||||
-- * **:OnAfterHit(From,Event,To,EventData)**: Triggers every time the aircraft takes damage or is struck by a weapon/explosion
|
||||
-- * **:OnAfterKill(From,Event,To,EventData)**: Triggers after the aircraft kills something with its weapons
|
||||
-- * **:OnAfterScore(From,Event,To,EventData)**: Triggers after accumulating score
|
||||
-- * **:OnAfterShot(From,Event,To,EventData)**: Triggers after a single-shot weapon is released
|
||||
-- * **:OnAfterShootingStart(From,Event,To,EventData)**: Triggers when an automatic weapon begins firing
|
||||
-- * **:OnAfterShootingEnd(From,Event,To,EventData)**: Triggers when an automatic weapon stops firing
|
||||
-- * **:OnAfterLand(From,Event,To,EventData)**: Triggers when an aircraft transitions from being airborne to on the ground
|
||||
-- * **:OnAfterTakeoff(From,Event,To,EventData)**: Triggers when an aircraft transitions from being on the ground to airborne
|
||||
-- * **:OnAfterRunwayTakeoff(From,Event,To,EventData)**: Triggers after lifting off from a runway
|
||||
-- * **:OnAfterRunwayTouch(From,Event,To,EventData)**: Triggers when an aircraft's gear makes contact with a runway
|
||||
-- * **:OnAfterRefueling(From,Event,To,EventData)**: Triggers when an aircraft begins taking on fuel
|
||||
-- * **:OnAfterRefuelingStop(From,Event,To,EventData)**: Triggers when an aircraft stops taking on fuel
|
||||
-- * **:OnAfterPlayerLeaveUnit(From,Event,To,EventData)**: Triggers when a player leaves an operational aircraft
|
||||
-- * **:OnAfterCrash(From,Event,To,EventData)**: Triggers when an aircraft is destroyed (may fail to trigger if the aircraft is only partially destroyed)
|
||||
-- * **:OnAfterDead(From,Event,To,EventData)**: Triggers when an aircraft is considered dead (may fail to trigger if the aircraft was partially destroyed first)
|
||||
-- * **:OnAfterPilotDead(From,Event,To,EventData)**: Triggers when the pilot is killed (may fail to trigger if the aircraft was partially destroyed first)
|
||||
-- * **:OnAfterUnitLost(From,Event,To,EventData)**: Triggers when an aircraft is lost for any reason (may fail to trigger if the aircraft was partially destroyed first)
|
||||
-- * **:OnAfterEjection(From,Event,To,EventData)**: Triggers when a pilot ejects from an aircraft
|
||||
-- * **:OnAfterHumanFailure(From,Event,To,EventData)**: Triggers when an aircraft or system is damaged from any source or action by the player
|
||||
-- * **:OnAfterHumanAircraftRepairStart(From,Event,To,EventData)**: Triggers when an aircraft repair is started
|
||||
-- * **:OnAfterHumanAircraftRepairFinish(From,Event,To,EventData)**: Triggers when an aircraft repair is completed
|
||||
-- * **:OnAfterEngineStartup(From,Event,To,EventData)**: Triggers when the engine enters what DCS considers to be a started state. Parameters vary by aircraft
|
||||
-- * **:OnAfterEngineShutdown(From,Event,To,EventData)**: Triggers when the engine enters what DCS considers to be a stopped state. Parameters vary by aircraft
|
||||
-- * **:OnAfterWeaponAdd(From,Event,To,EventData)**: Triggers when an item is added to an aircraft's payload
|
||||
-- * **:OnAfterWeaponDrop(From,Event,To,EventData)**: Triggers when an item is jettisoned or dropped from an aircraft (unconfirmed)
|
||||
-- * **:OnAfterWeaponRearm(From,Event,To,EventData)**: Triggers when an item with internal supply is restored (unconfirmed)
|
||||
--
|
||||
-- ### Examples
|
||||
--
|
||||
-- -- Show a message to player when they take damage from a weapon
|
||||
-- local clientInstance = CLIENTWATCH:New("Rotary")
|
||||
-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject,EventData)
|
||||
-- function ClientObject:OnAfterHit(From,Event,To,EventData)
|
||||
-- local typeShooter = EventData.IniTypeName
|
||||
-- local nameWeapon = EventData.weapon_name
|
||||
-- MESSAGE:New("A "..typeShooter.." hit you with a "..nameWeapon,20):ToUnit(ClientObject.Unit)
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- @field #CLIENTWATCH
|
||||
CLIENTWATCH = {}
|
||||
CLIENTWATCH.ClassName = "CLIENTWATCH"
|
||||
CLIENTWATCH.Debug = false
|
||||
CLIENTWATCH.DebugEventData = false
|
||||
CLIENTWATCH.lid = nil
|
||||
|
||||
-- @type CLIENTWATCHTools
|
||||
-- @field #table Unit Wrapper.UNIT of the cient object
|
||||
-- @field #table Group Wrapper.GROUP of the cient object
|
||||
-- @field #table Client Wrapper.CLIENT of the cient object
|
||||
-- @field #string PlayerName Name of the player controlling the client object
|
||||
-- @field #string UnitName Name of the unit that is the client object
|
||||
-- @field #string GroupName Name of the group the client object belongs to
|
||||
CLIENTWATCHTools = {}
|
||||
|
||||
--- CLIENTWATCH version
|
||||
-- @field #string version
|
||||
CLIENTWATCH.version="1.0.1"
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Creates a new instance of CLIENTWATCH to add scripts to. Can be used multiple times with the same client/prefixes if you need it for multiple scripts.
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param #string Will watch for clients whos UNIT NAME or GROUP NAME matches part of the #string as a prefix.
|
||||
-- @param #table Put strings in a table to use multiple prefixes for the above method.
|
||||
-- @param Wrapper.Client#CLIENT Provide a Moose CLIENT object to apply to that specific aircraft slot (static slots only!)
|
||||
-- @param #nil Leave blank to activate for ALL CLIENTS
|
||||
-- @return #CLIENTWATCH self
|
||||
function CLIENTWATCH:New(client)
|
||||
--Init FSM
|
||||
local self=BASE:Inherit(self, FSM:New())
|
||||
self:SetStartState( "Idle" )
|
||||
self:AddTransition( "*", "Spawn", "*" )
|
||||
|
||||
self.FilterCoalition = nil
|
||||
self.FilterCategory = nil
|
||||
|
||||
--- User function for OnAfter "Spawn" event.
|
||||
-- @function [parent=#CLIENTWATCH] OnAfterSpawn
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #table clientObject Custom object that handles events and stores Moose object data. See top documentation for more details.
|
||||
-- @param #table eventdata Data from EVENTS.Birth.
|
||||
|
||||
--Set up spawn tracking
|
||||
if not client then
|
||||
if self.Debug then self:I({"New client instance created. ClientType = All clients"}) end
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
end
|
||||
end
|
||||
elseif type(client) == "table" or type(client) == "string" then
|
||||
if type(client) == "table" then
|
||||
|
||||
--CLIENT TABLE
|
||||
if client.ClassName == "CLIENT" then
|
||||
if self.Debug then self:I({"New client instance created. ClientType = Wrapper.CLIENT",client}) end
|
||||
self.ClientName = client:GetName()
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if self.ClientName == eventdata.IniUnitName then
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--STRING TABLE
|
||||
else
|
||||
if self.Debug then self:I({"New client instance created. ClientType = Multiple Prefixes",client}) end
|
||||
local tableValid = true
|
||||
for _,entry in pairs(client) do
|
||||
if type(entry) ~= "string" then
|
||||
tableValid = false
|
||||
self:E({"The base handler failed to start because at least one entry in param1's table is not a string!",InvalidEntry = entry})
|
||||
return nil
|
||||
end
|
||||
end
|
||||
if tableValid then
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
for _,entry in pairs(client) do
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if string.match(eventdata.IniUnitName,entry) or string.match(eventdata.IniGroupName,entry) then
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if self.Debug then self:I({"New client instance created. ClientType = Single Prefix",client}) end
|
||||
|
||||
--SOLO STRING
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if string.match(eventdata.IniUnitName,client) or string.match(eventdata.IniGroupName,client) then
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self:E({"The base handler failed to start because param1 is not a CLIENT object or a prefix string!",param1 = client})
|
||||
return nil
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Filter out all clients not belonging to the provided coalition
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param #number Coalition number (1 = red, 2 = blue)
|
||||
-- @param #string Coalition string ('red' or 'blue')
|
||||
function CLIENTWATCH:FilterByCoalition(value)
|
||||
if value == 1 or value == "red" then
|
||||
self.FilterCoalition = 1
|
||||
else
|
||||
self.FilterCoalition = 2
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Filter out all clients that are not of the given category
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param #number Category number (0 = airplane, 1 = helicopter)
|
||||
-- @param #string Category string ('airplane' or 'helicopter')
|
||||
function CLIENTWATCH:FilterByCategory(value)
|
||||
if value == 1 or value == "helicopter" then
|
||||
self.FilterCategory = 1
|
||||
else
|
||||
self.FilterCategory = 0
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Internal function for creating a new client on birth. Do not use!!!.
|
||||
-- @param #CLIENTWATCHTools self
|
||||
-- @param #EVENTS.Birth EventData
|
||||
-- @return #CLIENTWATCHTools self
|
||||
function CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
--Init FSM
|
||||
local self=BASE:Inherit(self, FSM:New())
|
||||
self:SetStartState( "Alive" )
|
||||
self:AddTransition( "Alive", "Despawn", "Dead" )
|
||||
|
||||
self.Unit = eventdata.IniUnit
|
||||
self.Group = self.Unit:GetGroup()
|
||||
self.Client = self.Unit:GetClient()
|
||||
self.PlayerName = self.Unit:GetPlayerName()
|
||||
self.UnitName = self.Unit:GetName()
|
||||
self.GroupName = self.Group:GetName()
|
||||
|
||||
--Event events
|
||||
self:AddTransition( "*", "Hit", "*" )
|
||||
self:AddTransition( "*", "Kill", "*" )
|
||||
self:AddTransition( "*", "Score", "*" )
|
||||
self:AddTransition( "*", "Shot", "*" )
|
||||
self:AddTransition( "*", "ShootingStart", "*" )
|
||||
self:AddTransition( "*", "ShootingEnd", "*" )
|
||||
self:AddTransition( "*", "Land", "*" )
|
||||
self:AddTransition( "*", "Takeoff", "*" )
|
||||
self:AddTransition( "*", "RunwayTakeoff", "*" )
|
||||
self:AddTransition( "*", "RunwayTouch", "*" )
|
||||
self:AddTransition( "*", "Refueling", "*" )
|
||||
self:AddTransition( "*", "RefuelingStop", "*" )
|
||||
self:AddTransition( "*", "PlayerLeaveUnit", "*" )
|
||||
self:AddTransition( "*", "Crash", "*" )
|
||||
self:AddTransition( "*", "Dead", "*" )
|
||||
self:AddTransition( "*", "PilotDead", "*" )
|
||||
self:AddTransition( "*", "UnitLost", "*" )
|
||||
self:AddTransition( "*", "Ejection", "*" )
|
||||
self:AddTransition( "*", "HumanFailure", "*" )
|
||||
self:AddTransition( "*", "HumanAircraftRepairFinish", "*" )
|
||||
self:AddTransition( "*", "HumanAircraftRepairStart", "*" )
|
||||
self:AddTransition( "*", "EngineShutdown", "*" )
|
||||
self:AddTransition( "*", "EngineStartup", "*" )
|
||||
self:AddTransition( "*", "WeaponAdd", "*" )
|
||||
self:AddTransition( "*", "WeaponDrop", "*" )
|
||||
self:AddTransition( "*", "WeaponRearm", "*" )
|
||||
|
||||
--Event Handlers
|
||||
self:HandleEvent( EVENTS.Hit )
|
||||
self:HandleEvent( EVENTS.Kill )
|
||||
self:HandleEvent( EVENTS.Score )
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
self:HandleEvent( EVENTS.ShootingStart )
|
||||
self:HandleEvent( EVENTS.ShootingEnd )
|
||||
self:HandleEvent( EVENTS.Land )
|
||||
self:HandleEvent( EVENTS.Takeoff )
|
||||
self:HandleEvent( EVENTS.RunwayTakeoff )
|
||||
self:HandleEvent( EVENTS.RunwayTouch )
|
||||
self:HandleEvent( EVENTS.Refueling )
|
||||
self:HandleEvent( EVENTS.RefuelingStop )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit )
|
||||
self:HandleEvent( EVENTS.Crash )
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
self:HandleEvent( EVENTS.PilotDead )
|
||||
self:HandleEvent( EVENTS.UnitLost )
|
||||
self:HandleEvent( EVENTS.Ejection )
|
||||
self:HandleEvent( EVENTS.HumanFailure )
|
||||
self:HandleEvent( EVENTS.HumanAircraftRepairFinish )
|
||||
self:HandleEvent( EVENTS.HumanAircraftRepairStart )
|
||||
self:HandleEvent( EVENTS.EngineShutdown )
|
||||
self:HandleEvent( EVENTS.EngineStartup )
|
||||
self:HandleEvent( EVENTS.WeaponAdd )
|
||||
self:HandleEvent( EVENTS.WeaponDrop )
|
||||
self:HandleEvent( EVENTS.WeaponRearm )
|
||||
|
||||
function self:OnEventHit(EventData)
|
||||
if EventData.TgtUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered hit event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Hit(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventKill(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered kill event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Kill(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventScore(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered score event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Score(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventShot(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered shot event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Shot(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventShootingStart(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered shooting start event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:ShootingStart(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventShootingEnd(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered shooting end event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:ShootingEnd(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventLand(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered land event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Land(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventTakeoff(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered takeoff event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Takeoff(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRunwayTakeoff(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered runway takeoff event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:RunwayTakeoff(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRunwayTouch(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered runway touch event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:RunwayTouch(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRefueling(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered refueling event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Refueling(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRefuelingStop(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered refueling event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:RefuelingStop(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventPlayerLeaveUnit(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered leave unit event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:PlayerLeaveUnit(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventCrash(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered crash event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Crash(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventDead(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered dead event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Dead(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventPilotDead(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered pilot dead event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:PilotDead(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventUnitLost(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered unit lost event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:UnitLost(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventEjection(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered ejection event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Ejection(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventHumanFailure(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered human failure event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:HumanFailure(EventData)
|
||||
if not self.Unit:IsAlive() then
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventHumanAircraftRepairFinish(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered repair finished event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:HumanAircraftRepairFinish(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventHumanAircraftRepairStart(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered repair start event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:HumanAircraftRepairStart(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventEngineShutdown(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered engine shutdown event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:EngineShutdown(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventEngineStartup(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered engine startup event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:EngineStartup(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventWeaponAdd(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered weapon add event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:WeaponAdd(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventWeaponDrop(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered weapon drop event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:WeaponDrop(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventWeaponRearm(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered weapon rearm event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:WeaponRearm(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--Fallback timer
|
||||
self.FallbackTimer = TIMER:New(function()
|
||||
if not self.Unit:IsAlive() then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client is registered as dead without an event trigger. Running fallback dead routine.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
end
|
||||
self._deadRoutine()
|
||||
end
|
||||
end)
|
||||
self.FallbackTimer:Start(5,5)
|
||||
|
||||
--Stop event handlers and trigger Despawn
|
||||
function self._deadRoutine()
|
||||
if clientWatchDebug then self:I({"Client dead routine triggered. Shutting down tracking...",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) end
|
||||
self:UnHandleEvent( EVENTS.Hit )
|
||||
self:UnHandleEvent( EVENTS.Kill )
|
||||
self:UnHandleEvent( EVENTS.Score )
|
||||
self:UnHandleEvent( EVENTS.Shot )
|
||||
self:UnHandleEvent( EVENTS.ShootingStart )
|
||||
self:UnHandleEvent( EVENTS.ShootingEnd )
|
||||
self:UnHandleEvent( EVENTS.Land )
|
||||
self:UnHandleEvent( EVENTS.Takeoff )
|
||||
self:UnHandleEvent( EVENTS.RunwayTakeoff )
|
||||
self:UnHandleEvent( EVENTS.RunwayTouch )
|
||||
self:UnHandleEvent( EVENTS.Refueling )
|
||||
self:UnHandleEvent( EVENTS.RefuelingStop )
|
||||
self:UnHandleEvent( EVENTS.PlayerLeaveUnit )
|
||||
self:UnHandleEvent( EVENTS.Crash )
|
||||
self:UnHandleEvent( EVENTS.Dead )
|
||||
self:UnHandleEvent( EVENTS.PilotDead )
|
||||
self:UnHandleEvent( EVENTS.UnitLost )
|
||||
self:UnHandleEvent( EVENTS.Ejection )
|
||||
self:UnHandleEvent( EVENTS.HumanFailure )
|
||||
self:UnHandleEvent( EVENTS.HumanAircraftRepairFinish )
|
||||
self:UnHandleEvent( EVENTS.HumanAircraftRepairStart )
|
||||
self:UnHandleEvent( EVENTS.EngineShutdown )
|
||||
self:UnHandleEvent( EVENTS.EngineStartup )
|
||||
self:UnHandleEvent( EVENTS.WeaponAdd )
|
||||
self:UnHandleEvent( EVENTS.WeaponDrop )
|
||||
self:UnHandleEvent( EVENTS.WeaponRearm )
|
||||
self.FallbackTimer:Stop()
|
||||
self:Despawn()
|
||||
end
|
||||
|
||||
self:I({"Detected client spawn and applied internal functions and events.", PlayerName = self.PlayerName, UnitName = self.UnitName, GroupName = self.GroupName})
|
||||
return self
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Functional** - Management of target **Designation**. Lase, smoke and illuminate targets.
|
||||
--- **Functional** -- Management of target **Designation**. Lase, smoke and illuminate targets.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -15,12 +15,10 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Additional Material:
|
||||
--
|
||||
-- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Functional/Designate)
|
||||
-- * **YouTube videos:** None
|
||||
-- * **Guides:** None
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- [DES - Designation](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/DES%20-%20Designation)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- Targets detected by recce will be communicated to a group of attacking players.
|
||||
@@ -50,7 +48,7 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- A typical mission setup would require Recce (a @{Core.Set} of Recce) to be detecting potential targets.
|
||||
-- A typical mission setup would require Recce (a @{Set} of Recce) to be detecting potential targets.
|
||||
-- The DetectionObject will group the detected targets based on the detection method being used.
|
||||
-- Possible detection methods could be by Area, by Type or by Unit.
|
||||
-- Each grouping will result in a **TargetGroup**, for terminology and clarity we will use this term throughout the document.
|
||||
@@ -169,9 +167,9 @@
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Ciribob**: Showing the way how to lase targets + how laser codes work!!! Explained the autolase script.
|
||||
-- * **EasyEB**: Ideas and Beta Testing
|
||||
-- * **Wingthor**: Beta Testing
|
||||
-- * [**Ciribob**](https://forums.eagle.ru/member.php?u=112175): Showing the way how to lase targets + how laser codes work!!! Explained the autolase script.
|
||||
-- * [**EasyEB**](https://forums.eagle.ru/member.php?u=112055): Ideas and Beta Testing
|
||||
-- * [**Wingthor**](https://forums.eagle.ru/member.php?u=123698): Beta Testing
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
@@ -184,7 +182,7 @@
|
||||
|
||||
do -- DESIGNATE
|
||||
|
||||
-- @type DESIGNATE
|
||||
--- @type DESIGNATE
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
|
||||
--- Manage the designation of detected targets.
|
||||
@@ -215,7 +213,7 @@ do -- DESIGNATE
|
||||
-- In order to prevent an overflow of designations due to many Detected Targets, there is a
|
||||
-- Maximum Designations scope that is set in the DesignationObject.
|
||||
--
|
||||
-- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations (target groups) put in scope of the DesignationObject.
|
||||
-- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations put in scope of the DesignationObject.
|
||||
-- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected.
|
||||
--
|
||||
-- # 4. Laser codes
|
||||
@@ -278,7 +276,7 @@ do -- DESIGNATE
|
||||
-- # 7. Designate Menu Location for a Mission
|
||||
--
|
||||
-- You can make DESIGNATE work for a @{Tasking.Mission#MISSION} object. In this way, the designate menu will not appear in the root of the radio menu, but in the menu of the Mission.
|
||||
-- Use the method @{#DESIGNATE.SetMission}() to set the @{Tasking.Mission} object for the designate function.
|
||||
-- Use the method @{#DESIGNATE.SetMission}() to set the @{Mission} object for the designate function.
|
||||
--
|
||||
-- # 8. Status Report
|
||||
--
|
||||
@@ -301,11 +299,12 @@ do -- DESIGNATE
|
||||
|
||||
--- DESIGNATE Constructor. This class is an abstract class and should not be instantiated.
|
||||
-- @param #DESIGNATE self
|
||||
-- @param Wrapper.Group#GROUP CC Group as Commandcenter standin. Currently unused (03/2026)
|
||||
-- @param Tasking.CommandCenter#COMMANDCENTER CC
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection
|
||||
-- @param Core.Set#SET_GROUP AttackSet The Attack collection of GROUP objects to designate and report for.
|
||||
-- @param Tasking.Mission#MISSION Mission (Optional) The Mission where the menu needs to be attached.
|
||||
-- @return #DESIGNATE
|
||||
function DESIGNATE:New( CC, Detection, AttackSet )
|
||||
function DESIGNATE:New( CC, Detection, AttackSet, Mission )
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #DESIGNATE
|
||||
self:F( { Detection } )
|
||||
@@ -467,7 +466,7 @@ do -- DESIGNATE
|
||||
-- @param #DESIGNATE self
|
||||
-- @param #number Delay
|
||||
|
||||
--self.CC = CC
|
||||
self.CC = CC
|
||||
self.Detection = Detection
|
||||
self.AttackSet = AttackSet
|
||||
self.RecceSet = Detection:GetDetectionSet()
|
||||
@@ -479,7 +478,7 @@ do -- DESIGNATE
|
||||
|
||||
self:SetFlashStatusMenu( false )
|
||||
self:SetFlashDetectionMessages( true )
|
||||
--self:SetMission( Mission )
|
||||
self:SetMission( Mission )
|
||||
|
||||
self:SetLaserCodes( { 1688, 1130, 4785, 6547, 1465, 4578 } ) -- set self.LaserCodes
|
||||
self:SetAutoLase( false, false ) -- set self.Autolase and don't send message.
|
||||
@@ -524,7 +523,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
-- @param Wrapper.Group#GROUP AttackGroup
|
||||
--- @param Wrapper.Group#GROUP AttackGroup
|
||||
function( AttackGroup )
|
||||
self.FlashStatusMenu[AttackGroup] = FlashMenu
|
||||
end
|
||||
@@ -553,7 +552,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
-- @param Wrapper.Group#GROUP AttackGroup
|
||||
--- @param Wrapper.Group#GROUP AttackGroup
|
||||
function( AttackGroup )
|
||||
self.FlashDetectionMessage[AttackGroup] = FlashDetectionMessage
|
||||
end
|
||||
@@ -563,8 +562,7 @@ do -- DESIGNATE
|
||||
end
|
||||
|
||||
|
||||
--- Set the maximum amount of designations (target groups). This will put a limit on the amount of designations in scope.
|
||||
-- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected.
|
||||
--- Set the maximum amount of designations.
|
||||
-- @param #DESIGNATE self
|
||||
-- @param #number MaximumDesignations
|
||||
-- @return #DESIGNATE
|
||||
@@ -604,7 +602,7 @@ do -- DESIGNATE
|
||||
end
|
||||
|
||||
|
||||
--- Set the maximum amount of markings FACs will do, per designated target group. This will limit the number of parallelly marked units of a target group.
|
||||
--- Set the maximum amount of markings FACs will do, per designated target group.
|
||||
-- @param #DESIGNATE self
|
||||
-- @param #number MaximumMarkings Maximum markings FACs will do, per designated target group.
|
||||
-- @return #DESIGNATE
|
||||
@@ -681,7 +679,7 @@ do -- DESIGNATE
|
||||
|
||||
--- Set the lase duration for designations.
|
||||
-- @param #DESIGNATE self
|
||||
-- @param #number LaseDuration (Optional) The time in seconds a lase will continue to hold on target. The default is 120 seconds.
|
||||
-- @param #number LaseDuration The time in seconds a lase will continue to hold on target. The default is 120 seconds.
|
||||
-- @return #DESIGNATE
|
||||
function DESIGNATE:SetLaseDuration( LaseDuration )
|
||||
self.LaseDuration = LaseDuration or 120
|
||||
@@ -753,12 +751,10 @@ do -- DESIGNATE
|
||||
|
||||
if Message then
|
||||
local AutoLaseOnOff = ( self.AutoLase == true ) and "On" or "Off"
|
||||
|
||||
MESSAGE:New(self.DesignateName .. ": Auto Lase " .. AutoLaseOnOff .. ".",15):ToSet(self.AttackSet)
|
||||
--local CC = self.CC:GetPositionable()
|
||||
--if CC then
|
||||
--CC:MessageToSetGroup( self.DesignateName .. ": Auto Lase " .. AutoLaseOnOff .. ".", 15, self.AttackSet )
|
||||
--end
|
||||
local CC = self.CC:GetPositionable()
|
||||
if CC then
|
||||
CC:MessageToSetGroup( self.DesignateName .. ": Auto Lase " .. AutoLaseOnOff .. ".", 15, self.AttackSet )
|
||||
end
|
||||
end
|
||||
|
||||
self:CoordinateLase()
|
||||
@@ -778,14 +774,14 @@ do -- DESIGNATE
|
||||
return self
|
||||
end
|
||||
|
||||
--- [DEPRECATED DO NOT USE] Set the MISSION object for which designate will function.
|
||||
--- Set the MISSION object for which designate will function.
|
||||
-- When a MISSION object is assigned, the menu for the designation will be located at the Mission Menu.
|
||||
-- @param #DESIGNATE self
|
||||
-- @param Tasking.Mission#MISSION Mission The MISSION object.
|
||||
-- @return #DESIGNATE
|
||||
function DESIGNATE:SetMission( Mission ) --R2.2
|
||||
|
||||
--self.Mission = Mission
|
||||
self.Mission = Mission
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -827,12 +823,11 @@ do -- DESIGNATE
|
||||
-- This Detection is obsolete, remove from the designate scope
|
||||
self.Designating[DesignateIndex] = nil
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
-- @param Wrapper.Group#GROUP AttackGroup
|
||||
--- @param Wrapper.Group#GROUP AttackGroup
|
||||
function( AttackGroup )
|
||||
if AttackGroup:IsAlive() == true then
|
||||
local DetectionText = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " )
|
||||
--self.CC:GetPositionable():MessageToGroup( "Targets out of LOS\n" .. DetectionText, 10, AttackGroup, self.DesignateName )
|
||||
MESSAGE:New("Targets out of LOS\n" .. DetectionText,10,self.DesignateName):ToGroup(AttackGroup)
|
||||
self.CC:GetPositionable():MessageToGroup( "Targets out of LOS\n" .. DetectionText, 10, AttackGroup, self.DesignateName )
|
||||
end
|
||||
end
|
||||
)
|
||||
@@ -857,8 +852,7 @@ do -- DESIGNATE
|
||||
function( AttackGroup )
|
||||
if self.FlashDetectionMessage[AttackGroup] == true then
|
||||
local DetectionText = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " )
|
||||
--self.CC:GetPositionable():MessageToGroup( "Targets detected at \n" .. DetectionText, 10, AttackGroup, self.DesignateName )
|
||||
MESSAGE:New( "Targets detected at \n" .. DetectionText,10,self.DesignateName):ToGroup(AttackGroup)
|
||||
self.CC:GetPositionable():MessageToGroup( "Targets detected at \n" .. DetectionText, 10, AttackGroup, self.DesignateName )
|
||||
end
|
||||
end
|
||||
)
|
||||
@@ -906,7 +900,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
-- @param Wrapper.Group#GROUP GroupReport
|
||||
--- @param Wrapper.Group#GROUP GroupReport
|
||||
function( AttackGroup )
|
||||
|
||||
if self.FlashStatusMenu[AttackGroup] or ( MenuAttackGroup and ( AttackGroup:GetName() == MenuAttackGroup:GetName() ) ) then
|
||||
@@ -917,8 +911,8 @@ do -- DESIGNATE
|
||||
for DesignateIndex, Designating in pairs( self.Designating ) do
|
||||
local DetectedItem = DetectedItems[DesignateIndex]
|
||||
if DetectedItem then
|
||||
local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup, nil, true ):Text( ", " )
|
||||
DetectedReport:Add( string.rep( "-", 40 ) )
|
||||
local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " )
|
||||
DetectedReport:Add( string.rep( "-", 140 ) )
|
||||
DetectedReport:Add( " - " .. Report )
|
||||
if string.find( Designating, "L" ) then
|
||||
DetectedReport:Add( " - " .. "Lasing Targets" )
|
||||
@@ -932,10 +926,9 @@ do -- DESIGNATE
|
||||
end
|
||||
end
|
||||
|
||||
-- local CC = self.CC:GetPositionable()
|
||||
local CC = self.CC:GetPositionable()
|
||||
|
||||
--CC:MessageTypeToGroup( DetectedReport:Text( "\n" ), MESSAGE.Type.Information, AttackGroup, self.DesignateName )
|
||||
MESSAGE:New( DetectedReport:Text( "\n" ),15,self.DesignateName):ToGroup(AttackGroup)
|
||||
CC:MessageTypeToGroup( DetectedReport:Text( "\n" ), MESSAGE.Type.Information, AttackGroup, self.DesignateName )
|
||||
|
||||
local DesignationReport = REPORT:New( "Marking Targets:" )
|
||||
|
||||
@@ -969,10 +962,10 @@ do -- DESIGNATE
|
||||
|
||||
local MissionMenu = nil
|
||||
|
||||
--if self.Mission then
|
||||
if self.Mission then
|
||||
--MissionMenu = self.Mission:GetRootMenu( AttackGroup )
|
||||
--MissionMenu = self.Mission:GetMenu( AttackGroup )
|
||||
--end
|
||||
MissionMenu = self.Mission:GetMenu( AttackGroup )
|
||||
end
|
||||
|
||||
local MenuTime = timer.getTime()
|
||||
|
||||
@@ -1064,7 +1057,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
-- @param Wrapper.Group#GROUP GroupReport
|
||||
--- @param Wrapper.Group#GROUP GroupReport
|
||||
function( AttackGroup )
|
||||
|
||||
self:ScheduleOnce( Delay, self.SetMenu, self, AttackGroup )
|
||||
@@ -1199,10 +1192,10 @@ do -- DESIGNATE
|
||||
|
||||
local MarkingCount = 0
|
||||
local MarkedTypes = {}
|
||||
--local ReportTypes = REPORT:New()
|
||||
--local ReportLaserCodes = REPORT:New()
|
||||
local ReportTypes = REPORT:New()
|
||||
local ReportLaserCodes = REPORT:New()
|
||||
|
||||
--TargetSetUnit:Flush( self )
|
||||
TargetSetUnit:Flush( self )
|
||||
|
||||
--self:F( { Recces = self.Recces } )
|
||||
for TargetUnit, RecceData in pairs( self.Recces ) do
|
||||
@@ -1233,12 +1226,10 @@ do -- DESIGNATE
|
||||
end
|
||||
end
|
||||
|
||||
if TargetSetUnit == nil then return end
|
||||
|
||||
if self.AutoLase or ( not self.AutoLase and ( self.LaseStart + Duration >= timer.getTime() ) ) then
|
||||
|
||||
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
|
||||
-- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
--- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
function( TargetUnit )
|
||||
|
||||
self:F( { TargetUnit = TargetUnit:GetName() } )
|
||||
@@ -1252,14 +1243,14 @@ do -- DESIGNATE
|
||||
if not Recce then
|
||||
|
||||
self:F( "Lasing..." )
|
||||
--self.RecceSet:Flush( self)
|
||||
|
||||
self.RecceSet:Flush( self)
|
||||
|
||||
for RecceGroupID, RecceGroup in pairs( self.RecceSet:GetSet() ) do
|
||||
for UnitID, UnitData in pairs( RecceGroup:GetUnits() or {} ) do
|
||||
|
||||
local RecceUnit = UnitData -- Wrapper.Unit#UNIT
|
||||
local RecceUnitDesc = RecceUnit:GetDesc()
|
||||
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )x
|
||||
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )
|
||||
|
||||
if RecceUnit:IsLasing() == false then
|
||||
--self:F( { IsDetected = RecceUnit:IsDetected( TargetUnit ), IsLOS = RecceUnit:IsLOS( TargetUnit ) } )
|
||||
@@ -1281,10 +1272,9 @@ do -- DESIGNATE
|
||||
local Spot = RecceUnit:LaseUnit( TargetUnit, LaserCode, Duration )
|
||||
local AttackSet = self.AttackSet
|
||||
local DesignateName = self.DesignateName
|
||||
local typename = TargetUnit:GetTypeName()
|
||||
|
||||
function Spot:OnAfterDestroyed( From, Event, To )
|
||||
self.Recce:MessageToSetGroup( "Target " ..typename .. " destroyed. " .. TargetSetUnit:CountAlive() .. " targets left.",
|
||||
self.Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() .. " destroyed. " .. TargetSetUnit:Count() .. " targets left.",
|
||||
5, AttackSet, self.DesignateName )
|
||||
end
|
||||
|
||||
@@ -1292,13 +1282,13 @@ do -- DESIGNATE
|
||||
-- OK. We have assigned for the Recce a TargetUnit. We can exit the function.
|
||||
MarkingCount = MarkingCount + 1
|
||||
local TargetUnitType = TargetUnit:GetTypeName()
|
||||
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnitType .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
|
||||
10, self.AttackSet, DesignateName )
|
||||
--RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
|
||||
-- 5, self.AttackSet, DesignateName )
|
||||
if not MarkedTypes[TargetUnitType] then
|
||||
MarkedTypes[TargetUnitType] = true
|
||||
--ReportTypes:Add(TargetUnitType)
|
||||
ReportTypes:Add(TargetUnitType)
|
||||
end
|
||||
--ReportLaserCodes:Add(RecceUnit.LaserCode)
|
||||
ReportLaserCodes:Add(RecceUnit.LaserCode)
|
||||
return
|
||||
end
|
||||
else
|
||||
@@ -1313,16 +1303,16 @@ do -- DESIGNATE
|
||||
|
||||
if Recce then
|
||||
Recce:LaseOff()
|
||||
Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 10, self.AttackSet, self.DesignateName )
|
||||
Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 5, self.AttackSet, self.DesignateName )
|
||||
end
|
||||
else
|
||||
--MarkingCount = MarkingCount + 1
|
||||
local TargetUnitType = TargetUnit:GetTypeName()
|
||||
if not MarkedTypes[TargetUnitType] then
|
||||
MarkedTypes[TargetUnitType] = true
|
||||
--ReportTypes:Add(TargetUnitType)
|
||||
ReportTypes:Add(TargetUnitType)
|
||||
end
|
||||
--ReportLaserCodes:Add(RecceUnit.LaserCode)
|
||||
ReportLaserCodes:Add(RecceUnit.LaserCode)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1332,19 +1322,19 @@ do -- DESIGNATE
|
||||
local TargetUnitType = TargetUnit:GetTypeName()
|
||||
if not MarkedTypes[TargetUnitType] then
|
||||
MarkedTypes[TargetUnitType] = true
|
||||
--ReportTypes:Add(TargetUnitType)
|
||||
ReportTypes:Add(TargetUnitType)
|
||||
end
|
||||
--ReportLaserCodes:Add(Recce.LaserCode)
|
||||
Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 10, self.AttackSet )
|
||||
ReportLaserCodes:Add(Recce.LaserCode)
|
||||
--Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 5, self.AttackSet )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
--local MarkedTypesText = ReportTypes:Text(', ')
|
||||
--local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
|
||||
--self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
|
||||
local MarkedTypesText = ReportTypes:Text(', ')
|
||||
local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
|
||||
self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
|
||||
|
||||
self:__Lasing( -self.LaseDuration, Index, Duration, LaserCodeRequested )
|
||||
|
||||
@@ -1360,13 +1350,12 @@ do -- DESIGNATE
|
||||
-- @param #DESIGNATE self
|
||||
-- @return #DESIGNATE
|
||||
function DESIGNATE:onafterLaseOff( From, Event, To, Index )
|
||||
|
||||
MESSAGE:New("Stopped lasing.",5,self.DesignateName):ToSet(self.AttackSet)
|
||||
--local CC = self.CC:GetPositionable()
|
||||
|
||||
local CC = self.CC:GetPositionable()
|
||||
|
||||
--if CC then
|
||||
--CC:MessageToSetGroup( "Stopped lasing.", 5, self.AttackSet, self.DesignateName )
|
||||
--end
|
||||
if CC then
|
||||
CC:MessageToSetGroup( "Stopped lasing.", 5, self.AttackSet, self.DesignateName )
|
||||
end
|
||||
|
||||
local DetectedItem = self.Detection:GetDetectedItemByIndex( Index )
|
||||
local TargetSetUnit = self.Detection:GetDetectedItemSet( DetectedItem )
|
||||
@@ -1400,7 +1389,7 @@ do -- DESIGNATE
|
||||
local MarkedCount = 0
|
||||
|
||||
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
|
||||
-- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
--- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
function( SmokeUnit )
|
||||
|
||||
if MarkedCount < self.MaximumMarkings then
|
||||
@@ -1465,10 +1454,9 @@ do -- DESIGNATE
|
||||
-- @param #DESIGNATE self
|
||||
-- @return #DESIGNATE
|
||||
function DESIGNATE:onafterDoneSmoking( From, Event, To, Index )
|
||||
if self.Designating[Index] ~= nil then
|
||||
self.Designating[Index] = string.gsub( self.Designating[Index], "S", "" )
|
||||
self:SetDesignateMenu()
|
||||
end
|
||||
|
||||
self.Designating[Index] = string.gsub( self.Designating[Index], "S", "" )
|
||||
self:SetDesignateMenu()
|
||||
end
|
||||
|
||||
--- DoneIlluminating
|
||||
@@ -1481,3 +1469,5 @@ do -- DESIGNATE
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,8 @@
|
||||
--- **Functional** - Captures the class DETECTION_ZONES.
|
||||
-- @module Functional.DetectionZones
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
do -- DETECTION_ZONES
|
||||
|
||||
-- @type DETECTION_ZONES
|
||||
--- @type DETECTION_ZONES
|
||||
-- @field DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target.
|
||||
-- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Wrapper.Unit}s, @{Core.Zone}s, the center @{Wrapper.Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange.
|
||||
-- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Wrapper.Unit}s, @{Zone}s, the center @{Wrapper.Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange.
|
||||
-- @extends Functional.Detection#DETECTION_BASE
|
||||
|
||||
--- (old, to be revised ) Detect units within the battle zone for a list of @{Core.Zone}s detecting targets following (a) detection method(s),
|
||||
@@ -21,7 +17,7 @@ do -- DETECTION_ZONES
|
||||
--
|
||||
-- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned.
|
||||
--
|
||||
-- Retrieve the formed @{Core.Zone#ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}().
|
||||
-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}().
|
||||
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}().
|
||||
-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index.
|
||||
--
|
||||
@@ -44,31 +40,31 @@ do -- DETECTION_ZONES
|
||||
ClassName = "DETECTION_ZONES",
|
||||
DetectionZoneRange = nil,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
--- DETECTION_ZONES constructor.
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @param Core.Set#SET_ZONE DetectionSetZone The @{Core.Set} of ZONE_RADIUS.
|
||||
-- @param Core.Set#SET_ZONE DetectionSetZone The @{Set} of ZONE_RADIUS.
|
||||
-- @param DCS#Coalition.side DetectionCoalition The coalition of the detection.
|
||||
-- @return #DETECTION_ZONES
|
||||
function DETECTION_ZONES:New( DetectionSetZone, DetectionCoalition )
|
||||
|
||||
|
||||
-- Inherits from DETECTION_BASE
|
||||
local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetZone ) ) -- #DETECTION_ZONES
|
||||
|
||||
|
||||
self.DetectionSetZone = DetectionSetZone -- Core.Set#SET_ZONE
|
||||
self.DetectionCoalition = DetectionCoalition
|
||||
|
||||
|
||||
self._SmokeDetectedUnits = false
|
||||
self._FlareDetectedUnits = false
|
||||
self._SmokeDetectedZones = false
|
||||
self._FlareDetectedZones = false
|
||||
self._BoundDetectedZones = false
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- @param #DETECTION_ZONES self
|
||||
--- @param #DETECTION_ZONES self
|
||||
-- @param #number The amount of alive recce.
|
||||
function DETECTION_ZONES:CountAliveRecce()
|
||||
|
||||
@@ -76,7 +72,7 @@ do -- DETECTION_ZONES
|
||||
|
||||
end
|
||||
|
||||
-- @param #DETECTION_ZONES self
|
||||
--- @param #DETECTION_ZONES self
|
||||
function DETECTION_ZONES:ForEachAliveRecce( IteratorFunction, ... )
|
||||
self:F2( arg )
|
||||
|
||||
@@ -352,7 +348,7 @@ do -- DETECTION_ZONES
|
||||
--DetectedSet:Flush( self )
|
||||
|
||||
DetectedSet:ForEachUnit(
|
||||
-- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
--- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
function( DetectedUnit )
|
||||
if DetectedUnit:IsAlive() then
|
||||
--self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() )
|
||||
@@ -380,7 +376,7 @@ do -- DETECTION_ZONES
|
||||
|
||||
end
|
||||
|
||||
-- @param #DETECTION_ZONES self
|
||||
--- @param #DETECTION_ZONES self
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Functional** - Taking the lead of AI escorting your flight.
|
||||
--- **Functional** -- Taking the lead of AI escorting your flight.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -16,13 +16,11 @@
|
||||
-- * Escort tactical situation reporting.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Additional Material:
|
||||
--
|
||||
-- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Functional/Escort)
|
||||
-- * **YouTube videos:** None
|
||||
-- * **Guides:** None
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/ESC%20-%20Escorting)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- Allows you to interact with escorting AI on your flight and take the lead.
|
||||
@@ -110,8 +108,8 @@
|
||||
-- @image Escorting.JPG
|
||||
|
||||
|
||||
---
|
||||
-- @type ESCORT
|
||||
|
||||
--- @type ESCORT
|
||||
-- @extends Core.Base#BASE
|
||||
-- @field Wrapper.Client#CLIENT EscortClient
|
||||
-- @field Wrapper.Group#GROUP EscortGroup
|
||||
@@ -179,7 +177,7 @@ ESCORT = {
|
||||
-- @param Wrapper.Client#CLIENT EscortClient The client escorted by the EscortGroup.
|
||||
-- @param Wrapper.Group#GROUP EscortGroup The group AI escorting the EscortClient.
|
||||
-- @param #string EscortName Name of the escort.
|
||||
-- @param #string EscortBriefing (Optional) A text showing the ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @param #string EscortBriefing A text showing the ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #ESCORT self
|
||||
-- @usage
|
||||
-- -- Declare a new EscortPlanes object as follows:
|
||||
@@ -254,7 +252,7 @@ end
|
||||
--- Set a Detection method for the EscortClient to be reported upon.
|
||||
-- Detection methods are based on the derived classes from DETECTION_BASE.
|
||||
-- @param #ESCORT self
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection
|
||||
-- @param Function.Detection#DETECTION_BASE Detection
|
||||
function ESCORT:SetDetection( Detection )
|
||||
|
||||
self.Detection = Detection
|
||||
@@ -333,9 +331,9 @@ end
|
||||
--- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds.
|
||||
-- This menu will appear under **Hold position**.
|
||||
-- @param #ESCORT self
|
||||
-- @param DCS#Distance Height (Optional) parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
|
||||
-- @param DCS#Time Seconds (Optional) parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
|
||||
-- @param #string MenuTextFormat (Optional) parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed.
|
||||
-- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
|
||||
-- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
|
||||
-- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed.
|
||||
-- @return #ESCORT
|
||||
-- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function.
|
||||
function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat )
|
||||
@@ -394,9 +392,9 @@ end
|
||||
--- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds.
|
||||
-- This menu will appear under **Navigation**.
|
||||
-- @param #ESCORT self
|
||||
-- @param DCS#Distance Height (Optional) parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
|
||||
-- @param DCS#Time Seconds (Optional) parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
|
||||
-- @param #string MenuTextFormat (Optional) parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed.
|
||||
-- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
|
||||
-- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
|
||||
-- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed.
|
||||
-- @return #ESCORT
|
||||
-- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function.
|
||||
function ESCORT:MenuHoldAtLeaderPosition( Height, Seconds, MenuTextFormat )
|
||||
@@ -455,9 +453,9 @@ end
|
||||
--- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds.
|
||||
-- This menu will appear under **Scan targets**.
|
||||
-- @param #ESCORT self
|
||||
-- @param DCS#Distance Height (Optional) parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
|
||||
-- @param DCS#Time Seconds (Optional) parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
|
||||
-- @param #string MenuTextFormat (Optional) parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed.
|
||||
-- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
|
||||
-- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
|
||||
-- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed.
|
||||
-- @return #ESCORT
|
||||
function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat )
|
||||
self:F( { Height, Seconds, MenuTextFormat } )
|
||||
@@ -514,7 +512,7 @@ end
|
||||
-- This menu will appear under **Navigation**.
|
||||
-- The flare will be fired from the first unit in the group.
|
||||
-- @param #ESCORT self
|
||||
-- @param #string MenuTextFormat (Optional) parameter that shows the menu option text. If no text is given, the default text will be displayed.
|
||||
-- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed.
|
||||
-- @return #ESCORT
|
||||
function ESCORT:MenuFlare( MenuTextFormat )
|
||||
self:F()
|
||||
@@ -546,7 +544,7 @@ end
|
||||
-- Note that smoke menu options will only be displayed for ships and ground units. Not for air units.
|
||||
-- The smoke will be fired from the first unit in the group.
|
||||
-- @param #ESCORT self
|
||||
-- @param #string MenuTextFormat (Optional) parameter that shows the menu option text. If no text is given, the default text will be displayed.
|
||||
-- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed.
|
||||
-- @return #ESCORT
|
||||
function ESCORT:MenuSmoke( MenuTextFormat )
|
||||
self:F()
|
||||
@@ -580,7 +578,7 @@ end
|
||||
-- This menu will appear under **Report targets**.
|
||||
-- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed.
|
||||
-- @param #ESCORT self
|
||||
-- @param DCS#Time Seconds (Optional) parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds.
|
||||
-- @param DCS#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds.
|
||||
-- @return #ESCORT
|
||||
function ESCORT:MenuReportTargets( Seconds )
|
||||
self:F( { Seconds } )
|
||||
@@ -602,7 +600,7 @@ function ESCORT:MenuReportTargets( Seconds )
|
||||
self.EscortMenuAttackNearbyTargets = MENU_GROUP:New( self.EscortClient:GetGroup(), "Attack targets", self.EscortMenu )
|
||||
|
||||
|
||||
self.ReportTargetsScheduler, self.ReportTargetsSchedulerID = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, Seconds )
|
||||
self.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, Seconds )
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -695,7 +693,7 @@ function ESCORT:MenuResumeMission()
|
||||
end
|
||||
|
||||
|
||||
-- @param #MENUPARAM MenuParam
|
||||
--- @param #MENUPARAM MenuParam
|
||||
function ESCORT:_HoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds )
|
||||
|
||||
local EscortGroup = self.EscortGroup
|
||||
@@ -735,7 +733,7 @@ function ESCORT:_HoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds )
|
||||
|
||||
end
|
||||
|
||||
-- @param #MENUPARAM MenuParam
|
||||
--- @param #MENUPARAM MenuParam
|
||||
function ESCORT:_JoinUpAndFollow( Distance )
|
||||
|
||||
local EscortGroup = self.EscortGroup
|
||||
@@ -768,7 +766,7 @@ function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance )
|
||||
EscortGroup:MessageToClient( "Rejoining and Following at " .. Distance .. "!", 30, EscortClient )
|
||||
end
|
||||
|
||||
-- @param #MENUPARAM MenuParam
|
||||
--- @param #MENUPARAM MenuParam
|
||||
function ESCORT:_Flare( Color, Message )
|
||||
|
||||
local EscortGroup = self.EscortGroup
|
||||
@@ -778,7 +776,7 @@ function ESCORT:_Flare( Color, Message )
|
||||
EscortGroup:MessageToClient( Message, 10, EscortClient )
|
||||
end
|
||||
|
||||
-- @param #MENUPARAM MenuParam
|
||||
--- @param #MENUPARAM MenuParam
|
||||
function ESCORT:_Smoke( Color, Message )
|
||||
|
||||
local EscortGroup = self.EscortGroup
|
||||
@@ -789,7 +787,7 @@ function ESCORT:_Smoke( Color, Message )
|
||||
end
|
||||
|
||||
|
||||
-- @param #MENUPARAM MenuParam
|
||||
--- @param #MENUPARAM MenuParam
|
||||
function ESCORT:_ReportNearbyTargetsNow()
|
||||
|
||||
local EscortGroup = self.EscortGroup
|
||||
@@ -811,12 +809,12 @@ function ESCORT:_SwitchReportNearbyTargets( ReportTargets )
|
||||
self.ReportTargetsScheduler:Schedule( self, self._ReportTargetsScheduler, {}, 1, 30 )
|
||||
end
|
||||
else
|
||||
self.ReportTargetsScheduler:Remove(self.ReportTargetsSchedulerID)
|
||||
routines.removeFunction( self.ReportTargetsScheduler )
|
||||
self.ReportTargetsScheduler = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- @param #MENUPARAM MenuParam
|
||||
--- @param #MENUPARAM MenuParam
|
||||
function ESCORT:_ScanTargets( ScanDuration )
|
||||
|
||||
local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP
|
||||
@@ -846,7 +844,7 @@ function ESCORT:_ScanTargets( ScanDuration )
|
||||
|
||||
end
|
||||
|
||||
-- @param Wrapper.Group#GROUP EscortGroup
|
||||
--- @param Wrapper.Group#GROUP EscortGroup
|
||||
function _Resume( EscortGroup )
|
||||
env.info( '_Resume' )
|
||||
|
||||
@@ -858,7 +856,7 @@ function _Resume( EscortGroup )
|
||||
|
||||
end
|
||||
|
||||
-- @param #ESCORT self
|
||||
--- @param #ESCORT self
|
||||
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
|
||||
function ESCORT:_AttackTarget( DetectedItem )
|
||||
|
||||
@@ -879,7 +877,7 @@ function ESCORT:_AttackTarget( DetectedItem )
|
||||
local Tasks = {}
|
||||
|
||||
DetectedSet:ForEachUnit(
|
||||
-- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
--- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
function( DetectedUnit, Tasks )
|
||||
if DetectedUnit:IsAlive() then
|
||||
Tasks[#Tasks+1] = EscortGroup:TaskAttackUnit( DetectedUnit )
|
||||
@@ -902,7 +900,7 @@ function ESCORT:_AttackTarget( DetectedItem )
|
||||
local Tasks = {}
|
||||
|
||||
DetectedSet:ForEachUnit(
|
||||
-- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
--- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
function( DetectedUnit, Tasks )
|
||||
if DetectedUnit:IsAlive() then
|
||||
Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 )
|
||||
@@ -923,7 +921,7 @@ function ESCORT:_AttackTarget( DetectedItem )
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #ESCORT self
|
||||
--- @param #ESCORT self
|
||||
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
|
||||
function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
|
||||
|
||||
@@ -941,7 +939,7 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
|
||||
local Tasks = {}
|
||||
|
||||
DetectedSet:ForEachUnit(
|
||||
-- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
--- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
function( DetectedUnit, Tasks )
|
||||
if DetectedUnit:IsAlive() then
|
||||
Tasks[#Tasks+1] = EscortGroupAttack:TaskAttackUnit( DetectedUnit )
|
||||
@@ -963,7 +961,7 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
|
||||
local Tasks = {}
|
||||
|
||||
DetectedSet:ForEachUnit(
|
||||
-- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
--- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
function( DetectedUnit, Tasks )
|
||||
if DetectedUnit:IsAlive() then
|
||||
Tasks[#Tasks+1] = EscortGroupAttack:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 )
|
||||
@@ -983,7 +981,7 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
|
||||
|
||||
end
|
||||
|
||||
-- @param #MENUPARAM MenuParam
|
||||
--- @param #MENUPARAM MenuParam
|
||||
function ESCORT:_ROE( EscortROEFunction, EscortROEMessage )
|
||||
|
||||
local EscortGroup = self.EscortGroup
|
||||
@@ -993,7 +991,7 @@ function ESCORT:_ROE( EscortROEFunction, EscortROEMessage )
|
||||
EscortGroup:MessageToClient( EscortROEMessage, 10, EscortClient )
|
||||
end
|
||||
|
||||
-- @param #MENUPARAM MenuParam
|
||||
--- @param #MENUPARAM MenuParam
|
||||
function ESCORT:_ROT( EscortROTFunction, EscortROTMessage )
|
||||
|
||||
local EscortGroup = self.EscortGroup
|
||||
@@ -1003,7 +1001,7 @@ function ESCORT:_ROT( EscortROTFunction, EscortROTMessage )
|
||||
EscortGroup:MessageToClient( EscortROTMessage, 10, EscortClient )
|
||||
end
|
||||
|
||||
-- @param #MENUPARAM MenuParam
|
||||
--- @param #MENUPARAM MenuParam
|
||||
function ESCORT:_ResumeMission( WayPoint )
|
||||
|
||||
local EscortGroup = self.EscortGroup
|
||||
@@ -1038,7 +1036,7 @@ function ESCORT:RegisterRoute()
|
||||
return TaskPoints
|
||||
end
|
||||
|
||||
-- @param Functional.Escort#ESCORT self
|
||||
--- @param Functional.Escort#ESCORT self
|
||||
function ESCORT:_FollowScheduler()
|
||||
self:F( { self.FollowDistance } )
|
||||
|
||||
@@ -1154,6 +1152,8 @@ function ESCORT:_ReportTargetsScheduler()
|
||||
|
||||
if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then
|
||||
|
||||
if true then
|
||||
|
||||
local EscortGroupName = self.EscortGroup:GetName()
|
||||
|
||||
self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
|
||||
@@ -1224,6 +1224,177 @@ function ESCORT:_ReportTargetsScheduler()
|
||||
end
|
||||
|
||||
return true
|
||||
else
|
||||
-- local EscortGroupName = self.EscortGroup:GetName()
|
||||
-- local EscortTargets = self.EscortGroup:GetDetectedTargets()
|
||||
--
|
||||
-- local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets
|
||||
--
|
||||
-- local EscortTargetMessages = ""
|
||||
-- for EscortTargetID, EscortTarget in pairs( EscortTargets ) do
|
||||
-- local EscortObject = EscortTarget.object
|
||||
-- self:T( EscortObject )
|
||||
-- if EscortObject and EscortObject:isExist() and EscortObject.id_ < 50000000 then
|
||||
--
|
||||
-- local EscortTargetUnit = UNIT:Find( EscortObject )
|
||||
-- local EscortTargetUnitName = EscortTargetUnit:GetName()
|
||||
--
|
||||
--
|
||||
--
|
||||
-- -- local EscortTargetIsDetected,
|
||||
-- -- EscortTargetIsVisible,
|
||||
-- -- EscortTargetLastTime,
|
||||
-- -- EscortTargetKnowType,
|
||||
-- -- EscortTargetKnowDistance,
|
||||
-- -- EscortTargetLastPos,
|
||||
-- -- EscortTargetLastVelocity
|
||||
-- -- = self.EscortGroup:IsTargetDetected( EscortObject )
|
||||
-- --
|
||||
-- -- self:T( { EscortTargetIsDetected,
|
||||
-- -- EscortTargetIsVisible,
|
||||
-- -- EscortTargetLastTime,
|
||||
-- -- EscortTargetKnowType,
|
||||
-- -- EscortTargetKnowDistance,
|
||||
-- -- EscortTargetLastPos,
|
||||
-- -- EscortTargetLastVelocity } )
|
||||
--
|
||||
--
|
||||
-- local EscortTargetUnitVec3 = EscortTargetUnit:GetVec3()
|
||||
-- local EscortVec3 = self.EscortGroup:GetVec3()
|
||||
-- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 +
|
||||
-- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 +
|
||||
-- ( EscortTargetUnitVec3.z - EscortVec3.z )^2
|
||||
-- ) ^ 0.5 / 1000
|
||||
--
|
||||
-- self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } )
|
||||
--
|
||||
-- if Distance <= 15 then
|
||||
--
|
||||
-- if not ClientEscortTargets[EscortTargetUnitName] then
|
||||
-- ClientEscortTargets[EscortTargetUnitName] = {}
|
||||
-- end
|
||||
-- ClientEscortTargets[EscortTargetUnitName].AttackUnit = EscortTargetUnit
|
||||
-- ClientEscortTargets[EscortTargetUnitName].visible = EscortTarget.visible
|
||||
-- ClientEscortTargets[EscortTargetUnitName].type = EscortTarget.type
|
||||
-- ClientEscortTargets[EscortTargetUnitName].distance = EscortTarget.distance
|
||||
-- else
|
||||
-- if ClientEscortTargets[EscortTargetUnitName] then
|
||||
-- ClientEscortTargets[EscortTargetUnitName] = nil
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- self:T( { "Sorting Targets Table:", ClientEscortTargets } )
|
||||
-- table.sort( ClientEscortTargets, function( a, b ) return a.Distance < b.Distance end )
|
||||
-- self:T( { "Sorted Targets Table:", ClientEscortTargets } )
|
||||
--
|
||||
-- -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup.
|
||||
-- self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
|
||||
--
|
||||
-- if self.EscortMenuTargetAssistance then
|
||||
-- self.EscortMenuTargetAssistance:RemoveSubMenus()
|
||||
-- end
|
||||
--
|
||||
-- --for MenuIndex = 1, #self.EscortMenuAttackTargets do
|
||||
-- -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } )
|
||||
-- -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove()
|
||||
-- --end
|
||||
--
|
||||
--
|
||||
-- if ClientEscortTargets then
|
||||
-- for ClientEscortTargetUnitName, ClientEscortTargetData in pairs( ClientEscortTargets ) do
|
||||
--
|
||||
-- for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do
|
||||
--
|
||||
-- if ClientEscortTargetData and ClientEscortTargetData.AttackUnit:IsAlive() then
|
||||
--
|
||||
-- local EscortTargetMessage = ""
|
||||
-- local EscortTargetCategoryName = ClientEscortTargetData.AttackUnit:GetCategoryName()
|
||||
-- local EscortTargetCategoryType = ClientEscortTargetData.AttackUnit:GetTypeName()
|
||||
-- if ClientEscortTargetData.type then
|
||||
-- EscortTargetMessage = EscortTargetMessage .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") at "
|
||||
-- else
|
||||
-- EscortTargetMessage = EscortTargetMessage .. "Unknown target at "
|
||||
-- end
|
||||
--
|
||||
-- local EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3()
|
||||
-- local EscortVec3 = self.EscortGroup:GetVec3()
|
||||
-- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 +
|
||||
-- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 +
|
||||
-- ( EscortTargetUnitVec3.z - EscortVec3.z )^2
|
||||
-- ) ^ 0.5 / 1000
|
||||
--
|
||||
-- self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } )
|
||||
-- if ClientEscortTargetData.visible == false then
|
||||
-- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " estimated km"
|
||||
-- else
|
||||
-- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km"
|
||||
-- end
|
||||
--
|
||||
-- if ClientEscortTargetData.visible then
|
||||
-- EscortTargetMessage = EscortTargetMessage .. ", visual"
|
||||
-- end
|
||||
--
|
||||
-- if ClientEscortGroupName == EscortGroupName then
|
||||
--
|
||||
-- MENU_GROUP_COMMAND:New( self.EscortClient,
|
||||
-- EscortTargetMessage,
|
||||
-- self.EscortMenuAttackNearbyTargets,
|
||||
-- ESCORT._AttackTarget,
|
||||
-- { ParamSelf = self,
|
||||
-- ParamUnit = ClientEscortTargetData.AttackUnit
|
||||
-- }
|
||||
-- )
|
||||
-- EscortTargetMessages = EscortTargetMessages .. "\n - " .. EscortTargetMessage
|
||||
-- else
|
||||
-- if self.EscortMenuTargetAssistance then
|
||||
-- local MenuTargetAssistance = MENU_GROUP:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance )
|
||||
-- MENU_GROUP_COMMAND:New( self.EscortClient,
|
||||
-- EscortTargetMessage,
|
||||
-- MenuTargetAssistance,
|
||||
-- ESCORT._AssistTarget,
|
||||
-- self,
|
||||
-- EscortGroupData.EscortGroup,
|
||||
-- ClientEscortTargetData.AttackUnit
|
||||
-- )
|
||||
-- end
|
||||
-- end
|
||||
-- else
|
||||
-- ClientEscortTargetData = nil
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- if EscortTargetMessages ~= "" and self.ReportTargets == true then
|
||||
-- self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient )
|
||||
-- else
|
||||
-- self.EscortGroup:MessageToClient( "No targets detected!", 20, self.EscortClient )
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- if self.EscortMenuResumeMission then
|
||||
-- self.EscortMenuResumeMission:RemoveSubMenus()
|
||||
--
|
||||
-- -- if self.EscortMenuResumeWayPoints then
|
||||
-- -- for MenuIndex = 1, #self.EscortMenuResumeWayPoints do
|
||||
-- -- self:T( { "Remove Menu:", self.EscortMenuResumeWayPoints[MenuIndex] } )
|
||||
-- -- self.EscortMenuResumeWayPoints[MenuIndex] = self.EscortMenuResumeWayPoints[MenuIndex]:Remove()
|
||||
-- -- end
|
||||
-- -- end
|
||||
--
|
||||
-- local TaskPoints = self:RegisterRoute()
|
||||
-- for WayPointID, WayPoint in pairs( TaskPoints ) do
|
||||
-- local EscortVec3 = self.EscortGroup:GetVec3()
|
||||
-- local Distance = ( ( WayPoint.x - EscortVec3.x )^2 +
|
||||
-- ( WayPoint.y - EscortVec3.z )^2
|
||||
-- ) ^ 0.5 / 1000
|
||||
-- MENU_GROUP_COMMAND:New( self.EscortClient, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", self.EscortMenuResumeMission, ESCORT._ResumeMission, { ParamSelf = self, ParamWayPoint = WayPointID } )
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
@@ -0,0 +1,730 @@
|
||||
--- **Functional** - (R2.5) - Monitor and export flight model data.
|
||||
--
|
||||
-- Record flight data and export it to a csv file.
|
||||
--
|
||||
-- ## Recorded Data:
|
||||
--
|
||||
-- * Time,
|
||||
-- * Altitude,
|
||||
-- * Temperature,
|
||||
-- * Pressure,
|
||||
-- * Velocity (total and x,y,z components),
|
||||
-- * Acceleration (total and x,y,z components),
|
||||
-- * AoA,
|
||||
-- * Pitch and pitch rate,
|
||||
-- * Roll and roll rate,
|
||||
-- * Yaw and yaw rate,
|
||||
-- * Turn rate.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.FMData
|
||||
-- @image Functional_Rat2.png
|
||||
|
||||
|
||||
--- FMD class.
|
||||
-- @type FMD
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table players Player table.
|
||||
-- @field #table menuadded Table of units where the F10 radio menu was added.
|
||||
-- @field #string savepath Path to save data files.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Be sure!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The FMD Concept
|
||||
--
|
||||
-- This class can be used to record flight data such as velocity, acceleration, pitch, roll, yaw and turn rates. The output is written to a csv file for later analysis.
|
||||
--
|
||||
-- # Usage
|
||||
--
|
||||
-- The script is very easy to use. It only requires the line
|
||||
--
|
||||
-- fmd=FMD:New()
|
||||
--
|
||||
-- This will automatically start the FMD script.
|
||||
--
|
||||
-- Each player will get an entry "FMD" in the F-10 radio menu. There he can start or stop the data recording.
|
||||
--
|
||||
-- **IMPORTANT**
|
||||
-- Due to a DCS bug, it is necessary that (in single player mode), player/clients have to **hit ESC twice** before entering an aircraft client slot.
|
||||
-- Otherwise, the script will not load and now menus will be created for the player.
|
||||
--
|
||||
-- # Output
|
||||
--
|
||||
-- After the data recording is completed, the data is written to a csv file. The file name starts with **FMD** and contains the employed airframe plus a running number.
|
||||
--
|
||||
-- However, one must desanitize the **io** and **lfs** lines the DCS root directory. Otherwise, DCS will not allow data to be written to file.
|
||||
--
|
||||
-- @field #FMD
|
||||
FMD = {
|
||||
ClassName = "FMD",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
players = {},
|
||||
menuadded = {},
|
||||
savepath = nil,
|
||||
}
|
||||
|
||||
--- Player data table.
|
||||
-- @type FMD.PlayerData
|
||||
-- @field Core.Scheduler#SCHEDULER scheduler Scheduler
|
||||
-- @field Wrapper.Unit#UNIT unit Player unit.
|
||||
-- @field #string unitname Name of the unit.
|
||||
-- @field Wrapper.Client#CLIENT client Player client.
|
||||
-- @field #string actype Aircraft type.
|
||||
-- @field #string name Player name.
|
||||
-- @field #number dt Time step for data recording in seconds. Default 0.01 sec ==> 100 data points per second!
|
||||
-- @field #number rd Recording duration in seconds.
|
||||
-- @field #boolean recording If true, recording started.
|
||||
-- @field #table data Data table.
|
||||
-- @field #number SID Scheduler ID.
|
||||
|
||||
--- Data point table.
|
||||
-- @type FMD.DataPoint
|
||||
-- @field #number time Abs mission time.
|
||||
-- @field #number T Temperature in degrees Celsius.
|
||||
-- @field #number P Pressure.
|
||||
-- @field #number Alt Altitude ASL in meters.
|
||||
-- @field #number AoA Angle of Attack in degrees.
|
||||
-- @field #number Pitch Pitch angle in degrees.
|
||||
-- @field #number Roll Roll angle in degrees.
|
||||
-- @field #number Yaw Yaw angle in degrees.
|
||||
-- @field #number Climbrate Climb rate in m/s.
|
||||
-- @field #number Vtot Total velocity in m/s.
|
||||
-- @field DCS#Vec3 v Velocity vector. Components x,y,z in m/s.
|
||||
-- @field DCS#Vec3 o Orientation vector. Components x,y,z in meters.
|
||||
-- @field #number omega Angle velocity in m/s.
|
||||
-- @field #number Hdg Heading in degrees.
|
||||
-- @field #number Atot Total acceleration m/s^2.
|
||||
-- @field DCS#Vec3 a Acceleration vector.
|
||||
-- @field #number DRoll Roll speed in degrees/second.
|
||||
-- @field #number DPitch Pitch speed in degrees/second.
|
||||
-- @field #number DYaw Yaw speed in degrees/second.
|
||||
|
||||
--- Main group level radio menu: F10 Other/FMD.
|
||||
-- @field #table MenuF10
|
||||
FMD.MenuF10={}
|
||||
|
||||
--- FMD mission level F10 root menu.
|
||||
-- @field #table MenuF10Root
|
||||
FMD.MenuF10Root=nil
|
||||
|
||||
--- FMD script version.
|
||||
-- @field #string version
|
||||
FMD.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FMD class object.
|
||||
-- @param #FMD self
|
||||
-- @return #FMD self.
|
||||
function FMD:New()
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #FMD
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Log string.
|
||||
self.lid="FMD | "
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start FMD script.
|
||||
self:AddTransition("*", "Status", "*") -- Start FMD script.
|
||||
|
||||
-- Start FMD.
|
||||
self:Start()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- On after Start event.
|
||||
-- @param #FMD self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FMD:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting Flight Model Data script version %s", FMD.version)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Handle events.
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
end
|
||||
|
||||
--- On after Stop event.
|
||||
-- @param #FMD self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FMD:onafterStop(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Stopping Flight Model Data script version %s", FMD.version)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Handle events.
|
||||
self:UnHandleEvent(EVENTS.Birth)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- FMD event handler for event birth.
|
||||
-- @param #FMD self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function FMD:OnEventBirth(EventData)
|
||||
self:F3({eventbirth = EventData})
|
||||
|
||||
local _unitName=EventData.IniUnitName
|
||||
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
self:T2(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
|
||||
self:T2(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
|
||||
self:T2(self.lid.."BIRTH: player = "..tostring(_playername))
|
||||
|
||||
if _unit and _playername then
|
||||
|
||||
local _uid=_unit:GetID()
|
||||
local _group=_unit:GetGroup()
|
||||
local _callsign=_unit:GetCallsign()
|
||||
|
||||
-- Debug output.
|
||||
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", _playername, _callsign, _unitName, _group:GetName())
|
||||
self:T(self.lid..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
-- Add Menu commands.
|
||||
self:_AddF10Commands(_unitName)
|
||||
|
||||
-- Player data.
|
||||
self.players[_playername]={}
|
||||
|
||||
local player=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
player.scheduler=SCHEDULER:New()
|
||||
player.unit=_unit
|
||||
player.unitname=_unitName
|
||||
player.name=_playername
|
||||
player.client=CLIENT:FindByName(_unitName, nil, true)
|
||||
player.actype=_unit:GetTypeName()
|
||||
player.dt=0.01
|
||||
player.rd=30
|
||||
player.recording=false
|
||||
player.data={}
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Data Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get data of unit.
|
||||
-- @param #FMD self
|
||||
-- @param Wrapper.Unit#UNIT unit
|
||||
-- @return #FMD.DataPoint Datapoint.
|
||||
function FMD:_GetDataPoint(unit)
|
||||
|
||||
if unit and unit:IsAlive() then
|
||||
|
||||
-- Current coordinate.
|
||||
local coord=unit:GetCoordinate()
|
||||
|
||||
local dp={} --#FMD.DataPoint
|
||||
|
||||
dp.a={}
|
||||
dp.Alt=coord.y
|
||||
dp.AoA=unit:GetAoA()
|
||||
dp.Atot=nil
|
||||
dp.P=coord:GetPressure()
|
||||
dp.Pitch=unit:GetPitch()
|
||||
dp.Roll=unit:GetRoll()
|
||||
dp.time=timer.getAbsTime()
|
||||
dp.T=coord:GetTemperature()
|
||||
dp.v=unit:GetVelocityVec3()
|
||||
dp.Vtot=UTILS.VecNorm(dp.v)
|
||||
dp.Yaw=unit:GetYaw()
|
||||
dp.o=unit:GetOrientationX()
|
||||
dp.Hdg=unit:GetHeading()
|
||||
|
||||
return dp
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get data of unit.
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data.
|
||||
-- @return #FMD.DataPoint Datapoint.
|
||||
function FMD:_Derivative(playerData)
|
||||
|
||||
local function numderiv(fpm,fpp,h)
|
||||
return 0.5*(fpp-fpm)/h
|
||||
end
|
||||
|
||||
local datapoints=playerData.data
|
||||
--local dt=playerData.dt
|
||||
|
||||
for i=2,#datapoints-1 do
|
||||
|
||||
local dpm=datapoints[i-1] --#FMD.DataPoint
|
||||
local dpp=datapoints[i+1] --#FMD.DataPoint
|
||||
local dpi=datapoints[i] --#FMD.DataPoint
|
||||
|
||||
-- Time step.
|
||||
local dt=0.5*(dpp.time-dpm.time)
|
||||
|
||||
dpi.Atot=numderiv(dpm.Vtot, dpp.Vtot, dt)
|
||||
|
||||
dpi.a.x=numderiv(dpm.v.x, dpp.v.x, dt)
|
||||
dpi.a.y=numderiv(dpm.v.y, dpp.v.y, dt)
|
||||
dpi.a.z=numderiv(dpm.v.z, dpp.v.z, dt)
|
||||
|
||||
dpi.DPitch=numderiv(dpm.Pitch, dpp.Pitch, dt)
|
||||
--dpi.DRoll=numderiv(dpm.Roll, dpp.Roll, dt)
|
||||
dpi.DYaw=numderiv(dpm.Yaw, dpp.Yaw, dt)
|
||||
|
||||
-- Roll shortcuts.
|
||||
local r1=dpm.Roll
|
||||
local r2=dpp.Roll
|
||||
|
||||
-- Put roll in [0,360)
|
||||
if (r1<0) then
|
||||
r1=r1+360
|
||||
end
|
||||
if (r2<0) then
|
||||
r2=r2+360
|
||||
end
|
||||
|
||||
-- Handle case where 360 deg periodicity strikes.
|
||||
if r1<90 and r2>270 then
|
||||
r1=r1+360
|
||||
end
|
||||
if r1>270 and r2<90 then
|
||||
r2=r2+360
|
||||
end
|
||||
|
||||
--
|
||||
dpi.DRoll=numderiv(r1, r2, dt)
|
||||
|
||||
local ang=UTILS.VecAngle(dpm.o, dpp.o)
|
||||
dpi.omega=numderiv(0, ang, dt)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Record data
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data table.
|
||||
function FMD:_RecordData(playerData)
|
||||
|
||||
-- Check if we are recording already.
|
||||
if not playerData.recording then
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording starts for %.1f sec.", playerData.rd)
|
||||
MESSAGE:New(text, 3, "FMD"):ToClient(playerData.client)
|
||||
|
||||
-- Activate recording switch.
|
||||
playerData.recording=true
|
||||
end
|
||||
|
||||
-- Check if unit is alive.
|
||||
if playerData.unit and playerData.unit:IsAlive() then
|
||||
|
||||
-- Get data point.
|
||||
local dp=self:_GetDataPoint(playerData.unit)
|
||||
|
||||
-- Add data point to player table.
|
||||
table.insert(playerData.data, dp)
|
||||
|
||||
else
|
||||
-- Stop recording if player unit is not alive.
|
||||
self:_StopRecording(playerData.unitname)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Save data.
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data table.
|
||||
function FMD:_SaveData(playerData)
|
||||
|
||||
-- Nothing to save.
|
||||
if playerData==nil or #playerData.data==0 or not io then
|
||||
return
|
||||
end
|
||||
|
||||
--- Function that saves data to file
|
||||
local function _savefile(filename, data)
|
||||
local f = assert(io.open(filename, "wb"))
|
||||
f:write(data)
|
||||
f:close()
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
local path=self.savepath
|
||||
if lfs then
|
||||
path=path or lfs.writedir()
|
||||
end
|
||||
|
||||
-- Create unused file name.
|
||||
local filename=nil
|
||||
for i=1,9999 do
|
||||
|
||||
-- Create file name.
|
||||
filename=string.format("FMD-%s_%s-%04d.csv", playerData.name, playerData.actype, i)
|
||||
|
||||
-- Set path.
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
-- Check if file exists.
|
||||
local _exists=UTILS.FileExists(filename)
|
||||
if not _exists then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Info
|
||||
local text=string.format("Saving player %s flight data to file %s", playerData.name, filename)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Header line
|
||||
local data="#Time,Altitude,Temperature,Pressure,Vtot,Vx,Vy,Vz,Atot,ax,ay,az,AoA,Pitch,dPitch/dt,Roll,dRoll/dt,Yaw,dYaw/dt,Turn Rate\n"
|
||||
|
||||
local g0=playerData.data[1] --#FMD.DataPoint
|
||||
local T0=g0.time
|
||||
|
||||
-- Calculate derivatives.
|
||||
self:_Derivative(playerData)
|
||||
|
||||
for i=2,#playerData.data-1 do
|
||||
|
||||
local dp=playerData.data[i] --#FMD.DataPoint
|
||||
|
||||
-- Conversion m/s == Mach.
|
||||
local ms2mach=0.00291545
|
||||
|
||||
local t=(dp.time-T0) or 0
|
||||
local a=dp.Alt or 0
|
||||
local b=dp.T or 0
|
||||
local c=dp.P or 0
|
||||
local d=dp.Vtot or 0
|
||||
local e=dp.v.x or 0
|
||||
local f=dp.v.y or 0
|
||||
local g=dp.v.z or 0
|
||||
local h=dp.Atot or 0
|
||||
local i=dp.a.x or 0
|
||||
local j=dp.a.y or 0
|
||||
local k=dp.a.z or 0
|
||||
local l=dp.AoA or 0
|
||||
local m=dp.Pitch or 0
|
||||
local n=dp.DPitch or 0
|
||||
local roll=dp.Roll or 0
|
||||
if roll<0 then
|
||||
roll=roll+360
|
||||
end
|
||||
local o=roll --dp.Roll or 0
|
||||
local p=dp.DRoll or 0
|
||||
local q=dp.Yaw or 0
|
||||
local r=dp.DYaw or 0
|
||||
local s=dp.omega or 0
|
||||
|
||||
-- Debug output.
|
||||
self:T3(t)
|
||||
self:T3(a)
|
||||
self:T3(b)
|
||||
self:T3(c)
|
||||
self:T3(d)
|
||||
self:T3(e)
|
||||
self:T3(f)
|
||||
self:T3(g)
|
||||
self:T3(h)
|
||||
self:T3(i)
|
||||
self:T3(j)
|
||||
self:T3(k)
|
||||
self:T3(l)
|
||||
self:T3(m)
|
||||
self:T3(n)
|
||||
self:T3(o)
|
||||
self:T3(p)
|
||||
self:T3(q)
|
||||
self:T3(r)
|
||||
self:T3(s)
|
||||
|
||||
-- t a b c d e f g h i j k l m n o p q r s
|
||||
data=data..string.format("%.2f,%.2f,%.2f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f\n",
|
||||
t, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
|
||||
|
||||
end
|
||||
|
||||
-- Save file.
|
||||
_savefile(filename, data)
|
||||
|
||||
-- Clear data.
|
||||
playerData.data={}
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- RADIO MENU Functions
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add menu commands for player.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_AddF10Commands(_unitName)
|
||||
self:F(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check for player unit.
|
||||
if _unit and playername then
|
||||
|
||||
-- Get group and ID.
|
||||
local group=_unit:GetGroup()
|
||||
local gid=group:GetID()
|
||||
|
||||
if group and gid then
|
||||
|
||||
if not self.menuadded[gid] then
|
||||
|
||||
-- Enable switch so we don't do this twice.
|
||||
self.menuadded[gid]=true
|
||||
|
||||
-- Set menu root path.
|
||||
local _rootPath=nil
|
||||
if FMD.MenuF10Root then
|
||||
------------------------
|
||||
-- MISSON LEVEL MENUE --
|
||||
------------------------
|
||||
|
||||
-- F10/FMD/...
|
||||
_rootPath=FMD.MenuF10Root
|
||||
|
||||
else
|
||||
------------------------
|
||||
-- GROUP LEVEL MENUES --
|
||||
------------------------
|
||||
|
||||
-- Main F10 menu: F10/FMD/
|
||||
if FMD.MenuF10[gid]==nil then
|
||||
FMD.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "FMD")
|
||||
end
|
||||
|
||||
|
||||
-- F10/FMD/...
|
||||
_rootPath=FMD.MenuF10[gid]
|
||||
|
||||
end
|
||||
|
||||
--------------------------------
|
||||
-- F10/F<X> FMD/F1 Time Interval
|
||||
--------------------------------
|
||||
local _timePath=missionCommands.addSubMenuForGroup(gid, "Time Interval", _rootPath)
|
||||
-- F10/FMD/F1 Time Interval/
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=0.01 s", _timePath, self._SetTimeInterval, self, _unitName, 0.01)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=0.1 s", _timePath, self._SetTimeInterval, self, _unitName, 0.1)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=1.0 s", _timePath, self._SetTimeInterval, self, _unitName, 1.0)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=10 s", _timePath, self._SetTimeInterval, self, _unitName, 10.0)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=30 s", _timePath, self._SetTimeInterval, self, _unitName, 30.0)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=60 s", _timePath, self._SetTimeInterval, self, _unitName, 60.0)
|
||||
|
||||
-------------------------------
|
||||
-- F10/F<X> FMD/F1 Rec Duration
|
||||
-------------------------------
|
||||
local _durPath=missionCommands.addSubMenuForGroup(gid, "Rec Duration", _rootPath)
|
||||
-- F10/FMD/F1 Rec Duration/
|
||||
missionCommands.addCommandForGroup(gid, "T=10 s", _durPath, self._SetRecDuration, self, _unitName, 10)
|
||||
missionCommands.addCommandForGroup(gid, "T=30 s", _durPath, self._SetRecDuration, self, _unitName, 30)
|
||||
missionCommands.addCommandForGroup(gid, "T=60 s", _durPath, self._SetRecDuration, self, _unitName, 60)
|
||||
missionCommands.addCommandForGroup(gid, "T=5 min", _durPath, self._SetRecDuration, self, _unitName, 5*60)
|
||||
missionCommands.addCommandForGroup(gid, "T=10 min", _durPath, self._SetRecDuration, self, _unitName, 10*60)
|
||||
|
||||
--------------------------------
|
||||
-- F10/F<X> FMD/
|
||||
--------------------------------
|
||||
|
||||
-- F10/FMD/Start Recording
|
||||
missionCommands.addCommandForGroup(gid, "Start Recording", _rootPath, self._StartRecording, self, _unitName) -- F1
|
||||
missionCommands.addCommandForGroup(gid, "Stop Recording", _rootPath, self._StopRecording, self, _unitName) -- F2
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set recording duration.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
-- @param #number rd Recording duration in sec.
|
||||
function FMD:_SetRecDuration(_unitName, rd)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Set dt.
|
||||
playerData.rd=rd
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording duration set to %.1f sec.", playerData.rd)
|
||||
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set time interval for data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
-- @param #number dt Time interval in seconds.
|
||||
function FMD:_SetTimeInterval(_unitName, dt)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Set dt.
|
||||
playerData.dt=dt
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording time interval set to %.3f sec.", playerData.dt)
|
||||
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Start data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_StartRecording(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Delay before recording starts.
|
||||
local delay=3
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording will be started in %d seconds with %.3f sec timestep for %.1f sec.", delay, playerData.dt, playerData.rd)
|
||||
MESSAGE:New(text, 3, "FMD"):ToClient(playerData.client)
|
||||
|
||||
-- Start scheduler.
|
||||
playerData.SID=playerData.scheduler:Schedule(nil, self._RecordData, {self, playerData}, delay, playerData.dt, 0.0)
|
||||
|
||||
-- Stop scheduler once.
|
||||
playerData.scheduler:ScheduleOnce(playerData.rd+delay, self._StopRecording, self,_unitName)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Stop data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_StopRecording(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
local ndata=#playerData.data
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording stopped. Data points recorded: %d", ndata)
|
||||
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
|
||||
|
||||
-- No recording switch.
|
||||
playerData.recording=false
|
||||
|
||||
-- Stop scheduler.
|
||||
playerData.scheduler:Stop(playerData.SID)
|
||||
|
||||
-- Save data.
|
||||
self:_SaveData(playerData)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of the player unit.
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @return #string Name of the player or nil.
|
||||
function FMD:_GetPlayerUnitAndName(_unitName)
|
||||
self:F2(_unitName)
|
||||
|
||||
if _unitName ~= nil then
|
||||
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit=Unit.getByName(_unitName)
|
||||
|
||||
if DCSunit then
|
||||
|
||||
local playername=DCSunit:getPlayerName()
|
||||
local unit=UNIT:Find(DCSunit)
|
||||
|
||||
self:T2({DCSunit=DCSunit, unit=unit, playername=playername})
|
||||
if DCSunit and unit and playername then
|
||||
return unit, playername
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Return nil if we could not find a player.
|
||||
return nil,nil
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,724 @@
|
||||
--- **Functional** -- Train missile defence and deflection.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range
|
||||
-- * Provide alerts when a missile would have killed your aircraft.
|
||||
-- * Provide alerts when the missile self destructs.
|
||||
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft,
|
||||
-- the class will destroy the missile within a certain range, to avoid damage to your aircraft.
|
||||
--
|
||||
-- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players:
|
||||
--
|
||||
-- * **Messages**: Menu to configure all messages.
|
||||
-- * **Messages On**: Show all messages.
|
||||
-- * **Messages Off**: Disable all messages.
|
||||
-- * **Tracking**: Menu to configure missile tracking messages.
|
||||
-- * **To All**: Shows missile tracking messages to all players.
|
||||
-- * **To Target**: Shows missile tracking messages only to the player where the missile is targetted at.
|
||||
-- * **Tracking On**: Show missile tracking messages.
|
||||
-- * **Tracking Off**: Disable missile tracking messages.
|
||||
-- * **Frequency Increase**: Increases the missile tracking message frequency with one second.
|
||||
-- * **Frequency Decrease**: Decreases the missile tracking message frequency with one second.
|
||||
-- * **Alerts**: Menu to configure alert messages.
|
||||
-- * **To All**: Shows alert messages to all players.
|
||||
-- * **To Target**: Shows alert messages only to the player where the missile is (was) targetted at.
|
||||
-- * **Hits On**: Show missile hit alert messages.
|
||||
-- * **Hits Off**: Disable missile hit alert messages.
|
||||
-- * **Launches On**: Show missile launch messages.
|
||||
-- * **Launches Off**: Disable missile launch messages.
|
||||
-- * **Details**: Menu to configure message details.
|
||||
-- * **Range On**: Shows range information when a missile is fired to a target.
|
||||
-- * **Range Off**: Disable range information when a missile is fired to a target.
|
||||
-- * **Bearing On**: Shows bearing information when a missile is fired to a target.
|
||||
-- * **Bearing Off**: Disable bearing information when a missile is fired to a target.
|
||||
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
|
||||
-- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter.
|
||||
-- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter.
|
||||
-- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter.
|
||||
-- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
|
||||
-- Danny has shared his ideas and together we made a design.
|
||||
-- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback!
|
||||
-- * **132nd Squadron**: Testing and optimizing the logic.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Functional.MissileTrainer
|
||||
-- @image Missile_Trainer.JPG
|
||||
|
||||
|
||||
--- @type MISSILETRAINER
|
||||
-- @field Core.Set#SET_CLIENT DBClients
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
---
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
-- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed.
|
||||
--
|
||||
-- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those.
|
||||
--
|
||||
-- # Initialization:
|
||||
--
|
||||
-- A MISSILETRAINER object will behave differently based on the usage of initialization methods:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF.
|
||||
-- * @{#MISSILETRAINER.InitTrackingToAll}: Sets by default the missile tracking report for all players or only for those missiles targetted to you.
|
||||
-- * @{#MISSILETRAINER.InitTrackingOnOff}: Sets by default the display of missile tracking report to be ON or OFF.
|
||||
-- * @{#MISSILETRAINER.InitTrackingFrequency}: Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
|
||||
-- * @{#MISSILETRAINER.InitAlertsToAll}: Sets by default the display of alerts to be shown to all players or only to you.
|
||||
-- * @{#MISSILETRAINER.InitAlertsHitsOnOff}: Sets by default the display of hit alerts ON or OFF.
|
||||
-- * @{#MISSILETRAINER.InitAlertsLaunchesOnOff}: Sets by default the display of launch alerts ON or OFF.
|
||||
-- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu.
|
||||
--
|
||||
-- @field #MISSILETRAINER
|
||||
MISSILETRAINER = {
|
||||
ClassName = "MISSILETRAINER",
|
||||
TrackingMissiles = {},
|
||||
}
|
||||
|
||||
function MISSILETRAINER._Alive( Client, self )
|
||||
|
||||
if self.Briefing then
|
||||
Client:Message( self.Briefing, 15, "Trainer" )
|
||||
end
|
||||
|
||||
if self.MenusOnOff == true then
|
||||
Client:Message( "Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).", 15, "Trainer" )
|
||||
|
||||
Client.MainMenu = MENU_GROUP:New( Client:GetGroup(), "Missile Trainer", nil ) -- Menu#MENU_GROUP
|
||||
|
||||
Client.MenuMessages = MENU_GROUP:New( Client:GetGroup(), "Messages", Client.MainMenu )
|
||||
Client.MenuOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } )
|
||||
Client.MenuOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } )
|
||||
|
||||
Client.MenuTracking = MENU_GROUP:New( Client:GetGroup(), "Tracking", Client.MainMenu )
|
||||
Client.MenuTrackingToAll = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } )
|
||||
Client.MenuTrackingToTarget = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } )
|
||||
Client.MenuTrackOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = true } )
|
||||
Client.MenuTrackOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = false } )
|
||||
Client.MenuTrackIncrease = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Frequency Increase", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = -1 } )
|
||||
Client.MenuTrackDecrease = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Frequency Decrease", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = 1 } )
|
||||
|
||||
Client.MenuAlerts = MENU_GROUP:New( Client:GetGroup(), "Alerts", Client.MainMenu )
|
||||
Client.MenuAlertsToAll = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } )
|
||||
Client.MenuAlertsToTarget = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } )
|
||||
Client.MenuHitsOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = true } )
|
||||
Client.MenuHitsOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = false } )
|
||||
Client.MenuLaunchesOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = true } )
|
||||
Client.MenuLaunchesOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = false } )
|
||||
|
||||
Client.MenuDetails = MENU_GROUP:New( Client:GetGroup(), "Details", Client.MainMenu )
|
||||
Client.MenuDetailsDistanceOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = true } )
|
||||
Client.MenuDetailsDistanceOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = false } )
|
||||
Client.MenuDetailsBearingOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = true } )
|
||||
Client.MenuDetailsBearingOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = false } )
|
||||
|
||||
Client.MenuDistance = MENU_GROUP:New( Client:GetGroup(), "Set distance to plane", Client.MainMenu )
|
||||
Client.MenuDistance50 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "50 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 50 / 1000 } )
|
||||
Client.MenuDistance100 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "100 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 100 / 1000 } )
|
||||
Client.MenuDistance150 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "150 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 150 / 1000 } )
|
||||
Client.MenuDistance200 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "200 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 200 / 1000 } )
|
||||
else
|
||||
if Client.MainMenu then
|
||||
Client.MainMenu:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
local ClientID = Client:GetID()
|
||||
self:T( ClientID )
|
||||
if not self.TrackingMissiles[ClientID] then
|
||||
self.TrackingMissiles[ClientID] = {}
|
||||
end
|
||||
self.TrackingMissiles[ClientID].Client = Client
|
||||
if not self.TrackingMissiles[ClientID].MissileData then
|
||||
self.TrackingMissiles[ClientID].MissileData = {}
|
||||
end
|
||||
end
|
||||
|
||||
--- Creates the main object which is handling missile tracking.
|
||||
-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
|
||||
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
|
||||
-- @return #MISSILETRAINER
|
||||
function MISSILETRAINER:New( Distance, Briefing )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( Distance )
|
||||
|
||||
if Briefing then
|
||||
self.Briefing = Briefing
|
||||
end
|
||||
|
||||
self.Schedulers = {}
|
||||
self.SchedulerID = 0
|
||||
|
||||
self.MessageInterval = 2
|
||||
self.MessageLastTime = timer.getTime()
|
||||
|
||||
self.Distance = Distance / 1000
|
||||
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
|
||||
self.DBClients = SET_CLIENT:New():FilterStart()
|
||||
|
||||
|
||||
-- for ClientID, Client in pairs( self.DBClients.Database ) do
|
||||
-- self:F( "ForEach:" .. Client.UnitName )
|
||||
-- Client:Alive( self._Alive, self )
|
||||
-- end
|
||||
--
|
||||
self.DBClients:ForEachClient(
|
||||
function( Client )
|
||||
self:F( "ForEach:" .. Client.UnitName )
|
||||
Client:Alive( self._Alive, self )
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
|
||||
-- self.DB:ForEachClient(
|
||||
-- --- @param Wrapper.Client#CLIENT Client
|
||||
-- function( Client )
|
||||
--
|
||||
-- ... actions ...
|
||||
--
|
||||
-- end
|
||||
-- )
|
||||
|
||||
self.MessagesOnOff = true
|
||||
|
||||
self.TrackingToAll = false
|
||||
self.TrackingOnOff = true
|
||||
self.TrackingFrequency = 3
|
||||
|
||||
self.AlertsToAll = true
|
||||
self.AlertsHitsOnOff = true
|
||||
self.AlertsLaunchesOnOff = true
|
||||
|
||||
self.DetailsRangeOnOff = true
|
||||
self.DetailsBearingOnOff = true
|
||||
|
||||
self.MenusOnOff = true
|
||||
|
||||
self.TrackingMissiles = {}
|
||||
|
||||
self.TrackingScheduler = SCHEDULER:New( self, self._TrackMissiles, {}, 0.5, 0.05, 0 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Initialization methods.
|
||||
|
||||
|
||||
|
||||
--- Sets by default the display of any message to be ON or OFF.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #boolean MessagesOnOff true or false
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitMessagesOnOff( MessagesOnOff )
|
||||
self:F( MessagesOnOff )
|
||||
|
||||
self.MessagesOnOff = MessagesOnOff
|
||||
if self.MessagesOnOff == true then
|
||||
MESSAGE:New( "Messages ON", 15, "Menu" ):ToAll()
|
||||
else
|
||||
MESSAGE:New( "Messages OFF", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets by default the missile tracking report for all players or only for those missiles targetted to you.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #boolean TrackingToAll true or false
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitTrackingToAll( TrackingToAll )
|
||||
self:F( TrackingToAll )
|
||||
|
||||
self.TrackingToAll = TrackingToAll
|
||||
if self.TrackingToAll == true then
|
||||
MESSAGE:New( "Missile tracking to all players ON", 15, "Menu" ):ToAll()
|
||||
else
|
||||
MESSAGE:New( "Missile tracking to all players OFF", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets by default the display of missile tracking report to be ON or OFF.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #boolean TrackingOnOff true or false
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitTrackingOnOff( TrackingOnOff )
|
||||
self:F( TrackingOnOff )
|
||||
|
||||
self.TrackingOnOff = TrackingOnOff
|
||||
if self.TrackingOnOff == true then
|
||||
MESSAGE:New( "Missile tracking ON", 15, "Menu" ):ToAll()
|
||||
else
|
||||
MESSAGE:New( "Missile tracking OFF", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
|
||||
-- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency )
|
||||
self:F( TrackingFrequency )
|
||||
|
||||
self.TrackingFrequency = self.TrackingFrequency + TrackingFrequency
|
||||
if self.TrackingFrequency < 0.5 then
|
||||
self.TrackingFrequency = 0.5
|
||||
end
|
||||
if self.TrackingFrequency then
|
||||
MESSAGE:New( "Missile tracking frequency is " .. self.TrackingFrequency .. " seconds.", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets by default the display of alerts to be shown to all players or only to you.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #boolean AlertsToAll true or false
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitAlertsToAll( AlertsToAll )
|
||||
self:F( AlertsToAll )
|
||||
|
||||
self.AlertsToAll = AlertsToAll
|
||||
if self.AlertsToAll == true then
|
||||
MESSAGE:New( "Alerts to all players ON", 15, "Menu" ):ToAll()
|
||||
else
|
||||
MESSAGE:New( "Alerts to all players OFF", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets by default the display of hit alerts ON or OFF.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #boolean AlertsHitsOnOff true or false
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitAlertsHitsOnOff( AlertsHitsOnOff )
|
||||
self:F( AlertsHitsOnOff )
|
||||
|
||||
self.AlertsHitsOnOff = AlertsHitsOnOff
|
||||
if self.AlertsHitsOnOff == true then
|
||||
MESSAGE:New( "Alerts Hits ON", 15, "Menu" ):ToAll()
|
||||
else
|
||||
MESSAGE:New( "Alerts Hits OFF", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets by default the display of launch alerts ON or OFF.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #boolean AlertsLaunchesOnOff true or false
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitAlertsLaunchesOnOff( AlertsLaunchesOnOff )
|
||||
self:F( AlertsLaunchesOnOff )
|
||||
|
||||
self.AlertsLaunchesOnOff = AlertsLaunchesOnOff
|
||||
if self.AlertsLaunchesOnOff == true then
|
||||
MESSAGE:New( "Alerts Launches ON", 15, "Menu" ):ToAll()
|
||||
else
|
||||
MESSAGE:New( "Alerts Launches OFF", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets by default the display of range information of missiles ON of OFF.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #boolean DetailsRangeOnOff true or false
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitRangeOnOff( DetailsRangeOnOff )
|
||||
self:F( DetailsRangeOnOff )
|
||||
|
||||
self.DetailsRangeOnOff = DetailsRangeOnOff
|
||||
if self.DetailsRangeOnOff == true then
|
||||
MESSAGE:New( "Range display ON", 15, "Menu" ):ToAll()
|
||||
else
|
||||
MESSAGE:New( "Range display OFF", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets by default the display of bearing information of missiles ON of OFF.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #boolean DetailsBearingOnOff true or false
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitBearingOnOff( DetailsBearingOnOff )
|
||||
self:F( DetailsBearingOnOff )
|
||||
|
||||
self.DetailsBearingOnOff = DetailsBearingOnOff
|
||||
if self.DetailsBearingOnOff == true then
|
||||
MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll()
|
||||
else
|
||||
MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Enables / Disables the menus.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #boolean MenusOnOff true or false
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitMenusOnOff( MenusOnOff )
|
||||
self:F( MenusOnOff )
|
||||
|
||||
self.MenusOnOff = MenusOnOff
|
||||
if self.MenusOnOff == true then
|
||||
MESSAGE:New( "Menus are ENABLED (only when a player rejoins a slot)", 15, "Menu" ):ToAll()
|
||||
else
|
||||
MESSAGE:New( "Menus are DISABLED", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-- Menu functions
|
||||
|
||||
function MISSILETRAINER._MenuMessages( MenuParameters )
|
||||
|
||||
local self = MenuParameters.MenuSelf
|
||||
|
||||
if MenuParameters.MessagesOnOff ~= nil then
|
||||
self:InitMessagesOnOff( MenuParameters.MessagesOnOff )
|
||||
end
|
||||
|
||||
if MenuParameters.TrackingToAll ~= nil then
|
||||
self:InitTrackingToAll( MenuParameters.TrackingToAll )
|
||||
end
|
||||
|
||||
if MenuParameters.TrackingOnOff ~= nil then
|
||||
self:InitTrackingOnOff( MenuParameters.TrackingOnOff )
|
||||
end
|
||||
|
||||
if MenuParameters.TrackingFrequency ~= nil then
|
||||
self:InitTrackingFrequency( MenuParameters.TrackingFrequency )
|
||||
end
|
||||
|
||||
if MenuParameters.AlertsToAll ~= nil then
|
||||
self:InitAlertsToAll( MenuParameters.AlertsToAll )
|
||||
end
|
||||
|
||||
if MenuParameters.AlertsHitsOnOff ~= nil then
|
||||
self:InitAlertsHitsOnOff( MenuParameters.AlertsHitsOnOff )
|
||||
end
|
||||
|
||||
if MenuParameters.AlertsLaunchesOnOff ~= nil then
|
||||
self:InitAlertsLaunchesOnOff( MenuParameters.AlertsLaunchesOnOff )
|
||||
end
|
||||
|
||||
if MenuParameters.DetailsRangeOnOff ~= nil then
|
||||
self:InitRangeOnOff( MenuParameters.DetailsRangeOnOff )
|
||||
end
|
||||
|
||||
if MenuParameters.DetailsBearingOnOff ~= nil then
|
||||
self:InitBearingOnOff( MenuParameters.DetailsBearingOnOff )
|
||||
end
|
||||
|
||||
if MenuParameters.Distance ~= nil then
|
||||
self.Distance = MenuParameters.Distance
|
||||
MESSAGE:New( "Hit detection distance set to " .. ( self.Distance * 1000 ) .. " meters", 15, "Menu" ):ToAll()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function MISSILETRAINER:OnEventShot( EVentData )
|
||||
self:F( { EVentData } )
|
||||
|
||||
local TrainerSourceDCSUnit = EVentData.IniDCSUnit
|
||||
local TrainerSourceDCSUnitName = EVentData.IniDCSUnitName
|
||||
local TrainerWeapon = EVentData.Weapon -- Identify the weapon fired
|
||||
local TrainerWeaponName = EVentData.WeaponName -- return weapon type
|
||||
|
||||
self:T( "Missile Launched = " .. TrainerWeaponName )
|
||||
|
||||
local TrainerTargetDCSUnit = TrainerWeapon:getTarget() -- Identify target
|
||||
if TrainerTargetDCSUnit then
|
||||
local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
|
||||
local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
|
||||
|
||||
self:T(TrainerTargetDCSUnitName )
|
||||
|
||||
local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName )
|
||||
if Client then
|
||||
|
||||
local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit )
|
||||
local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit )
|
||||
|
||||
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched a %s",
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerWeaponName
|
||||
) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" )
|
||||
|
||||
if self.AlertsToAll then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
end
|
||||
|
||||
local ClientID = Client:GetID()
|
||||
self:T( ClientID )
|
||||
local MissileData = {}
|
||||
MissileData.TrainerSourceUnit = TrainerSourceUnit
|
||||
MissileData.TrainerWeapon = TrainerWeapon
|
||||
MissileData.TrainerTargetUnit = TrainerTargetUnit
|
||||
MissileData.TrainerWeaponTypeName = TrainerWeapon:getTypeName()
|
||||
MissileData.TrainerWeaponLaunched = true
|
||||
table.insert( self.TrackingMissiles[ClientID].MissileData, MissileData )
|
||||
--self:T( self.TrackingMissiles )
|
||||
end
|
||||
else
|
||||
-- TODO: some weapons don't know the target unit... Need to develop a workaround for this.
|
||||
if ( TrainerWeapon:getTypeName() == "9M311" ) then
|
||||
SCHEDULER:New( TrainerWeapon, TrainerWeapon.destroy, {}, 1 )
|
||||
else
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MISSILETRAINER:_AddRange( Client, TrainerWeapon )
|
||||
|
||||
local RangeText = ""
|
||||
|
||||
if self.DetailsRangeOnOff then
|
||||
|
||||
local PositionMissile = TrainerWeapon:getPoint()
|
||||
local TargetVec3 = Client:GetVec3()
|
||||
|
||||
local Range = ( ( PositionMissile.x - TargetVec3.x )^2 +
|
||||
( PositionMissile.y - TargetVec3.y )^2 +
|
||||
( PositionMissile.z - TargetVec3.z )^2
|
||||
) ^ 0.5 / 1000
|
||||
|
||||
RangeText = string.format( ", at %4.2fkm", Range )
|
||||
end
|
||||
|
||||
return RangeText
|
||||
end
|
||||
|
||||
function MISSILETRAINER:_AddBearing( Client, TrainerWeapon )
|
||||
|
||||
local BearingText = ""
|
||||
|
||||
if self.DetailsBearingOnOff then
|
||||
|
||||
local PositionMissile = TrainerWeapon:getPoint()
|
||||
local TargetVec3 = Client:GetVec3()
|
||||
|
||||
self:T2( { TargetVec3, PositionMissile })
|
||||
|
||||
local DirectionVector = { x = PositionMissile.x - TargetVec3.x, y = PositionMissile.y - TargetVec3.y, z = PositionMissile.z - TargetVec3.z }
|
||||
local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x )
|
||||
--DirectionRadians = DirectionRadians + routines.getNorthCorrection( PositionTarget )
|
||||
if DirectionRadians < 0 then
|
||||
DirectionRadians = DirectionRadians + 2 * math.pi
|
||||
end
|
||||
local DirectionDegrees = DirectionRadians * 180 / math.pi
|
||||
|
||||
BearingText = string.format( ", %d degrees", DirectionDegrees )
|
||||
end
|
||||
|
||||
return BearingText
|
||||
end
|
||||
|
||||
|
||||
function MISSILETRAINER:_TrackMissiles()
|
||||
self:F2()
|
||||
|
||||
|
||||
local ShowMessages = false
|
||||
if self.MessagesOnOff and self.MessageLastTime + self.TrackingFrequency <= timer.getTime() then
|
||||
self.MessageLastTime = timer.getTime()
|
||||
ShowMessages = true
|
||||
end
|
||||
|
||||
-- ALERTS PART
|
||||
|
||||
-- Loop for all Player Clients to check the alerts and deletion of missiles.
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
local Client = ClientData.Client
|
||||
|
||||
if Client and Client:IsAlive() then
|
||||
|
||||
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
|
||||
self:T3( MissileDataID )
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
local PositionMissile = TrainerWeapon:getPosition().p
|
||||
local TargetVec3 = Client:GetVec3()
|
||||
|
||||
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
|
||||
( PositionMissile.y - TargetVec3.y )^2 +
|
||||
( PositionMissile.z - TargetVec3.z )^2
|
||||
) ^ 0.5 / 1000
|
||||
|
||||
if Distance <= self.Distance then
|
||||
-- Hit alert
|
||||
TrainerWeapon:destroy()
|
||||
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
|
||||
|
||||
self:T( "killed" )
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched by %s killed %s",
|
||||
TrainerWeapon:getTypeName(),
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerTargetUnit:GetPlayerName()
|
||||
), 15, "Hit Alert" )
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
|
||||
MissileData = nil
|
||||
table.remove( ClientData.MissileData, MissileDataID )
|
||||
self:T(ClientData.MissileData)
|
||||
end
|
||||
end
|
||||
else
|
||||
if not ( TrainerWeapon and TrainerWeapon:isExist() ) then
|
||||
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
|
||||
-- Weapon does not exist anymore. Delete from Table
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched by %s self destructed!",
|
||||
TrainerWeaponTypeName,
|
||||
TrainerSourceUnit:GetTypeName()
|
||||
), 5, "Tracking" )
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
end
|
||||
MissileData = nil
|
||||
table.remove( ClientData.MissileData, MissileDataID )
|
||||
self:T( ClientData.MissileData )
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self.TrackingMissiles[ClientDataID] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed.
|
||||
|
||||
-- TRACKING PART
|
||||
|
||||
-- For the current client, the missile range and bearing details are displayed To the Player Client.
|
||||
-- For the other clients, the missile range and bearing details are displayed To the other Player Clients.
|
||||
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
|
||||
|
||||
-- Main Player Client loop
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
local Client = ClientData.Client
|
||||
--self:T2( { Client:GetName() } )
|
||||
|
||||
|
||||
ClientData.MessageToClient = ""
|
||||
ClientData.MessageToAll = ""
|
||||
|
||||
-- Other Players Client loop
|
||||
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
|
||||
--self:T3( MissileDataID )
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
|
||||
if ShowMessages == true then
|
||||
local TrackingTo
|
||||
TrackingTo = string.format( " -> %s",
|
||||
TrainerWeaponTypeName
|
||||
)
|
||||
|
||||
if ClientDataID == TrackingDataID then
|
||||
if ClientData.MessageToClient == "" then
|
||||
ClientData.MessageToClient = "Missiles to You:\n"
|
||||
end
|
||||
ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. "\n"
|
||||
else
|
||||
if self.TrackingToAll == true then
|
||||
if ClientData.MessageToAll == "" then
|
||||
ClientData.MessageToAll = "Missiles to other Players:\n"
|
||||
end
|
||||
ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. " ( " .. TrainerTargetUnit:GetPlayerName() .. " )\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Once the Player Client and the Other Player Client tracking messages are prepared, show them.
|
||||
if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then
|
||||
local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user