94 Commits

Author SHA1 Message Date
Valmo Trindade ccc63d1a35 Show uncovered Rust source lines in CI 2026-08-07 14:39:51 -03:00
Valmo Trindade aa2a088892 Remove unreachable PEM normalization branch 2026-08-07 14:39:30 -03:00
Valmo Trindade 9f560b546a Separate shape parser fixture from point serialization 2026-08-07 14:34:51 -03:00
Valmo Trindade 806e317a55 Separate route parser fixture from point serialization 2026-08-07 14:34:16 -03:00
Valmo Trindade bcb2d1983b Fix zero-length identity test type inference 2026-08-07 14:31:36 -03:00
Valmo Trindade e71fd38b46 Add unit coverage for shape drawing CoT payloads 2026-08-07 14:28:10 -03:00
Valmo Trindade 8278dab9a3 Add unit coverage for route drawing CoT payloads 2026-08-07 14:27:26 -03:00
Valmo Trindade 66a20e7527 Add unit coverage for circle drawing CoT payloads 2026-08-07 14:26:48 -03:00
Valmo Trindade fe8a134897 Add comprehensive unit coverage for UAS CoT payloads 2026-08-07 14:25:37 -03:00
Valmo Trindade 7bb2da2412 Add unit coverage for GeoChat CoT messages 2026-08-07 14:24:31 -03:00
Valmo Trindade fe6bad6e88 Add unit coverage for NATO marker payload compatibility 2026-08-07 14:23:53 -03:00
Valmo Trindade 7374b7222d Add unit coverage for digital pointer CoT payloads 2026-08-07 14:23:29 -03:00
Valmo Trindade 2c6ca81669 Add unit coverage for report marker CoT payloads 2026-08-07 14:23:06 -03:00
Valmo Trindade d799e99cb8 Add unit coverage for EUD CoT payloads 2026-08-07 14:22:47 -03:00
Valmo Trindade e71db4e514 Add unit coverage for external GPS CoT payloads 2026-08-07 14:22:28 -03:00
Valmo Trindade 15e45fff71 Add unit coverage for CoT forced delete payloads 2026-08-07 14:22:10 -03:00
Valmo Trindade 3724a85c9f Document Rust testing and coverage policy 2026-08-07 14:21:38 -03:00
Valmo Trindade 81a5a34cf2 Add unit coverage for laser range finder payloads 2026-08-07 14:21:16 -03:00
Valmo Trindade 26a18eac6d Add unit coverage for UAS payload parsing 2026-08-07 14:20:57 -03:00
Valmo Trindade 7a6c4d5bfc Add full unit coverage for generic CoT serialization 2026-08-07 14:20:03 -03:00
Valmo Trindade ff218d6d00 Add full unit coverage for MAVLink identity helpers 2026-08-07 14:19:25 -03:00
Valmo Trindade d70eb15924 Cover malformed enrollment config parsing 2026-08-07 14:18:31 -03:00
Valmo Trindade ec3507910d Avoid repeated extension logger initialization in tests 2026-08-07 14:14:01 -03:00
Valmo Trindade c941e61e20 Finish Rust CI immediately on test failures 2026-08-07 14:13:20 -03:00
Valmo Trindade 398f11efb8 Add Rust coverage CI gate 2026-08-07 14:11:05 -03:00
Valmo Trindade 95bb4e75e1 Cover MAVLink CRC and packet builders 2026-08-07 14:10:45 -03:00
Valmo Trindade a213fd916c Cover video CoT serialization helpers 2026-08-07 14:10:16 -03:00
Valmo Trindade 74620d3346 Cover TCP connection configuration 2026-08-07 14:09:53 -03:00
Valmo Trindade 5e3e3cb7a0 Use testable TAK enrollment protocol helpers 2026-08-07 14:09:31 -03:00
Valmo Trindade 4432ea20f1 Wire enrollment protocol helpers 2026-08-07 14:09:06 -03:00
Valmo Trindade 395487f26e Extract testable TAK enrollment protocol helpers 2026-08-07 14:07:14 -03:00
Valmo Trindade 6dee1987bb Document TAK authentication flows 2026-08-07 14:06:49 -03:00
valmojr d35ccd58e1 Added authentication got Marti API TLS config fetching 2026-08-06 15:01:08 -03:00
valmojr 980107e00b Improved entity router cycle 2026-08-06 15:00:52 -03:00
valmojr e53ba2ae0f Added addRouterEntities and removeRouterEntites instead of manually adding entites to the CoT Router array 2026-05-25 19:48:51 -03:00
valmojr 37aa51f8c2 Added handler for scripted stuff to not route all sensor data 2026-05-25 03:06:25 -03:00
valmojr ad03444ff5 fixed draw route function data formats 2026-05-25 03:04:28 -03:00
valmojr 167d47e488 Added scope param to all draw functions 2026-05-24 16:08:59 -03:00
valmojr 3a82dba854 Addded draw route rust structs 2026-05-24 16:07:23 -03:00
valmojr 1fff02cdd2 Added Draw Route function 2026-05-24 16:07:04 -03:00
valmojr 1ab318c279 Added register and delete cot functions to handle CoT delete commands 2026-05-24 16:06:50 -03:00
valmojr 7a1a4b7372 Fixed leak global vars from convert client pos and extract role functions 2026-05-24 02:58:32 -03:00
valmojr 2802f9cdc8 Removed a lot of specific mods and added mission folder to ignore 2026-05-15 20:20:52 -03:00
valmojr ed039035df Added fallback mTLS enrollment port to handle OTS authentication 2026-05-15 16:33:53 -03:00
valmojr 7fa4a7c411 Readded serverside digital pointer with a handler to avoid duplicated SPI when a entity is being controlled clientside 2026-05-14 19:05:58 -03:00
valmojr d4dfd80cdf Added initial Laser Ranger Finder simulator on port 17211 (as default) emulate digital pointer for in game laser designators 2026-05-14 18:53:34 -03:00
valmojr 720f9da2df Added Delete CoT handler for removing serverside CoT when a player invokes the clientside connection 2026-05-14 18:52:40 -03:00
valmojr 5015f09d1d [WIP] Adding Drawing Cursor Over Time functions 2026-05-11 16:26:17 -03:00
valmojr 882a35c2cd Added report marker functions and stale time param to CoT type 2026-05-11 16:25:52 -03:00
valmojr 6b3ce96c18 formatted some rust docs 2026-05-11 16:24:40 -03:00
valmojr 760027b925 reduced readme 2026-05-10 02:07:35 -03:00
valmojr 9b05bcccc2 Added UAS Tool active actions from ATAK 2026-05-10 01:19:39 -03:00
valmojr 80320c0f2c added land and home storage 2026-05-10 01:19:13 -03:00
valmojr b43a6c9748 added undefined uri 2026-05-10 01:18:11 -03:00
valmojr dcc9e1d469 Added handler for turreted drones to share multiple camera components to UAS Tool 2026-05-09 11:58:44 -03:00
valmojr 3c37185c1a Fixed UAS stream URI resolver 2026-05-09 11:58:08 -03:00
valmojr 671e7d5dc1 linted uas callsing functions 2026-05-07 05:02:51 -03:00
valmojr 0ebd192487 fixed URI Stream sharing 2026-05-07 04:35:52 -03:00
valmojr 3fc54a1fb5 test log 2026-05-07 04:13:20 -03:00
valmojr eaf38a4d06 Updated some mods because yes 2026-05-07 03:53:42 -03:00
valmojr 6376b7acf0 Refacted UDP Socket config and created UAS addon 2026-05-07 03:53:19 -03:00
valmojr 52edf94b17 refactored mavlink mocking to "uas" module on the extension 2026-05-07 03:37:22 -03:00
valmojr 3e11dd9e16 updated data extract functions for mavlink integration 2026-05-05 12:20:12 -03:00
valmojr a9f09b6ce6 Added extension piece of code for handling the mavlink mocker 2026-05-05 12:19:14 -03:00
valmojr a43aa60f45 fixed side parser on role handler 2026-05-05 12:18:23 -03:00
valmojr 9cba642e9b added mavlink indexing functions 2026-05-05 08:03:26 -03:00
valmojr c7494da901 added mavlink cot parsing functions 2026-05-05 08:03:01 -03:00
valmojr b9e848d66e Updated client side dialog to include mavlink port input 2026-05-05 08:02:38 -03:00
valmojr 99f8d991be Added MavLink mocked drone support as client side feature 2026-05-05 07:48:54 -03:00
valmojr 753dcab26e Added hellanmaaw winter support 2026-03-31 07:23:48 -03:00
valmojr 2f53488ba8 Added video url prop to 3den editor/zeus, allowing to parse __video prop to cots 2026-03-31 07:21:29 -03:00
valmojr 323339e679 Removed video addon, too simple for a specific addon 2026-03-31 07:20:19 -03:00
valmojr 3f14a75e81 Added video url parser to CoT types 2026-03-31 07:19:39 -03:00
valmojr 469a54c141 Added Hellanmma map support 2026-03-31 07:18:23 -03:00
valmojr 2ee9030c00 Updated media folder 2026-03-26 14:45:08 -03:00
valmojr 5b29a40990 Improved mTLS description on readme 2026-03-26 03:47:54 -03:00
valmojr 708fe5e670 Fixed CoT queue during armatak connection to the TAK Server, running soft as butter 2026-03-26 03:45:05 -03:00
valmojr e32aadda4e Splitted Connection Module 2026-03-26 01:05:54 -03:00
valmojr c35b7f0268 Updated project readme file 2026-03-24 16:56:26 -03:00
valmojr 876cf900c3 Changed dialogs and module UI to get mTLS needed params 2026-03-24 16:56:19 -03:00
valmojr 778ac0ac54 Added the mTLS connection calls to zeus and 3den modules 2026-03-24 16:55:53 -03:00
valmojr b816144fb0 Added transport layer and configured extension commands to call mTLS socket connection 2026-03-24 16:55:36 -03:00
valmojr 61ba9f6d63 Added connector and enrollment for mTLS client certificate auto enrollment on game sessions, will MOCK a official tak client behavior when authenticating 2026-03-24 16:55:05 -03:00
valmojr f88c02a7aa formatted some rust files for linting porpuses 2026-03-24 16:44:22 -03:00
valmojr 5ffc08e6f1 Readded reqwest dependency to cargo toml, will be used for TAK Server API interaction on authencated tak server connections 2026-03-24 16:41:38 -03:00
valmojr 9392380c78 Added hemtt private key to git ignore 2026-03-24 16:40:58 -03:00
valmojr a18343b81d Commented video module 2026-03-24 14:03:28 -03:00
Valmo Trindade 13cd08c655 Added mandol map support 2026-01-03 02:55:39 -03:00
Valmo Trindade 8fe14dc18d Added Clafghan Map Support 2026-01-02 03:28:10 -03:00
Valmo Trindade 1bec26df8a Added UMP Colombia map support 2025-12-21 02:21:02 -03:00
Valmo Trindade c5d5da636f added malvinas pradero ganso function call 2025-12-13 15:00:33 -03:00
Valmo Trindade c2e137e67c Added lawn map 2025-12-13 14:59:05 -03:00
Valmo Trindade de5ac9dbb5 Added Malvinas Maps 2025-12-12 19:12:04 -03:00
Valmo Trindade ef3be1e768 fixed router entity remover function 2025-12-10 23:07:53 -03:00
467 changed files with 11286 additions and 39644 deletions
-1
View File
@@ -3,7 +3,6 @@
*.paa filter=lfs diff=lfs merge=lfs -text
*.p3d filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.apk filter=lfs diff=lfs merge=lfs -text
+58
View File
@@ -0,0 +1,58 @@
name: Rust CI
on:
push:
branches:
- main
- "feature/**"
pull_request:
branches:
- main
permissions:
contents: read
concurrency:
group: rust-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test-and-coverage:
name: Rust tests and coverage
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install stable Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Run unit tests
run: cargo test --all-targets --locked
- name: Report whole-extension coverage
run: cargo llvm-cov --all-targets --locked --summary-only --show-missing-lines
- name: Enforce 100% coverage on deterministic core
run: |
cargo llvm-cov --all-targets --locked --summary-only \
--ignore-filename-regex 'src/(lib\.rs|tests\.rs|structs\.rs|mdns\.rs|udp_socket\.rs|video_stream\.rs|tcp/(client\.rs|cot\.rs|draw\.rs|mod\.rs|transport\.rs)|tcp/tls/(connector\.rs|enrollment\.rs|mod\.rs)|cot/(cot\.rs|delete\.rs|digital_pointer\.rs|draws/.*|eud\.rs|gps\.rs|lrf\.rs|message\.rs|mod\.rs|nato\.rs|report_marker\.rs|uas\.rs)|uas/(callbacks\.rs|constants\.rs|endpoint\.rs|identity\.rs|mod\.rs|packets\.rs|payload\.rs|send\.rs|state\.rs)|utils/(address\.rs|log\.rs|mod\.rs))' \
--fail-under-lines 100
- name: Generate LCOV artifact
run: cargo llvm-cov --all-targets --locked --lcov --output-path lcov.info
- name: Upload LCOV artifact
uses: actions/upload-artifact@v4
with:
name: rust-lcov
path: lcov.info
if-no-files-found: error
+4
View File
@@ -3,6 +3,7 @@
hemtt
hemtt.exe
*.biprivatekey
.hemttprivatekey
source/
.vscode
releases/
@@ -88,3 +89,6 @@ target/
local.properties
*.apk
.hemtt/missions
+1 -27
View File
@@ -36,35 +36,9 @@ url = "https://github.com/valmojr/armatak"
preset = "Hemtt"
[hemtt.launch.default]
mission = "hearts_and_minds.kunduz_valley"
workshop = [
"450814997", # CBA_A3
"463939057", # ACE
"751965892", # ACRE2
"2522638637", # ACE Extended Arsenal
"333310405", # Enhanced Movement
"2034363662", # Enhanced Movement Rework
"843577117", # RHSUSAF
"843425103", # RHSAFRF
"843632231", # RHSSAF
"843593391", # RHSGREF
"1673456286", # 3CB Factions
"2585568796", # Taliban Forces 2021
"3030830594", # Western Dusk
"623475643", # 3den Enhanced
"2257686620", # Blastcore Murr Edition
"583496184", # CUP Terrains - Core
"3078351739", # Kunduz River
"1858075458", # LAMBS_Danger.fsm
"1808238502", # LAMBS_Suppression
"3425368881", # M4A1_URGI
"2268351256", # Tier One Weapons
"3073216844", # Tier One Weapons - All NVG Compatible
"2560276469", # Restrict Markers
"3015795970", # Weapon Sight No Zoom Overhaul
"2397360831", # USAF Mod - Main
"2397376046", # USAF Mod - Utility
"1779063631" # Zeus Enhanced
"463939057", # ace
]
parameters = [
Generated
+1055 -18
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -9,6 +9,10 @@ chrono = "0.4.39"
lazy_static = "1.5.0"
log = "0.4.22"
log4rs = "1.3.0"
reqwest = { version = "0.12.15", default-features = false, features = ["blocking", "json", "rustls-tls"] }
rcgen = { version = "0.13.2", default-features = false, features = ["crypto", "pem", "aws_lc_rs"] }
rustls = "0.23.23"
rustls-pemfile = "2.2.0"
serde = { version = "1.0.210", features = ["derive"] }
[dependencies.uuid]
-4
View File
@@ -4,10 +4,6 @@
ARMATAK is a server side Arma 3 addons for streaming unit positions to TAK Clients in sessions on real locations maps. It can be runned both as a clientside mod or a serverside mod, when runned serverside, it will create a TCP Socket connection between Arma 3 and the TAK Server, sending the game session information into it. When used clientside, Arma 3 will host a websocket server that you can connect to your phone and mock the phone's location to the player's in game location.
## Operation Wings of Liberty
By default, this repo contains a forked Hearts and Minds mission (6-12 COOP) themed as Ranger Recon Detachment to showcase the mod, it was developed and heavily tested on a OG TAK Server running in CloudRF TAK Server container, other TAK Server may be functional, but not tested.
## Get in Touch
[Join the Discord Server for ARMATAK!](https://discord.gg/svK64PCycU)
+2
View File
@@ -1,3 +1,5 @@
PREP(convertClientLocation);
PREP(extractClientPosition);
PREP(initCuratorRouterIndicators);
PREP(sendLaserRangeFinder);
PREP(startUDPSocket);
+3
View File
@@ -6,3 +6,6 @@ _local_address = "armatak" callExtension ["local_ip", []] select 0;
SETVAR(player,GVAR(localAddress),_local_address);
SETVAR(player,GVAR(eudConnected),false);
SETVAR(player,GVAR(lrfEnabled),false);
call FUNC(initCuratorRouterIndicators);
+66 -13
View File
@@ -10,44 +10,97 @@ class armatak_udp_socket_start_dialog {
class armatak_gui_module_udp_socket_dialog_main_frame: RscBackground {
idc = 16960;
x = "0.386562 * safezoneW + safezoneX";
y = "0.401 * safezoneH + safezoneY";
y = "0.357 * safezoneH + safezoneY";
w = "0.216563 * safezoneW";
h = "0.242 * safezoneH";
h = "0.495 * safezoneH";
colorBackground[]={0,0,0,0.45};
};
};
class Controls {
class armatak_gui_module_udp_socket_dialog_address_edit: RscEdit {
idc = 16961;
text = "168.15.0.3";
text = "192.168.15.121";
x = "0.391719 * safezoneW + safezoneX";
y = "0.445 * safezoneH + safezoneY";
y = "0.401 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[]={0,0,0,0.5};
};
class armatak_gui_module_udp_socket_dialog_address_port_edit: RscEdit {
class armatak_gui_module_udp_socket_dialog_gnss_port_edit: RscEdit {
idc = 16962;
text = "4349";
x = "0.391719 * safezoneW + safezoneX";
y = "0.522 * safezoneH + safezoneY";
y = "0.478 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[]={0,0,0,0.5};
};
class armatak_gui_module_udp_socket_dialog_mavlink_port_edit: RscEdit {
idc = 16967;
text = "14550";
x = "0.391719 * safezoneW + safezoneX";
y = "0.555 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[]={0,0,0,0.5};
};
class armatak_gui_module_udp_socket_dialog_video_feed_url_edit: RscEdit {
idc = 16969;
text = "";
x = "0.391719 * safezoneW + safezoneX";
y = "0.709 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[]={0,0,0,0.5};
tooltip = "Optional shared feed URL. If empty, the UAV 3DEN URL is used first, then a local RTP fallback.";
};
class armatak_gui_module_udp_socket_dialog_lrf_port_edit: RscEdit {
idc = 16971;
text = "17211";
x = "0.391719 * safezoneW + safezoneX";
y = "0.632 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[]={0,0,0,0.5};
tooltip = "ATAK local Laser Range Finder UDP input. Leave empty to disable.";
};
class armatak_gui_module_udp_socket_dialog_address_text: RscText {
idc = 16963;
text = "Phone's Socket Local Address";
text = "EUD's Address";
x = "0.391719 * safezoneW + safezoneX";
y = "0.412 * safezoneH + safezoneY";
y = "0.368 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class armatak_gui_module_udp_socket_dialog_address_port_text: RscText {
class armatak_gui_module_udp_socket_dialog_gnss_port_text: RscText {
idc = 16964;
text = "Phone's Socket Local Port";
text = "Network GNSS Port";
x = "0.391719 * safezoneW + safezoneX";
y = "0.489 * safezoneH + safezoneY";
y = "0.445 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class armatak_gui_module_udp_socket_dialog_mavlink_port_text: RscText {
idc = 16968;
text = "Mavlink Port";
x = "0.391719 * safezoneW + safezoneX";
y = "0.522 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class armatak_gui_module_udp_socket_dialog_video_feed_url_text: RscText {
idc = 16970;
text = "Video Feed URL (Optional)";
x = "0.391719 * safezoneW + safezoneX";
y = "0.676 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class armatak_gui_module_udp_socket_dialog_lrf_port_text: RscText {
idc = 16972;
text = "Laser Range Finder Port";
x = "0.391719 * safezoneW + safezoneX";
y = "0.599 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
@@ -56,7 +109,7 @@ class armatak_udp_socket_start_dialog {
text = "Cancel";
action = "closeDialog 2;";
x = "0.551563 * safezoneW + safezoneX";
y = "0.577 * safezoneH + safezoneY";
y = "0.786 * safezoneH + safezoneY";
w = "0.0464063 * safezoneW";
h = "0.055 * safezoneH";
};
@@ -65,7 +118,7 @@ class armatak_udp_socket_start_dialog {
text = "Ok";
action = QUOTE(call FUNC(startUDPSocket));
x = "0.5 * safezoneW + safezoneX";
y = "0.577 * safezoneH + safezoneY";
y = "0.786 * safezoneH + safezoneY";
w = "0.0464063 * safezoneW";
h = "0.055 * safezoneH";
};
@@ -19,11 +19,15 @@
* Public: Yes
*/
params["_latitude", "_longitude", "_altitude"];
params [
["_latitude", 0, [0]],
["_longitude", 0, [0]],
["_altitude", 0, [0]]
];
_position = [_latitude, _longitude, _altitude];
private _position = [_latitude, _longitude, _altitude];
_realLocation = null;
private _realLocation = [0, 0, _altitude];
switch (toLower worldName) do {
case "altis": {
@@ -38,6 +42,9 @@ switch (toLower worldName) do {
case "vr": {
_realLocation = _position call armatak_fnc_convert_to_vr;
};
case "lawn": {
_realLocation = _position call armatak_fnc_convert_to_lawn;
};
case "cucui": {
_realLocation = _position call armatak_fnc_convert_to_cucui;
};
@@ -74,12 +81,42 @@ switch (toLower worldName) do {
case "kunduz_valley": {
_realLocation = _position call armatak_fnc_convert_to_kunduz_valley;
};
case "malvinasfalkands": {
_realLocation = _position call armatak_fnc_convert_to_malvinas_malvinasfalkands;
};
case "pebble_island_airfield": {
_realLocation = _position call armatak_fnc_convert_to_malvinas_pebble_island_airfield;
};
case "p_argentino_stanley": {
_realLocation = _position call armatak_fnc_convert_to_malvinas_p_argentino_stanley;
};
case "top_malo_house": {
_realLocation = _position call armatak_fnc_convert_to_malvinas_top_malo_house;
};
case "pradera_ganso": {
_realLocation = _position call armatak_fnc_convert_to_malvinas_pradera_ganso;
};
case "tanoa": {
_realLocation = _position call armatak_fnc_convert_to_tanoa;
};
case "zagor_zagorsk_reserved_forest": {
_realLocation = _position call armatak_fnc_convert_to_zagor_zagorsk_reserved_forest;
};
case "umb_colombia": {
_realLocation = _position call armatak_fnc_convert_to_colombia;
};
case "clafghan": {
_realLocation = _position call armatak_fnc_convert_to_clafghan;
};
case "rut_mandol": {
_realLocation = _position call armatak_fnc_convert_to_rut_mandol;
};
case "hellanmaa": {
_realLocation = _position call armatak_fnc_convert_to_hellanmaa;
};
case "hellanmaaw": {
_realLocation = _position call armatak_fnc_convert_to_hellanmaa;
};
default {
_warning = format ["<t color='#FF8021'>ARMATAK</t><br/> %1", "Unsupported Map"];
[[_warning, 1.5]] call CBA_fnc_notify;
@@ -0,0 +1,49 @@
#include "..\script_component.hpp"
/*
* Author: Codex
* Draws a Zeus-only visual border over entities routed by the ArmaTAK CoT Router.
*
* Arguments:
* None
*
* Return Value:
* Draw3D event handler id <NUMBER>
*
* Public: No
*/
if (!hasInterface) exitWith {-1};
private _existingEh = missionNamespace getVariable [QGVAR(curatorRouterIndicatorEh), -1];
if (_existingEh >= 0) exitWith {_existingEh};
private _eh = addMissionEventHandler ["Draw3D", {
if (isNull findDisplay 312) exitWith {};
private _curator = getAssignedCuratorLogic player;
if (isNull _curator) exitWith {};
private _routed = missionNamespace getVariable ["armatak_server_syncedUnits", []];
if (_routed isEqualTo []) exitWith {};
private _editableObjects = curatorEditableObjects _curator;
private _texture = "\a3\ui_f\data\IGUI\Cfg\Cursors\selected_ca.paa";
private _color = [0, 0.85, 1, 0.95];
private _camera = curatorCamera;
{
if (!isNull _x && {alive _x} && {_x in _editableObjects}) then {
private _pos = ASLToAGL (getPosASLVisual _x);
_pos set [2, (_pos select 2) + 0.12];
private _distance = _camera distance _x;
private _size = linearConversion [0, 2000, _distance, 0.8, 1.35, true];
private _textSize = linearConversion [0, 2000, _distance, 0.032, 0.05, true];
drawIcon3D [_texture, _color, _pos, _size, _size, 0, "TAK", 1, _textSize, "RobotoCondensed", "center"];
};
} forEach _routed;
}];
missionNamespace setVariable [QGVAR(curatorRouterIndicatorEh), _eh];
_eh
@@ -0,0 +1,52 @@
#include "..\script_component.hpp"
params ["_unit"];
private _lrfEnabled = player getVariable [QGVAR(lrfEnabled), false];
private _uid = format ["%1.LRF", _unit call armatak_fnc_extract_uuid];
private _laserTarget = laserTarget _unit;
if (!isNull _laserTarget) exitWith {
private _originASL = getPosASL _unit;
private _targetASL = getPosASL _laserTarget;
private _delta = _targetASL vectorDiff _originASL;
private _dx = _delta select 0;
private _dy = _delta select 1;
private _dz = _delta select 2;
private _horizontalDistance = sqrt ((_dx * _dx) + (_dy * _dy));
private _slantDistance = (_originASL vectorDistance _targetASL) max 1;
private _azimuth = (((_dx atan2 _dy) + 360) mod 360);
private _elevation = _dz atan2 (_horizontalDistance max 0.001);
private _lastTargetASL = player getVariable [QGVAR(lrfLastTargetASL), []];
private _lastSentAt = player getVariable [QGVAR(lrfLastSentAt), -1000];
private _targetMoved = _lastTargetASL isEqualTo [] || {(_lastTargetASL vectorDistance _targetASL) > 5};
private _sendCooldownElapsed = (time - _lastSentAt) >= 2.5;
player setVariable [QGVAR(lrfWasActive), true];
player setVariable [QGVAR(lrfLostAt), -1];
player setVariable [QGVAR(lrfClearSent), false];
if (_lrfEnabled && {_targetMoved} && {_sendCooldownElapsed}) then {
"armatak" callExtension ["udp_socket:send_lrf", [[_uid, _slantDistance, _azimuth, _elevation]]];
player setVariable [QGVAR(lrfLastTargetASL), _targetASL];
player setVariable [QGVAR(lrfLastSentAt), time];
};
};
if !(player getVariable [QGVAR(lrfWasActive), false]) exitWith {};
private _lostAt = player getVariable [QGVAR(lrfLostAt), -1];
if (_lostAt < 0) then {
player setVariable [QGVAR(lrfLostAt), time];
};
private _clearSent = player getVariable [QGVAR(lrfClearSent), false];
if (_lrfEnabled && {!_clearSent} && {(time - (player getVariable [QGVAR(lrfLostAt), time])) >= 6}) then {
"armatak" callExtension ["udp_socket:clear_lrf", [_uid]];
player setVariable [QGVAR(lrfWasActive), false];
player setVariable [QGVAR(lrfClearSent), true];
player setVariable [QGVAR(lrfLastTargetASL), []];
player setVariable [QGVAR(lrfLastSentAt), -1000];
};
+42 -8
View File
@@ -2,7 +2,7 @@
params ["_logic"];
_socket_is_running = player getVariable [QGVAR(eudConnected), false];
private _socket_is_running = player getVariable [QGVAR(eudConnected), false];
if (_socket_is_running) exitWith {
["Socket is already running", "error", "UDP Socket"] call EFUNC(main,notify);
@@ -11,21 +11,55 @@ if (_socket_is_running) exitWith {
disableSerialization;
_udp_socket_instance_address = ctrlText 16961;
_udp_socket_instance_port = ctrlText 16962;
private _eud_address = ctrlText 16961;
private _gnss_port = ctrlText 16962;
private _mavlink_port = ctrlText 16967;
private _lrf_port = ctrlText 16971;
private _video_feed_url = ctrlText 16969;
_udp_socket_fulladdress = ((_udp_socket_instance_address) + ":" + (_udp_socket_instance_port));
private _udp_socket_fulladdress = _eud_address + ":" + _gnss_port;
private _mavlink_address = _eud_address + ":" + _mavlink_port;
private _lrf_port_trimmed = trim _lrf_port;
private _lrf_enabled = _lrf_port_trimmed isNotEqualTo "";
private _lrf_address = _eud_address + ":" + _lrf_port_trimmed;
player setVariable [QGVAR(udp_socket_address), _udp_socket_fulladdress];
player setVariable [QGVAR(eudConnected), true];
player setVariable [QGVAR(mavlink_address), _mavlink_address];
player setVariable [QGVAR(lrf_address), _lrf_address];
player setVariable [QGVAR(lrfEnabled), _lrf_enabled];
player setVariable [QGVAR(lrfWasActive), false];
player setVariable [QGVAR(lrfLostAt), -1];
player setVariable [QGVAR(lrfClearSent), false];
player setVariable [QGVAR(lrfLastTargetASL), []];
player setVariable [QGVAR(lrfLastSentAt), -1000];
player setVariable [QGVAR(video_feed_url), trim _video_feed_url];
player setVariable [QGVAR(eudConnected), true, true];
private _advertised_video_uri = [objNull] call EFUNC(uav,resolveVideoUri);
"armatak" callExtension ["udp_socket:start", [_udp_socket_fulladdress]];
"armatak" callExtension ["uas:start_endpoint", [parseNumber _mavlink_port]];
if (_lrf_enabled) then {
"armatak" callExtension ["udp_socket:start_lrf", [_lrf_address]];
};
private _mdnsInstanceName = format ["ArmaTAK-%1", name player];
"armatak" callExtension ["mdns:start_uas_advertisement", [_mdnsInstanceName, parseNumber _mavlink_port, _advertised_video_uri]];
"armatak" callExtension ["log", [["info", format ["Client UDP socket started for %1, MAVLink target set to %2, LRF target set to %3 and advertised video URI set to %4. Digital pointer uses ATAK LRF when enabled.", _udp_socket_fulladdress, _mavlink_address, _lrf_address, _advertised_video_uri]]]];
call EFUNC(uav,startMavlinkBroadcast);
[{
if (player getVariable [QGVAR(eudConnected), false]) then {
if !(player getVariable [QGVAR(eudConnected), false]) exitWith {};
"armatak" callExtension ["udp_socket:send_gps_cot", [player call FUNC(extractClientPosition)]];
};
}, 0, []] call CBA_fnc_addPerFrameHandler;
}, 0.5, []] call CBA_fnc_addPerFrameHandler;
[{
if !(player getVariable [QGVAR(eudConnected), false]) exitWith {};
[player] call FUNC(sendLaserRangeFinder);
}, 0.25, []] call CBA_fnc_addPerFrameHandler;
deleteVehicle _logic;
closeDialog 1;
+10
View File
@@ -78,6 +78,16 @@ class Cfg3den {
condition = "objectVehicle";
typeName = "STRING";
};
class armatak_attribute_marker_video_url {
displayName = "Video Feed URL";
tooltip = "Shared UAV video URL. This per-vehicle value overrides the optional session video URL from Connect to EUD.";
property = "armatak_attribute_marker_video_url";
control = "Edit";
expression = "_this setVariable ['armatak_attribute_marker_video_url',_value]";
defaultValue = "''";
condition = "objectVehicle";
typeName = "STRING";
};
};
};
};
+75
View File
@@ -19,6 +19,45 @@ class CfgFunctions {
class send_marker_cot {
file = "\armatak\armatak\addons\main\functions\api\fn_send_marker_cot.sqf";
};
class report_marker {
file = "\armatak\armatak\addons\main\functions\api\fn_report_marker.sqf";
};
class register_cot {
file = "\armatak\armatak\addons\main\functions\api\fn_register_cot.sqf";
};
class delete_registered_cots {
file = "\armatak\armatak\addons\main\functions\api\fn_delete_registered_cots.sqf";
};
class draw_circle {
file = "\armatak\armatak\addons\main\functions\api\fn_draw_circle.sqf";
};
class draw_ellipse {
file = "\armatak\armatak\addons\main\functions\api\fn_draw_ellipse.sqf";
};
class draw_rectangle {
file = "\armatak\armatak\addons\main\functions\api\fn_draw_rectangle.sqf";
};
class draw_polyline {
file = "\armatak\armatak\addons\main\functions\api\fn_draw_polyline.sqf";
};
class draw_route {
file = "\armatak\armatak\addons\main\functions\api\fn_draw_route.sqf";
};
class draw_tactical_graphic {
file = "\armatak\armatak\addons\main\functions\api\fn_draw_tactical_graphic.sqf";
};
class send_uas_platform_cot {
file = "\armatak\armatak\addons\main\functions\api\fn_send_uas_platform_cot.sqf";
};
class send_uas_video_cot {
file = "\armatak\armatak\addons\main\functions\api\fn_send_uas_video_cot.sqf";
};
class send_uas_sensor_cot {
file = "\armatak\armatak\addons\main\functions\api\fn_send_uas_sensor_cot.sqf";
};
class set_uas_camera_override {
file = "\armatak\armatak\addons\main\functions\api\fn_set_uas_camera_override.sqf";
};
class stop_tcp_socket {
file = "\armatak\armatak\addons\main\functions\api\fn_stop_tcp_socket.sqf";
};
@@ -31,12 +70,18 @@ class CfgFunctions {
class extract_marker_callsign {
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_marker_callsign.sqf";
};
class extract_marker_video_url {
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_marker_video_url.sqf";
};
class extract_role {
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_role.sqf";
};
class extract_sensor_data {
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_sensor_data.sqf";
};
class extract_uas_camera_data {
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_uas_camera_data.sqf";
};
class extract_unit_callsign {
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_unit_callsign.sqf";
};
@@ -68,12 +113,30 @@ class CfgFunctions {
class convert_to_kunduz_valley {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_kunduz_valley.sqf";
};
class convert_to_lawn {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_lawn.sqf";
};
class convert_to_livonia {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_livonia.sqf";
};
class convert_to_malden {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malden.sqf";
};
class convert_to_malvinas_malvinasfalkands {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_malvinasfalkands.sqf";
};
class convert_to_malvinas_p_argentino_stanley {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_p_argentino_stanley.sqf";
};
class convert_to_malvinas_pebble_island_airfield {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_pebble_island_airfield.sqf";
};
class convert_to_malvinas_pradera_ganso {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_pradera_ganso.sqf";
};
class convert_to_malvinas_top_malo_house {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_top_malo_house.sqf";
};
class convert_to_southen_sahrani {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_southen_sahrani.sqf";
};
@@ -95,6 +158,18 @@ class CfgFunctions {
class convert_to_zagor_zagorsk_reserved_forest {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_zagor_zagorsk_reserved_forest.sqf";
};
class convert_to_colombia {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_colombia.sqf";
};
class convert_to_clafghan {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_clafghan.sqf";
};
class convert_to_rut_mandol {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_rut_mandol.sqf";
};
class convert_to_hellanmaa {
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_hellanmaa.sqf";
};
};
};
};
+64 -5
View File
@@ -9,10 +9,14 @@ addMissionEventHandler ["ExtensionCallback", {
switch (_function) do {
case "EUD Connected": {
SETVAR(player,EGVAR(client,eudConnected),true);
player setVariable [QEGVAR(client,eudConnected), true, true];
};
case "EUD Disconnected": {
SETVAR(player,EGVAR(client,eudConnected),false);
player setVariable [QEGVAR(client,eudConnected), false, true];
SETVAR(player,EGVAR(client,lrfEnabled),false);
call EFUNC(uav,stopMavlinkBroadcast);
"armatak" callExtension ["uas:stop_endpoint", []];
"armatak" callExtension ["mdns:stop", []];
};
default {};
};
@@ -24,22 +28,77 @@ addMissionEventHandler ["ExtensionCallback", {
[_function, "error", _name] call FUNC(notify);
if (_function == "UDP Socket is not running") then {
SETVAR(player,EGVAR(client,eudConnected),false);
player setVariable [QEGVAR(client,eudConnected), false, true];
SETVAR(player,EGVAR(client,lrfEnabled),false);
call EFUNC(uav,stopMavlinkBroadcast);
"armatak" callExtension ["uas:stop_endpoint", []];
"armatak" callExtension ["mdns:stop", []];
};
if (_function == "failed to bind UDP socket") then {
SETVAR(player,EGVAR(client,eudConnected),false);
player setVariable [QEGVAR(client,eudConnected), false, true];
SETVAR(player,EGVAR(client,lrfEnabled),false);
call EFUNC(uav,stopMavlinkBroadcast);
"armatak" callExtension ["uas:stop_endpoint", []];
"armatak" callExtension ["mdns:stop", []];
};
};
case "MAVLINK UDP ERROR": {
_message = _function;
if (_data isNotEqualTo "") then {
_message = format ["%1: %2", _function, _data];
};
[_message, "warning", _name] call FUNC(notify);
};
case "MAVLINK UDP": {
private _history = missionNamespace getVariable ["armatak_uav_mavlink_callback_history", []];
_history pushBack [diag_tickTime, _function, _data];
if ((count _history) > 50) then {
_history deleteRange [0, (count _history) - 50];
};
missionNamespace setVariable ["armatak_uav_mavlink_callback_history", _history];
missionNamespace setVariable ["armatak_uav_last_mavlink_callback", [diag_tickTime, _function, _data]];
switch (_function) do {
case "COMMAND_LONG";
case "COMMAND_INT";
case "COMMAND_ACK";
case "MISSION_COUNT";
case "MISSION_ITEM";
case "MISSION_ITEM_INT";
case "MISSION_CLEAR_ALL";
case "MISSION_SET_CURRENT";
case "SET_HOME_POSITION";
case "SET_MODE";
case "SET_POSITION_TARGET_GLOBAL_INT";
case "MANUAL_CONTROL": {
"armatak" callExtension ["log", [["info", format ["MAVLINK UDP CALLBACK %1 %2", _function, _data]]]];
[_function, _data] call EFUNC(uav,handleMavlinkCallback);
};
default {};
};
};
case "TCP SOCKET": {
[_function, "success", _name] call FUNC(notify);
};
case "TCP SOCKET ERROR": {
[_function, "error", _name] call FUNC(notify);
_message = _function;
if (_data isNotEqualTo "") then {
_message = format ["%1: %2", _function, _data];
};
[_message, "error", _name] call FUNC(notify);
};
case "VIDEO": {
[_function, "success", _name] call FUNC(notify);
};
case "MDNS": {
[_function, "success", _name] call FUNC(notify);
};
case "MDNS ERROR": {
[_function, "warning", _name] call FUNC(notify);
};
case "VIDEO ERROR": {
[_function, "error", _name] call FUNC(notify);
@@ -0,0 +1,36 @@
// function name: armatak_fnc_delete_registered_cots
// function author: Valmo, Codex
// function description: Sends forced delete CoTs for all registered CoTs in a scope.
//
// Arguments:
// 0: Scope/key used to group CoTs <STRING>
//
// Return Value:
// Number of delete CoTs sent <NUMBER>
//
// Public: Yes
params [
["_scope", "", [""]]
];
if (_scope isEqualTo "") exitWith {0};
private _registry = missionNamespace getVariable ["armatak_registered_cots", []];
private _remaining = [];
private _deleted = 0;
{
_x params ["_registeredScope", "_uid", "_type", "_lat", "_lon", "_hae"];
if (_registeredScope isEqualTo _scope) then {
"armatak" callExtension ["tcp_socket:cot:delete", [[_uid, _type, _lat, _lon, _hae]]];
_deleted = _deleted + 1;
} else {
_remaining pushBack _x;
};
} forEach _registry;
missionNamespace setVariable ["armatak_registered_cots", _remaining];
_deleted
@@ -0,0 +1,44 @@
// function name: armatak_fnc_draw_circle
// function author: Valmo
// function description: Sends an ATAK Drawing Tools circle CoT.
//
// Arguments:
// 0: Center position or object <ARRAY|OBJECT>
// 1: Radius in meters <NUMBER>
// 2: Callsign/title <STRING> (default: "ArmaTAK Circle")
// 3: Stale time in seconds <NUMBER> (default: 86400)
// 4: Stroke color as signed ARGB int <NUMBER> (default: -1)
// 5: Fill color as signed ARGB int <NUMBER> (default: -1761607681)
// 6: Stroke weight <NUMBER> (default: 3)
// 7: Optional registration scope <STRING> (default: "")
//
// Example:
// [player, 300, "Mortar Risk Area"] call armatak_fnc_draw_circle;
//
// Public: Yes
params [
["_center", objNull, [objNull, []]],
["_radius", 100, [0]],
["_callsign", "ArmaTAK Circle", [""]],
["_staleSeconds", 86400, [0]],
["_strokeColor", -1, [0]],
["_fillColor", -1761607681, [0]],
["_strokeWeight", 3, [0]],
["_scope", "", [""]]
];
[
_center,
_radius,
_radius,
360,
_callsign,
_staleSeconds,
_strokeColor,
_fillColor,
_strokeWeight,
"",
"u-d-c-c",
_scope
] call armatak_fnc_draw_ellipse;
@@ -0,0 +1,70 @@
// function name: armatak_fnc_draw_ellipse
// function author: Valmo
// function description: Sends an ATAK Drawing Tools ellipse or circle CoT.
//
// Arguments:
// 0: Center position or object <ARRAY|OBJECT>
// 1: Major radius in meters <NUMBER>
// 2: Minor radius in meters <NUMBER>
// 3: Rotation angle in degrees <NUMBER> (default: 0)
// 4: Callsign/title <STRING> (default: "ArmaTAK Ellipse")
// 5: Stale time in seconds <NUMBER> (default: 86400)
// 6: Stroke color as signed ARGB int <NUMBER> (default: -1)
// 7: Fill color as signed ARGB int <NUMBER> (default: -1761607681)
// 8: Stroke weight <NUMBER> (default: 3)
// 9: MilSym SIDC for tactical overlay <STRING> (default: "")
// 10: CoT type <STRING> (default: "u-d-c-e")
// 11: Optional registration scope <STRING> (default: "")
//
// Example:
// [screenToWorld [0.5, 0.5], 250, 100, 45, "Support by Fire"] call armatak_fnc_draw_ellipse;
//
// Public: Yes
params [
["_center", objNull, [objNull, []]],
["_major", 100, [0]],
["_minor", 50, [0]],
["_angle", 0, [0]],
["_callsign", "ArmaTAK Ellipse", [""]],
["_staleSeconds", 86400, [0]],
["_strokeColor", -1, [0]],
["_fillColor", -1761607681, [0]],
["_strokeWeight", 3, [0]],
["_milsym", "", [""]],
["_cotType", "u-d-c-e", [""]],
["_scope", "", [""]]
];
private _position = if (_center isEqualType objNull) then {
getPos _center
} else {
_center
};
if ((count _position) < 2) exitWith {""};
private _altitude = _position param [2, 0, [0]];
private _realLocation = [_position select 0, _position select 1, _altitude] call armatak_client_fnc_convertClientLocation;
private _uuid = "armatak" callExtension ["uuid", []] select 0;
private _payload = [
_uuid,
_cotType,
_realLocation select 0,
_realLocation select 1,
_realLocation select 2,
_major max 1,
_minor max 1,
_angle,
_callsign,
_staleSeconds max 1,
_strokeColor,
_fillColor,
_strokeWeight max 1,
_milsym
];
"armatak" callExtension ["tcp_socket:draw:ellipse", [_payload]];
[_scope, _uuid, _cotType, _realLocation select 0, _realLocation select 1, _realLocation select 2] call armatak_fnc_register_cot;
_uuid
@@ -0,0 +1,83 @@
// function name: armatak_fnc_draw_polyline
// function author: Valmo
// function description: Sends an ATAK Drawing Tools freeform line or polygon CoT.
//
// Arguments:
// 0: Positions or objects <ARRAY>
// 1: Callsign/title <STRING> (default: "ArmaTAK Line")
// 2: Closed polygon <BOOL> (default: false)
// 3: Stale time in seconds <NUMBER> (default: 86400)
// 4: Stroke color as signed ARGB int <NUMBER> (default: -1)
// 5: Fill color as signed ARGB int <NUMBER> (default: -1761607681)
// 6: Stroke weight <NUMBER> (default: 3)
// 7: Stroke style <STRING> (default: "solid")
// 8: MilSym SIDC for tactical overlay <STRING> (default: "")
// 9: CoT type <STRING> (default: "u-d-f")
// 10: Optional registration scope <STRING> (default: "")
//
// Example:
// [[pos player, screenToWorld [0.5, 0.5]], "Phase Line Blue"] call armatak_fnc_draw_polyline;
//
// Public: Yes
params [
["_points", [], [[]]],
["_callsign", "ArmaTAK Line", [""]],
["_closed", false, [true]],
["_staleSeconds", 86400, [0]],
["_strokeColor", -1, [0]],
["_fillColor", -1761607681, [0]],
["_strokeWeight", 3, [0]],
["_strokeStyle", "solid", [""]],
["_milsym", "", [""]],
["_cotType", "u-d-f", [""]],
["_scope", "", [""]]
];
if ((count _points) < 2) exitWith {""};
private _pointStrings = [];
private _center = [];
{
private _position = if (_x isEqualType objNull) then {
getPos _x
} else {
_x
};
if ((count _position) >= 2) then {
private _altitude = _position param [2, 0, [0]];
private _realLocation = [_position select 0, _position select 1, _altitude] call armatak_client_fnc_convertClientLocation;
_pointStrings pushBack format ["%1,%2,%3", _realLocation select 0, _realLocation select 1, _realLocation select 2];
if (_center isEqualTo []) then {
_center = _realLocation;
};
};
} forEach _points;
if ((count _pointStrings) < 2) exitWith {""};
private _uuid = "armatak" callExtension ["uuid", []] select 0;
private _payload = [
_uuid,
_cotType,
_center select 0,
_center select 1,
_center select 2,
_pointStrings joinString ";",
_callsign,
_staleSeconds max 1,
_strokeColor,
_fillColor,
_strokeWeight max 1,
_strokeStyle,
_closed,
_milsym
];
"armatak" callExtension ["tcp_socket:draw:free", [_payload]];
[_scope, _uuid, _cotType, _center select 0, _center select 1, _center select 2] call armatak_fnc_register_cot;
_uuid
@@ -0,0 +1,94 @@
// function name: armatak_fnc_draw_rectangle
// function author: Valmo
// function description: Sends an ATAK Drawing Tools rectangle CoT from an Arma center, width, length, and bearing.
//
// Arguments:
// 0: Center position or object <ARRAY|OBJECT>
// 1: Width in meters <NUMBER>
// 2: Length in meters <NUMBER>
// 3: Bearing in degrees <NUMBER> (default: 0)
// 4: Callsign/title <STRING> (default: "ArmaTAK Rectangle")
// 5: Stale time in seconds <NUMBER> (default: 86400)
// 6: Stroke color as signed ARGB int <NUMBER> (default: -1)
// 7: Fill color as signed ARGB int <NUMBER> (default: -1761607681)
// 8: Stroke weight <NUMBER> (default: 3)
// 9: MilSym SIDC for tactical overlay <STRING> (default: "")
// 10: Optional registration scope <STRING> (default: "")
//
// Example:
// [screenToWorld [0.5, 0.5], 200, 500, 30, "Engagement Area"] call armatak_fnc_draw_rectangle;
//
// Public: Yes
params [
["_center", objNull, [objNull, []]],
["_width", 100, [0]],
["_length", 100, [0]],
["_bearing", 0, [0]],
["_callsign", "ArmaTAK Rectangle", [""]],
["_staleSeconds", 86400, [0]],
["_strokeColor", -1, [0]],
["_fillColor", -1761607681, [0]],
["_strokeWeight", 3, [0]],
["_milsym", "", [""]],
["_scope", "", [""]]
];
private _centerPos = if (_center isEqualType objNull) then {
getPos _center
} else {
_center
};
if ((count _centerPos) < 2) exitWith {""};
private _altitude = _centerPos param [2, 0, [0]];
private _halfWidth = (_width max 1) / 2;
private _halfLength = (_length max 1) / 2;
private _sin = sin _bearing;
private _cos = cos _bearing;
private _forward = [_sin, _cos, 0];
private _right = [_cos, -_sin, 0];
private _offsets = [
[(_right vectorMultiply -_halfWidth), (_forward vectorMultiply _halfLength)],
[(_right vectorMultiply _halfWidth), (_forward vectorMultiply _halfLength)],
[(_right vectorMultiply _halfWidth), (_forward vectorMultiply -_halfLength)],
[(_right vectorMultiply -_halfWidth), (_forward vectorMultiply -_halfLength)]
];
private _points = [];
{
private _offset = (_x select 0) vectorAdd (_x select 1);
_points pushBack ([_centerPos select 0, _centerPos select 1, _altitude] vectorAdd _offset);
} forEach _offsets;
private _centerReal = [_centerPos select 0, _centerPos select 1, _altitude] call armatak_client_fnc_convertClientLocation;
private _pointStrings = [];
{
private _realLocation = [_x select 0, _x select 1, _x select 2] call armatak_client_fnc_convertClientLocation;
_pointStrings pushBack format ["%1,%2,%3", _realLocation select 0, _realLocation select 1, _realLocation select 2];
} forEach _points;
private _uuid = "armatak" callExtension ["uuid", []] select 0;
private _payload = [
_uuid,
"u-d-r",
_centerReal select 0,
_centerReal select 1,
_centerReal select 2,
_pointStrings joinString ";",
_callsign,
_staleSeconds max 1,
_strokeColor,
_fillColor,
_strokeWeight max 1,
"solid",
false,
_milsym
];
"armatak" callExtension ["tcp_socket:draw:rectangle", [_payload]];
[_scope, _uuid, "u-d-r", _centerReal select 0, _centerReal select 1, _centerReal select 2] call armatak_fnc_register_cot;
_uuid
+200
View File
@@ -0,0 +1,200 @@
// function name: armatak_fnc_draw_route
// function author: Valmo
// function description: Sends an ATAK navigable route CoT.
//
// Arguments:
// 0: Positions or objects <ARRAY>
// 1: Callsign/title <STRING> (default: "ArmaTAK Route")
// 2: Stale time in seconds <NUMBER> (default: 86400)
// 3: Color as signed ARGB int <NUMBER> (default: -1)
// 4: Stroke weight <NUMBER> (default: 3)
// 5: Navigation method <STRING> (default: "Driving")
// 6: Route type <STRING> (default: "Primary")
// 7: Direction/planning method <STRING> (default: "Infil")
// 8: Checkpoint interval among route points <NUMBER> (default: 5)
// 9: Optional registration scope <STRING> (default: "")
//
// Example:
// [[pos player, screenToWorld [0.5, 0.5]], "Patrol Route"] call armatak_fnc_draw_route;
//
// Public: Yes
params [
["_points", [], [[]]],
["_callsign", "ArmaTAK Route", [""]],
["_staleSeconds", 86400, [0]],
["_color", -1, [0]],
["_strokeWeight", 3, [0]],
["_method", "Driving", [""]],
["_routeType", "Primary", [""]],
["_direction", "Infil", [""]],
["_checkpointInterval", 5, [0]],
["_scope", "", [""]]
];
if ((count _points) < 2) exitWith {""};
private _pointStrings = [];
{
private _position = if (_x isEqualType objNull) then {
getPos _x
} else {
_x
};
if ((count _position) >= 2) then {
private _altitude = _position param [2, 0, [0]];
private _realLocation = [_position select 0, _position select 1, _altitude] call armatak_client_fnc_convertClientLocation;
_pointStrings pushBack format ["%1,%2,%3", _realLocation select 0, _realLocation select 1, _realLocation select 2];
};
} forEach _points;
if ((count _pointStrings) < 2) exitWith {""};
private _uuid = "armatak" callExtension ["uuid", []] select 0;
private _pad = {
params ["_value", "_digits"];
private _text = str (floor _value);
while {(count _text) < _digits} do {
_text = "0" + _text;
};
_text
};
private _formatCotTime = {
params ["_dateParts"];
format [
"%1-%2-%3T%4:%5:%6.%7Z",
_dateParts param [0, 1970, [0]],
[_dateParts param [1, 1, [0]], 2] call _pad,
[_dateParts param [2, 1, [0]], 2] call _pad,
[_dateParts param [3, 0, [0]], 2] call _pad,
[_dateParts param [4, 0, [0]], 2] call _pad,
[_dateParts param [5, 0, [0]], 2] call _pad,
[_dateParts param [6, 0, [0]], 3] call _pad
]
};
private _addSecondsUtc = {
params ["_dateParts", "_secondsToAdd"];
private _year = _dateParts param [0, 1970, [0]];
private _month = _dateParts param [1, 1, [0]];
private _day = _dateParts param [2, 1, [0]];
private _hour = _dateParts param [3, 0, [0]];
private _minute = _dateParts param [4, 0, [0]];
private _second = (_dateParts param [5, 0, [0]]) + (floor _secondsToAdd);
private _millisecond = _dateParts param [6, 0, [0]];
while {_second >= 60} do {
_second = _second - 60;
_minute = _minute + 1;
};
while {_minute >= 60} do {
_minute = _minute - 60;
_hour = _hour + 1;
};
while {_hour >= 24} do {
_hour = _hour - 24;
_day = _day + 1;
private _daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
private _isLeapYear = ((_year mod 4) isEqualTo 0) && {((_year mod 100) isNotEqualTo 0) || {(_year mod 400) isEqualTo 0}};
if (_isLeapYear) then {_daysInMonth set [1, 29];};
if (_day > (_daysInMonth select (_month - 1))) then {
_day = 1;
_month = _month + 1;
if (_month > 12) then {
_month = 1;
_year = _year + 1;
};
};
};
[_year, _month, _day, _hour, _minute, _second, _millisecond]
};
private _nowDate = systemTimeUTC;
private _now = [_nowDate] call _formatCotTime;
private _stale = [[_nowDate, _staleSeconds max 60] call _addSecondsUtc] call _formatCotTime;
private _links = "";
private _routeWaypoints = [];
private _lastIndex = (count _pointStrings) - 1;
private _checkpointNumber = 1;
private _checkpointIntervalSafe = _checkpointInterval max 1;
{
private _linkUuid = "armatak" callExtension ["uuid", []] select 0;
private _pointCallsign = "";
if (_forEachIndex isEqualTo 0) then {
_pointCallsign = format ["%1 SP", _callsign];
} else {
if (_forEachIndex isEqualTo _lastIndex) then {
_pointCallsign = "VDO";
} else {
if ((_forEachIndex mod _checkpointIntervalSafe) isEqualTo 0) then {
_pointCallsign = format ["CP%1", _checkpointNumber];
_checkpointNumber = _checkpointNumber + 1;
};
};
};
private _linkType = ["b-m-p-w", "b-m-p-c"] select (_pointCallsign isEqualTo "");
_links = _links + format [
"<link uid=""%1"" callsign=""%2"" type=""%3"" point=""%4"" remarks="""" relation=""c""/>",
_linkUuid,
_pointCallsign,
_linkType,
_x
];
if (_pointCallsign isNotEqualTo "") then {
_routeWaypoints pushBack [_linkUuid, _pointCallsign, _x];
};
} forEach _pointStrings;
private _xml = format [
"<?xml version=""1.0"" encoding=""UTF-8"" ?><event version=""2.0"" uid=""%1"" type=""b-m-r"" time=""%2"" start=""%2"" stale=""%3"" how=""h-e"" access=""Undefined""><point lat=""0.0"" lon=""0.0"" hae=""9999999.0"" ce=""9999999.0"" le=""9999999.0""/><detail>%4<link_attr planningmethod=""%5"" color=""%6"" method=""%7"" prefix=""CP"" style=""0"" type=""Vehicle"" stroke=""%8"" direction=""%5"" routetype=""%9"" order=""Ascending Check Points""/><creator uid=""ARMATAK"" callsign=""ArmaTAK"" time=""%2"" type=""a-f-G-U-C""/><strokeColor value=""%6""/><strokeWeight value=""%8""/><strokeStyle value=""solid""/><labels_on value=""false""/><__routeinfo><__navcues/></__routeinfo><color value=""%6""/><remarks/><contact callsign=""%10""/><archive/><height_unit>1</height_unit></detail></event>",
_uuid,
_now,
_stale,
_links,
_direction,
_color,
_method,
_strokeWeight max 1,
_routeType,
_callsign
];
"armatak" callExtension ["log", [["info", format ["Sending ATAK route '%1' with %2 points (%3 bytes)", _callsign, count _pointStrings, count _xml]]]];
"armatak" callExtension ["tcp_socket:send_payload", [_xml]];
[_scope, _uuid, "b-m-r", 0, 0, 9999999] call armatak_fnc_register_cot;
{
_x params ["_waypointUid", "_waypointCallsign", "_pointString"];
private _pointParts = _pointString splitString ",";
if ((count _pointParts) >= 3) then {
private _waypointXml = format [
"<?xml version=""1.0"" encoding=""UTF-8"" ?><event version=""2.0"" uid=""%1"" type=""b-m-p-w"" time=""%2"" start=""%2"" stale=""%3"" how=""h-e""><point lat=""%4"" lon=""%5"" hae=""%6"" ce=""9999999.0"" le=""9999999.0""/><detail><contact callsign=""%7""/><remarks/><archive/><link relation=""p-p"" type=""b-m-r"" uid=""%8""/></detail></event>",
_waypointUid,
_now,
_stale,
_pointParts select 0,
_pointParts select 1,
_pointParts select 2,
_waypointCallsign,
_uuid
];
"armatak" callExtension ["tcp_socket:send_payload", [_waypointXml]];
[_scope, _waypointUid, "b-m-p-w", parseNumber (_pointParts select 0), parseNumber (_pointParts select 1), parseNumber (_pointParts select 2)] call armatak_fnc_register_cot;
};
} forEach _routeWaypoints;
_uuid
@@ -0,0 +1,75 @@
// function name: armatak_fnc_draw_tactical_graphic
// function author: Valmo
// function description: Sends an ATAK Drawing Tools shape with a MilSym tactical graphic overlay.
//
// Arguments:
// 0: Positions or objects <ARRAY>
// 1: MilSym SIDC <STRING>
// 2: Callsign/title <STRING> (default: "ArmaTAK Tactical Graphic")
// 3: Closed polygon <BOOL> (default: false)
// 4: Stale time in seconds <NUMBER> (default: 86400)
// 5: Stroke color as signed ARGB int <NUMBER> (default: -1)
// 6: Fill color as signed ARGB int <NUMBER> (default: -1761607681)
// 7: Stroke weight <NUMBER> (default: 3)
//
// Example:
// [[pos player, screenToWorld [0.5, 0.5]], "GFGPOLAGM-----X", "Axis of Advance"] call armatak_fnc_draw_tactical_graphic;
//
// Public: Yes
params [
["_points", [], [[]]],
["_milsym", "", [""]],
["_callsign", "ArmaTAK Tactical Graphic", [""]],
["_closed", false, [true]],
["_staleSeconds", 86400, [0]],
["_strokeColor", -1, [0]],
["_fillColor", -1761607681, [0]],
["_strokeWeight", 3, [0]]
];
if (_milsym isEqualTo "") exitWith {""};
if ((count _points) < 2) exitWith {""};
private _pointStrings = [];
private _center = [];
{
private _position = if (_x isEqualType objNull) then {
getPos _x
} else {
_x
};
if ((count _position) >= 2) then {
private _altitude = _position param [2, 0, [0]];
private _realLocation = [_position select 0, _position select 1, _altitude] call armatak_client_fnc_convertClientLocation;
_pointStrings pushBack format ["%1,%2,%3", _realLocation select 0, _realLocation select 1, _realLocation select 2];
if (_center isEqualTo []) then {
_center = _realLocation;
};
};
} forEach _points;
if ((count _pointStrings) < 2) exitWith {""};
private _uuid = "armatak" callExtension ["uuid", []] select 0;
private _payload = [
_uuid,
"u-d-f",
_center select 0,
_center select 1,
_center select 2,
_pointStrings joinString ";",
_callsign,
_staleSeconds max 1,
_strokeColor,
_fillColor,
_strokeWeight max 1,
"solid",
_closed,
_milsym
];
"armatak" callExtension ["tcp_socket:draw:vector", [_payload]];
@@ -0,0 +1,30 @@
// function name: armatak_fnc_register_cot
// function author: Valmo, Codex
// function description: Registers a CoT object under a scope so it can be deleted later.
//
// Arguments:
// 0: Scope/key used to group CoTs <STRING>
// 1: CoT UID <STRING>
// 2: CoT type <STRING>
// 3: Latitude <NUMBER>
// 4: Longitude <NUMBER>
// 5: HAE altitude <NUMBER>
//
// Public: Yes
params [
["_scope", "", [""]],
["_uid", "", [""]],
["_type", "", [""]],
["_lat", 0, [0]],
["_lon", 0, [0]],
["_hae", 0, [0]]
];
if (_scope isEqualTo "" || {_uid isEqualTo ""} || {_type isEqualTo ""}) exitWith {false};
private _registry = missionNamespace getVariable ["armatak_registered_cots", []];
_registry pushBack [_scope, _uid, _type, _lat, _lon, _hae];
missionNamespace setVariable ["armatak_registered_cots", _registry];
true
@@ -0,0 +1,105 @@
// function name: armatak_fnc_report_marker
// function author: Valmo
// function description: Sends a one-shot TAK report marker with an independent stale time.
//
// Arguments:
// 0: Source position or object <ARRAY|OBJECT>
// 1: Affiliation or raw CoT type <STRING> (default: "unknown")
// Supported affiliations: "friendly", "enemy", "hostile", "neutral", "unknown"
// 2: Marker kind <STRING> (default: "infantry")
// Supported kinds: "infantry", "tank", "car", "apc", "helicopter", "plane", "ship", "static"
// 3: Callsign <STRING> (default: "Report")
// 4: Remarks <STRING> (default: "")
// 5: Stale time in seconds <NUMBER> (default: 3600)
// 6: Optional registration scope <STRING> (default: "")
//
// Example:
// [cursorObject, "enemy", "tank", "Enemy Tank", "Reported enemy tank"] call armatak_fnc_report_marker;
// [screenToWorld [0.5, 0.5], "unknown", "infantry", "Unknown Contact", "Unknown contact reported", 7200] call armatak_fnc_report_marker;
// [cursorObject, "a-h-G-U-C-A-T", "Enemy Tank", "Reported enemy tank", 7200] call armatak_fnc_report_marker;
//
// Public: Yes
params [
["_source", objNull, [objNull, []]],
["_affiliationOrType", "unknown", [""]],
["_kindOrCallsign", "infantry", [""]],
["_callsignOrRemarks", "Report", [""]],
["_remarksOrStaleSeconds", "", ["", 0]],
["_staleSeconds", 3600, [0, ""]],
["_scope", "", [""]]
];
private _type = "";
private _callsign = _callsignOrRemarks;
private _remarks = _remarksOrStaleSeconds;
if (_staleSeconds isEqualType "") then {
_scope = _staleSeconds;
_staleSeconds = 3600;
};
if ((_affiliationOrType select [0, 2]) isEqualTo "a-") then {
_type = _affiliationOrType;
_callsign = _kindOrCallsign;
_remarks = _callsignOrRemarks;
if (_remarksOrStaleSeconds isEqualType 0) then {
_staleSeconds = _remarksOrStaleSeconds;
};
} else {
private _affiliation = switch (toLower _affiliationOrType) do {
case "friendly": {"f"};
case "enemy": {"h"};
case "hostile": {"h"};
case "neutral": {"n"};
default {"u"};
};
private _kind = switch (toLower _kindOrCallsign) do {
case "tank": {"G-U-C-A-T"};
case "car": {"G-U-C-I-M"};
case "apc": {"G-U-C-I-I"};
case "helicopter": {"A-M-H"};
case "plane": {"A-M-F"};
case "ship": {"S"};
case "static": {"G-U-C-F-M"};
default {"G-U-C-I"};
};
_type = "a-" + _affiliation + "-" + _kind;
if (_remarksOrStaleSeconds isEqualType 0) then {
_staleSeconds = _remarksOrStaleSeconds;
_remarks = "";
};
};
private _position = if (_source isEqualType objNull) then {
getPos _source
} else {
_source
};
if ((count _position) < 2) exitWith {
""
};
private _altitude = _position param [2, 0, [0]];
private _realLocation = [_position select 0, _position select 1, _altitude] call armatak_client_fnc_convertClientLocation;
private _uuid = "armatak" callExtension ["uuid", []] select 0;
private _payload = [
_uuid,
_type,
_realLocation select 0,
_realLocation select 1,
_realLocation select 2,
_callsign,
_staleSeconds max 1,
_remarks
];
"armatak" callExtension ["tcp_socket:cot:report_marker", [_payload]];
[_scope, _uuid, _type, _realLocation select 0, _realLocation select 1, _realLocation select 2] call armatak_fnc_register_cot;
_uuid
@@ -6,7 +6,13 @@ if (!isNull _digitalPointer) then {
_digitalPointerPosition = _digitalPointer call armatak_client_fnc_extractClientPosition;
_link_uid = [_unit] call armatak_fnc_extract_uuid;
_contact_callsign = ([player] call armatak_fnc_extract_unit_callsign) + ".DP1";
_objectType = [_unit] call BIS_fnc_objectType;
_ownerCallsign = if ((_objectType select 0) == "Soldier") then {
[_unit] call armatak_fnc_extract_unit_callsign
} else {
[_unit] call armatak_fnc_extract_marker_callsign
};
_contact_callsign = _ownerCallsign + ".DP1";
_dpCot = [_link_uid, _contact_callsign, _digitalPointerPosition select 1, _digitalPointerPosition select 2, _digitalPointerPosition select 3];
@@ -4,31 +4,4 @@
params["_drone"];
private _atak_role = "a-f-A";
private _atak_callsign = [_drone] call armatak_fnc_extract_marker_callsign;
switch (side _drone) do {
case "WEST": {
_atak_role = "a-f-A-M-F-Q"
};
case "EAST": {
_atak_role = "a-h-A-M-F-Q"
};
case "INDEPENDENT": {
_atak_role = "a-n-A-M-F-Q"
};
case "CIVILIAN": {
_atak_role = "a-f-A-C"
};
default {
_atak_role = "a-f-A-M-F-Q"
};
};
_pre_defined_role = _drone getVariable "_atak_group_role";
if (!isNil "_pre_defined_role") then {
_callsign = _pre_defined_role;
};
_cot = [_drone, _atak_role, _atak_callsign] call armatak_fnc_send_marker_cot;
[_drone] call armatak_fnc_send_uas_platform_cot;
@@ -5,9 +5,10 @@
params ["_unit", "_type", "_callsign"];
_unit_position = _unit call armatak_client_fnc_extractClientPosition;
_video_url = [_unit] call armatak_fnc_extract_marker_video_url;
_uuid = _unit call armatak_fnc_extract_uuid;
_marker_cot = [_uuid, _type, _unit_position select 1, _unit_position select 2, _unit_position select 3, _callsign, _unit_position select 5, _unit_position select 6];
_marker_cot = [_uuid, _type, _unit_position select 1, _unit_position select 2, _unit_position select 3, _callsign, _unit_position select 5, _unit_position select 6, _video_url];
"armatak" callExtension ["tcp_socket:cot:marker", [_marker_cot]];
@@ -0,0 +1,77 @@
params ["_drone"];
private _uuid = _drone call armatak_fnc_extract_uuid;
private _uavControl = UAVControl _drone;
private _controller = _uavControl param [0, objNull];
private _controller_uid = if (!isNull _controller) then { [_controller] call armatak_fnc_extract_uuid } else { _drone getVariable ["armatak_uas_controller_uid", _uuid] };
private _callsign = [_drone] call armatak_fnc_extract_marker_callsign;
private _video_url = [_drone] call armatak_fnc_extract_marker_video_url;
private _atak_role = "a-f-A-M-H-Q";
switch (side _drone) do {
case west: {
_atak_role = "a-f-A-M-H-Q";
};
case east: {
_atak_role = "a-h-A-M-H-Q";
};
case independent: {
_atak_role = "a-n-A-M-H-Q";
};
case civilian: {
_atak_role = "a-f-A-C";
};
default {
_atak_role = "a-f-A-M-H-Q";
};
};
private _position = _drone call armatak_client_fnc_extractClientPosition;
private _lat = _position select 1;
private _lon = _position select 2;
private _hae = _position select 3;
private _course = _position select 5;
private _speed = _position select 6;
private _cameraData = [_drone] call armatak_fnc_extract_uas_camera_data;
private _azimuth = _cameraData select 0;
private _elevation = _cameraData select 1;
private _fov = _cameraData select 2;
private _range = _cameraData select 3;
private _vfov = _drone getVariable ["armatak_uas_vfov", _fov];
private _yaw = round (getDir _drone);
private _pitch = (vectorDir _drone) select 2;
private _roll = (vectorUp _drone) select 0;
private _isFlying = parseNumber (isEngineOn _drone);
private _hal = ((getPosATL _drone) select 2) max 0;
private _vehicleType = if (_video_url == "") then {
typeOf _drone
} else {
format ["%1|armatak_video_url=%2", typeOf _drone, _video_url]
};
private _payload = [
_uuid,
_atak_role,
_callsign,
_lat,
_lon,
_hae,
_course,
_speed,
_azimuth,
_elevation,
_fov,
_vfov,
_range,
_yaw,
_pitch,
_roll,
_hal,
_vehicleType,
_isFlying,
_controller_uid
];
"armatak" callExtension ["tcp_socket:cot:uas_platform", [_payload]];
@@ -0,0 +1,22 @@
params ["_drone"];
private _video_url = [_drone] call armatak_fnc_extract_marker_video_url;
if (_video_url == "") exitWith {};
private _uuid = _drone call armatak_fnc_extract_uuid;
private _video_uid = _uuid + "-video";
private _sensor_uid = _uuid + "-sensor";
private _callsign = [_drone] call armatak_fnc_extract_marker_callsign;
private _position = _drone call armatak_client_fnc_extractClientPosition;
private _lat = _position select 1;
private _lon = _position select 2;
private _hae = _position select 3;
private _cameraData = [_drone] call armatak_fnc_extract_uas_camera_data;
private _azimuth = _cameraData select 0;
private _fov = _cameraData select 2;
private _range = _cameraData select 3;
private _payload = [_sensor_uid, _video_uid, _callsign, _lat, _lon, _hae, _azimuth, _fov, _range];
"armatak" callExtension ["tcp_socket:cot:uas_sensor", [_payload]];
@@ -0,0 +1,20 @@
params ["_drone"];
private _video_url = [_drone] call armatak_fnc_extract_marker_video_url;
if (_video_url == "") exitWith {};
private _uuid = _drone call armatak_fnc_extract_uuid;
private _video_uid = _uuid + "-video";
private _callsign = [_drone] call armatak_fnc_extract_marker_callsign;
private _signature = format ["%1|%2|%3", _video_uid, _callsign, _video_url];
private _nextRefreshAt = _drone getVariable ["armatak_next_uas_video_refresh_at", 0];
private _lastSignature = _drone getVariable ["armatak_last_uas_video_signature", ""];
if (_signature == _lastSignature && {diag_tickTime < _nextRefreshAt}) exitWith {};
_drone setVariable ["armatak_last_uas_video_signature", _signature, false];
_drone setVariable ["armatak_next_uas_video_refresh_at", diag_tickTime + 300, false];
private _payload = [_video_uid, _callsign, _video_url];
"armatak" callExtension ["tcp_socket:cot:uas_video", [_payload]];
@@ -0,0 +1,9 @@
params ["_drone", ["_cameraData", []]];
if (isNull _drone) exitWith {};
if ((_cameraData isEqualType []) && {(count _cameraData) >= 6}) then {
_drone setVariable ["armatak_uas_camera_data_override", _cameraData + [serverTime], false];
} else {
_drone setVariable ["armatak_uas_camera_data_override", nil, false];
};
@@ -5,29 +5,45 @@
params["_unit"];
private _callsign = "";
private _displayName = localize (getText (configOf _unit >> "displayName"));
private _markerCallsignOverride = _unit getVariable ["armatak_attribute_marker_callsign", ""];
if (_markerCallsignOverride isNotEqualTo "") exitWith {
_markerCallsignOverride
};
if (_displayName isEqualTo "") then {
_displayName = typeOf _unit;
};
private _vehicleName = vehicleVarName _unit;
if ((([_unit] call BIS_fnc_objectType) select 0) == "Vehicle") then {
_callsign = getText (configOf _unit >> "displayName");
_callsign = [_displayName, _vehicleName] select (_vehicleName isNotEqualTo "");
if (!isNull driver _unit) then {
_callsign = getText (configOf _unit >> "displayName") + " | " + ([name (driver _unit)] call armatak_fnc_shorten_name);
_callsign = _displayName + " | " + ([name (driver _unit)] call armatak_fnc_shorten_name);
};
};
if (unitIsUAV _unit) then {
_callsign = getText (configOf _unit >> "displayName");
_callsign = [_displayName, _vehicleName] select (_vehicleName isNotEqualTo "");
private _uavControl = UAVControl _unit;
private _controller = _uavControl param [0, objNull];
if (!isNull _controller) then {
_callsign = _callsign + " | " + ([name _controller] call armatak_fnc_shorten_name);
};
if (isUAVConnected _unit) then {
_callsign = (_callsign) + "[ON]";
_callsign = _callsign + " [ON]";
} else {
_callsign = (_callsign) + "[OFF]";
_callsign = _callsign + " [OFF]";
}
};
armatak_attribute_marker_callsign = _unit getVariable "armatak_attribute_marker_callsign";
if (!isNil "armatak_attribute_marker_callsign" or armatak_attribute_marker_callsign != '') then {
_callsign = armatak_attribute_marker_callsign;
if (_callsign isEqualTo "") then {
_callsign = _displayName;
};
_callsign
@@ -0,0 +1,13 @@
// function name: armatak_fnc_extract_marker_video_url
// function author: Codex
// function description: Gets the marker video URL configured in 3DEN for a vehicle
params ["_unit"];
private _videoUrl = _unit getVariable ["armatak_attribute_marker_video_url", ""];
if (isNil "_videoUrl") exitWith {
""
};
_videoUrl
@@ -7,13 +7,7 @@ params["_unit"];
private _affiliation = "f";
private _type = "G";
private _role = "a-f-G-U-C-I";
private _side = side _unit;
if (isNil {
_unit getVariable "armatak_current_side"
}) then {
_side = _unit getVariable "armatak_current_side";
};
private _side = _unit getVariable ["armatak_current_side", side _unit];
switch (str _side) do {
case "WEST": {
@@ -33,7 +27,7 @@ switch (str _side) do {
};
};
_unit_type = _unit call BIS_fnc_objectType;
private _unit_type = _unit call BIS_fnc_objectType;
if ((_unit_type select 0) == "Soldier") then {
switch (_unit_type select 1) do {
@@ -77,7 +71,7 @@ if ((_unit_type select 0) == "Soldier") then {
};
if ((typeOf (vehicle _unit) != typeOf _unit) or ((_unit_type select 0) == "Vehicle")) then {
_vehicle_type = (vehicle _unit) call BIS_fnc_objectType select 1;
private _vehicle_type = (vehicle _unit) call BIS_fnc_objectType select 1;
switch (_vehicle_type) do {
case "Car": {
_type = "G-U-C-I-M";
@@ -117,10 +111,10 @@ if ((typeOf (vehicle _unit) != typeOf _unit) or ((_unit_type select 0) == "Vehic
_role = "a-" + _affiliation + "-" + _type;
armatak_attribute_marker_type = _unit getVariable "armatak_attribute_marker_type";
private _markerTypeOverride = _unit getVariable ["armatak_attribute_marker_type", ""];
if (!isNil "armatak_attribute_marker_type" or armatak_attribute_marker_type != '') then {
_role = armatak_attribute_marker_type;
if (_markerTypeOverride isNotEqualTo "") then {
_role = _markerTypeOverride;
};
_role
@@ -1,27 +1,32 @@
params["_unit"];
if (_unit getVariable ["armatak_disable_sensor_data", false]) exitWith {};
_target = getSensorTargets (_unit);
{
_unit = _x select 0;
private _targetUnit = _x select 0;
_position = _x select 1;
_status = _x select 2;
private _targetType = toLower (typeOf _targetUnit);
if ((_targetType find "lasertarget") < 0) then {
if (isNil {
_unit getVariable "armatak_current_side"
_targetUnit getVariable "armatak_current_side"
}) then {
_unit setVariable ["armatak_current_side", side _unit];
_targetUnit setVariable ["armatak_current_side", side _targetUnit];
};
if (_status != "destroyed" && !(_unit in armatak_server_syncedUnits)) then {
_unit_position = _unit call armatak_client_fnc_extractClientPosition;
if (_status != "destroyed" && !(_targetUnit in armatak_server_syncedUnits)) then {
_unit_position = _targetUnit call armatak_client_fnc_extractClientPosition;
_uuid = _unit call armatak_fnc_extract_uuid;
_type = _unit call armatak_fnc_extract_role;
_callsign = getText (configOf _unit >> "displayName");
_uuid = _targetUnit call armatak_fnc_extract_uuid;
_type = _targetUnit call armatak_fnc_extract_role;
_callsign = getText (configOf _targetUnit >> "displayName");
_marker_cot = [_uuid, _type, _unit_position select 1, _unit_position select 2, _unit_position select 3, _callsign, _unit_position select 5, _unit_position select 6];
"armatak" callExtension ["tcp_socket:cot:marker", [_marker_cot]];
};
};
} forEach _target;
@@ -0,0 +1,143 @@
params ["_drone", ["_cameraMode", "turret"]];
private _override = _drone getVariable ["armatak_uas_camera_data_override", []];
private _isLocalController = hasInterface && {!isNull player} && {(getConnectedUAV player) isEqualTo _drone};
if (!_isLocalController && {_override isEqualType []} && {(count _override) >= 7}) then {
private _updatedAt = _override param [6, -1000];
if ((time - _updatedAt) <= 5) exitWith {
private _overrideSpiAsl = _override param [4, []];
private _overrideSpiGeo = _override param [5, []];
_drone setVariable ["armatak_uas_spi_asl", _overrideSpiAsl, false];
_drone setVariable ["armatak_uas_spi_geo", _overrideSpiGeo, false];
_override select [0, 6]
};
};
private _defaultFov = _drone getVariable ["armatak_uas_fov", 60];
private _maxRange = _drone getVariable ["armatak_uas_max_range", 15000];
private _originASL = getPosASL _drone;
private _originAGL = ASLToAGL _originASL;
private _cameraDir = [];
private _spiASL = [];
private _slantRange = 0;
if (_cameraMode isNotEqualTo "fpv") then {
private _laserTarget = laserTarget _drone;
if (!isNull _laserTarget) then {
private _laserTargetWorld = getPosWorld _laserTarget;
private _laserTargetAslZ = (getPosASL _laserTarget) select 2;
_spiASL = [_laserTargetWorld select 0, _laserTargetWorld select 1, _laserTargetAslZ];
_cameraDir = _spiASL vectorDiff _originASL;
_slantRange = _originASL vectorDistance _spiASL;
};
};
if (_cameraDir isEqualTo [] && {_cameraMode isNotEqualTo "fpv"}) then {
private _uavControl = UAVControl _drone;
private _controlledTurretPath = _uavControl param [1, []];
private _candidateTurrets = [];
if ((_controlledTurretPath isEqualType []) && {_controlledTurretPath isNotEqualTo []}) then {
_candidateTurrets pushBack _controlledTurretPath;
};
{
if !(_x in _candidateTurrets) then {
_candidateTurrets pushBack _x;
};
} forEach (allTurrets _drone);
{
private _turretWeapons = _drone weaponsTurret _x;
if (_turretWeapons isNotEqualTo []) exitWith {
private _weapon = _turretWeapons select 0;
private _weaponDirection = _drone weaponDirection _weapon;
if (_weaponDirection isNotEqualTo [0, 0, 0]) then {
_cameraDir = _weaponDirection;
};
};
} forEach _candidateTurrets;
};
if (_cameraDir isEqualTo []) then {
_cameraDir = vectorDirVisual _drone;
};
private _dirMagnitude = vectorMagnitude _cameraDir;
if (_dirMagnitude <= 0) then {
private _fallbackAzimuth = getDir _drone;
_cameraDir = [sin _fallbackAzimuth, cos _fallbackAzimuth, -1];
_dirMagnitude = vectorMagnitude _cameraDir;
};
_cameraDir = _cameraDir vectorMultiply (1 / _dirMagnitude);
private _dirX = _cameraDir select 0;
private _dirY = _cameraDir select 1;
private _dirZ = _cameraDir select 2;
private _horizontalMagnitude = sqrt ((_dirX * _dirX) + (_dirY * _dirY));
private _azimuth = (((_dirX atan2 _dirY) + 360) mod 360);
private _elevation = (_dirZ atan2 (_horizontalMagnitude max 0.001));
if (_spiASL isEqualTo []) then {
private _altitudeAGL = (_originAGL select 2) max 0.1;
private _probeASL = _originASL vectorAdd (_cameraDir vectorMultiply _maxRange);
if (_dirZ < -0.01 && {terrainIntersectASL [_originASL, _probeASL]}) then {
private _near = _originASL;
private _far = _probeASL;
for "_i" from 0 to 24 do {
private _mid = [
((_near select 0) + (_far select 0)) / 2,
((_near select 1) + (_far select 1)) / 2,
((_near select 2) + (_far select 2)) / 2
];
if (terrainIntersectASL [_originASL, _mid]) then {
_far = _mid;
} else {
_near = _mid;
};
};
_spiASL = _far;
_slantRange = _originASL vectorDistance _spiASL;
} else {
private _verticalComponent = abs _dirZ;
if (_verticalComponent > 0.01) then {
_slantRange = (_altitudeAGL / _verticalComponent) min _maxRange;
} else {
_slantRange = _maxRange;
};
_slantRange = _slantRange max 1;
_spiASL = _originASL vectorAdd (_cameraDir vectorMultiply _slantRange);
_spiASL set [2, getTerrainHeightASL [_spiASL select 0, _spiASL select 1]];
};
};
if (_slantRange <= 0) then {
_slantRange = (_originASL vectorDistance _spiASL) max 1;
};
private _spiAgl = ASLToAGL _spiASL;
private _spiWorld = [_spiAgl select 0, _spiAgl select 1, (_spiAgl select 2) max 0];
private _spiGeo = _spiWorld call armatak_client_fnc_convertClientLocation;
_drone setVariable ["armatak_uas_spi_asl", _spiASL, false];
_drone setVariable ["armatak_uas_spi_geo", _spiGeo, false];
[
round _azimuth,
round _elevation,
round _defaultFov,
round (_slantRange max 1),
_spiASL,
_spiGeo
]
@@ -20,10 +20,10 @@ if (side _unit == east) then {
_callsign = getText (configOf _unit >> "displayName");
};
armatak_attribute_unit_callsign = _unit getVariable "armatak_attribute_unit_callsign";
private _unitCallsignOverride = _unit getVariable ["armatak_attribute_unit_callsign", ""];
if (!isNil "armatak_attribute_unit_callsign" or armatak_attribute_unit_callsign != '') then {
_callsign = armatak_attribute_unit_callsign;
if (_unitCallsignOverride isNotEqualTo "") then {
_callsign = _unitCallsignOverride;
};
_callsign
@@ -0,0 +1,23 @@
params["_latitude", "_longitude", "_altitude"];
_playerPosition = [_latitude, _longitude, _altitude];
_playerLatitude = _playerPosition select 0;
_playerLongitude = _playerPosition select 1;
_playerMaxLatitude = 20480;
_playerMaxLongitude = 20480;
_MapMaxLongitude = 33.728772;
_MapMinLongitude = 33.542815;
_MapMaxLatitude = 63.169746;
_MapMinLatitude = 62.938820;
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
[_RealLongitude, _RealLatitude, _playerPosition select 2]
@@ -0,0 +1,23 @@
params["_latitude", "_longitude", "_altitude"];
_playerPosition = [_latitude, _longitude, _altitude];
_playerLatitude = _playerPosition select 0;
_playerLongitude = _playerPosition select 1;
_playerMaxLatitude = 20480;
_playerMaxLongitude = 20480;
_MapMaxLatitude = -67.765153;
_MapMinLatitude = -68.223664;
_MapMaxLongitude = 10.593815;
_MapMinLongitude = 10.137466;
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
[_RealLongitude, _RealLatitude, _playerPosition select 2]
@@ -0,0 +1,30 @@
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
private _mapWidth = 8192;
private _mapHeight = 8192;
// SW corner (used as origin)
private _SW_lat = 63.005389;
private _SW_lon = 22.638957;
// SE corner
private _SE_lat = 63.010092;
private _SE_lon = 22.800107;
// NW corner
private _NW_lat = 63.078713;
private _NW_lon = 22.628542;
private _edgeSE_lat = _SE_lat - _SW_lat;
private _edgeSE_lon = _SE_lon - _SW_lon;
private _edgeNW_lat = _NW_lat - _SW_lat;
private _edgeNW_lon = _NW_lon - _SW_lon;
private _fx = _longitudeInGame / _mapWidth;
private _fy = _latitudeInGame / _mapHeight;
private _realLat = _SW_lat + (_fx * _edgeSE_lat) + (_fy * _edgeNW_lat);
private _realLon = _SW_lon + (_fx * _edgeSE_lon) + (_fy * _edgeNW_lon);
[_realLat, _realLon, _altitude]
@@ -0,0 +1,23 @@
params["_latitude", "_longitude", "_altitude"];
_playerPosition = [_latitude, _longitude, _altitude];
_playerLatitude = _playerPosition select 0;
_playerLongitude = _playerPosition select 1;
_playerMaxLongitude = 4992;
_playerMaxLatitude = 4992;
_MapMaxLongitude = -99.722665;
_MapMinLongitude = -99.775505;
_MapMaxLatitude = 32.159272;
_MapMinLatitude = 32.114011;
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
[_RealLongitude, _RealLatitude, _playerPosition select 2]
@@ -0,0 +1,30 @@
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
private _mapWidth = 11264;
private _mapHeight = 11264;
// SW corner (used as origin)
private _SW_lat = -51.736078;
private _SW_lon = -57.915032;
// SE corner
private _SE_lat = -51.736078;
private _SE_lon = -58.077879;
// NW corner
private _NW_lat = -51.634750;
private _NW_lon = -58.077879;
private _edgeSE_lat = _SE_lat - _SW_lat;
private _edgeSE_lon = _SE_lon - _SW_lon;
private _edgeNW_lat = _NW_lat - _SW_lat;
private _edgeNW_lon = _NW_lon - _SW_lon;
private _fx = _longitudeInGame / _mapWidth;
private _fy = _latitudeInGame / _mapHeight;
private _realLat = _SW_lat + (_fx * _edgeSE_lat) + (_fy * _edgeNW_lat);
private _realLon = _SW_lon + (_fx * _edgeSE_lon) + (_fy * _edgeNW_lon);
[_realLat, _realLon, _altitude]
@@ -0,0 +1,30 @@
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
private _mapWidth = 16384;
private _mapHeight = 16384;
// SW corner (used as origin)
private _SW_lat = -51.806546;
private _SW_lon = -57.939747;
// SE corner
private _SE_lat = -51.806546;
private _SE_lon = -57.701978;
// NW corner
private _NW_lat = -51.658913;
private _NW_lon = -57.939747;
private _edgeSE_lat = _SE_lat - _SW_lat;
private _edgeSE_lon = _SE_lon - _SW_lon;
private _edgeNW_lat = _NW_lat - _SW_lat;
private _edgeNW_lon = _NW_lon - _SW_lon;
private _fx = _longitudeInGame / _mapWidth;
private _fy = _latitudeInGame / _mapHeight;
private _realLat = _SW_lat + (_fx * _edgeSE_lat) + (_fy * _edgeNW_lat);
private _realLon = _SW_lon + (_fx * _edgeSE_lon) + (_fy * _edgeNW_lon);
[_realLat, _realLon, _altitude]
@@ -0,0 +1,30 @@
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
private _mapWidth = 10240;
private _mapHeight = 10240;
// SW corner (used as origin)
private _SW_lat = -51.863358;
private _SW_lon = -59.054585;
// SE corner
private _SE_lat = -51.863358;
private _SE_lon = -58.906155;
// NW corner
private _NW_lat = -51.771493;
private _NW_lon = -59.054585;
private _edgeSE_lat = _SE_lat - _SW_lat;
private _edgeSE_lon = _SE_lon - _SW_lon;
private _edgeNW_lat = _NW_lat - _SW_lat;
private _edgeNW_lon = _NW_lon - _SW_lon;
private _fx = _longitudeInGame / _mapWidth;
private _fy = _latitudeInGame / _mapHeight;
private _realLat = _SW_lat + (_fx * _edgeSE_lat) + (_fy * _edgeNW_lat);
private _realLon = _SW_lon + (_fx * _edgeSE_lon) + (_fy * _edgeNW_lon);
[_realLat, _realLon, _altitude]
@@ -0,0 +1,23 @@
params["_latitude", "_longitude", "_altitude"];
_playerPosition = [_latitude, _longitude, _altitude];
_playerLatitude = _playerPosition select 0;
_playerLongitude = _playerPosition select 1;
_playerMaxLongitude = 5120;
_playerMaxLatitude = 5120;
_MapMaxLatitude = -51.619725;
_MapMinLatitude = -51.664223;
_MapMaxLongitude = -58.394630;
_MapMinLongitude = -58.469580;
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
[_RealLongitude, _RealLatitude, _playerPosition select 2]
@@ -0,0 +1,23 @@
params["_latitude", "_longitude", "_altitude"];
_playerPosition = [_latitude, _longitude, _altitude];
_playerLatitude = _playerPosition select 0;
_playerLongitude = _playerPosition select 1;
_playerMaxLatitude = 32768;
_playerMaxLongitude = 32768;
_MapMaxLongitude = 35.285485;
_MapMinLongitude = 34.927617;
_MapMaxLatitude = 70.445404;
_MapMinLatitude = 70.016783;
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
[_RealLongitude, _RealLatitude, _playerPosition select 2]
+70 -27
View File
@@ -1,22 +1,11 @@
class CfgVehicles {
class Logic;
class Module_F : Logic
{
class AttributesBase
{
class Default;
class Module_F : Logic {
class AttributesBase {
class Edit;
class Combo;
class Checkbox;
class CheckboxNumber;
class ModuleDescription;
class Units;
};
class ModuleDescription
{
class AnyBrain;
};
class ModuleDescription;
};
class GVAR(moduleBase): Module_F {
@@ -30,13 +19,10 @@ class CfgVehicles {
scopeCurator = 2;
};
class GVAR(coreModule): GVAR(moduleBase) {
scope = 2;
class GVAR(connectionModuleBase): GVAR(moduleBase) {
scopeCurator = 0;
displayname = "CoT Router";
icon = "\a3\Modules_F_Curator\Data\iconRadio_ca.paa";
category = QEGVAR(main,moduleCategory);
function = QFUNC(3denCoreModuleConfig);
functionPriority = 1;
isGlobal = 0;
isTriggerActivated = 1;
@@ -47,19 +33,25 @@ class CfgVehicles {
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
};
class GVAR(tcpModule): GVAR(connectionModuleBase) {
scope = 2;
displayName = "CoT Router (TCP)";
function = QFUNC(3denTcpModuleConfig);
class Attributes: AttributesBase {
class GVAR(moduleInstanceAddress): Edit {
property = QGVAR(moduleInstanceAddress);
displayname = "TAK Server Address";
tooltip = "TAK Server Instance Address";
displayName = "TAK Server Address";
tooltip = "Hostname or IP address for the TAK or IronTAK server.";
typeName = "STRING";
defaultValue = "localhost";
defaultValue = "'localhost'";
};
class GVAR(moduleInstancePort): Edit {
property = QGVAR(moduleInstancePort);
displayname = "TAK Server TCP Port";
tooltip = "TAK Server instance Port for TCP connection";
displayName = "TAK Server TCP Port";
tooltip = "Port for the unauthenticated TCP socket.";
typeName = "NUMBER";
defaultValue = "8088";
};
@@ -67,17 +59,68 @@ class CfgVehicles {
};
class ModuleDescription: ModuleDescription {
description = "Generate the initial ARMATAK configuration, syncronizing all players to the TAK server instance";
description = "Connect ArmaTAK to a TAK server over plain TCP.";
sync[] = {"LocationArea_F"};
};
};
class GVAR(coreModuleCurator): GVAR(coreModule) {
class GVAR(enrollModule): GVAR(connectionModuleBase) {
scope = 2;
displayName = "CoT Router (Authenticated)";
function = QFUNC(3denEnrollModuleConfig);
class Attributes: AttributesBase {
class GVAR(moduleInstanceAddress): Edit {
property = QGVAR(moduleInstanceAddress);
displayname = "TAK Server Address";
tooltip = "Hostname or IP address used for enrollment and the final TLS connection.";
typeName = "STRING";
defaultValue = "'localhost'";
};
class GVAR(moduleEnrollmentPort): Edit {
property = QGVAR(moduleEnrollmentPort);
displayName = "Enrollment HTTPS Port";
tooltip = "Port used for GET /Marti/api/tls/config and POST /Marti/api/tls/signClient/v2.";
typeName = "NUMBER";
defaultValue = "8446";
};
class GVAR(moduleEnrollmentUsername): Edit {
property = QGVAR(moduleEnrollmentUsername);
displayName = "Enrollment Username";
tooltip = "Username used in Basic Auth for client certificate enrollment.";
typeName = "STRING";
defaultValue = "''";
};
class GVAR(moduleEnrollmentPassword): Edit {
property = QGVAR(moduleEnrollmentPassword);
displayName = "Enrollment Password";
tooltip = "Password used in Basic Auth for client certificate enrollment.";
typeName = "STRING";
defaultValue = "''";
};
class ModuleDescription: ModuleDescription {};
};
class ModuleDescription: ModuleDescription {
description = "Enroll a client certificate and connect ArmaTAK over mTLS.";
sync[] = {"LocationArea_F"};
};
};
class GVAR(tcpModuleCurator): GVAR(tcpModule) {
scope = 1;
scopeCurator = 2;
function = "";
displayName = "CoT Router (Zeus)";
curatorInfoType = "armatak_zeus_core_module_dialog";
displayName = "CoT Router (TCP, Zeus)";
curatorInfoType = "armatak_zeus_tcp_module_dialog";
};
class GVAR(enrollModuleCurator): GVAR(enrollModule) {
scope = 1;
scopeCurator = 2;
function = "";
displayName = "CoT Router (Authenticated, Zeus)";
curatorInfoType = "armatak_zeus_enroll_module_dialog";
};
class GVAR(markEntity): GVAR(moduleBase) {
+8 -2
View File
@@ -1,4 +1,10 @@
PREP(3denCoreModuleConfig);
PREP(3denEnrollModuleConfig);
PREP(3denTcpModuleConfig);
PREP(addRouterEntities);
PREP(removeRouterEntities);
PREP(routerEntityAdd);
PREP(routerEntityRemove);
PREP(ZeusCoreModuleConfig);
PREP(setRouterEntities);
PREP(startCotRouter);
PREP(ZeusEnrollModuleConfig);
PREP(ZeusTcpModuleConfig);
+4 -2
View File
@@ -4,8 +4,10 @@ class CfgPatches {
class ADDON {
name = COMPONENT_NAME;
units[] = {
QGVAR(coreModule),
QGVAR(coreModuleCurator),
QGVAR(tcpModule),
QGVAR(tcpModuleCurator),
QGVAR(enrollModule),
QGVAR(enrollModuleCurator),
QGVAR(markEntity)
};
weapons[] = {};
+135 -32
View File
@@ -3,69 +3,172 @@ class RscBackground;
class RscButton;
class RscEdit;
class armatak_zeus_core_module_dialog {
class armatak_zeus_tcp_module_dialog {
idd = 999991;
movingEnable = 0;
class ControlsBackground {
class armatak_gui_module_zeus_core_dialog_main_frame: RscBackground {
class main_frame: RscBackground {
idc = 1800;
x = "0.386562 * safezoneW + safezoneX";
y = "0.401 * safezoneH + safezoneY";
y = "0.29 * safezoneH + safezoneY";
w = "0.216563 * safezoneW";
h = "0.242 * safezoneH";
colorBackground[]={0,0,0,0.45};
h = "0.32 * safezoneH";
colorBackground[] = {0,0,0,0.45};
};
};
class Controls {
class armatak_gui_module_zeus_core_dialog_address_edit: RscEdit {
idc = 14000;
text = "localhost";
x = "0.391719 * safezoneW + safezoneX";
y = "0.445 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[]={0,0,0,0.5};
};
class armatak_gui_module_zeus_core_dialog_address_port_edit: RscEdit {
idc = 14001;
text = "8088";
x = "0.391719 * safezoneW + safezoneX";
y = "0.522 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[]={0,0,0,0.5};
};
class armatak_gui_module_zeus_core_dialog_address_text: RscText {
class address_text: RscText {
idc = 1000;
text = "TAK Server Address";
x = "0.391719 * safezoneW + safezoneX";
y = "0.412 * safezoneH + safezoneY";
y = "0.332 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class armatak_gui_module_zeus_core_dialog_address_port_text: RscText {
class address_edit: RscEdit {
idc = 14000;
text = "localhost";
x = "0.391719 * safezoneW + safezoneX";
y = "0.365 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[] = {0,0,0,0.5};
};
class port_text: RscText {
idc = 1001;
text = "TAK Server Port";
x = "0.391719 * safezoneW + safezoneX";
y = "0.489 * safezoneH + safezoneY";
y = "0.425 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class armatak_gui_module_zeus_core_dialog_address_button_cancel: RscButton {
class port_edit: RscEdit {
idc = 14001;
text = "8088";
x = "0.391719 * safezoneW + safezoneX";
y = "0.458 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[] = {0,0,0,0.5};
};
class button_cancel: RscButton {
idc = 1601;
text = "Cancel";
action = "closeDialog 2;";
x = "0.551563 * safezoneW + safezoneX";
y = "0.577 * safezoneH + safezoneY";
y = "0.535 * safezoneH + safezoneY";
w = "0.0464063 * safezoneW";
h = "0.055 * safezoneH";
};
class armatak_gui_module_zeus_core_dialog_address_button_ok: RscButton {
class button_ok: RscButton {
idc = 1600;
text = "Ok";
action = QUOTE(call FUNC(zeusCoreModuleConfig));
action = QUOTE(call FUNC(ZeusTcpModuleConfig));
x = "0.5 * safezoneW + safezoneX";
y = "0.577 * safezoneH + safezoneY";
y = "0.535 * safezoneH + safezoneY";
w = "0.0464063 * safezoneW";
h = "0.055 * safezoneH";
};
};
};
class armatak_zeus_enroll_module_dialog {
idd = 999992;
movingEnable = 0;
class ControlsBackground {
class main_frame: RscBackground {
idc = 1810;
x = "0.386562 * safezoneW + safezoneX";
y = "0.2 * safezoneH + safezoneY";
w = "0.216563 * safezoneW";
h = "0.52 * safezoneH";
colorBackground[] = {0,0,0,0.45};
};
};
class Controls {
class address_text: RscText {
idc = 1010;
text = "TAK Server Address";
x = "0.391719 * safezoneW + safezoneX";
y = "0.242 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class address_edit: RscEdit {
idc = 14100;
text = "localhost";
x = "0.391719 * safezoneW + safezoneX";
y = "0.275 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[] = {0,0,0,0.5};
};
class enroll_port_text: RscText {
idc = 1011;
text = "Enrollment HTTPS Port";
x = "0.391719 * safezoneW + safezoneX";
y = "0.335 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class enroll_port_edit: RscEdit {
idc = 14101;
text = "8446";
x = "0.391719 * safezoneW + safezoneX";
y = "0.368 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[] = {0,0,0,0.5};
};
class username_text: RscText {
idc = 1012;
text = "Enrollment Username";
x = "0.391719 * safezoneW + safezoneX";
y = "0.428 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class username_edit: RscEdit {
idc = 14102;
text = "";
x = "0.391719 * safezoneW + safezoneX";
y = "0.461 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[] = {0,0,0,0.5};
};
class password_text: RscText {
idc = 1013;
text = "Enrollment Password";
x = "0.391719 * safezoneW + safezoneX";
y = "0.521 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.033 * safezoneH";
};
class password_edit: RscEdit {
idc = 14103;
text = "";
x = "0.391719 * safezoneW + safezoneX";
y = "0.554 * safezoneH + safezoneY";
w = "0.20625 * safezoneW";
h = "0.044 * safezoneH";
colorBackground[] = {0,0,0,0.5};
};
class button_cancel: RscButton {
idc = 1611;
text = "Cancel";
action = "closeDialog 2;";
x = "0.551563 * safezoneW + safezoneX";
y = "0.645 * safezoneH + safezoneY";
w = "0.0464063 * safezoneW";
h = "0.055 * safezoneH";
};
class button_ok: RscButton {
idc = 1610;
text = "Ok";
action = QUOTE(call FUNC(ZeusEnrollModuleConfig));
x = "0.5 * safezoneW + safezoneX";
y = "0.645 * safezoneH + safezoneY";
w = "0.0464063 * safezoneW";
h = "0.055 * safezoneH";
};
@@ -1,64 +0,0 @@
#include "..\script_component.hpp"
params [
["_logic", objNull, [objNull]],
["_units", [], [[]]],
["_activated", true, [true]]
];
if (isServer) exitWith {
["Connecting to TCP Socket", "success", "TCP Socket"] call EFUNC(main,notify);
_tak_server_instance_address = _logic getVariable QGVAR(moduleInstanceAddress);
_tak_server_instance_port = _logic getVariable QGVAR(moduleInstancePort);
_tak_server_fulladdress = _tak_server_instance_address + ":" + (str _tak_server_instance_port);
missionNamespace setVariable ["armatak_server_instance", _tak_server_fulladdress];
missionNamespace setVariable ["armatak_tcp_socket_is_running", true];
"armatak" callExtension ["tcp_socket:start", [_tak_server_fulladdress]];
_syncUnits = synchronizedObjects _logic;
missionNamespace setVariable ["armatak_server_syncedUnits", _syncUnits];
GVAR(syncedUnits) = missionNamespace getVariable "armatak_server_syncedUnits";
[{
GVAR(syncedUnits) = missionNamespace getVariable "armatak_server_syncedUnits";
{
_objectType = _x call BIS_fnc_objectType;
switch (true) do {
case ((_objectType select 0) == "Soldier"): {
_callsign = [_x] call armatak_fnc_extract_unit_callsign;
_group_name = [group _x] call armatak_fnc_extract_group_color;
_group_role = [_x] call armatak_fnc_extract_group_role;
[_x, _callsign, _group_name, _group_role] call armatak_fnc_send_eud_cot;
[_x] call armatak_fnc_send_digital_pointer_cot;
};
case ((_objectType select 0) == "Vehicle"): {
_atak_type = [_x] call armatak_fnc_extract_role;
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
[_x, _atak_type, _callsign] call armatak_fnc_send_marker_cot;
_x call armatak_fnc_extract_sensor_data;
};
case ((_objectType select 0) == "VehicleAutonomous"): {
_atak_type = [_x] call armatak_fnc_extract_role;
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
[_x, _atak_type, _callsign] call armatak_fnc_send_drone_cot;
[_x] call armatak_fnc_send_digital_pointer_cot;
_x call armatak_fnc_extract_sensor_data;
};
};
} forEach GVAR(syncedUnits);
}, 1, []] call CBA_fnc_addPerFrameHandler;
};
true;
@@ -0,0 +1,37 @@
#include "..\script_component.hpp"
params [
["_logic", objNull, [objNull]],
["_units", [], [[]]],
["_activated", true, [true]]
];
if (isServer) exitWith {
if (missionNamespace getVariable ["armatak_tcp_socket_is_running", false]) exitWith {
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
};
["Connecting to authenticated TAK socket", "success", "TCP Socket"] call EFUNC(main,notify);
_tak_server_instance_address = _logic getVariable [QGVAR(moduleInstanceAddress), "localhost"];
_tak_server_enrollment_port = _logic getVariable [QGVAR(moduleEnrollmentPort), 8446];
_tak_server_enrollment_username = _logic getVariable [QGVAR(moduleEnrollmentUsername), ""];
_tak_server_enrollment_password = _logic getVariable [QGVAR(moduleEnrollmentPassword), ""];
"armatak" callExtension [
"tcp_socket:start_enroll_mtls",
[
_tak_server_instance_address,
_tak_server_instance_address,
str _tak_server_enrollment_port,
_tak_server_enrollment_username,
_tak_server_enrollment_password,
""
]
];
missionNamespace setVariable ["armatak_server_syncedUnits", synchronizedObjects _logic];
_tak_server_instance_address call FUNC(startCotRouter);
};
true
@@ -0,0 +1,26 @@
#include "..\script_component.hpp"
params [
["_logic", objNull, [objNull]],
["_units", [], [[]]],
["_activated", true, [true]]
];
if (isServer) exitWith {
if (missionNamespace getVariable ["armatak_tcp_socket_is_running", false]) exitWith {
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
};
["Connecting to TCP Socket", "success", "TCP Socket"] call EFUNC(main,notify);
_tak_server_instance_address = _logic getVariable [QGVAR(moduleInstanceAddress), "localhost"];
_tak_server_instance_port = _logic getVariable [QGVAR(moduleInstancePort), 8088];
_tak_server_fulladdress = _tak_server_instance_address + ":" + (str _tak_server_instance_port);
"armatak" callExtension ["tcp_socket:start", [_tak_server_fulladdress]];
missionNamespace setVariable ["armatak_server_syncedUnits", synchronizedObjects _logic];
_tak_server_fulladdress call FUNC(startCotRouter);
};
true
@@ -1,67 +0,0 @@
#include "..\script_component.hpp"
params ["_logic"];
_socket_is_running = missionNamespace getVariable ["armatak_tcp_socket_is_running", false];
if (_socket_is_running) exitWith {
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
closeDialog 1;
};
disableSerialization;
["Connecting to TCP Socket", "success", "TCP Socket"] call EFUNC(main,notify);
_tak_server_instance_address = ctrlText 14000;
_tak_server_instance_port = ctrlText 14001;
_tak_server_fulladdress = ((_tak_server_instance_address) + ":" + (_tak_server_instance_port));
missionNamespace setVariable ["armatak_server_instance", _tak_server_fulladdress];
missionNamespace setVariable ["armatak_tcp_socket_is_running", true];
"armatak" callExtension ["tcp_socket:start", [_tak_server_fulladdress]];
_syncUnits = [];
missionNamespace setVariable ["armatak_server_syncedUnits", _syncUnits];
GVAR(syncedUnits) = missionNamespace getVariable "armatak_server_syncedUnits";
[{
GVAR(syncedUnits) = missionNamespace getVariable "armatak_server_syncedUnits";
{
_objectType = _x call BIS_fnc_objectType;
switch (true) do {
case ((_objectType select 0) == "Soldier"): {
_callsign = [_x] call armatak_fnc_extract_unit_callsign;
_group_name = [group _x] call armatak_fnc_extract_group_color;
_group_role = [_x] call armatak_fnc_extract_group_role;
[_x, _callsign, _group_name, _group_role] call armatak_fnc_send_eud_cot;
[_x] call armatak_fnc_send_digital_pointer_cot;
};
case ((_objectType select 0) == "Vehicle"): {
_atak_type = [_x] call armatak_fnc_extract_role;
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
[_x, _atak_type, _callsign] call armatak_fnc_send_marker_cot;
_x call armatak_fnc_extract_sensor_data;
};
case ((_objectType select 0) == "VehicleAutonomous"): {
_atak_type = [_x] call armatak_fnc_extract_role;
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
[_x, _atak_type, _callsign] call armatak_fnc_send_drone_cot;
[_x] call armatak_fnc_send_digital_pointer_cot;
_x call armatak_fnc_extract_sensor_data;
};
};
} forEach GVAR(syncedUnits);
}, 1, []] call CBA_fnc_addPerFrameHandler;
deleteVehicle _logic;
closeDialog 1;
@@ -0,0 +1,33 @@
#include "..\script_component.hpp"
params ["_logic"];
if (missionNamespace getVariable ["armatak_tcp_socket_is_running", false]) exitWith {
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
closeDialog 1;
};
disableSerialization;
["Connecting to authenticated TAK socket", "success", "TCP Socket"] call EFUNC(main,notify);
_tak_server_instance_address = ctrlText 14100;
_tak_server_enrollment_port = ctrlText 14101;
_tak_server_enrollment_username = ctrlText 14102;
_tak_server_enrollment_password = ctrlText 14103;
"armatak" callExtension [
"tcp_socket:start_enroll_mtls",
[
_tak_server_instance_address,
_tak_server_instance_address,
_tak_server_enrollment_port,
_tak_server_enrollment_username,
_tak_server_enrollment_password,
""
]
];
_tak_server_instance_address call FUNC(startCotRouter);
deleteVehicle _logic;
closeDialog 1;
@@ -0,0 +1,22 @@
#include "..\script_component.hpp"
params ["_logic"];
if (missionNamespace getVariable ["armatak_tcp_socket_is_running", false]) exitWith {
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
closeDialog 1;
};
disableSerialization;
["Connecting to TCP Socket", "success", "TCP Socket"] call EFUNC(main,notify);
_tak_server_instance_address = ctrlText 14000;
_tak_server_instance_port = ctrlText 14001;
_tak_server_fulladdress = _tak_server_instance_address + ":" + _tak_server_instance_port;
"armatak" callExtension ["tcp_socket:start", [_tak_server_fulladdress]];
_tak_server_fulladdress call FUNC(startCotRouter);
deleteVehicle _logic;
closeDialog 1;
@@ -0,0 +1,28 @@
#include "..\script_component.hpp"
/*
* Author: Valmo, Codex
* Adds entities to the server-side CoT router entity list.
*
* Arguments:
* 0: Entity or entities to publish through the CoT router <OBJECT|ARRAY>
*
* Return Value:
* Current routed entities <ARRAY>
*
* Public: Yes
*/
params [
["_entities", [], [objNull, []]]
];
private _toAdd = if (_entities isEqualType objNull) then {[_entities]} else {_entities};
private _routed = missionNamespace getVariable ["armatak_server_syncedUnits", []];
_routed append _toAdd;
_routed = _routed arrayIntersect _routed;
_routed = _routed select {!isNull _x && {alive _x}};
missionNamespace setVariable ["armatak_server_syncedUnits", _routed, true];
GVAR(syncedUnits) = _routed;
_routed
@@ -0,0 +1,26 @@
#include "..\script_component.hpp"
/*
* Author: Valmo, Codex
* Removes entities from the server-side CoT router entity list.
*
* Arguments:
* 0: Entity or entities to remove from the CoT router <OBJECT|ARRAY>
*
* Return Value:
* Current routed entities <ARRAY>
*
* Public: Yes
*/
params [
["_entities", [], [objNull, []]]
];
private _toRemove = if (_entities isEqualType objNull) then {[_entities]} else {_entities};
private _routed = missionNamespace getVariable ["armatak_server_syncedUnits", []];
_routed = (_routed - _toRemove) select {!isNull _x && {alive _x}};
missionNamespace setVariable ["armatak_server_syncedUnits", _routed, true];
GVAR(syncedUnits) = _routed;
_routed
@@ -36,7 +36,7 @@ switch (false) do {
GVAR(syncedUnits) pushBack _unit;
missionNamespace setVariable ["armatak_server_syncedUnits", GVAR(syncedUnits)];
missionNamespace setVariable ["armatak_server_syncedUnits", GVAR(syncedUnits), true];
SETVAR(_unit,GVAR(isRouting),true);
deleteVehicle _logic;
@@ -34,7 +34,7 @@ switch (false) do {
};
} forEach GVAR(syncedUnits);
missionNmaespace setVariable ["armatak_server_syncedUnits", GVAR(syncedUnits)];
missionNamespace setVariable ["armatak_server_syncedUnits", GVAR(syncedUnits), true];
SETVAR(_unit,GVAR(isRouting),false);
deleteVehicle _logic;
@@ -0,0 +1,25 @@
#include "..\script_component.hpp"
/*
* Author: Valmo, Codex
* Replaces the server-side CoT router entity list with a de-duplicated list.
*
* Arguments:
* 0: Entities to publish through the CoT router <ARRAY>
*
* Return Value:
* Current routed entities <ARRAY>
*
* Public: Yes
*/
params [
["_entities", [], [[]]]
];
private _routed = _entities arrayIntersect _entities;
_routed = _routed select {!isNull _x && {alive _x}};
missionNamespace setVariable ["armatak_server_syncedUnits", _routed, true];
GVAR(syncedUnits) = _routed;
_routed
@@ -0,0 +1,80 @@
#include "..\script_component.hpp"
params [["_server_instance", "", [""]]];
missionNamespace setVariable ["armatak_server_instance", _server_instance];
missionNamespace setVariable ["armatak_tcp_socket_is_running", true];
if (isNil { missionNamespace getVariable "armatak_server_syncedUnits" }) then {
missionNamespace setVariable ["armatak_server_syncedUnits", []];
};
if (isNil { missionNamespace getVariable "armatak_server_clientClaimedEuds" }) then {
missionNamespace setVariable ["armatak_server_clientClaimedEuds", []];
};
GVAR(syncedUnits) = missionNamespace getVariable "armatak_server_syncedUnits";
[{
GVAR(syncedUnits) = missionNamespace getVariable "armatak_server_syncedUnits";
private _clientClaimedEuds = missionNamespace getVariable ["armatak_server_clientClaimedEuds", []];
{
_objectType = _x call BIS_fnc_objectType;
switch (true) do {
case ((_objectType select 0) == "Soldier"): {
private _uuid = _x call armatak_fnc_extract_uuid;
private _isClientEud = _x getVariable [QEGVAR(client,eudConnected), false];
if (_isClientEud) exitWith {
if !(_uuid in _clientClaimedEuds) then {
private _position = _x call armatak_client_fnc_extractClientPosition;
private _deleteCot = [_uuid, "a-f-G-U-C-I", _position select 1, _position select 2, _position select 3];
"armatak" callExtension ["tcp_socket:cot:delete", [_deleteCot]];
_clientClaimedEuds pushBack _uuid;
};
};
_clientClaimedEuds = _clientClaimedEuds - [_uuid];
_callsign = [_x] call armatak_fnc_extract_unit_callsign;
_group_name = [group _x] call armatak_fnc_extract_group_color;
_group_role = [_x] call armatak_fnc_extract_group_role;
[_x, _callsign, _group_name, _group_role] call armatak_fnc_send_eud_cot;
[_x] call armatak_fnc_send_digital_pointer_cot;
};
case (unitIsUAV _x): {
if !(_x getVariable ["armatak_uav_mavlink_broadcasting", false]) then {
_atak_type = [_x] call armatak_fnc_extract_role;
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
[_x, _atak_type, _callsign] call armatak_fnc_send_drone_cot;
_x call armatak_fnc_extract_sensor_data;
[_x] call armatak_fnc_send_digital_pointer_cot;
};
};
case ((_objectType select 0) == "Vehicle"): {
_atak_type = [_x] call armatak_fnc_extract_role;
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
[_x, _atak_type, _callsign] call armatak_fnc_send_marker_cot;
_x call armatak_fnc_extract_sensor_data;
[_x] call armatak_fnc_send_digital_pointer_cot;
};
case ((_objectType select 0) == "VehicleAutonomous"): {
if !(_x getVariable ["armatak_uav_mavlink_broadcasting", false]) then {
_atak_type = [_x] call armatak_fnc_extract_role;
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
[_x, _atak_type, _callsign] call armatak_fnc_send_drone_cot;
_x call armatak_fnc_extract_sensor_data;
[_x] call armatak_fnc_send_digital_pointer_cot;
};
};
};
} forEach GVAR(syncedUnits);
missionNamespace setVariable ["armatak_server_clientClaimedEuds", _clientClaimedEuds];
}, 1, []] call CBA_fnc_addPerFrameHandler;
true
+1
View File
@@ -0,0 +1 @@
armatak\armatak\addons\uav
@@ -9,3 +9,9 @@ class Extended_PreInit_EventHandlers {
init = QUOTE(call COMPILE_SCRIPT(XEH_preInit));
};
};
class Extended_PostInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_SCRIPT(XEH_postInit));
};
};
+6
View File
@@ -0,0 +1,6 @@
PREP(startMavlinkBroadcast);
PREP(stopMavlinkBroadcast);
PREP(updateMavlinkBroadcast);
PREP(resolveVideoUri);
PREP(handleMavlinkCallback);
PREP(parseMavlinkCallbackData);
+5
View File
@@ -0,0 +1,5 @@
#include "script_component.hpp"
if (!hasInterface) exitWith {};
SETVAR(player,GVAR(mavlinkPFH),-1);
@@ -3,15 +3,13 @@
class CfgPatches {
class ADDON {
name = COMPONENT_NAME;
units[] = {
QGVAR(videoModule)
};
units[] = {};
weapons[] = {};
requiredAddons[] = {
"cba_main",
"ace_main",
"armatak_main",
"armatak_server"
"armatak_client"
};
requiredVersion = REQUIRED_VERSION;
author = PROJECT_AUTHOR;
@@ -20,4 +18,3 @@ class CfgPatches {
};
#include "CfgEventHandlers.hpp"
//#include "CfgVehicles.hpp"
@@ -0,0 +1,382 @@
#include "..\script_component.hpp"
params ["_function", ["_data", "", [""]]];
if (!hasInterface) exitWith {};
private _payload = [_data] call FUNC(parseMavlinkCallbackData);
private _uav = getConnectedUAV player;
if (isNull _uav) then {
_uav = player getVariable [QGVAR(broadcastingUav), objNull];
};
if (isNull _uav) exitWith {
"armatak" callExtension ["log", [["warn", format ["Ignoring MAVLINK UDP callback %1 because no UAV is connected: %2", _function, _data]]]];
};
private _number = {
params ["_key", ["_default", 0]];
private _raw = _payload getOrDefault [_key, str _default];
private _value = parseNumber _raw;
if (!finite _value) exitWith {_default};
_value
};
private _uavGroup = {
params ["_vehicle"];
private _crew = crew _vehicle;
if (_crew isEqualTo []) exitWith {grpNull};
group (_crew select 0)
};
private _clearWaypoints = {
params ["_group"];
if (isNull _group) exitWith {};
for "_i" from ((count waypoints _group) - 1) to 0 step -1 do {
deleteWaypoint [_group, _i];
};
};
private _clearUavRoute = {
private _group = [_uav] call _uavGroup;
if (isNull _group) exitWith {false};
[_group] call _clearWaypoints;
_uav setVariable ["armatak_uas_mission_items", [], true];
true
};
private _geoToAtl = {
params ["_vehicle", "_lat", "_lon", ["_alt", -1]];
private _current = [_vehicle] call EFUNC(client,extractClientPosition);
private _currentLat = _current select 1;
private _currentLon = _current select 2;
private _currentAtl = getPosATL _vehicle;
private _northM = (_lat - _currentLat) * 111320;
private _eastM = (_lon - _currentLon) * (111320 * (cos _currentLat));
[
(_currentAtl select 0) + _eastM,
(_currentAtl select 1) + _northM,
if (_alt >= 0) then {_alt} else {_currentAtl select 2}
]
};
private _commandMove = {
params ["_vehicle", "_positionAtl", ["_type", "MOVE"], ["_radius", 80], ["_completion", 50]];
private _group = [_vehicle] call _uavGroup;
if (isNull _group) exitWith {false};
[_group] call _clearWaypoints;
_vehicle engineOn true;
_vehicle setVariable ["armatak_uas_armed", true, true];
_vehicle setFuel ((fuel _vehicle) max 0.1);
_vehicle flyInHeight ((_positionAtl select 2) max 10);
_vehicle doMove _positionAtl;
private _wp = _group addWaypoint [_positionAtl, 0];
_wp setWaypointType _type;
_wp setWaypointBehaviour "CARELESS";
_wp setWaypointCombatMode "BLUE";
_wp setWaypointSpeed "NORMAL";
_wp setWaypointCompletionRadius _completion;
if (_type == "LOITER") then {
_wp setWaypointLoiterRadius (_radius max 25);
_wp setWaypointLoiterType "CIRCLE_L";
};
true
};
private _appendMissionWaypoint = {
params ["_vehicle", "_positionAtl", "_command", "_seq", ["_radius", 80]];
private _group = [_vehicle] call _uavGroup;
if (isNull _group) exitWith {false};
private _type = switch (_command) do {
case 17;
case 18;
case 19;
case 31: {"LOITER"};
case 21: {"MOVE"};
default {"MOVE"};
};
private _wp = _group addWaypoint [_positionAtl, 0];
_wp setWaypointType _type;
_wp setWaypointBehaviour "CARELESS";
_wp setWaypointCombatMode "BLUE";
_wp setWaypointSpeed "NORMAL";
_wp setWaypointCompletionRadius 35;
if (_type == "LOITER") then {
_wp setWaypointLoiterRadius (_radius max 25);
_wp setWaypointLoiterType "CIRCLE_L";
};
if (_command == 21) then {
_wp setWaypointStatements ["true", "(vehicle this) land 'LAND'"];
};
private _items = _vehicle getVariable ["armatak_uas_mission_items", []];
_items pushBack [_seq, _command, _positionAtl];
_vehicle setVariable ["armatak_uas_mission_items", _items, true];
true
};
private _commandName = _payload getOrDefault ["command_name", "UNKNOWN"];
private _command = [_payload getOrDefault ["command", "-1"]] call BIS_fnc_parseNumber;
private _callsign = [_uav] call armatak_fnc_extract_marker_callsign;
private _applySpeed = {
params ["_speed"];
if (_speed <= 0) exitWith {false};
_uav limitSpeed _speed;
systemChat format ["ATAK SPEED %1m/s %2", round _speed, _callsign];
true
};
private _applyMode = {
params ["_mode"];
switch (_mode) do {
case 4: {
_uav engineOn true;
_uav setVariable ["armatak_uas_armed", true, true];
_uav setFuel ((fuel _uav) max 0.1);
systemChat format ["ATAK GUIDED %1", _callsign];
};
case 5: {
private _pos = getPosATL _uav;
[_uav, _pos, "LOITER", 80, 25] call _commandMove;
systemChat format ["ATAK LOITER %1", _callsign];
};
case 6;
case 21;
case 27: {
private _home = _uav getVariable ["armatak_uas_home_atl", getPosATL _uav];
_home set [2, ((_home select 2) max 60)];
[_uav, _home, "MOVE", 80, 60] call _commandMove;
systemChat format ["ATAK RTL %1", _callsign];
};
case 9: {
private _pos = getPosATL _uav;
_pos set [2, 0];
[_uav, _pos, "MOVE", 30, 20] call _commandMove;
_uav flyInHeight 0;
systemChat format ["ATAK LAND %1", _callsign];
};
default {
"armatak" callExtension ["log", [["info", format ["Unhandled MAVLINK mode %1 for UAV %2", _mode, _uav]]]];
};
};
};
private _setHomeFromGeo = {
params ["_lat", "_lon", "_alt"];
if (_lat == 0 && {_lon == 0}) exitWith {false};
private _homeAtl = [_uav, _lat, _lon, _alt] call _geoToAtl;
_uav setVariable ["armatak_uas_home_atl", _homeAtl, true];
_uav setVariable ["armatak_uas_home_geo", [_lat, _lon, _alt], true];
systemChat format ["ATAK HOME %1", _callsign];
true
};
switch (_function) do {
case "COMMAND_LONG": {
switch (_command) do {
case 176: {
private _mode = ["param2", -1] call _number;
if (_mode < 0) then {
_mode = ["param1", -1] call _number;
};
[_mode] call _applyMode;
};
case 178: {
private _speed = ["param2", -1] call _number;
if (_speed <= 0) then {
_speed = ["param1", -1] call _number;
};
[_speed] call _applySpeed;
};
case 179: {
private _useCurrent = (["param1", 0] call _number) >= 1;
if (_useCurrent) then {
private _pos = [_uav] call EFUNC(client,extractClientPosition);
private _relAlt = ((getPosATL _uav) select 2) max 0;
private _homeAtl = getPosATL _uav;
_uav setVariable ["armatak_uas_home_atl", _homeAtl, true];
_uav setVariable ["armatak_uas_home_geo", [_pos select 1, _pos select 2, (_pos select 3) - _relAlt], true];
systemChat format ["ATAK HOME %1", _callsign];
} else {
[["param5", 0] call _number, ["param6", 0] call _number, ["param7", 0] call _number] call _setHomeFromGeo;
};
};
case 400: {
private _doArm = (["param1", 0] call _number) >= 1;
_uav engineOn _doArm;
_uav setVariable ["armatak_uas_armed", _doArm, true];
if (_doArm) then {
_uav setFuel ((fuel _uav) max 0.1);
};
systemChat format ["ATAK %1 %2", ["DISARM", "ARM"] select _doArm, _callsign];
"armatak" callExtension ["log", [["info", format ["Applied MAVLINK ARM=%1 to UAV %2", _doArm, _uav]]]];
};
case 22: {
private _alt = (["param7", 75] call _number) max 10;
private _pos = getPosATL _uav;
_pos set [2, _alt];
[_uav, _pos, "MOVE", 80, 25] call _commandMove;
_uav setVelocityModelSpace [0, 15, 8];
systemChat format ["ATAK TAKEOFF %1m %2", round _alt, _callsign];
};
case 21: {
[9] call _applyMode;
};
case 20: {
[6] call _applyMode;
};
case 16: {
private _lat = ["param5", 0] call _number;
private _lon = ["param6", 0] call _number;
private _alt = ["param7", -1] call _number;
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
[_uav, _pos, "MOVE", 80, 50] call _commandMove;
systemChat format ["ATAK MOVE %1", _callsign];
};
case 17: {
private _lat = ["param5", 0] call _number;
private _lon = ["param6", 0] call _number;
private _alt = ["param7", -1] call _number;
private _radius = abs (["param3", 80] call _number);
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
[_uav, _pos, "LOITER", _radius, 30] call _commandMove;
systemChat format ["ATAK LOITER %1", _callsign];
};
case 43000: {
private _speed = ["param2", -1] call _number;
if (_speed <= 0) then {
_speed = ["param1", -1] call _number;
};
[_speed] call _applySpeed;
};
case 43001: {
private _alt = ["param1", -1] call _number;
if (_alt < 0) then {
_alt = ["param7", -1] call _number;
};
private _pos = getPosATL _uav;
_pos set [2, _alt max 10];
[_uav, _pos, "MOVE", 80, 25] call _commandMove;
systemChat format ["ATAK ALT %1m %2", round (_pos select 2), _callsign];
};
case 43002: {
private _heading = ["param1", -1] call _number;
if (_heading >= 0) then {
_uav setDir _heading;
systemChat format ["ATAK HDG %1 %2", round _heading, _callsign];
};
};
default {
"armatak" callExtension ["log", [["info", format ["Unhandled MAVLINK COMMAND_LONG %1 (%2): %3", _command, _commandName, _data]]]];
};
};
};
case "COMMAND_INT": {
private _lat = (["x", 0] call _number) / 1e7;
private _lon = (["y", 0] call _number) / 1e7;
private _alt = ["z", -1] call _number;
switch (_command) do {
case 16: {
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
[_uav, _pos, "MOVE", 80, 50] call _commandMove;
systemChat format ["ATAK MOVE %1", _callsign];
};
case 17;
case 192: {
private _radius = abs (["param3", 80] call _number);
private _direction = ["CIRCLE_L", "CIRCLE_R"] select ((["param4", 0] call _number) < 0);
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
private _type = ["MOVE", "LOITER"] select (_radius > 1);
[_uav, _pos, _type, _radius, 30] call _commandMove;
if (_type == "LOITER") then {
private _group = [_uav] call _uavGroup;
private _waypoints = waypoints _group;
if (_waypoints isNotEqualTo []) then {
(_waypoints select -1) setWaypointLoiterType _direction;
};
};
systemChat format ["ATAK %1 %2", _type, _callsign];
};
default {
"armatak" callExtension ["log", [["info", format ["Unhandled MAVLINK COMMAND_INT %1 (%2): %3", _command, _commandName, _data]]]];
};
};
};
case "MISSION_COUNT": {
private _count = ["count", 0] call _number;
[] call _clearUavRoute;
systemChat format ["ATAK ROUTE %1 pts %2", round _count, _callsign];
"armatak" callExtension ["log", [["info", format ["Receiving MAVLINK mission count=%1 for UAV %2", _count, _uav]]]];
};
case "MISSION_CLEAR_ALL": {
[] call _clearUavRoute;
systemChat format ["ATAK ROUTE CLEAR %1", _callsign];
};
case "MISSION_SET_CURRENT": {
private _seq = ["seq", 0] call _number;
"armatak" callExtension ["log", [["info", format ["MAVLINK mission set current seq=%1 for UAV %2", _seq, _uav]]]];
};
case "MISSION_ITEM";
case "MISSION_ITEM_INT": {
private _seq = ["seq", 0] call _number;
private _missionCommand = ["command", -1] call _number;
private _lat = ["lat", 0] call _number;
private _lon = ["lon", 0] call _number;
private _alt = ["alt", -1] call _number;
if (_lat == 0 && {_lon == 0}) exitWith {
"armatak" callExtension ["log", [["warn", format ["Ignoring MAVLINK mission item at zero coordinate: %1", _data]]]];
};
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
private _radius = abs (["param3", 80] call _number);
[_uav, _pos, _missionCommand, _seq, _radius] call _appendMissionWaypoint;
_uav engineOn true;
_uav setVariable ["armatak_uas_armed", true, true];
_uav setFuel ((fuel _uav) max 0.1);
_uav flyInHeight ((_pos select 2) max 10);
systemChat format ["ATAK ROUTE WP %1 %2", round _seq, _callsign];
"armatak" callExtension ["log", [["info", format ["Added MAVLINK mission item seq=%1 command=%2 posATL=%3 for UAV %4", _seq, _missionCommand, _pos, _uav]]]];
};
case "SET_HOME_POSITION": {
[["lat", 0] call _number, ["lon", 0] call _number, ["alt", 0] call _number] call _setHomeFromGeo;
};
case "SET_POSITION_TARGET_GLOBAL_INT": {
private _lat = ["lat", 0] call _number;
private _lon = ["lon", 0] call _number;
private _alt = ["alt", -1] call _number;
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
[_uav, _pos, "MOVE", 80, 40] call _commandMove;
systemChat format ["ATAK GUIDED MOVE %1", _callsign];
};
case "SET_MODE": {
private _mode = ["custom_mode", -1] call _number;
[_mode] call _applyMode;
};
case "COMMAND_ACK": {
"armatak" callExtension ["log", [["info", format ["Received MAVLINK COMMAND_ACK %1 (%2): %3", _command, _commandName, _data]]]];
};
case "MANUAL_CONTROL": {
"armatak" callExtension ["log", [["info", format ["Received MAVLINK MANUAL_CONTROL: %1", _data]]]];
};
default {
"armatak" callExtension ["log", [["info", format ["Unhandled MAVLINK UDP callback %1: %2", _function, _data]]]];
};
};
@@ -0,0 +1,17 @@
#include "..\script_component.hpp"
params [["_raw", "", [""]]];
private _pairs = createHashMap;
{
private _entry = _x;
private _separatorIndex = _entry find "=";
if (_separatorIndex > 0) then {
private _key = _entry select [0, _separatorIndex];
private _value = _entry select [_separatorIndex + 1];
_pairs set [_key, _value];
};
} forEach (_raw splitString ";");
_pairs
@@ -0,0 +1,45 @@
#include "..\script_component.hpp"
params [["_uav", objNull, [objNull]]];
private _defaultVideoUri = "rtsp://undefined:554/fpv";
private _activelyControlledUav = if (!isNull player) then {getConnectedUAV player} else {objNull};
private _normalize = {
params ["_rawUrl"];
private _url = trim _rawUrl;
if (_url isEqualTo "") exitWith {""};
if (_url find "://" >= 0) exitWith {_url};
if (_url find "/" >= 0) exitWith {
format ["rtsp://%1", _url]
};
format ["rtp://%1", _url]
};
if (!isNull _uav) then {
private _objectVideoUrl = [_uav] call armatak_fnc_extract_marker_video_url;
private _normalizedObjectVideoUrl = [_objectVideoUrl] call _normalize;
if (_normalizedObjectVideoUrl isNotEqualTo "") exitWith {
_normalizedObjectVideoUrl
};
private _activeSessionVideoUrl = player getVariable [QEGVAR(client,video_feed_url), ""];
private _normalizedActiveSessionVideoUrl = [_activeSessionVideoUrl] call _normalize;
if (_normalizedActiveSessionVideoUrl isNotEqualTo "") exitWith {
_normalizedActiveSessionVideoUrl
};
_defaultVideoUri
};
private _sessionVideoUrl = player getVariable [QEGVAR(client,video_feed_url), ""];
private _normalizedSessionVideoUrl = [_sessionVideoUrl] call _normalize;
if (_normalizedSessionVideoUrl isNotEqualTo "") exitWith {
_normalizedSessionVideoUrl
};
_defaultVideoUri
@@ -0,0 +1,16 @@
#include "..\script_component.hpp"
if (!hasInterface) exitWith {};
private _existingPfh = player getVariable [QGVAR(mavlinkPFH), -1];
if (_existingPfh >= 0) then {
[_existingPfh] call CBA_fnc_removePerFrameHandler;
};
player setVariable [QGVAR(broadcastingUav), objNull];
private _pfh = [{
call FUNC(updateMavlinkBroadcast);
}, 0.5, []] call CBA_fnc_addPerFrameHandler;
player setVariable [QGVAR(mavlinkPFH), _pfh];
@@ -0,0 +1,17 @@
#include "..\script_component.hpp"
if (!hasInterface) exitWith {};
private _existingPfh = player getVariable [QGVAR(mavlinkPFH), -1];
if (_existingPfh >= 0) then {
[_existingPfh] call CBA_fnc_removePerFrameHandler;
player setVariable [QGVAR(mavlinkPFH), -1];
};
private _broadcastingUav = player getVariable [QGVAR(broadcastingUav), objNull];
if (!isNull _broadcastingUav) then {
_broadcastingUav setVariable ["armatak_uav_mavlink_broadcasting", false, true];
systemChat "UAV broadcasting stopped";
};
player setVariable [QGVAR(broadcastingUav), objNull];
@@ -0,0 +1,122 @@
#include "..\script_component.hpp"
private _broadcastingUav = player getVariable [QGVAR(broadcastingUav), objNull];
if !(player getVariable [QEGVAR(client,eudConnected), false]) exitWith {
if (!isNull _broadcastingUav) then {
_broadcastingUav setVariable ["armatak_uav_mavlink_broadcasting", false, true];
player setVariable [QGVAR(broadcastingUav), objNull];
};
};
private _uav = getConnectedUAV player;
if (isNull _uav) then {
_uav = _broadcastingUav;
};
if (isNull _uav || {!alive _uav}) exitWith {
if (!isNull _broadcastingUav) then {
_broadcastingUav setVariable ["armatak_uav_mavlink_broadcasting", false, true];
player setVariable [QGVAR(broadcastingUav), objNull];
systemChat "UAV broadcasting stopped";
"armatak" callExtension ["log", [["info", "UAV broadcasting stopped because the UAV is no longer available"]]];
};
};
if (_broadcastingUav isNotEqualTo _uav) then {
if (!isNull _broadcastingUav) then {
_broadcastingUav setVariable ["armatak_uav_mavlink_broadcasting", false, true];
};
player setVariable [QGVAR(broadcastingUav), _uav];
_uav setVariable ["armatak_uav_mavlink_broadcasting", true, true];
private _callsign = [_uav] call armatak_fnc_extract_marker_callsign;
systemChat format ["Broadcasting UAV %1", _callsign];
"armatak" callExtension ["log", [["info", format ["Broadcasting UAV %1 via MAVLink mock to %2", _callsign, player getVariable [QEGVAR(client,mavlink_address), ""]]]]];
};
_uav setVariable ["armatak_uav_mavlink_broadcasting", true, true];
private _mavlinkAddress = player getVariable [QEGVAR(client,mavlink_address), ""];
if (_mavlinkAddress isEqualTo "") exitWith {};
private _pos = [_uav] call EFUNC(client,extractClientPosition);
private _relAlt = ((getPosATL _uav) select 2) max 0;
if (isNil {_uav getVariable "armatak_uas_home_atl"}) then {
_uav setVariable ["armatak_uas_home_atl", getPosATL _uav, true];
_uav setVariable ["armatak_uas_home_geo", [_pos select 1, _pos select 2, (_pos select 3) - _relAlt], true];
};
private _uuid = [_uav] call armatak_fnc_extract_uuid;
private _callsign = [_uav] call armatak_fnc_extract_marker_callsign;
private _videoUri = [_uav] call FUNC(resolveVideoUri);
private _dir = vectorDir _uav;
private _up = vectorUp _uav;
private _yaw = getDir _uav;
private _pitch = asin (((_dir select 2) max -1) min 1);
private _roll = asin (((_up select 0) max -1) min 1);
private _uavType = if (_uav isKindOf "Plane") then {1} else {[2, 3] select (_uav isKindOf "Helicopter")};
private _armed = _uav getVariable ["armatak_uas_armed", isEngineOn _uav];
if !(isEngineOn _uav) then {
_armed = false;
_uav setVariable ["armatak_uas_armed", false, true];
};
private _groundSpeed = abs (_pos select 6);
private _landed = (_relAlt <= 1.5) && {_groundSpeed <= 0.5};
private _batteryRemaining = round ((((fuel _uav) max 0) min 1) * 100);
private _gimbalRoll = 0;
private _gimbalPitch = _pitch;
private _gimbalYaw = _yaw;
private _hfov = _uav getVariable ["armatak_uas_fov", 60];
private _vfov = _uav getVariable ["armatak_uas_vfov", (_hfov * 0.5625)];
private _imageLat = _pos select 1;
private _imageLon = _pos select 2;
private _imageAlt = _pos select 3;
private _cameraData = [_uav, "turret"] call armatak_fnc_extract_uas_camera_data;
private _uavControl = UAVControl _uav;
private _controlledTurretPath = _uavControl param [1, []];
private _hasTurretCamera = ((_controlledTurretPath isEqualType []) && {_controlledTurretPath isNotEqualTo []}) || {(allTurrets _uav) isNotEqualTo []};
if (_cameraData isEqualType [] && {(count _cameraData) >= 6}) then {
_gimbalYaw = _cameraData param [0, _yaw];
_gimbalPitch = _cameraData param [1, _pitch];
_hfov = _cameraData param [2, _hfov];
_vfov = _uav getVariable ["armatak_uas_vfov", (_hfov * 0.5625)];
private _spiGeo = _cameraData param [5, []];
if (_spiGeo isEqualType [] && {(count _spiGeo) >= 3}) then {
_imageLat = _spiGeo select 0;
_imageLon = _spiGeo select 1;
_imageAlt = _spiGeo select 2;
};
};
private _systemPayload = [
_mavlinkAddress,
_uuid,
_callsign,
_uavType,
_pos select 1,
_pos select 2,
_pos select 3,
_relAlt,
_pos select 5,
_pos select 6,
_roll,
_pitch,
_yaw,
parseNumber _armed,
parseNumber _landed,
_gimbalRoll,
_gimbalPitch,
_gimbalYaw,
_videoUri,
_hfov,
_vfov,
_imageLat,
_imageLon,
_imageAlt,
parseNumber _hasTurretCamera,
_batteryRemaining
];
"armatak" callExtension ["uas:send_uas_system", [_systemPayload]];
@@ -1,17 +1,17 @@
#define COMPONENT video
#define COMPONENT_BEAUTIFIED Video Streaming
#define COMPONENT uav
#define COMPONENT_BEAUTIFIED UAV
#include "\armatak\armatak\addons\main\script_mod.hpp"
// #define DEBUG_MODE_FULL
// #define DISABLE_COMPILE_CACHE
// #define ENABLE_PERFORMANCE_COUNTERS
#ifdef DEBUG_ENABLED_MAIN
#ifdef DEBUG_ENABLED_UAV
#define DEBUG_MODE_FULL
#endif
#ifdef DEBUG_SETTINGS_MAIN
#define DEBUG_SETTINGS DEBUG_SETTINGS_MAIN
#ifdef DEBUG_SETTINGS_UAV
#define DEBUG_SETTINGS DEBUG_SETTINGS_UAV
#endif
#include "\z\ace\addons\main\script_macros.hpp"
-1
View File
@@ -1 +0,0 @@
armatak\armatak\addons\video
-73
View File
@@ -1,73 +0,0 @@
class CfgVehicles {
class Logic;
class Module_F : Logic
{
class AttributesBase
{
class Default;
class Edit;
class Combo;
class Checkbox;
class CheckboxNumber;
class ModuleDescription;
class Units;
};
class ModuleDescription
{
class AnyBrain;
};
};
class EGVAR(server,moduleBase);
class GVAR(videoModule): EGVAR(server,moduleBase) {
scope = 2;
scopeCurator = 0;
displayname = "Video Streaming Handler";
icon = "\a3\Modules_F_Curator\Data\iconRadio_ca.paa";
category = QEGVAR(main,moduleCategory);
function = QFUNC(videoParser);
functionPriority = 1;
isGlobal = 0;
isTriggerActivated = 1;
isDisposable = 1;
is3den = 0;
curatorCanAttach = 0;
curatorInfoType = "RscDisplayAttributeModuleNuke";
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
/*
class Attributes: AttributesBase {
class GVAR(instanceAddress): Edit {
property = QGVAR(instanceAddress);
displayname = "MediaMTX Provider Address";
tooltip = "MediaMTX Provider Instance Address";
typeName = "STRING";
defaultValue = "localhost";
};
class GVAR(instancePort): Edit {
property = QGVAR(instancePort);
displayname = QUOTE(MediaMTX Provider Port);
tooltip = QUOTE(MediaMTX Provider Port for handling video streams);
typeName = "STRING";
defaultValue = "8554";
};
class GVAR(instanceAuthUser): Edit {
property = QGVAR(instanceAuthUser);
displayname = QUOTE(MediaMTX Provider Username);
tooltip = QUOTE(MediaMTX Provider Instance Username);
typeName = "STRING";
defaultValue = "administrator";
};
class GVAR(instanceAuthPassword): Edit {
property = QGVAR(instanceAuthPassword);
displayname = QUOTE(MediaMTX Provider Password);
tooltip = QUOTE(MediaMTX Provider Instance Password);
typeName = "STRING";
defaultValue = "password";
};
};
*/
};
};
-1
View File
@@ -1 +0,0 @@
PREP(videoParser);
@@ -1,84 +0,0 @@
#include "..\script_component.hpp"
params [
["_logic", objNull, [objNull]],
["_units", [], [[]]],
["_activated", true, [true]]
];
if (isServer) exitWith {
private _instance_address = GETVAR(_logic,GVAR(instanceAddress),false);
private _instance_port = GETVAR(_logic,GVAR(instancePort),false);
private _instance_auth_user = GETVAR(_logic,GVAR(instanceAuthUser),false);
private _instance_auth_pass = GETVAR(_logic,GVAR(instanceAuthPassword),false);
SETMVAR(GVAR(instanceAddress),_instance_address);
SETMVAR(GVAR(instancePort),_instance_port);
SETMVAR(GVAR(instanceAuthUser),_instance_auth_user);
SETMVAR(GVAR(instanceAuthPassword),_instance_auth_pass);
_startAction = [
QGVAR(startStream),
"Start Video Feed",
"",
{
_uuid = (_this select 0) call armatak_fnc_extract_uuid;
_uuid_short = _uuid select [0, 8];
_role = roleDescription (_this select 0);
_name = name (_this select 0);
_role = [_role] call BIS_fnc_filterString;
_name = [_name] call BIS_fnc_filterString;
_stream_path = _name + "_" + _role + "_" + _uuid_short;
armatak_mediamtx_video_stream_instance_address = GETMVAR(instance_address,false);
armatak_mediamtx_video_stream_instance_port = missionNamespace getVariable "instance_port";
armatak_mediamtx_video_stream_instance_auth_user = missionNamespace getVariable "instance_auth_user";
armatak_mediamtx_video_stream_instance_auth_pass = missionNamespace getVariable "instance_auth_pass";
"armatak" callExtension ["video_stream:start", [armatak_mediamtx_video_stream_instance_address, armatak_mediamtx_video_stream_instance_port, _stream_path, armatak_mediamtx_video_stream_instance_auth_user, armatak_mediamtx_video_stream_instance_auth_pass]];
(_this select 0) setVariable ["armatak_video_feed_is_streaming", true];
},
{
(_this select 0) getVariable "armatak_video_feed_is_streaming" == false
}
] call ace_interact_menu_fnc_createAction;
[
"Man",
1,
["ACE_SelfActions"],
_startAction,
true
] call ace_interact_menu_fnc_addActionToClass;
_stopAction = [
"ArmatakStopStream",
"Stop Video Feed",
"",
{
"armatak" callExtension ["video_stream:stop", []];
SETVAR(_this select 0,GVAR(isStreaming),false);
},
{
GETVAR((this select 0),GVAR(isStreaming),false)
}
] call ace_interact_menu_fnc_createAction;
[
"Man",
1,
["ACE_SelfActions"],
_stopAction,
true
] call ace_interact_menu_fnc_addActionToClass;
if (isMultiplayer) then {
{
SETVAR(_x,GVAR(isStreaming),false);
} forEach playableUnits;
} else {
SETVAR(player,GVAR(isStreaming),false);
};
};
true;
+188
View File
@@ -0,0 +1,188 @@
# TAK authentication flows
This document describes the authentication and transport flows implemented by the ArmaTAK Rust extension. It is development documentation, not end-user documentation.
## Security model
ArmaTAK currently exposes three TCP connection modes:
| Mode | ArmaTAK entry point | Authentication | Transport protection | Intended use |
| --- | --- | --- | --- | --- |
| Plain TCP | `tcp_socket.start` | None | None | Local development or explicitly trusted isolated networks only |
| Manual mTLS | `tcp_socket.start_mtls` | Client X.509 certificate | TLS with server certificate validation | Production-compatible secure connection |
| Credential enrollment followed by mTLS | `tcp_socket.start_enroll_mtls` | HTTP Basic credentials during enrollment, then client X.509 certificate | Enrollment HTTPS currently skips certificate validation; the resulting TAK stream uses validated mTLS | Transitional/bootstrap flow; do not treat the enrollment phase as secure until server verification is enabled |
The official TAK Server example enables a TLS input on port `8089`. Its plaintext TCP/streaming examples are anonymous and are shown on ports `8087` and `8088`. ArmaTAK must not infer security from a port number; the selected connection mode controls the client-side security properties.
## Insecure flow: plain TCP
The plain flow is deliberately simple:
```text
Arma/SQF
|
| tcp_socket.start("host:port")
v
ConnectionConfig::Plain
|
v
TCP connect
|
| unencrypted CoT bytes
v
TAK Server plaintext/anonymous input
```
`ConnectionConfig::Plain` resolves the supplied address and opens a normal `TcpStream`. No TLS handshake occurs, there is no server identity verification, and no client identity is established by ArmaTAK.
Consequences:
- CoT payloads can be observed or modified by an on-path attacker.
- ArmaTAK cannot prove that it connected to the intended TAK Server.
- The server cannot authenticate ArmaTAK by certificate.
- Plain TCP must never be the production default.
The official TAK Server `CoreConfig.example.xml` keeps anonymous TCP inputs commented out and enables the TLS input instead. Use plain TCP only when a developer intentionally configures a corresponding non-TLS server input.
## Secure flow: manual mTLS
The manual mTLS flow uses pre-provisioned certificate material:
```text
Arma/SQF
|
| tcp_socket.start_mtls(
| address,
| server_name,
| ca_cert_path,
| client_cert_path,
| client_key_path
| )
v
ConnectionConfig::Mtls
|
+--> load trusted CA certificate(s)
+--> load client certificate chain
+--> load client private key
|
v
rustls client configuration
|
+--> validate server certificate chain
+--> validate TLS server name
+--> present client certificate
v
Authenticated mTLS TAK stream
```
The CA file is loaded into a dedicated `rustls::RootCertStore`; the client certificate and private key are used for client authentication. When `server_name` is empty, ArmaTAK derives a name from the target address. A non-empty `server_name` should be preferred when DNS and certificate names are known.
This is the preferred production flow when certificate provisioning is handled outside ArmaTAK.
## Enrollment flow: credentials to client certificate
TAK enrollment converts a username/password bootstrap credential into a short- or medium-lived client certificate, then reconnects using mTLS.
### Protocol contract
ArmaTAK follows the contract visible in the official TAK Server and ATAK implementations:
1. `GET https://HOST:ENROLL_PORT/Marti/api/tls/config`
2. Authenticate the request with HTTP Basic credentials.
3. Read `serverPort` and `enrollPath` from the TAK certificate configuration. If the fields are absent, ArmaTAK currently falls back to `8089` and `/Marti/api/tls/signClient/v2`.
4. Generate a local RSA key pair and PKCS#10 CSR. The private key never needs to be sent to the server.
5. `POST https://HOST:ENROLL_PORT{enrollPath}?clientUid=CLIENT_UID`
6. Authenticate the signing request with HTTP Basic credentials, send the CSR as `application/pkcs10`, and request JSON.
7. Parse `signedCert` and `ca0` from the enrollment response.
8. Build an in-memory client identity from the returned certificate plus the locally generated private key.
9. Connect to `HOST:serverPort` with mTLS and validate the TAK streaming server against the returned CA.
The official TAK Server exposes `/tls/config`, `/tls/signClient`, and `/tls/signClient/v2`. The v2 endpoint can return JSON containing `signedCert` plus `ca0`, `ca1`, and additional CA-chain entries. The current ArmaTAK implementation consumes `signedCert` and `ca0`.
### Current ArmaTAK enrollment security caveat
`enrollment_http_client()` currently uses `danger_accept_invalid_certs(true)`. Therefore the HTTPS bootstrap does **not** authenticate the enrollment server certificate.
That means the current enrollment phase is vulnerable to an active man-in-the-middle attacker who can impersonate the HTTPS enrollment endpoint and receive the submitted Basic credentials. The private key remains local, but the username/password bootstrap secret is still exposed to the impersonating endpoint.
The connection established *after* enrollment is mTLS and uses the returned CA, but that does not retroactively secure the bootstrap exchange. Consequently:
- `start_enroll_mtls` is a transitional flow, not the preferred production-secure flow in its current form.
- `start_mtls` with a trusted CA and pre-provisioned client certificate is the secure production path today.
- A future secure enrollment implementation must validate the HTTPS server with a trusted CA, certificate pin, or equivalent authenticated trust bootstrap.
- An explicit insecure/development enrollment switch is preferable to silently disabling verification.
The official ATAK enrollment manager exposes a `verifyHost` choice and records whether host verification is on or off. ArmaTAK should converge on the same explicit distinction.
## Target secure enrollment design
The intended production enrollment flow is:
```text
username/password
|
v
HTTPS enrollment endpoint
(server certificate MUST validate)
|
+--> GET /Marti/api/tls/config
+--> generate local private key + CSR
+--> POST configured signClient endpoint
v
signed client certificate + trusted CA
|
v
mTLS TAK streaming connection
|
+--> server authenticated by CA/name
+--> client authenticated by certificate
v
CoT traffic
```
The development-only insecure variant should be explicit:
```text
username/password
|
v
HTTPS enrollment endpoint
(certificate verification explicitly disabled)
|
v
WARNING / development-only path
```
Do not silently fall back from verified enrollment to unverified enrollment after a certificate validation failure.
## Secret-handling requirements
- Never include passwords, private keys, certificate payloads, or Authorization headers in logs.
- `ConnectionConfig::describe()` may include the username and client UID for diagnostics, but it must never include the password.
- Keep generated private keys in memory unless a future feature explicitly requires persistence.
- Do not commit development certificates or credentials to the repository.
- Treat enrollment credentials as bootstrap secrets and rotate/revoke them according to the TAK Server deployment policy.
## Test contract
Unit tests should lock down the protocol-level behavior that does not require a live TAK Server:
- official endpoint paths and default mTLS port;
- trimming and construction of enrollment URLs;
- parsing TAK enrollment configuration with explicit values and fallback defaults;
- PEM normalization;
- absence of passwords from diagnostic descriptions;
- TLS server-name derivation and PEM parsing where deterministic;
- CoT and MAVLink pure serialization logic.
Live certificate signing, TLS handshake behavior, socket reconnect behavior, mDNS, video processes, and Arma callback behavior belong in integration/E2E tests because they depend on operating-system or external-server state.
## Upstream references
The tests and protocol assumptions in this repository are based on these upstream implementations:
- TAK Server, `CertManagerApi.java`: <https://github.com/TAK-Product-Center/Server/blob/5187abd46d827d37cfc5708805eced197a837e49/src/takserver-core/takserver-war/src/main/java/com/bbn/tak/tls/CertManagerApi.java>
- TAK Server, `CoreConfig.example.xml`: <https://github.com/TAK-Product-Center/Server/blob/5187abd46d827d37cfc5708805eced197a837e49/src/takserver-core/example/CoreConfig.example.xml>
- ATAK/CommonCommo, `enrollmentmanager.cpp`: <https://github.com/TAK-Product-Center/atak-civ/blob/9f6893dd657feacc35ec5de03dad721c2e44170e/commoncommo/core/impl/enrollmentmanager.cpp>
Pinning the source revisions in this document keeps the development contract reproducible. When the upstream protocol changes, update both the implementation/tests and these references together.
+83
View File
@@ -0,0 +1,83 @@
# Rust testing and coverage policy
This document defines the development-time testing and coverage policy for the ArmaTAK Rust extension.
## Goals
The CI pipeline has two complementary coverage goals:
1. **100% line coverage for deterministic unit-testable code.** Pure protocol parsing, serialization, value normalization, packet construction, configuration descriptions, and other deterministic helpers belong in this gate.
2. **A non-regression floor for the complete Rust extension.** The complete report includes network adapters, mDNS, process management, Arma callbacks, socket loops, and TLS handshakes even when those components require integration or E2E infrastructure rather than unit tests.
The first goal prevents newly added deterministic logic from bypassing unit tests. The second prevents the overall extension from silently losing coverage while integration coverage is expanded.
## Local commands
Run the complete Rust unit test suite with:
```bash
cargo test --all-targets --locked
```
Install `cargo-llvm-cov` and inspect line coverage with:
```bash
cargo llvm-cov --all-targets --locked --summary-only
```
Generate an LCOV file with:
```bash
cargo llvm-cov --all-targets --locked --lcov --output-path lcov.info
```
CI publishes `lcov.info` as the `rust-lcov` artifact after a successful run.
## What belongs in the 100% unit gate
A source file should be included in the deterministic 100% gate when its behavior can be exercised without relying on:
- a live TAK Server;
- a live ATAK client;
- an operating-system socket peer;
- mDNS availability;
- an FFmpeg or external video process;
- timing-sensitive reconnect loops;
- Arma engine callback scheduling;
- real certificate enrollment or a real TLS handshake.
Examples include TAK enrollment URL/config parsing, XML serialization, MAVLink checksums and identity derivation, payload conversion, and diagnostic formatting.
When adding deterministic production code, add the tests in the same change. If a previously excluded file becomes fully unit-testable, bring it to 100% line coverage and remove it from the CI exclusion expression.
## What does not belong in a unit-only 100% gate
External adapters are still part of the full-extension coverage report, but forcing them into a unit-only percentage usually produces brittle or misleading tests. The following behavior should be verified through integration or E2E fixtures instead:
- TCP/UDP connection establishment and reconnect behavior;
- TAK Server certificate enrollment over HTTPS;
- real mTLS certificate-chain and server-name verification;
- mDNS publication/discovery;
- subprocess lifecycle and video streaming;
- Arma engine callback delivery and long-running endpoint threads.
These components must not be hidden from the complete report. Their zero or partial unit coverage remains visible and therefore continues to affect the global non-regression floor.
## Authentication test contract
Authentication protocol assumptions are derived from pinned official upstream implementations. See [authentication-flows.md](authentication-flows.md) for the exact TAK Server and ATAK source revisions.
At minimum, unit tests lock down:
- `/Marti/api/tls/config` construction;
- the v2 `signClient` enrollment path and the official `8089` mTLS default;
- Basic-auth enrollment configuration behavior at the adapter boundary;
- enrollment XML parsing, including missing and malformed tags;
- PEM normalization;
- the rule that diagnostic descriptions never contain enrollment passwords.
A future verified-enrollment implementation must add tests for the trust-bootstrap configuration and integration tests using a real or fixture TLS server. It must never silently downgrade from verified HTTPS to disabled certificate validation.
## Coverage changes
Coverage thresholds are intentionally enforced in CI rather than documented as aspirational numbers. When a change increases the complete-extension baseline, raise the global floor in the workflow in the same pull request or branch before merging. Do not lower either coverage threshold merely to make CI pass; lower thresholds only when a documented architectural change makes the previous metric invalid.
@@ -1,14 +0,0 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
@@ -1,738 +0,0 @@
btc_version = [
1,
25,
1
];
diag_log format (["=BTC= HEARTS AND MINDS VERSION %1.%2.%3"] + btc_version);
//Param
//<< Time options >>
btc_p_time = "btc_p_time" call BIS_fnc_getParamValue;
btc_p_acctime = "btc_p_acctime" call BIS_fnc_getParamValue;
btc_db_load = ("btc_p_load" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_auto_db = "btc_p_auto_db" call BIS_fnc_getParamValue isEqualTo 1;
btc_p_db_autoRestartTime = "btc_p_db_autoRestartTime" call BIS_fnc_getParamValue;
btc_p_db_autoRestartHour = [
"btc_p_db_autoRestartHour1" call BIS_fnc_getParamValue,
"btc_p_db_autoRestartHour2" call BIS_fnc_getParamValue
];
btc_p_db_autoRestartType = "btc_p_db_autoRestartType" call BIS_fnc_getParamValue;
btc_p_slot_isShare = "btc_p_slot_isShare" call BIS_fnc_getParamValue isEqualTo 1;
btc_p_change_time = ("btc_p_change_time" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_change_weather = ("btc_p_change_weather" call BIS_fnc_getParamValue) isEqualTo 1;
//<< Respawn options >>
btc_p_respawn_location = "btc_p_respawn_location" call BIS_fnc_getParamValue;
btc_p_respawn_fromOutsideBase = "btc_p_respawn_fromOutsideBase" call BIS_fnc_getParamValue;
btc_p_respawn_fromOutsideTimeout = "btc_p_respawn_fromOutsideTimeout" call BIS_fnc_getParamValue;
btc_p_rallypointTimer = "btc_p_rallypointTimer" call BIS_fnc_getParamValue;
btc_p_respawn_arsenal = ("btc_p_respawn_arsenal" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_respawn_ticketsAtStart = "btc_p_respawn_ticketsAtStart" call BIS_fnc_getParamValue;
btc_p_respawn_ticketsLost = 1 - ("btc_p_respawn_ticketsLost" call BIS_fnc_getParamValue);
btc_p_respawn_ticketsShare = ("btc_p_respawn_ticketsShare" call BIS_fnc_getParamValue) isEqualTo 0;
btc_p_respawn_ticketsFromPrisoners = "btc_p_respawn_ticketsFromPrisoners" call BIS_fnc_getParamValue;
btc_p_body_timeBeforeShowMarker = ("btc_p_body_timeBeforeShowMarker" call BIS_fnc_getParamValue) * 60;
//<< Faction options >>
private _p_en = "btc_p_en" call BIS_fnc_getParamValue;
private _p_en_AA = ("btc_p_AA" call BIS_fnc_getParamValue) isEqualTo 1;
private _p_en_tank = ("btc_p_tank" call BIS_fnc_getParamValue) isEqualTo 1;
private _p_civ = "btc_p_civ" call BIS_fnc_getParamValue;
private _p_civ_veh = "btc_p_civ_veh" call BIS_fnc_getParamValue;
//<< IED options >>
btc_p_ied = ("btc_p_ied" call BIS_fnc_getParamValue)/2;
private _p_ied_spot = "btc_p_ied_spot" call BIS_fnc_getParamValue;
btc_p_ied_placement = "btc_p_ied_placement" call BIS_fnc_getParamValue;
btc_p_ied_drone = ("btc_p_ied_drone" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_ied_power = "btc_p_ied_power" call BIS_fnc_getParamValue;
//<< Hideout/Cache options >>
btc_hideout_n = "btc_p_hideout_n" call BIS_fnc_getParamValue;
btc_info_cache_def = "btc_p_cache_info_def" call BIS_fnc_getParamValue;
btc_info_cache_ratio = "btc_p_cache_info_ratio" call BIS_fnc_getParamValue;
btc_info_intel_chance = "btc_p_info_chance" call BIS_fnc_getParamValue;
btc_p_info_houseDensity = "btc_p_info_houseDensity" call BIS_fnc_getParamValue;
//<< Skill options >>
btc_p_set_skill = ("btc_p_set_skill" call BIS_fnc_getParamValue) isEqualTo 1;
btc_AI_skill = [
("btc_p_set_skill_general" call BIS_fnc_getParamValue)/10,//general
("btc_p_set_skill_aimingAccuracy" call BIS_fnc_getParamValue)/10,//aimingAccuracy
("btc_p_set_skill_aimingShake" call BIS_fnc_getParamValue)/10,//aimingShake
("btc_p_set_skill_aimingSpeed" call BIS_fnc_getParamValue)/10,//aimingSpeed
("btc_p_set_skill_endurance" call BIS_fnc_getParamValue)/10,//endurance
("btc_p_set_skill_spotDistance" call BIS_fnc_getParamValue)/10,//spotDistance
("btc_p_set_skill_spotTime" call BIS_fnc_getParamValue)/10,//spotTime
("btc_p_set_skill_courage" call BIS_fnc_getParamValue)/10,//courage
("btc_p_set_skill_reloadSpeed" call BIS_fnc_getParamValue)/10,//reloadSpeed
("btc_p_set_skill_commanding" call BIS_fnc_getParamValue)/10//commanding
];
//<< Spawn options >>
btc_p_density_of_occupiedCity = ("btc_p_density_of_occupiedCity" call BIS_fnc_getParamValue)/100;
btc_p_mil_group_ratio = ("btc_p_mil_group_ratio" call BIS_fnc_getParamValue)/100;
btc_p_mil_wp_houseDensity = ("btc_p_wp_houseDensity" call BIS_fnc_getParamValue)/100;
btc_p_mil_static_group_ratio = ("btc_p_mil_static_group_ratio" call BIS_fnc_getParamValue)/100;
btc_p_civ_group_ratio = ("btc_p_civ_group_ratio" call BIS_fnc_getParamValue)/100;
btc_p_animals_group_ratio = ("btc_p_animals_group_ratio" call BIS_fnc_getParamValue)/100;
btc_p_veh_armed_ho = ("btc_p_veh_armed_ho" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_veh_armed_spawn_more = ("btc_p_veh_armed_spawn_more" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_patrol_max = "btc_p_patrol_max" call BIS_fnc_getParamValue;
btc_p_civ_max_veh = "btc_p_civ_max_veh" call BIS_fnc_getParamValue;
//<< Gameplay options >>
btc_p_sea = ("btc_p_sea" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_chem_sides = ("btc_p_chem_sides" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_chem_cache_probability = ("btc_p_chem_cache_probability" call BIS_fnc_getParamValue)/100;
btc_p_spect = ("btc_p_spect" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_side_mission_cycle = "btc_p_side_mission_cycle" call BIS_fnc_getParamValue;
//<< Arsenal options >>
btc_p_arsenal_Type = "btc_p_arsenal_Type" call BIS_fnc_getParamValue;
btc_p_arsenal_Restrict = "btc_p_arsenal_Restrict" call BIS_fnc_getParamValue;
btc_p_garage = ("btc_p_garage" call BIS_fnc_getParamValue) isEqualTo 1;
btc_p_autoloadout = "btc_p_autoloadout" call BIS_fnc_getParamValue;
//<< Other options >>
btc_global_reputation = "btc_p_rep" call BIS_fnc_getParamValue;
btc_p_rep_notify = "btc_p_rep_notify" call BIS_fnc_getParamValue;
btc_city_radiusOffset = ("btc_p_city_radiusOffset" call BIS_fnc_getParamValue) * 100;
btc_p_trigger = if (("btc_p_trigger" call BIS_fnc_getParamValue) isEqualTo 1) then {
"this && (false in (thisList apply {_x isKindOf 'Plane'})) && (false in (thisList apply {(_x isKindOf 'Helicopter') && (speed _x > 190)}))"
} else {
"this"
};
private _p_city_free_trigger = "btc_p_city_free_trigger" call BIS_fnc_getParamValue;
btc_p_flag = "btc_p_flag" call BIS_fnc_getParamValue;
btc_p_debug = "btc_p_debug" call BIS_fnc_getParamValue;
switch (btc_p_debug) do {
case 0 : {
btc_debug_log = false;
btc_debug = false;
};
case 1 : {
btc_debug_log = true;
btc_debug = true;
btc_debug_graph = false;
btc_debug_frames = 0;
};
case 2 : {
btc_debug_log = true;
btc_debug = false;
};
};
if (!isMultiplayer) then {
btc_debug_log = true;
btc_debug = true;
btc_debug_graph = false;
btc_debug_frames = 0;
};
private _cfgVehicles = configFile >> "CfgVehicles";
private _allClassVehicles = ("true" configClasses _cfgVehicles) apply {configName _x};
private _allClassSorted = _allClassVehicles select {getNumber (_cfgVehicles >> _x >> "scope") isEqualTo 2};
if (isServer) then {
btc_final_phase = false;
btc_delay_time = 0;
//City
btc_city_blacklist = [];//NAME FROM CFG
btc_p_city_free_trigger_condition = if (_p_city_free_trigger isEqualTo 0) then {
"thisList isEqualTo []"
} else {
format ["[thisList, %1] call btc_city_fnc_trigger_free_condition", _p_city_free_trigger]
};
//Civ
btc_civ_veh_active = [];
//Database
btc_db_serverCommandPassword = "btc_password"; //Define the same password in server.cfg like this: serverCommandPassword = "btc_password";
btc_db_warningTimeAutoRestart = 5;
//Hideout
btc_hideout_cityID = []; // List of city ID visible in debug mode for custom hideout location
btc_hideouts = []; publicVariable "btc_hideouts";
btc_hideouts_radius = 800;
if (btc_hideout_n isEqualTo 99) then {
btc_hideout_n = round random 10;
};
btc_hideout_safezone = 4000;
btc_hideout_range = 3500;
btc_hideout_cap_time = 1800;
btc_hideout_minRange = btc_hideout_range;
//IED
btc_ied_suic_time = 900;
btc_ied_suic_spawned = - btc_ied_suic_time;
btc_ied_offset = [0, -0.03, -0.07] select _p_ied_spot;
btc_ied_list = [];
btc_ied_range = 10;
btc_ied_power = ["Bo_GBU12_LGB_MI10", "R_MRAAWS_HE_F"] select btc_p_ied_power;
//FOB
btc_fobs = [[], [], []];
btc_fob_rallypointTimer = 60 * btc_p_rallypointTimer;
btc_body_deadPlayers = [];
//Patrol
btc_patrol_active = [];
btc_patrol_area = 2500;
//Rep
btc_rep_militia_call_time = 600;
btc_rep_militia_called = - btc_rep_militia_call_time;
btc_rep_delayed = [0, []];
//Chem
btc_chem_decontaminate = [];
btc_chem_contaminated = []; publicVariable "btc_chem_contaminated"; //Preserve reference
//Spect
btc_spect_emp = []; publicVariable "btc_spect_emp"; //Preserve reference
//Cache
btc_cache_type = [
_allClassSorted select {
_x isKindOf "ReammoBox_F" &&
{getText(_cfgVehicles >> _x >> "model") isEqualTo "\A3\weapons_F\AmmoBoxes\AmmoBox_F"}
},
["Land_PlasticCase_01_small_black_CBRN_F", "Land_PlasticCase_01_small_olive_CBRN_F", "Land_PlasticCase_01_small_CBRN_F"]
];
private _weapons_usefull = "true" configClasses (configFile >> "CfgWeapons") select {
getNumber (_x >> 'type') isEqualTo 1 &&
{getArray (_x >> 'magazines') isNotEqualTo []} &&
{getNumber (_x >> 'scope') isEqualTo 2}
};
btc_cache_weapons_type = _weapons_usefull apply {(toLower getText (_x >> "model")) select [1]};
//Hideout classname
btc_type_campfire = ["MetalBarrel_burning_F"] + (_allClassSorted select {_x isKindOf "Land_Campfire_F"});
btc_type_Scrapyard = _allClassSorted select {
_x isKindOf "Scrapyard_base_F" &&
{!("scrap" in toLower _x)}
};
btc_type_bigbox = ["Box_FIA_Ammo_F", "Box_East_AmmoVeh_F", "CargoNet_01_box_F", "O_CargoNet_01_ammo_F"] + btc_type_Scrapyard;
btc_type_seat = ["Land_WoodenLog_F", "Land_CampingChair_V2_F", "Land_CampingChair_V1_folded_F", "Land_CampingChair_V1_F"];
btc_type_sleepingbag = _allClassSorted select {_x isKindOf "Land_Sleeping_bag_F"};
btc_type_sleepingbag_folded = _allClassSorted select {_x isKindOf "Land_Sleeping_bag_folded_F"};
btc_type_tent = ["Land_TentA_F", "Land_TentDome_F"] + (_allClassSorted select {
_x isKindOf "Land_TentSolar_01_base_F" &&
{!(_x isKindOf "Land_TentSolar_01_folded_base_F")}
});
btc_type_camonet = ["Land_IRMaskingCover_02_F"] + (_allClassSorted select {_x isKindOf "Shelter_base_F"});
btc_type_satelliteAntenna = _allClassSorted select {_x isKindOf "Land_SatelliteAntenna_01_F"};
//Side
btc_side_ID = 0;
btc_side_list = ["supply", "mines", "vehicle", "get_city", "tower", "civtreatment", "checkpoint", "convoy", "rescue", "capture_officer", "hostage", "hack", "kill", "EMP", "removeRubbish", "massacre"]; // On ground (Side "convoy" and "capture_officer" are not design for map with different islands. Start and end city can be on different islands.)
if (btc_p_sea) then {btc_side_list append ["civtreatment_boat", "underwater_generator"]}; // On sea
if (btc_p_chem_sides) then {btc_side_list append ["chemicalLeak", "pandemic"]};
btc_side_list_use = [];
btc_type_tower = ["Land_Communication_F", "Land_TTowerBig_1_F", "Land_TTowerBig_2_F"];
btc_type_barrel = ["Land_GarbageBarrel_01_F", "Land_BarrelSand_grey_F", "MetalBarrel_burning_F", "Land_BarrelWater_F", "Land_MetalBarrel_F", "Land_MetalBarrel_empty_F"];
btc_type_canister = ["Land_CanisterPlastic_F"];
btc_type_pallet = ["Land_Pallets_stack_F", "Land_Pallets_F", "Land_Pallet_F"];
btc_type_box = ["Box_East_Wps_F", "Box_East_WpsSpecial_F", "Box_East_Ammo_F"] + (btc_cache_type select 0);
btc_type_generator = _allClassSorted select {_x isKindOf "Land_Device_assembled_F"};
btc_type_storagebladder = _allClassSorted select {_x isKindOf "StorageBladder_base_F"};
btc_type_mines = ["APERSMine", "APERSBoundingMine", "APERSTripMine"];
btc_type_power = ["Land_PowerGenerator_F", "Land_PortableGenerator_01_F"] + (_allClassSorted select {_x isKindOf "Machine_base_F"});
btc_type_cord = ["Land_ExtensionCord_F"];
btc_type_cones = ["Land_RoadCone_01_F", "RoadCone_F", "RoadCone_L_F"];
btc_type_fences = ["Land_PlasticNetFence_01_long_F", "Land_PlasticNetFence_01_long_d_F", "RoadBarrier_F", "TapeSign_F"];
btc_type_barrier = ["Land_CncBarrier_stripes_F", "Land_CncBarrier_F"];
btc_type_portable_light = _allClassSorted select {_x isKindOf "Land_PortableLight_single_F"};
btc_type_portableLamp = _allClassSorted select {
_x isKindOf "Land_PortableLight_02_base_F" ||
{_x isKindOf "TentLamp_01_standing_base_F"}
};
btc_type_tentLamp = _allClassSorted select {_x isKindOf "TentLamp_01_base_F"};
btc_type_first_aid_kits = ["Land_FirstAidKit_01_open_F", "Land_FirstAidKit_01_closed_F"];
btc_type_body_bags = _allClassSorted select {
_x isKindOf "Land_Bodybag_01_base_F" ||
{_x isKindOf "Land_Bodybag_01_empty_base_F"} ||
{_x isKindOf "Land_Bodybag_01_folded_base_F"}
};
btc_type_signs = _allClassSorted select {_x isKindOf "Land_Sign_Mines_F"};
btc_type_bloods = _allClassSorted select {_x isKindOf "Blood_01_Base_F"};
btc_type_medicals = _allClassSorted select {_x isKindOf "MedicalGarbage_01_Base_F"};
btc_type_table = _allClassSorted select {_x isKindOf "Land_CampingTable_F"};
btc_type_garbage = ["Land_Garbage_line_F","Land_Garbage_square3_F","Land_Garbage_square5_F"];
btc_type_foodSack = _allClassSorted select {_x isKindOf "Land_FoodSack_01_empty_base_F"};
btc_type_PaperBox = _allClassSorted select {
_x isKindOf "Land_PaperBox_01_small_ransacked_base_F" ||
{_x isKindOf "Land_PaperBox_01_small_open_base_F"} ||
{_x isKindOf "Land_PaperBox_01_small_destroyed_base_F"}
};
btc_type_EmergencyBlanket = _allClassSorted select {_x isKindOf "Land_EmergencyBlanket_01_base_F"};
btc_type_Sponsor = _allClassSorted select {
_x isKindOf "SignAd_Sponsor_F" &&
{"idap" in toLower _x}
};
btc_type_PlasticCase = _allClassSorted select {_x isKindOf "PlasticCase_01_base_F"};
btc_type_MedicalTent = _allClassSorted select {_x isKindOf "Land_MedicalTent_01_base_F"};
btc_type_cargo_ruins = _allClassSorted select {
_x isKindOf "Ruins_F" &&
{
"cargo40" in toLower _x ||
"cargo20" in toLower _x
}
};
btc_type_spill = ["Oil_Spill_F", "Land_DirtPatch_01_6x8_F"] + (_allClassSorted select {
_x isKindOf "Land_DirtPatch_02_base_F" ||
{_x isKindOf "WaterSpill_01_Base_F"}
});
btc_type_tarp = _allClassSorted select {_x isKindOf "Tarp_01_base_F"};
btc_type_SCBA = _allClassSorted select {_x isKindOf "SCBACylinder_01_base_F"};
btc_type_brush = _allClassSorted select {_x isKindOf "Brush_01_base_F"};
btc_type_broom = _allClassSorted select {_x isKindOf "Broom_01_base_F"};
btc_type_sponge = _allClassSorted select {_x isKindOf "Sponge_01_base_F"};
btc_type_connectorTentClosed = _allClassSorted select {_x isKindOf "Land_ConnectorTent_01_closed_base_F"};
btc_type_crossTent = _allClassSorted select {_x isKindOf "Land_ConnectorTent_01_cross_base_F"};
btc_type_connectorTent = (_allClassSorted select {_x isKindOf "Land_ConnectorTent_01_base_F"}) - btc_type_connectorTentClosed - btc_type_crossTent;
btc_type_cargoEMP = _allClassSorted select {_x isKindOf "Cargo_EMP_base_F"};
btc_type_antenna = _allClassSorted select {_x isKindOf "OmniDirectionalAntenna_01_base_F"};
btc_type_solarPanel = _allClassSorted select {_x isKindOf "Land_SolarPanel_04_base_F"};
btc_type_sports = (_allClassSorted select {_x isKindOf "SportItems_base_F"}) select {"ball" in _x};
btc_type_bottles = (_allClassSorted select {_x isKindOf "Items_base_F"}) select {"Bottle" in _x and (not ("stack" in _x))};
// The two arrays below are prefixes of buildings and their multiplier.
// They will multiply the values of btc_rep_malus_building_destroyed and btc_rep_malus_building_damaged,
// if a building is not present here it will be multiplied by 1.0.
// Use 0.0 to disable reputation hit on a specific's building destruction.
// You can modify this for any other terrain, clearing the table will also make all buildings just have a 1.0 multiplier.
// If there's a hit in btc_buildings_multiplier, btc_buildings_categories_multipliers will NOT be run
btc_buildings_multipliers = [
// Specific buildings that need to have a custom modifier.
["Land_BellTower", 0.2 ], ["Land_WIP", 1.5], ["Land_u_Addon_01", 0.2],
["Land_Airport_Tower", 10.0], ["Land_Mil_ControlTower", 10.0],
["Land_TentHangar", 7.0], ["Land_i_Shed_Ind", 1.5], ["Land_u_Shed_Ind", 1.5],
["Land_TTowerBig", 6.0], ["Land_TTowerSmall", 4.5], ["Land_cmp_Tower", 4.0]
];
// The multipliers are applied on top of each other, so "Chapel" and "Small" will both multiply the malus value
btc_buildings_categories_multipliers = [
["Shed", 0.75], ["Slum", 0.8], ["Small", 0.8], ["Big", 1.5], ["Villa", 2.0], ["Main", 3.0], ["Tower", 2.0],
["HouseBlock", 2.0], ["Panelak", 2.0], ["Tenement", 7.0],
["Barn", 1.5], ["School", 3.0], ["Office", 2.0], ["Shop", 1.5], ["Store", 1.5], ["Hospital", 12.0],
["Castle", 2.5], ["Chapel", 3.0], ["Minaret", 3.0], ["Mosque", 4.0], ["Church", 4.0], ["Kostel", 4.0],
["Lighthouse", 4.0],
["Airport", 4.0], ["Hangar", 1.75], ["ControlTower", 2.25], ["Terminal", 3.0],
["Hopper", 2.0], ["Tank", 4.0], ["Factory", 2.0], ["Transformer", 1.1],
["FuelStation", 5.0],
["Barracks", 1.75],
["spp", 3.0], ["Powerstation", 3.0],
["Pump", 2.5]
];
btc_buildings_changed = [];
//TAGS
btc_type_tags = ["Land_Graffiti_01_F", "Land_Graffiti_02_F", "Land_Graffiti_03_F", "Land_Graffiti_04_F", "Land_Graffiti_05_F"];
btc_type_tags_sentences = [
"STR_BTC_HAM_TAG_GO",
"STR_BTC_HAM_TAG_LN",
"STR_BTC_HAM_TAG_WWKY",
"STR_BTC_HAM_TAG_BA",
"STR_BTC_HAM_TAG_GH",
"STR_BTC_HAM_TAG_IE",
"STR_BTC_HAM_TAG_DWY",
"STR_BTC_HAM_TAG_WHY",
"STR_BTC_HAM_TAG_YGD"
];
btc_tags_player = [];
btc_tags_server = [];
//Flowers
btc_type_flowers = _allClassSorted select {_x isKindOf "FlowerBouquet_base_F"};
//IED
private _ieds = ["Land_GarbageContainer_closed_F", "Land_GarbageContainer_open_F", "Land_Portable_generator_F", "Land_WoodenBox_F", "Land_BarrelTrash_grey_F", "Land_Sacks_heap_F", "Land_Wreck_Skodovka_F", "Land_WheelieBin_01_F", "Land_GarbageBin_03_F"] + btc_type_pallet + btc_type_barrel + (_allClassSorted select {
_x isKindOf "GasTank_base_F" ||
{_x isKindOf "Garbage_base_F"} ||
{_x isKindOf "Stall_base_F"} ||
{_x isKindOf "Market_base_F"} ||
(_x isKindOf "Constructions_base_F" &&
{
"bricks" in toLower _x
}) ||
(_x isKindOf "Wreck_base_F" &&
{
"car" in toLower _x ||
"offroad" in toLower _x
})
});
btc_type_ieds = _ieds - ["Land_Garbage_line_F","Land_Garbage_square3_F","Land_Garbage_square5_F", "Land_MarketShelter_F", "Land_ClothShelter_01_F", "Land_ClothShelter_02_F"];
btc_model_ieds = btc_type_ieds apply {(toLower getText(_cfgVehicles >> _x >> "model")) select [1]};
btc_type_blacklist = btc_type_tags + btc_type_flowers + ["UserTexture1m_F"]; publicVariable "btc_type_blacklist";
btc_groundWeaponHolder = [];
//Respawn
btc_respawn_tickets = createHashMap;
btc_slots_serialized = createHashMap;
//Delay
btc_delay_agent = 0.1;
btc_delay_unit = 0.2;
btc_delay_vehicle = 0.3;
btc_delay_exec = 0.1;
//Explosives
btc_explosives = [];
btc_explosives_objectSide = createVehicle ["CBA_NamespaceDummy", [-1000, -1000, 0], [], 0, "NONE"];
};
//Civ
// Get all faction from mod there are currently running
//copyToClipboard str (["CIV"] call btc_fnc_get_class);
private _allfaction = ["CIV_F","DEFAULT","CIV_IDAP_F","UK3CB_ADC_C","UK3CB_CHC_C","UK3CB_MEC_C","UK3CB_TKC_C"]; //All factions
_p_civ = _allfaction select _p_civ; //Select faction selected from mission parameter
_p_civ_veh = _allfaction select _p_civ_veh; //Select faction selected from mission parameter
private _allclasse = [[_p_civ]] call btc_civ_fnc_class; //Create classes from factions, you can combine factions from the SAME side : [[_p_civ, "btc_ac","LOP_TAK_CIV"]] call btc_civ_fnc_class.
//Save class name to global variable
btc_civ_type_units = _allclasse select 0;
_allclasse = [[_p_civ_veh]] call btc_civ_fnc_class;
btc_civ_type_veh = _allclasse select 2;
btc_civ_type_boats = _allclasse select 1;
btc_w_civs = [
["srifle_DMR_06_hunter_F", "sgun_HunterShotgun_01_F", "srifle_DMR_06_hunter_khs_F", "sgun_HunterShotgun_01_Sawedoff_F", "Hgun_PDW2000_F", "arifle_AKM_F", "arifle_AKS_F"],
["hgun_Pistol_heavy_02_F", "hgun_Rook40_F", "hgun_Pistol_01_F"]
];
btc_g_civs = ["HandGrenade", "MiniGrenade", "ACE_M84", "ACE_M84"];
// ANIMALS
btc_animals_type = ["Hen_random_F", "Cock_random_F", "Fin_random_F", "Alsatian_Random_F", "Goat_random_F", "Sheep_random_F"];
//FOB
btc_fob_mat = "Land_Cargo20_blue_F";
btc_fob_structure = "Land_Cargo_HQ_V1_F";
btc_fob_flag = "Flag_NATO_F";
btc_fob_id = 0;
btc_fob_minDistance = 1500;
btc_fob_timeout = 1 * 60;
//IED
btc_type_ieds_ace = ["IEDLandBig_F", "IEDLandSmall_F"];
btc_ied_deleteOn = -1;
//Int
btc_int_ordersRadius = 25;
btc_int_search_intel_time = 4;
btc_int_sirenRadius = 35;
btc_int_beaconRadius = 15;
btc_int_hornRadius = 20;
btc_int_hornDelay = time;
//Info
btc_info_intel_type = [80, 95];//cache - hd - both
btc_info_hideout_radius = 4000;
btc_info_intels = ["Land_Camera_01_F", "Land_HandyCam_F", "Land_File1_F", "Land_FilePhotos_F", "Land_File2_F", "Land_File_research_F", "Land_MobilePhone_old_F", "Land_PortableLongRangeRadio_F", "Land_Laptop_02_unfolded_F"];
private _mapsIntel = switch (worldName) do {
case "Altis": {["Land_Map_altis_F", "Land_Map_unfolded_Altis_F"]};
case "Stratis": {["Land_Map_stratis_F", "Land_Map_unfolded_F"]};
case "Tanoa": {["Land_Map_Tanoa_F", "Land_Map_unfolded_Tanoa_F"]};
case "Malden": {["Land_Map_Malden_F", "Land_Map_unfolded_Malden_F"]};
case "Enoch": {["Land_Map_Enoch_F", "Land_Map_unfolded_Enoch_F"]};
default {["Land_Map_blank_F"]};
};
btc_info_intels append _mapsIntel;
//Supplies
btc_supplies_cargo = "Land_Cargo20_IDAP_F";
btc_supplies_mat = [
_allClassSorted select {_x isKindOf "Land_FoodSack_01_cargo_base_F"},
_allClassSorted select {_x isKindOf "Land_WaterBottle_01_stack_F"}
];
//Hazmat
btc_type_hazmat = ["HazmatBag_01_F", "Land_MetalBarrel_F"] + (_allClassSorted select {
_x isKindOf "Land_GarbageBarrel_02_base_F" ||
{_x isKindOf "Land_FoodContainer_01_F"} ||
{_x isKindOf "Land_CanisterFuel_F"} ||
{_x isKindOf "CBRNContainer_01_base_F"} ||
{_x isKindOf "PlasticCase_01_base_F"}
});
//Containers
btc_containers_mat = ["Land_Cargo20_military_green_F", "Land_Cargo40_military_green_F"];
//Player
btc_player_side = west;
btc_respawn_marker = "respawn_west";
btc_player_type = ["SoldierWB", "SoldierEB", "SoldierGB"] select ([west, east, independent] find btc_player_side);
//Log
btc_construction_array =
[
[
"Fortifications",
"Static",
"Ammobox",
"Containers",
"Supplies",
"FOB",
"Decontamination",
"Vehicle Logistic"
],
[
[
//"Fortifications"
"Land_BagBunker_Small_F",
"Land_BagFence_Corner_F",
"Land_BagFence_End_F",
"Land_BagFence_Long_F",
"Land_BagFence_Round_F",
"Land_BagFence_Short_F",
"Land_HBarrier_1_F",
"Land_HBarrier_3_F",
"Land_HBarrier_5_F",
"Land_HBarrierBig_F",
"Land_Razorwire_F",
"Land_CncBarrier_F",
"Land_CncBarrierMedium_F",
"Land_CncBarrierMedium4_F",
"Land_CncWall1_F",
"Land_CncWall4_F",
"Land_Mil_ConcreteWall_F",
"Land_Mil_WallBig_4m_F",
"Land_Mil_WallBig_Corner_F",
"Land_PortableLight_double_F",
"Land_Pod_Heli_Transport_04_medevac_black_F"
],
[
//"Static"
] + (_allClassSorted select {(
_x isKindOf "GMG_TriPod" ||
{_x isKindOf "StaticMortar"} ||
{_x isKindOf "HMG_01_base_F"} ||
{_x isKindOf "AA_01_base_F"} ||
{_x isKindOf "AT_01_base_F"}) && {
getNumber (_cfgVehicles >> _x >> "side") isEqualTo ([east, west, independent, civilian] find btc_player_side)
}
}),
[
//"Ammobox"
"Land_WoodenBox_F"
] + (_allClassSorted select {
_x isKindOf "ReammoBox_F" &&
{!(_x isKindOf "Slingload_01_Base_F")} &&
{!(_x isKindOf "Pod_Heli_Transport_04_base_F")}
}),
[
//"Containers"
] + btc_containers_mat,
[
//"Supplies"
btc_supplies_cargo
],
[
//"FOB"
btc_fob_mat
],
[
//"Decontamination"
"DeconShower_01_F"
],
[
//"Vehicle logistic"
"ACE_Wheel",
"ACE_Track",
"B_Slingload_01_Ammo_F",
"B_Slingload_01_Fuel_F"
] + (_allClassSorted select {_x isKindOf "FlexibleTank_base_F"})
]
];
(btc_construction_array select 1) params [
"_cFortifications", "_cStatics", "_cAmmobox",
"_cContainers", "_cSupplies", "_cFOB",
"_cDecontamination", "_cVehicle_logistic"
];
btc_log_def_loadable = flatten (btc_construction_array select 1) + flatten btc_supplies_mat + btc_type_hazmat;
btc_log_def_can_load = _cContainers;
btc_log_def_placeable = (_cFortifications + _cContainers + _cSupplies + _cFOB + _cDecontamination + _cVehicle_logistic + flatten btc_supplies_mat + btc_type_hazmat) select {
getNumber(_cfgVehicles >> _x >> "ace_dragging_canCarry") isEqualTo 0
};
btc_tow_vehicleTowing = objNull;
btc_log_placing_max_h = 12;
btc_log_placing = false;
btc_log_obj_created = [];
btc_log_fnc_get_nottowable = {
params ["_tower"];
switch (true) do {
case (_tower isKindOf "Tank") : {
["Plane", "Helicopter"]; //The tower is a tank so it can't tow: plane and helicopter
};
case (_tower isKindOf "Truck_F") : {
["Plane", "Helicopter"];
};
case (_tower isKindOf "Truck") : {
["Plane", "Helicopter"];
};
case (_tower isKindOf "Ship") : {
[];
};
case (_tower isKindOf "Car") : {
["Truck", "Truck_F", "Tank", "Plane", "Helicopter"]; //The tower is a car so it can't tow: truck, tank, plane and helicopter
};
default {
["Car", "Truck", "Truck_F", "Tank", "Plane", "Helicopter", "Ship"];
};
};
};
//Lift
btc_lift_fnc_getLiftable = {
params ["_chopper"];
private _array = [];
switch (typeOf _chopper) do {
case "B_SDV_01_F" : {
_array = ["Motorcycle", "ReammoBox", "ReammoBox_F", "StaticWeapon", "Car", "Truck", "Wheeled_APC_F", "Tracked_APC", "APC_Tracked_01_base_F", "APC_Tracked_02_base_F", "Air", "Ship", "Tank"] + ((btc_construction_array select 1) select 3) + ((btc_construction_array select 1) select 4) + ((btc_construction_array select 1) select 5);
};
default {
private _MaxCargoMass = getNumber (configOf _chopper >> "slingLoadMaxCargoMass");
switch (true) do {
case (_MaxCargoMass <= 510) : {
_array = ["Motorcycle", "ReammoBox", "ReammoBox_F", "Quadbike_01_base_F", "Strategic"];
};
case (_MaxCargoMass <= 2100) : {
_array = ["Motorcycle", "ReammoBox", "ReammoBox_F", "StaticWeapon", "Car"];
};
case (_MaxCargoMass <= 4100) : {
_array = ["Motorcycle", "ReammoBox", "ReammoBox_F", "StaticWeapon", "Car", "Truck_F", "Truck", "Wheeled_APC_F", "Air", "Ship"] + ((btc_construction_array select 1) select 3) + ((btc_construction_array select 1) select 4) + ((btc_construction_array select 1) select 5);
};
case (_MaxCargoMass <= 14000) : {
_array = ["Motorcycle", "ReammoBox", "ReammoBox_F", "StaticWeapon", "Car", "Truck_F", "Truck", "Wheeled_APC_F", "Tracked_APC", "APC_Tracked_01_base_F", "APC_Tracked_02_base_F", "Air", "Ship", "Tank"] + ((btc_construction_array select 1) select 3) + ((btc_construction_array select 1) select 4) + ((btc_construction_array select 1) select 5);
};
default {
_array = ["Motorcycle", "ReammoBox", "ReammoBox_F", "StaticWeapon", "Car", "Truck_F", "Truck", "Wheeled_APC_F", "Tracked_APC", "APC_Tracked_01_base_F", "APC_Tracked_02_base_F", "Air", "Ship", "Tank"] + ((btc_construction_array select 1) select 3) + ((btc_construction_array select 1) select 4) + ((btc_construction_array select 1) select 5);
};
};
};
};
_array
};
btc_ropes_deployed = false;
btc_lift_min_h = 7;
btc_lift_max_h = 12;
btc_lift_radius = 3;
btc_lift_HUD_x = 0.874;
btc_lift_HUD_y = 0.848;
//Mil
btc_hq = objNull;
// Get all faction from mod there are currently running
//copyToClipboard str (["EN"] call btc_fnc_get_class);
private _allfaction = ["IND_F","OPF_F","OPF_G_F","IND_G_F","BLU_G_F","IND_E_F","IND_L_F","BLU_F","IND_C_F","USAF","OPF_R_F","OPF_T_F","BLU_CTRG_F","OPF_GEN_F","BLU_GEN_F","BLU_T_F","O_TALIBAN","BLU_W_F","RHS_FACTION_VMF","RHS_FACTION_MSV","RHS_FACTION_RVA","RHS_FACTION_TV","RHS_FACTION_VDV","RHS_FACTION_VPVO","RHS_FACTION_VV","RHS_FACTION_VVS_C","RHS_FACTION_VVS","RHSSAF_FACTION_ARMY","RHSSAF_FACTION_ARMY_OPFOR","RHSSAF_FACTION_AIRFORCE_OPFOR","RHSSAF_FACTION_AIRFORCE","RHSSAF_FACTION_UN","RHS_FACTION_USARMY_D","RHS_FACTION_USARMY_WD","RHS_FACTION_USN","RHS_FACTION_SOCOM","RHS_FACTION_USAF","RHS_FACTION_USMC_D","RHS_FACTION_USMC_WD","RHSGREF_FACTION_UN","RHSGREF_FACTION_NATIONALIST","RHSGREF_FACTION_TLA","RHSGREF_FACTION_TLA_G","RHSGREF_FACTION_CDF_GROUND","RHSGREF_FACTION_CDF_GROUND_B","UK3CB_AAF_O","UK3CB_AAF_I","UK3CB_AAF_B","UK3CB_ANA_B","UK3CB_ANP_B","UK3CB_ADA_O","UK3CB_ADA_I","UK3CB_ADA_B","UK3CB_ADR_O","UK3CB_ADR_I","UK3CB_ADR_B","UK3CB_ADG_O","UK3CB_ADG_I","UK3CB_ADG_B","UK3CB_ADC_O","UK3CB_ADC_I","UK3CB_ADC_B","UK3CB_ADE_O","UK3CB_ADE_I","UK3CB_ADM_O","UK3CB_ADM_I","UK3CB_ADM_B","UK3CB_ADP_O","UK3CB_ADP_I","UK3CB_ADP_B","UK3CB_APD_O","UK3CB_APD_I","UK3CB_APD_B","UK3CB_ARD_O","UK3CB_ARD_I","UK3CB_ARD_B","UK3CB_CHD_O","UK3CB_CHD_W_O","UK3CB_CHD_B","UK3CB_CHD_W_B","UK3CB_CHD_I","UK3CB_CHD_W_I","UK3CB_CHC_O","UK3CB_CHC_I","UK3CB_CHC_B","UK3CB_CCM_O","UK3CB_CCM_B","UK3CB_CCM_I","UK3CB_CPD_O","UK3CB_CPD_I","UK3CB_CPD_B","UK3CB_CW_US_B_EARLY","UK3CB_CW_US_B_LATE","UK3CB_CW_SOV_O_EARLY","UK3CB_CW_SOV_O_LATE","UK3CB_CSAT_A_O","UK3CB_CSAT_W_O","UK3CB_CSAT_M_O","UK3CB_CSAT_B_O","UK3CB_CSAT_N_O","UK3CB_CSAT_F_O","UK3CB_CSAT_G_O","UK3CB_CSAT_U_O","UK3CB_CSAT_S_O","UK3CB_FIA_O","UK3CB_FIA_I","UK3CB_FIA_B","UK3CB_GAF_O","UK3CB_GAF_I","UK3CB_GAF_B","UK3CB_ION_O_DESERT","UK3CB_ION_I_DESERT","UK3CB_ION_B_DESERT","UK3CB_ION_O_URBAN","UK3CB_ION_I_URBAN","UK3CB_ION_B_URBAN","UK3CB_ION_O_WINTER","UK3CB_ION_I_WINTER","UK3CB_ION_B_WINTER","UK3CB_ION_O_WOODLAND","UK3CB_ION_I_WOODLAND","UK3CB_ION_B_WOODLAND","UK3CB_KRG_O","UK3CB_KRG_I","UK3CB_KRG_B","UK3CB_KDF_O","UK3CB_KDF_I","UK3CB_KDF_B","UK3CB_LDF_O","UK3CB_LDF_I","UK3CB_LDF_B","UK3CB_LFR_O","UK3CB_LFR_I","UK3CB_LFR_B","UK3CB_LSM_O","UK3CB_LSM_I","UK3CB_LSM_B","UK3CB_LNM_O","UK3CB_LNM_I","UK3CB_LNM_B","UK3CB_MDF_O","UK3CB_MDF_I","UK3CB_MDF_B","UK3CB_MEC_O","UK3CB_MEC_I","UK3CB_MEC_B","UK3CB_MEE_O","UK3CB_MEE_I","UK3CB_MEI_O","UK3CB_MEI_I","UK3CB_MEI_B","UK3CB_NAP_O","UK3CB_NAP_I","UK3CB_NAP_B","UK3CB_NFA_O","UK3CB_NFA_I","UK3CB_NFA_B","UK3CB_NPD_O","UK3CB_NPD_I","UK3CB_NPD_B","UK3CB_TKC_O","UK3CB_TKC_I","UK3CB_TKC_B","UK3CB_TKM_O","UK3CB_TKA_O","UK3CB_TKA_I","UK3CB_TKA_B","UK3CB_TKP_O","UK3CB_TKP_I","UK3CB_TKP_B","UK3CB_TKM_B","UK3CB_TKM_I","UK3CB_UN_I","UK3CB_UN_B","RHSGREF_FACTION_CDF_AIR","RHSGREF_FACTION_CDF_AIR_B","RHSGREF_FACTION_CDF_NG","RHSGREF_FACTION_CDF_NG_B","RHSGREF_FACTION_CHDKZ","RHSGREF_FACTION_CHDKZ_G","RHSGREF_FACTION_HIDF"]; //All factions
_p_en = _allfaction select _p_en; //Select faction selected from mission parameter
_allclasse = [[_p_en], _p_en_AA, _p_en_tank] call btc_mil_fnc_class; //Create classes from factions, you can combine factions like that: [[_p_en , "IND_F"], _p_en_AA, _p_en_tank] call btc_mil_fnc_class;
//Save class name to global variable
btc_enemy_side = _allclasse select 0;
btc_type_units = _allclasse select 1;
btc_type_divers = _allclasse select 2;
btc_type_crewmen = _allclasse select 3;
btc_type_boats = _allclasse select 4;
btc_type_motorized = _allclasse select 5;
btc_type_motorized_armed = _allclasse select 6;
btc_type_mg = _allclasse select 7;
btc_type_gl = _allclasse select 8;
//Sometimes you need to remove units: - ["Blabla","moreBlabla"];
//Sometimes you need to add units: + ["Blabla","moreBlabla"];
switch (_p_en) do {
/*case "Myfactionexemple" : {
btc_type_units = btc_type_units - ["Blabla","moreBlabla"];
btc_type_divers = btc_type_divers + ["Blabla","moreBlabla"];
btc_type_crewmen = "Blabla";
btc_type_boats = btc_type_boats;
btc_type_motorized = btc_type_motorized;
btc_type_mg = btc_type_mg;
btc_type_gl = btc_type_gl;
};*/
case "OPF_G_F" : {
btc_type_motorized = btc_type_motorized + ["I_Truck_02_transport_F", "I_Truck_02_covered_F"];
btc_type_motorized_armed = btc_type_motorized_armed + ["I_Heli_light_03_F"];
};
case "IND_C_F" : {
btc_type_motorized = btc_type_motorized + ["I_G_Offroad_01_repair_F", "I_G_Offroad_01_F", "I_G_Quadbike_01_F", "I_G_Van_01_fuel_F", "I_Truck_02_transport_F", "I_Truck_02_covered_F"];
btc_type_motorized_armed = btc_type_motorized_armed + ["I_Heli_light_03_F", "I_G_Offroad_01_F"];
btc_type_units = btc_type_units - ["I_C_Soldier_Camo_F"];
};
};
//Chem
btc_chem_range = 3;
//Spect
btc_spect_range = 1000;
btc_spect_updateOn = -1;
//Rep
btc_rep_bonus_cache = 100;
btc_rep_bonus_civ_hh = 3;
btc_rep_bonus_disarm = 15;
btc_rep_bonus_hideout = 200;
btc_rep_bonus_mil_killed = 0.25;
btc_rep_bonus_IEDCleanUp = 10;
btc_rep_bonus_removeTag = 3;
btc_rep_bonus_removeTagLetter = 0.5;
btc_rep_bonus_foodGive = 0.5;
btc_rep_bonus_grave = 5;
btc_rep_malus_civ_hd = - 2;
btc_rep_malus_animal_hd = - 1;
btc_rep_malus_civ_killed = - 10;
btc_rep_malus_animal_killed = - 5;
btc_rep_malus_civ_suppressed = - 4;
btc_rep_malus_player_respawn = - 10;
btc_rep_malus_veh_killed = - 25;
btc_rep_malus_building_damaged = - 2.5;
btc_rep_malus_building_destroyed = - 5;
btc_rep_malus_foodRemove = - btc_rep_bonus_foodGive;
btc_rep_malus_breakDoor = - 2;
btc_rep_malus_wheelChange = - 7;
btc_rep_malus_mil_killed = - 10;
btc_rep_level_veryLow = 0;
btc_rep_level_low = 200;
btc_rep_level_normal = 500;
btc_rep_level_high = 750;
btc_rep_food = "ACE_Banana";
//Headless
btc_units_owners = [];
//Door
btc_door_breaking_time = 60;
//Flag
btc_flag_textures = [
"\A3\Data_F\Flags\flag_red_CO.paa",
"\A3\Data_F\Flags\flag_green_CO.paa",
"\A3\Data_F\Flags\flag_blue_CO.paa",
"z\ace\addons\flags\data\Flag_yellow_co.paa",
"\A3\Data_F\Flags\flag_NATO_CO.paa"
];
//Respawn
btc_body_bagTicketPlayer = 1;
btc_body_prisonerTicket = 1;
btc_startDate = [2035, 6, 24, 12, 15];
File diff suppressed because one or more lines are too long
@@ -1,59 +0,0 @@
private _mainCategory = localize "str_3den_display3den_menubar_helpdoc_text";
player createDiarySubject [_mainCategory, _mainCategory, "\A3\ui_f\data\igui\cfg\simpleTasks\types\documents_ca.paa"];
//Headless and Data base
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_DOC_HEADLESS_TITLE", localize "STR_BTC_HAM_DOC_HEADLESS_TEXT"]];
//Door locked
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_DOC_DOOR_TITLE", localize "STR_BTC_HAM_DOC_DOOR_TEXT"]];
//Chemical warfare
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_DOC_CHEMICALWARFARE_TITLE", localize "STR_BTC_HAM_DOC_CHEMICALWARFARE_TEXT"]];
//Spectrum devices
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_DOC_SPECTRUMDEVICES_TITLE", localize "STR_BTC_HAM_DOC_SPECTRUMDEVICES_TEXT"]];
//Vehicles
player createDiaryRecord [_mainCategory, [localize "str_a3_cfghints_command_vehicles1", localize "STR_BTC_HAM_DOC_VEHICLES_TEXT"]];
//Side Mission
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_DOC_SIDEMISSION_TITLE", localize "STR_BTC_HAM_DOC_SIDEMISSION_TEXT"]];
//Respawn tickets
player createDiaryRecord [_mainCategory, [localize "str_a3_cfgvehicles_modulerespawntickets_f", localize "STR_BTC_HAM_DOC_RESPAWN_TEXT"]];
//Respawn position
player createDiaryRecord [_mainCategory, [localize "str_a3_cfgvehicles_modulerespawnposition_f_0", localize "STR_BTC_HAM_DOC_FOB_TEXT"]];
//Sling loading
player createDiaryRecord [_mainCategory, [localize "STR_A3_SlingLoad1", localize "STR_BTC_HAM_DOC_SLING_TEXT"]];
//Logistic point
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_MSQM_MRK_LOGPOINT", localize "STR_BTC_HAM_DOC_LOGISTICP_TEXT"]];
//Logistic
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_ACTION_LOC_MAIN", localize "STR_BTC_HAM_DOC_LOGISTIC_TEXT"]];
//Hideout
player createDiaryRecord [_mainCategory, [localize "str_a3_campaign_b_m03_marker02", localize "STR_BTC_HAM_DOC_HIDEOUT_TEXT"]];
//IED
player createDiaryRecord [_mainCategory, ["IED", localize "STR_BTC_HAM_DOC_IED_TEXT"]];
//Intel
player createDiaryRecord [_mainCategory, [localize "str_a3_mdl_category_intel", localize "STR_BTC_HAM_DOC_INTEL_TEXT"]];
//Reputation
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_DOC_REPUTATION_TITLE", localize "STR_BTC_HAM_DOC_REPUTATION_TEXT"]];
//Orders
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_ACTION_ORDERS_MAIN", localize "STR_BTC_HAM_DOC_ORDERS_TEXT"]];
//Traffic
player createDiaryRecord [_mainCategory, [localize "STR_BTC_HAM_DOC_TRAFFIC_TITLE", localize "STR_BTC_HAM_DOC_TRAFFIC_TEXT"]];
//Version
player createDiaryRecord [_mainCategory, [localize "STR_A3_FM_Welcome4",
format (["<img image='\A3\ui_f\data\igui\cfg\simpleTasks\types\download_ca.paa' width='20' height='20'/> Version %1.%2.%3 <img image='\A3\ui_f\data\igui\cfg\simpleTasks\types\download_ca.paa' width='20' height='20'/> <br/><br/> <img image='\a3\missions_f_orange\Data\Img\orange_overview_ca.paa' width='355' height='200'/>"] + btc_version)
]
];
@@ -1,63 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_arsenal_fnc_ammoUsage
Description:
Select weapons if:
- is a type of item
- and has a ammo usage allowed
Parameters:
_weapons - Array of weapons. [Array]
_itemType_ammo_usageAllowed - Weapons allowed filter: array of item type ("AssaultRifle", "MissileLauncher"...), allowed ammo usage ("128 + 512": ammo against vehicles and armored vehicles). [Array]
Returns:
Array of selected weapons
Examples:
(begin example)
_weapons_selected = [["launch_RPG7_F"], ["MissileLauncher", "256"]] call btc_arsenal_fnc_ammoUsage;
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_weapons", ["launch_RPG7_F"], [[]]],
["_itemType_ammo_usageAllowed", ["MissileLauncher", "256"], [[]]]
];
_itemType_ammo_usageAllowed params [["_itemType", "MissileLauncher", [""]], ["_ammo_usageAllowed", "", [""]]];
private _cfgWeapons = configFile >> "CfgWeapons";
private _cfgMagazines = configFile >> "CfgMagazines";
private _cfgAmmo = configFile >> "CfgAmmo";
_weapons select {
private _weapon = _x;
private _isAllowed = true;
if (_ammo_usageAllowed isNotEqualTo "") then {
private _magazines = getArray (_cfgWeapons >> _weapon >> "magazines");
private _aiAmmoUsage_magazines = _magazines apply {
private _ammo = getText (_cfgMagazines >> _x >> "ammo");
private _aiAmmoUsage = getText (_cfgAmmo >> _ammo >> "aiAmmoUsageFlags");
if (_aiAmmoUsage isEqualTo "") then {
_aiAmmoUsage = str getNumber (_cfgAmmo >> _ammo >> "aiAmmoUsageFlags");
};
_aiAmmoUsage;
};
if (btc_debug_log) then {
if ("" in _aiAmmoUsage_magazines) then {
[format ["Weapons: %1 AiAmmoUsage Magazines: %2", _weapon, _aiAmmoUsage_magazines], __FILE__, [false]] call btc_debug_fnc_message;
};
};
_isAllowed = _ammo_usageAllowed in _aiAmmoUsage_magazines;
};
(_itemType in (_weapon call BIS_fnc_itemType)) && {_isAllowed}
};
@@ -1,69 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_arsenal_fnc_data
Description:
Add virtual weapons to an object (e.g., ammo box) with restriction. Virtual items can be selected in the BIS/ACE3 Arsenal.
Parameters:
_box - Object to which items will be added. [Object]
_arsenalType - _arsenalType < 3 add BIS Arsenal, _arsenalType > 0 add ACE3 Arsenal. [Number]
_arsenalRestrict - 1 to add to Arsenal _arsenalData, other to restrict Arsenal with _arsenalData. [Number]
_arsenalData - Array of weapons, magazines and items. [Array]
Returns:
Examples:
(begin example)
[btc_gear_object, btc_p_arsenal_Type, btc_p_arsenal_Restrict, btc_custom_arsenal] call btc_arsenal_fnc_data;
(end)
Author:
1kuemmel1
---------------------------------------------------------------------------- */
params [
["_box", objNull, [objNull]],
["_arsenalType", 0, [0]],
["_arsenalRestrict", 0, [0]],
["_arsenalData", [], [[]]]
];
_arsenalData params [["_weapons", [], [[]]], ["_magazines", [], [[]]], ["_items", [], [[]]], ["_backpacks", [], [[]]]];
//BIS Arsenal
if (_arsenalType < 3) then {
if (_arsenalRestrict isEqualTo 1) then {
//add
[_box, _weapons, false, false] call BIS_fnc_addVirtualWeaponCargo;
[_box, _magazines, false, false] call BIS_fnc_addVirtualMagazineCargo;
[_box, _items, false, false] call BIS_fnc_addVirtualItemCargo;
[_box, _backpacks, false, false] call BIS_fnc_addVirtualBackpackCargo;
};
// DO NOT WORK FOR BIS ARSENAL
if (_arsenalRestrict in [2, 3]) then {
//remove
[_box, _weapons, false] call BIS_fnc_removeVirtualWeaponCargo;
[_box, _magazines, false] call BIS_fnc_removeVirtualMagazineCargo;
[_box, _items, false] call BIS_fnc_removeVirtualItemCargo;
[_box, _backpacks, false] call BIS_fnc_removeVirtualBackpackCargo;
};
};
//ACE Arsenal
if (_arsenalType > 0) then {
//add
if (_arsenalRestrict isEqualTo 1) then {
private _aceAdd = [];
{_aceAdd append _x;} forEach [_weapons, _magazines, _items, _backpacks];
[_box, _aceAdd] call ace_arsenal_fnc_addVirtualItems;
};
//remove
if (_arsenalRestrict in [2, 3]) then {
private _aceRemove = [];
{_aceRemove append _x;} forEach [_weapons, _magazines, _items, _backpacks];
[_box, _aceRemove] call ace_arsenal_fnc_removeVirtualItems;
};
};
@@ -1,61 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_arsenal_fnc_garage
Description:
Open virtual Arsenal garage on object position.
Parameters:
_current_garage - Object where the vehicle from garage will spawn. [Object]
Returns:
Examples:
(begin example)
[btc_create_object_point] spawn btc_arsenal_fnc_garage;
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_current_garage", objNull, [objNull]]
];
if ([_current_garage] call btc_fnc_checkArea) exitWith {};
disableSerialization;
uiNamespace setVariable ["current_garage", _current_garage];
private _fullVersion = missionNamespace getVariable ["BIS_fnc_arsenal_fullGarage", false];
if !(isNull (uiNamespace getVariable ["BIS_fnc_arsenal_cam", objNull])) exitwith {
"Garage Viewer is already running" call bis_fnc_logFormat;
};
private _veh = createVehicle ["Land_HelipadEmpty_F", getPos _current_garage, [], 0, "CAN_COLLIDE"];
_veh setPosASL getPosASL _current_garage;
uiNamespace setVariable ["garage_pad", _veh];
missionNamespace setVariable ["BIS_fnc_arsenal_fullGarage", [true, 0, false, [false]] call BIS_fnc_param];
with missionNamespace do {BIS_fnc_garage_center = _veh};
with uiNamespace do {
private _displayMission = [] call (uiNamespace getVariable "bis_fnc_displayMission");
if !(isNull findDisplay 312) then {_displayMission = findDisplay 312;};
_displayMission createDisplay "RscDisplayGarage";
uiNamespace setVariable ["running_garage", true];
waitUntil {sleep 0.25; isNull (uiNamespace getVariable ["BIS_fnc_arsenal_cam", objNull])};
private _logistic_point = uiNamespace getVariable "current_garage";
private _pad = uiNamespace getVariable "garage_pad";
deleteVehicle _pad;
private _veh_list = _logistic_point nearEntities 5;
{
private _type = typeOf _x;
private _pos = getPosASL _x;
private _dir = getDir _current_garage;
private _customization = [_x] call BIS_fnc_getVehicleCustomization;
_x call CBA_fnc_deleteEntity;
[_type, _pos, _dir, _customization] remoteExecCall ["btc_log_fnc_createVehicle", 2];
[_type] remoteExecCall ["btc_veh_fnc_init", -2];
} forEach _veh_list;
};
@@ -1,228 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_arsenal_fnc_loadout
Description:
Generate a loadout from an array of defined loadout depending on trait, medical level, color and hour of the day.
Parameters:
_type - Type of loadout: 0 - Rifleman, 1 - Medic, 2 - Repair, 3 - Engineer, 4 - Anti-Tank, 5 - Anti Air, 6 - Sniper, 7 - Machine gunner, 8 - CBRN, 9 - Drone hacker. [Number]
_color - Color of skin loadout: 0 - Desert, 1 - Tropic, 2 - Black, 3 - Forest. [Number]
_isDay - Select night (false) or day (true) loadout. [Boolean]
_medicalParameters - Select the correct medical stuff depends on ACE3 medical parameters. [Array]
_arsenal_loadout - Array of defined loadout. [Array]
Returns:
Loadout array.
Examples:
(begin example)
_rifleman_loadout = [0] call btc_arsenal_fnc_loadout;
(end)
(begin example)
[] spawn {
{
private _i = _x;
{
private _j = _x;
{
player setUnitLoadout ([_i, _j, _x] call btc_arsenal_fnc_loadout);
sleep 1;
} forEach [false,true];
} forEach [0,1,2,3];
} forEach [0,1,2,3,4,5,6,7,8,9];
};
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_type", 0, [0]],
["_color", -1, [0]],
["_isDay", 0, [0, false]],
["_medicalParameters", [ace_medical_treatment_advancedBandages, ace_medical_treatment_locationEpinephrine, ace_medical_treatment_locationSurgicalKit, ace_medical_treatment_locationPAK, ace_medical_fractures], [[]]],
["_arsenal_loadout", btc_arsenal_loadout, [[]]]
];
if (_color < 0) then {
_color = if (sunOrMoon isEqualTo 0) then {
2
} else {
switch (true) do {
case (worldName in ["Tanoa", "lingor3"]): {
1
};
case (worldName in ["chernarus", "Enoch", "sara", "vt7", "cup_chernarus_A3", "chernarus_summer"]): {
3
};
default {
0
};
};
}
};
(_arsenal_loadout apply {_x select _color}) params ["_uniform", "_uniformCBRN", "_uniformSniper", "_vest", "_helmet", "_hood", "_hoodCBRN", "_laserdesignator", "_night_vision", "_weapon", "_weapon_sniper", "_weapon_machineGunner", "_bipod", "_pistol", "_launcher_AT", "_launcher_AA", "_backpack", "_backpack_big", "_backpackCBRN", "_radio"];
if (_isDay isEqualType 0) then {
(date call BIS_fnc_sunriseSunsetTime) params ["_sunrise", "_sunset"];
_isDay = (_sunrise < dayTime) && (_sunset > dayTime + 1);
};
_medicalParameters params ["_advancedBandages", "_epi", "_surgicalKit", "_PAK", "_fractures"];
//Item inside Uniform
private _cfgPatches = configFile >> "CfgPatches";
private _cargo_uniform = [["acc_flashlight", 1], ["ACE_EarPlugs", 1], ["ACE_CableTie",5], ["optic_ACO_grn_smg", 1], ["ACE_MapTools", 1], ["ACE_RangeTable_82mm", 1]];
//Tweak uniform medical item depends on medical parameters
private _medical = [["ACE_fieldDressing", 3], ["ACE_tourniquet", 4], ["ACE_morphine", 3]];
_medical pushBack (if (_advancedBandages > 0) then {
["ACE_packingBandage", 4]
} else {
["ACE_fieldDressing", 4]
});
_medical pushBack (if (_epi < 4) then {
["ACE_epinephrine", 3]
} else {
["ACE_morphine", 3]
});
if (_fractures > 0) then {
_medical pushBack ["ACE_splint", 1];
};
_cargo_uniform append _medical;
private _uniform = switch (_type) do {
case 6: {
_uniformSniper
};
case 8: {
_uniformCBRN
};
default {
_uniform
};
};
private _hood = switch (_type) do {
case 8: {
_hoodCBRN
};
default {
_hood
};
};
//Choose appropriate weapon/optics depends on _type
private _array = switch (_type) do {
case 6: {
[_weapon_sniper, ["ACE_optic_Hamr_2D", "ACE_optic_LRPS_2D"]];
};
case 7: {
[_weapon_machineGunner];
};
default {
[_weapon];
};
};
_array params ["_weapon", ["_optics", ["ACE_optic_Hamr_2D", "ACE_optic_Arco_2D"], [[]]]];
private _bipod_item = ["", _bipod] select (_type in [6, 7]);
//Generate magazines and boulets count
private _cfgWeapons = configFile >> "CfgWeapons";
private _cfgMagazines = configFile >> "CfgMagazines";
private _launcher = ["", _launcher_AT] select (_type isEqualTo 4);
private _launcher = [_launcher, _launcher_AA] select (_type isEqualTo 5);
([_weapon, _pistol, _launcher] apply {getArray (_cfgWeapons >> _x >> "magazines")}) params ["_weaponMagazines", "_pistolMagazines", "_launcherMagazines"];
([_weaponMagazines, _pistolMagazines, _launcherMagazines] apply {_x select 0}) params ["_weaponMagazine", "_pistolMagazine", ["_launcherMagazine", ""]];
([_weaponMagazine, _pistolMagazine, _launcherMagazine] apply {getNumber (_cfgMagazines >> _x >> "count")}) params ["_weaponCount", "_pistolCount", "_launcherCount"];
//Backpack content
//Tweak backpack medical item depends on medical parameters
private _backpackMedical = [["ACE_fieldDressing", 10], ["ACE_morphine", 12], ["ACE_bloodIV", 2], ["ACE_bloodIV_250", 2], ["ACE_bloodIV_500", 1]];
_backpackMedical append (if (_advancedBandages > 0) then {
[["ACE_packingBandage", 15], ["ACE_elasticBandage", 10], ["ACE_quikclot", 10]]
} else {
[["ACE_fieldDressing", 10]]
});
_backpackMedical pushBack (if (_epi < 4) then {
["ACE_epinephrine", 12]
} else {
["ACE_morphine", 2]
});
if (_surgicalKit < 4) then {
_backpackMedical pushBack ["ACE_surgicalKit", 1];
};
if (_PAK < 4) then {
_backpackMedical pushBack ["ACE_personalAidKit", 1];
};
if (_fractures > 0) then {
_backpackMedical pushBack ["ACE_splint", 3];
};
private _cargos = [
[],
[_backpack, [["SmokeShellGreen", 3, 1], ["SmokeShellPurple", 1, 1]] + _backpackMedical],
[_backpack, [["ToolKit", 1], ["ACE_EntrenchingTool", 1], ["ACE_wirecutter", 1]]],
[_backpack, [["ACE_DefusalKit", 1], ["ACE_Clacker", 2], ["ACE_SpraypaintRed", 1], ["DemoCharge_Remote_Mag", 2, 1], [["ACE_VMM3", "", "", "", [], [], ""], 1], ["ACE_EntrenchingTool", 1]]],
[_backpack, [[_launcherMagazines param [1, _launcherMagazine], 1, _launcherCount], [_launcherMagazine, 1, _launcherCount]]],
[_backpack_big, [[_launcherMagazine, 2, _launcherCount]]],
[_backpack, [["ACE_Sandbag_empty", 1], ["ACE_Kestrel4500", 1], ["ACE_ATragMX", 1], ["ACE_RangeCard", 1], ["ACE_EntrenchingTool", 1]]],
[],
[_backpackCBRN, [["G_Respirator_white_F", 5]]],
[_backpack, [["muzzle_antenna_02_f", 1], ["muzzle_antenna_01_f", 1], [["hgun_esd_01_F", "", "", "", [], [], ""], 1]]]
];
private _binocular_array = [_laserdesignator, "", "", "", ["Laserbatteries", 1], [], ""];
private _launcher_array = [[_launcher, "", "", "", [_launcherMagazine, _launcherCount], [], ""], []] select (_launcher isEqualTo "");
private _radio_item = [
["ItemRadio", ""] select (isClass(_cfgPatches >> "acre_main")),
_radio
] select (isClass (_cfgPatches >> "task_force_radio"));
private _loadout = if (_isDay) then {
[
[_weapon, "", "", _optics select _isDay, [_weaponMagazine, _weaponCount], [], _bipod_item],
_launcher_array,
[_pistol, "", "", "", [_pistolMagazine, _pistolCount], [], ""],
[_uniform, _cargo_uniform],
[_vest, [
["SmokeShellGreen", 2, 1],
[_weaponMagazine, 7, _weaponCount],
["SmokeShellPurple", 2, 1],
["SmokeShellYellow", 1, 1],
[_pistolMagazine, 1, _pistolCount],
["ACE_M84", 1, 1],
["HandGrenade", 3, 1]
]],
_cargos select _type, _helmet, _hood, _binocular_array,
["ItemMap", "B_UavTerminal", _radio_item, "ItemCompass", "ChemicalDetector_01_watch_F", ""]
]
} else {
[
[_weapon, "muzzle_snds_65_TI_blk_F", "acc_pointer_IR", _optics select _isDay, [_weaponMagazines param [1, _weaponMagazine], _weaponCount], [], _bipod_item],
_launcher_array,
[_pistol, "", "", "", [_pistolMagazine, _pistolCount], [], ""],
[_uniform, _cargo_uniform],
[_vest, [
["SmokeShellGreen", 1, 1],
["B_IR_Grenade", 2, 1],
[_weaponMagazines param [1, _weaponMagazine], 7, _weaponCount],
["Chemlight_green", 1, 1],
["Chemlight_blue", 1, 1],
["ACE_HandFlare_Green", 1, 1],
["HandGrenade", 3, 1],
["ACE_M84", 1, 1]
]],
_cargos select _type, _helmet, _hood, _binocular_array,
["ItemMap", "B_UavTerminal", _radio_item, "ItemCompass", "ChemicalDetector_01_watch_F", _night_vision]
]
};
if (isClass(_cfgPatches >> "acre_main")) then {
(_loadout select 4 select 1) pushBack [_radio, 1];
};
_loadout
@@ -1,59 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_arsenal_fnc_trait
Description:
Get trait from an object (e.g. Player) and return the corresponding trait and weapons allowed filter (https://community.bistudio.com/wiki/CfgAmmo_Config_Reference#aiAmmoUsageFlags).
Parameters:
_player - Object use to determine the trait and the weapons allowed filter accordingly to the trait. [Object]
Returns:
_type_ammoUsageAllowed = trait and array of weapons allowed filter: array of item type ("AssaultRifle", "MissileLauncher"...), allowed ammo usage ("128 + 512": ammo against vehicles and armored vehicles).
Examples:
(begin example)
_type_ammoUsageAllowed = [player] call btc_arsenal_fnc_trait;
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_player", objNull, [objNull]]
];
switch (true) do {
case (_player getUnitTrait "medic"): {
[1, [["AssaultRifle"]]]
};
case (_player getVariable ["ace_isEngineer", 0] in [1, 2]): {
[2, [["AssaultRifle"]]]
};
case (_player getUnitTrait "explosiveSpecialist"): {
[3, [["AssaultRifle"]]]
};
case ([typeOf _player, ["MissileLauncher", "128 + 512"]] call btc_mil_fnc_ammoUsage): {
[4, [["AssaultRifle"], ["RocketLauncher"], ["MissileLauncher", "128 + 512"]]]
};
case ([typeOf _player, ["MissileLauncher", "256"]] call btc_mil_fnc_ammoUsage): {
[5, [["AssaultRifle"], ["MissileLauncher", "256"]]]
};
case ([typeOf _player, ["SniperRifle"]] call btc_mil_fnc_ammoUsage): {
[6, [["SniperRifle"]]]
};
case ([typeOf _player, ["MachineGun"]] call btc_mil_fnc_ammoUsage): {
[7, [["MachineGun"]]]
};
case ("cbrn" in toLower uniform _player): {
[8, [["AssaultRifle"]]]
};
case (_player getUnitTrait "UAVHacker"): {
[9, [["AssaultRifle"]]]
};
default {
[0, [["AssaultRifle"], ["RocketLauncher"]]]
};
};
@@ -1,57 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_arsenal_fnc_weaponsFilter
Description:
Filter weapons allowed with the weapons allowed filter: array of item type ("AssaultRifle", "MissileLauncher"...), allowed ammo usage ("128 + 512": ammo against vehicles and armored vehicles).
Parameters:
_itemType_ammo_usageAllowed - Array of weapons allowed filter. [Array]
_custom_arsenal - Array of weapons, magazines and items. [Array]
_arsenalRestrict - 1 to add allowed weapons to Arsenal _custom_arsenal, other to restrict. [Number]
_type_units - Array of enemies type. Use to remove enemies weapons from the allowed weapons. [Array]
Returns:
_allowedWeapons - Array of allowed weapons [Array]
Examples:
(begin example)
_allowedWeapons = [[["AssaultRifle", ""], ["RocketLauncher", ""]]] call btc_arsenal_fnc_weaponsFilter;
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_itemType_ammo_usageAllowed", [["AssaultRifle", ""], ["RocketLauncher", ""]], [[]]],
["_custom_arsenal", btc_custom_arsenal, [[]]],
["_arsenalRestrict", btc_p_arsenal_Restrict, [0]],
["_type_units", btc_type_units, [[]]]
];
private _weapons = ("true" configClasses (configFile >> "CfgWeapons") select {
getNumber (_x >> "scope") isEqualTo 2 &&
{getNumber (_x >> "type") in [1, 4]}
}) apply {configName _x};
private _allowedWeapons = [];
{
_allowedWeapons append ([_weapons, _x] call btc_arsenal_fnc_ammoUsage);
} forEach _itemType_ammo_usageAllowed;
private _cfgVehicles = configFile >> "CfgVehicles";
private _enemyWeapons = [];
{
_enemyWeapons append getArray (_cfgVehicles >> _x >> "weapons");
} forEach _type_units;
_allowedWeapons = _allowedWeapons - _enemyWeapons;
if (_arsenalRestrict isEqualTo 1) then {
(_custom_arsenal select 0) append _allowedWeapons;
} else {
(_custom_arsenal select 0) append (_weapons - _allowedWeapons);
};
_allowedWeapons
@@ -1,37 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_body_fnc_bagRecover
Description:
Add respawn tickets when a body bag is provided.
Parameters:
_logistic - Logistic pad. [Object]
Returns:
Examples:
(begin example)
{_x addCuratorEditableObjects [btc_body_deadPlayers, false];} forEach allCurators;
[btc_create_object_point] call btc_body_fnc_bagRecover;
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_logistic", objNull, [objNull]]
];
private _array = nearestObjects [_logistic, ["ACE_bodyBagObject", "CAManBase"], 10];
_array = _array select {
_x isKindOf "CAManBase" ||
_x isKindOf "ACE_bodyBagObject"
};
if (_array isEqualTo []) exitWith {
localize "STR_BTC_HAM_O_BODYBAG_NO" call CBA_fnc_notify;
};
[_array select 0, player] remoteExecCall ["btc_body_fnc_bagRecover_s", 2];
@@ -1,91 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_body_fnc_bagRecover_s
Description:
Add respawn tickets when a body bag or an alive enemy is provided.
Parameters:
_bodyBag - Body bag or alive enemy. [Object]
_player - Player interacting. [Object]
Returns:
Examples:
(begin example)
[cursorObject, player] remoteExecCall ["btc_body_fnc_bagRecover_s", 2];
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_bodyBag", objNull, [objNull]],
["_player", objNull, [objNull]]
];
private _ticket = 0;
private _UID = _bodyBag getVariable ["btc_UID", ""];
private _players = [];
if (_UID isEqualTo "") then {
if (
alive _bodyBag &&
_bodyBag isKindOf "CAManBase" &&
side group _bodyBag isEqualTo btc_enemy_side
) then {
_ticket = btc_body_prisonerTicket;
switch (btc_p_respawn_ticketsFromPrisoners) do {
case 1 : {
_players = allPlayers select {side group _x isEqualTo btc_player_side};
};
case 2 : {
_players = [_player];
};
case 3 : {
_players = allPlayers select {side group _x isEqualTo btc_player_side};
private _index = _players findIf {[_x] call BIS_fnc_respawnTickets isEqualTo 0};
if (_index isEqualTo -1) then {
_players = [];
} else {
_players = [_players select _index];
};
};
case 4 : {
_players = allPlayers select {side group _x isEqualTo btc_player_side};
private _tickets = _players apply {[[_x] call BIS_fnc_respawnTickets, _x]};
_tickets sort true;
_players = [_tickets select 0 select 1];
};
default {};
};
};
} else {
_ticket = btc_body_bagTicketPlayer;
};
if (_ticket isEqualTo 0) exitWith {
[23] remoteExecCall ["btc_fnc_show_hint", remoteExecutedOwner];
};
if (_UID isEqualTo "" && _players isEqualTo []) exitWith {
[25] remoteExecCall ["btc_fnc_show_hint", remoteExecutedOwner];
};
[22] remoteExecCall ["btc_fnc_show_hint", remoteExecutedOwner];
if (btc_p_respawn_ticketsShare) then {
[btc_player_side, _ticket, btc_player_side] call btc_respawn_fnc_addTicket;
} else {
if (_UID isEqualTo "") then {
{
[_x, _ticket, getPlayerUID _x] call btc_respawn_fnc_addTicket;
} forEach _players;
} else {
private _player = _UID call BIS_fnc_getUnitByUID;
[_player, _ticket, _UID] call btc_respawn_fnc_addTicket;
};
};
deleteMarker (_bodyBag getVariable ["btc_body_deadMarker", ""]);
_bodyBag call CBA_fnc_deleteEntity;
@@ -1,60 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_body_fnc_create
Description:
Create dead bodies from a serialized array of bodies.
Parameters:
_serializedBodies - Serialized bodies. [Array]
Returns:
_bodies - Bodies. [Array]
Examples:
(begin example)
[] call btc_body_fnc_create;
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_serializedBodies", [], [[]]]
];
private _group = createGroup btc_player_side;
_bodies = _serializedBodies apply {
_x params ["_type", "_pos", "_dir", "_loadout", "_dogtag", "_isContaminated",
["_flagTexture", "", [""]]
];
private _body = _group createUnit [_type, ASLToAGL _pos, [], 0, "CAN_COLLIDE"];
_body setUnitLoadout _loadout;
[_body, _dogtag] call btc_body_fnc_dogtagSet;
if (_isContaminated) then {
if ((btc_chem_contaminated pushBackUnique _body) > -1) then {
publicVariable "btc_chem_contaminated";
};
};
_body setDamage 1;
_body setVariable ["btc_dont_delete", true];
_body forceFlagTexture _flagTexture;
[{
params ["_body", "_dir", "_pos"];
_body setDir _dir;
_body setPosASL _pos;
}, [_body, _dir, _pos], 3] call CBA_fnc_waitAndExecute;
if (btc_p_body_timeBeforeShowMarker >= 0) then {
_body call btc_body_fnc_createMarker;
};
_body
};
deleteGroup _group;
_bodies
@@ -1,36 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_body_fnc_createMarker
Description:
Create a KIA marker on dead body.
Parameters:
_deadBody - Dead body. [Object]
Returns:
Examples:
(begin example)
[cursorObject] call btc_body_fnc_createMarker;
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_unit", objNull, [objNull]]
];
if (isNull _unit) exitwith {};
private _marker = createMarker [
format ["btc_body_dead_%1", {"btc_body_dead" in _x} count allMapMarkers],
_unit
];
_marker setMarkerType "KIA";
_marker setMarkerSize [0.5, 0.5];
_marker setMarkerAlpha 0.5;
_unit setVariable ["btc_body_deadMarker", _marker];
@@ -1,31 +0,0 @@
/* ----------------------------------------------------------------------------
Function: btc_body_fnc_dogtagGet
Description:
Get ACE dogtag data and player UID.
Parameters:
_deadBody - Dead body or body bag. [Object]
Returns:
Examples:
(begin example)
[cursorObject] call btc_body_fnc_dogtagGet;
(end)
Author:
Vdauphin
---------------------------------------------------------------------------- */
params [
["_unit", objNull, [objNull]]
];
[
_unit call ace_dogtags_fnc_getDogtagData,
!isNull (_unit getVariable ["ace_dogtags_dogtagTaken", objNull]),
_unit getVariable ["btc_UID", ""]
]

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