mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
8227 lines
313 KiB
Lua
8227 lines
313 KiB
Lua
env.info("ctldstart")
|
|
--[[
|
|
Combat Troop and Logistics Drop
|
|
|
|
Allows Huey, Mi-8 and C130 to transport troops internally and Helicopters to transport Logistic / Vehicle units to the field via sling-loads
|
|
without requiring external mods.
|
|
|
|
Supports all of the original CTTS functionality such as AI auto troop load and unload as well as group spawning and preloading of troops into units.
|
|
|
|
Supports deployment of Auto Lasing JTAC to the field
|
|
|
|
See https://github.com/ciribob/DCS-CTLD for a user manual and the latest version
|
|
|
|
Contributors:
|
|
- Steggles - https://github.com/Bob7heBuilder
|
|
- mvee - https://github.com/mvee
|
|
- jmontleon - https://github.com/jmontleon
|
|
- emilianomolina - https://github.com/emilianomolina
|
|
- davidp57 - https://github.com/veaf
|
|
- Queton1-1 - https://github.com/Queton1-1
|
|
- Proxy404 - https://github.com/Proxy404
|
|
- atcz - https://github.com/atcz
|
|
- marcos2221- https://github.com/marcos2221
|
|
- FullGas1 - https://github.com/FullGas1 (i18n concept, FR and SP translations)
|
|
|
|
Add [issues](https://github.com/ciribob/DCS-CTLD/issues) to the GitHub repository if you want to report a bug or suggest a new feature.
|
|
|
|
Contact Zip [on Discord](https://discordapp.com/users/421317390807203850) or [on Github](https://github.com/davidp57) if you need help or want to have a friendly chat.
|
|
|
|
Send beers (or kind messages) to Ciribob [on Discord](https://discordapp.com/users/204712384747536384), he's the reason we have CTLD ^^
|
|
]]
|
|
|
|
if not ctld then -- should be defined first by CTLD-i18n.lua, but just in case it's an old mission, let's keep it here
|
|
--trigger.action.outText("\n\n** HEY MISSION-DESIGNER! **\n\nCTLD-i18n has not been loaded!\n\nMake sure CTLD-i18n is loaded\n*before* running this script!\n\nIt contains all the translations!\n", 10)
|
|
ctld = {} -- DONT REMOVE!
|
|
end
|
|
|
|
--- Identifier. All output in DCS.log will start with this.
|
|
ctld.Id = "CTLD - "
|
|
|
|
--- Version.
|
|
ctld.Version = "1.4.0"
|
|
|
|
-- To add debugging messages to dcs.log, change the following log levels to `true`; `Debug` is less detailed than `Trace`
|
|
ctld.Debug = false
|
|
ctld.Trace = false
|
|
|
|
ctld.dontInitialize = false -- if true, ctld.initialize() will not run; instead, you'll have to run it from your own code - it's useful when you want to override some functions/parameters before the initialization takes place
|
|
|
|
-- ***************************************************************
|
|
-- *************** Internationalization (I18N) *******************
|
|
-- ***************************************************************
|
|
|
|
-- If you want to change the language replace "en" with the language you want to use
|
|
|
|
--======== ENGLISH - the reference ===========================================================================
|
|
ctld.i18n_lang = "en"
|
|
--======== FRENCH - FRANCAIS =================================================================================
|
|
--ctld.i18n_lang = "fr"
|
|
--====== SPANISH : ESPAÑOL ====================================================================================
|
|
--ctld.i18n_lang = "es"
|
|
--====== Korean : 한국어 ====================================================================================
|
|
--ctld.i18n_lang = "ko"
|
|
|
|
if not ctld.i18n then -- should be defined first by CTLD-i18n.lua, but just in case it's an old mission, let's keep it here
|
|
ctld.i18n = {} -- DONT REMOVE!
|
|
end
|
|
|
|
-- This is the default language
|
|
-- If a string is not found in the current language then it will default to this language
|
|
-- Note that no translation is provided for this language (obviously) but that we'll maintain this table to help the translators.
|
|
ctld.i18n["en"] = {}
|
|
ctld.i18n["en"].translation_version = "1.4" -- make sure that all the translations are compatible with this version of the english language texts
|
|
local lang="en";env.info(string.format("I - CTLD.i18n_translate: Loading %s language version %s", lang, tostring(ctld.i18n[lang].translation_version)))
|
|
|
|
--- groups names
|
|
ctld.i18n["en"]["Standard Group"] = ""
|
|
ctld.i18n["en"]["Anti Air"] = ""
|
|
ctld.i18n["en"]["Anti Tank"] = ""
|
|
ctld.i18n["en"]["Mortar Squad"] = ""
|
|
ctld.i18n["en"]["JTAC Group"] = ""
|
|
ctld.i18n["en"]["Single JTAC"] = ""
|
|
ctld.i18n["en"]["2x - Standard Groups"] = ""
|
|
ctld.i18n["en"]["2x - Anti Air"] = ""
|
|
ctld.i18n["en"]["2x - Anti Tank"] = ""
|
|
ctld.i18n["en"]["2x - Standard Groups + 2x Mortar"] = ""
|
|
ctld.i18n["en"]["3x - Standard Groups"] = ""
|
|
ctld.i18n["en"]["3x - Anti Air"] = ""
|
|
ctld.i18n["en"]["3x - Anti Tank"] = ""
|
|
ctld.i18n["en"]["3x - Mortar Squad"] = ""
|
|
ctld.i18n["en"]["5x - Mortar Squad"] = ""
|
|
ctld.i18n["en"]["Mortar Squad Red"] = ""
|
|
|
|
--- crates names
|
|
ctld.i18n["en"]["Humvee - MG"] = ""
|
|
ctld.i18n["en"]["Humvee - TOW"] = ""
|
|
ctld.i18n["en"]["Light Tank - MRAP"] = ""
|
|
ctld.i18n["en"]["Med Tank - LAV-25"] = ""
|
|
ctld.i18n["en"]["Heavy Tank - Abrams"] = ""
|
|
ctld.i18n["en"]["BTR-D"] = ""
|
|
ctld.i18n["en"]["BRDM-2"] = ""
|
|
ctld.i18n["en"]["Hummer - JTAC"] = ""
|
|
ctld.i18n["en"]["M-818 Ammo Truck"] = ""
|
|
ctld.i18n["en"]["M-978 Tanker"] = ""
|
|
ctld.i18n["en"]["SKP-11 - JTAC"] = ""
|
|
ctld.i18n["en"]["Ural-375 Ammo Truck"] = ""
|
|
ctld.i18n["en"]["KAMAZ Ammo Truck"] = ""
|
|
ctld.i18n["en"]["EWR Radar"] = ""
|
|
ctld.i18n["en"]["FOB Crate - Small"] = ""
|
|
ctld.i18n["en"]["MQ-9 Repear - JTAC"] = ""
|
|
ctld.i18n["en"]["RQ-1A Predator - JTAC"] = ""
|
|
ctld.i18n["en"]["MLRS"] = ""
|
|
ctld.i18n["en"]["SpGH DANA"] = ""
|
|
ctld.i18n["en"]["T155 Firtina"] = ""
|
|
ctld.i18n["en"]["Howitzer"] = ""
|
|
ctld.i18n["en"]["SPH 2S19 Msta"] = ""
|
|
ctld.i18n["en"]["M1097 Avenger"] = ""
|
|
ctld.i18n["en"]["M48 Chaparral"] = ""
|
|
ctld.i18n["en"]["Roland ADS"] = ""
|
|
ctld.i18n["en"]["Gepard AAA"] = ""
|
|
ctld.i18n["en"]["LPWS C-RAM"] = ""
|
|
ctld.i18n["en"]["9K33 Osa"] = ""
|
|
ctld.i18n["en"]["9P31 Strela-1"] = ""
|
|
ctld.i18n["en"]["9K35M Strela-10"] = ""
|
|
ctld.i18n["en"]["9K331 Tor"] = ""
|
|
ctld.i18n["en"]["2K22 Tunguska"] = ""
|
|
ctld.i18n["en"]["HAWK Launcher"] = ""
|
|
ctld.i18n["en"]["HAWK Search Radar"] = ""
|
|
ctld.i18n["en"]["HAWK Track Radar"] = ""
|
|
ctld.i18n["en"]["HAWK PCP"] = ""
|
|
ctld.i18n["en"]["HAWK CWAR"] = ""
|
|
ctld.i18n["en"]["HAWK Repair"] = ""
|
|
ctld.i18n["en"]["NASAMS Launcher 120C"] = ""
|
|
ctld.i18n["en"]["NASAMS Search/Track Radar"] = ""
|
|
ctld.i18n["en"]["NASAMS Command Post"] = ""
|
|
ctld.i18n["en"]["NASAMS Repair"] = ""
|
|
ctld.i18n["en"]["KUB Launcher"] = ""
|
|
ctld.i18n["en"]["KUB Radar"] = ""
|
|
ctld.i18n["en"]["KUB Repair"] = ""
|
|
ctld.i18n["en"]["BUK Launcher"] = ""
|
|
ctld.i18n["en"]["BUK Search Radar"] = ""
|
|
ctld.i18n["en"]["BUK CC Radar"] = ""
|
|
ctld.i18n["en"]["BUK Repair"] = ""
|
|
ctld.i18n["en"]["Patriot Launcher"] = ""
|
|
ctld.i18n["en"]["Patriot Radar"] = ""
|
|
ctld.i18n["en"]["Patriot ECS"] = ""
|
|
ctld.i18n["en"]["Patriot ICC"] = ""
|
|
ctld.i18n["en"]["Patriot EPP"] = ""
|
|
ctld.i18n["en"]["Patriot AMG (optional)"] = ""
|
|
ctld.i18n["en"]["Patriot Repair"] = ""
|
|
ctld.i18n["en"]["S-300 Grumble TEL C"] = ""
|
|
ctld.i18n["en"]["S-300 Grumble Flap Lid-A TR"] = ""
|
|
ctld.i18n["en"]["S-300 Grumble Clam Shell SR"] = ""
|
|
ctld.i18n["en"]["S-300 Grumble Big Bird SR"] = ""
|
|
ctld.i18n["en"]["S-300 Grumble C2"] = ""
|
|
ctld.i18n["en"]["S-300 Repair"] = ""
|
|
ctld.i18n["en"]["Humvee - TOW - All crates"] = ""
|
|
ctld.i18n["en"]["Light Tank - MRAP - All crates"] = ""
|
|
ctld.i18n["en"]["Med Tank - LAV-25 - All crates"] = ""
|
|
ctld.i18n["en"]["Heavy Tank - Abrams - All crates"] = ""
|
|
ctld.i18n["en"]["Hummer - JTAC - All crates"] = ""
|
|
ctld.i18n["en"]["M-818 Ammo Truck - All crates"] = ""
|
|
ctld.i18n["en"]["M-978 Tanker - All crates"] = ""
|
|
ctld.i18n["en"]["Ural-375 Ammo Truck - All crates"] = ""
|
|
ctld.i18n["en"]["EWR Radar - All crates"] = ""
|
|
ctld.i18n["en"]["MLRS - All crates"] = ""
|
|
ctld.i18n["en"]["SpGH DANA - All crates"] = ""
|
|
ctld.i18n["en"]["T155 Firtina - All crates"] = ""
|
|
ctld.i18n["en"]["Howitzer - All crates"] = ""
|
|
ctld.i18n["en"]["SPH 2S19 Msta - All crates"] = ""
|
|
ctld.i18n["en"]["M1097 Avenger - All crates"] = ""
|
|
ctld.i18n["en"]["M48 Chaparral - All crates"] = ""
|
|
ctld.i18n["en"]["Roland ADS - All crates"] = ""
|
|
ctld.i18n["en"]["Gepard AAA - All crates"] = ""
|
|
ctld.i18n["en"]["LPWS C-RAM - All crates"] = ""
|
|
ctld.i18n["en"]["9K33 Osa - All crates"] = ""
|
|
ctld.i18n["en"]["9P31 Strela-1 - All crates"] = ""
|
|
ctld.i18n["en"]["9K35M Strela-10 - All crates"] = ""
|
|
ctld.i18n["en"]["9K331 Tor - All crates"] = ""
|
|
ctld.i18n["en"]["2K22 Tunguska - All crates"] = ""
|
|
ctld.i18n["en"]["HAWK - All crates"] = ""
|
|
ctld.i18n["en"]["NASAMS - All crates"] = ""
|
|
ctld.i18n["en"]["KUB - All crates"] = ""
|
|
ctld.i18n["en"]["BUK - All crates"] = ""
|
|
ctld.i18n["en"]["Patriot - All crates"] = ""
|
|
ctld.i18n["en"]["Patriot - All crates"] = ""
|
|
|
|
--- mission design error messages
|
|
ctld.i18n["en"]["CTLD.lua ERROR: Can't find trigger called %1"] = ""
|
|
ctld.i18n["en"]["CTLD.lua ERROR: Can't find zone called %1"] = ""
|
|
ctld.i18n["en"]["CTLD.lua ERROR: Can't find zone or ship called %1"] = ""
|
|
ctld.i18n["en"]["CTLD.lua ERROR: Can't find crate with weight %1"] = ""
|
|
|
|
--- runtime messages
|
|
ctld.i18n["en"]["You are not close enough to friendly logistics to get a crate!"] = ""
|
|
ctld.i18n["en"]["No more JTAC Crates Left!"] = ""
|
|
ctld.i18n["en"]["Sorry you must wait %1 seconds before you can get another crate"] = ""
|
|
ctld.i18n["en"]["A %1 crate weighing %2 kg has been brought out and is at your %3 o'clock "] = ""
|
|
ctld.i18n["en"]["%1 fast-ropped troops from %2 into combat"] = ""
|
|
ctld.i18n["en"]["%1 dropped troops from %2 into combat"] = ""
|
|
ctld.i18n["en"]["%1 fast-ropped troops from %2 into %3"] = ""
|
|
ctld.i18n["en"]["%1 dropped troops from %2 into %3"] = ""
|
|
ctld.i18n["en"]["Too high or too fast to drop troops into combat! Hover below %1 feet or land."] = ""
|
|
ctld.i18n["en"]["%1 dropped vehicles from %2 into combat"] = ""
|
|
ctld.i18n["en"]["%1 loaded troops into %2"] = ""
|
|
ctld.i18n["en"]["%1 loaded %2 vehicles into %3"] = ""
|
|
ctld.i18n["en"]["%1 delivered a FOB Crate"] = ""
|
|
ctld.i18n["en"]["Delivered FOB Crate 60m at 6'oclock to you"] = ""
|
|
ctld.i18n["en"]["FOB Crate dropped back to base"] = ""
|
|
ctld.i18n["en"]["FOB Crate Loaded"] = ""
|
|
ctld.i18n["en"]["%1 loaded a FOB Crate ready for delivery!"] = ""
|
|
ctld.i18n["en"]["There are no friendly logistic units nearby to load a FOB crate from!"] = ""
|
|
ctld.i18n["en"]["You already have troops onboard."] = ""
|
|
ctld.i18n["en"]["You already have vehicles onboard."] = ""
|
|
ctld.i18n["en"]["This area has no more reinforcements available!"] = ""
|
|
ctld.i18n["en"]["You are not in a pickup zone and no one is nearby to extract"] = ""
|
|
ctld.i18n["en"]["You are not in a pickup zone"] = ""
|
|
ctld.i18n["en"]["No one to unload"] = ""
|
|
ctld.i18n["en"]["Dropped troops back to base"] = ""
|
|
ctld.i18n["en"]["Dropped vehicles back to base"] = ""
|
|
ctld.i18n["en"]["You already have troops onboard."] = ""
|
|
ctld.i18n["en"]["You already have vehicles onboard."] = ""
|
|
ctld.i18n["en"]["Sorry - The group of %1 is too large to fit. \n\nLimit is %2 for %3"] = ""
|
|
ctld.i18n["en"]["%1 extracted troops in %2 from combat"] = ""
|
|
ctld.i18n["en"]["No extractable troops nearby!"] = ""
|
|
ctld.i18n["en"]["%1 extracted vehicles in %2 from combat"] = ""
|
|
ctld.i18n["en"]["No extractable vehicles nearby!"] = ""
|
|
ctld.i18n["en"]["%1 troops onboard (%2 kg)\n"] = ""
|
|
ctld.i18n["en"]["%1 vehicles onboard (%2)\n"] = ""
|
|
ctld.i18n["en"]["1 FOB Crate oboard (%1 kg)\n"] = ""
|
|
ctld.i18n["en"]["%1 crate onboard (%2 kg)\n"] = ""
|
|
ctld.i18n["en"]["Total weight of cargo : %1 kg\n"] = ""
|
|
ctld.i18n["en"]["No cargo."] = ""
|
|
ctld.i18n["en"]["Hovering above %1 crate. \n\nHold hover for %2 seconds! \n\nIf the countdown stops you're too far away!"] = ""
|
|
ctld.i18n["en"]["Loaded %1 crate!"] = ""
|
|
ctld.i18n["en"]["Too low to hook %1 crate.\n\nHold hover for %2 seconds"] = ""
|
|
ctld.i18n["en"]["Too high to hook %1 crate.\n\nHold hover for %2 seconds"] = ""
|
|
ctld.i18n["en"]["You must land before you can load a crate!"] = ""
|
|
ctld.i18n["en"]["No Crates within 50m to load!"] = ""
|
|
ctld.i18n["en"]["Maximum number of crates are on board!"] = ""
|
|
ctld.i18n["en"]["%1\n%2 crate - kg %3 - %4 m - %5 o'clock"] = ""
|
|
ctld.i18n["en"]["FOB Crate - %1 m - %2 o'clock\n"] = ""
|
|
ctld.i18n["en"]["No Nearby Crates"] = ""
|
|
ctld.i18n["en"]["Nearby Crates:\n%1"] = ""
|
|
ctld.i18n["en"]["Nearby FOB Crates (Not Slingloadable):\n%1"] = ""
|
|
ctld.i18n["en"]["FOB Positions:"] = ""
|
|
ctld.i18n["en"]["%1\nFOB @ %2"] = ""
|
|
ctld.i18n["en"]["Sorry, there are no active FOBs!"] = ""
|
|
ctld.i18n["en"]["You can't unpack that here! Take it to where it's needed!"] = ""
|
|
ctld.i18n["en"]["Sorry you must move this crate before you unpack it!"] = ""
|
|
ctld.i18n["en"]["%1 successfully deployed %2 to the field"] = ""
|
|
ctld.i18n["en"]["No friendly crates close enough to unpack, or crate too close to aircraft."] = ""
|
|
ctld.i18n["en"]["Finished building FOB! Crates and Troops can now be picked up."] = ""
|
|
ctld.i18n["en"]["Finished building FOB! Crates can now be picked up."] = ""
|
|
ctld.i18n["en"]["%1 started building FOB using %2 FOB crates, it will be finished in %3 seconds.\nPosition marked with smoke."] = ""
|
|
ctld.i18n["en"]["Cannot build FOB!\n\nIt requires %1 Large FOB crates ( 3 small FOB crates equal 1 large FOB Crate) and there are the equivalent of %2 large FOB crates nearby\n\nOr the crates are not within 750m of each other"] = ""
|
|
ctld.i18n["en"]["You are not currently transporting any crates. \n\nTo Pickup a crate, hover for %1 seconds above the crate or land and use F10 Crate Commands."] = ""
|
|
ctld.i18n["en"]["You are not currently transporting any crates. \n\nTo Pickup a crate, hover for %1 seconds above the crate."] = ""
|
|
ctld.i18n["en"]["You are not currently transporting any crates. \n\nTo Pickup a crate, land and use F10 Crate Commands to load one."] = ""
|
|
ctld.i18n["en"]["%1 crate has been safely unhooked and is at your %2 o'clock"] = ""
|
|
ctld.i18n["en"]["%1 crate has been safely dropped below you"] = ""
|
|
ctld.i18n["en"]["You were too high! The crate has been destroyed"] = ""
|
|
ctld.i18n["en"]["Radio Beacons:\n%1"] = ""
|
|
ctld.i18n["en"]["No Active Radio Beacons"] = ""
|
|
ctld.i18n["en"]["%1 deployed a Radio Beacon.\n\n%2"] = ""
|
|
ctld.i18n["en"]["You need to land before you can deploy a Radio Beacon!"] = ""
|
|
ctld.i18n["en"]["%1 removed a Radio Beacon.\n\n%2"] = ""
|
|
ctld.i18n["en"]["No Radio Beacons within 500m."] = ""
|
|
ctld.i18n["en"]["You need to land before remove a Radio Beacon"] = ""
|
|
ctld.i18n["en"]["%1 successfully rearmed a full %2 in the field"] = ""
|
|
ctld.i18n["en"]["Missing %1\n"] = ""
|
|
ctld.i18n["en"]["Out of parts for AA Systems. Current limit is %1\n"] = ""
|
|
ctld.i18n["en"]["Cannot build %1\n%2\n\nOr the crates are not close enough together"] = ""
|
|
ctld.i18n["en"]["%1 successfully deployed a full %2 in the field. \n\nAA Active System limit is: %3\nActive: %4"] = ""
|
|
ctld.i18n["en"]["%1 successfully repaired a full %2 in the field."] = ""
|
|
ctld.i18n["en"]["Cannot repair %1. No damaged %2 within 300m"] = ""
|
|
ctld.i18n["en"]["%1 successfully deployed %2 to the field using %3 crates."] = ""
|
|
ctld.i18n["en"]["Cannot build %1!\n\nIt requires %2 crates and there are %3 \n\nOr the crates are not within 300m of each other"] = ""
|
|
ctld.i18n["en"]["%1 dropped %2 smoke."] = ""
|
|
|
|
--- JTAC messages
|
|
ctld.i18n["en"]["JTAC Group %1 KIA!"] = ""
|
|
ctld.i18n["en"]["%1, selected target reacquired, %2"] = ""
|
|
ctld.i18n["en"][". CODE: %1. POSITION: %2"] = ""
|
|
ctld.i18n["en"]["new target, "] = ""
|
|
ctld.i18n["en"]["standing by on %1"] = ""
|
|
ctld.i18n["en"]["lasing %1"] = ""
|
|
ctld.i18n["en"][", temporarily %1"] = ""
|
|
ctld.i18n["en"]["target lost"] = ""
|
|
ctld.i18n["en"]["target destroyed"] = ""
|
|
ctld.i18n["en"][", selected %1"] = ""
|
|
ctld.i18n["en"]["%1 %2 target lost."] = ""
|
|
ctld.i18n["en"]["%1 %2 target destroyed."] = ""
|
|
ctld.i18n["en"]["JTAC STATUS: \n\n"] = ""
|
|
ctld.i18n["en"][", available on %1 %2,"] = ""
|
|
ctld.i18n["en"]["UNKNOWN"] = ""
|
|
ctld.i18n["en"][" targeting "] = ""
|
|
ctld.i18n["en"][" targeting selected unit "] = ""
|
|
ctld.i18n["en"][" attempting to find selected unit, temporarily targeting "] = ""
|
|
ctld.i18n["en"]["(Laser OFF) "] = ""
|
|
ctld.i18n["en"]["Visual On: "] = ""
|
|
ctld.i18n["en"][" searching for targets %1\n"] = ""
|
|
ctld.i18n["en"]["No Active JTACs"] = ""
|
|
ctld.i18n["en"][", targeting selected unit, %1"] = ""
|
|
ctld.i18n["en"][". CODE: %1. POSITION: %2"] = ""
|
|
ctld.i18n["en"][", target selection reset."] = ""
|
|
ctld.i18n["en"]["%1, laser and smokes enabled"] = ""
|
|
ctld.i18n["en"]["%1, laser and smokes disabled"] = ""
|
|
ctld.i18n["en"]["%1, wind and target speed laser spot compensations enabled"] = ""
|
|
ctld.i18n["en"]["%1, wind and target speed laser spot compensations disabled"] = ""
|
|
ctld.i18n["en"]["%1, WHITE smoke deployed near target"] = ""
|
|
|
|
--- F10 menu messages
|
|
ctld.i18n["en"]["Actions"] = ""
|
|
ctld.i18n["en"]["Troop Transport"] = ""
|
|
ctld.i18n["en"]["Unload / Extract Troops"] = ""
|
|
ctld.i18n["en"]["Next page"] = ""
|
|
ctld.i18n["en"]["Load "] = ""
|
|
ctld.i18n["en"]["Vehicle / FOB Transport"] = ""
|
|
ctld.i18n["en"]["Vehicle / FOB Crates / Drone"] = ""
|
|
ctld.i18n["en"]["Unload Vehicles"] = ""
|
|
ctld.i18n["en"]["Load / Extract Vehicles"] = ""
|
|
ctld.i18n["en"]["Load / Unload FOB Crate"] = ""
|
|
ctld.i18n["en"]["CTLD Commands"] = ""
|
|
ctld.i18n["en"]["CTLD"] = ""
|
|
ctld.i18n["en"]["Check Cargo"] = ""
|
|
ctld.i18n["en"]["Load Nearby Crate(s)"] = ""
|
|
ctld.i18n["en"]["Unpack Any Crate"] = ""
|
|
ctld.i18n["en"]["Drop Crate(s)"] = ""
|
|
ctld.i18n["en"]["List Nearby Crates"] = ""
|
|
ctld.i18n["en"]["List FOBs"] = ""
|
|
ctld.i18n["en"]["List Beacons"] = ""
|
|
ctld.i18n["en"]["List Radio Beacons"] = ""
|
|
ctld.i18n["en"]["Smoke Markers"] = ""
|
|
ctld.i18n["en"]["Drop Red Smoke"] = ""
|
|
ctld.i18n["en"]["Drop Blue Smoke"] = ""
|
|
ctld.i18n["en"]["Drop Orange Smoke"] = ""
|
|
ctld.i18n["en"]["Drop Green Smoke"] = ""
|
|
ctld.i18n["en"]["Drop Beacon"] = ""
|
|
ctld.i18n["en"]["Radio Beacons"] = ""
|
|
ctld.i18n["en"]["Remove Closest Beacon"] = ""
|
|
ctld.i18n["en"]["JTAC Status"] = ""
|
|
ctld.i18n["en"]["DISABLE "] = ""
|
|
ctld.i18n["en"]["ENABLE "] = ""
|
|
ctld.i18n["en"]["REQUEST "] = ""
|
|
ctld.i18n["en"]["Reset TGT Selection"] = ""
|
|
-- F10 RECON menus
|
|
ctld.i18n["en"]["RECON"] = ""
|
|
ctld.i18n["en"]["Show targets in LOS (refresh)"] = ""
|
|
ctld.i18n["en"]["Hide targets in LOS"] = ""
|
|
ctld.i18n["en"]["START autoRefresh targets in LOS"] = ""
|
|
ctld.i18n["en"]["STOP autoRefresh targets in LOS"] = ""
|
|
|
|
--- Translates a string (text) with parameters (parameters) to the language defined in ctld.i18n_lang
|
|
---@param text string The text to translate, with the parameters as %1, %2, etc. (all strings!!!!)
|
|
---@param ... any (list) The parameters to replace in the text, in order (all paremeters will be converted to string)
|
|
---@return string the translated and formatted text
|
|
function ctld.i18n_translate(text, ...)
|
|
local _text
|
|
|
|
if not ctld.i18n[ctld.i18n_lang] then
|
|
env.info(string.format(" E - CTLD.i18n_translate: Language %s not found, defaulting to 'en'", tostring(ctld.i18n_lang)))
|
|
_text = ctld.i18n["en"][text]
|
|
else
|
|
_text = ctld.i18n[ctld.i18n_lang][text]
|
|
end
|
|
|
|
-- default to english
|
|
if _text == nil then
|
|
_text = ctld.i18n["en"][text]
|
|
end
|
|
|
|
-- default to the provided text
|
|
if _text == nil or _text == "" then
|
|
_text = text
|
|
end
|
|
|
|
if arg and arg.n and arg.n > 0 then
|
|
local _args = {}
|
|
for i=1,arg.n do
|
|
_args[i] = tostring(arg[i]) or ""
|
|
end
|
|
for i = 1, #_args do
|
|
_text = string.gsub(_text, "%%" .. i, _args[i])
|
|
end
|
|
end
|
|
|
|
return _text
|
|
end
|
|
|
|
-- ************************************************************************
|
|
-- ********************* USER CONFIGURATION ******************************
|
|
-- ************************************************************************
|
|
ctld.staticBugWorkaround = false -- DCS had a bug where destroying statics would cause a crash. If this happens again, set this to TRUE
|
|
|
|
ctld.disableAllSmoke = false -- if true, all smoke is diabled at pickup and drop off zones regardless of settings below. Leave false to respect settings below
|
|
|
|
-- Allow units to CTLD by aircraft type and not by pilot name - this is done everytime a player enters a new unit
|
|
ctld.addPlayerAircraftByType = true
|
|
|
|
ctld.hoverPickup = true -- if set to false you can load crates with the F10 menu instead of hovering... Only if not using real crates!
|
|
ctld.loadCrateFromMenu = true -- if set to true, you can load crates with the F10 menu OR hovering, in case of using choppers and planes for example.
|
|
|
|
ctld.enableCrates = true -- if false, Helis will not be able to spawn or unpack crates so will be normal CTTS
|
|
ctld.enableAllCrates = true -- if false, the "all crates" menu items will not be displayed
|
|
ctld.slingLoad = false -- if false, crates can be used WITHOUT slingloading, by hovering above the crate, simulating slingloading but not the weight...
|
|
-- There are some bug with Sling-loading that can cause crashes, if these occur set slingLoad to false
|
|
-- to use the other method.
|
|
-- Set staticBugFix to FALSE if use set ctld.slingLoad to TRUE
|
|
ctld.enableSmokeDrop = true -- if false, helis and c-130 will not be able to drop smoke
|
|
ctld.maxExtractDistance = 125 -- max distance from vehicle to troops to allow a group extraction
|
|
ctld.maximumDistanceLogistic = 250 -- max distance from vehicle to logistics to allow a loading or spawning operation
|
|
ctld.maximumSearchDistance = 0 -- max distance for troops to search for enemy
|
|
ctld.maximumMoveDistance = 0 -- max distance for troops to move from drop point if no enemy is nearby
|
|
ctld.minimumDeployDistance = 500 -- minimum distance from a friendly pickup zone where you can deploy a crate
|
|
ctld.numberOfTroops = 10 -- default number of troops to load on a transport heli or C-130
|
|
-- also works as maximum size of group that'll fit into a helicopter unless overridden
|
|
ctld.enableFastRopeInsertion = true -- allows you to drop troops by fast rope
|
|
ctld.fastRopeMaximumHeight = 20.00 -- in meters which is 60 ft max fast rope (not rappell) safe height
|
|
ctld.vehiclesForTransportRED = { "BRDM-2", "BTR_D" } -- vehicles to load onto Il-76 - Alternatives {"Strela-1 9P31","BMP-1"}
|
|
ctld.vehiclesForTransportBLUE = { "M1045 HMMWV TOW", "M1043 HMMWV Armament" } -- vehicles to load onto c130 - Alternatives {"M1128 Stryker MGS","M1097 Avenger"}
|
|
ctld.vehiclesWeight = {
|
|
["BRDM-2"] = 7000,
|
|
["BTR_D"] = 8000,
|
|
["M1045 HMMWV TOW"] = 3220,
|
|
["M1043 HMMWV Armament"] = 2500
|
|
}
|
|
|
|
ctld.spawnRPGWithCoalition = false --spawns a friendly RPG unit with Coalition forces
|
|
ctld.spawnStinger = false -- spawns a stinger / igla soldier with a group of 6 or more soldiers!
|
|
ctld.enabledFOBBuilding = true -- if true, you can load a crate INTO a C-130 than when unpacked creates a Forward Operating Base (FOB) which is a new place to spawn (crates) and carry crates from
|
|
-- In future i'd like it to be a FARP but so far that seems impossible...
|
|
-- You can also enable troop Pickup at FOBS
|
|
ctld.cratesRequiredForFOB = 1 -- The amount of crates required to build a FOB. Once built, helis can spawn crates at this outpost to be carried and deployed in another area.
|
|
-- The large crates can only be loaded and dropped by large aircraft, like the C-130 and listed in ctld.vehicleTransportEnabled
|
|
-- Small FOB crates can be moved by helicopter. The FOB will require ctld.cratesRequiredForFOB larges crates and small crates are 1/3 of a large fob crate
|
|
-- To build the FOB entirely out of small crates you will need ctld.cratesRequiredForFOB * 3
|
|
|
|
ctld.troopPickupAtFOB = true -- if true, troops can also be picked up at a created FOB
|
|
ctld.buildTimeFOB = 90 --time in seconds for the FOB to be built
|
|
ctld.crateWaitTime = 0 -- time in seconds to wait before you can spawn another crate
|
|
ctld.forceCrateToBeMoved = false -- a crate must be picked up at least once and moved before it can be unpacked. Helps to reduce crate spam
|
|
ctld.radioSound = "beacon.ogg" -- the name of the sound file to use for the FOB radio beacons. If this isnt added to the mission BEACONS WONT WORK!
|
|
ctld.radioSoundFC3 = "beaconsilent.ogg" -- name of the second silent radio file, used so FC3 aircraft dont hear ALL the beacon noises... :)
|
|
ctld.deployedBeaconBattery = 60 -- the battery on deployed beacons will last for this number minutes before needing to be re-deployed
|
|
ctld.enabledRadioBeaconDrop = true -- if its set to false then beacons cannot be dropped by units
|
|
ctld.allowRandomAiTeamPickups = false -- Allows the AI to randomize the loading of infantry teams (specified below) at pickup zones
|
|
|
|
-- Simulated Sling load configuration
|
|
ctld.minimumHoverHeight = 7.5 -- Lowest allowable height for crate hover
|
|
ctld.maximumHoverHeight = 15.0 -- Highest allowable height for crate hover
|
|
ctld.maxDistanceFromCrate = 5.5 -- Maximum distance from from crate for hover
|
|
ctld.hoverTime = 3 -- Time to hold hover above a crate for loading in seconds
|
|
|
|
-- end of Simulated Sling load configuration
|
|
|
|
-- ***************** AA SYSTEM CONFIG *****************
|
|
ctld.aaLaunchers = 3 -- controls how many launchers to add to the AA systems when its spawned if no amount is specified in the template.
|
|
-- Sets a limit on the number of active AA systems that can be built for RED.
|
|
-- A system is counted as Active if its fully functional and has all parts
|
|
-- If a system is partially destroyed, it no longer counts towards the total
|
|
-- When this limit is hit, a player will still be able to get crates for an AA system, just unable
|
|
-- to unpack them
|
|
|
|
ctld.AASystemLimitRED = 5 -- Red side limit
|
|
ctld.AASystemLimitBLUE = 5 -- Blue side limit
|
|
|
|
-- Allows players to create systems using as many crates as they like
|
|
-- Example : an amount X of patriot launcher crates allows for Y launchers to be deployed, if a player brings 2*X+Z crates (Z being lower then X), then deploys the patriot site, 2*Y launchers will be in the group and Z launcher crate will be left over
|
|
|
|
ctld.AASystemCrateStacking = false
|
|
--END AA SYSTEM CONFIG ------------------------------------
|
|
|
|
-- ***************** JTAC CONFIGURATION *****************
|
|
ctld.JTAC_LIMIT_RED = 10 -- max number of JTAC Crates for the RED Side
|
|
ctld.JTAC_LIMIT_BLUE = 10 -- max number of JTAC Crates for the BLUE Side
|
|
ctld.JTAC_dropEnabled = true -- allow JTAC Crate spawn from F10 menu
|
|
ctld.JTAC_maxDistance = 20000 -- How far a JTAC can "see" in meters (with Line of Sight)
|
|
ctld.JTAC_smokeOn_RED = true -- enables marking of target with smoke for RED forces
|
|
ctld.JTAC_smokeOn_BLUE = true -- enables marking of target with smoke for BLUE forces
|
|
ctld.JTAC_smokeColour_RED = 4 -- RED side smoke colour -- Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4
|
|
ctld.JTAC_smokeColour_BLUE = 1 -- BLUE side smoke colour -- Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4
|
|
ctld.JTAC_smokeMarginOfError = 0 -- error that the JTAC is allowed to make when popping a smoke (in meters)
|
|
ctld.JTAC_smokeOffset_x = 0.0 -- distance in the X direction from target to smoke (meters)
|
|
ctld.JTAC_smokeOffset_y = 10.0 -- distance in the Y direction from target to smoke (meters)
|
|
ctld.JTAC_smokeOffset_z = 0.0 -- distance in the z direction from target to smoke (meters)
|
|
ctld.JTAC_jtacStatusF10 = true -- enables F10 JTAC Status menu
|
|
ctld.JTAC_location = true -- shows location of target in JTAC message
|
|
ctld.location_DMS = false -- shows coordinates as Degrees Minutes Seconds instead of Degrees Decimal minutes
|
|
ctld.JTAC_lock = "all" -- "vehicle" OR "troop" OR "all" forces JTAC to only lock vehicles or troops or all ground units
|
|
ctld.JTAC_allowStandbyMode = true -- if true, allow players to toggle lasing on/off
|
|
ctld.JTAC_laseSpotCorrections = true -- if true, each JTAC will have a special option (toggle on/off) available in it's menu to attempt to lead the target, taking into account current wind conditions and the speed of the target (particularily useful against moving heavy armor)
|
|
ctld.JTAC_allowSmokeRequest = true -- if true, allow players to request a smoke on target (temporary)
|
|
ctld.JTAC_allow9Line = true -- if true, allow players to ask for a 9Line (individual) for a specific JTAC's target
|
|
|
|
-- ***************** Pickup, dropoff and waypoint zones *****************
|
|
|
|
-- Available colors (anything else like "none" disables smoke): "green", "red", "white", "orange", "blue", "none",
|
|
-- Use any of the predefined names or set your own ones
|
|
-- You can add number as a third option to limit the number of soldier or vehicle groups that can be loaded from a zone.
|
|
-- Dropping back a group at a limited zone will add one more to the limit
|
|
-- If a zone isn't ACTIVE then you can't pickup from that zone until the zone is activated by ctld.activatePickupZone
|
|
-- using the Mission editor
|
|
-- You can pickup from a SHIP by adding the SHIP UNIT NAME instead of a zone name
|
|
-- Side - Controls which side can load/unload troops at the zone
|
|
-- Flag Number - Optional last field. If set the current number of groups remaining can be obtained from the flag value
|
|
--pickupZones = { "Zone name or Ship Unit Name", "smoke color", "limit (-1 unlimited)", "ACTIVE (yes/no)", "side (0 = Both sides / 1 = Red / 2 = Blue )", flag number (optional) }
|
|
ctld.pickupZones = {
|
|
{ "Luostari Supply", "blue", -1, "yes", 0 },
|
|
{ "Ivalo Supply", "blue", -1, "yes", 0 },
|
|
{ "London FARP Supply", "blue", -1, "yes", 0 },
|
|
{ "Dallas FARP Supply", "blue", -1, "yes", 0 },
|
|
{ "Paris FARP Supply", "blue", -1, "yes", 0 },
|
|
{ "Kilpyavr Supply", "none", -1, "yes", 0 },
|
|
{ "Severomorsk-1 Supply", "none", -1, "yes", 0 },
|
|
{ "Severomorsk-3 Supply", "none", -1, "yes", 0 },
|
|
{ "Murmansk Supply", "none", -1, "yes", 0 },
|
|
{ "Olenya Supply", "none", -1, "yes", 0 },
|
|
{ "Monchegorsk Supply", "none", -1, "yes", 0 },
|
|
{ "Afrikanda Supply", "none", -1, "yes", 0 },
|
|
{ "Koshka Supply", "none", -1, "yes", 0 },
|
|
{ "Alakurtti Supply", "blue", -1, "yes", 0},
|
|
|
|
|
|
{ "CVN-72 Abraham Lincoln", "none", -1, "yes", 0, 1001 }, -- instead of a Zone Name you can also use the UNIT NAME of a ship
|
|
}
|
|
|
|
-- dropOffZones = {"name","smoke colour",0,side 1 = Red or 2 = Blue or 0 = Both sides}
|
|
ctld.dropOffZones = {
|
|
{ "dropzone1", "none", 2 },
|
|
{ "dropzone2", "blue", 2 },
|
|
{ "dropzone3", "orange", 2 },
|
|
{ "dropzone4", "none", 2 },
|
|
{ "dropzone5", "none", 1 },
|
|
{ "dropzone6", "none", 1 },
|
|
{ "dropzone7", "none", 1 },
|
|
{ "dropzone8", "none", 1 },
|
|
{ "dropzone9", "none", 1 },
|
|
{ "dropzone10", "none", 1 },
|
|
}
|
|
|
|
--wpZones = { "Zone name", "smoke color", "ACTIVE (yes/no)", "side (0 = Both sides / 1 = Red / 2 = Blue )", }
|
|
ctld.wpZones = {
|
|
{ "wpzone1", "green","yes", 2 },
|
|
{ "wpzone2", "blue","yes", 2 },
|
|
{ "wpzone3", "orange","yes", 2 },
|
|
{ "wpzone4", "none","yes", 2 },
|
|
{ "wpzone5", "none","yes", 2 },
|
|
{ "wpzone6", "none","yes", 1 },
|
|
{ "wpzone7", "none","yes", 1 },
|
|
{ "wpzone8", "none","yes", 1 },
|
|
{ "wpzone9", "none","yes", 1 },
|
|
{ "wpzone10", "none","no", 0 }, -- Both sides as its set to 0
|
|
}
|
|
|
|
-- ******************** Transports names **********************
|
|
-- If ctld.addPlayerAircraftByType = True, comment or uncomment lines to allow aircraft's type carry CTLD
|
|
ctld.aircraftTypeTable = {
|
|
--%%%%% MODS %%%%%
|
|
--"Bronco-OV-10A",
|
|
--"Hercules",
|
|
--"SK-60",
|
|
"UH-60L",
|
|
--"T-45",
|
|
|
|
--%%%%% CHOPPERS %%%%%
|
|
--"Ka-50",
|
|
--"Ka-50_3",
|
|
"Mi-8MT",
|
|
"Mi-24P",
|
|
--"SA342L",
|
|
--"SA342M",
|
|
--"SA342Mistral",
|
|
--"SA342Minigun",
|
|
"UH-1H",
|
|
"CH-47Fbl1",
|
|
|
|
--%%%%% AIRCRAFTS %%%%%
|
|
--"C-101EB",
|
|
--"C-101CC",
|
|
--"Christen Eagle II",
|
|
--"L-39C",
|
|
--"L-39ZA",
|
|
--"MB-339A",
|
|
--"MB-339APAN",
|
|
--"Mirage-F1B",
|
|
--"Mirage-F1BD",
|
|
--"Mirage-F1BE",
|
|
--"Mirage-F1BQ",
|
|
--"Mirage-F1DDA",
|
|
--"Su-25T",
|
|
--"Yak-52",
|
|
|
|
--%%%%% WARBIRDS %%%%%
|
|
--"Bf-109K-4",
|
|
--"Fw 190A8",
|
|
--"FW-190D9",
|
|
--"I-16",
|
|
--"MosquitoFBMkVI",
|
|
--"P-47D-30",
|
|
--"P-47D-40",
|
|
--"P-51D",
|
|
--"P-51D-30-NA",
|
|
--"SpitfireLFMkIX",
|
|
--"SpitfireLFMkIXCW",
|
|
--"TF-51D",
|
|
}
|
|
|
|
-- Use any of the predefined names or set your own ones
|
|
ctld.transportPilotNames = {
|
|
-- BLUE
|
|
"Mi-8 NELLIS-1",
|
|
"Mi-8 NELLIS-2",
|
|
"Mi-8 NELLIS-3 AFAC",
|
|
"Mi-24P NELLIS-1",
|
|
"Mi-24P NELLIS-2",
|
|
"Mi-24P NELLIS-3 AFAC",
|
|
"UH-1H NELLIS-1",
|
|
"UH-1H NELLIS-2",
|
|
"UH-1H NELLIS-3 AFAC",
|
|
"CH-47F NELLIS-1",
|
|
"CH-47F NELLIS-2",
|
|
"CH-47F NELLIS-3",
|
|
"CH-47F NELLIS-4 AFAC",
|
|
}
|
|
|
|
-- *************** Optional Extractable GROUPS *****************
|
|
|
|
-- Use any of the predefined names or set your own ones
|
|
ctld.extractableGroups = {
|
|
"extract1",
|
|
"extract2",
|
|
"extract3",
|
|
"extract4",
|
|
"extract5",
|
|
"extract6",
|
|
"extract7",
|
|
"extract8",
|
|
"extract9",
|
|
"extract10",
|
|
}
|
|
|
|
-- ************** Logistics UNITS FOR CRATE SPAWNING ******************
|
|
|
|
-- Use any of the predefined names or set your own ones
|
|
-- When a logistic unit is destroyed, you will no longer be able to spawn crates
|
|
ctld.logisticUnits = {
|
|
"Luostari Supply",
|
|
"Ivalo Supply",
|
|
"London FARP Supply",
|
|
"Paris FARP Supply",
|
|
"Dallas FARP Supply",
|
|
"Kilpyavr Supply",
|
|
"Severomorsk-1 Supply",
|
|
"Severomorsk-3 Supply",
|
|
"Koshka Supply",
|
|
"Murmansk Supply",
|
|
"Olenya Supply",
|
|
"Monchegorsk Supply",
|
|
"Afrikanda Supply",
|
|
"Alakurtti Supply"
|
|
|
|
}
|
|
|
|
-- ************** UNITS ABLE TO TRANSPORT VEHICLES ******************
|
|
-- Add the model name of the unit that you want to be able to transport and deploy vehicles
|
|
-- units db has all the names or you can extract a mission.miz file by making it a zip and looking
|
|
-- in the contained mission file
|
|
ctld.vehicleTransportEnabled = {
|
|
"76MD", -- the il-76 mod doesnt use a normal - sign so il-76md wont match... !!!! GRR
|
|
"Hercules",
|
|
}
|
|
|
|
-- ************** Units able to use DCS dynamic cargo system ******************
|
|
-- DCS (version) added the ability to load and unload cargo from aircraft.
|
|
-- Units listed here will spawn a cargo static that can be loaded with the standard DCS cargo system
|
|
-- We will also use this to make modifications to the menu and other checks and messages
|
|
ctld.dynamicCargoUnits = {
|
|
--"CH-47Fbl1",
|
|
}
|
|
|
|
-- ************** Maximum Units SETUP for UNITS ******************
|
|
-- Put the name of the Unit you want to limit group sizes too
|
|
-- i.e
|
|
--["UH-1H"] = 16,
|
|
--
|
|
-- Will limit UH1 to only transport groups with a size 10 or less
|
|
-- Make sure the unit name is exactly right or it wont work
|
|
|
|
ctld.unitLoadLimits = {
|
|
-- Remove the -- below to turn on options
|
|
-- ["SA342Mistral"] = 4,
|
|
-- ["SA342L"] = 4,
|
|
-- ["SA342M"] = 4,
|
|
|
|
--%%%%% MODS %%%%%
|
|
--["Bronco-OV-10A"] = 4,
|
|
["Hercules"] = 30,
|
|
--["SK-60"] = 1,
|
|
["UH-60L"] = 16,
|
|
--["T-45"] = 1,
|
|
|
|
--%%%%% CHOPPERS %%%%%
|
|
["Mi-8MT"] = 16,
|
|
["Mi-24P"] = 16,
|
|
--["SA342L"] = 4,
|
|
--["SA342M"] = 4,
|
|
--["SA342Mistral"] = 4,
|
|
--["SA342Minigun"] = 3,
|
|
["UH-1H"] = 16,
|
|
["CH-47Fbl1"] = 33,
|
|
|
|
--%%%%% AIRCRAFTS %%%%%
|
|
--["C-101EB"] = 1,
|
|
--["C-101CC"] = 1,
|
|
--["Christen Eagle II"] = 1,
|
|
--["L-39C"] = 1,
|
|
--["L-39ZA"] = 1,
|
|
--["MB-339A"] = 1,
|
|
--["MB-339APAN"] = 1,
|
|
--["Mirage-F1B"] = 1,
|
|
--["Mirage-F1BD"] = 1,
|
|
--["Mirage-F1BE"] = 1,
|
|
--["Mirage-F1BQ"] = 1,
|
|
--["Mirage-F1DDA"] = 1,
|
|
--["Su-25T"] = 1,
|
|
--["Yak-52"] = 1,
|
|
|
|
--%%%%% WARBIRDS %%%%%
|
|
--["Bf-109K-4"] = 1,
|
|
--["Fw 190A8"] = 1,
|
|
--["FW-190D9"] = 1,
|
|
--["I-16"] = 1,
|
|
--["MosquitoFBMkVI"] = 1,
|
|
--["P-47D-30"] = 1,
|
|
--["P-47D-40"] = 1,
|
|
--["P-51D"] = 1,
|
|
--["P-51D-30-NA"] = 1,
|
|
--["SpitfireLFMkIX"] = 1,
|
|
--["SpitfireLFMkIXCW"] = 1,
|
|
--["TF-51D"] = 1,
|
|
}
|
|
|
|
-- Put the name of the Unit you want to enable loading multiple crates
|
|
ctld.internalCargoLimits = {
|
|
|
|
-- Remove the -- below to turn on options
|
|
--["Mi-8MT"] = 1,
|
|
--["CH-47Fbl1"] = 1,
|
|
}
|
|
|
|
|
|
-- ************** Allowable actions for UNIT TYPES ******************
|
|
-- Put the name of the Unit you want to limit actions for
|
|
-- NOTE - the unit must've been listed in the transportPilotNames list above
|
|
-- This can be used in conjunction with the options above for group sizes
|
|
-- By default you can load both crates and troops unless overriden below
|
|
-- i.e
|
|
-- ["UH-1H"] = {crates=true, troops=false},
|
|
--
|
|
-- Will limit UH1 to only transport CRATES but NOT TROOPS
|
|
--
|
|
-- ["SA342Mistral"] = {crates=fales, troops=true},
|
|
-- Will allow Mistral Gazelle to only transport crates, not troops
|
|
|
|
ctld.unitActions = {
|
|
|
|
-- Remove the -- below to turn on options
|
|
-- ["SA342Mistral"] = {crates=true, troops=true},
|
|
-- ["SA342L"] = {crates=false, troops=true},
|
|
-- ["SA342M"] = {crates=false, troops=true},
|
|
|
|
--%%%%% MODS %%%%%
|
|
--["Bronco-OV-10A"] = {crates=true, troops=true},
|
|
["Hercules"] = {crates=true, troops=true},
|
|
["SK-60"] = {crates=true, troops=true},
|
|
["UH-60L"] = {crates=true, troops=true},
|
|
--["T-45"] = {crates=true, troops=true},
|
|
|
|
--%%%%% CHOPPERS %%%%%
|
|
--["Ka-50"] = {crates=true, troops=false},
|
|
--["Ka-50_3"] = {crates=true, troops=false},
|
|
["Mi-8MT"] = {crates=true, troops=true},
|
|
["Mi-24P"] = {crates=true, troops=true},
|
|
--["SA342L"] = {crates=false, troops=true},
|
|
--["SA342M"] = {crates=false, troops=true},
|
|
--["SA342Mistral"] = {crates=false, troops=true},
|
|
--["SA342Minigun"] = {crates=false, troops=true},
|
|
["UH-1H"] = {crates=true, troops=true},
|
|
["CH-47Fbl1"] = {crates=true, troops=true},
|
|
|
|
--%%%%% AIRCRAFTS %%%%%
|
|
--["C-101EB"] = {crates=true, troops=true},
|
|
--["C-101CC"] = {crates=true, troops=true},
|
|
--["Christen Eagle II"] = {crates=true, troops=true},
|
|
--["L-39C"] = {crates=true, troops=true},
|
|
--["L-39ZA"] = {crates=true, troops=true},
|
|
--["MB-339A"] = {crates=true, troops=true},
|
|
--["MB-339APAN"] = {crates=true, troops=true},
|
|
--["Mirage-F1B"] = {crates=true, troops=true},
|
|
--["Mirage-F1BD"] = {crates=true, troops=true},
|
|
--["Mirage-F1BE"] = {crates=true, troops=true},
|
|
--["Mirage-F1BQ"] = {crates=true, troops=true},
|
|
--["Mirage-F1DDA"] = {crates=true, troops=true},
|
|
--["Su-25T"]= {crates=true, troops=false},
|
|
--["Yak-52"] = {crates=true, troops=true},
|
|
|
|
--%%%%% WARBIRDS %%%%%
|
|
--["Bf-109K-4"] = {crates=true, troops=false},
|
|
--["Fw 190A8"] = {crates=true, troops=false},
|
|
--["FW-190D9"] = {crates=true, troops=false},
|
|
--["I-16"] = {crates=true, troops=false},
|
|
--["MosquitoFBMkVI"] = {crates=true, troops=true},
|
|
--["P-47D-30"] = {crates=true, troops=false},
|
|
--["P-47D-40"] = {crates=true, troops=false},
|
|
--["P-51D"] = {crates=true, troops=false},
|
|
--["P-51D-30-NA"] = {crates=true, troops=false},
|
|
--["SpitfireLFMkIX"] = {crates=true, troops=false},
|
|
--["SpitfireLFMkIXCW"] = {crates=true, troops=false},
|
|
--["TF-51D"] = {crates=true, troops=true},
|
|
}
|
|
|
|
-- ************** WEIGHT CALCULATIONS FOR INFANTRY GROUPS ******************
|
|
|
|
-- Infantry groups weight is calculated based on the soldiers' roles, and the weight of their kit
|
|
-- Every soldier weights between 90% and 120% of ctld.SOLDIER_WEIGHT, and they all carry a backpack and their helmet (ctld.KIT_WEIGHT)
|
|
-- Standard grunts have a rifle and ammo (ctld.RIFLE_WEIGHT)
|
|
-- AA soldiers have a MANPAD tube (ctld.MANPAD_WEIGHT)
|
|
-- Anti-tank soldiers have a RPG and a rocket (ctld.RPG_WEIGHT)
|
|
-- Machine gunners have the squad MG and 200 bullets (ctld.MG_WEIGHT)
|
|
-- JTAC have the laser sight, radio and binoculars (ctld.JTAC_WEIGHT)
|
|
-- Mortar servants carry their tube and a few rounds (ctld.MORTAR_WEIGHT)
|
|
|
|
ctld.SOLDIER_WEIGHT = 80 -- kg, will be randomized between 90% and 120%
|
|
ctld.KIT_WEIGHT = 20 -- kg
|
|
ctld.RIFLE_WEIGHT = 5 -- kg
|
|
ctld.MANPAD_WEIGHT = 18 -- kg
|
|
ctld.RPG_WEIGHT = 7.6 -- kg
|
|
ctld.MG_WEIGHT = 10 -- kg
|
|
ctld.MORTAR_WEIGHT = 26 -- kg
|
|
ctld.JTAC_WEIGHT = 15 -- kg
|
|
|
|
-- ************** INFANTRY GROUPS FOR PICKUP ******************
|
|
-- Unit Types
|
|
-- inf is normal infantry
|
|
-- mg is M249
|
|
-- at is RPG-16
|
|
-- aa is Stinger or Igla
|
|
-- mortar is a 2B11 mortar unit
|
|
-- jtac is a JTAC soldier, which will use JTACAutoLase
|
|
-- You must add a name to the group for it to work
|
|
-- You can also add an optional coalition side to limit the group to one side
|
|
-- for the side - 2 is BLUE and 1 is RED
|
|
ctld.loadableGroups = {
|
|
{name = ctld.i18n_translate("Standard Group"), inf = 6, mg = 2, at = 2 }, -- will make a loadable group with 6 infantry, 2 MGs and 2 anti-tank for both coalitions
|
|
{name = ctld.i18n_translate("Anti Air"), inf = 2, aa = 2 },
|
|
{name = ctld.i18n_translate("Anti Tank"), inf = 2, at = 6 },
|
|
{name = ctld.i18n_translate("Mortar Squad"), mortar = 5 },
|
|
--{name = ctld.i18n_translate("JTAC Group"), inf = 4, jtac = 1 }, -- will make a loadable group with 4 infantry and a JTAC soldier for both coalitions
|
|
{name = ctld.i18n_translate("Single JTAC"), jtac = 1 }, -- will make a loadable group witha single JTAC soldier for both coalitions
|
|
--{name = ctld.i18n_translate("2x - Standard Groups"), inf = 12, mg = 4, at = 4 },
|
|
--{name = ctld.i18n_translate("2x - Anti Air"), inf = 4, aa = 6 },
|
|
--{name = ctld.i18n_translate("2x - Anti Tank"), inf = 4, at = 12 },
|
|
--{name = ctld.i18n_translate("2x - Standard Groups + 2x Mortar"), inf = 12, mg = 4, at = 4, mortar = 12 },
|
|
--{name = ctld.i18n_translate("3x - Standard Groups"), inf = 18, mg = 6, at = 6 },
|
|
--{name = ctld.i18n_translate("3x - Anti Air"), inf = 6, aa = 9 },
|
|
--{name = ctld.i18n_translate("3x - Anti Tank"), inf = 6, at = 18 },
|
|
--{name = ctld.i18n_translate("3x - Mortar Squad"), mortar = 18},
|
|
--{name = ctld.i18n_translate("5x - Mortar Squad"), mortar = 30},
|
|
--{name = ctld.i18n_translate("Mortar Squad Red"), inf = 2, mortar = 5, side =1 }, --would make a group loadable by RED only
|
|
}
|
|
|
|
-- ************** SPAWNABLE CRATES ******************
|
|
-- Weights must be unique as we use the weight to change the cargo to the correct unit
|
|
-- when we unpack
|
|
--
|
|
|
|
ctld.spawnableCrates = {
|
|
-- name of the sub menu on F10 for spawning crates
|
|
["Combat Vehicles"] = {
|
|
--crates you can spawn
|
|
-- weight in KG
|
|
-- Desc is the description on the F10 MENU
|
|
-- unit is the model name of the unit to spawn
|
|
-- cratesRequired - if set requires that many crates of the same type within 100m of each other in order build the unit
|
|
-- side is optional but 2 is BLUE and 1 is RED
|
|
|
|
-- Some descriptions are filtered to determine if JTAC or not!
|
|
|
|
--- BLUE
|
|
{ weight = 500.10, desc = ctld.i18n_translate("M1128 Stryker MGS"), unit = "M1128 Stryker MGS", side = 2 },
|
|
{ weight = 500.01, desc = ctld.i18n_translate("M-60A3 Patton"), unit = "M-60", side = 2 }, --careful with the names as the script matches the desc to JTAC types
|
|
{ weight = 500.02, desc = ctld.i18n_translate("Humvee - TOW"), unit = "M1045 HMMWV TOW", side = 2 },
|
|
{ weight = 500.07, desc = ctld.i18n_translate("M1134 Stryker ATGM"), unit = "M1134 Stryker ATGM", side = 2 },
|
|
{ weight = 500.06, desc = ctld.i18n_translate("LAV-25"), unit = "LAV-25", side = 2 },
|
|
{ weight = 500.04, desc = ctld.i18n_translate("M2A2 Bradley"), unit="M-2 Bradley", side = 2 },
|
|
{ weight = 500.09, desc = ctld.i18n_translate("ATGM VAB Mephisto"), unit="VAB_Mephisto", side = 2 },
|
|
{ weight = 500.05, desc = ctld.i18n_translate("M1A2C Abrams"), unit="M1A2C_SEP_V3", side = 2, },
|
|
|
|
--- RED
|
|
{ weight = 511.01, desc = ctld.i18n_translate("BTR-82A"), unit = "BTR-82A", side = 1 },
|
|
{ weight = 511.02, desc = ctld.i18n_translate("BRDM-2"), unit = "BRDM-2", side = 1 },
|
|
{ weight = 511.03, desc = ctld.i18n_translate("BMP-3"), unit = "BMP-3", side = 1 },
|
|
{ weight = 511.04, desc = ctld.i18n_translate("T-55"), unit = "T-55", side = 1 },
|
|
{ weight = 511.05, desc = ctld.i18n_translate("T-72B3"), unit = "T-72B3", side = 1 },
|
|
-- need more redfor!
|
|
},
|
|
["Support"] = {
|
|
--- BLUE
|
|
{ weight = 501.01, desc = ctld.i18n_translate("MRAP - JTAC"), unit = "MaxxPro_MRAP", side = 2 }, -- used as jtac and unarmed, not on the crate list if JTAC is disabled
|
|
{ weight = 501.02, desc = ctld.i18n_translate("M-818 Ammo Truck"), unit = "M 818", side = 2 },
|
|
{ weight = 501.03, desc = ctld.i18n_translate("M-978 Tanker"), unit = "M978 HEMTT Tanker", side = 2 },
|
|
{ weight = 501.21, desc = ctld.i18n_translate("EWR Radar FPS-117"), unit="FPS-117", side = 2 },
|
|
|
|
--- RED
|
|
{ weight = 501.11, desc = ctld.i18n_translate("Tigr - JTAC"), unit = "Tigr_233036", side = 1 }, -- used as jtac and unarmed, not on the crate list if JTAC is disabled
|
|
{ weight = 501.12, desc = ctld.i18n_translate("Ural-4320-31 Ammo Truck"), unit = "Ural-4320-31", side = 1 },
|
|
{ weight = 501.13, desc = ctld.i18n_translate("ATZ-10 Refueler"), unit = "ATZ-10", side = 1 },
|
|
{ weight = 501.23, desc = ctld.i18n_translate("EWR Radar 1L13"), unit="1L13 EWR", side = 1 },
|
|
|
|
--- Both
|
|
{ multiple = {501.22, 501.22, 501.22}, desc = ctld.i18n_translate("FOB Crates - All") },
|
|
{ weight = 501.22, desc = ctld.i18n_translate("FOB Crate - Small"), unit = "FOB-SMALL" }, -- Builds a FOB! - requires 3 * ctld.cratesRequiredForFOB
|
|
|
|
},
|
|
["Artillery"] = {
|
|
--- BLUE
|
|
{ weight = 502.01, desc = ctld.i18n_translate("MLRS"), unit = "MLRS", side = 2, cratesRequired = 2 },
|
|
{ weight = 502.07, desc = ctld.i18n_translate("Smerch_CM"), unit = "Smerch", side = 2, cratesRequired = 2 },
|
|
{ weight = 502.03, desc = ctld.i18n_translate("L118 Light Artillery 105mm"), unit = "L118_Unit", side = 2 },
|
|
{ weight = 502.06, desc = ctld.i18n_translate("Smerch_HE"), unit = "Smerch_HE", side = 2, cratesRequired = 2 },
|
|
{ weight = 502.04, desc = ctld.i18n_translate("M-109"), unit = "M-109", side = 2, cratesRequired = 2 },
|
|
|
|
|
|
--- RED
|
|
{ weight = 502.12, desc = ctld.i18n_translate("SAU Gvozdika"), unit = "SAU Gvozdika", side = 1, cratesRequired = 2 },
|
|
{ weight = 502.11, desc = ctld.i18n_translate("SPH 2S19 Msta"), unit = "SAU Msta", side = 1, cratesRequired = 2 },
|
|
{ weight = 502.14, desc = ctld.i18n_translate("Uragan_BM-27"), unit = "Uragan_BM-27", side = 1, cratesRequired = 2 },
|
|
{ weight = 502.13, desc = ctld.i18n_translate("BM-21 Grad Ural"), unit = "Grad-URAL", side = 1, cratesRequired = 2 },
|
|
{ weight = 502.15, desc = ctld.i18n_translate("PLZ-05 Mobile Artillery"), unit = "PLZ05", side = 1, cratesRequired = 2 },
|
|
},
|
|
|
|
["AAA"] = {
|
|
--- BLUE
|
|
{ weight = 508.04, desc = ctld.i18n_translate("Gepard AAA"), unit = "Gepard", side = 2 },
|
|
{ weight = 508.05, desc = ctld.i18n_translate("LPWS C-RAM"), unit = "HEMTT_C-RAM_Phalanx", side = 2 },
|
|
{ weight = 508.06, desc = ctld.i18n_translate("SPAAA Vulcan M163"), unit = "Vulcan", side = 2 },
|
|
{ weight = 508.09, desc = ctld.i18n_translate("Bofors 40mm"), unit = "bofors40", side = 2 },
|
|
|
|
--- RED
|
|
{ weight = 508.11, desc = ctld.i18n_translate("Ural-375 ZU-23"), unit = "Ural-375 ZU-23", side = 1, cratesRequired = 1 },
|
|
{ weight = 508.12, desc = ctld.i18n_translate("ZSU-23-4 Shilka"), unit = "ZSU-23-4 Shilka", side = 1, cratesRequired = 1 },
|
|
{ weight = 508.13, desc = ctld.i18n_translate("ZSU_57_2"), unit = "ZSU_57_2", side = 1, cratesRequired = 1 },
|
|
|
|
},
|
|
|
|
["SAM short range"] = {
|
|
--- BLUE
|
|
{ weight = 503.01, desc = ctld.i18n_translate("M1097 Avenger"), unit = "M1097 Avenger", side = 2, cratesRequired = 2 },
|
|
{ weight = 503.02, desc = ctld.i18n_translate("M48 Chaparral"), unit = "M48 Chaparral", side = 2, cratesRequired = 2 },
|
|
{ weight = 503.03, desc = ctld.i18n_translate("Roland ADS"), unit = "Roland ADS", side = 2, cratesRequired = 2 },
|
|
{ weight = 503.05, desc = ctld.i18n_translate("M6 Linebacker"), unit = "M6 Linebacker", side = 2 },
|
|
-- Rapier System
|
|
{ multiple = {507.01, 507.02, 507.03}, desc = ctld.i18n_translate("Rapier - All crates"), side = 2 },
|
|
{ weight = 507.01, desc = ctld.i18n_translate("Rapier Launcher"), unit = "rapier_fsa_launcher", side = 2 },
|
|
{ weight = 507.02, desc = ctld.i18n_translate("Rapier SR"), unit = "rapier_fsa_blindfire_radar", side = 2 },
|
|
{ weight = 507.03, desc = ctld.i18n_translate("Rapier Tracker"), unit = "rapier_fsa_optical_tracker_unit", side = 2 },
|
|
{ weight = 507.04, desc = ctld.i18n_translate("Rapier Repair"), unit = "Rapier Repair" , side = 2 },
|
|
-- End of Rapier
|
|
|
|
--- RED
|
|
{ weight = 503.11, desc = ctld.i18n_translate("9K33 Osa"), unit = "Osa 9A33 ln", side = 1, cratesRequired = 2 },
|
|
{ weight = 503.12, desc = ctld.i18n_translate("9P31 Strela-1"), unit = "Strela-1 9P31", side = 1, cratesRequired = 2 },
|
|
{ weight = 503.15, desc = ctld.i18n_translate("2K22 Tunguska"), unit = "2S6 Tunguska", side = 1, cratesRequired = 2 },
|
|
{ weight = 503.16, desc = ctld.i18n_translate("SA-13 Strela-10M3"), unit = "Strela-10M3", side = 1, cratesRequired = 2 },
|
|
|
|
-- HQ-7
|
|
{ multiple = {503.20, 503.20, 503.21}, desc = ("HQ-7 - All crates"), side = 1 },
|
|
{ weight = 503.20, desc = ctld.i18n_translate("HQ-7_Launcher"), unit = "HQ-7_LN_SP", side = 1, cratesRequired = 2 },
|
|
{ weight = 503.21, desc = ctld.i18n_translate("HQ-7_STR_SP"), unit = "HQ-7_STR_SP", side = 1 },
|
|
{ weight = 503.23, desc = ctld.i18n_translate("HQ-7 Repair"), unit = "HQ-7 Repair", side = 1 },
|
|
-- End of HQ-7
|
|
|
|
},
|
|
["SAM mid range"] = {
|
|
--- BLUE
|
|
-- HAWK System
|
|
{ multiple = {504.01, 504.02, 504.03, 504.04, 504.05}, desc = ctld.i18n_translate("HAWK - All crates"), side = 2 },
|
|
{ multiple = {504.11, 504.12, 504.13}, desc = ctld.i18n_translate("NASAMS - All crates"), side = 2 },
|
|
{ weight = 504.01, desc = ctld.i18n_translate("HAWK Launcher"), unit = "Hawk ln", side = 2},
|
|
{ weight = 504.02, desc = ctld.i18n_translate("HAWK Search Radar"), unit = "Hawk sr", side = 2 },
|
|
{ weight = 504.03, desc = ctld.i18n_translate("HAWK Track Radar"), unit = "Hawk tr", side = 2 },
|
|
{ weight = 504.04, desc = ctld.i18n_translate("HAWK PCP"), unit = "Hawk pcp" , side = 2 },
|
|
{ weight = 504.05, desc = ctld.i18n_translate("HAWK CWAR"), unit = "Hawk cwar" , side = 2 },
|
|
{ weight = 504.06, desc = ctld.i18n_translate("HAWK Repair"), unit = "HAWK Repair" , side = 2 },
|
|
-- End of HAWK
|
|
|
|
-- NASAMS System
|
|
{ weight = 504.11, desc = ctld.i18n_translate("NASAMS Launcher 120C"), unit = "NASAMS_LN_C", side = 2},
|
|
{ weight = 504.12, desc = ctld.i18n_translate("NASAMS Search/Track Radar"), unit = "NASAMS_Radar_MPQ64F1", side = 2 },
|
|
{ weight = 504.13, desc = ctld.i18n_translate("NASAMS Command Post"), unit = "NASAMS_Command_Post", side = 2 },
|
|
{ weight = 504.14, desc = ctld.i18n_translate("NASAMS Repair"), unit = "NASAMS Repair", side = 2 },
|
|
-- End of NASAMS
|
|
|
|
--- RED
|
|
-- KUB SYSTEM
|
|
{ multiple = {504.21, 504.22}, desc = ctld.i18n_translate("KUB - All crates"), side = 1 },
|
|
{ weight = 504.21, desc = ctld.i18n_translate("KUB Launcher"), unit = "Kub 2P25 ln", side = 1},
|
|
{ weight = 504.22, desc = ctld.i18n_translate("KUB Radar"), unit = "Kub 1S91 str", side = 1 },
|
|
{ weight = 504.23, desc = ctld.i18n_translate("KUB Repair"), unit = "KUB Repair", side = 1},
|
|
-- End of KUB
|
|
|
|
},
|
|
["SAM long range"] = {
|
|
--- BLUE
|
|
-- Patriot System
|
|
{ multiple = {505.01, 505.02, 505.03}, desc = ctld.i18n_translate("Patriot - All crates"), side = 2 },
|
|
{ weight = 505.01, desc = ctld.i18n_translate("Patriot Launcher"), unit = "Patriot ln", side = 2 },
|
|
{ weight = 505.02, desc = ctld.i18n_translate("Patriot Radar"), unit = "Patriot str" , side = 2 },
|
|
{ weight = 505.03, desc = ctld.i18n_translate("Patriot ECS"), unit = "Patriot ECS", side = 2 },
|
|
--{ weight = 505.04, desc = ctld.i18n_translate("Patriot ICC"), unit = "Patriot cp", side = 2 },
|
|
--{ weight = 505.05, desc = ctld.i18n_translate("Patriot EPP"), unit = "Patriot EPP", side = 2 },
|
|
--{ weight = 505.06, desc = ctld.i18n_translate("Patriot AMG"), unit = "Patriot AMG" , side = 2 },
|
|
{ weight = 505.07, desc = ctld.i18n_translate("Patriot Repair"), unit = "Patriot Repair" , side = 2 },
|
|
-- End of Patriot
|
|
|
|
--BUK System
|
|
{ weight = 504.31, desc = ctld.i18n_translate("BUK Launcher"), unit = "SA-11 Buk LN 9A310M1", side = 1 },
|
|
{ weight = 504.32, desc = ctld.i18n_translate("BUK Search Radar"), unit = "SA-11 Buk SR 9S18M1", side = 1 },
|
|
{ weight = 504.33, desc = ctld.i18n_translate("BUK CC Radar"), unit = "SA-11 Buk CC 9S470M1", side = 1 },
|
|
{ weight = 504.34, desc = ctld.i18n_translate("BUK Repair"), unit = "BUK Repair", side = 1},
|
|
{ multiple = {504.31, 504.32, 504.33}, desc = ctld.i18n_translate("BUK - All crates"), side = 1 },
|
|
-- END of BUK
|
|
|
|
|
|
-- SA-2 SYSTEM
|
|
--{ weight = 505.20, desc = ctld.i18n_translate("SA-2 Guidline Launcher"), unit = "S_75M_Volhov", side = 1 },
|
|
--{ weight = 505.21, desc = ctld.i18n_translate("SA-2 Flat Face SR"), unit = "p-19 s-125 sr", side = 1 },
|
|
--{ weight = 505.22, desc = ctld.i18n_translate("SA-2 Fan Song TR"), unit = "SNR_75V", side = 1 },
|
|
--{ weight = 505.23, desc = ctld.i18n_translate("SA-2 CP"), unit = "RD_75", side = 1 },
|
|
--{ weight = 505.16, desc = ctld.i18n_translate("SA-2 Repair"), unit = "SA-2 Repair", side = 1 },
|
|
--{ multiple = {505.20, 505.21, 505.22, 505.23}, desc = ctld.i18n_translate("SA-2 - All crates"), side = 1 },
|
|
-- End of SA-2
|
|
|
|
|
|
-- S-300 SYSTEM
|
|
--{ weight = 505.11, desc = ctld.i18n_translate("S-300 Grumble TEL C"), unit = "S-300PS 5P85C ln", side = 1 },
|
|
--{ weight = 505.12, desc = ctld.i18n_translate("S-300 Grumble Flap Lid-A TR"), unit = "S-300PS 40B6M tr", side = 1 },
|
|
--{ weight = 505.13, desc = ctld.i18n_translate("S-300 Grumble Clam Shell SR"), unit = "S-300PS 40B6MD sr", side = 1 },
|
|
--{ weight = 505.14, desc = ctld.i18n_translate("S-300 Grumble Big Bird SR"), unit = "S-300PS 64H6E sr", side = 1 },
|
|
--{ weight = 505.15, desc = ctld.i18n_translate("S-300 Grumble C2"), unit = "S-300PS 54K6 cp", side = 1 },
|
|
--{ weight = 505.16, desc = ctld.i18n_translate("S-300 Repair"), unit = "S-300 Repair", side = 1 },
|
|
--{ multiple = {505.11, 505.12, 505.13, 505.14, 505.15}, desc = ctld.i18n_translate("SA-10 - All crates"), side = 1 },
|
|
-- End of S-300
|
|
},
|
|
["Drone"] = {
|
|
--- BLUE MQ-9 Repear
|
|
{ weight = 506.01, desc = ctld.i18n_translate("MQ-9 Repear - JTAC"), unit = "MQ-9 Reaper", side = 2 },
|
|
-- End of BLUE MQ-9 Repear
|
|
|
|
--- RED WingLoong-I
|
|
{ weight = 506.11, desc = ctld.i18n_translate("WingLoong-I - JTAC"), unit = "WingLoong-I", side = 1 },
|
|
-- End of WingLoong-I
|
|
},
|
|
}
|
|
|
|
ctld.spawnableCratesModels = {
|
|
["load"] = {
|
|
["category"] = "Unarmed",
|
|
["type"] = "Hummer",
|
|
["shape_name"] = "Hummer",
|
|
["canCargo"] = true
|
|
},
|
|
["sling"] = {
|
|
["category"] = "Cargos",
|
|
["shape_name"] = "bw_container_cargo",
|
|
["type"] = "container_cargo",
|
|
["canCargo"] = true
|
|
},
|
|
["dynamic"] = {
|
|
["category"] = "Unarmed",
|
|
["type"] = "Hummer",
|
|
["shape_name"] = "Hummer",
|
|
["canCargo"] = true
|
|
}
|
|
}
|
|
|
|
|
|
--[[ Placeholder for different type of cargo containers. Let's say pipes and trunks, fuel for FOB building
|
|
["shape_name"] = "ab-212_cargo",
|
|
["type"] = "uh1h_cargo" --new type for the container previously used
|
|
|
|
["shape_name"] = "ammo_box_cargo",
|
|
["type"] = "ammo_cargo",
|
|
|
|
["shape_name"] = "barrels_cargo",
|
|
["type"] = "barrels_cargo",
|
|
|
|
["shape_name"] = "bw_container_cargo",
|
|
["type"] = "container_cargo",
|
|
|
|
["shape_name"] = "f_bar_cargo",
|
|
["type"] = "f_bar_cargo",
|
|
|
|
["shape_name"] = "fueltank_cargo",
|
|
["type"] = "fueltank_cargo",
|
|
|
|
["shape_name"] = "iso_container_cargo",
|
|
["type"] = "iso_container",
|
|
|
|
["shape_name"] = "iso_container_small_cargo",
|
|
["type"] = "iso_container_small",
|
|
|
|
["shape_name"] = "oiltank_cargo",
|
|
["type"] = "oiltank_cargo",
|
|
|
|
["shape_name"] = "pipes_big_cargo",
|
|
["type"] = "pipes_big_cargo",
|
|
|
|
["shape_name"] = "pipes_small_cargo",
|
|
["type"] = "pipes_small_cargo",
|
|
|
|
["shape_name"] = "tetrapod_cargo",
|
|
["type"] = "tetrapod_cargo",
|
|
|
|
["shape_name"] = "trunks_long_cargo",
|
|
["type"] = "trunks_long_cargo",
|
|
|
|
["shape_name"] = "trunks_small_cargo",
|
|
["type"] = "trunks_small_cargo",
|
|
]]--
|
|
|
|
-- if the unit is on this list, it will be made into a JTAC when deployed
|
|
ctld.jtacUnitTypes = {
|
|
"Tigr_233036", "MaxxPro_MRAP", -- there are some wierd encoding issues so if you write SKP-11 it wont match as the - sign is encoded differently...
|
|
"MQ", "RQ", "WingLoong" --"MQ-9 Repear", "RQ-1A Predator", "WingLoong-I"}
|
|
}
|
|
ctld.jtacDroneRadius = 1000 -- JTAC offset radius in meters for orbiting drones
|
|
ctld.jtacDroneAltitude = 7000 -- JTAC altitude in meters for orbiting drones
|
|
-- ***************************************************************
|
|
-- **************** Mission Editor Functions *********************
|
|
-- ***************************************************************
|
|
|
|
-----------------------------------------------------------------
|
|
-- Spawn group at a trigger and set them as extractable. Usage:
|
|
-- ctld.spawnGroupAtTrigger("groupside", number, "triggerName", radius)
|
|
-- Variables:
|
|
-- "groupSide" = "red" for Russia "blue" for USA
|
|
-- _number = number of groups to spawn OR Group description
|
|
-- "triggerName" = trigger name in mission editor between commas
|
|
-- _searchRadius = random distance for units to move from spawn zone (0 will leave troops at the spawn position - no search for enemy)
|
|
--
|
|
-- Example: ctld.spawnGroupAtTrigger("red", 2, "spawn1", 1000)
|
|
--
|
|
-- This example will spawn 2 groups of russians at the specified point
|
|
-- and they will search for enemy or move randomly withing 1000m
|
|
-- OR
|
|
--
|
|
-- ctld.spawnGroupAtTrigger("blue", {mg=1,at=2,aa=3,inf=4,mortar=5},"spawn2", 2000)
|
|
-- Spawns 1 machine gun, 2 anti tank, 3 anti air, 4 standard soldiers and 5 mortars
|
|
--
|
|
function ctld.spawnGroupAtTrigger(_groupSide, _number, _triggerName, _searchRadius)
|
|
local _spawnTrigger = trigger.misc.getZone(_triggerName) -- trigger to use as reference position
|
|
|
|
if _spawnTrigger == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find trigger called %1",_triggerName), 10)
|
|
return
|
|
end
|
|
|
|
local _country
|
|
if _groupSide == "red" then
|
|
_groupSide = 1
|
|
_country = 0
|
|
else
|
|
_groupSide = 2
|
|
_country = 2
|
|
end
|
|
|
|
if _searchRadius < 0 then
|
|
_searchRadius = 0
|
|
end
|
|
|
|
local _pos2 = { x = _spawnTrigger.point.x, y = _spawnTrigger.point.z }
|
|
local _alt = land.getHeight(_pos2)
|
|
local _pos3 = { x = _pos2.x, y = _alt, z = _pos2.y }
|
|
|
|
local _groupDetails = ctld.generateTroopTypes(_groupSide, _number, _country)
|
|
|
|
local _droppedTroops = ctld.spawnDroppedGroup(_pos3, _groupDetails, false, _searchRadius);
|
|
|
|
if _groupSide == 1 then
|
|
table.insert(ctld.droppedTroopsRED, _droppedTroops:getName())
|
|
else
|
|
table.insert(ctld.droppedTroopsBLUE, _droppedTroops:getName())
|
|
end
|
|
end
|
|
|
|
|
|
-----------------------------------------------------------------
|
|
-- Spawn group at a Vec3 Point and set them as extractable. Usage:
|
|
-- ctld.spawnGroupAtPoint("groupside", number,Vec3 Point, radius)
|
|
-- Variables:
|
|
-- "groupSide" = "red" for Russia "blue" for USA
|
|
-- _number = number of groups to spawn OR Group Description
|
|
-- Vec3 Point = A vec3 point like {x=1,y=2,z=3}. Can be obtained from a unit like so: Unit.getName("Unit1"):getPoint()
|
|
-- _searchRadius = random distance for units to move from spawn zone (0 will leave troops at the spawn position - no search for enemy)
|
|
--
|
|
-- Example: ctld.spawnGroupAtPoint("red", 2, {x=1,y=2,z=3}, 1000)
|
|
--
|
|
-- This example will spawn 2 groups of russians at the specified point
|
|
-- and they will search for enemy or move randomly withing 1000m
|
|
-- OR
|
|
--
|
|
-- ctld.spawnGroupAtPoint("blue", {mg=1,at=2,aa=3,inf=4,mortar=5}, {x=1,y=2,z=3}, 2000)
|
|
-- Spawns 1 machine gun, 2 anti tank, 3 anti air, 4 standard soldiers and 5 mortars
|
|
function ctld.spawnGroupAtPoint(_groupSide, _number, _point, _searchRadius)
|
|
|
|
local _country
|
|
if _groupSide == "red" then
|
|
_groupSide = 1
|
|
_country = 0
|
|
else
|
|
_groupSide = 2
|
|
_country = 2
|
|
end
|
|
|
|
if _searchRadius < 0 then
|
|
_searchRadius = 0
|
|
end
|
|
|
|
local _groupDetails = ctld.generateTroopTypes(_groupSide, _number, _country)
|
|
|
|
local _droppedTroops = ctld.spawnDroppedGroup(_point, _groupDetails, false, _searchRadius);
|
|
|
|
if _groupSide == 1 then
|
|
table.insert(ctld.droppedTroopsRED, _droppedTroops:getName())
|
|
else
|
|
table.insert(ctld.droppedTroopsBLUE, _droppedTroops:getName())
|
|
end
|
|
end
|
|
|
|
|
|
-- Preloads a transport with troops or vehicles
|
|
-- replaces any troops currently on board
|
|
function ctld.preLoadTransport(_unitName, _number, _troops)
|
|
|
|
local _unit = ctld.getTransportUnit(_unitName)
|
|
|
|
if _unit ~= nil then
|
|
|
|
-- will replace any units currently on board
|
|
-- if not ctld.troopsOnboard(_unit,_troops) then
|
|
ctld.loadTroops(_unit, _troops, _number)
|
|
-- end
|
|
end
|
|
end
|
|
|
|
|
|
-- Continuously counts the number of crates in a zone and sets the value of the passed in flag
|
|
-- to the count amount
|
|
-- This means you can trigger actions based on the count and also trigger messages before the count is reached
|
|
-- Just pass in the zone name and flag number like so as a single (NOT Continuous) Trigger
|
|
-- This will now work for Mission Editor and Spawned Crates
|
|
-- e.g. ctld.cratesInZone("DropZone1", 5)
|
|
function ctld.cratesInZone(_zone, _flagNumber)
|
|
local _triggerZone = trigger.misc.getZone(_zone) -- trigger to use as reference position
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zone), 10)
|
|
return
|
|
end
|
|
|
|
local _zonePos = mist.utils.zoneToVec3(_zone)
|
|
|
|
--ignore side, if crate has been used its discounted from the count
|
|
local _crateTables = { ctld.spawnedCratesRED, ctld.spawnedCratesBLUE, ctld.missionEditorCargoCrates }
|
|
|
|
local _crateCount = 0
|
|
|
|
for _, _crates in pairs(_crateTables) do
|
|
|
|
for _crateName, _dontUse in pairs(_crates) do
|
|
|
|
--get crate
|
|
local _crate = ctld.getCrateObject(_crateName)
|
|
|
|
--in air seems buggy with crates so if in air is true, get the height above ground and the speed magnitude
|
|
if _crate ~= nil and _crate:getLife() > 0
|
|
and (ctld.inAir(_crate) == false) then
|
|
|
|
local _dist = ctld.getDistance(_crate:getPoint(), _zonePos)
|
|
|
|
if _dist <= _triggerZone.radius then
|
|
_crateCount = _crateCount + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--set flag stuff
|
|
trigger.action.setUserFlag(_flagNumber, _crateCount)
|
|
|
|
-- env.info("FLAG ".._flagNumber.." crates ".._crateCount)
|
|
|
|
--retrigger in 5 seconds
|
|
timer.scheduleFunction(function(_args)
|
|
|
|
ctld.cratesInZone(_args[1], _args[2])
|
|
end, { _zone, _flagNumber }, timer.getTime() + 5)
|
|
end
|
|
|
|
-- Creates an extraction zone
|
|
-- any Soldiers (not vehicles) dropped at this zone by a helicopter will disappear
|
|
-- and be added to a running total of soldiers for a set flag number
|
|
-- The idea is you can then drop say 20 troops in a zone and trigger an action using the mission editor triggers
|
|
-- and the flag value
|
|
--
|
|
-- The ctld.createExtractZone function needs to be called once in a trigger action do script.
|
|
-- if you dont want smoke, pass -1 to the function.
|
|
--Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4, NO SMOKE = -1
|
|
--
|
|
-- e.g. ctld.createExtractZone("extractzone1", 2, -1) will create an extraction zone at trigger zone "extractzone1", store the number of troops dropped at
|
|
-- the zone in flag 2 and not have smoke
|
|
--
|
|
--
|
|
--
|
|
function ctld.createExtractZone(_zone, _flagNumber, _smoke)
|
|
local _triggerZone = trigger.misc.getZone(_zone) -- trigger to use as reference position
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zone), 10)
|
|
return
|
|
end
|
|
|
|
local _pos2 = { x = _triggerZone.point.x, y = _triggerZone.point.z }
|
|
local _alt = land.getHeight(_pos2)
|
|
local _pos3 = { x = _pos2.x, y = _alt, z = _pos2.y }
|
|
|
|
trigger.action.setUserFlag(_flagNumber, 0) --start at 0
|
|
|
|
local _details = { point = _pos3, name = _zone, smoke = _smoke, flag = _flagNumber, radius = _triggerZone.radius}
|
|
|
|
ctld.extractZones[_zone.."-".._flagNumber] = _details
|
|
|
|
if _smoke ~= nil and _smoke > -1 then
|
|
|
|
local _smokeFunction
|
|
|
|
_smokeFunction = function(_args)
|
|
|
|
local _extractDetails = ctld.extractZones[_zone.."-".._flagNumber]
|
|
-- check zone is still active
|
|
if _extractDetails == nil then
|
|
-- stop refreshing smoke, zone is done
|
|
return
|
|
end
|
|
|
|
|
|
trigger.action.smoke(_args.point, _args.smoke)
|
|
--refresh in 5 minutes
|
|
timer.scheduleFunction(_smokeFunction, _args, timer.getTime() + 300)
|
|
end
|
|
|
|
--run local function
|
|
_smokeFunction(_details)
|
|
end
|
|
end
|
|
|
|
|
|
-- Removes an extraction zone
|
|
--
|
|
-- The smoke will take up to 5 minutes to disappear depending on the last time the smoke was activated
|
|
--
|
|
-- The ctld.removeExtractZone function needs to be called once in a trigger action do script.
|
|
--
|
|
-- e.g. ctld.removeExtractZone("extractzone1", 2) will remove an extraction zone at trigger zone "extractzone1"
|
|
-- that was setup with flag 2
|
|
--
|
|
--
|
|
--
|
|
function ctld.removeExtractZone(_zone,_flagNumber)
|
|
|
|
local _extractDetails = ctld.extractZones[_zone.."-".._flagNumber]
|
|
|
|
if _extractDetails ~= nil then
|
|
--remove zone
|
|
ctld.extractZones[_zone.."-".._flagNumber] = nil
|
|
|
|
end
|
|
end
|
|
|
|
-- CONTINUOUS TRIGGER FUNCTION
|
|
-- This function will count the current number of extractable RED and BLUE
|
|
-- GROUPS in a zone and store the values in two flags
|
|
-- A group is only counted as being in a zone when the leader of that group
|
|
-- is in the zone
|
|
-- Use: ctld.countDroppedGroupsInZone("Zone Name", flagBlue, flagRed)
|
|
function ctld.countDroppedGroupsInZone(_zone, _blueFlag, _redFlag)
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zone) -- trigger to use as reference position
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zone), 10)
|
|
return
|
|
end
|
|
|
|
local _zonePos = mist.utils.zoneToVec3(_zone)
|
|
|
|
local _redCount = 0;
|
|
local _blueCount = 0;
|
|
|
|
local _allGroups = {ctld.droppedTroopsRED,ctld.droppedTroopsBLUE,ctld.droppedVehiclesRED,ctld.droppedVehiclesBLUE}
|
|
for _, _extractGroups in pairs(_allGroups) do
|
|
for _,_groupName in pairs(_extractGroups) do
|
|
local _groupUnits = ctld.getGroup(_groupName)
|
|
|
|
if #_groupUnits > 0 then
|
|
local _zonePos = mist.utils.zoneToVec3(_zone)
|
|
local _dist = ctld.getDistance(_groupUnits[1]:getPoint(), _zonePos)
|
|
|
|
if _dist <= _triggerZone.radius then
|
|
|
|
if (_groupUnits[1]:getCoalition() == 1) then
|
|
_redCount = _redCount + 1;
|
|
else
|
|
_blueCount = _blueCount + 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
--set flag stuff
|
|
trigger.action.setUserFlag(_blueFlag, _blueCount)
|
|
trigger.action.setUserFlag(_redFlag, _redCount)
|
|
|
|
-- env.info("Groups in zone ".._blueCount.." ".._redCount)
|
|
|
|
end
|
|
|
|
-- CONTINUOUS TRIGGER FUNCTION
|
|
-- This function will count the current number of extractable RED and BLUE
|
|
-- UNITS in a zone and store the values in two flags
|
|
|
|
-- Use: ctld.countDroppedUnitsInZone("Zone Name", flagBlue, flagRed)
|
|
function ctld.countDroppedUnitsInZone(_zone, _blueFlag, _redFlag)
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zone) -- trigger to use as reference position
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zone), 10)
|
|
return
|
|
end
|
|
|
|
local _zonePos = mist.utils.zoneToVec3(_zone)
|
|
|
|
local _redCount = 0;
|
|
local _blueCount = 0;
|
|
|
|
local _allGroups = {ctld.droppedTroopsRED,ctld.droppedTroopsBLUE,ctld.droppedVehiclesRED,ctld.droppedVehiclesBLUE}
|
|
|
|
for _, _extractGroups in pairs(_allGroups) do
|
|
for _,_groupName in pairs(_extractGroups) do
|
|
local _groupUnits = ctld.getGroup(_groupName)
|
|
|
|
if #_groupUnits > 0 then
|
|
|
|
local _zonePos = mist.utils.zoneToVec3(_zone)
|
|
for _,_unit in pairs(_groupUnits) do
|
|
local _dist = ctld.getDistance(_unit:getPoint(), _zonePos)
|
|
|
|
if _dist <= _triggerZone.radius then
|
|
|
|
if (_unit:getCoalition() == 1) then
|
|
_redCount = _redCount + 1;
|
|
else
|
|
_blueCount = _blueCount + 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
--set flag stuff
|
|
trigger.action.setUserFlag(_blueFlag, _blueCount)
|
|
trigger.action.setUserFlag(_redFlag, _redCount)
|
|
|
|
-- env.info("Units in zone ".._blueCount.." ".._redCount)
|
|
end
|
|
|
|
|
|
-- Creates a radio beacon on a random UHF - VHF and HF/FM frequency for homing
|
|
-- This WILL NOT WORK if you dont add beacon.ogg and beaconsilent.ogg to the mission!!!
|
|
-- e.g. ctld.createRadioBeaconAtZone("beaconZone","red", 1440,"Waypoint 1") will create a beacon at trigger zone "beaconZone" for the Red side
|
|
-- that will last 1440 minutes (24 hours ) and named "Waypoint 1" in the list of radio beacons
|
|
--
|
|
-- e.g. ctld.createRadioBeaconAtZone("beaconZoneBlue","blue", 20) will create a beacon at trigger zone "beaconZoneBlue" for the Blue side
|
|
-- that will last 20 minutes
|
|
function ctld.createRadioBeaconAtZone(_zone, _coalition, _batteryLife, _name)
|
|
local _triggerZone = trigger.misc.getZone(_zone) -- trigger to use as reference position
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zone), 10)
|
|
return
|
|
end
|
|
|
|
local _zonePos = mist.utils.zoneToVec3(_zone)
|
|
|
|
ctld.beaconCount = ctld.beaconCount + 1
|
|
|
|
if _name == nil or _name == "" then
|
|
_name = "Beacon #" .. ctld.beaconCount
|
|
end
|
|
|
|
if _coalition == "red" then
|
|
ctld.createRadioBeacon(_zonePos, 1, 0, _name, _batteryLife) --1440
|
|
else
|
|
ctld.createRadioBeacon(_zonePos, 2, 2, _name, _batteryLife) --1440
|
|
end
|
|
end
|
|
|
|
|
|
-- Activates a pickup zone
|
|
-- Activates a pickup zone when called from a trigger
|
|
-- EG: ctld.activatePickupZone("pickzone3")
|
|
-- This is enable pickzone3 to be used as a pickup zone for the team set
|
|
function ctld.activatePickupZone(_zoneName)
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zoneName) -- trigger to use as reference position
|
|
|
|
if _triggerZone == nil then
|
|
local _ship = ctld.getTransportUnit(_triggerZone)
|
|
|
|
if _ship then
|
|
local _point = _ship:getPoint()
|
|
_triggerZone = {}
|
|
_triggerZone.point = _point
|
|
end
|
|
|
|
end
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone or ship called %1", _zoneName), 10)
|
|
end
|
|
|
|
for _, _zoneDetails in pairs(ctld.pickupZones) do
|
|
|
|
if _zoneName == _zoneDetails[1] then
|
|
|
|
--smoke could get messy if designer keeps calling this on an active zone, check its not active first
|
|
if _zoneDetails[4] == 1 then
|
|
-- they might have a continuous trigger so i've hidden the warning
|
|
return
|
|
end
|
|
|
|
_zoneDetails[4] = 1 --activate zone
|
|
|
|
if ctld.disableAllSmoke == true then --smoke disabled
|
|
return
|
|
end
|
|
|
|
if _zoneDetails[2] >= 0 then
|
|
|
|
-- Trigger smoke marker
|
|
-- This will cause an overlapping smoke marker on next refreshsmoke call
|
|
-- but will only happen once
|
|
local _pos2 = { x = _triggerZone.point.x, y = _triggerZone.point.z }
|
|
local _alt = land.getHeight(_pos2)
|
|
local _pos3 = { x = _pos2.x, y = _alt, z = _pos2.y }
|
|
|
|
trigger.action.smoke(_pos3, _zoneDetails[2])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- Deactivates a pickup zone
|
|
-- Deactivates a pickup zone when called from a trigger
|
|
-- EG: ctld.deactivatePickupZone("pickzone3")
|
|
-- This is disables pickzone3 and can no longer be used to as a pickup zone
|
|
-- These functions can be called by triggers, like if a set of buildings is used, you can trigger the zone to be 'not operational'
|
|
-- once they are destroyed
|
|
function ctld.deactivatePickupZone(_zoneName)
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zoneName) -- trigger to use as reference position
|
|
|
|
if _triggerZone == nil then
|
|
local _ship = ctld.getTransportUnit(_triggerZone)
|
|
|
|
if _ship then
|
|
local _point = _ship:getPoint()
|
|
_triggerZone = {}
|
|
_triggerZone.point = _point
|
|
end
|
|
|
|
end
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zoneName), 10)
|
|
return
|
|
end
|
|
|
|
for _, _zoneDetails in pairs(ctld.pickupZones) do
|
|
|
|
if _zoneName == _zoneDetails[1] then
|
|
-- i'd just ignore it if its already been deactivated
|
|
_zoneDetails[4] = 0 --deactivate zone
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Change the remaining groups currently available for pickup at a zone
|
|
-- e.g. ctld.changeRemainingGroupsForPickupZone("pickup1", 5) -- adds 5 groups
|
|
-- ctld.changeRemainingGroupsForPickupZone("pickup1", -3) -- remove 3 groups
|
|
function ctld.changeRemainingGroupsForPickupZone(_zoneName, _amount)
|
|
local _triggerZone = trigger.misc.getZone(_zoneName) -- trigger to use as reference position
|
|
|
|
if _triggerZone == nil then
|
|
local _ship = ctld.getTransportUnit(_triggerZone)
|
|
|
|
if _ship then
|
|
local _point = _ship:getPoint()
|
|
_triggerZone = {}
|
|
_triggerZone.point = _point
|
|
end
|
|
|
|
end
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zoneName), 10)
|
|
return
|
|
end
|
|
|
|
for _, _zoneDetails in pairs(ctld.pickupZones) do
|
|
|
|
if _zoneName == _zoneDetails[1] then
|
|
ctld.updateZoneCounter(_zoneName, _amount)
|
|
end
|
|
end
|
|
|
|
|
|
end
|
|
|
|
-- Activates a Waypoint zone
|
|
-- Activates a Waypoint zone when called from a trigger
|
|
-- EG: ctld.activateWaypointZone("pickzone3")
|
|
-- This means that troops dropped within the radius of the zone will head to the center
|
|
-- of the zone instead of searching for troops
|
|
function ctld.activateWaypointZone(_zoneName)
|
|
local _triggerZone = trigger.misc.getZone(_zoneName) -- trigger to use as reference position
|
|
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zoneName), 10)
|
|
|
|
return
|
|
end
|
|
|
|
for _, _zoneDetails in pairs(ctld.wpZones) do
|
|
|
|
if _zoneName == _zoneDetails[1] then
|
|
|
|
--smoke could get messy if designer keeps calling this on an active zone, check its not active first
|
|
if _zoneDetails[3] == 1 then
|
|
-- they might have a continuous trigger so i've hidden the warning
|
|
return
|
|
end
|
|
|
|
_zoneDetails[3] = 1 --activate zone
|
|
|
|
if ctld.disableAllSmoke == true then --smoke disabled
|
|
return
|
|
end
|
|
|
|
if _zoneDetails[2] >= 0 then
|
|
|
|
-- Trigger smoke marker
|
|
-- This will cause an overlapping smoke marker on next refreshsmoke call
|
|
-- but will only happen once
|
|
local _pos2 = { x = _triggerZone.point.x, y = _triggerZone.point.z }
|
|
local _alt = land.getHeight(_pos2)
|
|
local _pos3 = { x = _pos2.x, y = _alt, z = _pos2.y }
|
|
|
|
trigger.action.smoke(_pos3, _zoneDetails[2])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- Deactivates a Waypoint zone
|
|
-- Deactivates a Waypoint zone when called from a trigger
|
|
-- EG: ctld.deactivateWaypointZone("wpzone3")
|
|
-- This disables wpzone3 so that troops dropped in this zone will search for troops as normal
|
|
-- These functions can be called by triggers
|
|
function ctld.deactivateWaypointZone(_zoneName)
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zoneName)
|
|
|
|
if _triggerZone == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zoneName), 10)
|
|
return
|
|
end
|
|
|
|
for _, _zoneDetails in pairs(ctld.pickupZones) do
|
|
|
|
if _zoneName == _zoneDetails[1] then
|
|
|
|
_zoneDetails[3] = 0 --deactivate zone
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Continuous Trigger Function
|
|
-- Causes an AI unit with the specified name to unload troops / vehicles when
|
|
-- an enemy is detected within a specified distance
|
|
-- The enemy must have Line or Sight to the unit to be detected
|
|
function ctld.unloadInProximityToEnemy(_unitName,_distance)
|
|
|
|
local _unit = ctld.getTransportUnit(_unitName)
|
|
|
|
if _unit ~= nil and _unit:getPlayerName() == nil then
|
|
|
|
-- no player name means AI!
|
|
-- the findNearest visible enemy you'd want to modify as it'll find enemies quite far away
|
|
-- limited by ctld.JTAC_maxDistance
|
|
local _nearestEnemy = ctld.findNearestVisibleEnemy(_unit,"all",_distance)
|
|
|
|
if _nearestEnemy ~= nil then
|
|
|
|
if ctld.troopsOnboard(_unit, true) then
|
|
ctld.deployTroops(_unit, true)
|
|
return true
|
|
end
|
|
|
|
if ctld.unitCanCarryVehicles(_unit) and ctld.troopsOnboard(_unit, false) then
|
|
ctld.deployTroops(_unit, false)
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Unit will unload any units onboard if the unit is on the ground
|
|
-- when this function is called
|
|
function ctld.unloadTransport(_unitName)
|
|
|
|
local _unit = ctld.getTransportUnit(_unitName)
|
|
|
|
if _unit ~= nil then
|
|
|
|
if ctld.troopsOnboard(_unit, true) then
|
|
ctld.unloadTroops({_unitName,true})
|
|
end
|
|
|
|
if ctld.unitCanCarryVehicles(_unit) and ctld.troopsOnboard(_unit, false) then
|
|
ctld.unloadTroops({_unitName,false})
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-- Loads Troops and Vehicles from a zone or picks up nearby troops or vehicles
|
|
function ctld.loadTransport(_unitName)
|
|
|
|
local _unit = ctld.getTransportUnit(_unitName)
|
|
|
|
if _unit ~= nil then
|
|
|
|
ctld.loadTroopsFromZone({ _unitName, true,"",true })
|
|
|
|
if ctld.unitCanCarryVehicles(_unit) then
|
|
ctld.loadTroopsFromZone({ _unitName, false,"",true })
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- adds a callback that will be called for many actions ingame
|
|
function ctld.addCallback(_callback)
|
|
|
|
table.insert(ctld.callbacks,_callback)
|
|
|
|
end
|
|
|
|
-- Spawns a sling loadable crate at a Trigger Zone
|
|
--
|
|
-- Weights can be found in the ctld.spawnableCrates list
|
|
-- e.g. ctld.spawnCrateAtZone("red", 500,"triggerzone1") -- spawn a humvee at triggerzone 1 for red side
|
|
-- e.g. ctld.spawnCrateAtZone("blue", 505,"triggerzone1") -- spawn a tow humvee at triggerzone1 for blue side
|
|
--
|
|
function ctld.spawnCrateAtZone(_side, _weight,_zone)
|
|
local _spawnTrigger = trigger.misc.getZone(_zone) -- trigger to use as reference position
|
|
|
|
if _spawnTrigger == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find zone called %1", _zone), 10)
|
|
return
|
|
end
|
|
|
|
local _crateType = ctld.crateLookupTable[tostring(_weight)]
|
|
|
|
if _crateType == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find crate with weight %1", _weight), 10)
|
|
return
|
|
end
|
|
|
|
local _country
|
|
if _side == "red" then
|
|
_side = 1
|
|
_country = 0
|
|
else
|
|
_side = 2
|
|
_country = 2
|
|
end
|
|
|
|
local _pos2 = { x = _spawnTrigger.point.x, y = _spawnTrigger.point.z }
|
|
local _alt = land.getHeight(_pos2)
|
|
local _point = { x = _pos2.x, y = _alt, z = _pos2.y }
|
|
|
|
local _unitId = ctld.getNextUnitId()
|
|
|
|
local _name = string.format("%s #%i", _crateType.desc, _unitId)
|
|
|
|
ctld.spawnCrateStatic(_country, _unitId, _point, _name, _crateType.weight, _side)
|
|
|
|
end
|
|
|
|
-- Spawns a sling loadable crate at a Point
|
|
--
|
|
-- Weights can be found in the ctld.spawnableCrates list
|
|
-- Points can be made by hand or obtained from a Unit position by Unit.getByName("PilotName"):getPoint()
|
|
-- e.g. ctld.spawnCrateAtZone("red", 500,{x=1,y=2,z=3}) -- spawn a humvee at triggerzone 1 for red side at a specified point
|
|
-- e.g. ctld.spawnCrateAtZone("blue", 505,{x=1,y=2,z=3}) -- spawn a tow humvee at triggerzone1 for blue side at a specified point
|
|
--
|
|
--
|
|
function ctld.spawnCrateAtPoint(_side, _weight, _point,_hdg)
|
|
|
|
|
|
local _crateType = ctld.crateLookupTable[tostring(_weight)]
|
|
|
|
if _crateType == nil then
|
|
trigger.action.outText(ctld.i18n_translate("CTLD.lua ERROR: Can't find crate with weight %1", _weight), 10)
|
|
return
|
|
end
|
|
|
|
local _country
|
|
if _side == "red" then
|
|
_side = 1
|
|
_country = 0
|
|
else
|
|
_side = 2
|
|
_country = 2
|
|
end
|
|
|
|
local _unitId = ctld.getNextUnitId()
|
|
|
|
local _name = string.format("%s #%i", _crateType.desc, _unitId)
|
|
|
|
ctld.spawnCrateStatic(_country, _unitId, _point, _name, _crateType.weight, _side, _hdg)
|
|
|
|
end
|
|
|
|
-- ***************************************************************
|
|
-- **************** BE CAREFUL BELOW HERE ************************
|
|
-- ***************************************************************
|
|
|
|
--- Tells CTLD What multipart AA Systems there are and what parts they need
|
|
-- A New system added here also needs the launcher added
|
|
-- The number of times that each part is spawned for each system is specified by the entry "amount", NOTE : they will be spawned in a circle with the corresponding headings, NOTE 2 : launchers will use the default ctld.aaLauncher amount if nothing is specified
|
|
-- If a component does not require a crate, it can be specified via the entry "NoCrate" set to true
|
|
ctld.AASystemTemplate = {
|
|
|
|
{
|
|
name = "HAWK AA System",
|
|
count = 5,
|
|
parts = {
|
|
{name = "Hawk ln", desc = "HAWK Launcher", launcher = true, amount = 5 },
|
|
{name = "Hawk tr", desc = "HAWK Track Radar"},
|
|
{name = "Hawk sr", desc = "HAWK Search Radar"},
|
|
{name = "Hawk pcp", desc = "HAWK PCP"},
|
|
{name = "Hawk cwar", desc = "HAWK CWAR"},
|
|
},
|
|
repair = "HAWK Repair",
|
|
},
|
|
{
|
|
name = "Patriot AA System",
|
|
count = 3,
|
|
parts = {
|
|
{name = "Patriot ln", desc = "Patriot Launcher", launcher = true, amount = 4},
|
|
{name = "Patriot ECS", desc = "Patriot Control Unit"},
|
|
{name = "Patriot str", desc = "Patriot Search and Track Radar"},
|
|
--{name = "Patriot cp", desc = "Patriot ICC"},
|
|
--{name = "Patriot EPP", desc = "Patriot EPP"},
|
|
--{name = "Patriot AMG", desc = "Patriot AMG DL relay"},
|
|
},
|
|
repair = "Patriot Repair",
|
|
},
|
|
{
|
|
name = "NASAMS AA System",
|
|
count = 3,
|
|
parts = {
|
|
{name = "NASAMS_LN_C", desc = "NASAMS Launcher 120C", launcher = true, amount = 4},
|
|
{name = "NASAMS_Radar_MPQ64F1", desc = "NASAMS Search/Track Radar"},
|
|
{name = "NASAMS_Command_Post", desc = "NASAMS Command Post"},
|
|
},
|
|
repair = "NASAMS Repair",
|
|
},
|
|
{
|
|
name = "BUK AA System",
|
|
count = 3,
|
|
parts = {
|
|
{name = "SA-11 Buk LN 9A310M1", desc = "BUK Launcher" , launcher = true, amount = 3},
|
|
{name = "SA-11 Buk CC 9S470M1", desc = "BUK CC Radar"},
|
|
{name = "SA-11 Buk SR 9S18M1", desc = "BUK Search Radar"},
|
|
},
|
|
repair = "BUK Repair",
|
|
},
|
|
{
|
|
name = "KUB AA System",
|
|
count = 2,
|
|
parts = {
|
|
{name = "Kub 2P25 ln", desc = "KUB Launcher", launcher = true, amount = 3},
|
|
{name = "Kub 1S91 str", desc = "KUB Radar"},
|
|
},
|
|
repair = "KUB Repair",
|
|
},
|
|
--{
|
|
-- name = "S-300 AA System",
|
|
-- count = 6,
|
|
-- parts = {
|
|
-- { desc = "S-300 Grumble TEL C", name = "S-300PS 5P85C ln", launcher = true, amount = 1 },
|
|
-- { desc = "S-300 Grumble TEL D", name = "S-300PS 5P85D ln", NoCrate = true, amount = 2 },
|
|
-- { desc = "S-300 Grumble Flap Lid-A TR", name = "S-300PS 40B6M tr"},
|
|
-- { desc = "S-300 Grumble Clam Shell SR", name = "S-300PS 40B6MD sr"},
|
|
-- { desc = "S-300 Grumble Big Bird SR", name = "S-300PS 64H6E sr"},
|
|
-- { desc = "S-300 Grumble C2", name = "S-300PS 54K6 cp"},
|
|
-- },
|
|
-- repair = "S-300 Repair",
|
|
--},
|
|
{
|
|
name = "SA-2 AA System",
|
|
count = 4,
|
|
parts = {
|
|
{ desc = "S_75M_Volhov", name = "SA-2 Guidline Launcher", launcher = true, amount = 4 },
|
|
{ desc = "p-19 s-125 sr", name = "SA-2 Flat Face SR"},
|
|
{ desc = "SNR_75V", name = "SA-2 Fan Song TR"},
|
|
{ desc = "RD_75", name = "SA-2 CP"},
|
|
},
|
|
repair = "SA-2 Repair",
|
|
},
|
|
{
|
|
name = "HQ-7 System",
|
|
count = 2,
|
|
parts = {
|
|
{ desc = "HQ-7_Launcher", name = "HQ-7_LN_SP", launcher = true, amount = 2 },
|
|
{ desc = "HQ-7_STR_SP", name = "HQ-7_STR_SP"},
|
|
},
|
|
repair = "HQ-7 Repair",
|
|
},
|
|
{
|
|
name = "Rapier AA System",
|
|
count = 3,
|
|
parts = {
|
|
{name = "rapier_fsa_launcher", desc = "Rapier Launcher", launcher = true},
|
|
{name = "rapier_fsa_optical_tracker_unit", desc = "Rapier Tracker"},
|
|
{name = "rapier_fsa_blindfire_radar", desc = "Rapier SR"},
|
|
},
|
|
repair = "Rapier Repair",
|
|
},
|
|
}
|
|
|
|
ctld.crateWait = {}
|
|
ctld.crateMove = {}
|
|
|
|
---------------- INTERNAL FUNCTIONS ----------------
|
|
---
|
|
---
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Utility methods
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- print an object for a debugging log
|
|
function ctld.p(o, level)
|
|
local MAX_LEVEL = 20
|
|
if level == nil then level = 0 end
|
|
if level > MAX_LEVEL then
|
|
ctld.logError("max depth reached in ctld.p : "..tostring(MAX_LEVEL))
|
|
return ""
|
|
end
|
|
local text = ""
|
|
if (type(o) == "table") then
|
|
text = "\n"
|
|
for key,value in pairs(o) do
|
|
for i=0, level do
|
|
text = text .. " "
|
|
end
|
|
text = text .. ".".. key.."="..ctld.p(value, level+1) .. "\n"
|
|
end
|
|
elseif (type(o) == "function") then
|
|
text = "[function]"
|
|
elseif (type(o) == "boolean") then
|
|
if o == true then
|
|
text = "[true]"
|
|
else
|
|
text = "[false]"
|
|
end
|
|
else
|
|
if o == nil then
|
|
text = "[nil]"
|
|
else
|
|
text = tostring(o)
|
|
end
|
|
end
|
|
return text
|
|
end
|
|
|
|
function ctld.formatText(text, ...)
|
|
if not text then
|
|
return ""
|
|
end
|
|
if type(text) ~= 'string' then
|
|
text = ctld.p(text)
|
|
else
|
|
local args = ...
|
|
if args and args.n and args.n > 0 then
|
|
local pArgs = {}
|
|
for i=1,args.n do
|
|
pArgs[i] = ctld.p(args[i])
|
|
end
|
|
text = text:format(unpack(pArgs))
|
|
end
|
|
end
|
|
local fName = nil
|
|
local cLine = nil
|
|
if debug and debug.getinfo then
|
|
local dInfo = debug.getinfo(3)
|
|
fName = dInfo.name
|
|
cLine = dInfo.currentline
|
|
end
|
|
if fName and cLine then
|
|
return fName .. '|' .. cLine .. ': ' .. text
|
|
elseif cLine then
|
|
return cLine .. ': ' .. text
|
|
else
|
|
return ' ' .. text
|
|
end
|
|
end
|
|
|
|
function ctld.logError(message, ...)
|
|
message = ctld.formatText(message, arg)
|
|
env.info(" E - " .. ctld.Id .. message)
|
|
end
|
|
|
|
|
|
function ctld.logWarning(message, ...)
|
|
message = ctld.formatText(message, arg)
|
|
env.info(" W - " .. ctld.Id .. message)
|
|
end
|
|
|
|
function ctld.logInfo(message, ...)
|
|
message = ctld.formatText(message, arg)
|
|
env.info(" I - " .. ctld.Id .. message)
|
|
end
|
|
|
|
function ctld.logDebug(message, ...)
|
|
if message and ctld.Debug then
|
|
message = ctld.formatText(message, arg)
|
|
env.info(" D - " .. ctld.Id .. message)
|
|
end
|
|
end
|
|
|
|
function ctld.logTrace(message, ...)
|
|
if message and ctld.Trace then
|
|
message = ctld.formatText(message, arg)
|
|
env.info(" T - " .. ctld.Id .. message)
|
|
end
|
|
end
|
|
|
|
ctld.nextUnitId = 1;
|
|
ctld.getNextUnitId = function()
|
|
ctld.nextUnitId = ctld.nextUnitId + 1
|
|
|
|
return ctld.nextUnitId
|
|
end
|
|
|
|
ctld.nextGroupId = 1;
|
|
|
|
ctld.getNextGroupId = function()
|
|
ctld.nextGroupId = ctld.nextGroupId + 1
|
|
|
|
return ctld.nextGroupId
|
|
end
|
|
|
|
function ctld.getTransportUnit(_unitName)
|
|
|
|
if _unitName == nil then
|
|
return nil
|
|
end
|
|
|
|
local _heli = Unit.getByName(_unitName)
|
|
|
|
if _heli ~= nil and _heli:isActive() and _heli:getLife() > 0 then
|
|
|
|
return _heli
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function ctld.spawnCrateStatic(_country, _unitId, _point, _name, _weight, _side, _hdg, _model_type)
|
|
|
|
local _crate
|
|
local _spawnedCrate
|
|
|
|
local hdg = _hdg or 0
|
|
|
|
if ctld.staticBugWorkaround and ctld.slingLoad == false then
|
|
local _groupId = ctld.getNextGroupId()
|
|
local _groupName = "Crate Group #".._groupId
|
|
|
|
local _group = {
|
|
["visible"] = false,
|
|
-- ["groupId"] = _groupId,
|
|
["hidden"] = false,
|
|
["units"] = {},
|
|
-- ["y"] = _positions[1].z,
|
|
-- ["x"] = _positions[1].x,
|
|
["name"] = _groupName,
|
|
["task"] = {},
|
|
}
|
|
|
|
_group.units[1] = ctld.createUnit(_point.x , _point.z , hdg, {type="UAZ-469",name=_name,unitId=_unitId})
|
|
|
|
--switch to MIST
|
|
_group.category = Group.Category.GROUND;
|
|
_group.country = _country;
|
|
|
|
local _spawnedGroup = Group.getByName(mist.dynAdd(_group).name)
|
|
|
|
-- Turn off AI
|
|
trigger.action.setGroupAIOff(_spawnedGroup)
|
|
|
|
_spawnedCrate = Unit.getByName(_name)
|
|
else
|
|
|
|
if _model_type ~= nil then
|
|
_crate = mist.utils.deepCopy(ctld.spawnableCratesModels[_model_type])
|
|
elseif ctld.slingLoad then
|
|
_crate = mist.utils.deepCopy(ctld.spawnableCratesModels["sling"])
|
|
else
|
|
_crate = mist.utils.deepCopy(ctld.spawnableCratesModels["load"])
|
|
end
|
|
|
|
_crate["y"] = _point.z
|
|
_crate["x"] = _point.x
|
|
_crate["mass"] = _weight
|
|
_crate["name"] = _name
|
|
_crate["heading"] = hdg
|
|
_crate["country"] = _country
|
|
|
|
mist.dynAddStatic(_crate)
|
|
|
|
_spawnedCrate = StaticObject.getByName(_crate["name"])
|
|
end
|
|
|
|
|
|
local _crateType = ctld.crateLookupTable[tostring(_weight)]
|
|
|
|
if _side == 1 then
|
|
ctld.spawnedCratesRED[_name] =_crateType
|
|
else
|
|
ctld.spawnedCratesBLUE[_name] = _crateType
|
|
end
|
|
|
|
return _spawnedCrate
|
|
end
|
|
|
|
function ctld.spawnFOBCrateStatic(_country, _unitId, _point, _name)
|
|
|
|
local _crate = {
|
|
["category"] = "Fortifications",
|
|
["shape_name"] = "konteiner_red1",
|
|
["type"] = "Container red 1",
|
|
-- ["unitId"] = _unitId,
|
|
["y"] = _point.z,
|
|
["x"] = _point.x,
|
|
["name"] = _name,
|
|
["canCargo"] = false,
|
|
["heading"] = 0,
|
|
}
|
|
|
|
_crate["country"] = _country
|
|
|
|
mist.dynAddStatic(_crate)
|
|
|
|
local _spawnedCrate = StaticObject.getByName(_crate["name"])
|
|
--local _spawnedCrate = coalition.addStaticObject(_country, _crate)
|
|
|
|
return _spawnedCrate
|
|
end
|
|
|
|
|
|
function ctld.spawnFOB(_country, _unitId, _point, _name)
|
|
|
|
local _id = ctld.getNextUnitId()
|
|
local _crate = {
|
|
["category"] = "Heliports",
|
|
["shape_name"] = "FARP",
|
|
["type"] = "FARP",
|
|
-- ["unitId"] = _unitId,
|
|
["rate"] = 100,
|
|
["y"] = _point.z,
|
|
["x"] = _point.x + 30.00,
|
|
["name"] = _name,
|
|
["canCargo"] = true,
|
|
["heading"] = 0,
|
|
}
|
|
|
|
_crate["country"] = _country
|
|
mist.dynAddStatic(_crate)
|
|
--local _spawnedCrate = StaticObject.getByName(_crate["name"])
|
|
local _spawnedCrate = coalition.addStaticObject(_country, _crate)
|
|
|
|
local _id = ctld.getNextUnitId()
|
|
local _crate = {
|
|
["type"] = "FARP Tent",
|
|
["shape_name"] = "PalatkaB",
|
|
-- ["unitId"] = _id,
|
|
["rate"] = 150,
|
|
["y"] = _point.z,
|
|
["x"] = _point.x + -10.00,
|
|
["name"] = "FARP Tent #" .. _id,
|
|
["category"] = "Structures",
|
|
["canCargo"] = false,
|
|
["heading"] = 0,
|
|
}
|
|
|
|
_crate["country"] = _country
|
|
mist.dynAddStatic(_crate)
|
|
local _spawnedCrate = StaticObject.getByName(_crate["name"])
|
|
--local _spawnedCrate = coalition.addStaticObject(_country, _crate)
|
|
|
|
local _id = ctld.getNextUnitId()
|
|
local _crate = {
|
|
["type"] = "M 818",
|
|
["shape_name"] = "M 818",
|
|
-- ["unitId"] = _id,
|
|
["rate"] = 200,
|
|
["y"] = _point.z + 16.00,
|
|
["x"] = _point.x + -5.00,
|
|
["name"] = "M 818" .. _id,
|
|
["category"] = "Unarmed",
|
|
["canCargo"] = false,
|
|
["heading"] = 0,
|
|
}
|
|
|
|
_crate["country"] = _country
|
|
mist.dynAddStatic(_crate)
|
|
local _spawnedCrate = StaticObject.getByName(_crate["name"])
|
|
--local _spawnedCrate = coalition.addStaticObject(_country, _crate)
|
|
|
|
local _id = ctld.getNextUnitId()
|
|
local _crate = {
|
|
["type"] = "FARP Fuel Depot",
|
|
["shape_name"] = "GSM Rus",
|
|
-- ["unitId"] = _id,
|
|
["rate"] = 250,
|
|
["y"] = _point.z + 10.00,
|
|
["x"] = _point.x + -2.00,
|
|
["name"] = "FARP Fuel Depot #" .. _id,
|
|
["category"] = "Structures",
|
|
["canCargo"] = false,
|
|
["heading"] = 0,
|
|
}
|
|
|
|
_crate["country"] = _country
|
|
mist.dynAddStatic(_crate)
|
|
local _spawnedCrate = StaticObject.getByName(_crate["name"])
|
|
--local _spawnedCrate = coalition.addStaticObject(_country, _crate)
|
|
|
|
local _id = ctld.getNextUnitId()
|
|
local _crate = {
|
|
["type"] = "FARP Ammo Dump Coating",
|
|
["shape_name"] = "SetkaKP",
|
|
-- ["unitId"] = _id,
|
|
["rate"] = 300,
|
|
["y"] = _point.z + -10.00,
|
|
["x"] = _point.x + -5.00,
|
|
["name"] = "FARP Ammo Dump Coating #" .. _id,
|
|
["category"] = "Structures",
|
|
["canCargo"] = true,
|
|
["heading"] = 0,
|
|
}
|
|
_crate["country"] = _country
|
|
mist.dynAddStatic(_crate)
|
|
local _spawnedCrate = StaticObject.getByName(_crate["name"])
|
|
--local _spawnedCrate = coalition.addStaticObject(_country, _crate)
|
|
|
|
local _id = ctld.getNextUnitId()
|
|
local _tower = {
|
|
["type"] = "Container_watchtower_lights",
|
|
["shape_name"] = "Container_watchtower_lights",
|
|
-- ["unitId"] = _id,
|
|
["rate"] = 350,
|
|
["y"] = _point.z + 20.00,
|
|
["x"] = _point.x + -5.00,
|
|
["name"] = "Container_watchtower_lights #" .. _id,
|
|
["category"] = "Structures",
|
|
["canCargo"] = false,
|
|
["heading"] = 0,
|
|
}
|
|
|
|
--coalition.addStaticObject(_country, _tower)
|
|
_tower["country"] = _country
|
|
|
|
mist.dynAddStatic(_tower)
|
|
|
|
|
|
local _id = ctld.getNextUnitId()
|
|
local _tower = {
|
|
["type"] = "Container_watchtower_lights",
|
|
["shape_name"] = "Container_watchtower_lights",
|
|
-- ["unitId"] = _id,
|
|
["rate"] = 400,
|
|
["y"] = _point.z + -20.00,
|
|
["x"] = _point.x + -5.00,
|
|
["name"] = "Container_watchtower_lights #" .. _id,
|
|
["category"] = "Structures",
|
|
["canCargo"] = false,
|
|
["heading"] = 0,
|
|
}
|
|
|
|
--coalition.addStaticObject(_country, _tower)
|
|
_tower["country"] = _country
|
|
|
|
mist.dynAddStatic(_tower)
|
|
|
|
return _spawnedCrate
|
|
end
|
|
|
|
|
|
function ctld.spawnCrate(_arguments, bypassCrateWaitTime)
|
|
|
|
local _status, _err = pcall(function(_args)
|
|
|
|
-- use the cargo weight to guess the type of unit as no way to add description :(
|
|
local _crateType = ctld.crateLookupTable[tostring(_args[2])]
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
if not _heli then
|
|
return
|
|
end
|
|
|
|
-- check crate spam
|
|
if not(bypassCrateWaitTime) and _heli:getPlayerName() ~= nil and ctld.crateWait[_heli:getPlayerName()] and ctld.crateWait[_heli:getPlayerName()] > timer.getTime() then
|
|
ctld.displayMessageToGroup(_heli,ctld.i18n_translate("Sorry you must wait %1 seconds before you can get another crate", (ctld.crateWait[_heli:getPlayerName()] - timer.getTime())), 20)
|
|
return
|
|
end
|
|
|
|
if _crateType and _crateType.multiple then
|
|
for _, weight in pairs(_crateType.multiple) do
|
|
local _aCrateType = ctld.crateLookupTable[tostring(weight)]
|
|
if _aCrateType then
|
|
ctld.spawnCrate({_args[1], _aCrateType.weight}, true)
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
if _crateType ~= nil and _heli ~= nil and ctld.inAir(_heli) == false then
|
|
|
|
if ctld.inLogisticsZone(_heli) == false then
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You are not close enough to friendly logistics to get a crate!"), 10)
|
|
|
|
return
|
|
end
|
|
|
|
if ctld.isJTACUnitType(_crateType.unit) then
|
|
|
|
local _limitHit = false
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
|
|
if ctld.JTAC_LIMIT_RED == 0 then
|
|
_limitHit = true
|
|
else
|
|
ctld.JTAC_LIMIT_RED = ctld.JTAC_LIMIT_RED - 1
|
|
end
|
|
else
|
|
if ctld.JTAC_LIMIT_BLUE == 0 then
|
|
_limitHit = true
|
|
else
|
|
ctld.JTAC_LIMIT_BLUE = ctld.JTAC_LIMIT_BLUE - 1
|
|
end
|
|
end
|
|
|
|
if _limitHit then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("No more JTAC Crates Left!"), 10)
|
|
return
|
|
end
|
|
end
|
|
|
|
if _heli:getPlayerName() ~= nil then
|
|
ctld.crateWait[_heli:getPlayerName()] = timer.getTime() + ctld.crateWaitTime
|
|
end
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
|
|
local _model_type = nil
|
|
|
|
local _point = ctld.getPointAt12Oclock(_heli, 30)
|
|
local _position = "12"
|
|
|
|
if ctld.unitDynamicCargoCapable(_heli) then
|
|
_model_type = "dynamic"
|
|
_point = ctld.getPointAt12Oclock(_heli, 30)
|
|
_position = "12"
|
|
end
|
|
|
|
local _unitId = ctld.getNextUnitId()
|
|
|
|
local _side = _heli:getCoalition()
|
|
|
|
local _name = string.format("%s #%i", _crateType.desc, _unitId)
|
|
|
|
ctld.spawnCrateStatic(_heli:getCountry(), _unitId, _point, _name, _crateType.weight, _side, 0, _model_type)
|
|
|
|
-- add to move table
|
|
ctld.crateMove[_name] = _name
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("A %1 crate weighing %2 kg has been brought out and is at your %3 o'clock ", _crateType.desc, _crateType.weight, _position), 20)
|
|
else
|
|
env.info("Couldn't find crate item to spawn")
|
|
end
|
|
end, _arguments)
|
|
|
|
if (not _status) then
|
|
env.error(string.format("CTLD ERROR: %s", _err))
|
|
end
|
|
end
|
|
|
|
ctld.randomCrateSpacing = 0 -- meters
|
|
function ctld.getPointAt12Oclock(_unit, _offset)
|
|
return ctld.getPointAtDirection(_unit, _offset, 0)
|
|
end
|
|
|
|
function ctld.getPointAt6Oclock(_unit, _offset)
|
|
return ctld.getPointAtDirection(_unit, _offset, math.pi)
|
|
end
|
|
|
|
function ctld.getPointAtDirection(_unit, _offset, _directionInRadian)
|
|
|
|
ctld.logTrace("_offset = %s", ctld.p(_offset))
|
|
local _randomOffsetX = math.random(ctld.randomCrateSpacing * 0 ) - ctld.randomCrateSpacing
|
|
local _randomOffsetZ = math.random(0, ctld.randomCrateSpacing)
|
|
ctld.logTrace("_randomOffsetX = %s", ctld.p(_randomOffsetX))
|
|
ctld.logTrace("_randomOffsetZ = %s", ctld.p(_randomOffsetZ))
|
|
local _position = _unit:getPosition()
|
|
local _angle = math.atan2(_position.x.z, _position.x.x) + _directionInRadian
|
|
local _xOffset = math.cos(_angle) * _offset + _randomOffsetX
|
|
local _zOffset = math.sin(_angle) * _offset + _randomOffsetZ
|
|
|
|
local _point = _unit:getPoint()
|
|
return { x = _point.x + _xOffset, z = _point.z + _zOffset, y = _point.y }
|
|
end
|
|
|
|
function ctld.troopsOnboard(_heli, _troops)
|
|
|
|
if ctld.inTransitTroops[_heli:getName()] ~= nil then
|
|
|
|
local _onboard = ctld.inTransitTroops[_heli:getName()]
|
|
|
|
if _troops then
|
|
|
|
if _onboard.troops ~= nil and _onboard.troops.units ~= nil and #_onboard.troops.units > 0 then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
else
|
|
|
|
if _onboard.vehicles ~= nil and _onboard.vehicles.units ~= nil and #_onboard.vehicles.units > 0 then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- if its dropped by AI then there is no player name so return the type of unit
|
|
function ctld.getPlayerNameOrType(_heli)
|
|
|
|
if _heli:getPlayerName() == nil then
|
|
|
|
return _heli:getTypeName()
|
|
else
|
|
return _heli:getPlayerName()
|
|
end
|
|
end
|
|
|
|
function ctld.inExtractZone(_heli)
|
|
|
|
local _heliPoint = _heli:getPoint()
|
|
|
|
for _, _zoneDetails in pairs(ctld.extractZones) do
|
|
|
|
--get distance to center
|
|
local _dist = ctld.getDistance(_heliPoint, _zoneDetails.point)
|
|
|
|
if _dist <= _zoneDetails.radius then
|
|
return _zoneDetails
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- safe to fast rope if speed is less than 0.5 Meters per second
|
|
function ctld.safeToFastRope(_heli)
|
|
|
|
if ctld.enableFastRopeInsertion == false then
|
|
return false
|
|
end
|
|
|
|
--landed or speed is less than 8 km/h and height is less than fast rope height
|
|
if (ctld.inAir(_heli) == false or (ctld.heightDiff(_heli) <= ctld.fastRopeMaximumHeight + 3.0 and mist.vec.mag(_heli:getVelocity()) < 2.2)) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
function ctld.metersToFeet(_meters)
|
|
|
|
local _feet = _meters * 3.2808399
|
|
|
|
return mist.utils.round(_feet)
|
|
end
|
|
|
|
function ctld.inAir(_heli)
|
|
|
|
if _heli:inAir() == false then
|
|
return false
|
|
end
|
|
|
|
-- less than 5 cm/s a second so landed
|
|
-- BUT AI can hold a perfect hover so ignore AI
|
|
if mist.vec.mag(_heli:getVelocity()) < 0.05 and _heli:getPlayerName() ~= nil then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ctld.deployTroops(_heli, _troops)
|
|
|
|
local _onboard = ctld.inTransitTroops[_heli:getName()]
|
|
|
|
-- deloy troops
|
|
if _troops then
|
|
if _onboard.troops ~= nil and #_onboard.troops.units > 0 then
|
|
if ctld.inAir(_heli) == false or ctld.safeToFastRope(_heli) then
|
|
|
|
-- check we're not in extract zone
|
|
local _extractZone = ctld.inExtractZone(_heli)
|
|
|
|
if _extractZone == false then
|
|
|
|
local _droppedTroops = ctld.spawnDroppedGroup(_heli:getPoint(), _onboard.troops, false)
|
|
if _onboard.troops.jtac or _droppedTroops:getName():lower():find("jtac") then
|
|
local _code = table.remove(ctld.jtacGeneratedLaserCodes, 1)
|
|
table.insert(ctld.jtacGeneratedLaserCodes, _code)
|
|
ctld.JTACStart(_droppedTroops:getName(), _code)
|
|
end
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
|
|
table.insert(ctld.droppedTroopsRED, _droppedTroops:getName())
|
|
else
|
|
|
|
table.insert(ctld.droppedTroopsBLUE, _droppedTroops:getName())
|
|
end
|
|
|
|
ctld.inTransitTroops[_heli:getName()].troops = nil
|
|
ctld.adaptWeightToCargo(_heli:getName())
|
|
|
|
if ctld.inAir(_heli) then
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 fast-ropped troops from %2 into combat", ctld.getPlayerNameOrType(_heli), _heli:getTypeName()), 10)
|
|
else
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 dropped troops from %2 into combat", ctld.getPlayerNameOrType(_heli), _heli:getTypeName()), 10)
|
|
end
|
|
|
|
ctld.processCallback({unit = _heli, unloaded = _droppedTroops, action = "dropped_troops"})
|
|
|
|
|
|
else
|
|
--extract zone!
|
|
local _droppedCount = trigger.misc.getUserFlag(_extractZone.flag)
|
|
|
|
_droppedCount = (#_onboard.troops.units) + _droppedCount
|
|
|
|
trigger.action.setUserFlag(_extractZone.flag, _droppedCount)
|
|
|
|
ctld.inTransitTroops[_heli:getName()].troops = nil
|
|
ctld.adaptWeightToCargo(_heli:getName())
|
|
|
|
if ctld.inAir(_heli) then
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 fast-ropped troops from %2 into %3", ctld.getPlayerNameOrType(_heli), _heli:getTypeName(), _extractZone.name), 10)
|
|
else
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 dropped troops from %2 into %3", ctld.getPlayerNameOrType(_heli), _heli:getTypeName(), _extractZone.name), 10)
|
|
end
|
|
end
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Too high or too fast to drop troops into combat! Hover below %1 feet or land.", ctld.metersToFeet(ctld.fastRopeMaximumHeight)), 10)
|
|
end
|
|
end
|
|
|
|
else
|
|
if ctld.inAir(_heli) == false then
|
|
if _onboard.vehicles ~= nil and #_onboard.vehicles.units > 0 then
|
|
|
|
local _droppedVehicles = ctld.spawnDroppedGroup(_heli:getPoint(), _onboard.vehicles, true)
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
|
|
table.insert(ctld.droppedVehiclesRED, _droppedVehicles:getName())
|
|
else
|
|
|
|
table.insert(ctld.droppedVehiclesBLUE, _droppedVehicles:getName())
|
|
end
|
|
|
|
ctld.inTransitTroops[_heli:getName()].vehicles = nil
|
|
ctld.adaptWeightToCargo(_heli:getName())
|
|
|
|
ctld.processCallback({unit = _heli, unloaded = _droppedVehicles, action = "dropped_vehicles"})
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 dropped vehicles from %2 into combat", ctld.getPlayerNameOrType(_heli), _heli:getTypeName()), 10)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ctld.insertIntoTroopsArray(_troopType,_count,_troopArray,_troopName)
|
|
|
|
for _i = 1, _count do
|
|
local _unitId = ctld.getNextUnitId()
|
|
table.insert(_troopArray, { type = _troopType, unitId = _unitId, name = string.format("Dropped %s #%i", _troopName or _troopType, _unitId) })
|
|
end
|
|
|
|
return _troopArray
|
|
|
|
end
|
|
|
|
|
|
function ctld.generateTroopTypes(_side, _countOrTemplate, _country)
|
|
local _troops = {}
|
|
local _weight = 0
|
|
local _hasJTAC = false
|
|
|
|
local function getSoldiersWeight(count, additionalWeight)
|
|
local _weight = 0
|
|
for i = 1, count do
|
|
local _soldierWeight = math.random(90, 120) * ctld.SOLDIER_WEIGHT / 100
|
|
_weight = _weight + _soldierWeight + ctld.KIT_WEIGHT + additionalWeight
|
|
end
|
|
return _weight
|
|
end
|
|
|
|
if type(_countOrTemplate) == "table" then
|
|
|
|
if _countOrTemplate.aa then
|
|
if _side == 2 then
|
|
_troops = ctld.insertIntoTroopsArray("Soldier stinger",_countOrTemplate.aa,_troops)
|
|
else
|
|
_troops = ctld.insertIntoTroopsArray("SA-18 Igla manpad",_countOrTemplate.aa,_troops)
|
|
end
|
|
_weight = _weight + getSoldiersWeight(_countOrTemplate.aa, ctld.MANPAD_WEIGHT)
|
|
end
|
|
|
|
if _countOrTemplate.inf then
|
|
if _side == 2 then
|
|
_troops = ctld.insertIntoTroopsArray("Soldier M4 GRG",_countOrTemplate.inf,_troops)
|
|
else
|
|
_troops = ctld.insertIntoTroopsArray("Infantry AK",_countOrTemplate.inf,_troops)
|
|
end
|
|
_weight = _weight + getSoldiersWeight(_countOrTemplate.inf, ctld.RIFLE_WEIGHT)
|
|
end
|
|
|
|
if _countOrTemplate.mg then
|
|
if _side == 2 then
|
|
_troops = ctld.insertIntoTroopsArray("Soldier M249",_countOrTemplate.mg,_troops)
|
|
else
|
|
_troops = ctld.insertIntoTroopsArray("Paratrooper AKS-74",_countOrTemplate.mg,_troops)
|
|
end
|
|
_weight = _weight + getSoldiersWeight(_countOrTemplate.mg, ctld.MG_WEIGHT)
|
|
end
|
|
|
|
if _countOrTemplate.at then
|
|
_troops = ctld.insertIntoTroopsArray("Paratrooper RPG-16",_countOrTemplate.at,_troops)
|
|
_weight = _weight + getSoldiersWeight(_countOrTemplate.at, ctld.RPG_WEIGHT)
|
|
end
|
|
|
|
if _countOrTemplate.mortar then
|
|
_troops = ctld.insertIntoTroopsArray("2B11 mortar",_countOrTemplate.mortar,_troops)
|
|
_weight = _weight + getSoldiersWeight(_countOrTemplate.mortar, ctld.MORTAR_WEIGHT)
|
|
end
|
|
|
|
if _countOrTemplate.jtac then
|
|
if _side == 2 then
|
|
_troops = ctld.insertIntoTroopsArray("Soldier M4 GRG",_countOrTemplate.jtac,_troops, "JTAC")
|
|
else
|
|
_troops = ctld.insertIntoTroopsArray("Infantry AK",_countOrTemplate.jtac,_troops, "JTAC")
|
|
end
|
|
_hasJTAC = true
|
|
_weight = _weight + getSoldiersWeight(_countOrTemplate.jtac, ctld.JTAC_WEIGHT + ctld.RIFLE_WEIGHT)
|
|
end
|
|
|
|
else
|
|
for _i = 1, _countOrTemplate do
|
|
|
|
local _unitType = "Infantry AK"
|
|
|
|
if _side == 2 then
|
|
if _i <=2 then
|
|
_unitType = "Soldier M249"
|
|
_weight = _weight + getSoldiersWeight(1, ctld.MG_WEIGHT)
|
|
elseif ctld.spawnRPGWithCoalition and _i > 2 and _i <= 4 then
|
|
_unitType = "Paratrooper RPG-16"
|
|
_weight = _weight + getSoldiersWeight(1, ctld.RPG_WEIGHT)
|
|
elseif ctld.spawnStinger and _i > 4 and _i <= 5 then
|
|
_unitType = "Soldier stinger"
|
|
_weight = _weight + getSoldiersWeight(1, ctld.MANPAD_WEIGHT)
|
|
else
|
|
_unitType = "Soldier M4 GRG"
|
|
_weight = _weight + getSoldiersWeight(1, ctld.RIFLE_WEIGHT)
|
|
end
|
|
else
|
|
if _i <=2 then
|
|
_unitType = "Paratrooper AKS-74"
|
|
_weight = _weight + getSoldiersWeight(1, ctld.MG_WEIGHT)
|
|
elseif ctld.spawnRPGWithCoalition and _i > 2 and _i <= 4 then
|
|
_unitType = "Paratrooper RPG-16"
|
|
_weight = _weight + getSoldiersWeight(1, ctld.RPG_WEIGHT)
|
|
elseif ctld.spawnStinger and _i > 4 and _i <= 5 then
|
|
_unitType = "SA-18 Igla manpad"
|
|
_weight = _weight + getSoldiersWeight(1, ctld.MANPAD_WEIGHT)
|
|
else
|
|
_unitType = "Infantry AK"
|
|
_weight = _weight + getSoldiersWeight(1, ctld.RIFLE_WEIGHT)
|
|
end
|
|
end
|
|
|
|
local _unitId = ctld.getNextUnitId()
|
|
|
|
_troops[_i] = { type = _unitType, unitId = _unitId, name = string.format("Dropped %s #%i", _unitType, _unitId) }
|
|
end
|
|
end
|
|
|
|
local _groupId = ctld.getNextGroupId()
|
|
local _groupName = "Dropped Group"
|
|
if _hasJTAC then
|
|
_groupName = "Dropped JTAC Group"
|
|
end
|
|
local _details = { units = _troops, groupId = _groupId, groupName = string.format("%s %i", _groupName, _groupId), side = _side, country = _country, weight = _weight, jtac = _hasJTAC }
|
|
|
|
return _details
|
|
end
|
|
|
|
--Special F10 function for players for troops
|
|
function ctld.unloadExtractTroops(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
|
|
if _heli == nil then
|
|
return false
|
|
end
|
|
|
|
|
|
local _extract = nil
|
|
if not ctld.inAir(_heli) then
|
|
if _heli:getCoalition() == 1 then
|
|
_extract = ctld.findNearestGroup(_heli, ctld.droppedTroopsRED)
|
|
else
|
|
_extract = ctld.findNearestGroup(_heli, ctld.droppedTroopsBLUE)
|
|
end
|
|
|
|
end
|
|
|
|
if _extract ~= nil and not ctld.troopsOnboard(_heli, true) then
|
|
-- search for nearest troops to pickup
|
|
return ctld.extractTroops({_heli:getName(), true})
|
|
else
|
|
return ctld.unloadTroops({_heli:getName(),true,true})
|
|
end
|
|
|
|
|
|
end
|
|
|
|
-- load troops onto vehicle
|
|
function ctld.loadTroops(_heli, _troops, _numberOrTemplate)
|
|
|
|
-- load troops + vehicles if c130 or herc
|
|
-- "M1045 HMMWV TOW"
|
|
-- "M1043 HMMWV Armament"
|
|
local _onboard = ctld.inTransitTroops[_heli:getName()]
|
|
|
|
--number doesnt apply to vehicles
|
|
if _numberOrTemplate == nil or (type(_numberOrTemplate) ~= "table" and type(_numberOrTemplate) ~= "number") then
|
|
_numberOrTemplate = ctld.getTransportLimit(_heli:getTypeName())
|
|
end
|
|
|
|
if _onboard == nil then
|
|
_onboard = { troops = {}, vehicles = {} }
|
|
end
|
|
|
|
local _list
|
|
if _heli:getCoalition() == 1 then
|
|
_list = ctld.vehiclesForTransportRED
|
|
else
|
|
_list = ctld.vehiclesForTransportBLUE
|
|
end
|
|
|
|
if _troops then
|
|
_onboard.troops = ctld.generateTroopTypes(_heli:getCoalition(), _numberOrTemplate, _heli:getCountry())
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 loaded troops into %2", ctld.getPlayerNameOrType(_heli), _heli:getTypeName()), 10)
|
|
|
|
ctld.processCallback({unit = _heli, onboard = _onboard.troops, action = "load_troops"})
|
|
else
|
|
|
|
_onboard.vehicles = ctld.generateVehiclesForTransport(_heli:getCoalition(), _heli:getCountry())
|
|
|
|
local _count = #_list
|
|
|
|
ctld.processCallback({unit = _heli, onboard = _onboard.vehicles, action = "load_vehicles"})
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 loaded %2 vehicles into %3", ctld.getPlayerNameOrType(_heli), _count, _heli:getTypeName()), 10)
|
|
end
|
|
|
|
ctld.inTransitTroops[_heli:getName()] = _onboard
|
|
ctld.adaptWeightToCargo(_heli:getName())
|
|
end
|
|
|
|
function ctld.generateVehiclesForTransport(_side, _country)
|
|
|
|
local _vehicles = {}
|
|
local _list
|
|
if _side == 1 then
|
|
_list = ctld.vehiclesForTransportRED
|
|
else
|
|
_list = ctld.vehiclesForTransportBLUE
|
|
end
|
|
|
|
|
|
for _i, _type in ipairs(_list) do
|
|
|
|
local _unitId = ctld.getNextUnitId()
|
|
local _weight = ctld.vehiclesWeight[_type] or 2500
|
|
_vehicles[_i] = { type = _type, unitId = _unitId, name = string.format("Dropped %s #%i", _type, _unitId), weight = _weight }
|
|
end
|
|
|
|
|
|
local _groupId = ctld.getNextGroupId()
|
|
local _details = { units = _vehicles, groupId = _groupId, groupName = string.format("Dropped Group %i", _groupId), side = _side, country = _country }
|
|
|
|
return _details
|
|
end
|
|
|
|
function ctld.loadUnloadFOBCrate(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
local _troops = _args[2]
|
|
|
|
if _heli == nil then
|
|
return
|
|
end
|
|
|
|
if ctld.inAir(_heli) == true then
|
|
return
|
|
end
|
|
|
|
|
|
local _side = _heli:getCoalition()
|
|
|
|
local _inZone = ctld.inLogisticsZone(_heli)
|
|
local _crateOnboard = ctld.inTransitFOBCrates[_heli:getName()] ~= nil
|
|
|
|
if _inZone == false and _crateOnboard == true then
|
|
|
|
ctld.inTransitFOBCrates[_heli:getName()] = nil
|
|
|
|
local _position = _heli:getPosition()
|
|
|
|
--try to spawn at 6 oclock to us
|
|
local _angle = math.atan2(_position.x.z, _position.x.x)
|
|
local _xOffset = math.cos(_angle) * -60
|
|
local _yOffset = math.sin(_angle) * -60
|
|
|
|
local _point = _heli:getPoint()
|
|
|
|
local _side = _heli:getCoalition()
|
|
|
|
local _unitId = ctld.getNextUnitId()
|
|
|
|
local _name = string.format("FOB Crate #%i", _unitId)
|
|
|
|
local _spawnedCrate = ctld.spawnFOBCrateStatic(_heli:getCountry(), ctld.getNextUnitId(), { x = _point.x + _xOffset, z = _point.z + _yOffset }, _name)
|
|
|
|
if _side == 1 then
|
|
ctld.droppedFOBCratesRED[_name] = _name
|
|
else
|
|
ctld.droppedFOBCratesBLUE[_name] = _name
|
|
end
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 delivered a FOB Crate", ctld.getPlayerNameOrType(_heli)), 10)
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Delivered FOB Crate 60m at 6'oclock to you"), 10)
|
|
|
|
elseif _inZone == true and _crateOnboard == true then
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("FOB Crate dropped back to base"), 10)
|
|
|
|
ctld.inTransitFOBCrates[_heli:getName()] = nil
|
|
|
|
elseif _inZone == true and _crateOnboard == false then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("FOB Crate Loaded"), 10)
|
|
|
|
ctld.inTransitFOBCrates[_heli:getName()] = true
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 loaded a FOB Crate ready for delivery!", ctld.getPlayerNameOrType(_heli)), 10)
|
|
|
|
else
|
|
|
|
-- nearest Crate
|
|
local _crates = ctld.getCratesAndDistance(_heli)
|
|
local _nearestCrate = ctld.getClosestCrate(_heli, _crates, "FOB")
|
|
|
|
if _nearestCrate ~= nil and _nearestCrate.dist < 150 then
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("FOB Crate Loaded"), 10)
|
|
ctld.inTransitFOBCrates[_heli:getName()] = true
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 loaded a FOB Crate ready for delivery!", ctld.getPlayerNameOrType(_heli)), 10)
|
|
|
|
if _side == 1 then
|
|
ctld.droppedFOBCratesRED[_nearestCrate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.droppedFOBCratesBLUE[_nearestCrate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
--remove
|
|
_nearestCrate.crateUnit:destroy()
|
|
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("There are no friendly logistic units nearby to load a FOB crate from!"), 10)
|
|
end
|
|
end
|
|
end
|
|
|
|
function ctld.loadTroopsFromZone(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
local _troops = _args[2]
|
|
local _groupTemplate = _args[3] or ""
|
|
local _allowExtract = _args[4]
|
|
|
|
if _heli == nil then
|
|
return false
|
|
end
|
|
|
|
local _zone = ctld.inPickupZone(_heli)
|
|
|
|
if ctld.troopsOnboard(_heli, _troops) then
|
|
|
|
if _troops then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You already have troops onboard."), 10)
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You already have vehicles onboard."), 10)
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local _extract
|
|
|
|
if _allowExtract then
|
|
-- first check for extractable troops regardless of if we're in a zone or not
|
|
if _troops then
|
|
if _heli:getCoalition() == 1 then
|
|
_extract = ctld.findNearestGroup(_heli, ctld.droppedTroopsRED)
|
|
else
|
|
_extract = ctld.findNearestGroup(_heli, ctld.droppedTroopsBLUE)
|
|
end
|
|
else
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
_extract = ctld.findNearestGroup(_heli, ctld.droppedVehiclesRED)
|
|
else
|
|
_extract = ctld.findNearestGroup(_heli, ctld.droppedVehiclesBLUE)
|
|
end
|
|
end
|
|
end
|
|
|
|
if _extract ~= nil then
|
|
-- search for nearest troops to pickup
|
|
return ctld.extractTroops({_heli:getName(), _troops})
|
|
elseif _zone.inZone == true then
|
|
|
|
if _zone.limit - 1 >= 0 then
|
|
-- decrease zone counter by 1
|
|
ctld.updateZoneCounter(_zone.index, -1)
|
|
|
|
ctld.loadTroops(_heli, _troops,_groupTemplate)
|
|
|
|
return true
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("This area has no more reinforcements available!"), 20)
|
|
|
|
return false
|
|
end
|
|
|
|
else
|
|
if _allowExtract then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You are not in a pickup zone and no one is nearby to extract"), 10)
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You are not in a pickup zone"), 10)
|
|
end
|
|
|
|
return false
|
|
end
|
|
end
|
|
|
|
|
|
|
|
function ctld.unloadTroops(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
local _troops = _args[2]
|
|
|
|
if _heli == nil then
|
|
return false
|
|
end
|
|
|
|
local _zone = ctld.inPickupZone(_heli)
|
|
if not ctld.troopsOnboard(_heli, _troops) then
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("No one to unload"), 10)
|
|
|
|
return false
|
|
else
|
|
|
|
-- troops must be onboard to get here
|
|
if _zone.inZone == true then
|
|
|
|
if _troops then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Dropped troops back to base"), 20)
|
|
|
|
ctld.processCallback({unit = _heli, unloaded = ctld.inTransitTroops[_heli:getName()].troops, action = "unload_troops_zone"})
|
|
|
|
ctld.inTransitTroops[_heli:getName()].troops = nil
|
|
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Dropped vehicles back to base"), 20)
|
|
|
|
ctld.processCallback({unit = _heli, unloaded = ctld.inTransitTroops[_heli:getName()].vehicles, action = "unload_vehicles_zone"})
|
|
|
|
ctld.inTransitTroops[_heli:getName()].vehicles = nil
|
|
end
|
|
|
|
ctld.adaptWeightToCargo(_heli:getName())
|
|
|
|
-- increase zone counter by 1
|
|
ctld.updateZoneCounter(_zone.index, 1)
|
|
|
|
return true
|
|
|
|
elseif ctld.troopsOnboard(_heli, _troops) then
|
|
|
|
return ctld.deployTroops(_heli, _troops)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
function ctld.extractTroops(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
local _troops = _args[2]
|
|
|
|
if _heli == nil then
|
|
return false
|
|
end
|
|
|
|
if ctld.inAir(_heli) then
|
|
return false
|
|
end
|
|
|
|
if ctld.troopsOnboard(_heli, _troops) then
|
|
if _troops then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You already have troops onboard."), 10)
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You already have vehicles onboard."), 10)
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local _onboard = ctld.inTransitTroops[_heli:getName()]
|
|
|
|
if _onboard == nil then
|
|
_onboard = { troops = nil, vehicles = nil }
|
|
end
|
|
|
|
local _extracted = false
|
|
|
|
if _troops then
|
|
|
|
local _extractTroops
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
_extractTroops = ctld.findNearestGroup(_heli, ctld.droppedTroopsRED)
|
|
else
|
|
_extractTroops = ctld.findNearestGroup(_heli, ctld.droppedTroopsBLUE)
|
|
end
|
|
|
|
|
|
if _extractTroops ~= nil then
|
|
|
|
local _limit = ctld.getTransportLimit(_heli:getTypeName())
|
|
|
|
local _size = #_extractTroops.group:getUnits()
|
|
|
|
if _limit < #_extractTroops.group:getUnits() then
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Sorry - The group of %1 is too large to fit. \n\nLimit is %2 for %3", _size, _limit, _heli:getTypeName()), 20)
|
|
|
|
return
|
|
end
|
|
|
|
_onboard.troops = _extractTroops.details
|
|
_onboard.troops.weight = #_extractTroops.group:getUnits() * 130 -- default to 130kg per soldier
|
|
|
|
if _extractTroops.group:getName():lower():find("jtac") then
|
|
_onboard.troops.jtac = true
|
|
end
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 extracted troops in %2 from combat", ctld.getPlayerNameOrType(_heli), _heli:getTypeName()), 10)
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
ctld.droppedTroopsRED[_extractTroops.group:getName()] = nil
|
|
else
|
|
ctld.droppedTroopsBLUE[_extractTroops.group:getName()] = nil
|
|
end
|
|
|
|
ctld.processCallback({unit = _heli, extracted = _extractTroops, action = "extract_troops"})
|
|
|
|
--remove
|
|
_extractTroops.group:destroy()
|
|
|
|
_extracted = true
|
|
else
|
|
_onboard.troops = nil
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("No extractable troops nearby!"), 20)
|
|
end
|
|
|
|
else
|
|
|
|
local _extractVehicles
|
|
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
|
|
_extractVehicles = ctld.findNearestGroup(_heli, ctld.droppedVehiclesRED)
|
|
else
|
|
|
|
_extractVehicles = ctld.findNearestGroup(_heli, ctld.droppedVehiclesBLUE)
|
|
end
|
|
|
|
if _extractVehicles ~= nil then
|
|
_onboard.vehicles = _extractVehicles.details
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
|
|
ctld.droppedVehiclesRED[_extractVehicles.group:getName()] = nil
|
|
else
|
|
|
|
ctld.droppedVehiclesBLUE[_extractVehicles.group:getName()] = nil
|
|
end
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 extracted vehicles in %2 from combat", ctld.getPlayerNameOrType(_heli), _heli:getTypeName()), 10)
|
|
|
|
ctld.processCallback({unit = _heli, extracted = _extractVehicles, action = "extract_vehicles"})
|
|
--remove
|
|
_extractVehicles.group:destroy()
|
|
_extracted = true
|
|
|
|
else
|
|
_onboard.vehicles = nil
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("No extractable vehicles nearby!"), 20)
|
|
end
|
|
end
|
|
|
|
ctld.inTransitTroops[_heli:getName()] = _onboard
|
|
ctld.adaptWeightToCargo(_heli:getName())
|
|
|
|
return _extracted
|
|
end
|
|
|
|
|
|
function ctld.checkTroopStatus(_args)
|
|
local _unitName = _args[1]
|
|
--list onboard troops, if c130
|
|
local _heli = ctld.getTransportUnit(_unitName)
|
|
|
|
if _heli == nil then
|
|
return
|
|
end
|
|
|
|
local _, _message = ctld.getWeightOfCargo(_unitName)
|
|
if _message and _message ~= "" then
|
|
ctld.displayMessageToGroup(_heli, _message, 10)
|
|
end
|
|
end
|
|
|
|
-- Removes troops from transport when it dies
|
|
function ctld.checkTransportStatus()
|
|
|
|
timer.scheduleFunction(ctld.checkTransportStatus, nil, timer.getTime() + 3)
|
|
|
|
for _, _name in ipairs(ctld.transportPilotNames) do
|
|
|
|
local _transUnit = ctld.getTransportUnit(_name)
|
|
|
|
if _transUnit == nil then
|
|
--env.info("CTLD Transport Unit Dead event")
|
|
ctld.inTransitTroops[_name] = nil
|
|
ctld.inTransitFOBCrates[_name] = nil
|
|
ctld.inTransitSlingLoadCrates[_name] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
function ctld.adaptWeightToCargo(unitName)
|
|
local _weight = ctld.getWeightOfCargo(unitName)
|
|
trigger.action.setUnitInternalCargo(unitName, _weight)
|
|
end
|
|
|
|
function ctld.getWeightOfCargo(unitName)
|
|
|
|
local FOB_CRATE_WEIGHT = 800
|
|
local _weight = 0
|
|
local _description = ""
|
|
|
|
ctld.inTransitSlingLoadCrates[unitName] = ctld.inTransitSlingLoadCrates[unitName] or {}
|
|
|
|
-- add troops weight
|
|
if ctld.inTransitTroops[unitName] then
|
|
local _inTransit = ctld.inTransitTroops[unitName]
|
|
if _inTransit then
|
|
local _troops = _inTransit.troops
|
|
if _troops and _troops.units then
|
|
_description = _description .. ctld.i18n_translate("%1 troops onboard (%2 kg)\n", #_troops.units, _troops.weight)
|
|
_weight = _weight + _troops.weight
|
|
end
|
|
local _vehicles = _inTransit.vehicles
|
|
if _vehicles and _vehicles.units then
|
|
for _, _unit in pairs(_vehicles.units) do
|
|
_weight = _weight + _unit.weight
|
|
end
|
|
_description = _description .. ctld.i18n_translate("%1 vehicles onboard (%2)\n", #_vehicles.units, _weight)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- add FOB crates weight
|
|
if ctld.inTransitFOBCrates[unitName] then
|
|
_weight = _weight + FOB_CRATE_WEIGHT
|
|
_description = _description .. ctld.i18n_translate("1 FOB Crate oboard (%1 kg)\n", FOB_CRATE_WEIGHT)
|
|
end
|
|
|
|
-- add simulated slingload crates weight
|
|
for i = 1, #ctld.inTransitSlingLoadCrates[unitName] do
|
|
local _crate = ctld.inTransitSlingLoadCrates[unitName][i]
|
|
if _crate and _crate.simulatedSlingload then
|
|
_weight = _weight + _crate.weight
|
|
_description = _description .. ctld.i18n_translate("%1 crate onboard (%2 kg)\n", _crate.desc, _crate.weight)
|
|
end
|
|
end
|
|
if _description ~= "" then
|
|
_description = _description .. ctld.i18n_translate("Total weight of cargo : %1 kg\n", _weight)
|
|
else
|
|
_description = ctld.i18n_translate("No cargo.")
|
|
end
|
|
|
|
return _weight, _description
|
|
end
|
|
|
|
function ctld.checkHoverStatus()
|
|
timer.scheduleFunction(ctld.checkHoverStatus, nil, timer.getTime() + 1.0)
|
|
|
|
local _status, _result = pcall(function()
|
|
|
|
for _, _name in ipairs(ctld.transportPilotNames) do
|
|
|
|
local _reset = true
|
|
local _transUnit = ctld.getTransportUnit(_name)
|
|
local _transUnitTypeName = _transUnit and _transUnit:getTypeName()
|
|
local _cargoCapacity = ctld.internalCargoLimits[_transUnitTypeName] or 1
|
|
ctld.inTransitSlingLoadCrates[_name] = ctld.inTransitSlingLoadCrates[_name] or {}
|
|
|
|
--only check transports that are hovering and not planes
|
|
if _transUnit ~= nil and #ctld.inTransitSlingLoadCrates[_name] < _cargoCapacity and ctld.inAir(_transUnit) and ctld.unitCanCarryVehicles(_transUnit) == false and not ctld.unitDynamicCargoCapable(_transUnit) then
|
|
|
|
|
|
local _crates = ctld.getCratesAndDistance(_transUnit)
|
|
|
|
for _, _crate in pairs(_crates) do
|
|
local _crateUnitName = _crate.crateUnit:getName()
|
|
if _crate.dist < ctld.maxDistanceFromCrate and _crate.details.unit ~= "FOB" then
|
|
|
|
--check height!
|
|
local _height = _transUnit:getPoint().y - _crate.crateUnit:getPoint().y
|
|
if _height > ctld.minimumHoverHeight and _height <= ctld.maximumHoverHeight then
|
|
|
|
local _time = ctld.hoverStatus[_name]
|
|
|
|
if _time == nil then
|
|
ctld.hoverStatus[_name] = ctld.hoverTime
|
|
_time = ctld.hoverTime
|
|
else
|
|
_time = ctld.hoverStatus[_name] - 1
|
|
ctld.hoverStatus[_name] = _time
|
|
end
|
|
|
|
if _time > 0 then
|
|
ctld.displayMessageToGroup(_transUnit, ctld.i18n_translate("Hovering above %1 crate. \n\nHold hover for %2 seconds! \n\nIf the countdown stops you're too far away!", _crate.details.desc, _time), 10,true)
|
|
else
|
|
ctld.hoverStatus[_name] = nil
|
|
ctld.displayMessageToGroup(_transUnit, ctld.i18n_translate("Loaded %1 crate!", _crate.details.desc), 10,true)
|
|
|
|
--crates been moved once!
|
|
ctld.crateMove[_crateUnitName] = nil
|
|
|
|
if _transUnit:getCoalition() == 1 then
|
|
ctld.spawnedCratesRED[_crateUnitName] = nil
|
|
else
|
|
ctld.spawnedCratesBLUE[_crateUnitName] = nil
|
|
end
|
|
|
|
_crate.crateUnit:destroy()
|
|
|
|
local _copiedCrate = mist.utils.deepCopy(_crate.details)
|
|
_copiedCrate.simulatedSlingload = true
|
|
table.insert(ctld.inTransitSlingLoadCrates[_name], _copiedCrate)
|
|
ctld.adaptWeightToCargo(_name)
|
|
end
|
|
|
|
_reset = false
|
|
|
|
break
|
|
elseif _height <= ctld.minimumHoverHeight then
|
|
ctld.displayMessageToGroup(_transUnit, ctld.i18n_translate("Too low to hook %1 crate.\n\nHold hover for %2 seconds", _crate.details.desc, ctld.hoverTime), 5,true)
|
|
break
|
|
else
|
|
ctld.displayMessageToGroup(_transUnit, ctld.i18n_translate("Too high to hook %1 crate.\n\nHold hover for %2 seconds", _crate.details.desc, ctld.hoverTime), 5,true)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if _reset then
|
|
ctld.hoverStatus[_name] = nil
|
|
end
|
|
end
|
|
end)
|
|
|
|
if (not _status) then
|
|
env.error(string.format("CTLD ERROR: %s", _result))
|
|
end
|
|
end
|
|
|
|
function ctld.loadNearbyCrate(_name)
|
|
local _transUnit = ctld.getTransportUnit(_name)
|
|
|
|
if _transUnit ~= nil then
|
|
|
|
local _cargoCapacity = ctld.internalCargoLimits[_transUnit:getTypeName()] or 1
|
|
ctld.inTransitSlingLoadCrates[_name] = ctld.inTransitSlingLoadCrates[_name] or {}
|
|
|
|
if ctld.inAir(_transUnit) then
|
|
ctld.displayMessageToGroup(_transUnit, ctld.i18n_translate("You must land before you can load a crate!"), 10,true)
|
|
return
|
|
end
|
|
|
|
local _crates = ctld.getCratesAndDistance(_transUnit)
|
|
local loaded = false
|
|
for _, _crate in pairs(_crates) do
|
|
|
|
if _crate.dist < 50.0 then
|
|
if #ctld.inTransitSlingLoadCrates[_name] < _cargoCapacity then
|
|
ctld.displayMessageToGroup(_transUnit, ctld.i18n_translate("Loaded %1 crate!", _crate.details.desc), 10)
|
|
|
|
if _transUnit:getCoalition() == 1 then
|
|
ctld.spawnedCratesRED[_crate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.spawnedCratesBLUE[_crate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
ctld.crateMove[_crate.crateUnit:getName()] = nil
|
|
|
|
_crate.crateUnit:destroy()
|
|
|
|
local _copiedCrate = mist.utils.deepCopy(_crate.details)
|
|
_copiedCrate.simulatedSlingload = true
|
|
table.insert(ctld.inTransitSlingLoadCrates[_name], _copiedCrate)
|
|
loaded = true
|
|
ctld.adaptWeightToCargo(_name)
|
|
else
|
|
-- Max crates onboard
|
|
local outputMsg = ctld.i18n_translate("Crate Loaded!")
|
|
for i = 1, _cargoCapacity do
|
|
outputMsg = outputMsg .. "\n" .. ctld.inTransitSlingLoadCrates[_name][i].desc
|
|
end
|
|
ctld.displayMessageToGroup(_transUnit, outputMsg, 10,true)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
if not loaded then
|
|
ctld.displayMessageToGroup(_transUnit, ctld.i18n_translate("No Crates within 50m to load!"), 10,true)
|
|
end
|
|
end
|
|
end
|
|
|
|
--check each minute if the beacons' batteries have failed, and stop them accordingly
|
|
--there's no more need to actually refresh the beacons, since we set "loop" to true.
|
|
function ctld.refreshRadioBeacons()
|
|
|
|
timer.scheduleFunction(ctld.refreshRadioBeacons, nil, timer.getTime() + 60)
|
|
|
|
|
|
for _index, _beaconDetails in ipairs(ctld.deployedRadioBeacons) do
|
|
|
|
if ctld.updateRadioBeacon(_beaconDetails) == false then
|
|
|
|
--search used frequencies + remove, add back to unused
|
|
|
|
for _i, _freq in ipairs(ctld.usedUHFFrequencies) do
|
|
if _freq == _beaconDetails.uhf then
|
|
|
|
table.insert(ctld.freeUHFFrequencies, _freq)
|
|
table.remove(ctld.usedUHFFrequencies, _i)
|
|
end
|
|
end
|
|
|
|
for _i, _freq in ipairs(ctld.usedVHFFrequencies) do
|
|
if _freq == _beaconDetails.vhf then
|
|
|
|
table.insert(ctld.freeVHFFrequencies, _freq)
|
|
table.remove(ctld.usedVHFFrequencies, _i)
|
|
end
|
|
end
|
|
|
|
for _i, _freq in ipairs(ctld.usedFMFrequencies) do
|
|
if _freq == _beaconDetails.fm then
|
|
|
|
table.insert(ctld.freeFMFrequencies, _freq)
|
|
table.remove(ctld.usedFMFrequencies, _i)
|
|
end
|
|
end
|
|
|
|
--clean up beacon table
|
|
table.remove(ctld.deployedRadioBeacons, _index)
|
|
end
|
|
end
|
|
end
|
|
|
|
function ctld.getClockDirection(_heli, _crate)
|
|
|
|
-- Source: Helicopter Script - Thanks!
|
|
|
|
local _position = _crate:getPosition().p -- get position of crate
|
|
local _playerPosition = _heli:getPosition().p -- get position of helicopter
|
|
local _relativePosition = mist.vec.sub(_position, _playerPosition)
|
|
|
|
local _playerHeading = mist.getHeading(_heli) -- the rest of the code determines the 'o'clock' bearing of the missile relative to the helicopter
|
|
|
|
local _headingVector = { x = math.cos(_playerHeading), y = 0, z = math.sin(_playerHeading) }
|
|
|
|
local _headingVectorPerpendicular = { x = math.cos(_playerHeading + math.pi / 2), y = 0, z = math.sin(_playerHeading + math.pi / 2) }
|
|
|
|
local _forwardDistance = mist.vec.dp(_relativePosition, _headingVector)
|
|
|
|
local _rightDistance = mist.vec.dp(_relativePosition, _headingVectorPerpendicular)
|
|
|
|
local _angle = math.atan2(_rightDistance, _forwardDistance) * 180 / math.pi
|
|
|
|
if _angle < 0 then
|
|
_angle = 360 + _angle
|
|
end
|
|
_angle = math.floor(_angle * 12 / 360 + 0.5)
|
|
if _angle == 0 then
|
|
_angle = 12
|
|
end
|
|
|
|
return _angle
|
|
end
|
|
|
|
|
|
function ctld.getCompassBearing(_ref, _unitPos)
|
|
|
|
_ref = mist.utils.makeVec3(_ref, 0) -- turn it into Vec3 if it is not already.
|
|
_unitPos = mist.utils.makeVec3(_unitPos, 0) -- turn it into Vec3 if it is not already.
|
|
|
|
local _vec = { x = _unitPos.x - _ref.x, y = _unitPos.y - _ref.y, z = _unitPos.z - _ref.z }
|
|
|
|
local _dir = mist.utils.getDir(_vec, _ref)
|
|
|
|
local _bearing = mist.utils.round(mist.utils.toDegree(_dir), 0)
|
|
|
|
return _bearing
|
|
end
|
|
|
|
function ctld.listNearbyCrates(_args)
|
|
|
|
local _message = ""
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
|
|
if _heli == nil then
|
|
|
|
return -- no heli!
|
|
end
|
|
|
|
local _crates = ctld.getCratesAndDistance(_heli)
|
|
|
|
--sort
|
|
local _sort = function( a,b ) return a.dist < b.dist end
|
|
table.sort(_crates,_sort)
|
|
|
|
for _, _crate in pairs(_crates) do
|
|
|
|
if _crate.dist < 1000 and _crate.details.unit ~= "FOB" then
|
|
_message = ctld.i18n_translate("%1\n%2 crate - kg %3 - %4 m - %5 o'clock", _message, _crate.details.desc, _crate.details.weight, _crate.dist, ctld.getClockDirection(_heli, _crate.crateUnit))
|
|
end
|
|
end
|
|
|
|
|
|
local _fobMsg = ""
|
|
for _, _fobCrate in pairs(_crates) do
|
|
|
|
if _fobCrate.dist < 1000 and _fobCrate.details.unit == "FOB" then
|
|
_fobMsg = _fobMsg .. ctld.i18n_translate("FOB Crate - %1 m - %2 o'clock\n", _fobCrate.dist, ctld.getClockDirection(_heli, _fobCrate.crateUnit))
|
|
end
|
|
end
|
|
|
|
local _txt = ctld.i18n_translate("No Nearby Crates")
|
|
if _message ~= "" or _fobMsg ~= "" then
|
|
|
|
_txt = ""
|
|
|
|
if _message ~= "" then
|
|
_txt = ctld.i18n_translate("Nearby Crates:\n%1", _message)
|
|
end
|
|
|
|
if _fobMsg ~= "" then
|
|
|
|
if _txt ~= "" then
|
|
_txt = _txt .. "\n\n"
|
|
end
|
|
|
|
_txt = _txt .. ctld.i18n_translate("Nearby FOB Crates (Not Slingloadable):\n%1", _fobMsg)
|
|
end
|
|
end
|
|
ctld.displayMessageToGroup(_heli, _txt, 20)
|
|
end
|
|
|
|
|
|
function ctld.listFOBS(_args)
|
|
|
|
local _msg = ctld.i18n_translate("FOB Positions:")
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
|
|
if _heli == nil then
|
|
|
|
return -- no heli!
|
|
end
|
|
|
|
-- get fob positions
|
|
local _fobs = ctld.getSpawnedFobs(_heli)
|
|
|
|
if _fobs and #_fobs > 0 then
|
|
-- now check spawned fobs
|
|
for _, _fob in ipairs(_fobs) do
|
|
_msg = ctld.i18n_translate("%1\nFOB @ %2", _msg, ctld.getFOBPositionString(_fob))
|
|
end
|
|
else
|
|
_msg = ctld.i18n_translate("Sorry, there are no active FOBs!")
|
|
end
|
|
ctld.displayMessageToGroup(_heli, _msg, 20)
|
|
end
|
|
|
|
function ctld.getFOBPositionString(_fob)
|
|
|
|
local _lat, _lon = coord.LOtoLL(_fob:getPosition().p)
|
|
|
|
local _latLngStr = mist.tostringLL(_lat, _lon, 3, ctld.location_DMS)
|
|
|
|
-- local _mgrsString = mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(_fob:getPosition().p)), 5)
|
|
|
|
local _message = _latLngStr
|
|
|
|
local _beaconInfo = ctld.fobBeacons[_fob:getName()]
|
|
|
|
if _beaconInfo ~= nil then
|
|
_message = string.format("%s - %.2f KHz ", _message, _beaconInfo.vhf / 1000)
|
|
_message = string.format("%s - %.2f MHz ", _message, _beaconInfo.uhf / 1000000)
|
|
_message = string.format("%s - %.2f MHz ", _message, _beaconInfo.fm / 1000000)
|
|
end
|
|
|
|
return _message
|
|
end
|
|
|
|
|
|
function ctld.displayMessageToGroup(_unit, _text, _time,_clear)
|
|
|
|
local _groupId = ctld.getGroupId(_unit)
|
|
if _groupId then
|
|
if _clear == true then
|
|
trigger.action.outTextForGroup(_groupId, _text, _time,_clear)
|
|
else
|
|
trigger.action.outTextForGroup(_groupId, _text, _time)
|
|
end
|
|
end
|
|
end
|
|
|
|
function ctld.heightDiff(_unit)
|
|
|
|
local _point = _unit:getPoint()
|
|
|
|
-- env.info("heightunit " .. _point.y)
|
|
--env.info("heightland " .. land.getHeight({ x = _point.x, y = _point.z }))
|
|
|
|
return _point.y - land.getHeight({ x = _point.x, y = _point.z })
|
|
end
|
|
|
|
--includes fob crates!
|
|
function ctld.getCratesAndDistance(_heli)
|
|
|
|
local _crates = {}
|
|
|
|
local _allCrates
|
|
if _heli:getCoalition() == 1 then
|
|
_allCrates = ctld.spawnedCratesRED
|
|
else
|
|
_allCrates = ctld.spawnedCratesBLUE
|
|
end
|
|
|
|
for _crateName, _details in pairs(_allCrates) do
|
|
|
|
--get crate
|
|
local _crate = ctld.getCrateObject(_crateName)
|
|
|
|
--in air seems buggy with crates so if in air is true, get the height above ground and the speed magnitude
|
|
if _crate ~= nil and _crate:getLife() > 0
|
|
and (ctld.inAir(_crate) == false) then
|
|
|
|
local _dist = ctld.getDistance(_crate:getPoint(), _heli:getPoint())
|
|
|
|
local _crateDetails = { crateUnit = _crate, dist = _dist, details = _details }
|
|
|
|
table.insert(_crates, _crateDetails)
|
|
end
|
|
end
|
|
|
|
local _fobCrates
|
|
if _heli:getCoalition() == 1 then
|
|
_fobCrates = ctld.droppedFOBCratesRED
|
|
else
|
|
_fobCrates = ctld.droppedFOBCratesBLUE
|
|
end
|
|
|
|
for _crateName, _details in pairs(_fobCrates) do
|
|
|
|
--get crate
|
|
local _crate = ctld.getCrateObject(_crateName)
|
|
|
|
if _crate ~= nil and _crate:getLife() > 0 then
|
|
|
|
local _dist = ctld.getDistance(_crate:getPoint(), _heli:getPoint())
|
|
|
|
local _crateDetails = { crateUnit = _crate, dist = _dist, details = { unit = "FOB" }, }
|
|
|
|
table.insert(_crates, _crateDetails)
|
|
end
|
|
end
|
|
|
|
return _crates
|
|
end
|
|
|
|
|
|
function ctld.getClosestCrate(_heli, _crates, _type)
|
|
|
|
local _closetCrate = nil
|
|
local _shortestDistance = -1
|
|
local _distance = 0
|
|
local _minimumDistance = 5 -- prevents dynamic cargo crates from unpacking while in cargo hold
|
|
|
|
for _, _crate in pairs(_crates) do
|
|
|
|
if (_crate.details.unit == _type or _type == nil) then
|
|
_distance = _crate.dist
|
|
|
|
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) and _distance > _minimumDistance then
|
|
_shortestDistance = _distance
|
|
_closetCrate = _crate
|
|
end
|
|
end
|
|
end
|
|
|
|
return _closetCrate
|
|
end
|
|
|
|
function ctld.findNearestAASystem(_heli,_aaSystem)
|
|
|
|
local _closestHawkGroup = nil
|
|
local _shortestDistance = -1
|
|
local _distance = 0
|
|
|
|
for _groupName, _hawkDetails in pairs(ctld.completeAASystems) do
|
|
|
|
local _hawkGroup = Group.getByName(_groupName)
|
|
|
|
-- env.info(_groupName..": "..mist.utils.tableShow(_hawkDetails))
|
|
if _hawkGroup ~= nil and _hawkGroup:getCoalition() == _heli:getCoalition() and _hawkDetails[1].system.name == _aaSystem.name then
|
|
|
|
local _units = _hawkGroup:getUnits()
|
|
|
|
for _, _leader in pairs(_units) do
|
|
|
|
if _leader ~= nil and _leader:getLife() > 0 then
|
|
|
|
_distance = ctld.getDistance(_leader:getPoint(), _heli:getPoint())
|
|
|
|
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
|
|
_shortestDistance = _distance
|
|
_closestHawkGroup = _hawkGroup
|
|
end
|
|
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if _closestHawkGroup ~= nil then
|
|
|
|
return { group = _closestHawkGroup, dist = _shortestDistance }
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function ctld.getCrateObject(_name)
|
|
local _crate
|
|
|
|
if ctld.staticBugWorkaround then
|
|
_crate = Unit.getByName(_name)
|
|
else
|
|
_crate = StaticObject.getByName(_name)
|
|
end
|
|
return _crate
|
|
end
|
|
|
|
|
|
|
|
function ctld.unpackCrates(_arguments)
|
|
|
|
local _status, _err = pcall(function(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
|
|
if _heli ~= nil and ctld.inAir(_heli) == false then
|
|
|
|
local _crates = ctld.getCratesAndDistance(_heli)
|
|
local _crate = ctld.getClosestCrate(_heli, _crates)
|
|
|
|
|
|
if ctld.inLogisticsZone(_heli) == true or ctld.farEnoughFromLogisticZone(_heli) == false then
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You can't unpack that here! Take it to where it's needed!"), 20)
|
|
|
|
return
|
|
end
|
|
|
|
|
|
|
|
if _crate ~= nil and _crate.dist < 750
|
|
and (_crate.details.unit == "FOB" or _crate.details.unit == "FOB-SMALL") then
|
|
|
|
ctld.unpackFOBCrates(_crates, _heli)
|
|
|
|
return
|
|
|
|
elseif _crate ~= nil and _crate.dist < 200 then
|
|
|
|
if ctld.forceCrateToBeMoved and ctld.crateMove[_crate.crateUnit:getName()] and not ctld.unitDynamicCargoCapable(_heli) then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Sorry you must move this crate before you unpack it!"), 20)
|
|
return
|
|
end
|
|
|
|
|
|
local _aaTemplate = ctld.getAATemplate(_crate.details.unit)
|
|
|
|
if _aaTemplate then
|
|
|
|
if _crate.details.unit == _aaTemplate.repair then
|
|
ctld.repairAASystem(_heli, _crate,_aaTemplate)
|
|
else
|
|
ctld.unpackAASystem(_heli, _crate, _crates,_aaTemplate)
|
|
end
|
|
|
|
return -- stop processing
|
|
-- is multi crate?
|
|
elseif _crate.details.cratesRequired ~= nil and _crate.details.cratesRequired > 1 then
|
|
-- multicrate
|
|
|
|
ctld.unpackMultiCrate(_heli, _crate, _crates)
|
|
|
|
return
|
|
|
|
else
|
|
-- single crate
|
|
local _cratePoint = _crate.crateUnit:getPoint()
|
|
local _crateName = _crate.crateUnit:getName()
|
|
local _crateHdg = mist.getHeading(_crate.crateUnit, true)
|
|
|
|
--remove crate
|
|
-- if ctld.slingLoad == false then
|
|
_crate.crateUnit:destroy()
|
|
-- end
|
|
|
|
local _spawnedGroups = ctld.spawnCrateGroup(_heli, { _cratePoint }, { _crate.details.unit }, { _crateHdg })
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
ctld.spawnedCratesRED[_crateName] = nil
|
|
else
|
|
ctld.spawnedCratesBLUE[_crateName] = nil
|
|
end
|
|
|
|
ctld.processCallback({unit = _heli, crate = _crate , spawnedGroup = _spawnedGroups, action = "unpack"})
|
|
|
|
if _crate.details.unit == "1L13 EWR" then
|
|
ctld.addEWRTask(_spawnedGroups)
|
|
|
|
-- env.info("Added EWR")
|
|
end
|
|
|
|
if _crate.details.unit == "FPS-117" then
|
|
ctld.addEWRTask(_spawnedGroups)
|
|
|
|
-- env.info("Added EWR")
|
|
end
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 successfully deployed %2 to the field", ctld.getPlayerNameOrType(_heli), _crate.details.desc), 10)
|
|
|
|
if ctld.isJTACUnitType(_crate.details.unit) and ctld.JTAC_dropEnabled then
|
|
|
|
local _code = table.remove(ctld.jtacGeneratedLaserCodes, 1)
|
|
--put to the end
|
|
table.insert(ctld.jtacGeneratedLaserCodes, _code)
|
|
|
|
ctld.JTACStart(_spawnedGroups:getName(), _code) --(_jtacGroupName, _laserCode, _smoke, _lock, _colour)
|
|
end
|
|
end
|
|
|
|
else
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("No friendly crates close enough to unpack, or crate too close to aircraft."), 20)
|
|
end
|
|
end
|
|
end, _arguments)
|
|
|
|
if (not _status) then
|
|
env.error(string.format("CTLD ERROR: %s", _err))
|
|
end
|
|
end
|
|
|
|
|
|
-- builds a fob!
|
|
function ctld.unpackFOBCrates(_crates, _heli)
|
|
|
|
if ctld.inLogisticsZone(_heli) == true then
|
|
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You can't unpack that here! Take it to where it's needed!"), 20)
|
|
|
|
return
|
|
end
|
|
|
|
-- unpack multi crate
|
|
local _nearbyMultiCrates = {}
|
|
|
|
local _bigFobCrates = 0
|
|
local _smallFobCrates = 0
|
|
local _totalCrates = 0
|
|
|
|
for _, _nearbyCrate in pairs(_crates) do
|
|
|
|
if _nearbyCrate.dist < 750 then
|
|
|
|
if _nearbyCrate.details.unit == "FOB" then
|
|
_bigFobCrates = _bigFobCrates + 1
|
|
table.insert(_nearbyMultiCrates, _nearbyCrate)
|
|
elseif _nearbyCrate.details.unit == "FOB-SMALL" then
|
|
_smallFobCrates = _smallFobCrates + 1
|
|
table.insert(_nearbyMultiCrates, _nearbyCrate)
|
|
end
|
|
|
|
--catch divide by 0
|
|
if _smallFobCrates > 0 then
|
|
_totalCrates = _bigFobCrates + (_smallFobCrates/3.0)
|
|
else
|
|
_totalCrates = _bigFobCrates
|
|
end
|
|
|
|
if _totalCrates >= ctld.cratesRequiredForFOB then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
--- check crate count
|
|
if _totalCrates >= ctld.cratesRequiredForFOB then
|
|
|
|
-- destroy crates
|
|
|
|
local _points = {}
|
|
|
|
for _, _crate in pairs(_nearbyMultiCrates) do
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
ctld.droppedFOBCratesRED[_crate.crateUnit:getName()] = nil
|
|
ctld.spawnedCratesRED[_crate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.droppedFOBCratesBLUE[_crate.crateUnit:getName()] = nil
|
|
ctld.spawnedCratesBLUE[_crate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
table.insert(_points, _crate.crateUnit:getPoint())
|
|
|
|
--destroy
|
|
_crate.crateUnit:destroy()
|
|
end
|
|
|
|
local _centroid = ctld.getCentroid(_points)
|
|
|
|
timer.scheduleFunction(function(_args)
|
|
|
|
local _unitId = ctld.getNextUnitId()
|
|
local _name = "Deployed FOB #" .. _unitId
|
|
|
|
local _fob = ctld.spawnFOB(_args[2], _unitId, _args[1], _name)
|
|
|
|
--make it able to deploy crates
|
|
table.insert(ctld.logisticUnits, _fob:getName())
|
|
|
|
ctld.beaconCount = ctld.beaconCount + 1
|
|
|
|
local _radioBeaconName = "FOB Beacon #" .. ctld.beaconCount
|
|
|
|
local _radioBeaconDetails = ctld.createRadioBeacon(_args[1], _args[3], _args[2], _radioBeaconName, nil, true)
|
|
|
|
ctld.fobBeacons[_name] = { vhf = _radioBeaconDetails.vhf, uhf = _radioBeaconDetails.uhf, fm = _radioBeaconDetails.fm }
|
|
|
|
if ctld.troopPickupAtFOB == true then
|
|
table.insert(ctld.builtFOBS, _fob:getName())
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("Finished building FOB! Crates and Troops can now be picked up."), 10)
|
|
else
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("Finished building FOB! Crates can now be picked up."), 10)
|
|
end
|
|
end, { _centroid, _heli:getCountry(), _heli:getCoalition() }, timer.getTime() + ctld.buildTimeFOB)
|
|
|
|
ctld.processCallback({unit = _heli, position = _centroid, action = "fob"})
|
|
|
|
trigger.action.smoke(_centroid, trigger.smokeColor.Green)
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 started building FOB using %2 FOB crates, it will be finished in %3 seconds.\nPosition marked with smoke.", ctld.getPlayerNameOrType(_heli), _totalCrates, ctld.buildTimeFOB, 10))
|
|
else
|
|
local _txt = ctld.i18n_translate("Cannot build FOB!\n\nIt requires %1 Large FOB crates ( 3 small FOB crates equal 1 large FOB Crate) and there are the equivalent of %2 large FOB crates nearby\n\nOr the crates are not within 750m of each other", ctld.cratesRequiredForFOB, _totalCrates)
|
|
ctld.displayMessageToGroup(_heli, _txt, 20)
|
|
end
|
|
end
|
|
|
|
--unloads the sling crate when the helicopter is on the ground or between 4.5 - 10 meters
|
|
function ctld.dropSlingCrate(_args)
|
|
local _unitName = _args[1]
|
|
local _heli = ctld.getTransportUnit(_unitName)
|
|
ctld.inTransitSlingLoadCrates[_unitName] = ctld.inTransitSlingLoadCrates[_unitName] or {}
|
|
|
|
if _heli == nil then
|
|
return -- no heli!
|
|
end
|
|
|
|
local _currentCrate = ctld.inTransitSlingLoadCrates[_unitName][#ctld.inTransitSlingLoadCrates[_unitName]]
|
|
|
|
if _currentCrate == nil then
|
|
if ctld.hoverPickup and ctld.loadCrateFromMenu then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You are not currently transporting any crates. \n\nTo Pickup a crate, hover for %1 seconds above the crate or land and use F10 Crate Commands.", ctld.hoverTime), 10)
|
|
elseif ctld.hoverPickup then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You are not currently transporting any crates. \n\nTo Pickup a crate, hover for %1 seconds above the crate.", ctld.hoverTime), 10)
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You are not currently transporting any crates. \n\nTo Pickup a crate, land and use F10 Crate Commands to load one."), 10)
|
|
end
|
|
else
|
|
local _point = _heli:getPoint()
|
|
local _side = _heli:getCoalition()
|
|
local _hdg = mist.getHeading(_heli, true)
|
|
local _heightDiff = ctld.heightDiff(_heli)
|
|
|
|
if _heightDiff > 50.0 then
|
|
table.remove(ctld.inTransitSlingLoadCrates[_unitName],#ctld.inTransitSlingLoadCrates[_unitName])
|
|
ctld.adaptWeightToCargo(_unitName)
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You were too high! The crate has been destroyed"), 10)
|
|
return
|
|
end
|
|
local _loadedCratesCopy = mist.utils.deepCopy(ctld.inTransitSlingLoadCrates[_unitName])
|
|
ctld.logTrace("_loadedCratesCopy = %s", ctld.p(_loadedCratesCopy))
|
|
for _, _crate in pairs(_loadedCratesCopy) do
|
|
ctld.logTrace("_crate = %s", ctld.p(_crate))
|
|
ctld.logTrace("ctld.inAir(_heli) = %s", ctld.p(ctld.inAir(_heli)))
|
|
ctld.logTrace("_heightDiff = %s", ctld.p(_heightDiff))
|
|
local _unitId = ctld.getNextUnitId()
|
|
local _name = string.format("%s #%i", _crate.desc, _unitId)
|
|
local _model_type = nil
|
|
if ctld.inAir(_heli) == false or _heightDiff <= 7.5 then
|
|
_point = ctld.getPointAt12Oclock(_heli, 30)
|
|
local _position = "12"
|
|
if ctld.unitDynamicCargoCapable(_heli) then
|
|
_model_type = "dynamic"
|
|
_point = ctld.getPointAt12Oclock(_heli, 30)
|
|
_position = "12"
|
|
end
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("%1 crate has been safely unhooked and is at your %2 o'clock", _crate.desc, _position), 10)
|
|
elseif _heightDiff > 7.5 and _heightDiff <= 50.0 then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("%1 crate has been safely dropped below you", _crate.desc), 10)
|
|
end
|
|
--remove crate from cargo
|
|
table.remove(ctld.inTransitSlingLoadCrates[_unitName],#ctld.inTransitSlingLoadCrates[_unitName])
|
|
ctld.spawnCrateStatic(_heli:getCountry(), _unitId, _point, _name, _crate.weight, _side, _hdg, _model_type)
|
|
end
|
|
ctld.adaptWeightToCargo(_unitName)
|
|
end
|
|
end
|
|
|
|
--spawns a radio beacon made up of two units,
|
|
-- one for VHF and one for UHF
|
|
-- The units are set to to NOT engage
|
|
function ctld.createRadioBeacon(_point, _coalition, _country, _name, _batteryTime, _isFOB)
|
|
|
|
local _freq = ctld.generateADFFrequencies()
|
|
|
|
--create timeout
|
|
local _battery
|
|
|
|
if _batteryTime == nil then
|
|
_battery = timer.getTime() + (ctld.deployedBeaconBattery * 60)
|
|
else
|
|
_battery = timer.getTime() + (_batteryTime * 60)
|
|
end
|
|
|
|
local _lat, _lon = coord.LOtoLL(_point)
|
|
|
|
local _latLngStr = mist.tostringLL(_lat, _lon, 3, ctld.location_DMS)
|
|
|
|
--local _mgrsString = mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(_point)), 5)
|
|
|
|
local _freqsText = _name
|
|
|
|
if _isFOB then
|
|
-- _message = "FOB " .. _message
|
|
_battery = -1 --never run out of power!
|
|
end
|
|
|
|
_freqsText = _freqsText .. " - " .. _latLngStr
|
|
|
|
|
|
_freqsText = string.format("%.2f kHz - %.2f / %.2f MHz", _freq.vhf / 1000, _freq.uhf / 1000000, _freq.fm / 1000000)
|
|
|
|
local _uhfGroup = ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
|
|
local _vhfGroup = ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
|
|
local _fmGroup = ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
|
|
|
|
local _beaconDetails = {
|
|
vhf = _freq.vhf,
|
|
vhfGroup = _vhfGroup:getName(),
|
|
uhf = _freq.uhf,
|
|
uhfGroup = _uhfGroup:getName(),
|
|
fm = _freq.fm,
|
|
fmGroup = _fmGroup:getName(),
|
|
text = _freqsText,
|
|
battery = _battery,
|
|
coalition = _coalition,
|
|
}
|
|
|
|
ctld.updateRadioBeacon(_beaconDetails)
|
|
|
|
table.insert(ctld.deployedRadioBeacons, _beaconDetails)
|
|
|
|
return _beaconDetails
|
|
end
|
|
|
|
function ctld.generateADFFrequencies()
|
|
|
|
if #ctld.freeUHFFrequencies <= 3 then
|
|
ctld.freeUHFFrequencies = ctld.usedUHFFrequencies
|
|
ctld.usedUHFFrequencies = {}
|
|
end
|
|
|
|
--remove frequency at RANDOM
|
|
local _uhf = table.remove(ctld.freeUHFFrequencies, math.random(#ctld.freeUHFFrequencies))
|
|
table.insert(ctld.usedUHFFrequencies, _uhf)
|
|
|
|
|
|
if #ctld.freeVHFFrequencies <= 3 then
|
|
ctld.freeVHFFrequencies = ctld.usedVHFFrequencies
|
|
ctld.usedVHFFrequencies = {}
|
|
end
|
|
|
|
local _vhf = table.remove(ctld.freeVHFFrequencies, math.random(#ctld.freeVHFFrequencies))
|
|
table.insert(ctld.usedVHFFrequencies, _vhf)
|
|
|
|
if #ctld.freeFMFrequencies <= 3 then
|
|
ctld.freeFMFrequencies = ctld.usedFMFrequencies
|
|
ctld.usedFMFrequencies = {}
|
|
end
|
|
|
|
local _fm = table.remove(ctld.freeFMFrequencies, math.random(#ctld.freeFMFrequencies))
|
|
table.insert(ctld.usedFMFrequencies, _fm)
|
|
|
|
return { uhf = _uhf, vhf = _vhf, fm = _fm }
|
|
--- return {uhf=_uhf,vhf=_vhf}
|
|
end
|
|
|
|
|
|
|
|
function ctld.spawnRadioBeaconUnit(_point, _country, _name, _freqsText)
|
|
|
|
local _groupId = ctld.getNextGroupId()
|
|
|
|
local _unitId = ctld.getNextUnitId()
|
|
|
|
local _radioGroup = {
|
|
["visible"] = false,
|
|
-- ["groupId"] = _groupId,
|
|
["hidden"] = false,
|
|
["units"] = {
|
|
[1] = {
|
|
["y"] = _point.z,
|
|
["type"] = "TACAN_beacon",
|
|
["name"] = "Unit #" .. _unitId .. " - " .. _name .. " [" .. _freqsText .. "]",
|
|
-- ["unitId"] = _unitId,
|
|
["heading"] = 0,
|
|
["playerCanDrive"] = true,
|
|
["skill"] = "Excellent",
|
|
["x"] = _point.x,
|
|
}
|
|
},
|
|
-- ["y"] = _positions[1].z,
|
|
-- ["x"] = _positions[1].x,
|
|
["name"] = "Group #" .. _groupId .. " - " .. _name,
|
|
["task"] = {},
|
|
--added two fields below for MIST
|
|
["category"] = Group.Category.GROUND,
|
|
["country"] = _country
|
|
}
|
|
|
|
-- return coalition.addGroup(_country, Group.Category.GROUND, _radioGroup)
|
|
return Group.getByName(mist.dynAdd(_radioGroup).name)
|
|
end
|
|
|
|
function ctld.updateRadioBeacon(_beaconDetails)
|
|
|
|
local _vhfGroup = Group.getByName(_beaconDetails.vhfGroup)
|
|
|
|
local _uhfGroup = Group.getByName(_beaconDetails.uhfGroup)
|
|
|
|
local _fmGroup = Group.getByName(_beaconDetails.fmGroup)
|
|
|
|
local _radioLoop = {}
|
|
|
|
if _vhfGroup ~= nil and _vhfGroup:getUnits() ~= nil and #_vhfGroup:getUnits() == 1 then
|
|
table.insert(_radioLoop, { group = _vhfGroup, freq = _beaconDetails.vhf, silent = false, mode = 0 })
|
|
end
|
|
|
|
if _uhfGroup ~= nil and _uhfGroup:getUnits() ~= nil and #_uhfGroup:getUnits() == 1 then
|
|
table.insert(_radioLoop, { group = _uhfGroup, freq = _beaconDetails.uhf, silent = true, mode = 0 })
|
|
end
|
|
|
|
if _fmGroup ~= nil and _fmGroup:getUnits() ~= nil and #_fmGroup:getUnits() == 1 then
|
|
table.insert(_radioLoop, { group = _fmGroup, freq = _beaconDetails.fm, silent = false, mode = 1 })
|
|
end
|
|
|
|
local _batLife = _beaconDetails.battery - timer.getTime()
|
|
|
|
if (_batLife <= 0 and _beaconDetails.battery ~= -1) or #_radioLoop ~= 3 then
|
|
-- ran out of batteries
|
|
if _vhfGroup ~= nil then
|
|
trigger.action.stopRadioTransmission(_vhfGroup:getName())
|
|
_vhfGroup:destroy()
|
|
end
|
|
if _uhfGroup ~= nil then
|
|
trigger.action.stopRadioTransmission(_uhfGroup:getName())
|
|
_uhfGroup:destroy()
|
|
end
|
|
if _fmGroup ~= nil then
|
|
trigger.action.stopRadioTransmission(_fmGroup:getName())
|
|
_fmGroup:destroy()
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--fobs have unlimited battery life
|
|
-- if _battery ~= -1 then
|
|
-- _text = _text.." "..mist.utils.round(_batLife).." seconds of battery"
|
|
-- end
|
|
|
|
for _, _radio in pairs(_radioLoop) do
|
|
|
|
local _groupController = _radio.group:getController()
|
|
|
|
local _sound = ctld.radioSound
|
|
if _radio.silent then
|
|
_sound = ctld.radioSoundFC3
|
|
end
|
|
|
|
_sound = "l10n/DEFAULT/".._sound
|
|
|
|
_groupController:setOption(AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD)
|
|
|
|
|
|
-- stop the transmission at each call to the ctld.updateRadioBeacon method (default each minute)
|
|
trigger.action.stopRadioTransmission(_radio.group:getName())
|
|
|
|
-- restart it as the battery is still up
|
|
-- the transmission is set to loop and has the name of the transmitting DCS group (that includes the type - i.e. FM, UHF, VHF)
|
|
trigger.action.radioTransmission(_sound, _radio.group:getUnit(1):getPoint(), _radio.mode, true, _radio.freq, 1000, _radio.group:getName())
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function ctld.listRadioBeacons(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
local _message = ""
|
|
|
|
if _heli ~= nil then
|
|
|
|
for _x, _details in pairs(ctld.deployedRadioBeacons) do
|
|
|
|
if _details.coalition == _heli:getCoalition() then
|
|
_message = _message .. _details.text .. "\n"
|
|
end
|
|
end
|
|
|
|
if _message ~= "" then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Radio Beacons:\n%1", _message), 20)
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("No Active Radio Beacons"), 20)
|
|
end
|
|
end
|
|
end
|
|
|
|
function ctld.dropRadioBeacon(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
local _message = ""
|
|
|
|
if _heli ~= nil and ctld.inAir(_heli) == false then
|
|
|
|
--deploy 50 m infront
|
|
--try to spawn at 12 oclock to us
|
|
local _point = ctld.getPointAt12Oclock(_heli, 50)
|
|
|
|
ctld.beaconCount = ctld.beaconCount + 1
|
|
local _name = "Beacon #" .. ctld.beaconCount
|
|
|
|
local _radioBeaconDetails = ctld.createRadioBeacon(_point, _heli:getCoalition(), _heli:getCountry(), _name, nil, false)
|
|
|
|
-- mark with flare?
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 deployed a Radio Beacon.\n\n%2", ctld.getPlayerNameOrType(_heli), _radioBeaconDetails.text, 20))
|
|
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You need to land before you can deploy a Radio Beacon!"), 20)
|
|
end
|
|
end
|
|
|
|
--remove closet radio beacon
|
|
function ctld.removeRadioBeacon(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
local _message = ""
|
|
|
|
if _heli ~= nil and ctld.inAir(_heli) == false then
|
|
|
|
-- mark with flare?
|
|
|
|
local _closestBeacon = nil
|
|
local _shortestDistance = -1
|
|
local _distance = 0
|
|
|
|
for _x, _details in pairs(ctld.deployedRadioBeacons) do
|
|
|
|
if _details.coalition == _heli:getCoalition() then
|
|
|
|
local _group = Group.getByName(_details.vhfGroup)
|
|
|
|
if _group ~= nil and #_group:getUnits() == 1 then
|
|
|
|
_distance = ctld.getDistance(_heli:getPoint(), _group:getUnit(1):getPoint())
|
|
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
|
|
_shortestDistance = _distance
|
|
_closestBeacon = _details
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if _closestBeacon ~= nil and _shortestDistance then
|
|
local _vhfGroup = Group.getByName(_closestBeacon.vhfGroup)
|
|
|
|
local _uhfGroup = Group.getByName(_closestBeacon.uhfGroup)
|
|
|
|
local _fmGroup = Group.getByName(_closestBeacon.fmGroup)
|
|
|
|
if _vhfGroup ~= nil then
|
|
trigger.action.stopRadioTransmission(_vhfGroup:getName())
|
|
_vhfGroup:destroy()
|
|
end
|
|
if _uhfGroup ~= nil then
|
|
trigger.action.stopRadioTransmission(_uhfGroup:getName())
|
|
_uhfGroup:destroy()
|
|
end
|
|
if _fmGroup ~= nil then
|
|
trigger.action.stopRadioTransmission(_fmGroup:getName())
|
|
_fmGroup:destroy()
|
|
end
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 removed a Radio Beacon.\n\n%2", ctld.getPlayerNameOrType(_heli), _closestBeacon.text, 20))
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("No Radio Beacons within 500m."), 20)
|
|
end
|
|
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("You need to land before remove a Radio Beacon"), 20)
|
|
end
|
|
end
|
|
|
|
-- gets the center of a bunch of points!
|
|
-- return proper DCS point with height
|
|
function ctld.getCentroid(_points)
|
|
local _tx, _ty = 0, 0
|
|
for _index, _point in ipairs(_points) do
|
|
_tx = _tx + _point.x
|
|
_ty = _ty + _point.z
|
|
end
|
|
|
|
local _npoints = #_points
|
|
|
|
local _point = { x = _tx / _npoints, z = _ty / _npoints }
|
|
|
|
_point.y = land.getHeight({ _point.x, _point.z })
|
|
|
|
return _point
|
|
end
|
|
|
|
function ctld.getAATemplate(_unitName)
|
|
|
|
for _,_system in pairs(ctld.AASystemTemplate) do
|
|
|
|
if _system.repair == _unitName then
|
|
return _system
|
|
end
|
|
|
|
for _,_part in pairs(_system.parts) do
|
|
|
|
if _unitName == _part.name then
|
|
return _system
|
|
end
|
|
end
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
function ctld.getLauncherUnitFromAATemplate(_aaTemplate)
|
|
for _,_part in pairs(_aaTemplate.parts) do
|
|
|
|
if _part.launcher then
|
|
return _part.name
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function ctld.rearmAASystem(_heli, _nearestCrate, _nearbyCrates, _aaSystemTemplate)
|
|
|
|
-- are we adding to existing aa system?
|
|
-- check to see if the crate is a launcher
|
|
if ctld.getLauncherUnitFromAATemplate(_aaSystemTemplate) == _nearestCrate.details.unit then
|
|
|
|
-- find nearest COMPLETE AA system
|
|
local _nearestSystem = ctld.findNearestAASystem(_heli, _aaSystemTemplate)
|
|
|
|
if _nearestSystem ~= nil and _nearestSystem.dist < 300 then
|
|
|
|
local _uniqueTypes = {} -- stores each unique part of system
|
|
local _types = {}
|
|
local _points = {}
|
|
local _hdgs = {}
|
|
|
|
local _units = _nearestSystem.group:getUnits()
|
|
|
|
if _units ~= nil and #_units > 0 then
|
|
|
|
for x = 1, #_units do
|
|
if _units[x]:getLife() > 0 then
|
|
|
|
--this allows us to count each type once
|
|
_uniqueTypes[_units[x]:getTypeName()] = _units[x]:getTypeName()
|
|
|
|
table.insert(_points, _units[x]:getPoint())
|
|
table.insert(_types, _units[x]:getTypeName())
|
|
table.insert(_hdgs, mist.getHeading(_units[x], true))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- do we have the correct number of unique pieces and do we have enough points for all the pieces
|
|
if ctld.countTableEntries(_uniqueTypes) == _aaSystemTemplate.count and #_points >= _aaSystemTemplate.count then
|
|
|
|
-- rearm aa system
|
|
-- destroy old group
|
|
ctld.completeAASystems[_nearestSystem.group:getName()] = nil
|
|
|
|
_nearestSystem.group:destroy()
|
|
|
|
local _spawnedGroup = ctld.spawnCrateGroup(_heli, _points, _types, _hdgs)
|
|
|
|
ctld.completeAASystems[_spawnedGroup:getName()] = ctld.getAASystemDetails(_spawnedGroup, _aaSystemTemplate)
|
|
|
|
ctld.processCallback({unit = _heli, crate = _nearestCrate , spawnedGroup = _spawnedGroup, action = "rearm"})
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 successfully rearmed a full %2 in the field", ctld.getPlayerNameOrType(_heli), _aaSystemTemplate.name, 20))
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
ctld.spawnedCratesRED[_nearestCrate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.spawnedCratesBLUE[_nearestCrate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
-- remove crate
|
|
-- if ctld.slingLoad == false then
|
|
_nearestCrate.crateUnit:destroy()
|
|
-- end
|
|
|
|
return true -- all done so quit
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function ctld.getAASystemDetails(_hawkGroup,_aaSystemTemplate)
|
|
|
|
local _units = _hawkGroup:getUnits()
|
|
|
|
local _hawkDetails = {}
|
|
|
|
for _, _unit in pairs(_units) do
|
|
table.insert(_hawkDetails, { point = _unit:getPoint(), unit = _unit:getTypeName(), name = _unit:getName(), system =_aaSystemTemplate, hdg = mist.getHeading(_unit, true) })
|
|
end
|
|
|
|
return _hawkDetails
|
|
end
|
|
|
|
function ctld.countTableEntries(_table)
|
|
|
|
if _table == nil then
|
|
return 0
|
|
end
|
|
|
|
|
|
local _count = 0
|
|
|
|
for _key, _value in pairs(_table) do
|
|
|
|
_count = _count + 1
|
|
end
|
|
|
|
return _count
|
|
end
|
|
|
|
function ctld.unpackAASystem(_heli, _nearestCrate, _nearbyCrates,_aaSystemTemplate)
|
|
ctld.logTrace("_nearestCrate = %s", ctld.p(_nearestCrate))
|
|
ctld.logTrace("_nearbyCrates = %s", ctld.p(_nearbyCrates))
|
|
ctld.logTrace("_aaSystemTemplate = %s", ctld.p(_aaSystemTemplate))
|
|
|
|
if ctld.rearmAASystem(_heli, _nearestCrate, _nearbyCrates,_aaSystemTemplate) then
|
|
-- rearmed system
|
|
return
|
|
end
|
|
|
|
local _systemParts = {}
|
|
|
|
--initialise list of parts
|
|
for _,_part in pairs(_aaSystemTemplate.parts) do
|
|
local _systemPart = {name = _part.name, desc = _part.desc, launcher = _part.launcher, amount = _part.amount, NoCrate = _part.NoCrate, found = 0, required = 1}
|
|
-- if the part is a NoCrate required, it's found by default
|
|
if _systemPart.NoCrate ~= nil then
|
|
_systemPart.found = 1
|
|
end
|
|
_systemParts[_part.name] = _systemPart
|
|
end
|
|
|
|
local _cratePositions = {}
|
|
local _crateHdg = {}
|
|
|
|
local crateDistance = 500
|
|
|
|
-- find all crates close enough and add them to the list if they're part of the AA System
|
|
for _, _nearbyCrate in pairs(_nearbyCrates) do
|
|
ctld.logTrace("_nearbyCrate = %s", ctld.p(_nearbyCrate))
|
|
if _nearbyCrate.dist < crateDistance then
|
|
|
|
local _name = _nearbyCrate.details.unit
|
|
ctld.logTrace("_name = %s", ctld.p(_name))
|
|
|
|
if _systemParts[_name] ~= nil then
|
|
|
|
local foundCount = _systemParts[_name].found
|
|
ctld.logTrace("foundCount = %s", ctld.p(foundCount))
|
|
|
|
if not _cratePositions[_name] then
|
|
_cratePositions[_name] = {}
|
|
end
|
|
if not _crateHdg[_name] then
|
|
_crateHdg[_name] = {}
|
|
end
|
|
|
|
-- if this is our first time encountering this part of the system
|
|
if foundCount == 0 then
|
|
local _foundPart = _systemParts[_name]
|
|
|
|
_foundPart.found = 1
|
|
|
|
-- store the number of crates required to compute how many crates will have to be removed later and to see if the system can be deployed
|
|
local cratesRequired = _nearbyCrate.details.cratesRequired
|
|
ctld.logTrace("cratesRequired = %s", ctld.p(cratesRequired))
|
|
if cratesRequired ~= nil then
|
|
_foundPart.required = cratesRequired
|
|
end
|
|
|
|
_systemParts[_name] = _foundPart
|
|
else
|
|
-- otherwise, we found another crate for the same part
|
|
_systemParts[_name].found = foundCount + 1
|
|
end
|
|
|
|
-- add the crate to the part info along with it's position and heading
|
|
local crateUnit = _nearbyCrate.crateUnit
|
|
if not _systemParts[_name].crates then
|
|
_systemParts[_name].crates = {}
|
|
end
|
|
table.insert(_systemParts[_name].crates, _nearbyCrate)
|
|
table.insert(_cratePositions[_name], crateUnit:getPoint())
|
|
table.insert(_crateHdg[_name], mist.getHeading(crateUnit, true))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Compute the centroids for each type of crates and then the centroid of all the system crates which is used to find the spawn location for each part and a position for the NoCrate parts respectively
|
|
-- One issue, all crates are considered for the centroid and the headings but not all of them may be used if crate stacking is allowed
|
|
local _crateCentroids = {}
|
|
local _idxCentroids = {}
|
|
for _partName, _partPositions in pairs(_cratePositions) do
|
|
_crateCentroids[_partName] = ctld.getCentroid(_partPositions)
|
|
table.insert(_idxCentroids, _crateCentroids[_partName])
|
|
end
|
|
local _crateCentroid = ctld.getCentroid(_idxCentroids)
|
|
|
|
-- Compute the average heading for each type of crates to know the heading to spawn the part
|
|
local _aveHdg = {}
|
|
-- Headings of each group of crates
|
|
for _partName, _crateHeadings in pairs(_crateHdg) do
|
|
local crateCount = #_crateHeadings
|
|
_aveHdg[_partName] = 0
|
|
-- Heading of each crate within a group
|
|
for _index, _crateHeading in pairs(_crateHeadings) do
|
|
_aveHdg[_partName] = _crateHeading / crateCount + _aveHdg[_partName]
|
|
end
|
|
end
|
|
|
|
local spawnDistance = 30 -- circle radius to spawn units in a circle and randomize position relative to the crate location
|
|
local arcRad = math.pi * 2
|
|
|
|
local _txt = ""
|
|
|
|
local _posArray = {}
|
|
local _hdgArray = {}
|
|
local _typeArray = {}
|
|
-- for each part of the system parts
|
|
for _name, _systemPart in pairs(_systemParts) do
|
|
|
|
-- check if enough crates were found to build the part
|
|
if _systemPart.found < _systemPart.required then
|
|
_txt = _txt..ctld.i18n_translate("Missing %1\n",_systemPart.desc)
|
|
else
|
|
-- use the centroid of the crates for this part as a spawn location
|
|
local _point = _crateCentroids[_name]
|
|
-- in the case this centroid does not exist (NoCrate), use the centroid of all crates found and add some randomness
|
|
if _point == nil then
|
|
_point = _crateCentroid
|
|
_point = { x = _point.x + math.random(0,3)*spawnDistance, y = _point.y, z = _point.z + math.random(0,3)*spawnDistance}
|
|
end
|
|
|
|
-- use the average heading to spawn the part at
|
|
local _hdg = _aveHdg[_name]
|
|
-- if non are found (NoCrate), random heading
|
|
if _hdg == nil then
|
|
_hdg = math.random(0, arcRad)
|
|
end
|
|
|
|
-- search for the amount of times this part needs to be spawned, by default 1 for any unit and aaLaunchers for launchers
|
|
local partAmount = 1
|
|
if _systemPart.amount == nil then
|
|
if _systemPart.launcher ~= nil then
|
|
partAmount = ctld.aaLaunchers
|
|
end
|
|
else
|
|
-- but the amount may also be specified in the template
|
|
partAmount = _systemPart.amount
|
|
end
|
|
-- if crate stacking is allowed, then find the multiplication factor for the amount depending on how many crates are required and how many were found
|
|
if ctld.AASystemCrateStacking then
|
|
_systemPart.amountFactor = _systemPart.found - _systemPart.found%_systemPart.required
|
|
else
|
|
_systemPart.amountFactor = 1
|
|
end
|
|
partAmount = partAmount * _systemPart.amountFactor
|
|
|
|
--handle multiple units per part by spawning them in a circle around the crate
|
|
if partAmount > 1 then
|
|
|
|
local angular_step = arcRad / partAmount
|
|
|
|
for _i = 1, partAmount do
|
|
local _angle = (angular_step * (_i - 1) + _hdg)%arcRad
|
|
local _xOffset = math.cos(_angle) * spawnDistance
|
|
local _yOffset = math.sin(_angle) * spawnDistance
|
|
|
|
table.insert(_posArray, { x = _point.x + _xOffset, y = _point.y, z = _point.z + _yOffset })
|
|
table.insert(_hdgArray, _angle) -- also spawn them perpendicular to that point of the circle
|
|
table.insert(_typeArray, _name)
|
|
end
|
|
else
|
|
table.insert(_posArray, _point)
|
|
table.insert(_hdgArray, _hdg)
|
|
table.insert(_typeArray, _name)
|
|
end
|
|
end
|
|
end
|
|
|
|
local _activeLaunchers = ctld.countCompleteAASystems(_heli)
|
|
|
|
local _allowed = ctld.getAllowedAASystems(_heli)
|
|
|
|
env.info("Active: ".._activeLaunchers.." Allowed: ".._allowed)
|
|
|
|
if _activeLaunchers + 1 > _allowed then
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("Out of parts for AA Systems. Current limit is %1\n", _allowed, 10))
|
|
return
|
|
end
|
|
|
|
if _txt ~= "" then
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Cannot build %1\n%2\n\nOr the crates are not close enough together", _aaSystemTemplate.name, _txt), 20)
|
|
return
|
|
else
|
|
|
|
-- destroy crates
|
|
for _name, _systemPart in pairs(_systemParts) do
|
|
-- if there is a crate to delete in the first place
|
|
if _systemPart.NoCrate ~= true then
|
|
-- figure out how many crates to delete since we searched for as many as possible, not all of them might have been used
|
|
local amountToDel = _systemPart.amountFactor*_systemPart.required
|
|
local DelCounter = 0
|
|
|
|
-- for each crate found for this part
|
|
for _index, _crate in pairs(_systemPart.crates) do
|
|
-- if we still need to delete some crates
|
|
if DelCounter < amountToDel then
|
|
if _heli:getCoalition() == 1 then
|
|
ctld.spawnedCratesRED[_crate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.spawnedCratesBLUE[_crate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
--destroy
|
|
-- if ctld.slingLoad == false then
|
|
_crate.crateUnit:destroy()
|
|
DelCounter = DelCounter + 1 -- count up for one more crate has been deleted
|
|
--end
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- HAWK / BUK READY!
|
|
local _spawnedGroup = ctld.spawnCrateGroup(_heli, _posArray, _typeArray, _hdgArray)
|
|
|
|
ctld.completeAASystems[_spawnedGroup:getName()] = ctld.getAASystemDetails(_spawnedGroup,_aaSystemTemplate)
|
|
|
|
ctld.processCallback({unit = _heli, crate = _nearestCrate , spawnedGroup = _spawnedGroup, action = "unpack"})
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 successfully deployed a full %2 in the field. \n\nAA Active System limit is: %3\nActive: %4", ctld.getPlayerNameOrType(_heli), _aaSystemTemplate.name, _allowed, (_activeLaunchers+1)), 10)
|
|
end
|
|
end
|
|
|
|
--count the number of captured cities, sets the amount of allowed AA Systems
|
|
function ctld.getAllowedAASystems(_heli)
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
return ctld.AASystemLimitBLUE
|
|
else
|
|
return ctld.AASystemLimitRED
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
function ctld.countCompleteAASystems(_heli)
|
|
|
|
local _count = 0
|
|
|
|
for _groupName, _hawkDetails in pairs(ctld.completeAASystems) do
|
|
|
|
local _hawkGroup = Group.getByName(_groupName)
|
|
|
|
-- env.info(_groupName..": "..mist.utils.tableShow(_hawkDetails))
|
|
if _hawkGroup ~= nil and _hawkGroup:getCoalition() == _heli:getCoalition() then
|
|
|
|
local _units = _hawkGroup:getUnits()
|
|
|
|
if _units ~=nil and #_units > 0 then
|
|
--get the system template
|
|
local _aaSystemTemplate = _hawkDetails[1].system
|
|
|
|
local _uniqueTypes = {} -- stores each unique part of system
|
|
local _types = {}
|
|
local _points = {}
|
|
|
|
if _units ~= nil and #_units > 0 then
|
|
|
|
for x = 1, #_units do
|
|
if _units[x]:getLife() > 0 then
|
|
|
|
--this allows us to count each type once
|
|
_uniqueTypes[_units[x]:getTypeName()] = _units[x]:getTypeName()
|
|
|
|
table.insert(_points, _units[x]:getPoint())
|
|
table.insert(_types, _units[x]:getTypeName())
|
|
end
|
|
end
|
|
end
|
|
|
|
-- do we have the correct number of unique pieces and do we have enough points for all the pieces
|
|
if ctld.countTableEntries(_uniqueTypes) == _aaSystemTemplate.count and #_points >= _aaSystemTemplate.count then
|
|
_count = _count +1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return _count
|
|
end
|
|
|
|
|
|
function ctld.repairAASystem(_heli, _nearestCrate,_aaSystem)
|
|
|
|
-- find nearest COMPLETE AA system
|
|
local _nearestHawk = ctld.findNearestAASystem(_heli,_aaSystem)
|
|
|
|
|
|
|
|
if _nearestHawk ~= nil and _nearestHawk.dist < 300 then
|
|
|
|
local _oldHawk = ctld.completeAASystems[_nearestHawk.group:getName()]
|
|
|
|
--spawn new one
|
|
|
|
local _types = {}
|
|
local _hdgs = {}
|
|
local _points = {}
|
|
|
|
for _, _part in pairs(_oldHawk) do
|
|
table.insert(_points, _part.point)
|
|
table.insert(_hdgs, _part.hdg)
|
|
table.insert(_types, _part.unit)
|
|
end
|
|
|
|
--remove old system
|
|
ctld.completeAASystems[_nearestHawk.group:getName()] = nil
|
|
_nearestHawk.group:destroy()
|
|
|
|
local _spawnedGroup = ctld.spawnCrateGroup(_heli, _points, _types, _hdgs)
|
|
|
|
ctld.completeAASystems[_spawnedGroup:getName()] = ctld.getAASystemDetails(_spawnedGroup,_aaSystem)
|
|
|
|
ctld.processCallback({unit = _heli, crate = _nearestCrate , spawnedGroup = _spawnedGroup, action = "repair"})
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 successfully repaired a full %2 in the field.", ctld.getPlayerNameOrType(_heli), _aaSystem.name), 10)
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
ctld.spawnedCratesRED[_nearestCrate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.spawnedCratesBLUE[_nearestCrate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
-- remove crate
|
|
-- if ctld.slingLoad == false then
|
|
_nearestCrate.crateUnit:destroy()
|
|
-- end
|
|
|
|
else
|
|
ctld.displayMessageToGroup(_heli, ctld.i18n_translate("Cannot repair %1. No damaged %2 within 300m", _aaSystem.name, _aaSystem.name), 10)
|
|
end
|
|
end
|
|
|
|
function ctld.unpackMultiCrate(_heli, _nearestCrate, _nearbyCrates)
|
|
|
|
-- unpack multi crate
|
|
local _nearbyMultiCrates = {}
|
|
|
|
for _, _nearbyCrate in pairs(_nearbyCrates) do
|
|
|
|
if _nearbyCrate.dist < 300 then
|
|
|
|
if _nearbyCrate.details.unit == _nearestCrate.details.unit then
|
|
|
|
table.insert(_nearbyMultiCrates, _nearbyCrate)
|
|
|
|
if #_nearbyMultiCrates == _nearestCrate.details.cratesRequired then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- check crate count
|
|
if #_nearbyMultiCrates == _nearestCrate.details.cratesRequired then
|
|
|
|
local _point = _nearestCrate.crateUnit:getPoint()
|
|
local _crateHdg = mist.getHeading(_nearestCrate.crateUnit, true)
|
|
|
|
-- destroy crates
|
|
for _, _crate in pairs(_nearbyMultiCrates) do
|
|
|
|
if _point == nil then
|
|
_point = _crate.crateUnit:getPoint()
|
|
end
|
|
|
|
if _heli:getCoalition() == 1 then
|
|
ctld.spawnedCratesRED[_crate.crateUnit:getName()] = nil
|
|
else
|
|
ctld.spawnedCratesBLUE[_crate.crateUnit:getName()] = nil
|
|
end
|
|
|
|
--destroy
|
|
-- if ctld.slingLoad == false then
|
|
_crate.crateUnit:destroy()
|
|
-- end
|
|
end
|
|
|
|
|
|
local _spawnedGroup = ctld.spawnCrateGroup(_heli, { _point }, { _nearestCrate.details.unit }, { _crateHdg })
|
|
if _spawnedGroup == nil then
|
|
ctld.logError("ctld.unpackMultiCrate group was not spawned - skipping setGrpROE")
|
|
else
|
|
ctld.setGrpROE(_spawnedGroup)
|
|
ctld.processCallback({unit = _heli, crate = _nearestCrate , spawnedGroup = _spawnedGroup, action = "unpack"})
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 successfully deployed %2 to the field using %3 crates.", ctld.getPlayerNameOrType(_heli), _nearestCrate.details.desc, #_nearbyMultiCrates), 10)
|
|
end
|
|
else
|
|
|
|
local _txt = ctld.i18n_translate("Cannot build %1!\n\nIt requires %2 crates and there are %3 \n\nOr the crates are not within 300m of each other", _nearestCrate.details.desc, _nearestCrate.details.cratesRequired, #_nearbyMultiCrates)
|
|
|
|
ctld.displayMessageToGroup(_heli, _txt, 20)
|
|
end
|
|
end
|
|
|
|
|
|
function ctld.spawnCrateGroup(_heli, _positions, _types, _hdgs)
|
|
local _id = ctld.getNextGroupId()
|
|
local _groupName = _types[1] .. " #" .. _id
|
|
local _side = _heli:getCoalition()
|
|
local _group = {
|
|
["visible"] = false,
|
|
-- ["groupId"] = _id,
|
|
["hidden"] = false,
|
|
["units"] = {},
|
|
-- ["y"] = _positions[1].z,
|
|
-- ["x"] = _positions[1].x,
|
|
["name"] = _groupName,
|
|
["tasks"] = {},
|
|
["radioSet"] = false,
|
|
["task"] = "Reconnaissance",
|
|
["route"] = {},
|
|
}
|
|
|
|
local _hdg = 120 * math.pi / 180 -- radians = 120 degrees
|
|
if _types[1] ~= "MQ-9 Reaper" and _types[1] ~= "WingLoong-I" then -- non-drones - JTAC
|
|
local _spreadMin = 0
|
|
local _spreadMax = 0
|
|
local _spreadMult = 1
|
|
for _i, _pos in ipairs(_positions) do
|
|
local _unitId = ctld.getNextUnitId()
|
|
local _details = { type = _types[_i], unitId = _unitId, name = string.format("Unpacked %s #%i", _types[_i], _unitId) }
|
|
|
|
if _hdgs and _hdgs[_i] then
|
|
_hdg = _hdgs[_i]
|
|
end
|
|
|
|
_group.units[_i] = ctld.createUnit(_pos.x + 0, _pos.z + 0, mist.getHeading(_heli, true), _details)
|
|
end
|
|
_group.category = Group.Category.GROUND
|
|
else -- drones - JTAC
|
|
local _unitId = ctld.getNextUnitId()
|
|
local _details = { type = _types[1],
|
|
unitId = _unitId,
|
|
name = string.format("Unpacked %s #%i", _types[1], _unitId),
|
|
livery_id = "'camo' scheme",
|
|
skill = "High",
|
|
speed = 100,
|
|
payload = { pylons = {}, fuel = 1300, flare = 0, chaff = 0, gun = 100 } }
|
|
|
|
_group.units[1] = ctld.createUnit( _positions[1].x,
|
|
_positions[1].z + ctld.jtacDroneRadius,
|
|
_hdg,
|
|
_details)
|
|
|
|
_group.category = Group.Category.AIRPLANE -- for drones
|
|
|
|
-- create drone orbiting route
|
|
local DroneRoute = {
|
|
["points"] =
|
|
{
|
|
[1] =
|
|
{
|
|
["alt"] = 7000,
|
|
["action"] = "Turning Point",
|
|
["alt_type"] = "BARO",
|
|
["properties"] =
|
|
{
|
|
["addopt"] = {},
|
|
}, -- end of ["properties"]
|
|
["speed"] = 100,
|
|
["task"] =
|
|
{
|
|
["id"] = "ComboTask",
|
|
["params"] =
|
|
{
|
|
["tasks"] =
|
|
{
|
|
[1] =
|
|
{
|
|
["enabled"] = true,
|
|
["auto"] = false,
|
|
["id"] = "WrappedAction",
|
|
["number"] = 1,
|
|
["params"] =
|
|
{
|
|
["action"] =
|
|
{
|
|
["id"] = "EPLRS",
|
|
["params"] =
|
|
{
|
|
["value"] = true,
|
|
["groupId"] = 0,
|
|
}, -- end of ["params"]
|
|
}, -- end of ["action"]
|
|
}, -- end of ["params"]
|
|
}, -- end of [1]
|
|
[2] =
|
|
{
|
|
["number"] = 2,
|
|
["auto"] = false,
|
|
["id"] = "Orbit",
|
|
["enabled"] = true,
|
|
["params"] =
|
|
{
|
|
["altitude"] = ctld.jtacDroneAltitude,
|
|
["pattern"] = "Circle",
|
|
["speed"] = 125,
|
|
}, -- end of ["params"]
|
|
}, -- end of [2]
|
|
[3] =
|
|
{
|
|
["enabled"] = true,
|
|
["auto"] = false,
|
|
["id"] = "WrappedAction",
|
|
["number"] = 3,
|
|
["params"] =
|
|
{
|
|
["action"] =
|
|
{
|
|
["id"] = "Option",
|
|
["params"] =
|
|
{
|
|
["value"] = true,
|
|
["name"] = 6,
|
|
}, -- end of ["params"]
|
|
}, -- end of ["action"]
|
|
}, -- end of ["params"]
|
|
}, -- end of [3]
|
|
}, -- end of ["tasks"]
|
|
}, -- end of ["params"]
|
|
}, -- end of ["task"]
|
|
["type"] = "Turning Point",
|
|
["ETA"] = 0,
|
|
["ETA_locked"] = true,
|
|
["y"] = _positions[1].z,
|
|
["x"] = _positions[1].x,
|
|
["speed_locked"] = true,
|
|
["formation_template"] = "",
|
|
}, -- end of [1]
|
|
}, -- end of ["points"]
|
|
} -- end of ["route"]
|
|
---------------------------------------------------------------------------------
|
|
_group.route = DroneRoute
|
|
end
|
|
|
|
_group.country = _heli:getCountry()
|
|
local _spawnedGroup = Group.getByName(mist.dynAdd(_group).name)
|
|
return _spawnedGroup
|
|
end
|
|
|
|
|
|
|
|
-- spawn normal group
|
|
function ctld.spawnDroppedGroup(_point, _details, _spawnBehind, _maxSearch)
|
|
|
|
local _groupName = _details.groupName
|
|
|
|
local _group = {
|
|
["visible"] = false,
|
|
-- ["groupId"] = _details.groupId,
|
|
["hidden"] = false,
|
|
["units"] = {},
|
|
-- ["y"] = _positions[1].z,
|
|
-- ["x"] = _positions[1].x,
|
|
["name"] = _groupName,
|
|
["task"] = {},
|
|
}
|
|
|
|
|
|
if _spawnBehind == false then
|
|
|
|
-- spawn in circle around heli
|
|
|
|
local _pos = _point
|
|
|
|
for _i, _detail in ipairs(_details.units) do
|
|
|
|
local _angle = math.pi * 2 * (_i - 1) / #_details.units
|
|
local _xOffset = math.cos(_angle) * 30
|
|
local _yOffset = math.sin(_angle) * 30
|
|
|
|
_group.units[_i] = ctld.createUnit(_pos.x + _xOffset, _pos.z + _yOffset, _angle, _detail)
|
|
end
|
|
|
|
else
|
|
|
|
local _pos = _point
|
|
|
|
--try to spawn at 6 oclock to us
|
|
local _angle = math.atan2(_pos.z, _pos.x)
|
|
local _xOffset = math.cos(_angle) * -30
|
|
local _yOffset = math.sin(_angle) * -30
|
|
|
|
|
|
for _i, _detail in ipairs(_details.units) do
|
|
_group.units[_i] = ctld.createUnit(_pos.x + (_xOffset + 10 * _i), _pos.z + (_yOffset + 10 * _i), _angle, _detail)
|
|
end
|
|
end
|
|
|
|
--switch to MIST
|
|
_group.category = Group.Category.GROUND;
|
|
_group.country = _details.country;
|
|
|
|
local _spawnedGroup = Group.getByName(mist.dynAdd(_group).name)
|
|
|
|
--local _spawnedGroup = coalition.addGroup(_details.country, Group.Category.GROUND, _group)
|
|
|
|
|
|
-- find nearest enemy and head there
|
|
if _maxSearch == nil then
|
|
_maxSearch = ctld.maximumSearchDistance
|
|
end
|
|
|
|
local _wpZone = ctld.inWaypointZone(_point,_spawnedGroup:getCoalition())
|
|
|
|
if _wpZone.inZone then
|
|
ctld.orderGroupToMoveToPoint(_spawnedGroup:getUnit(1), _wpZone.point)
|
|
env.info("Heading to waypoint - In Zone ".._wpZone.name)
|
|
else
|
|
local _enemyPos = ctld.findNearestEnemy(_details.side, _point, _maxSearch)
|
|
|
|
ctld.orderGroupToMoveToPoint(_spawnedGroup:getUnit(1), _enemyPos)
|
|
end
|
|
|
|
return _spawnedGroup
|
|
end
|
|
|
|
function ctld.findNearestEnemy(_side, _point, _searchDistance)
|
|
|
|
local _closestEnemy = nil
|
|
|
|
local _groups
|
|
|
|
local _closestEnemyDist = _searchDistance
|
|
|
|
local _heliPoint = _point
|
|
|
|
if _side == 2 then
|
|
_groups = coalition.getGroups(1, Group.Category.GROUND)
|
|
else
|
|
_groups = coalition.getGroups(2, Group.Category.GROUND)
|
|
end
|
|
|
|
for _, _group in pairs(_groups) do
|
|
|
|
if _group ~= nil then
|
|
local _units = _group:getUnits()
|
|
|
|
if _units ~= nil and #_units > 0 then
|
|
|
|
local _leader = nil
|
|
|
|
-- find alive leader
|
|
for x = 1, #_units do
|
|
if _units[x]:getLife() > 0 then
|
|
_leader = _units[x]
|
|
break
|
|
end
|
|
end
|
|
|
|
if _leader ~= nil then
|
|
local _leaderPos = _leader:getPoint()
|
|
local _dist = ctld.getDistance(_heliPoint, _leaderPos)
|
|
if _dist < _closestEnemyDist then
|
|
_closestEnemyDist = _dist
|
|
_closestEnemy = _leaderPos
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- no enemy - move to random point
|
|
if _closestEnemy ~= nil then
|
|
|
|
-- env.info("found enemy")
|
|
return _closestEnemy
|
|
else
|
|
|
|
local _x = _heliPoint.x + math.random(0, ctld.maximumMoveDistance) - math.random(0, ctld.maximumMoveDistance)
|
|
local _z = _heliPoint.z + math.random(0, ctld.maximumMoveDistance) - math.random(0, ctld.maximumMoveDistance)
|
|
local _y = _heliPoint.y + math.random(0, ctld.maximumMoveDistance) - math.random(0, ctld.maximumMoveDistance)
|
|
|
|
return { x = _x, z = _z,y=_y }
|
|
end
|
|
end
|
|
|
|
function ctld.findNearestGroup(_heli, _groups)
|
|
|
|
local _closestGroupDetails = {}
|
|
local _closestGroup = nil
|
|
|
|
local _closestGroupDist = ctld.maxExtractDistance
|
|
|
|
local _heliPoint = _heli:getPoint()
|
|
|
|
for _, _groupName in pairs(_groups) do
|
|
|
|
local _group = Group.getByName(_groupName)
|
|
|
|
if _group ~= nil then
|
|
local _units = _group:getUnits()
|
|
|
|
if _units ~= nil and #_units > 0 then
|
|
|
|
local _leader = nil
|
|
|
|
local _groupDetails = { groupId = _group:getID(), groupName = _group:getName(), side = _group:getCoalition(), units = {} }
|
|
|
|
-- find alive leader
|
|
for x = 1, #_units do
|
|
if _units[x]:getLife() > 0 then
|
|
|
|
if _leader == nil then
|
|
_leader = _units[x]
|
|
-- set country based on leader
|
|
_groupDetails.country = _leader:getCountry()
|
|
end
|
|
|
|
local _unitDetails = { type = _units[x]:getTypeName(), unitId = _units[x]:getID(), name = _units[x]:getName() }
|
|
|
|
table.insert(_groupDetails.units, _unitDetails)
|
|
end
|
|
end
|
|
|
|
if _leader ~= nil then
|
|
local _leaderPos = _leader:getPoint()
|
|
local _dist = ctld.getDistance(_heliPoint, _leaderPos)
|
|
if _dist < _closestGroupDist then
|
|
_closestGroupDist = _dist
|
|
_closestGroupDetails = _groupDetails
|
|
_closestGroup = _group
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
if _closestGroup ~= nil then
|
|
|
|
return { group = _closestGroup, details = _closestGroupDetails }
|
|
else
|
|
|
|
return nil
|
|
end
|
|
end
|
|
|
|
|
|
function ctld.createUnit(_x, _y, _angle, _details)
|
|
|
|
local _newUnit = {
|
|
["y"] = _y,
|
|
["type"] = _details.type,
|
|
["name"] = _details.name,
|
|
-- ["unitId"] = _details.unitId,
|
|
["heading"] = _angle,
|
|
["playerCanDrive"] = true,
|
|
["skill"] = "Excellent",
|
|
["x"] = _x,
|
|
}
|
|
|
|
return _newUnit
|
|
end
|
|
|
|
function ctld.addEWRTask(_group)
|
|
|
|
-- delayed 2 second to work around bug
|
|
timer.scheduleFunction(function(_ewrGroup)
|
|
local _grp = ctld.getAliveGroup(_ewrGroup)
|
|
|
|
if _grp ~= nil then
|
|
local _controller = _grp:getController();
|
|
local _EWR = {
|
|
id = 'EWR',
|
|
auto = true,
|
|
params = {
|
|
}
|
|
}
|
|
end
|
|
end
|
|
, _group:getName(), timer.getTime() + 2)
|
|
|
|
end
|
|
|
|
function ctld.orderGroupToMoveToPoint(_leader, _destination)
|
|
|
|
local _group = _leader:getGroup()
|
|
|
|
local _path = {}
|
|
table.insert(_path, mist.ground.buildWP(_leader:getPoint(), 'Off Road', 50))
|
|
table.insert(_path, mist.ground.buildWP(_destination, 'Off Road', 50))
|
|
|
|
local _mission = {
|
|
id = 'Mission',
|
|
params = {
|
|
route = {
|
|
points =_path
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
-- delayed 2 second to work around bug
|
|
timer.scheduleFunction(function(_arg)
|
|
local _grp = ctld.getAliveGroup(_arg[1])
|
|
|
|
if _grp ~= nil then
|
|
local _controller = _grp:getController();
|
|
Controller.setOption(_controller, AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO)
|
|
Controller.setOption(_controller, AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE)
|
|
_controller:setTask(_arg[2])
|
|
end
|
|
end
|
|
, {_group:getName(), _mission}, timer.getTime() + 2)
|
|
|
|
end
|
|
|
|
-- are we in pickup zone
|
|
function ctld.inPickupZone(_heli)
|
|
|
|
if ctld.inAir(_heli) then
|
|
return { inZone = false, limit = -1, index = -1 }
|
|
end
|
|
|
|
local _heliPoint = _heli:getPoint()
|
|
|
|
for _i, _zoneDetails in pairs(ctld.pickupZones) do
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
|
|
|
|
if _triggerZone == nil then
|
|
local _ship = ctld.getTransportUnit(_zoneDetails[1])
|
|
|
|
if _ship then
|
|
local _point = _ship:getPoint()
|
|
_triggerZone = {}
|
|
_triggerZone.point = _point
|
|
_triggerZone.radius = 200 -- should be big enough for ship
|
|
end
|
|
|
|
end
|
|
|
|
if _triggerZone ~= nil then
|
|
|
|
--get distance to center
|
|
|
|
local _dist = ctld.getDistance(_heliPoint, _triggerZone.point)
|
|
if _dist <= _triggerZone.radius then
|
|
local _heliCoalition = _heli:getCoalition()
|
|
if _zoneDetails[4] == 1 and (_zoneDetails[5] == _heliCoalition or _zoneDetails[5] == 0) then
|
|
return { inZone = true, limit = _zoneDetails[3], index = _i }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local _fobs = ctld.getSpawnedFobs(_heli)
|
|
|
|
-- now check spawned fobs
|
|
for _, _fob in ipairs(_fobs) do
|
|
|
|
--get distance to center
|
|
|
|
local _dist = ctld.getDistance(_heliPoint, _fob:getPoint())
|
|
|
|
if _dist <= 150 then
|
|
return { inZone = true, limit = 10000, index = -1 };
|
|
end
|
|
end
|
|
|
|
|
|
|
|
return { inZone = false, limit = -1, index = -1 };
|
|
end
|
|
|
|
function ctld.getSpawnedFobs(_heli)
|
|
|
|
local _fobs = {}
|
|
|
|
for _, _fobName in ipairs(ctld.builtFOBS) do
|
|
|
|
local _fob = StaticObject.getByName(_fobName)
|
|
|
|
if _fob ~= nil and _fob:isExist() and _fob:getCoalition() == _heli:getCoalition() and _fob:getLife() > 0 then
|
|
|
|
table.insert(_fobs, _fob)
|
|
end
|
|
end
|
|
|
|
return _fobs
|
|
end
|
|
|
|
-- are we in a dropoff zone
|
|
function ctld.inDropoffZone(_heli)
|
|
|
|
if ctld.inAir(_heli) then
|
|
return false
|
|
end
|
|
|
|
local _heliPoint = _heli:getPoint()
|
|
|
|
for _, _zoneDetails in pairs(ctld.dropOffZones) do
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
|
|
|
|
if _triggerZone ~= nil and (_zoneDetails[3] == _heli:getCoalition() or _zoneDetails[3]== 0) then
|
|
|
|
--get distance to center
|
|
|
|
local _dist = ctld.getDistance(_heliPoint, _triggerZone.point)
|
|
|
|
if _dist <= _triggerZone.radius then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- are we in a waypoint zone
|
|
function ctld.inWaypointZone(_point,_coalition)
|
|
|
|
for _, _zoneDetails in pairs(ctld.wpZones) do
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
|
|
|
|
--right coalition and active?
|
|
if _triggerZone ~= nil and (_zoneDetails[4] == _coalition or _zoneDetails[4]== 0) and _zoneDetails[3] == 1 then
|
|
|
|
--get distance to center
|
|
|
|
local _dist = ctld.getDistance(_point, _triggerZone.point)
|
|
|
|
if _dist <= _triggerZone.radius then
|
|
return {inZone = true, point = _triggerZone.point, name = _zoneDetails[1]}
|
|
end
|
|
end
|
|
end
|
|
|
|
return {inZone = false}
|
|
end
|
|
|
|
-- are we near friendly logistics zone
|
|
function ctld.inLogisticsZone(_heli)
|
|
ctld.logDebug("ctld.inLogisticsZone(), _heli = %s", ctld.p(_heli))
|
|
|
|
if ctld.inAir(_heli) then
|
|
return false
|
|
end
|
|
local _heliPoint = _heli:getPoint()
|
|
ctld.logDebug("_heliPoint = %s", ctld.p(_heliPoint))
|
|
for _, _name in pairs(ctld.logisticUnits) do
|
|
ctld.logDebug("_name = %s", ctld.p(_name))
|
|
local _logistic = StaticObject.getByName(_name)
|
|
if not _logistic then
|
|
_logistic = Unit.getByName(_name)
|
|
end
|
|
ctld.logDebug("_logistic = %s", ctld.p(_logistic))
|
|
if _logistic ~= nil and _logistic:getCoalition() == _heli:getCoalition() and _logistic:getLife() > 0 then
|
|
--get distance
|
|
local _dist = ctld.getDistance(_heliPoint, _logistic:getPoint())
|
|
ctld.logDebug("_dist = %s", ctld.p(_dist))
|
|
if _dist <= ctld.maximumDistanceLogistic then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
|
|
-- are far enough from a friendly logistics zone
|
|
function ctld.farEnoughFromLogisticZone(_heli)
|
|
|
|
if ctld.inAir(_heli) then
|
|
return false
|
|
end
|
|
|
|
local _heliPoint = _heli:getPoint()
|
|
|
|
local _farEnough = true
|
|
|
|
for _, _name in pairs(ctld.logisticUnits) do
|
|
|
|
local _logistic = StaticObject.getByName(_name)
|
|
|
|
if _logistic ~= nil and _logistic:getCoalition() == _heli:getCoalition() then
|
|
|
|
--get distance
|
|
local _dist = ctld.getDistance(_heliPoint, _logistic:getPoint())
|
|
-- env.info("DIST ".._dist)
|
|
if _dist <= ctld.minimumDeployDistance then
|
|
-- env.info("TOO CLOSE ".._dist)
|
|
_farEnough = false
|
|
end
|
|
end
|
|
end
|
|
|
|
return _farEnough
|
|
end
|
|
|
|
function ctld.refreshSmoke()
|
|
|
|
if ctld.disableAllSmoke == true then
|
|
return
|
|
end
|
|
|
|
for _, _zoneGroup in pairs({ ctld.pickupZones, ctld.dropOffZones }) do
|
|
|
|
for _, _zoneDetails in pairs(_zoneGroup) do
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
|
|
|
|
if _triggerZone == nil then
|
|
local _ship = ctld.getTransportUnit(_triggerZone)
|
|
|
|
if _ship then
|
|
local _point = _ship:getPoint()
|
|
_triggerZone = {}
|
|
_triggerZone.point = _point
|
|
end
|
|
|
|
end
|
|
|
|
|
|
--only trigger if smoke is on AND zone is active
|
|
if _triggerZone ~= nil and _zoneDetails[2] >= 0 and _zoneDetails[4] == 1 then
|
|
|
|
-- Trigger smoke markers
|
|
|
|
local _pos2 = { x = _triggerZone.point.x, y = _triggerZone.point.z }
|
|
local _alt = land.getHeight(_pos2)
|
|
local _pos3 = { x = _pos2.x, y = _alt, z = _pos2.y }
|
|
|
|
trigger.action.smoke(_pos3, _zoneDetails[2])
|
|
end
|
|
end
|
|
end
|
|
|
|
--waypoint zones
|
|
for _, _zoneDetails in pairs(ctld.wpZones) do
|
|
|
|
local _triggerZone = trigger.misc.getZone(_zoneDetails[1])
|
|
|
|
--only trigger if smoke is on AND zone is active
|
|
if _triggerZone ~= nil and _zoneDetails[2] >= 0 and _zoneDetails[3] == 1 then
|
|
|
|
-- Trigger smoke markers
|
|
|
|
local _pos2 = { x = _triggerZone.point.x, y = _triggerZone.point.z }
|
|
local _alt = land.getHeight(_pos2)
|
|
local _pos3 = { x = _pos2.x, y = _alt, z = _pos2.y }
|
|
|
|
trigger.action.smoke(_pos3, _zoneDetails[2])
|
|
end
|
|
end
|
|
|
|
|
|
--refresh in 5 minutes
|
|
timer.scheduleFunction(ctld.refreshSmoke, nil, timer.getTime() + 300)
|
|
end
|
|
|
|
function ctld.dropSmoke(_args)
|
|
|
|
local _heli = ctld.getTransportUnit(_args[1])
|
|
|
|
if _heli ~= nil then
|
|
|
|
local _colour = ""
|
|
|
|
if _args[2] == trigger.smokeColor.Red then
|
|
|
|
_colour = "RED"
|
|
elseif _args[2] == trigger.smokeColor.Blue then
|
|
|
|
_colour = "BLUE"
|
|
elseif _args[2] == trigger.smokeColor.Green then
|
|
|
|
_colour = "GREEN"
|
|
elseif _args[2] == trigger.smokeColor.Orange then
|
|
|
|
_colour = "ORANGE"
|
|
end
|
|
|
|
local _point = _heli:getPoint()
|
|
|
|
local _pos2 = { x = _point.x, y = _point.z }
|
|
local _alt = land.getHeight(_pos2)
|
|
local _pos3 = { x = _point.x, y = _alt, z = _point.z }
|
|
|
|
trigger.action.smoke(_pos3, _args[2])
|
|
|
|
trigger.action.outTextForCoalition(_heli:getCoalition(), ctld.i18n_translate("%1 dropped %2 smoke.", ctld.getPlayerNameOrType(_heli), _colour), 10)
|
|
end
|
|
end
|
|
|
|
function ctld.unitCanCarryVehicles(_unit)
|
|
|
|
local _type = string.lower(_unit:getTypeName())
|
|
|
|
for _, _name in ipairs(ctld.vehicleTransportEnabled) do
|
|
local _nameLower = string.lower(_name)
|
|
if string.find(_type, _nameLower, 1, true) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function ctld.unitDynamicCargoCapable(_unit)
|
|
local cache = {}
|
|
local _type = string.lower(_unit:getTypeName())
|
|
local result = cache[_type]
|
|
if result == nil then
|
|
result = false
|
|
ctld.logDebug("ctld.unitDynamicCargoCapable(_type=[%s])", ctld.p(_type))
|
|
for _, _name in ipairs(ctld.dynamicCargoUnits) do
|
|
local _nameLower = string.lower(_name)
|
|
if string.find(_type, _nameLower, 1, true) then --string.match does not work with patterns containing '-' as it is a magic character
|
|
result = true
|
|
break
|
|
end
|
|
end
|
|
cache[_type] = result
|
|
ctld.logDebug("result=[%s]", ctld.p(result))
|
|
end
|
|
return result
|
|
end
|
|
|
|
function ctld.isJTACUnitType(_type)
|
|
if _type then
|
|
_type = string.lower(_type)
|
|
for _, _name in ipairs(ctld.jtacUnitTypes) do
|
|
local _nameLower = string.lower(_name)
|
|
if string.match(_type, _nameLower) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function ctld.updateZoneCounter(_index, _diff)
|
|
|
|
if ctld.pickupZones[_index] ~= nil then
|
|
|
|
ctld.pickupZones[_index][3] = ctld.pickupZones[_index][3] + _diff
|
|
|
|
if ctld.pickupZones[_index][3] < 0 then
|
|
ctld.pickupZones[_index][3] = 0
|
|
end
|
|
|
|
if ctld.pickupZones[_index][6] ~= nil then
|
|
trigger.action.setUserFlag(ctld.pickupZones[_index][6], ctld.pickupZones[_index][3])
|
|
end
|
|
-- env.info(ctld.pickupZones[_index][1].." = " ..ctld.pickupZones[_index][3])
|
|
end
|
|
end
|
|
|
|
function ctld.processCallback(_callbackArgs)
|
|
|
|
for _, _callback in pairs(ctld.callbacks) do
|
|
|
|
local _status, _result = pcall(function()
|
|
|
|
_callback(_callbackArgs)
|
|
|
|
end)
|
|
|
|
if (not _status) then
|
|
env.error(string.format("CTLD Callback Error: %s", _result))
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- checks the status of all AI troop carriers and auto loads and unloads troops
|
|
-- as long as the troops are on the ground
|
|
function ctld.checkAIStatus()
|
|
|
|
timer.scheduleFunction(ctld.checkAIStatus, nil, timer.getTime() + 2)
|
|
|
|
|
|
for _, _unitName in pairs(ctld.transportPilotNames) do
|
|
local status, error = pcall(function()
|
|
|
|
local _unit = ctld.getTransportUnit(_unitName)
|
|
|
|
-- no player name means AI!
|
|
if _unit ~= nil and _unit:getPlayerName() == nil then
|
|
local _zone = ctld.inPickupZone(_unit)
|
|
-- env.error("Checking.. ".._unit:getName())
|
|
if _zone.inZone == true and not ctld.troopsOnboard(_unit, true) then
|
|
-- env.error("in zone, loading.. ".._unit:getName())
|
|
|
|
if ctld.allowRandomAiTeamPickups == true then
|
|
-- Random troop pickup implementation
|
|
local _team = nil
|
|
if _unit:getCoalition() == 1 then
|
|
_team = math.floor((math.random(#ctld.redTeams * 100) / 100) + 1)
|
|
ctld.loadTroopsFromZone({ _unitName, true,ctld.loadableGroups[ctld.redTeams[_team]],true })
|
|
else
|
|
_team = math.floor((math.random(#ctld.blueTeams * 100) / 100) + 1)
|
|
ctld.loadTroopsFromZone({ _unitName, true,ctld.loadableGroups[ctld.blueTeams[_team]],true })
|
|
end
|
|
else
|
|
ctld.loadTroopsFromZone({ _unitName, true,"",true })
|
|
end
|
|
|
|
elseif ctld.inDropoffZone(_unit) and ctld.troopsOnboard(_unit, true) then
|
|
-- env.error("in dropoff zone, unloading.. ".._unit:getName())
|
|
ctld.unloadTroops( { _unitName, true })
|
|
end
|
|
|
|
if ctld.unitCanCarryVehicles(_unit) then
|
|
|
|
if _zone.inZone == true and not ctld.troopsOnboard(_unit, false) then
|
|
|
|
ctld.loadTroopsFromZone({ _unitName, false,"",true })
|
|
|
|
elseif ctld.inDropoffZone(_unit) and ctld.troopsOnboard(_unit, false) then
|
|
|
|
ctld.unloadTroops( { _unitName, false })
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
if (not status) then
|
|
env.error(string.format("Error with ai status: %s", error), false)
|
|
end
|
|
end
|
|
|
|
|
|
end
|
|
|
|
function ctld.getTransportLimit(_unitType)
|
|
|
|
if ctld.unitLoadLimits[_unitType] then
|
|
|
|
return ctld.unitLoadLimits[_unitType]
|
|
end
|
|
|
|
return ctld.numberOfTroops
|
|
|
|
end
|
|
|
|
function ctld.getUnitActions(_unitType)
|
|
|
|
if ctld.unitActions[_unitType] then
|
|
return ctld.unitActions[_unitType]
|
|
end
|
|
|
|
return {crates=true,troops=true}
|
|
|
|
end
|
|
|
|
-- Adds menuitem to a human unit
|
|
function ctld.addTransportF10MenuOptions(_unitName)
|
|
ctld.logDebug("ctld.addTransportF10MenuOptions(_unitName=[%s])", ctld.p(_unitName))
|
|
|
|
local status, error = pcall(function()
|
|
|
|
local _unit = ctld.getTransportUnit(_unitName)
|
|
ctld.logTrace("_unit = %s", ctld.p(_unit))
|
|
if _unit then
|
|
local _unitTypename = _unit:getTypeName()
|
|
local _groupId = ctld.getGroupId(_unit)
|
|
|
|
if _groupId then
|
|
ctld.logTrace("_groupId = %s", ctld.p(_groupId))
|
|
ctld.logTrace("ctld.addedTo = %s", ctld.p(ctld.addedTo))
|
|
if ctld.addedTo[tostring(_groupId)] == nil then
|
|
ctld.logTrace("adding CTLD menu for _groupId = %s", ctld.p(_groupId))
|
|
local _rootPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("CTLD"))
|
|
|
|
local _unitActions = ctld.getUnitActions(_unitTypename)
|
|
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Check Cargo"), _rootPath, ctld.checkTroopStatus, { _unitName })
|
|
|
|
if _unitActions.troops then
|
|
|
|
local _troopCommandsPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Troop Transport"), _rootPath)
|
|
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Unload / Extract Troops"), _troopCommandsPath, ctld.unloadExtractTroops, { _unitName })
|
|
|
|
|
|
-- local _loadPath = missionCommands.addSubMenuForGroup(_groupId, "Load From Zone", _troopCommandsPath)
|
|
local _transportLimit = ctld.getTransportLimit(_unitTypename)
|
|
local itemNb = 0
|
|
local menuEntries = {}
|
|
local menuPath = _troopCommandsPath
|
|
for _,_loadGroup in pairs(ctld.loadableGroups) do
|
|
if not _loadGroup.side or _loadGroup.side == _unit:getCoalition() then
|
|
-- check size & unit
|
|
if _transportLimit >= _loadGroup.total then
|
|
table.insert(menuEntries, { text = ctld.i18n_translate("Load ").._loadGroup.name, group = _loadGroup })
|
|
end
|
|
end
|
|
end
|
|
for _i, _menu in ipairs(menuEntries) do
|
|
-- add the menu item
|
|
itemNb = itemNb + 1
|
|
if itemNb == 9 and _i < #menuEntries then -- page limit reached (first item is "unload")
|
|
menuPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Next page"), menuPath)
|
|
itemNb = 1
|
|
end
|
|
missionCommands.addCommandForGroup(_groupId, _menu.text, menuPath, ctld.loadTroopsFromZone, { _unitName, true,_menu.group,false })
|
|
end
|
|
|
|
if ctld.unitCanCarryVehicles(_unit) then
|
|
|
|
local _vehicleCommandsPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Vehicle / FOB Transport"), _rootPath)
|
|
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Unload Vehicles"), _vehicleCommandsPath, ctld.unloadTroops, { _unitName, false })
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Load / Extract Vehicles"), _vehicleCommandsPath, ctld.loadTroopsFromZone, { _unitName, false,"",true })
|
|
|
|
if ctld.enabledFOBBuilding and ctld.staticBugWorkaround == false then
|
|
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Load / Unload FOB Crate"), _vehicleCommandsPath, ctld.loadUnloadFOBCrate, { _unitName, false })
|
|
end
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Check Cargo"), _vehicleCommandsPath, ctld.checkTroopStatus, { _unitName })
|
|
end
|
|
|
|
end
|
|
|
|
if ctld.enableCrates and _unitActions.crates then
|
|
if ctld.unitCanCarryVehicles(_unit) == false then
|
|
-- sort the crate categories alphabetically
|
|
local crateCategories = {}
|
|
for category, _ in pairs(ctld.spawnableCrates) do
|
|
table.insert(crateCategories, category)
|
|
end
|
|
table.sort(crateCategories)
|
|
ctld.logTrace("crateCategories = [%s]", ctld.p(crateCategories))
|
|
|
|
-- add menu for spawning crates
|
|
local itemNbMain = 0
|
|
local _cratesMenuPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Vehicle / FOB Crates / Drone"), _rootPath)
|
|
for _i, _category in ipairs(crateCategories) do
|
|
local _subMenuName = _category
|
|
local _crates = ctld.spawnableCrates[_subMenuName]
|
|
|
|
-- add the submenu item
|
|
itemNbMain = itemNbMain + 1
|
|
if itemNbMain == 10 and _i < #crateCategories then -- page limit reached
|
|
_cratesMenuPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Next page"), _cratesMenuPath)
|
|
itemNbMain = 1
|
|
end
|
|
local itemNbSubmenu = 0
|
|
local menuEntries = {}
|
|
local _subMenuPath = missionCommands.addSubMenuForGroup(_groupId, _subMenuName, _cratesMenuPath)
|
|
for _, _crate in pairs(_crates) do
|
|
ctld.logTrace("_crate = [%s]", ctld.p(_crate))
|
|
if not(_crate.multiple) or ctld.enableAllCrates then
|
|
local isJTAC = ctld.isJTACUnitType(_crate.unit)
|
|
ctld.logTrace("isJTAC = [%s]", ctld.p(isJTAC))
|
|
if not isJTAC or (isJTAC and ctld.JTAC_dropEnabled) then
|
|
if _crate.side == nil or (_crate.side == _unit:getCoalition()) then
|
|
local _crateRadioMsg = _crate.desc
|
|
--add in the number of crates required to build something
|
|
if _crate.cratesRequired ~= nil and _crate.cratesRequired > 1 then
|
|
_crateRadioMsg = _crateRadioMsg.." (".._crate.cratesRequired..")"
|
|
end
|
|
if _crate.multiple then
|
|
_crateRadioMsg = "* " .. _crateRadioMsg
|
|
end
|
|
local _menuEntry = { text = _crateRadioMsg, crate = _crate }
|
|
ctld.logTrace("_menuEntry = [%s]", ctld.p(_menuEntry))
|
|
table.insert(menuEntries, _menuEntry)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
for _i, _menu in ipairs(menuEntries) do
|
|
ctld.logTrace("_menu = [%s]", ctld.p(_menu))
|
|
-- add the submenu item
|
|
itemNbSubmenu = itemNbSubmenu + 1
|
|
if itemNbSubmenu == 10 and _i < #menuEntries then -- page limit reached
|
|
_subMenuPath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Next page"), _subMenuPath)
|
|
itemNbSubmenu = 1
|
|
end
|
|
missionCommands.addCommandForGroup(_groupId, _menu.text, _subMenuPath, ctld.spawnCrate, { _unitName, _menu.crate.weight })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if (ctld.enabledFOBBuilding or ctld.enableCrates) and _unitActions.crates then
|
|
|
|
local _crateCommands = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("CTLD Commands"), _rootPath)
|
|
if ctld.hoverPickup == false or ctld.loadCrateFromMenu == true then
|
|
if ctld.loadCrateFromMenu then
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Load Nearby Crate(s)"), _crateCommands, ctld.loadNearbyCrate, _unitName )
|
|
end
|
|
end
|
|
|
|
if ctld.loadCrateFromMenu or ctld.hoverPickup then
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Crate(s)"), _crateCommands, ctld.dropSlingCrate, { _unitName })
|
|
end
|
|
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Unpack Any Crate"), _crateCommands, ctld.unpackCrates, { _unitName })
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Nearby Crates"), _crateCommands, ctld.listNearbyCrates, { _unitName })
|
|
|
|
if ctld.enabledFOBBuilding then
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List FOBs"), _crateCommands, ctld.listFOBS, { _unitName })
|
|
end
|
|
end
|
|
|
|
if ctld.enableSmokeDrop then
|
|
local _smokeMenu = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Smoke Markers"), _rootPath)
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Red Smoke"), _smokeMenu, ctld.dropSmoke, { _unitName, trigger.smokeColor.Red })
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Blue Smoke"), _smokeMenu, ctld.dropSmoke, { _unitName, trigger.smokeColor.Blue })
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Orange Smoke"), _smokeMenu, ctld.dropSmoke, { _unitName, trigger.smokeColor.Orange })
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Green Smoke"), _smokeMenu, ctld.dropSmoke, { _unitName, trigger.smokeColor.Green })
|
|
end
|
|
|
|
if ctld.enabledRadioBeaconDrop then
|
|
local _radioCommands = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Radio Beacons"), _rootPath)
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Beacons"), _radioCommands, ctld.listRadioBeacons, { _unitName })
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Drop Beacon"), _radioCommands, ctld.dropRadioBeacon, { _unitName })
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Remove Closest Beacon"), _radioCommands, ctld.removeRadioBeacon, { _unitName })
|
|
elseif ctld.deployedRadioBeacons ~= {} then
|
|
local _radioCommands = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Radio Beacons"), _rootPath)
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Beacons"), _radioCommands, ctld.listRadioBeacons, { _unitName })
|
|
end
|
|
|
|
if ctld.enabledRadioBeaconDrop then
|
|
local _radioCommands = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("CTLD Pilots Read This"), _rootPath)
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("We use the unarmed Hummer as the CTLD crate because they will not stack up on top of each other causing issues for hovering helicopters to pick up crates. Multiple crates will appear as one until moved. To prevent the stacking, the original programming spread out the crates randomly within a 12m circle in front of the chopper. This caused an issue trying to place a single crate where you want it and now it places your dropped crate 30m directly in front of the chopper. When building a large sam site, spread the pieces out, don't place the launchers on top of the radars. Allow space for them. Don't build a launcher crate near a runway or taxiway, it may spread onto them when built. MOVE AWAY FROM THE SITE SO IT DOESN'T BUILD AND DESTROY YOUR CHOPPER"), _radioCommands, ctld.listRadioBeacons,{ _unitName })
|
|
end
|
|
|
|
|
|
ctld.addedTo[tostring(_groupId)] = true
|
|
ctld.logTrace("ctld.addedTo = %s", ctld.p(ctld.addedTo))
|
|
ctld.logTrace("done adding CTLD menu for _groupId = %s", ctld.p(_groupId))
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
if (not status) then
|
|
ctld.logError(string.format("Error adding f10 to transport: %s", error))
|
|
end
|
|
end
|
|
|
|
function ctld.addOtherF10MenuOptions()
|
|
ctld.logDebug("ctld.addOtherF10MenuOptions")
|
|
-- reschedule every 10 seconds
|
|
timer.scheduleFunction(ctld.addOtherF10MenuOptions, nil, timer.getTime() + 10)
|
|
local status, error = pcall(function()
|
|
|
|
-- now do any player controlled aircraft that ARENT transport units
|
|
if ctld.enabledRadioBeaconDrop then
|
|
-- get all BLUE players
|
|
ctld.addRadioListCommand(2)
|
|
|
|
-- get all RED players
|
|
ctld.addRadioListCommand(1)
|
|
end
|
|
|
|
if ctld.JTAC_jtacStatusF10 then
|
|
ctld.addJTACRadioCommand(2) -- get all BLUE players
|
|
ctld.addJTACRadioCommand(1) -- get all RED players
|
|
end
|
|
|
|
if ctld.reconF10Menu then
|
|
ctld.addReconRadioCommand(2) -- get all BLUE players
|
|
ctld.addReconRadioCommand(1) -- get all RED players
|
|
end
|
|
end)
|
|
|
|
if (not status) then
|
|
env.error(string.format("Error adding f10 to other players: %s", error), false)
|
|
end
|
|
end
|
|
|
|
--add to all players that arent transport
|
|
function ctld.addRadioListCommand(_side)
|
|
|
|
local _players = coalition.getPlayers(_side)
|
|
|
|
if _players ~= nil then
|
|
|
|
for _, _playerUnit in pairs(_players) do
|
|
|
|
local _groupId = ctld.getGroupId(_playerUnit)
|
|
|
|
if _groupId then
|
|
|
|
ctld.logTrace("ctld.addedTo = %s", ctld.p(ctld.addedTo))
|
|
if ctld.addedTo[tostring(_groupId)] == nil then
|
|
ctld.logTrace("adding List Radio Beacons for _groupId = %s", ctld.p(_groupId))
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("List Radio Beacons"), nil, ctld.listRadioBeacons, { _playerUnit:getName() })
|
|
ctld.addedTo[tostring(_groupId)] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ctld.addJTACRadioCommand(_side)
|
|
|
|
local _players = coalition.getPlayers(_side)
|
|
|
|
if _players ~= nil then
|
|
|
|
for _, _playerUnit in pairs(_players) do
|
|
|
|
local _groupId = ctld.getGroupId(_playerUnit)
|
|
|
|
if _groupId then
|
|
|
|
local newGroup = false
|
|
if ctld.jtacRadioAdded[tostring(_groupId)] == nil then
|
|
ctld.logDebug("ctld.addJTACRadioCommand - adding JTAC radio menu for unit [%s]", ctld.p(_playerUnit:getName()))
|
|
newGroup = true
|
|
local JTACpath = missionCommands.addSubMenuForGroup(_groupId, ctld.jtacMenuName)
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("JTAC Status"), JTACpath, ctld.getJTACStatus, { _playerUnit:getName() })
|
|
ctld.jtacRadioAdded[tostring(_groupId)] = true
|
|
end
|
|
|
|
--fetch the time to check for a regular refresh
|
|
local time = timer.getTime()
|
|
|
|
--depending on the delay, this part of the radio menu will be refreshed less often or as often as the static JTAC status command, this is for better reliability for the user when navigating through the menus. New groups will get the lists regardless and if a new JTAC is added all lists will be refreshed regardless of the delay.
|
|
if ctld.jtacLastRadioRefresh + ctld.jtacRadioRefreshDelay <= time or ctld.refreshJTACmenu[_side] or newGroup then
|
|
|
|
ctld.jtacLastRadioRefresh = time
|
|
|
|
--build the path to the CTLD JTAC menu
|
|
local jtacCurrentPagePath = {[1]=ctld.jtacMenuName}
|
|
--build the path for the NextPage submenu on the first page of the CTLD JTAC menu
|
|
local NextPageText = "Next Page"
|
|
local MainNextPagePath = {[1]=ctld.jtacMenuName, [2]=NextPageText}
|
|
--remove it along with everything that's in it
|
|
missionCommands.removeItemForGroup(_groupId, MainNextPagePath)
|
|
|
|
--counter to know when to add the next page submenu to fit all of the JTAC group submenus
|
|
local jtacCounter = 0
|
|
|
|
for _jtacGroupName,jtacUnit in pairs(ctld.jtacUnits) do
|
|
--ctld.logTrace(string.format("JTAC - MENU - [%s] - processing menu", ctld.p(_jtacGroupName)))
|
|
|
|
--if the JTAC is on the same team as the group being considered
|
|
local jtacCoalition = ctld.jtacUnits[_jtacGroupName].side
|
|
if jtacCoalition and jtacCoalition == _side then
|
|
--only bother removing the submenus on the first page of the CTLD JTAC menu as the other pages were deleted entirely above
|
|
if ctld.jtacGroupSubMenuPath[_jtacGroupName] and #ctld.jtacGroupSubMenuPath[_jtacGroupName]==2 then
|
|
missionCommands.removeItemForGroup(_groupId, ctld.jtacGroupSubMenuPath[_jtacGroupName])
|
|
end
|
|
--ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacTargetsList = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacTargetsList[_jtacGroupName])))
|
|
--ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacCurrentTargets = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacCurrentTargets[_jtacGroupName])))
|
|
|
|
local jtacActionMenu = false
|
|
for _,_specialOptionTable in pairs(ctld.jtacSpecialOptions) do
|
|
if _specialOptionTable.globalToggle then
|
|
jtacActionMenu = true
|
|
break
|
|
end
|
|
end
|
|
|
|
--if JTAC has at least one other target in sight or (if special options are available (NOTE : accessed through the JTAC's own menu also) and the JTAC has at least one target)
|
|
if (ctld.jtacTargetsList[_jtacGroupName] and #ctld.jtacTargetsList[_jtacGroupName] >= 1) or (ctld.jtacCurrentTargets[_jtacGroupName] and jtacActionMenu) then
|
|
|
|
local jtacGroupSubMenuName = string.format(_jtacGroupName .. " Selection")
|
|
|
|
jtacCounter = jtacCounter + 1
|
|
--F2 through F10 makes 9 entries possible per page, with one being the NextMenu submenu. F1 is taken by JTAC status entry.
|
|
if jtacCounter % 9 == 0 then
|
|
--recover the path to the current page with space available for JTAC group submenus
|
|
jtacCurrentPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacCurrentPagePath)
|
|
end
|
|
--add the JTAC group submenu to the current page
|
|
ctld.jtacGroupSubMenuPath[_jtacGroupName] = missionCommands.addSubMenuForGroup(_groupId, jtacGroupSubMenuName, jtacCurrentPagePath)
|
|
--ctld.logTrace(string.format("JTAC - MENU - [%s] - jtacGroupSubMenuPath = %s", ctld.p(_jtacGroupName), ctld.p(ctld.jtacGroupSubMenuPath[_jtacGroupName])))
|
|
|
|
--make a copy of the JTAC group submenu's path to insert the target's list on as many pages as required. The JTAC's group submenu path only leads to the first page
|
|
local jtacTargetPagePath = mist.utils.deepCopy(ctld.jtacGroupSubMenuPath[_jtacGroupName])
|
|
|
|
--counter to know when to add the next page submenu to fit all of the targets in the JTAC's group submenu. SMay not actually start at 0 due to static items being present on the first page
|
|
local itemCounter = 0
|
|
local jtacSpecialOptPagePath = nil
|
|
|
|
if jtacActionMenu then
|
|
--special options
|
|
local SpecialOptionsCounter = 0
|
|
|
|
for _,_specialOption in pairs(ctld.jtacSpecialOptions) do
|
|
if _specialOption.globalToggle then
|
|
|
|
if not jtacSpecialOptPagePath then
|
|
itemCounter = itemCounter + 1 --one item is added to the first JTAC target page
|
|
jtacSpecialOptPagePath = missionCommands.addSubMenuForGroup(_groupId, ctld.i18n_translate("Actions"), jtacTargetPagePath)
|
|
end
|
|
|
|
SpecialOptionsCounter = SpecialOptionsCounter+1
|
|
|
|
if SpecialOptionsCounter%10 == 0 then
|
|
jtacSpecialOptPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacSpecialOptPagePath)
|
|
SpecialOptionsCounter = SpecialOptionsCounter+1 --Added Next Page item
|
|
end
|
|
|
|
if _specialOption.jtacs then
|
|
if _specialOption.jtacs[_jtacGroupName] then
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("DISABLE ") .. _specialOption.message, jtacSpecialOptPagePath, _specialOption.setter, {jtacGroupName = _jtacGroupName, value = false})
|
|
else
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("ENABLE ") .. _specialOption.message, jtacSpecialOptPagePath, _specialOption.setter, {jtacGroupName = _jtacGroupName, value = true})
|
|
end
|
|
else
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("REQUEST ") .. _specialOption.message, jtacSpecialOptPagePath, _specialOption.setter, {jtacGroupName = _jtacGroupName, value = false}) --value is not used here
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if #ctld.jtacTargetsList[_jtacGroupName] >= 1 then
|
|
--ctld.logTrace(string.format("JTAC - MENU - [%s] - adding targets menu", ctld.p(_jtacGroupName)))
|
|
|
|
--add a reset targeting option to revert to automatic JTAC unit targeting
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Reset TGT Selection"), jtacTargetPagePath, ctld.setJTACTarget, {jtacGroupName = _jtacGroupName, targetName = nil})
|
|
|
|
itemCounter = itemCounter + 1 --one item is added to the first JTAC target page
|
|
|
|
--indicator table to know which unitType was already added to the radio submenu
|
|
local typeNameList = {}
|
|
for _,target in pairs(ctld.jtacTargetsList[_jtacGroupName]) do
|
|
local targetName = target.unit:getName()
|
|
--check if the jtac has a current target before filtering it out if possible
|
|
if (ctld.jtacCurrentTargets[_jtacGroupName] and targetName ~= ctld.jtacCurrentTargets[_jtacGroupName].name) then
|
|
local targetType_name = target.unit:getTypeName()
|
|
|
|
if targetType_name then
|
|
if typeNameList[targetType_name] then
|
|
typeNameList[targetType_name].amount = typeNameList[targetType_name].amount + 1
|
|
else
|
|
typeNameList[targetType_name] = {}
|
|
typeNameList[targetType_name].targetName = targetName --store the first targetName
|
|
typeNameList[targetType_name].amount = 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for typeName,info in pairs(typeNameList) do
|
|
local amount = info.amount
|
|
local targetName = info.targetName
|
|
itemCounter = itemCounter + 1
|
|
|
|
--F1 through F10 makes 10 entries possible per page, with one being the NextMenu submenu.
|
|
if itemCounter%10 == 0 then
|
|
jtacTargetPagePath = missionCommands.addSubMenuForGroup(_groupId, NextPageText, jtacTargetPagePath)
|
|
itemCounter = itemCounter + 1 --added the next page item
|
|
end
|
|
|
|
missionCommands.addCommandForGroup(_groupId, string.format(typeName .. "(" .. amount .. ")"), jtacTargetPagePath, ctld.setJTACTarget, {jtacGroupName = _jtacGroupName, targetName = targetName})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if ctld.refreshJTACmenu[_side] then
|
|
ctld.refreshJTACmenu[_side] = false
|
|
end
|
|
end
|
|
end
|
|
|
|
function ctld.getGroupId(_unit)
|
|
|
|
local _unitDB = mist.DBs.unitsById[tonumber(_unit:getID())]
|
|
if _unitDB ~= nil and _unitDB.groupId then
|
|
return _unitDB.groupId
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--get distance in meters assuming a Flat world
|
|
function ctld.getDistance(_point1, _point2)
|
|
|
|
local xUnit = _point1.x
|
|
local yUnit = _point1.z
|
|
local xZone = _point2.x
|
|
local yZone = _point2.z
|
|
|
|
local xDiff = xUnit - xZone
|
|
local yDiff = yUnit - yZone
|
|
|
|
return math.sqrt(xDiff * xDiff + yDiff * yDiff)
|
|
end
|
|
|
|
|
|
------------ JTAC -----------
|
|
|
|
ctld.jtacMenuName = "JTAC" --name of the CTLD JTAC radio menu
|
|
ctld.jtacLaserPoints = {}
|
|
ctld.jtacIRPoints = {}
|
|
ctld.jtacSmokeMarks = {}
|
|
ctld.jtacUnits = {} -- list of JTAC units for f10 command
|
|
ctld.jtacStop = {} -- jtacs to tell to stop lasing
|
|
ctld.jtacCurrentTargets = {}
|
|
ctld.jtacTargetsList = {} --current available targets to each JTAC for lasing (targets from other JTACs are filtered out). Contains DCS unit objects with their methods and the distance to the JTAC {unit, dist}
|
|
ctld.jtacSelectedTarget = {} --currently user selected target if it contains a unit's name, otherwise contains 1 or nil (if not initialized)
|
|
ctld.jtacSpecialOptions = { --list which contains the status of special options for each jtac, ordered for them to show up in the correct order in the corresponding radio menu
|
|
standbyMode = { --#1
|
|
globalToggle = ctld.JTAC_allowStandbyMode;
|
|
message = "Standby Mode";
|
|
setter = nil; --ctld.setStdbMode, will be set after declaration of said function
|
|
jtacs = {
|
|
--enable flag for each JTAC
|
|
};
|
|
}; --disable designation by the JTAC
|
|
smokeMarker = { --#4
|
|
globalToggle = ctld.JTAC_allowSmokeRequest;
|
|
message = "Smoke on TGT";
|
|
setter = nil; --ctld.setSmokeOnTarget
|
|
}; --smoke marker on target
|
|
laseSpotCorrections = { --#2
|
|
globalToggle = ctld.JTAC_laseSpotCorrections;
|
|
message = "Speed Corrections";
|
|
setter = nil; --ctld.setLaseCompensation
|
|
jtacs = {
|
|
--enable flag for each JTAC
|
|
};
|
|
}; --target speed and wind compensation for laser spot
|
|
_9Line = { --#3
|
|
globalToggle = ctld.JTAC_allow9Line;
|
|
message = "9 Line";
|
|
setter = nil; --ctld.setJTAC9Line
|
|
}; --9Line message for JTAC
|
|
}
|
|
ctld.jtacRadioAdded = {} --keeps track of who's had the radio command added
|
|
ctld.jtacGroupSubMenuPath = {} --keeps track of which submenu contains each JTAC's target selection menu
|
|
ctld.jtacRadioRefreshDelay = 120 --determines how often in seconds the dynamic parts of the jtac radio menu (target lists) will be refreshed
|
|
ctld.jtacLastRadioRefresh = 0 -- time at which the target lists were refreshed for everyone at least
|
|
ctld.refreshJTACmenu = {} --indicator to know when a new JTAC is added to a coalition in order to rebuild the corresponding target lists
|
|
ctld.jtacGeneratedLaserCodes = {} -- keeps track of generated codes, cycles when they run out
|
|
ctld.jtacLaserPointCodes = {}
|
|
ctld.jtacRadioData = {}
|
|
|
|
--[[
|
|
Called when a new JTAC is spawned, it will wait one second for DCS to have time to fill the group with units, and then call ctld.JTACAutoLase.
|
|
|
|
The goal here is to correct a bug: when a group is respawned (i.e. when any group with the name of a previously existing group is spawned),
|
|
DCS spawns a group which exists (Group.getByName gets a valid table, and group:isExist returns true), but has no units (i.e. group:getUnits returns an empty table).
|
|
This causes JTACAutoLase to call cleanupJTAC because it does not find the JTAC unit, and the JTAC to be put out of the JTACAutoLase loop, and never processed again.
|
|
By waiting a bit, the group gets populated before JTACAutoLase is called, hence avoiding a trip to cleanupJTAC.
|
|
]]
|
|
function ctld.JTACStart(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio)
|
|
mist.scheduleFunction(ctld.JTACAutoLase, {_jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio}, timer.getTime()+1)
|
|
end
|
|
|
|
function ctld.JTACAutoLase(_jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio)
|
|
ctld.logDebug(string.format("ctld.JTACAutoLase(_jtacGroupName=%s, _laserCode=%s", ctld.p(_jtacGroupName), ctld.p(_laserCode)))
|
|
|
|
local _radio = _radio
|
|
if not _radio then
|
|
_radio = {}
|
|
if _laserCode then
|
|
local _laserCode = tonumber(_laserCode)
|
|
if _laserCode and _laserCode >= 1111 and _laserCode <= 1688 then
|
|
local _laserB = math.floor((_laserCode - 1000)/100)
|
|
local _laserCD = _laserCode - 1000 - _laserB*100
|
|
local _frequency = tostring(30+_laserB+_laserCD*0.05)
|
|
ctld.logTrace(string.format("_laserB=%s", ctld.p(_laserB)))
|
|
ctld.logTrace(string.format("_laserCD=%s", ctld.p(_laserCD)))
|
|
ctld.logTrace(string.format("_frequency=%s", ctld.p(_frequency)))
|
|
_radio.freq = _frequency
|
|
_radio.mod = "fm"
|
|
end
|
|
end
|
|
end
|
|
|
|
if _radio and not _radio.name then
|
|
_radio.name = _jtacGroupName
|
|
end
|
|
|
|
if ctld.jtacStop[_jtacGroupName] == true then
|
|
ctld.jtacStop[_jtacGroupName] = nil -- allow it to be started again
|
|
ctld.cleanupJTAC(_jtacGroupName)
|
|
return
|
|
end
|
|
|
|
if _lock == nil then
|
|
_lock = ctld.JTAC_lock
|
|
end
|
|
|
|
ctld.jtacLaserPointCodes[_jtacGroupName] = _laserCode
|
|
ctld.jtacRadioData[_jtacGroupName] = _radio
|
|
|
|
local _jtacGroup = ctld.getGroup(_jtacGroupName)
|
|
local _jtacUnit
|
|
|
|
if _jtacGroup == nil or #_jtacGroup == 0 then
|
|
|
|
--check not in a heli
|
|
if ctld.inTransitTroops then
|
|
for _, _onboard in pairs(ctld.inTransitTroops) do
|
|
if _onboard ~= nil then
|
|
if _onboard.troops ~= nil and _onboard.troops.groupName ~= nil and _onboard.troops.groupName == _jtacGroupName then
|
|
|
|
--jtac soldier being transported by heli
|
|
ctld.cleanupJTAC(_jtacGroupName)
|
|
|
|
ctld.logTrace(string.format("JTAC - LASE - [%s] - in transport, waiting - scheduling JTACAutoLase in %ss at %s", ctld.p(_jtacGroupName), ctld.p(10), ctld.p(timer.getTime() + 10)))
|
|
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 10)
|
|
return
|
|
end
|
|
|
|
if _onboard.vehicles ~= nil and _onboard.vehicles.groupName ~= nil and _onboard.vehicles.groupName == _jtacGroupName then
|
|
--jtac vehicle being transported by heli
|
|
ctld.cleanupJTAC(_jtacGroupName)
|
|
|
|
ctld.logTrace(string.format("JTAC - LASE - [%s] - in transport, waiting - scheduling JTACAutoLase in %ss at %s", ctld.p(_jtacGroupName), ctld.p(10), ctld.p(timer.getTime() + 10)))
|
|
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 10)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if ctld.jtacUnits[_jtacGroupName] ~= nil then
|
|
ctld.notifyCoalition(ctld.i18n_translate("JTAC Group %1 KIA!", _jtacGroupName), 10, ctld.jtacUnits[_jtacGroupName].side, _radio)
|
|
end
|
|
|
|
--remove from list
|
|
ctld.cleanupJTAC(_jtacGroupName)
|
|
|
|
return
|
|
else
|
|
|
|
_jtacUnit = _jtacGroup[1]
|
|
local _jtacCoalition = _jtacUnit:getCoalition()
|
|
--add to list
|
|
ctld.jtacUnits[_jtacGroupName] = { name = _jtacUnit:getName(), side = _jtacCoalition, radio = _radio }
|
|
|
|
--Targets list, special options and Selected target initialization
|
|
if not ctld.jtacTargetsList[_jtacGroupName] then
|
|
--Target list
|
|
ctld.jtacTargetsList[_jtacGroupName] = {}
|
|
if _jtacCoalition then ctld.refreshJTACmenu[_jtacCoalition] = true end
|
|
|
|
--Special Options
|
|
for _,_specialOption in pairs(ctld.jtacSpecialOptions) do
|
|
if _specialOption.jtacs then
|
|
_specialOption.jtacs[_jtacGroupName] = false
|
|
end
|
|
end
|
|
end
|
|
|
|
if not ctld.jtacSelectedTarget[_jtacGroupName] then
|
|
ctld.jtacSelectedTarget[_jtacGroupName] = 1
|
|
end
|
|
|
|
-- work out smoke colour
|
|
if _colour == nil then
|
|
|
|
if _jtacUnit:getCoalition() == 1 then
|
|
_colour = ctld.JTAC_smokeColour_RED
|
|
else
|
|
_colour = ctld.JTAC_smokeColour_BLUE
|
|
end
|
|
end
|
|
|
|
|
|
if _smoke == nil then
|
|
|
|
if _jtacUnit:getCoalition() == 1 then
|
|
_smoke = ctld.JTAC_smokeOn_RED
|
|
else
|
|
_smoke = ctld.JTAC_smokeOn_BLUE
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- search for current unit
|
|
|
|
if _jtacUnit:isActive() == false then
|
|
|
|
ctld.cleanupJTAC(_jtacGroupName)
|
|
|
|
ctld.logTrace(string.format("JTAC - LASE - [%s] - not active, scheduling JTACAutoLase in 30s at %s", ctld.p(_jtacGroupName), ctld.p(timer.getTime() + 30)))
|
|
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 30)
|
|
|
|
return
|
|
end
|
|
|
|
local _enemyUnit = ctld.getCurrentUnit(_jtacUnit, _jtacGroupName)
|
|
--update targets list and store the next potential target if the selected one was lost
|
|
local _defaultEnemyUnit = ctld.findNearestVisibleEnemy(_jtacUnit, _lock)
|
|
|
|
-- if the JTAC sees a unit and a target was selected by users but is not the current unit, check if the selected target is in the targets list, if it is, then it's been reacquired
|
|
if _enemyUnit and ctld.jtacSelectedTarget[_jtacGroupName] ~= 1 and ctld.jtacSelectedTarget[_jtacGroupName] ~= _enemyUnit:getName() then
|
|
for _,target in pairs(ctld.jtacTargetsList[_jtacGroupName]) do
|
|
if target then
|
|
local targetUnit = target.unit
|
|
local targetName = targetUnit:getName()
|
|
|
|
if ctld.jtacSelectedTarget[_jtacGroupName] == targetName then
|
|
|
|
ctld.jtacCurrentTargets[_jtacGroupName] = { name = targetName, unitType = targetUnit:getTypeName(), unitId = targetUnit:getID() }
|
|
_enemyUnit = targetUnit
|
|
|
|
local message = ctld.i18n_translate("%1, selected target reacquired, %2", _jtacGroupName, _enemyUnit:getTypeName())
|
|
local fullMessage = message .. ctld.i18n_translate(". CODE: %1. POSITION: %2", _laserCode, ctld.getPositionString(_enemyUnit))
|
|
ctld.notifyCoalition(fullMessage, 10, _jtacUnit:getCoalition(), _radio, message)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local targetDestroyed = false
|
|
local targetLost = false
|
|
local wasSelected = false
|
|
|
|
if _enemyUnit == nil and ctld.jtacCurrentTargets[_jtacGroupName] ~= nil then
|
|
|
|
local _tempUnitInfo = ctld.jtacCurrentTargets[_jtacGroupName]
|
|
|
|
-- env.info("TEMP UNIT INFO: " .. tempUnitInfo.name .. " " .. tempUnitInfo.unitType)
|
|
|
|
local _tempUnit = Unit.getByName(_tempUnitInfo.name)
|
|
|
|
wasSelected = (ctld.jtacCurrentTargets[_jtacGroupName].name == ctld.jtacSelectedTarget[_jtacGroupName])
|
|
|
|
if _tempUnit ~= nil and _tempUnit:getLife() > 0 and _tempUnit:isActive() == true then
|
|
targetLost = true
|
|
else
|
|
targetDestroyed = true
|
|
ctld.jtacSelectedTarget[_jtacGroupName] = 1
|
|
end
|
|
|
|
--remove from smoke list
|
|
ctld.jtacSmokeMarks[_tempUnitInfo.name] = nil
|
|
|
|
-- JTAC Unit: resume his route ------------
|
|
trigger.action.groupContinueMoving(Group.getByName(_jtacGroupName))
|
|
|
|
-- remove from target list
|
|
ctld.jtacCurrentTargets[_jtacGroupName] = nil
|
|
|
|
--stop lasing
|
|
ctld.cancelLase(_jtacGroupName)
|
|
end
|
|
|
|
|
|
if _enemyUnit == nil then
|
|
if _defaultEnemyUnit ~= nil then
|
|
|
|
-- store current target for easy lookup
|
|
ctld.jtacCurrentTargets[_jtacGroupName] = { name = _defaultEnemyUnit:getName(), unitType = _defaultEnemyUnit:getTypeName(), unitId = _defaultEnemyUnit:getID() }
|
|
|
|
--add check for lasing or not
|
|
local action = ctld.i18n_translate("new target, ")
|
|
|
|
if ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] then
|
|
action = ctld.i18n_translate("standing by on %1", action)
|
|
else
|
|
action = ctld.i18n_translate("lasing %1", action)
|
|
end
|
|
|
|
if wasSelected and targetLost then
|
|
action = ctld.i18n_translate(", temporarily %1", action)
|
|
else
|
|
action = ", " .. action
|
|
end
|
|
|
|
if targetLost then
|
|
action = ctld.i18n_translate("target lost") .. action
|
|
elseif targetDestroyed then
|
|
action = ctld.i18n_translate("target destroyed") .. action
|
|
end
|
|
|
|
if wasSelected then
|
|
action = ctld.i18n_translate(", selected %1", action)
|
|
elseif targetLost or targetDestroyed then
|
|
action = ", " .. action
|
|
end
|
|
wasSelected = false
|
|
targetDestroyed = false
|
|
targetLost = false
|
|
|
|
local message = _jtacGroupName .. action .. _defaultEnemyUnit:getTypeName()
|
|
local fullMessage = message .. '. CODE: ' .. _laserCode .. ". POSITION: " .. ctld.getPositionString(_defaultEnemyUnit)
|
|
ctld.notifyCoalition(fullMessage, 10, _jtacUnit:getCoalition(), _radio, message)
|
|
|
|
-- JTAC Unit stop his route -----------------
|
|
trigger.action.groupStopMoving(Group.getByName(_jtacGroupName)) -- stop JTAC
|
|
|
|
-- create smoke
|
|
if _smoke == true then
|
|
|
|
--create first smoke
|
|
ctld.createSmokeMarker(_defaultEnemyUnit, _colour)
|
|
end
|
|
end
|
|
end
|
|
|
|
if _enemyUnit ~= nil and not ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] then
|
|
|
|
local refreshDelay = 15 --delay in between JTACAutoLase scheduled calls when a target is tracked
|
|
local targetSpeedVec = _enemyUnit:getVelocity()
|
|
local targetSpeed = math.sqrt(targetSpeedVec.x^2+targetSpeedVec.y^2+targetSpeedVec.z^2)
|
|
local maxUpdateDist = 5 --maximum distance the unit will be allowed to travel before the lase spot is updated again
|
|
ctld.logTrace(string.format("targetSpeed=%s", ctld.p(targetSpeed)))
|
|
|
|
ctld.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode)
|
|
|
|
--if the target is going sufficiently fast for it to wander off futher than the maxUpdateDist, schedule laseUnit calls to update the lase spot only (we consider that the unit lives and drives on between JTACAutoLase calls)
|
|
if targetSpeed >= maxUpdateDist/refreshDelay then
|
|
local updateTimeStep = maxUpdateDist/targetSpeed --calculate the time step so that the target is never more than maxUpdateDist from it's last lased position
|
|
ctld.logTrace(string.format("JTAC - LASE - [%s] - target is moving at %s m/s, schedulting lasing steps every %ss", ctld.p(_jtacGroupName), ctld.p(targetSpeed), ctld.p(updateTimeStep)))
|
|
|
|
local i = 1
|
|
while i*updateTimeStep <= refreshDelay - updateTimeStep do --while the scheduled time for the laseUnit call isn't greater than the time between two JTACAutoLase() calls minus one time step (because at the next time step JTACAutoLase() should have been called and this in term also calls laseUnit())
|
|
timer.scheduleFunction(ctld.timerLaseUnit,{_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode}, timer.getTime()+i*updateTimeStep)
|
|
i = i + 1
|
|
end
|
|
ctld.logTrace(string.format("JTAC - LASE - [%s] - scheduled %s moving target lasing steps", ctld.p(_jtacGroupName), ctld.p(i)))
|
|
end
|
|
|
|
ctld.logTrace(string.format("JTAC - LASE - [%s] - scheduling JTACAutoLase in %ss at %s", ctld.p(_jtacGroupName), ctld.p(refreshDelay), ctld.p(timer.getTime() + refreshDelay)))
|
|
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + refreshDelay)
|
|
|
|
if _smoke == true then
|
|
local _nextSmokeTime = ctld.jtacSmokeMarks[_enemyUnit:getName()]
|
|
|
|
--recreate smoke marker after 5 mins
|
|
if _nextSmokeTime ~= nil and _nextSmokeTime < timer.getTime() then
|
|
|
|
ctld.createSmokeMarker(_enemyUnit, _colour)
|
|
end
|
|
end
|
|
|
|
else
|
|
ctld.logDebug(string.format("JTAC - MODE - [%s] - No Enemies Nearby / Standby mode", ctld.p(_jtacGroupName)))
|
|
|
|
-- stop lazing the old spot
|
|
ctld.logDebug(string.format("JTAC - LASE - [%s] - canceling lasing of the old spot", ctld.p(_jtacGroupName)))
|
|
ctld.cancelLase(_jtacGroupName)
|
|
|
|
ctld.logTrace(string.format("JTAC - LASE - [%s] - scheduling JTACAutoLase in %ss at %s", ctld.p(_jtacGroupName), ctld.p(5), ctld.p(timer.getTime() + 5)))
|
|
timer.scheduleFunction(ctld.timerJTACAutoLase, { _jtacGroupName, _laserCode, _smoke, _lock, _colour, _radio }, timer.getTime() + 5)
|
|
end
|
|
|
|
local action = ", "
|
|
if wasSelected then
|
|
action = action .. "selected "
|
|
end
|
|
|
|
if targetLost then
|
|
ctld.notifyCoalition(ctld.i18n_translate("%1 %2 target lost.", _jtacGroupName , action), 10, _jtacUnit:getCoalition(), _radio)
|
|
elseif targetDestroyed then
|
|
ctld.notifyCoalition(ctld.i18n_translate("%1 %2 target destroyed.", _jtacGroupName , action), 10, _jtacUnit:getCoalition(), _radio)
|
|
end
|
|
end
|
|
|
|
function ctld.JTACAutoLaseStop(_jtacGroupName)
|
|
ctld.jtacStop[_jtacGroupName] = true
|
|
end
|
|
|
|
-- used by the timer function
|
|
function ctld.timerJTACAutoLase(_args)
|
|
|
|
ctld.JTACAutoLase(_args[1], _args[2], _args[3], _args[4], _args[5], _args[6])
|
|
end
|
|
|
|
function ctld.cleanupJTAC(_jtacGroupName)
|
|
-- clear laser - just in case
|
|
ctld.cancelLase(_jtacGroupName)
|
|
|
|
-- Cleanup
|
|
ctld.jtacCurrentTargets[_jtacGroupName] = nil
|
|
|
|
ctld.jtacTargetsList[_jtacGroupName] = nil
|
|
|
|
ctld.jtacSelectedTarget[_jtacGroupName] = nil
|
|
|
|
for _,_specialOption in pairs(ctld.jtacSpecialOptions) do --delete jtac specific settings for all special options
|
|
if _specialOption.jtacs then
|
|
_specialOption.jtacs[_jtacGroupName] = nil
|
|
end
|
|
end
|
|
|
|
ctld.jtacRadioData[_jtacGroupName] = nil
|
|
|
|
--remove the JTAC's group submenu and all of the target pages it potentially contained if the JTAC has or had a menu
|
|
if ctld.jtacUnits[_jtacGroupName] and ctld.jtacUnits[_jtacGroupName].side and ctld.jtacGroupSubMenuPath[_jtacGroupName] then
|
|
local _players = coalition.getPlayers(ctld.jtacUnits[_jtacGroupName].side)
|
|
|
|
if _players ~= nil then
|
|
|
|
for _, _playerUnit in pairs(_players) do
|
|
|
|
local _groupId = ctld.getGroupId(_playerUnit)
|
|
|
|
if _groupId then
|
|
missionCommands.removeItemForGroup(_groupId, ctld.jtacGroupSubMenuPath[_jtacGroupName])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
ctld.jtacUnits[_jtacGroupName] = nil
|
|
|
|
ctld.jtacGroupSubMenuPath[_jtacGroupName] = nil
|
|
end
|
|
|
|
|
|
--- send a message to the coalition
|
|
--- if _radio is set, the message will be read out loud via SRS
|
|
function ctld.notifyCoalition(_message, _displayFor, _side, _radio, _shortMessage)
|
|
|
|
trigger.action.outTextForCoalition(_side, _message, _displayFor)
|
|
|
|
local _shortMessage = _shortMessage
|
|
if _shortMessage == nil then
|
|
_shortMessage = _message
|
|
end
|
|
|
|
if STTS and STTS.TextToSpeech and _radio and _radio.freq then
|
|
local _freq = _radio.freq
|
|
local _modulation = _radio.mod or "FM"
|
|
local _volume = _radio.volume or "1.0"
|
|
local _name = _radio.name or "JTAC"
|
|
local _gender = _radio.gender or "male"
|
|
local _culture = _radio.culture or "en-US"
|
|
local _voice = _radio.voice
|
|
local _googleTTS = _radio.googleTTS or false
|
|
STTS.TextToSpeech(_shortMessage, _freq, _modulation, _volume, _name, _side, nil, 1, _gender, _culture, _voice, _googleTTS)
|
|
else
|
|
trigger.action.outSoundForCoalition(_side, "radiobeep.ogg")
|
|
end
|
|
end
|
|
|
|
function ctld.createSmokeMarker(_enemyUnit, _colour)
|
|
|
|
--recreate in 5 mins
|
|
ctld.jtacSmokeMarks[_enemyUnit:getName()] = timer.getTime() + 300.0
|
|
|
|
local _enemyPoint = _enemyUnit:getPoint()
|
|
trigger.action.smoke({ x = _enemyPoint.x + math.random(-ctld.JTAC_smokeMarginOfError, ctld.JTAC_smokeMarginOfError) + ctld.JTAC_smokeOffset_x, y = _enemyPoint.y + ctld.JTAC_smokeOffset_y, z = _enemyPoint.z + math.random(-ctld.JTAC_smokeMarginOfError, ctld.JTAC_smokeMarginOfError) + ctld.JTAC_smokeOffset_z }, _colour)
|
|
end
|
|
|
|
function ctld.cancelLase(_jtacGroupName)
|
|
|
|
--local index = "JTAC_"..jtacUnit:getID()
|
|
|
|
local _tempLase = ctld.jtacLaserPoints[_jtacGroupName]
|
|
|
|
if _tempLase ~= nil then
|
|
Spot.destroy(_tempLase)
|
|
ctld.jtacLaserPoints[_jtacGroupName] = nil
|
|
|
|
-- env.info('Destroy laze '..index)
|
|
|
|
_tempLase = nil
|
|
end
|
|
|
|
local _tempIR = ctld.jtacIRPoints[_jtacGroupName]
|
|
|
|
if _tempIR ~= nil then
|
|
Spot.destroy(_tempIR)
|
|
ctld.jtacIRPoints[_jtacGroupName] = nil
|
|
|
|
-- env.info('Destroy laze '..index)
|
|
|
|
_tempIR = nil
|
|
end
|
|
end
|
|
|
|
-- used by the timer function
|
|
function ctld.timerLaseUnit(_args)
|
|
|
|
ctld.laseUnit(_args[1], _args[2], _args[3], _args[4])
|
|
end
|
|
|
|
function ctld.laseUnit(_enemyUnit, _jtacUnit, _jtacGroupName, _laserCode)
|
|
|
|
--cancelLase(jtacGroupName)
|
|
ctld.logTrace("ctld.laseUnit()")
|
|
|
|
local _spots = {}
|
|
|
|
if _enemyUnit:isExist() then
|
|
local _enemyVector = _enemyUnit:getPoint()
|
|
local _enemyVectorUpdated = { x = _enemyVector.x, y = _enemyVector.y + 2.0, z = _enemyVector.z }
|
|
|
|
if ctld.jtacSpecialOptions.laseSpotCorrections.jtacs[_jtacGroupName] then
|
|
local _enemySpeedVector = _enemyUnit:getVelocity()
|
|
ctld.logTrace(string.format("_enemySpeedVector=%s", ctld.p(_enemySpeedVector)))
|
|
|
|
local _WindSpeedVector = atmosphere.getWind(_enemyVectorUpdated)
|
|
ctld.logTrace(string.format("_WindSpeedVector=%s", ctld.p(_WindSpeedVector)))
|
|
|
|
--if target speed is greater than 0, calculated using absolute value norm
|
|
if math.abs(_enemySpeedVector.x) + math.abs(_enemySpeedVector.y) + math.abs(_enemySpeedVector.z) > 0 then
|
|
local CorrectionFactor = 1 --correction factor in seconds applied to the target speed components to determine the lasing spot for a direct hit on a moving vehicle
|
|
|
|
--correct in the direction of the movement
|
|
_enemyVectorUpdated.x = _enemyVectorUpdated.x + _enemySpeedVector.x * CorrectionFactor
|
|
_enemyVectorUpdated.y = _enemyVectorUpdated.y + _enemySpeedVector.y * CorrectionFactor
|
|
_enemyVectorUpdated.z = _enemyVectorUpdated.z + _enemySpeedVector.z * CorrectionFactor
|
|
end
|
|
|
|
--if wind speed is greater than 0, calculated using absolute value norm
|
|
if math.abs(_WindSpeedVector.x) + math.abs(_WindSpeedVector.y) + math.abs(_WindSpeedVector.z) > 0 then
|
|
local CorrectionFactor = 1.05 --correction factor in seconds applied to the wind speed components to determine the lasing spot for a direct hit in adverse conditions
|
|
|
|
--correct to the opposite of the wind direction
|
|
_enemyVectorUpdated.x = _enemyVectorUpdated.x - _WindSpeedVector.x * CorrectionFactor
|
|
_enemyVectorUpdated.y = _enemyVectorUpdated.y - _WindSpeedVector.y * CorrectionFactor --not sure about correcting altitude but that component is always 0 in testing
|
|
_enemyVectorUpdated.z = _enemyVectorUpdated.z - _WindSpeedVector.z * CorrectionFactor
|
|
end
|
|
--combination of both should result in near perfect accuracy if the bomb doesn't stall itself following fast vehicles or correcting for heavy winds, correction factors can be adjusted but should work up to 40kn of wind for vehicles moving at 90kph (beware to drop the bomb in a way to not stall it, facing which ever is larger, target speed or wind)
|
|
end
|
|
|
|
local _oldLase = ctld.jtacLaserPoints[_jtacGroupName]
|
|
local _oldIR = ctld.jtacIRPoints[_jtacGroupName]
|
|
|
|
if _oldLase == nil or _oldIR == nil then
|
|
|
|
-- create lase
|
|
|
|
local _status, _result = pcall(function()
|
|
_spots['irPoint'] = Spot.createInfraRed(_jtacUnit, { x = 0, y = 2.0, z = 0 }, _enemyVectorUpdated)
|
|
_spots['laserPoint'] = Spot.createLaser(_jtacUnit, { x = 0, y = 2.0, z = 0 }, _enemyVectorUpdated, _laserCode)
|
|
return _spots
|
|
end)
|
|
|
|
if not _status then
|
|
env.error('ERROR: ' .. _result, false)
|
|
else
|
|
if _result.irPoint then
|
|
|
|
-- env.info(jtacUnit:getName() .. ' placed IR Pointer on '..enemyUnit:getName())
|
|
|
|
ctld.jtacIRPoints[_jtacGroupName] = _result.irPoint --store so we can remove after
|
|
end
|
|
if _result.laserPoint then
|
|
|
|
-- env.info(jtacUnit:getName() .. ' is Lasing '..enemyUnit:getName()..'. CODE:'..laserCode)
|
|
|
|
ctld.jtacLaserPoints[_jtacGroupName] = _result.laserPoint
|
|
end
|
|
end
|
|
|
|
else
|
|
|
|
-- update lase
|
|
|
|
if _oldLase ~= nil then
|
|
_oldLase:setPoint(_enemyVectorUpdated)
|
|
end
|
|
|
|
if _oldIR ~= nil then
|
|
_oldIR:setPoint(_enemyVectorUpdated)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- get currently selected unit and check they're still in range
|
|
function ctld.getCurrentUnit(_jtacUnit, _jtacGroupName)
|
|
|
|
local _unit = nil
|
|
|
|
if ctld.jtacCurrentTargets[_jtacGroupName] ~= nil then
|
|
_unit = Unit.getByName(ctld.jtacCurrentTargets[_jtacGroupName].name)
|
|
end
|
|
|
|
local _tempPoint = nil
|
|
local _tempDist = nil
|
|
local _tempPosition = nil
|
|
|
|
local _jtacPosition = _jtacUnit:getPosition()
|
|
local _jtacPoint = _jtacUnit:getPoint()
|
|
|
|
if _unit ~= nil and _unit:getLife() > 0 and _unit:isActive() == true then
|
|
|
|
-- calc distance
|
|
_tempPoint = _unit:getPoint()
|
|
-- tempPosition = unit:getPosition()
|
|
|
|
_tempDist = ctld.getDistance(_unit:getPoint(), _jtacUnit:getPoint())
|
|
if _tempDist < ctld.JTAC_maxDistance then
|
|
-- calc visible
|
|
|
|
-- check slightly above the target as rounding errors can cause issues, plus the unit has some height anyways
|
|
local _offsetEnemyPos = { x = _tempPoint.x, y = _tempPoint.y + 2.0, z = _tempPoint.z }
|
|
local _offsetJTACPos = { x = _jtacPoint.x, y = _jtacPoint.y + 2.0, z = _jtacPoint.z }
|
|
|
|
if land.isVisible(_offsetEnemyPos, _offsetJTACPos) then
|
|
return _unit
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
|
|
-- Find nearest enemy to JTAC that isn't blocked by terrain
|
|
function ctld.findNearestVisibleEnemy(_jtacUnit, _targetType,_distance)
|
|
|
|
--local startTime = os.clock()
|
|
|
|
local _maxDistance = _distance or ctld.JTAC_maxDistance
|
|
|
|
local _nearestDistance = _maxDistance
|
|
|
|
local _jtacGroupName = _jtacUnit:getGroup():getName()
|
|
local _jtacPoint = _jtacUnit:getPoint()
|
|
local _coa = _jtacUnit:getCoalition()
|
|
|
|
local _offsetJTACPos = { x = _jtacPoint.x, y = _jtacPoint.y + 2.0, z = _jtacPoint.z }
|
|
|
|
local _volume = {
|
|
id = world.VolumeType.SPHERE,
|
|
params = {
|
|
point = _offsetJTACPos,
|
|
radius = _maxDistance
|
|
}
|
|
}
|
|
|
|
local _unitList = {}
|
|
|
|
|
|
local _search = function(_unit, _coa)
|
|
pcall(function()
|
|
|
|
if _unit ~= nil
|
|
and _unit:getLife() > 0
|
|
and _unit:isActive()
|
|
and _unit:getCoalition() ~= _coa
|
|
and not _unit:inAir()
|
|
and not ctld.alreadyTarget(_jtacUnit,_unit) then
|
|
|
|
local _tempPoint = _unit:getPoint()
|
|
local _offsetEnemyPos = { x = _tempPoint.x, y = _tempPoint.y + 2.0, z = _tempPoint.z }
|
|
|
|
if land.isVisible(_offsetJTACPos,_offsetEnemyPos ) then
|
|
|
|
local _dist = ctld.getDistance(_offsetJTACPos, _offsetEnemyPos)
|
|
|
|
if _dist < _maxDistance then
|
|
table.insert(_unitList,{unit=_unit, dist=_dist})
|
|
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
return true
|
|
end
|
|
|
|
world.searchObjects(Object.Category.UNIT, _volume, _search, _coa)
|
|
|
|
--log.info(string.format("JTAC Search elapsed time: %.4f\n", os.clock() - startTime))
|
|
|
|
-- generate list order by distance & visible
|
|
|
|
-- first check
|
|
-- hpriority
|
|
-- priority
|
|
-- vehicle
|
|
-- unit
|
|
|
|
|
|
ctld.jtacTargetsList[_jtacGroupName] = _unitList
|
|
--from the units in range, build the targets list, unsorted as to keep consistency between radio menu refreshes
|
|
|
|
local _sort = function( a,b ) return a.dist < b.dist end
|
|
table.sort(_unitList,_sort)
|
|
-- sort list
|
|
|
|
-- check for hpriority
|
|
for _, _enemyUnit in ipairs(_unitList) do
|
|
local _enemyName = _enemyUnit.unit:getName()
|
|
|
|
if string.match(_enemyName, "hpriority") then
|
|
return _enemyUnit.unit
|
|
end
|
|
end
|
|
|
|
for _, _enemyUnit in ipairs(_unitList) do
|
|
local _enemyName = _enemyUnit.unit:getName()
|
|
|
|
if string.match(_enemyName, "priority") then
|
|
return _enemyUnit.unit
|
|
end
|
|
end
|
|
|
|
local result = nil
|
|
for _, _enemyUnit in ipairs(_unitList) do
|
|
local _enemyName = _enemyUnit.unit:getName()
|
|
--log.info(string.format("CTLD - checking _enemyName=%s", _enemyName))
|
|
|
|
-- check for air defenses
|
|
--log.info(string.format("CTLD - _enemyUnit.unit:getDesc()[attributes]=%s", ctld.p(_enemyUnit.unit:getDesc()["attributes"])))
|
|
local airdefense = (_enemyUnit.unit:getDesc()["attributes"]["Air Defence"] ~= nil)
|
|
--log.info(string.format("CTLD - airdefense=%s", tostring(airdefense)))
|
|
|
|
if (_targetType == "vehicle" and ctld.isVehicle(_enemyUnit.unit)) or _targetType == "all" then
|
|
if airdefense then
|
|
return _enemyUnit.unit
|
|
else
|
|
result = _enemyUnit.unit
|
|
end
|
|
|
|
elseif (_targetType == "troop" and ctld.isInfantry(_enemyUnit.unit)) or _targetType == "all" then
|
|
if airdefense then
|
|
return _enemyUnit.unit
|
|
else
|
|
result = _enemyUnit.unit
|
|
end
|
|
end
|
|
end
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
|
function ctld.listNearbyEnemies(_jtacUnit)
|
|
|
|
local _maxDistance = ctld.JTAC_maxDistance
|
|
|
|
local _jtacPoint = _jtacUnit:getPoint()
|
|
local _coa = _jtacUnit:getCoalition()
|
|
|
|
local _offsetJTACPos = { x = _jtacPoint.x, y = _jtacPoint.y + 2.0, z = _jtacPoint.z }
|
|
|
|
local _volume = {
|
|
id = world.VolumeType.SPHERE,
|
|
params = {
|
|
point = _offsetJTACPos,
|
|
radius = _maxDistance
|
|
}
|
|
}
|
|
local _enemies = nil
|
|
|
|
local _search = function(_unit, _coa)
|
|
pcall(function()
|
|
|
|
if _unit ~= nil
|
|
and _unit:getLife() > 0
|
|
and _unit:isActive()
|
|
and _unit:getCoalition() ~= _coa
|
|
and not _unit:inAir() then
|
|
|
|
local _tempPoint = _unit:getPoint()
|
|
local _offsetEnemyPos = { x = _tempPoint.x, y = _tempPoint.y + 2.0, z = _tempPoint.z }
|
|
|
|
if land.isVisible(_offsetJTACPos,_offsetEnemyPos ) then
|
|
|
|
if not _enemies then
|
|
_enemies = {}
|
|
end
|
|
|
|
_enemies[_unit:getTypeName()] = _unit:getTypeName()
|
|
|
|
end
|
|
end
|
|
end)
|
|
|
|
return true
|
|
end
|
|
|
|
world.searchObjects(Object.Category.UNIT, _volume, _search, _coa)
|
|
|
|
return _enemies
|
|
end
|
|
|
|
-- tests whether the unit is targeted by another JTAC
|
|
function ctld.alreadyTarget(_jtacUnit, _enemyUnit)
|
|
|
|
for _, _jtacTarget in pairs(ctld.jtacCurrentTargets) do
|
|
|
|
if _jtacTarget.unitId == _enemyUnit:getID() then
|
|
-- env.info("ALREADY TARGET")
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
|
|
-- Returns only alive units from group but the group / unit may not be active
|
|
|
|
function ctld.getGroup(groupName)
|
|
|
|
local _group = Group.getByName(groupName)
|
|
|
|
local _filteredUnits = {} --contains alive units
|
|
local _x = 1
|
|
|
|
if _group ~= nil then
|
|
ctld.logTrace(string.format("ctld.getGroup - %s - group ~= nil", ctld.p(groupName)))
|
|
if _group:isExist() then
|
|
ctld.logTrace(string.format("ctld.getGroup - %s - group:isExist()", ctld.p(groupName)))
|
|
local _groupUnits = _group:getUnits()
|
|
|
|
if _groupUnits ~= nil and #_groupUnits > 0 then
|
|
ctld.logTrace(string.format("ctld.getGroup - %s - group has %s units", ctld.p(groupName), ctld.p(#_groupUnits)))
|
|
for _x = 1, #_groupUnits do
|
|
if _groupUnits[_x]:getLife() > 0 then -- removed and _groupUnits[_x]:isExist() as isExist doesnt work on single units!
|
|
table.insert(_filteredUnits, _groupUnits[_x])
|
|
else
|
|
ctld.logTrace(string.format("ctld.getGroup - %s - dead unit %s", ctld.p(groupName), ctld.p(_groupUnits[_x]:getName())))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return _filteredUnits
|
|
end
|
|
|
|
function ctld.getAliveGroup(_groupName)
|
|
|
|
local _group = Group.getByName(_groupName)
|
|
|
|
if _group and _group:isExist() == true and #_group:getUnits() > 0 then
|
|
return _group
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- gets the JTAC status and displays to coalition units
|
|
function ctld.getJTACStatus(_args)
|
|
|
|
--returns the status of all JTAC units unless the status of a single JTAC is asked for (by inserting it's groupName in _args[2])
|
|
|
|
local _playerUnit = ctld.getTransportUnit(_args[1])
|
|
local _singleJtacGroupName = _args[2]
|
|
|
|
if _playerUnit == nil and _singleJtacGroupName == nil then
|
|
return
|
|
end
|
|
|
|
local _side = nil
|
|
|
|
if _playerUnit == nil then
|
|
_side = ctld.jtacUnits[_singleJtacGroupName].side
|
|
else
|
|
_side = _playerUnit:getCoalition()
|
|
end
|
|
|
|
local _jtacUnit = nil
|
|
local hasJTAC = false
|
|
local _message = ctld.i18n_translate("JTAC STATUS: \n\n")
|
|
|
|
for _jtacGroupName, _jtacDetails in pairs(ctld.jtacUnits) do
|
|
|
|
--look up units
|
|
if _singleJtacGroupName == nil or (_singleJtacGroupName and _singleJtacGroupName == _jtacGroupName) then --if the status of a single JTAC or if the status of a single JTAC was asked and this is the correct JTAC we're going over in the loop
|
|
_jtacUnit = Unit.getByName(_jtacDetails.name)
|
|
|
|
if _jtacUnit ~= nil and _jtacUnit:getLife() > 0 and _jtacUnit:isActive() == true and _jtacUnit:getCoalition() == _side then
|
|
|
|
hasJTAC = true
|
|
|
|
local _enemyUnit = ctld.getCurrentUnit(_jtacUnit, _jtacGroupName)
|
|
|
|
local _laserCode = ctld.jtacLaserPointCodes[_jtacGroupName]
|
|
|
|
local _start = "->" .. _jtacGroupName
|
|
if (_jtacDetails.radio) then
|
|
_start = _start .. ctld.i18n_translate(", available on %1 %2,", _jtacDetails.radio.freq, _jtacDetails.radio.mod)
|
|
end
|
|
|
|
if _laserCode == nil then
|
|
_laserCode = ctld.i18n_translate("UNKNOWN")
|
|
end
|
|
|
|
if _enemyUnit ~= nil and _enemyUnit:getLife() > 0 and _enemyUnit:isActive() == true then
|
|
|
|
local action = ctld.i18n_translate(" targeting ")
|
|
|
|
if ctld.jtacSelectedTarget[_jtacGroupName] == _enemyUnit:getName() then
|
|
action = ctld.i18n_translate(" targeting selected unit ")
|
|
else
|
|
if ctld.jtacSelectedTarget[_jtacGroupName] ~= 1 then
|
|
action = ctld.i18n_translate(" attempting to find selected unit, temporarily targeting ")
|
|
end
|
|
end
|
|
|
|
if ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] then
|
|
action = action .. ctld.i18n_translate("(Laser OFF) ")
|
|
end
|
|
|
|
_message = _message .. "" .. _start .. action .. _enemyUnit:getTypeName() .. " CODE: " .. _laserCode .. ctld.getPositionString(_enemyUnit) .. "\n"
|
|
|
|
local _list = ctld.listNearbyEnemies(_jtacUnit)
|
|
|
|
if _list then
|
|
_message = _message .. ctld.i18n_translate("Visual On: ")
|
|
|
|
for _,_type in pairs(_list) do
|
|
_message = _message.._type..", "
|
|
end
|
|
_message = _message.."\n"
|
|
end
|
|
|
|
else
|
|
_message = _message .. "" .. _start .. ctld.i18n_translate(" searching for targets %1\n", ctld.getPositionString(_jtacUnit))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not hasJTAC then
|
|
ctld.notifyCoalition(ctld.i18n_translate("No Active JTACs"), 10, _side)
|
|
else
|
|
ctld.notifyCoalition(_message, 10, _side)
|
|
end
|
|
end
|
|
|
|
function ctld.setJTACTarget(_args)
|
|
if _args then
|
|
local _jtacGroupName = _args.jtacGroupName
|
|
local targetName = _args.targetName
|
|
|
|
if _jtacGroupName and targetName and ctld.jtacSelectedTarget[_jtacGroupName] and ctld.jtacTargetsList[_jtacGroupName] then
|
|
|
|
--look for the unit's (target) name in the Targets List, create the required data structure for jtacCurrentTargets and then assign it to the JTAC called _jtacGroupName
|
|
for _, target in pairs(ctld.jtacTargetsList[_jtacGroupName]) do
|
|
|
|
if target then
|
|
|
|
local listedTargetUnit = target.unit
|
|
local ListedTargetName = listedTargetUnit:getName()
|
|
|
|
if ListedTargetName == targetName then
|
|
|
|
ctld.jtacSelectedTarget[_jtacGroupName] = targetName
|
|
ctld.jtacCurrentTargets[_jtacGroupName] = { name = targetName, unitType = listedTargetUnit:getTypeName(), unitId = listedTargetUnit:getID() }
|
|
|
|
local message = _jtacGroupName .. ctld.i18n_translate(", targeting selected unit, %1",listedTargetUnit:getTypeName())
|
|
local fullMessage = message .. ctld.i18n_translate(". CODE: %1. POSITION: %2", ctld.jtacLaserPointCodes[_jtacGroupName], ctld.getPositionString(listedTargetUnit))
|
|
ctld.notifyCoalition(fullMessage, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName], message)
|
|
end
|
|
end
|
|
end
|
|
elseif not targetName and ctld.jtacSelectedTarget[_jtacGroupName] ~= 1 then
|
|
ctld.jtacSelectedTarget[_jtacGroupName] = 1
|
|
ctld.jtacCurrentTargets[_jtacGroupName] = nil
|
|
|
|
local message = _jtacGroupName .. ctld.i18n_translate(", target selection reset.")
|
|
ctld.notifyCoalition(message, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName])
|
|
|
|
if ctld.jtacSpecialOptions.laseSpotCorrections.jtacs[_jtacGroupName] then
|
|
ctld.setLaseCompensation({jtacGroupName = _jtacGroupName, value = false}) --disable laser spot corrections
|
|
end
|
|
|
|
if ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] then
|
|
ctld.setStdbMode({jtacGroupName = _jtacGroupName, value = false}) --make the JTAC exit standby mode after either target selection or targeting selection reset
|
|
end
|
|
end
|
|
|
|
ctld.refreshJTACmenu[ctld.jtacUnits[_jtacGroupName].side] = true
|
|
end
|
|
end
|
|
|
|
--special option setters (make sure to affect the function pointer to the corresponding .setter in the special options table after declaration of said function)
|
|
function ctld.setSpecialOptionArgsCheck(_args)
|
|
if _args then
|
|
local _jtacGroupName = _args.jtacGroupName
|
|
local _value = _args.value --expected boolean
|
|
local _notOutput = _args.noOutput --expected boolean
|
|
|
|
if _jtacGroupName then
|
|
return {jtacGroupName = _jtacGroupName, value = _value, noOutput = _notOutput}
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function ctld.setStdbMode(_args)
|
|
local parsedArgs = ctld.setSpecialOptionArgsCheck(_args)
|
|
if parsedArgs then
|
|
|
|
local _jtacGroupName = parsedArgs.jtacGroupName
|
|
local _value = parsedArgs.value
|
|
local _noOutput = parsedArgs.noOutput
|
|
|
|
local message = ctld.i18n_translate("%1, laser and smokes enabled", _jtacGroupName)
|
|
if _value then
|
|
message = ctld.i18n_translate("%1, laser and smokes disabled", _jtacGroupName)
|
|
end
|
|
if not _noOutput then
|
|
ctld.notifyCoalition(message, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName])
|
|
end
|
|
|
|
ctld.jtacSpecialOptions.standbyMode.jtacs[_jtacGroupName] = _value
|
|
ctld.refreshJTACmenu[ctld.jtacUnits[_jtacGroupName].side] = true
|
|
end
|
|
end
|
|
ctld.jtacSpecialOptions.standbyMode.setter = ctld.setStdbMode
|
|
|
|
function ctld.setLaseCompensation(_args)
|
|
local parsedArgs = ctld.setSpecialOptionArgsCheck(_args)
|
|
if parsedArgs then
|
|
|
|
local _jtacGroupName = parsedArgs.jtacGroupName
|
|
local _value = parsedArgs.value
|
|
local _noOutput = parsedArgs.noOutput
|
|
|
|
local message = ctld.i18n_translate("%1, wind and target speed laser spot compensations enabled", _jtacGroupName)
|
|
if _value then
|
|
message = ctld.i18n_translate("%1, wind and target speed laser spot compensations disabled", _jtacGroupName)
|
|
end
|
|
if not _noOutput then
|
|
ctld.notifyCoalition(message, 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName])
|
|
end
|
|
|
|
ctld.jtacSpecialOptions.laseSpotCorrections.jtacs[_jtacGroupName] = _value
|
|
ctld.refreshJTACmenu[ctld.jtacUnits[_jtacGroupName].side] = true
|
|
end
|
|
end
|
|
ctld.jtacSpecialOptions.laseSpotCorrections.setter = ctld.setLaseCompensation
|
|
|
|
function ctld.setSmokeOnTarget(_args)
|
|
local parsedArgs = ctld.setSpecialOptionArgsCheck(_args)
|
|
if parsedArgs then
|
|
|
|
local _jtacGroupName = parsedArgs.jtacGroupName
|
|
local _noOutput = parsedArgs.noOutput
|
|
local _enemyUnit = Unit.getByName(ctld.jtacCurrentTargets[_jtacGroupName].name)
|
|
|
|
if _enemyUnit then
|
|
if not _noOutput then
|
|
ctld.notifyCoalition(ctld.i18n_translate("%1, WHITE smoke deployed near target", _jtacGroupName), 10, ctld.jtacUnits[_jtacGroupName].side, ctld.jtacRadioData[_jtacGroupName])
|
|
end
|
|
|
|
local _enemyPoint = _enemyUnit:getPoint()
|
|
local randomCircleDiam = 30;
|
|
trigger.action.smoke({ x = _enemyPoint.x + math.random(randomCircleDiam,-randomCircleDiam), y = _enemyPoint.y + 2.0, z = _enemyPoint.z + math.random(randomCircleDiam,-randomCircleDiam)}, 2)
|
|
end
|
|
end
|
|
end
|
|
ctld.jtacSpecialOptions.smokeMarker.setter = ctld.setSmokeOnTarget
|
|
|
|
function ctld.setJTAC9Line(_args)
|
|
local parsedArgs = ctld.setSpecialOptionArgsCheck(_args)
|
|
if parsedArgs then
|
|
|
|
local _jtacGroupName = parsedArgs.jtacGroupName
|
|
|
|
ctld.getJTACStatus({nil, _jtacGroupName})
|
|
end
|
|
end
|
|
ctld.jtacSpecialOptions._9Line.setter = ctld.setJTAC9Line
|
|
|
|
function ctld.setGrpROE(_grp, _ROE)
|
|
if _grp == nil then
|
|
ctld.logError("ctld.setGrpROE called with a nil group")
|
|
return
|
|
end
|
|
|
|
if _ROE == nil then
|
|
_ROE = AI.Option.Ground.val.ROE.OPEN_FIRE
|
|
end
|
|
|
|
if _grp and _grp:isExist() == true and #_grp:getUnits() > 0 then -- check if the group truly exists
|
|
local _controller = _grp:getController();
|
|
Controller.setOption(_controller, AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO)
|
|
Controller.setOption(_controller, AI.Option.Ground.id.ROE, _ROE)
|
|
_controller:setTask(_grp)
|
|
end
|
|
end
|
|
|
|
function ctld.isInfantry(_unit)
|
|
|
|
local _typeName = _unit:getTypeName()
|
|
|
|
--type coerce tostring
|
|
_typeName = string.lower(_typeName .. "")
|
|
|
|
local _soldierType = { "infantry", "paratrooper", "stinger", "manpad", "mortar" }
|
|
|
|
for _key, _value in pairs(_soldierType) do
|
|
if string.match(_typeName, _value) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- assume anything that isnt soldier is vehicle
|
|
function ctld.isVehicle(_unit)
|
|
|
|
if ctld.isInfantry(_unit) then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- The entered value can range from 1111 - 1788,
|
|
-- -- but the first digit of the series must be a 1 or 2
|
|
-- -- and the last three digits must be between 1 and 8.
|
|
-- The range used to be bugged so its not 1 - 8 but 0 - 7.
|
|
-- function below will use the range 1-7 just incase
|
|
function ctld.generateLaserCode()
|
|
|
|
ctld.jtacGeneratedLaserCodes = {}
|
|
|
|
-- generate list of laser codes
|
|
local _code = 1511
|
|
|
|
local _count = 1
|
|
|
|
while _code < 1777 and _count < 30 do
|
|
|
|
while true do
|
|
|
|
_code = _code + 1
|
|
|
|
if not ctld.containsDigit(_code, 8)
|
|
and not ctld.containsDigit(_code, 9)
|
|
and not ctld.containsDigit(_code, 0) then
|
|
|
|
table.insert(ctld.jtacGeneratedLaserCodes, _code)
|
|
|
|
--env.info(_code.." Code")
|
|
break
|
|
end
|
|
end
|
|
_count = _count + 1
|
|
end
|
|
end
|
|
|
|
function ctld.containsDigit(_number, _numberToFind)
|
|
|
|
local _thisNumber = _number
|
|
local _thisDigit = 0
|
|
|
|
while _thisNumber ~= 0 do
|
|
|
|
_thisDigit = _thisNumber % 10
|
|
_thisNumber = math.floor(_thisNumber / 10)
|
|
|
|
if _thisDigit == _numberToFind then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- 200 - 400 in 10KHz
|
|
-- 400 - 850 in 10 KHz
|
|
-- 850 - 1250 in 50 KHz
|
|
function ctld.generateVHFrequencies()
|
|
|
|
--ignore list
|
|
--list of all frequencies in KHZ that could conflict with
|
|
-- 191 - 1290 KHz, beacon range
|
|
local _skipFrequencies = {
|
|
745, --Astrahan
|
|
381,
|
|
384,
|
|
300.50,
|
|
312.5,
|
|
1175,
|
|
342,
|
|
735,
|
|
300.50,
|
|
353.00,
|
|
440,
|
|
795,
|
|
525,
|
|
520,
|
|
690,
|
|
625,
|
|
291.5,
|
|
300.50,
|
|
435,
|
|
309.50,
|
|
920,
|
|
1065,
|
|
274,
|
|
312.50,
|
|
580,
|
|
602,
|
|
297.50,
|
|
750,
|
|
485,
|
|
950,
|
|
214,
|
|
1025, 730, 995, 455, 307, 670, 329, 395, 770,
|
|
380, 705, 300.5, 507, 740, 1030, 515,
|
|
330, 309.5,
|
|
348, 462, 905, 352, 1210, 942, 435,
|
|
324,
|
|
320, 420, 311, 389, 396, 862, 680, 297.5,
|
|
920, 662,
|
|
866, 907, 309.5, 822, 515, 470, 342, 1182, 309.5, 720, 528,
|
|
337, 312.5, 830, 740, 309.5, 641, 312, 722, 682, 1050,
|
|
1116, 935, 1000, 430, 577,
|
|
326 -- Nevada
|
|
}
|
|
|
|
ctld.freeVHFFrequencies = {}
|
|
local _start = 200000
|
|
|
|
-- first range
|
|
while _start < 400000 do
|
|
|
|
-- skip existing NDB frequencies
|
|
local _found = false
|
|
for _, value in pairs(_skipFrequencies) do
|
|
if value * 1000 == _start then
|
|
_found = true
|
|
break
|
|
end
|
|
end
|
|
|
|
|
|
if _found == false then
|
|
table.insert(ctld.freeVHFFrequencies, _start)
|
|
end
|
|
|
|
_start = _start + 10000
|
|
end
|
|
|
|
_start = 400000
|
|
-- second range
|
|
while _start < 850000 do
|
|
|
|
-- skip existing NDB frequencies
|
|
local _found = false
|
|
for _, value in pairs(_skipFrequencies) do
|
|
if value * 1000 == _start then
|
|
_found = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if _found == false then
|
|
table.insert(ctld.freeVHFFrequencies, _start)
|
|
end
|
|
|
|
|
|
_start = _start + 10000
|
|
end
|
|
|
|
_start = 850000
|
|
-- third range
|
|
while _start <= 1250000 do
|
|
|
|
-- skip existing NDB frequencies
|
|
local _found = false
|
|
for _, value in pairs(_skipFrequencies) do
|
|
if value * 1000 == _start then
|
|
_found = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if _found == false then
|
|
table.insert(ctld.freeVHFFrequencies, _start)
|
|
end
|
|
|
|
_start = _start + 50000
|
|
end
|
|
end
|
|
|
|
-- 220 - 399 MHZ, increments of 0.5MHZ
|
|
function ctld.generateUHFrequencies()
|
|
|
|
ctld.freeUHFFrequencies = {}
|
|
local _start = 220000000
|
|
|
|
while _start < 399000000 do
|
|
table.insert(ctld.freeUHFFrequencies, _start)
|
|
_start = _start + 500000
|
|
end
|
|
end
|
|
|
|
|
|
-- 220 - 399 MHZ, increments of 0.5MHZ
|
|
-- -- first digit 3-7MHz
|
|
-- -- second digit 0-5KHz
|
|
-- -- third digit 0-9
|
|
-- -- fourth digit 0 or 5
|
|
-- -- times by 10000
|
|
--
|
|
function ctld.generateFMFrequencies()
|
|
|
|
ctld.freeFMFrequencies = {}
|
|
local _start = 220000000
|
|
|
|
while _start < 399000000 do
|
|
|
|
_start = _start + 500000
|
|
end
|
|
|
|
for _first = 3, 7 do
|
|
for _second = 0, 5 do
|
|
for _third = 0, 9 do
|
|
local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit
|
|
table.insert(ctld.freeFMFrequencies, _frequency)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ctld.getPositionString(_unit)
|
|
if ctld.JTAC_location == false then
|
|
return ""
|
|
end
|
|
|
|
local _lat, _lon = coord.LOtoLL(_unit:getPosition().p)
|
|
local _latLngStr = mist.tostringLL(_lat, _lon, 3, false)
|
|
local _latLngSecStr = mist.tostringLL(_lat, _lon, 3, true)
|
|
local _mgrsString = mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(_unit:getPosition().p)), 5)
|
|
local _TargetAlti = land.getHeight(mist.utils.makeVec2(_unit:getPoint()))
|
|
return " @\n- DD " .. _latLngStr .." \n- DMS: " .. _latLngSecStr .. " \n- MGRS: " .. _mgrsString .. " - ALTI: " .. mist.utils.round(_TargetAlti, 0) .. " m / " .. mist.utils.round(_TargetAlti/0.3048, 0) .. " ft"
|
|
end
|
|
|
|
--**********************************************************************
|
|
-- RECOGNITION SUPPORT FUNCTIONS
|
|
-- Shows/remove/refresh marks in F10 map on targets in LOS of a unit passed in params
|
|
---------------------------------------------------------------------
|
|
-- examples ---------------------------------------------------------
|
|
--ctld.reconRefreshTargetsInLosOnF10Map(Unit.getByName("uh2-1"), 2000, 200)
|
|
--ctld.reconRemoveTargetsInLosOnF10Map(Unit.getByName("uh2-1"))
|
|
--ctld.reconShowTargetsInLosOnF10Map(Unit.getByName("uh2-1"), 2000, 200)
|
|
----------------------------------------------------------------------
|
|
--if ctld == nil then ctld = {} end
|
|
if ctld.lastMarkId == nil then
|
|
ctld.lastMarkId = 0
|
|
end
|
|
|
|
-- ***************** RECON CONFIGURATION *****************
|
|
ctld.reconF10Menu = true -- enables F10 RECON menu
|
|
ctld.reconMenuName = ctld.i18n_translate("RECON") --name of the CTLD JTAC radio menu
|
|
ctld.reconRadioAdded = {} --stores the groups that have had the radio menu added
|
|
ctld.reconLosSearchRadius = 2000 -- search radius in meters
|
|
ctld.reconLosMarkRadius = 100 -- mark radius dimension in meters
|
|
ctld.reconAutoRefreshLosTargetMarks = true -- if true recon LOS marks are automaticaly refreshed on F10 map
|
|
ctld.reconLastScheduleIdAutoRefresh = 0
|
|
|
|
---- F10 RECON Menus ------------------------------------------------------------------
|
|
function ctld.addReconRadioCommand(_side) -- _side = 1 or 2 (red or blue)
|
|
if ctld.reconF10Menu then
|
|
if _side == 1 or _side == 2 then
|
|
local _players = coalition.getPlayers(_side)
|
|
if _players ~= nil then
|
|
for _, _playerUnit in pairs(_players) do
|
|
local _groupId = ctld.getGroupId(_playerUnit)
|
|
if _groupId then
|
|
if ctld.reconRadioAdded[tostring(_groupId)] == nil then
|
|
ctld.logDebug("ctld.addReconRadioCommand - adding RECON radio menu for unit [%s]", ctld.p(_playerUnit:getName()))
|
|
local RECONpath = missionCommands.addSubMenuForGroup(_groupId, ctld.reconMenuName)
|
|
missionCommands.addCommandForGroup(_groupId,
|
|
ctld.i18n_translate("Show targets in LOS (refresh)"), RECONpath,
|
|
ctld.reconRefreshTargetsInLosOnF10Map, {
|
|
_groupId = _groupId,
|
|
_playerUnit = _playerUnit,
|
|
_searchRadius = ctld.reconLosSearchRadius,
|
|
_markRadius = ctld.reconLosMarkRadius,
|
|
_boolRemove = true
|
|
})
|
|
missionCommands.addCommandForGroup(_groupId, ctld.i18n_translate("Hide targets in LOS"), RECONpath, ctld.reconRemoveTargetsInLosOnF10Map, _playerUnit)
|
|
if ctld.reconAutoRefreshLosTargetMarks then
|
|
missionCommands.addCommandForGroup(_groupId,
|
|
ctld.i18n_translate("STOP autoRefresh targets in LOS"), RECONpath,
|
|
ctld.reconStopAutorefreshTargetsInLosOnF10Map,
|
|
_groupId,
|
|
_playerUnit,
|
|
ctld.reconLosSearchRadius,
|
|
ctld.reconLosMarkRadius,
|
|
true)
|
|
else
|
|
missionCommands.addCommandForGroup(_groupId,
|
|
ctld.i18n_translate("START autoRefresh targets in LOS"), RECONpath,
|
|
ctld.reconStartAutorefreshTargetsInLosOnF10Map,
|
|
_groupId,
|
|
_playerUnit,
|
|
ctld.reconLosSearchRadius,
|
|
ctld.reconLosMarkRadius,
|
|
true
|
|
)
|
|
end
|
|
ctld.reconRadioAdded[tostring(_groupId)] = timer.getTime() --fetch the time to check for a regular refresh
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
--------------------------------------------------------------------
|
|
function ctld.reconStopAutorefreshTargetsInLosOnF10Map(_groupId, _playerUnit, _searchRadius, _markRadius, _boolRemove)
|
|
ctld.reconAutoRefreshLosTargetMarks = false
|
|
|
|
if ctld.reconLastScheduleIdAutoRefresh ~= 0 then
|
|
timer.removeFunction(ctld.reconLastScheduleIdAutoRefresh) -- reset last schedule
|
|
end
|
|
|
|
ctld.reconRemoveTargetsInLosOnF10Map(_playerUnit)
|
|
missionCommands.removeItemForGroup(_groupId, {ctld.reconMenuName, ctld.i18n_translate("STOP autoRefresh targets in LOS")})
|
|
missionCommands.addCommandForGroup( _groupId, ctld.i18n_translate("START autoRefresh targets in LOS"), {ctld.reconMenuName},
|
|
ctld.reconStartAutorefreshTargetsInLosOnF10Map,
|
|
_groupId,
|
|
_playerUnit,
|
|
_searchRadius,
|
|
_markRadius,
|
|
_boolRemove)
|
|
end
|
|
--------------------------------------------------------------------
|
|
function ctld.reconStartAutorefreshTargetsInLosOnF10Map(_groupId, _playerUnit, _searchRadius, _markRadius, _boolRemove)
|
|
ctld.reconAutoRefreshLosTargetMarks = true
|
|
ctld.reconRefreshTargetsInLosOnF10Map({ _groupId = _groupId,
|
|
_playerUnit = _playerUnit,
|
|
_searchRadius = _searchRadius or ctld.reconLosSearchRadius,
|
|
_markRadius = _markRadius or ctld.reconLosMarkRadius,
|
|
_boolRemove = _boolRemove or true},
|
|
timer.getTime())
|
|
missionCommands.removeItemForGroup( _groupId, {ctld.reconMenuName, ctld.i18n_translate("START autoRefresh targets in LOS")})
|
|
missionCommands.addCommandForGroup( _groupId, ctld.i18n_translate("STOP autoRefresh targets in LOS"), {ctld.reconMenuName},
|
|
ctld.reconStopAutorefreshTargetsInLosOnF10Map,
|
|
_groupId,
|
|
_playerUnit,
|
|
_searchRadius,
|
|
_markRadius,
|
|
_boolRemove)
|
|
end
|
|
--------------------------------------------------------------------
|
|
function ctld.reconShowTargetsInLosOnF10Map(_playerUnit, _searchRadius, _markRadius) -- _groupId targeting
|
|
-- _searchRadius and _markRadius in meters
|
|
if _playerUnit then
|
|
local TargetsInLOS = {}
|
|
|
|
local enemyColor = "red"
|
|
local color = {1, 0, 0, 0.2} -- red
|
|
|
|
if _playerUnit:getCoalition() == 1 then
|
|
enemyColor = "blue"
|
|
color = {51/255, 51/255, 1, 0.2} -- blue
|
|
end
|
|
|
|
local t = mist.getUnitsLOS({_playerUnit:getName()}, 180, mist.makeUnitTable({'['..enemyColor..'][vehicle]'}),180, _searchRadius)
|
|
|
|
local MarkIds = {}
|
|
if t then
|
|
for i=1, #t do -- for each unit having los on enemies
|
|
for j=1, #t[i].vis do -- for each enemy unit in los
|
|
local targetPoint = t[i].vis[j]:getPoint() -- point of each target on LOS
|
|
ctld.lastMarkId = ctld.lastMarkId + 1
|
|
trigger.action.circleToAll(_playerUnit:getCoalition(), ctld.lastMarkId, targetPoint, _markRadius , color, color, 1, false, nil)
|
|
MarkIds[#MarkIds+1] = ctld.lastMarkId
|
|
TargetsInLOS[#TargetsInLOS+1] = { targetObject = t[i].vis[j]:getName(),
|
|
targetTypeName = t[i].vis[j]:getTypeName(),
|
|
targetPoint = targetPoint}
|
|
end
|
|
end
|
|
end
|
|
mist.DBs.humansByName[_playerUnit:getName()].losMarkIds = MarkIds -- store list of marksIds generated and showed on F10 map
|
|
return TargetsInLOS
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
---------------------------------------------------------
|
|
function ctld.reconRemoveTargetsInLosOnF10Map(_playerUnit)
|
|
local unitName = _playerUnit:getName()
|
|
if mist.DBs.humansByName[unitName].losMarkIds then
|
|
for i=1, #mist.DBs.humansByName[unitName].losMarkIds do -- for each unit having los on enemies
|
|
trigger.action.removeMark(mist.DBs.humansByName[unitName].losMarkIds[i])
|
|
end
|
|
mist.DBs.humansByName[unitName].losMarkIds = nil
|
|
end
|
|
end
|
|
---------------------------------------------------------
|
|
function ctld.reconRefreshTargetsInLosOnF10Map(_params, _t) -- _params._playerUnit targeting
|
|
-- _params._searchRadius and _params._markRadius in meters
|
|
-- _params._boolRemove = true to remove previous marksIds
|
|
if _t == nil then _t = timer.getTime() end
|
|
|
|
if ctld.reconAutoRefreshLosTargetMarks then -- to follow mobile enemy targets
|
|
ctld.reconLastScheduleIdAutoRefresh = timer.scheduleFunction(ctld.reconRefreshTargetsInLosOnF10Map, {_groupId = _params._groupId,
|
|
_playerUnit = _params._playerUnit,
|
|
_searchRadius = _params._searchRadius,
|
|
_markRadius = _params._markRadius,
|
|
_boolRemove = _params._boolRemove
|
|
},
|
|
timer.getTime() + 10)
|
|
end
|
|
|
|
if _params._boolRemove == true then
|
|
ctld.reconRemoveTargetsInLosOnF10Map(_params._playerUnit)
|
|
end
|
|
|
|
return ctld.reconShowTargetsInLosOnF10Map(_params._playerUnit, _params._searchRadius, _params._markRadius) -- returns TargetsInLOS table
|
|
end
|
|
--- test ------------------------------------------------------
|
|
--local unitName = "uh2-1" --"uh1-1" --"uh2-1"
|
|
--ctld.reconShowTargetsInLosOnF10Map(Unit.getByName(unitName),2000,200)
|
|
|
|
|
|
--**********************************************************************
|
|
|
|
-- ***************** SETUP SCRIPT ****************
|
|
function ctld.initialize()
|
|
ctld.logInfo(string.format("Initializing version %s", ctld.Version))
|
|
|
|
assert(mist ~= nil, "\n\n** HEY MISSION-DESIGNER! **\n\nMiST has not been loaded!\n\nMake sure MiST 3.6 or higher is running\n*before* running this script!\n")
|
|
|
|
ctld.addedTo = {}
|
|
ctld.spawnedCratesRED = {} -- use to store crates that have been spawned
|
|
ctld.spawnedCratesBLUE = {} -- use to store crates that have been spawned
|
|
|
|
ctld.droppedTroopsRED = {} -- stores dropped troop groups
|
|
ctld.droppedTroopsBLUE = {} -- stores dropped troop groups
|
|
|
|
ctld.droppedVehiclesRED = {} -- stores vehicle groups for c-130 / hercules
|
|
ctld.droppedVehiclesBLUE = {} -- stores vehicle groups for c-130 / hercules
|
|
|
|
ctld.inTransitTroops = {}
|
|
|
|
ctld.inTransitFOBCrates = {}
|
|
|
|
ctld.inTransitSlingLoadCrates = {} -- stores crates that are being transported by helicopters for alternative to real slingload
|
|
|
|
ctld.droppedFOBCratesRED = {}
|
|
ctld.droppedFOBCratesBLUE = {}
|
|
|
|
ctld.builtFOBS = {} -- stores fully built fobs
|
|
|
|
ctld.completeAASystems = {} -- stores complete spawned groups from multiple crates
|
|
|
|
ctld.fobBeacons = {} -- stores FOB radio beacon details, refreshed every 60 seconds
|
|
|
|
ctld.deployedRadioBeacons = {} -- stores details of deployed radio beacons
|
|
|
|
ctld.beaconCount = 1
|
|
|
|
ctld.usedUHFFrequencies = {}
|
|
ctld.usedVHFFrequencies = {}
|
|
ctld.usedFMFrequencies = {}
|
|
|
|
ctld.freeUHFFrequencies = {}
|
|
ctld.freeVHFFrequencies = {}
|
|
ctld.freeFMFrequencies = {}
|
|
|
|
--used to lookup what the crate will contain
|
|
ctld.crateLookupTable = {}
|
|
|
|
ctld.extractZones = {} -- stored extract zones
|
|
|
|
ctld.missionEditorCargoCrates = {} --crates added by mission editor for triggering cratesinzone
|
|
ctld.hoverStatus = {} -- tracks status of a helis hover above a crate
|
|
|
|
ctld.callbacks = {} -- function callback
|
|
|
|
|
|
-- Remove intransit troops when heli / cargo plane dies
|
|
--ctld.eventHandler = {}
|
|
--function ctld.eventHandler:onEvent(_event)
|
|
--
|
|
-- if _event == nil or _event.initiator == nil then
|
|
-- env.info("CTLD null event")
|
|
-- elseif _event.id == 9 then
|
|
-- -- Pilot dead
|
|
-- ctld.inTransitTroops[_event.initiator:getName()] = nil
|
|
--
|
|
-- elseif world.event.S_EVENT_EJECTION == _event.id or _event.id == 8 then
|
|
-- -- env.info("Event unit - Pilot Ejected or Unit Dead")
|
|
-- ctld.inTransitTroops[_event.initiator:getName()] = nil
|
|
--
|
|
-- -- env.info(_event.initiator:getName())
|
|
-- end
|
|
--
|
|
--end
|
|
|
|
-- create crate lookup table
|
|
for _subMenuName, _crates in pairs(ctld.spawnableCrates) do
|
|
|
|
for _, _crate in pairs(_crates) do
|
|
-- convert number to string otherwise we'll have a pointless giant
|
|
-- table. String means 'hashmap' so it will only contain the right number of elements
|
|
if _crate.multiple then
|
|
local _totalWeight = 0
|
|
for _, _weight in pairs(_crate.multiple) do
|
|
_totalWeight = _totalWeight + _weight
|
|
end
|
|
_crate.weight = _totalWeight
|
|
end
|
|
ctld.crateLookupTable[tostring(_crate.weight)] = _crate
|
|
end
|
|
end
|
|
|
|
|
|
--sort out pickup zones
|
|
for _, _zone in pairs(ctld.pickupZones) do
|
|
|
|
local _zoneName = _zone[1]
|
|
local _zoneColor = _zone[2]
|
|
local _zoneActive = _zone[4]
|
|
|
|
if _zoneColor == "green" then
|
|
_zone[2] = trigger.smokeColor.Green
|
|
elseif _zoneColor == "red" then
|
|
_zone[2] = trigger.smokeColor.Red
|
|
elseif _zoneColor == "white" then
|
|
_zone[2] = trigger.smokeColor.White
|
|
elseif _zoneColor == "orange" then
|
|
_zone[2] = trigger.smokeColor.Orange
|
|
elseif _zoneColor == "blue" then
|
|
_zone[2] = trigger.smokeColor.Blue
|
|
else
|
|
_zone[2] = -1 -- no smoke colour
|
|
end
|
|
|
|
-- add in counter for troops or units
|
|
if _zone[3] == -1 then
|
|
_zone[3] = 10000;
|
|
end
|
|
|
|
-- change active to 1 / 0
|
|
if _zoneActive == "yes" then
|
|
_zone[4] = 1
|
|
else
|
|
_zone[4] = 0
|
|
end
|
|
end
|
|
|
|
--sort out dropoff zones
|
|
for _, _zone in pairs(ctld.dropOffZones) do
|
|
|
|
local _zoneColor = _zone[2]
|
|
|
|
if _zoneColor == "green" then
|
|
_zone[2] = trigger.smokeColor.Green
|
|
elseif _zoneColor == "red" then
|
|
_zone[2] = trigger.smokeColor.Red
|
|
elseif _zoneColor == "white" then
|
|
_zone[2] = trigger.smokeColor.White
|
|
elseif _zoneColor == "orange" then
|
|
_zone[2] = trigger.smokeColor.Orange
|
|
elseif _zoneColor == "blue" then
|
|
_zone[2] = trigger.smokeColor.Blue
|
|
else
|
|
_zone[2] = -1 -- no smoke colour
|
|
end
|
|
|
|
--mark as active for refresh smoke logic to work
|
|
_zone[4] = 1
|
|
end
|
|
|
|
--sort out waypoint zones
|
|
for _, _zone in pairs(ctld.wpZones) do
|
|
|
|
local _zoneColor = _zone[2]
|
|
|
|
if _zoneColor == "green" then
|
|
_zone[2] = trigger.smokeColor.Green
|
|
elseif _zoneColor == "red" then
|
|
_zone[2] = trigger.smokeColor.Red
|
|
elseif _zoneColor == "white" then
|
|
_zone[2] = trigger.smokeColor.White
|
|
elseif _zoneColor == "orange" then
|
|
_zone[2] = trigger.smokeColor.Orange
|
|
elseif _zoneColor == "blue" then
|
|
_zone[2] = trigger.smokeColor.Blue
|
|
else
|
|
_zone[2] = -1 -- no smoke colour
|
|
end
|
|
|
|
--mark as active for refresh smoke logic to work
|
|
-- change active to 1 / 0
|
|
if _zone[3] == "yes" then
|
|
_zone[3] = 1
|
|
else
|
|
_zone[3] = 0
|
|
end
|
|
end
|
|
|
|
-- Sort out extractable groups
|
|
for _, _groupName in pairs(ctld.extractableGroups) do
|
|
|
|
local _group = Group.getByName(_groupName)
|
|
|
|
if _group ~= nil then
|
|
|
|
if _group:getCoalition() == 1 then
|
|
table.insert(ctld.droppedTroopsRED, _group:getName())
|
|
else
|
|
table.insert(ctld.droppedTroopsBLUE, _group:getName())
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- Seperate troop teams into red and blue for random AI pickups
|
|
if ctld.allowRandomAiTeamPickups == true then
|
|
ctld.redTeams = {}
|
|
ctld.blueTeams = {}
|
|
for _,_loadGroup in pairs(ctld.loadableGroups) do
|
|
if not _loadGroup.side then
|
|
table.insert(ctld.redTeams, _)
|
|
table.insert(ctld.blueTeams, _)
|
|
elseif _loadGroup.side == 1 then
|
|
table.insert(ctld.redTeams, _)
|
|
elseif _loadGroup.side == 2 then
|
|
table.insert(ctld.blueTeams, _)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- add total count
|
|
|
|
for _,_loadGroup in pairs(ctld.loadableGroups) do
|
|
|
|
_loadGroup.total = 0
|
|
if _loadGroup.aa then
|
|
_loadGroup.total = _loadGroup.aa + _loadGroup.total
|
|
end
|
|
|
|
if _loadGroup.inf then
|
|
_loadGroup.total = _loadGroup.inf + _loadGroup.total
|
|
end
|
|
|
|
|
|
if _loadGroup.mg then
|
|
_loadGroup.total = _loadGroup.mg + _loadGroup.total
|
|
end
|
|
|
|
if _loadGroup.at then
|
|
_loadGroup.total = _loadGroup.at + _loadGroup.total
|
|
end
|
|
|
|
if _loadGroup.mortar then
|
|
_loadGroup.total = _loadGroup.mortar + _loadGroup.total
|
|
end
|
|
|
|
end
|
|
|
|
|
|
-- Scheduled functions (run cyclically) -- but hold execution for a second so we can override parts
|
|
|
|
timer.scheduleFunction(ctld.checkAIStatus, nil, timer.getTime() + 1)
|
|
timer.scheduleFunction(ctld.checkTransportStatus, nil, timer.getTime() + 5)
|
|
|
|
timer.scheduleFunction(function()
|
|
|
|
timer.scheduleFunction(ctld.refreshRadioBeacons, nil, timer.getTime() + 5)
|
|
timer.scheduleFunction(ctld.refreshSmoke, nil, timer.getTime() + 5)
|
|
timer.scheduleFunction(ctld.addOtherF10MenuOptions, nil, timer.getTime() + 5)
|
|
|
|
if ctld.enableCrates == true and ctld.hoverPickup == true then
|
|
timer.scheduleFunction(ctld.checkHoverStatus, nil, timer.getTime() + 1)
|
|
end
|
|
|
|
end,nil, timer.getTime()+1 )
|
|
|
|
--event handler for deaths
|
|
--world.addEventHandler(ctld.eventHandler)
|
|
|
|
--env.info("CTLD event handler added")
|
|
|
|
env.info("Generating Laser Codes")
|
|
ctld.generateLaserCode()
|
|
env.info("Generated Laser Codes")
|
|
|
|
|
|
|
|
env.info("Generating UHF Frequencies")
|
|
ctld.generateUHFrequencies()
|
|
env.info("Generated UHF Frequencies")
|
|
|
|
env.info("Generating VHF Frequencies")
|
|
ctld.generateVHFrequencies()
|
|
env.info("Generated VHF Frequencies")
|
|
|
|
|
|
env.info("Generating FM Frequencies")
|
|
ctld.generateFMFrequencies()
|
|
env.info("Generated FM Frequencies")
|
|
|
|
-- Search for crates
|
|
-- Crates are NOT returned by coalition.getStaticObjects() for some reason
|
|
-- Search for crates in the mission editor instead
|
|
env.info("Searching for Crates")
|
|
for _coalitionName, _coalitionData in pairs(env.mission.coalition) do
|
|
|
|
if (_coalitionName == 'red' or _coalitionName == 'blue')
|
|
and type(_coalitionData) == 'table' then
|
|
if _coalitionData.country then --there is a country table
|
|
for _, _countryData in pairs(_coalitionData.country) do
|
|
|
|
if type(_countryData) == 'table' then
|
|
for _objectTypeName, _objectTypeData in pairs(_countryData) do
|
|
if _objectTypeName == "static" then
|
|
|
|
if ((type(_objectTypeData) == 'table')
|
|
and _objectTypeData.group
|
|
and (type(_objectTypeData.group) == 'table')
|
|
and (#_objectTypeData.group > 0)) then
|
|
|
|
for _groupId, _group in pairs(_objectTypeData.group) do
|
|
if _group and _group.units and type(_group.units) == 'table' then
|
|
for _unitNum, _unit in pairs(_group.units) do
|
|
if _unit.canCargo == true then
|
|
local _cargoName = env.getValueDictByKey(_unit.name)
|
|
ctld.missionEditorCargoCrates[_cargoName] = _cargoName
|
|
env.info("Crate Found: " .. _unit.name.." - Unit: ".._cargoName)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
env.info("END search for crates")
|
|
|
|
-- register event handler
|
|
ctld.logInfo("registering event handler")
|
|
world.addEventHandler(ctld.eventHandler)
|
|
|
|
env.info("CTLD READY")
|
|
end
|
|
|
|
--- Handle world events.
|
|
ctld.eventHandler = {}
|
|
function ctld.eventHandler:onEvent(event)
|
|
ctld.logTrace("ctld.eventHandler:onEvent()")
|
|
if event == nil then
|
|
ctld.logError("Event handler was called with a nil event!")
|
|
return
|
|
end
|
|
|
|
local eventName = "unknown"
|
|
-- check that we know the event
|
|
if event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT then
|
|
eventName = "S_EVENT_PLAYER_ENTER_UNIT"
|
|
elseif event.id == world.event.S_EVENT_BIRTH then
|
|
eventName = "S_EVENT_BIRTH"
|
|
else
|
|
ctld.logTrace("Ignoring event %s", ctld.p(event))
|
|
return
|
|
end
|
|
ctld.logDebug("caught event %s: %s", ctld.p(eventName), ctld.p(event))
|
|
|
|
-- find the originator unit
|
|
local unitName = nil
|
|
if event.initiator ~= nil and event.initiator.getName then
|
|
unitName = event.initiator:getName()
|
|
ctld.logDebug("unitName = [%s]", ctld.p(unitName))
|
|
end
|
|
if not unitName then
|
|
ctld.logInfo("no unitname found in event %s", ctld.p(event))
|
|
return
|
|
end
|
|
|
|
local function processHumanPlayer()
|
|
ctld.logTrace("in the 'processHumanPlayer' function")
|
|
if mist.DBs.humansByName[unitName] then -- it's a human unit
|
|
ctld.logDebug("caught event %s for human unit [%s]", ctld.p(eventName), ctld.p(unitName))
|
|
local _unit = Unit.getByName(unitName)
|
|
if _unit ~= nil then
|
|
-- assign transport pilot
|
|
ctld.logTrace("_unit = %s", ctld.p(_unit))
|
|
|
|
local playerTypeName = _unit:getTypeName()
|
|
ctld.logTrace("playerTypeName = %s", ctld.p(playerTypeName))
|
|
|
|
-- Allow units to CTLD by aircraft type and not by pilot name
|
|
if ctld.addPlayerAircraftByType then
|
|
for _,aircraftType in pairs(ctld.aircraftTypeTable) do
|
|
if aircraftType == playerTypeName then
|
|
ctld.logTrace("adding by aircraft type, unitName = %s", ctld.p(unitName))
|
|
-- add transport unit to the list
|
|
table.insert(ctld.transportPilotNames, unitName)
|
|
-- add transport radio menu
|
|
ctld.addTransportF10MenuOptions(unitName)
|
|
break
|
|
end
|
|
end
|
|
else
|
|
for _, _unitName in pairs(ctld.transportPilotNames) do
|
|
if _unitName == unitName then
|
|
ctld.logTrace("adding by transportPilotNames, unitName = %s", ctld.p(unitName))
|
|
-- add transport radio menu
|
|
ctld.addTransportF10MenuOptions(unitName)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not mist.DBs.humansByName[unitName] then
|
|
-- give a few milliseconds for MiST to handle the BIRTH event too
|
|
ctld.logTrace("give MiST some time to handle the BIRTH event too")
|
|
timer.scheduleFunction(function()
|
|
ctld.logTrace("calling the 'processHumanPlayer' function in a timer")
|
|
processHumanPlayer()
|
|
end, nil, timer.getTime()+0.5)
|
|
else
|
|
ctld.logTrace("calling the 'processHumanPlayer' function immediately")
|
|
processHumanPlayer()
|
|
end
|
|
|
|
end
|
|
|
|
function ctld.i18n_check(language, verbose)
|
|
local english = ctld.i18n["en"]
|
|
local tocheck = ctld.i18n[language]
|
|
if not tocheck then
|
|
ctld.logError(string.format("CTLD.i18n_check: Language %s not found", language))
|
|
return false
|
|
end
|
|
local englishVersion = english.translation_version
|
|
local tocheckVersion = tocheck.translation_version
|
|
if englishVersion ~= tocheckVersion then
|
|
ctld.logError(string.format("CTLD.i18n_check: Language version mismatch: EN has version %s, %s has version %s", englishVersion, language, tocheckVersion))
|
|
end
|
|
--ctld.logTrace(string.format("english = %s", ctld.p(english)))
|
|
for textRef, textEnglish in pairs(english) do
|
|
if textRef ~= "translation_version" then
|
|
local textTocheck = tocheck[textRef]
|
|
if not textTocheck then
|
|
ctld.logError(string.format( "CTLD.i18n_check: NOT FOUND: checking %s text [%s]", language, textRef))
|
|
elseif textTocheck == textEnglish then
|
|
ctld.logWarning(string.format("CTLD.i18n_check: SAME: checking %s text [%s] as in EN", language, textRef))
|
|
elseif verbose then
|
|
ctld.logInfo(string.format( "CTLD.i18n_check: OK: checking %s text [%s]", language, textRef))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- example of usage:
|
|
--ctld.i18n_check("fr")
|
|
|
|
|
|
-- initialize the random number generator to make it almost random
|
|
math.random(); math.random(); math.random()
|
|
|
|
--- Enable/Disable error boxes displayed on screen.
|
|
env.setErrorMessageBoxEnabled(false)
|
|
|
|
-- initialize CTLD
|
|
-- if you need to have a chance to modify the configuration before initialization in your other scripts, please set ctld.dontInitialize to true and call ctld.initialize() manually
|
|
if ctld.dontInitialize then
|
|
ctld.logInfo(string.format("Skipping initializion of version %s because ctld.dontInitialize is true", ctld.Version))
|
|
else
|
|
ctld.initialize()
|
|
end
|
|
trigger.action.outText("\n\n** CTLD LOADED\n", 10)
|
|
|
|
|
|
|
|
-- === Welcome-on-spawn for CTLD helicopters (no double message) ===
|
|
local _ctldTypeSet = {}
|
|
do
|
|
for k, v in pairs(ctld.aircraftTypeTable or {}) do
|
|
if type(k) == "string" then _ctldTypeSet[k] = true end
|
|
if type(v) == "string" then _ctldTypeSet[v] = true
|
|
elseif type(v) == "table" and v.type then _ctldTypeSet[v.type] = true end
|
|
end
|
|
end
|
|
|
|
local function _isCtldChopper(u)
|
|
if not u or not Unit.isExist(u) then return false end
|
|
local ut = u:getTypeName()
|
|
return ut and _ctldTypeSet[ut] == true
|
|
end
|
|
|
|
local function _welcomeUnit(u)
|
|
if not u or not Unit.isExist(u) then return end
|
|
timer.scheduleFunction(function()
|
|
if not Unit.isExist(u) then return end
|
|
if u:getPlayerName() and _isCtldChopper(u) then
|
|
trigger.action.outTextForUnit(u:getID(), "When building a SAM site spread the crates out so they don't spawn together. Keep launcher crates away from the other SAM parts, taxiways and runways. Move your chopper away from the launcher crate so the spawning launchers don't damage your chopper.", 40)
|
|
end
|
|
end, {}, timer.getTime() + 0.1)
|
|
end
|
|
|
|
local WelcomeHandler = {}
|
|
function WelcomeHandler:onEvent(e)
|
|
if e.id == world.event.S_EVENT_PLAYER_ENTER_UNIT and e.initiator then
|
|
_welcomeUnit(e.initiator)
|
|
end
|
|
end
|
|
world.addEventHandler(WelcomeHandler)
|
|
-- === End ===
|