Compare commits

...

100 Commits

Author SHA1 Message Date
Thomas 589e65963f Merge pull request #2604 from FlightControl-Master/leka1986-patch-2
Fix aircraft model names for Lancer and Kiowa
2026-07-12 17:57:29 +02:00
leka1986 af4f20af67 Fix aircraft model names for Lancer and Kiowa 2026-07-12 13:30:33 +02:00
Applevangelist 98ed7d50c1 #EASYGCICAP
* Added `SetDefaultINTERCEPTAlt()`
2026-06-28 09:30:45 +02:00
Applevangelist 626b12f486 #MSRS #HOUND corrected frequency formatting to allow 1000ths of a frequency 2026-06-27 10:57:27 +02:00
Thomas 8c60a8c678 Update ScheduleDispatcher.lua
Undoing previous change as some delayed event calls stopped working, probably FSM. Need to find the reason for that.
2026-06-26 12:31:54 +02:00
Thomas 867f919d5f Merge pull request #2601 from FlightControl-Master/leka1986-patch-2
Update Utils.lua
2026-06-24 06:29:28 +02:00
leka1986 f769a0deb5 Update Utils.lua
Added KA-50_3 door check.
2026-06-24 01:56:31 +02:00
Applevangelist 7e9dd9deff #EASYGCICAP - fall back to GCI behaviour if no CAP Points have been defined. 2026-06-23 12:29:45 +02:00
Applevangelist b6ea6606fc #SCHEDULEDISPATCHER - Fixing a memory leak. Thanks to @Shazzer1951 2026-06-21 12:51:50 +02:00
Thomas 5005203206 Merge pull request #2598 from patpatpowercat/develop
Update Airboss.lua
2026-06-14 17:52:13 +02:00
patpatpowercat a3455855b8 Update Airboss.lua
Remove max speed check that limits WOD in high winds.

This restriction seems redundant with GetHeadingIntoWind_new should return 30 with 0 wind (max carrier speed) regardless of requested WOD speed.
2026-06-14 08:36:38 -07:00
Applevangelist d1e4dd3fe9 #EVENT - small fix 2026-06-14 16:10:34 +02:00
Applevangelist 9326cfad1c #AIRBASE - Add Syria Airbases 2026-06-14 13:44:44 +02:00
Applevangelist b8e114101b #EVENT - Event Shoot fix for ZELL Boosters 2026-06-14 12:06:54 +02:00
Thomas f083b77390 Merge pull request #2596 from patpatpowercat/develop
Update Airboss.lua
2026-06-14 09:56:13 +02:00
patpatpowercat db3bc3742e Update Airboss.lua
Fix: manually stopped recovery windows immediately re-open. _CheckRecoveryTimes re-opened any window in its active time range without checking recovery.OVER, while DeleteRecoveryWindow failed to remove still-open windows. Added the OVER guard on the open path and made window deletion reliable and iteration-safe.
2026-06-13 09:36:27 -07:00
Applevangelist bf10196662 xx 2026-06-11 17:34:19 +02:00
Thomas a0420e1cb6 Merge pull request #2593 from patpatpowercat/develop
Update Mantis.lua
2026-06-07 20:04:10 +02:00
Applevangelist 27bd27a2e5 #EASYGCICAP - create correct number of Alert5 missions instead of relying on the number of required assets 2026-06-05 14:25:02 +02:00
patpatpowercat 8e219c5877 Update Mantis.lua
Widened (long-range systems): Nike, Hawk, SA-5, SA-12, SA-20A/B, SA-21/S-400, SA-22, SA-23/B, Patriot PAC-2/3, NASAMS, HQ-22, THAAD, IRIS-T, SON-9 → matches actual radar coverage rather than missile kinematic range
Tightened (short-range systems): SA-15, SA-19, HQ-7, HQ-17, Rapier → SHORAD shouldn't be jammable from absurd standoff
2026-06-01 11:20:24 -07:00
Applevangelist 3d1dcd78a3 #SPAWN - fix SpawnOnRunway if definitely requested (SP only!) 2026-05-24 13:00:57 +02:00
Thomas 9cda1b88ea Merge pull request #2591 from patpatpowercat/develop
Add jammer ability to MANTIS
2026-05-21 20:03:54 +02:00
patpatpowercat 09f99b9a74 Merge branch 'develop' of https://github.com/FlightControl-Master/MOOSE into develop 2026-05-19 09:41:34 -07:00
Thomas ff5205e686 Merge pull request #2590 from FlightControl-Master/master-ng
Merge from master
2026-05-18 06:24:53 +02:00
Thomas ebd105f80d Merge pull request #2589 from FlightControl-Master/leka1986-patch-1
Update Shorad.lua
2026-05-18 06:23:59 +02:00
leka1986 cc8a4e0bad Update CTLD_Localization.lua
Added Italian localization.
2026-05-16 19:29:50 +02:00
leka1986 5f70e2d6b1 Add Italian locale support to CTLD
Added Italian Localization
2026-05-16 19:29:07 +02:00
leka1986 95f38fbf55 Update Shorad.lua
Fix tgtgrp2 is nil.
2026-05-16 19:27:06 +02:00
Applevangelist aec3466f13 Merge remote-tracking branch 'origin/master-ng' into develop 2026-05-14 10:47:23 +02:00
Applevangelist 696630fe4d #SUPPRESSION - fix typo 2026-05-14 10:46:22 +02:00
Applevangelist 47aa524677 Merge remote-tracking branch 'origin/master-ng' into develop 2026-05-10 13:04:56 +02:00
Applevangelist 79f03c164c xx 2026-05-10 13:04:29 +02:00
patpatpowercat ca7d3a48a7 Update Mantis.lua
The following code is modified MANTIS code that adds jamming capability. It has new custom code for all SAMs, fixes a couple SAM issues (S-300V/V4, adds SON-9). Clients/players can select their loadout, and then utilize the F10 menu to turn the jammer on/off. For AI aircraft, they can be setup with whatever loadout, and then scripted to turn their jammer on/off.
2026-05-08 14:02:06 -07:00
Thomas 2556dbe145 Merge pull request #2588 from FlightControl-Master/master-ng
Update Auftrag.lua
2026-05-05 11:47:29 +02:00
Thomas af1e9546e5 Update Auftrag.lua
line 6613 - Fix for HasSEAD should it find statics unexpectedly
2026-05-05 11:46:16 +02:00
Thomas 3203a22668 Merge pull request #2587 from FlightControl-Master/master-ng
Merge
2026-05-05 10:34:35 +02:00
Thomas b69ac2f86f Merge pull request #2586 from FlightControl-Master/leka1986-patch-1
Update CTLD.lua
2026-05-05 10:33:36 +02:00
leka1986 0d410f3e55 Update CTLD.lua
remove whitespace
2026-05-03 19:51:24 +02:00
leka1986 cbe6756330 Update CTLD.lua 2026-05-03 19:27:46 +02:00
leka1986 07045be32b Update CTLD_Localization.lua
Added traditional Chinese and Taiwan Chinese
2026-05-03 19:27:16 +02:00
leka1986 52a2132463 Update CTLD_Localization.lua
Added CH,  PT-BR, TR.
2026-05-03 16:21:14 +02:00
leka1986 9272b1f18c Update CTLD.lua
### Summary
- Added an optional CTLD group locale resolver for group-targeted menus and messages.
- Routed CTLD group menu/message localization through group-aware lookup while preserving `self.locale` as the default fallback.
- Added an optional cargo display formatter with group context for missions that need custom cargo labels.
- Added `MENU_CRATE_SINGLE` localization so single-crate menu labels use the localization table, matching multi-crate labels.

### Compatibility
Default behavior is unchanged unless a mission calls `SetGroupLocaleResolver` or `SetCargoDisplayFormatter`.

### Motivation
This allows missions with per-player or per-group language settings to localize CTLD menus and group messages without changing the global CTLD locale, This can be used to persist localization choice using the player name.
2026-05-03 16:19:44 +02:00
Applevangelist b84880c0dd Merge remote-tracking branch 'origin/develop' into develop 2026-05-01 12:46:49 +02:00
Applevangelist a9be18b284 Merge remote-tracking branch 'origin/master-ng' into develop 2026-05-01 12:46:45 +02:00
Thomas 93178df67f Merge pull request #2585 from FlightControl-Master/master-ng
Merge from master
2026-04-30 17:38:17 +02:00
Thomas f34bf1e052 Merge pull request #2584 from HoodiNahi/fix/spawn-init-randomize-zones-nil-gaurd
Fix(Spawn):add nil gaurd to InitRandomizeZones
2026-04-30 17:37:30 +02:00
HoodiNahi e159eb83b7 fix syntax 2026-04-30 20:07:36 +05:30
HoodiNahi 2581836058 Fix(Spawn):add nil gaurd to InitRandomizeZones 2026-04-30 20:03:52 +05:30
Thomas d9651e1dea Merge pull request #2583 from FlightControl-Master/master-ng
Pr
2026-04-27 17:34:03 +02:00
Thomas 0347f185a5 Merge pull request #2582 from FlightControl-Master/leka1986-patch-1
Leka1986 patch 1
2026-04-27 17:33:27 +02:00
leka1986 5e2218de38 Expand locale options in CTLD.lua
Updated language locale options to include Spanish and Russian.
2026-04-26 19:26:23 +02:00
leka1986 6db5441bb1 Modify parking spot handling in Spawn.lua
Refactor parking spot usage logic to use explicit spots when available.
2026-04-26 19:13:09 +02:00
leka1986 13b7e43729 Update CTLD_Localization.lua
Russian localization.
2026-04-26 19:11:26 +02:00
leka1986 a72f7298d0 Update CTLD.lua
Added Russian locale, 

- my_ctld.locale = "ru"
2026-04-26 19:10:03 +02:00
Applevangelist 801cc105da Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-25 16:49:58 +02:00
Applevangelist f186b4bd58 xx 2026-04-25 16:49:10 +02:00
Frank 79f13fa6ae Update Warehouse.lua
- fixes bug that descriptors are not obtained
2026-04-25 16:45:26 +02:00
Applevangelist d38fc4ec8f Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-23 12:41:40 +02:00
Applevangelist ba1575d674 #MSRS - Added Hound Jammer option functionality 2026-04-23 12:41:01 +02:00
Applevangelist 391182e23f Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-21 18:30:01 +02:00
Applevangelist 6d5fb64fde #TARS - Docu additions 2026-04-21 18:29:25 +02:00
Frank 5df1fb88f1 Merge branch 'master-ng' of https://github.com/FlightControl-Master/MOOSE into master-ng 2026-04-20 21:47:38 +02:00
Frank 2e4d4f056a Update SRS.lua
- fixed bug if no frequencies and/or modulations are specified (UTILS.EnsureTable returned an empty table not nil. therefore, self.frequencies and self.modulations was not used)
2026-04-20 21:47:36 +02:00
Thomas a9ceda4edd Merge pull request #2580 from FlightControl-Master/master-ng
Merge from master
2026-04-20 06:36:40 +02:00
Thomas e76fd23396 Merge pull request #2579 from FlightControl-Master/leka1986-patch-1
Update Spawn.lua
2026-04-20 06:35:31 +02:00
leka1986 7df1eb963f Update CSAR.lua
CSAR now treats aircraft with AircraftType[type] <= 0 as not CSAR-capable when building the active SAR unit list, while still allowing those type keys to exist for other systems such as FARP storage setup.

Added those to the type,

CSAR.AircraftType["Ka-50_3"] = 0
CSAR.AircraftType["Ka-50"] = 0
CSAR.AircraftType["AV8BNA"] = 0
2026-04-19 20:36:44 +02:00
leka1986 aff1d3c7db Update Spawn.lua
SPAWN:SpawnAtAirbase() and SPAWN:ParkAircraft() now handle HELIPAD/FARP parking the same way as airbases: explicit parking is honored first, helicopter spawns try the smarter parking search before falling back to the simpler free-spot table.
2026-04-19 20:35:20 +02:00
Applevangelist d15f6cdb44 Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-19 15:31:01 +02:00
Applevangelist c59b668eec #TARS
- Correct optical view projection implemented (ellipse bc of the angle of view)
- Duration now describes the number of expositions your camera has
- Interval of shots is dependent on the speed of the TARS unit
- Added platform parameters overlap (of shots, usually 25% for faster movers, 50% for slower ones), overlap is clamped by minimum interval — at high speeds the interval floor is reached and overlap effectively increases (and you go through shots really fast)
- Fixed zone draw for debug
2026-04-19 15:29:40 +02:00
Applevangelist 4620a83317 Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-16 12:38:47 +02:00
Applevangelist 87d0c4e9d4 #TARS - Added TARS_SESSION.debugunitsearch = false and F10 search zone draw for debugging 2026-04-16 12:38:15 +02:00
Thomas 74361f6a3a Merge pull request #2578 from FlightControl-Master/master-ng
Merge
2026-04-16 07:00:29 +02:00
Thomas b82b882e02 Merge pull request #2577 from FlightControl-Master/leka1986-patch-1
Update CTLD.lua
2026-04-16 06:59:46 +02:00
leka1986 347783d4b7 Update CTLD_Localization.lua
---
-- @field Messages
CTLD.Messages = {
    EN = {
        -- ============================================================
        -- Crate / Cargo Loading
        -- ============================================================
        CRATE_LOADED_GROUNDCREW         = "Crate %s loaded by ground crew!",
        CRATE_UNLOADED_GROUNDCREW       = "Crate %s unloaded by ground crew!",
        CRATE_LOADED_ID                 = "Crate ID %d for %s loaded!",
        LOADED_FULL                     = "Loaded %d %s.",
        LOADED_SETS_LEFTOVER            = "Loaded %d %s(s), with %d leftover crate(s).",
        LOADED_SETS                     = "Loaded %d %s(s).",
        LOADED_PARTIAL                  = "Loaded only %d/%d crate(s) of %s.",
        LOADED_PARTIAL_LIMIT            = "Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",
        LOADED_BATCH                    = "Loaded %d %s.",
        LOADED_BATCH_PARTIAL            = "Some sets could not be fully loaded.",
        -- ============================================================
        -- Dropping / Unloading
        -- ============================================================
        DROPPED_FULL                    = "Dropped %d %s.",
        DROPPED_SETS_LEFTOVER           = "Dropped %d %s(s), with %d leftover crate(s).",
        DROPPED_SETS                    = "Dropped %d %s(s).",
        DROPPED_PARTIAL                 = "Dropped %d/%d crate(s) of %s.",
        DROPPED_INTO_ACTION             = "Dropped %s into action!",
        DROPPED_BEACON                  = "Dropped %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
        CRATES_POSITIONED               = "%d crates for %s have been positioned near you!",
        CRATES_DROPPED                  = "%d crates for %s have been dropped!",
        -- ============================================================
        -- Troops
        -- ============================================================
        BOARDED                         = "%s boarded!",
        BOARDING                        = "%s boarding!",
        TROOPS_RETURNED                 = "Troops have returned to base!",
        -- ============================================================
        -- Deployment
        -- ============================================================
        DEPLOYED_NEAR_YOU               = "%s have been deployed near you!",
        UNITS_REMOVED                   = "%s have been removed",
        -- ============================================================
        -- Build / Repair
        -- ============================================================
        BUILD_STARTED                   = "Build started, ready in %d seconds!",
        REPAIR_STARTED                  = "Repair started using %s taking %d secs",
        NO_UNIT_TO_REPAIR               = "No unit close enough to repair!",
        CANT_REPAIR_WITH                = "Can't repair this unit with %s",
        CRATES_MOVE_BEFORE_BUILD        = "*** Crates need to be moved before building!",
        -- ============================================================
        -- Errors - Chopper / Weight / Capacity
        -- ============================================================
        CHOPPER_CANNOT_CARRY            = "Sorry this chopper cannot carry crates!",
        TOO_HEAVY                       = "Sorry, that's too heavy to load!",
        FULLY_LOADED                    = "Sorry, we are fully loaded!",
        CRAMMED                         = "Sorry, we're crammed already!",
        NO_CAPACITY_NOW                 = "No capacity to load more now!",
        NO_MORE_CAPACITY                = "No more capacity to load crates!",
        CANNOT_LOAD_NONE_OR_FULL        = "Cannot load crates: either none found or no capacity left.",
        -- ============================================================
        -- Errors - Position
        -- ============================================================
        NEED_TO_LAND_OR_HOVER_LOAD      = "You need to land or hover in position to load!",
        HOVER_OVER_CRATES               = "Hover over the crates to pick them up!",
        LAND_OR_HOVER_OVER_CRATES       = "Land or hover over the crates to pick them up!",
        MUST_LAND_OR_HOVER_CRATES       = "You must land or hover to load crates!",
        NEED_TO_LAND_BUILD              = "You need to land / stop to build something, Pilot!",
        NOT_CLOSE_ENOUGH_LOGISTICS      = "You are not close enough to a logistics zone!",
        NOT_CLOSE_ENOUGH_DROP           = "You are not close enough to a drop zone!",
        NOT_CLOSE_ENOUGH_ZONE_NM        = "Negative, need to be closer than %dnm to a zone!",
        CANNOT_BUILD_LOADING_AREA       = "You cannot build in a loading area, Pilot!",
        -- ============================================================
        -- Errors - Doors
        -- ============================================================
        OPEN_DOORS_LOAD_CARGO           = "You need to open the door(s) to load cargo!",
        OPEN_DOORS_LOAD_TROOPS          = "You need to open the door(s) to load troops!",
        OPEN_DOORS_EXTRACT_TROOPS       = "You need to open the door(s) to extract troops!",
        OPEN_DOORS_UNLOAD_TROOPS        = "You need to open the door(s) to unload troops!",
        OPEN_DOORS_DROP_CARGO           = "You need to open the door(s) to drop cargo!",
        -- ============================================================
        -- Errors - Stock / Availability
        -- ============================================================
        ALL_GONE                        = "Sorry, all %s are gone!",
        RAN_OUT_OF                      = "Sorry, we ran out of %s",
        CARGO_NOT_AVAILABLE_ZONE        = "The requested cargo is not available in this zone!",
        ENOUGH_CRATES_NEARBY            = "There are enough crates nearby already! Take care of those first!",
        NO_CRATES_WITHIN                = "No (loadable) crates within %d meters!",
        NO_CRATES_WITHIN_PLAIN          = "No crates within %d meters!",
        NO_CRATES_IN_RANGE              = "No crates found in range!",
        NO_NAMED_CRATES_IN_RANGE        = "No \"%s\" crates found in range!",
        NO_LOADABLE_CRATES              = "Sorry, no loadable crates nearby or max cargo weight reached!",
        NO_UNITS_TO_EXTRACT             = "No units close enough to extract!",
        NO_UNIT_CONFIG                  = "No unit configuration found for %s",
        CANT_ONBOARD                    = "Can't onboard %s",
        TOO_MANY_UNITS_NEARBY           = "You already have %d units nearby!",
        NO_CRATE_GROUPS                 = "No crate groups found for this unit!",
        NO_CRATE_SET                    = "No crate set found or index invalid!",
        NO_CRATE_IN_SET                 = "No crate found in that set!",
        NO_TROOP_CHUNK                  = "No troop cargo chunk found for ID %d!",
        TROOP_CHUNK_EMPTY               = "Troop chunk is empty for ID %d!",
        -- ============================================================
        -- Nothing loaded / in stock
        -- ============================================================
        NOTHING_LOADED                  = "Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs",
        NOTHING_LOADED_AIRDROP          = "Nothing loaded or not within airdrop parameters!",
        NOTHING_LOADED_HOVER            = "Nothing loaded or not hovering within parameters!",
        NOTHING_IN_STOCK                = "Nothing in stock!",
        NOTHING_TO_PACK                 = "Nothing to pack at this distance pilot!",
        NOTHING_TO_REMOVE               = "Nothing to remove at this distance pilot!",
        -- ============================================================
        -- Zone / Info
        -- ============================================================
        ROGER_ZONE                      = "Roger, %s zone %s!",
        -- ============================================================
        -- Report: Hover / Flight Parameters
        -- ============================================================
        HOVER_PARAMS_METRIC             = "Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s",
        HOVER_PARAMS_IMPERIAL           = "Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s",
        FLIGHT_PARAMS_IMPERIAL          = "Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s",
        FLIGHT_PARAMS_METRIC            = "Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s",
        -- ============================================================
        -- Report Titles  (REPORT:New())
        -- ============================================================
        REPORT_CRATES_FOUND             = "Crates Found Nearby:",
        REPORT_REMOVING_CRATES          = "Removing Crates Found Nearby:",
        REPORT_TRANSPORT_CHECKOUT       = "Transport Checkout Sheet",
        REPORT_INVENTORY                = "Inventory Sheet",
        REPORT_BUILD_CHECKLIST          = "Checklist Buildable Crates",
        REPORT_REPAIR_CHECKLIST         = "Checklist Repairs",
        REPORT_BEACONS                  = "Active Zone Beacons",
        -- ============================================================
        -- Report Section Headers  (report:Add())
        -- ============================================================
        REPORT_SECTION_TROOPS           = "        -- TROOPS --",
        REPORT_SECTION_CRATES           = "       -- CRATES --",
        REPORT_SECTION_CRATES_GC        = "       -- CRATES loaded via Ground Crew --",
        REPORT_SECTION_NONE             = "        N O N E",
        REPORT_SECTION_NONE_ALT         = "     --- None found! ---",
        REPORT_SECTION_NONE_REPAIR      = "     --- None Found ---",
        REPORT_GC_LOADABLE_HINT         = "Probably ground crew loadable (F8)",
        REPORT_TOTAL_MASS               = "Total Mass: %s kg. Loadable: %s kg.",
        REPORT_TROOPS_CRATES_COUNT      = "Troops: %d(%d), Crates: %d(%d)",
        REPORT_TROOPS_CRATETYPES_COUNT  = "Troops: %d, Cratetypes: %d",
        -- ============================================================
        -- Report Row Templates  (per-item lines in reports)
        -- ============================================================
        REPORT_ROW_TROOP                = "Troop: %s size %d",
        REPORT_ROW_CRATE                = "Crate: %s %d/%d",
        REPORT_ROW_CRATE_SIZE1          = "Crate: %s size 1",
        REPORT_ROW_GC_CRATE             = "GC loaded Crate: %s size 1",
        REPORT_ROW_DROPPED_CRATE        = "Dropped crate for %s, %dkg",
        REPORT_ROW_CRATE_KG             = "Crate for %s, %dkg",
        REPORT_ROW_CRATE_REMOVED        = "Crate for %s, %dkg removed",
        REPORT_ROW_UNIT_STOCK           = "Unit: %s | Soldiers: %d | Stock: %s",
        REPORT_ROW_TYPE_CRATE_STOCK     = "Type: %s | Crates per Set: %d | Stock: %s",
        REPORT_ROW_TYPE_STOCK           = "Type: %s | Stock: %s",
        REPORT_ROW_BUILD_CHECK          = "Type: %s | Required %d | Found %d | Can Build %s",
        REPORT_ROW_REPAIR_CHECK         = "Type: %s | Required %d | Found %d | Can Repair %s",
        REPORT_ROW_BEACON               = " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
        -- ============================================================
        -- Weight / Crate limit tokens
        -- ============================================================
        WEIGHT_LIMIT                    = "Weight limit reached",
        CRATE_LIMIT                     = "Crate limit reached",
        -- ============================================================
        -- Menu labels - Top level
        -- ============================================================
        MENU_CTLD                       = "CTLD",
        MENU_MANAGE_TROOPS              = "Manage Troops",
        MENU_MANAGE_CRATES              = "Manage Crates",
        MENU_MANAGE_UNITS               = "Manage Units",
        -- ============================================================
        -- Menu labels - Troops
        -- ============================================================
        MENU_LOAD_TROOPS                = "Load troops",
        MENU_DROP_TROOPS                = "Drop Troops",
        MENU_DROP_ALL_TROOPS            = "Drop ALL troops",
        MENU_EXTRACT_TROOPS             = "Extract troops",
        MENU_DROP_N_TROOPS              = "Drop (%d) %s",
        -- ============================================================
        -- Menu labels - Crates: Get
        -- ============================================================
        MENU_GET_CRATES                 = "Get Crates",
        MENU_GET                        = "Get",
        MENU_GET_AND_LOAD               = "Get and Load",
        MENU_GET_ANYWAY                 = "Get anyway",
        MENU_PARTIALLY_LOAD             = "Partially load",
        MENU_OUT_OF_STOCK               = "Out of stock",
        MENU_TROOP_LIMIT                = "Troop limit reached",
        -- ============================================================
        -- Menu labels - Crates: Load
        -- ============================================================
        MENU_LOAD_CRATES                = "Load Crates",
        MENU_LOAD_ALL                   = "Load ALL",
        MENU_SHOW_LOADABLE_CRATES       = "Show loadable crates",
        MENU_NO_CRATES_FOUND_RESCAN     = "No crates found! Rescan?",
        MENU_USE_C130_LOAD              = "Use C-130 Load system",
        MENU_LOAD_SINGLE                = "Load",
        -- ============================================================
        -- Menu labels - Crates: Drop
        -- ============================================================
        MENU_DROP_CRATES                = "Drop Crates",
        MENU_DROP_ALL_CRATES            = "Drop ALL crates",
        MENU_DROP                       = "Drop",
        MENU_DROP_AND_BUILD             = "Drop and build",
        MENU_DROP_N_SETS                = "Drop %d Set%s",
        MENU_NO_CRATES_TO_DROP          = "No crates to drop!",
        -- ============================================================
        -- Menu labels - Crates: Build / Repair / Pack / Remove
        -- ============================================================
        MENU_BUILD_CRATES               = "Build crates",
        MENU_REPAIR                     = "Repair",
        MENU_PACK_CRATES                = "Pack crates",
        MENU_PACK                       = "Pack",
        MENU_SCAN_PACKABLE_UNITS        = "Scan packable units nearby",
        MENU_NO_PACKABLE_UNITS_FOUND_RESCAN = "No packable units found! Rescan?",
        MENU_PACK_ALL                   = "Pack nearby",
        MENU_PACK_AND_LOAD              = "Pack and Load",
        MENU_PACK_AND_LOAD_ALL          = "Pack and Load nearby",
        MENU_PACK_AND_REMOVE            = "Pack and Remove",
        MENU_PACK_AND_REMOVE_ALL        = "Pack and Remove nearby",
        MENU_REMOVE_CRATES              = "Remove crates",
        MENU_REMOVE_CRATES_NEARBY       = "Remove crates nearby",
        MENU_LIST_CRATES_NEARBY         = "List crates nearby",
        MENU_CRATES_NEEDED              = "%d crate%s %s (%dkg)",
        -- ============================================================
        -- Menu labels - Units (C-130)
        -- ============================================================
        MENU_GET_UNITS                  = "Get Units",
        MENU_REMOVE_UNITS_NEARBY        = "Remove units nearby",
        -- ============================================================
        -- Menu labels - Info / Cargo
        -- ============================================================
        MENU_LIST_BOARDED_CARGO         = "List boarded cargo",
        MENU_INVENTORY                  = "Inventory",
        MENU_LIST_ZONE_BEACONS          = "List active zone beacons",
        -- ============================================================
        -- Menu labels - Smokes / Flares / Beacons
        -- ============================================================
        MENU_SMOKES_FLARES_BEACONS      = "Smokes, Flares, Beacons",
        MENU_SMOKE_ZONES_NEARBY         = "Smoke zones nearby",
        MENU_DROP_SMOKE_NOW             = "Drop smoke now",
        MENU_RED_SMOKE                  = "Red smoke",
        MENU_BLUE_SMOKE                 = "Blue smoke",
        MENU_GREEN_SMOKE                = "Green smoke",
        MENU_ORANGE_SMOKE               = "Orange smoke",
        MENU_WHITE_SMOKE                = "White smoke",
        MENU_FLARE_ZONES_NEARBY         = "Flare zones nearby",
        MENU_FIRE_FLARE_NOW             = "Fire flare now",
        MENU_DROP_BEACON_NOW            = "Drop beacon now",
        -- ============================================================
        -- Menu labels - Parameters
        -- ============================================================
        MENU_SHOW_FLIGHT_PARAMS         = "Show flight parameters",
        MENU_SHOW_HOVER_PARAMS          = "Show hover parameters",
        STOCK_NONE                      = "none",
        STOCK_UNLIMITED                 = "unlimited",
        BUILD_YES                       = "YES",
        BUILD_NO                        = "NO",
    },
  DE = {
        -- ============================================================
        -- Kiste / Fracht laden
        -- ============================================================
        CRATE_LOADED_GROUNDCREW         = "Kiste %s vom Bodenpersonal geladen!",
        CRATE_UNLOADED_GROUNDCREW       = "Kiste %s vom Bodenpersonal entladen!",
        CRATE_LOADED_ID                 = "Kiste ID %d für %s geladen!",
        LOADED_FULL                     = "%d %s geladen.",
        LOADED_SETS_LEFTOVER            = "%d %s geladen, %d Kiste(n) übrig.",
        LOADED_SETS                     = "%d %s geladen.",
        LOADED_PARTIAL                  = "Nur %d/%d Kiste(n) von %s geladen.",
        LOADED_PARTIAL_LIMIT            = "Nur %d/%d Kiste(n) von %s geladen. Frachtlimit erreicht!",
        LOADED_BATCH                    = "%d %s geladen.",
        LOADED_BATCH_PARTIAL            = "Einige Sets konnten nicht vollständig geladen werden.",
        -- ============================================================
        -- Abwerfen / Entladen
        -- ============================================================
        DROPPED_FULL                    = "%d %s abgeworfen.",
        DROPPED_SETS_LEFTOVER           = "%d %s abgeworfen, %d Kiste(n) übrig.",
        DROPPED_SETS                    = "%d %s abgeworfen.",
        DROPPED_PARTIAL                 = "%d/%d Kiste(n) von %s abgeworfen.",
        DROPPED_INTO_ACTION             = "%s im Einsatz abgesetzt!",
        DROPPED_BEACON                  = "%s abgesetzt | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
        CRATES_POSITIONED               = "%d Kisten für %s in Ihrer Nähe positioniert!",
        CRATES_DROPPED                  = "%d Kisten für %s abgeworfen!",
        -- ============================================================
        -- Truppen
        -- ============================================================
        BOARDED                         = "%s eingestiegen!",
        BOARDING                        = "%s steigt ein!",
        TROOPS_RETURNED                 = "Truppen zur Basis zurückgekehrt!",
        -- ============================================================
        -- Einsatz
        -- ============================================================
        DEPLOYED_NEAR_YOU               = "%s in Ihrer Nähe eingesetzt!",
        UNITS_REMOVED                   = "%s entfernt",
        -- ============================================================
        -- Bauen / Reparieren
        -- ============================================================
        BUILD_STARTED                   = "Bau gestartet, fertig in %d Sekunden!",
        REPAIR_STARTED                  = "Reparatur mit %s gestartet, dauert %d Sek.",
        NO_UNIT_TO_REPAIR               = "Keine Einheit in Reichweite zum Reparieren!",
        CANT_REPAIR_WITH                = "Diese Einheit kann nicht mit %s repariert werden",
        CRATES_MOVE_BEFORE_BUILD        = "*** Kisten müssen vor dem Bau verschoben werden!",
        -- ============================================================
        -- Fehler - Hubschrauber / Gewicht / Kapazität
        -- ============================================================
        CHOPPER_CANNOT_CARRY            = "Dieser Hubschrauber kann keine Kisten transportieren!",
        TOO_HEAVY                       = "Entschuldigung, das ist zu schwer zum Laden!",
        FULLY_LOADED                    = "Entschuldigung, wir sind voll beladen!",
        CRAMMED                         = "Entschuldigung, wir sind bereits voll besetzt!",
        NO_CAPACITY_NOW                 = "Aktuell keine Ladekapazität mehr vorhanden!",
        NO_MORE_CAPACITY                = "Keine Kapazität mehr für weitere Kisten!",
        CANNOT_LOAD_NONE_OR_FULL        = "Laden nicht möglich: keine Kisten gefunden oder Kapazität erschöpft.",
        -- ============================================================
        -- Fehler - Position
        -- ============================================================
        NEED_TO_LAND_OR_HOVER_LOAD      = "Bitte landen oder schweben Sie zum Laden!",
        HOVER_OVER_CRATES               = "Schweben Sie über die Kisten, um sie aufzunehmen!",
        LAND_OR_HOVER_OVER_CRATES       = "Landen oder schweben Sie über die Kisten, um sie aufzunehmen!",
        MUST_LAND_OR_HOVER_CRATES       = "Sie müssen landen oder schweben, um Kisten zu laden!",
        NEED_TO_LAND_BUILD              = "Sie müssen landen / anhalten, um etwas zu bauen, Pilot!",
        NOT_CLOSE_ENOUGH_LOGISTICS      = "Sie sind nicht nah genug an einer Logistikzone!",
        NOT_CLOSE_ENOUGH_DROP           = "Sie sind nicht nah genug an einer Abwurfzone!",
        NOT_CLOSE_ENOUGH_ZONE_NM        = "Negativ, Sie müssen näher als %d Seemeilen an einer Zone sein!",
        CANNOT_BUILD_LOADING_AREA       = "In einem Ladebereich kann nicht gebaut werden, Pilot!",
        -- ============================================================
        -- Fehler - Türen
        -- ============================================================
        OPEN_DOORS_LOAD_CARGO           = "Bitte öffnen Sie die Tür(en) zum Laden von Fracht!",
        OPEN_DOORS_LOAD_TROOPS          = "Bitte öffnen Sie die Tür(en) zum Einladen von Truppen!",
        OPEN_DOORS_EXTRACT_TROOPS       = "Bitte öffnen Sie die Tür(en) zum Aussteigen der Truppen!",
        OPEN_DOORS_UNLOAD_TROOPS        = "Bitte öffnen Sie die Tür(en) zum Entladen der Truppen!",
        OPEN_DOORS_DROP_CARGO           = "Bitte öffnen Sie die Tür(en) zum Abwerfen der Fracht!",
        -- ============================================================
        -- Fehler - Bestand / Verfügbarkeit
        -- ============================================================
        ALL_GONE                        = "Entschuldigung, alle %s sind vergriffen!",
        RAN_OUT_OF                      = "Entschuldigung, %s ist nicht mehr vorrätig",
        CARGO_NOT_AVAILABLE_ZONE        = "Die angeforderte Fracht ist in dieser Zone nicht verfügbar!",
        ENOUGH_CRATES_NEARBY            = "Es sind bereits genügend Kisten in der Nähe! Bitte zuerst um diese kümmern!",
        NO_CRATES_WITHIN                = "Keine (ladbaren) Kisten in %d Metern Umkreis!",
        NO_CRATES_WITHIN_PLAIN          = "Keine Kisten in %d Metern Umkreis!",
        NO_CRATES_IN_RANGE              = "Keine Kisten in Reichweite gefunden!",
        NO_NAMED_CRATES_IN_RANGE        = "Keine \"%s\"-Kisten in Reichweite gefunden!",
        NO_LOADABLE_CRATES              = "Entschuldigung, keine ladbaren Kisten in der Nähe oder maximales Frachtgewicht erreicht!",
        NO_UNITS_TO_EXTRACT             = "Keine Einheiten nah genug zum Aussteigen!",
        NO_UNIT_CONFIG                  = "Keine Einheitenkonfiguration für %s gefunden",
        CANT_ONBOARD                    = "%s kann nicht eingeladen werden",
        TOO_MANY_UNITS_NEARBY           = "Sie haben bereits %d Einheiten in der Nähe!",
        NO_CRATE_GROUPS                 = "Keine Kistengruppen für diese Einheit gefunden!",
        NO_CRATE_SET                    = "Kein Kistenset gefunden oder Index ungültig!",
        NO_CRATE_IN_SET                 = "Keine Kiste in diesem Set gefunden!",
        NO_TROOP_CHUNK                  = "Kein Truppenfracht-Block für ID %d gefunden!",
        TROOP_CHUNK_EMPTY               = "Truppenfracht-Block für ID %d ist leer!",
        -- ============================================================
        -- Nichts geladen / kein Bestand
        -- ============================================================
        NOTHING_LOADED                  = "Nichts geladen!\nTruppenlimit: %d | Kistenlimit: %d | Gewichtslimit: %d kg",
        NOTHING_LOADED_AIRDROP          = "Nichts geladen oder nicht innerhalb der Abwurfparameter!",
        NOTHING_LOADED_HOVER            = "Nichts geladen oder Schwebeparameter nicht erfüllt!",
        NOTHING_IN_STOCK                = "Nichts vorrätig!",
        NOTHING_TO_PACK                 = "Nichts in dieser Entfernung zum Verpacken, Pilot!",
        NOTHING_TO_REMOVE               = "Nichts in dieser Entfernung zum Entfernen, Pilot!",
        -- ============================================================
        -- Zone / Info
        -- ============================================================
        ROGER_ZONE                      = "Verstanden, %s Zone %s!",
        -- ============================================================
        -- Report: Schwebe- / Flugparameter
        -- ============================================================
        HOVER_PARAMS_METRIC             = "Schwebeparameter (Autoladen/Abwurf):\n - Min. Höhe %dm \n - Max. Höhe %dm \n - Max. Geschwindigkeit 2m/s \n - Im Parameter: %s",
        HOVER_PARAMS_IMPERIAL           = "Schwebeparameter (Autoladen/Abwurf):\n - Min. Höhe %dft \n - Max. Höhe %dft \n - Max. Geschwindigkeit 6ft/s \n - Im Parameter: %s",
        FLIGHT_PARAMS_IMPERIAL          = "Flugparameter (Luftabwurf):\n - Min. Höhe %dft \n - Max. Höhe %dft \n - Im Parameter: %s",
        FLIGHT_PARAMS_METRIC            = "Flugparameter (Luftabwurf):\n - Min. Höhe %dm \n - Max. Höhe %dm \n - Im Parameter: %s",
        -- ============================================================
        -- Report-Titel
        -- ============================================================
        REPORT_CRATES_FOUND             = "Kisten in der Nähe:",
        REPORT_REMOVING_CRATES          = "Entferne Kisten in der Nähe:",
        REPORT_TRANSPORT_CHECKOUT       = "Transport-Checkliste",
        REPORT_INVENTORY                = "Inventarliste",
        REPORT_BUILD_CHECKLIST          = "Checkliste baubare Kisten",
        REPORT_REPAIR_CHECKLIST         = "Checkliste Reparaturen",
        REPORT_BEACONS                  = "Aktive Zonenfeuer",
        -- ============================================================
        -- Report-Sektionskopfzeilen
        -- ============================================================
        REPORT_SECTION_TROOPS           = "        -- TRUPPEN --",
        REPORT_SECTION_CRATES           = "       -- KISTEN --",
        REPORT_SECTION_CRATES_GC        = "       -- KISTEN via Bodenpersonal geladen --",
        REPORT_SECTION_NONE             = "        K E I N E",
        REPORT_SECTION_NONE_ALT         = "     --- Keine gefunden! ---",
        REPORT_SECTION_NONE_REPAIR      = "     --- Keine gefunden ---",
        REPORT_GC_LOADABLE_HINT         = "Wahrscheinlich durch Bodenpersonal ladbar (F8)",
        REPORT_TOTAL_MASS               = "Gesamtgewicht: %s kg. Ladbar: %s kg.",
        REPORT_TROOPS_CRATES_COUNT      = "Truppen: %d(%d), Kisten: %d(%d)",
        REPORT_TROOPS_CRATETYPES_COUNT  = "Truppen: %d, Kistentypen: %d",
        -- ============================================================
        -- Report-Zeilenvorlagen
        -- ============================================================
        REPORT_ROW_TROOP                = "Truppe: %s Größe %d",
        REPORT_ROW_CRATE                = "Kiste: %s %d/%d",
        REPORT_ROW_CRATE_SIZE1          = "Kiste: %s Größe 1",
        REPORT_ROW_GC_CRATE             = "Bodenpersonal-Kiste: %s Größe 1",
        REPORT_ROW_DROPPED_CRATE        = "Abgeworfene Kiste für %s, %dkg",
        REPORT_ROW_CRATE_KG             = "Kiste für %s, %dkg",
        REPORT_ROW_CRATE_REMOVED        = "Kiste für %s, %dkg entfernt",
        REPORT_ROW_UNIT_STOCK           = "Einheit: %s | Soldaten: %d | Bestand: %s",
        REPORT_ROW_TYPE_CRATE_STOCK     = "Typ: %s | Kisten pro Set: %d | Bestand: %s",
        REPORT_ROW_TYPE_STOCK           = "Typ: %s | Bestand: %s",
        REPORT_ROW_BUILD_CHECK          = "Typ: %s | Benötigt: %d | Gefunden: %d | Baubar: %s",
        REPORT_ROW_REPAIR_CHECK         = "Typ: %s | Benötigt: %d | Gefunden: %d | Reparierbar: %s",
        REPORT_ROW_BEACON               = " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
        -- ============================================================
        -- Gewichts- / Kistenlimit-Token
        -- ============================================================
        WEIGHT_LIMIT                    = "Gewichtslimit erreicht",
        CRATE_LIMIT                     = "Kistenlimit erreicht",
        -- ============================================================
        -- Menübezeichnungen - Obere Ebene
        -- ============================================================
        MENU_CTLD                       = "CTLD",
        MENU_MANAGE_TROOPS              = "Truppen verwalten",
        MENU_MANAGE_CRATES              = "Kisten verwalten",
        MENU_MANAGE_UNITS               = "Einheiten verwalten",
        -- ============================================================
        -- Menübezeichnungen - Truppen
        -- ============================================================
        MENU_LOAD_TROOPS                = "Truppen einladen",
        MENU_DROP_TROOPS                = "Truppen absetzen",
        MENU_DROP_ALL_TROOPS            = "ALLE Truppen absetzen",
        MENU_EXTRACT_TROOPS             = "Truppen aufnehmen",
        MENU_DROP_N_TROOPS              = "(%d) %s absetzen",
        -- ============================================================
        -- Menübezeichnungen - Kisten: Holen
        -- ============================================================
        MENU_GET_CRATES                 = "Kisten holen",
        MENU_GET                        = "Holen",
        MENU_GET_AND_LOAD               = "Holen und laden",
        MENU_GET_ANYWAY                 = "Trotzdem holen",
        MENU_PARTIALLY_LOAD             = "Teilweise laden",
        MENU_OUT_OF_STOCK               = "Nicht vorrätig",
        MENU_TROOP_LIMIT                = "Truppenlimit erreicht",
        -- ============================================================
        -- Menübezeichnungen - Kisten: Laden
        -- ============================================================
        MENU_LOAD_CRATES                = "Kisten laden",
        MENU_LOAD_ALL                   = "ALLE laden",
        MENU_SHOW_LOADABLE_CRATES       = "Ladbare Kisten anzeigen",
        MENU_NO_CRATES_FOUND_RESCAN     = "Keine Kisten gefunden! Neu scannen?",
        MENU_USE_C130_LOAD              = "C-130-Ladesystem verwenden",
        MENU_LOAD_SINGLE                = "Lade",
        -- ============================================================
        -- Menübezeichnungen - Kisten: Abwerfen
        -- ============================================================
        MENU_DROP_CRATES                = "Kisten abwerfen",
        MENU_DROP_ALL_CRATES            = "ALLE Kisten abwerfen",
        MENU_DROP                       = "Abwerfen",
        MENU_DROP_AND_BUILD             = "Abwerfen und bauen",
        MENU_DROP_N_SETS                = "%d Set%s abwerfen",
        MENU_NO_CRATES_TO_DROP          = "Keine Kisten zum Abwerfen!",
        -- ============================================================
        -- Menübezeichnungen - Kisten: Bauen / Reparieren / Packen / Entfernen
        -- ============================================================
        MENU_BUILD_CRATES               = "Kisten bauen",
        MENU_REPAIR                     = "Reparieren",
        MENU_PACK_CRATES                = "Kisten packen",
        MENU_PACK                       = "Packen",
        MENU_SCAN_PACKABLE_UNITS        = "Packbare Einheiten in der Nähe scannen",
        MENU_NO_PACKABLE_UNITS_FOUND_RESCAN = "Keine packbaren Einheiten gefunden! Neu scannen?",
        MENU_PACK_ALL                   = "In der Nähe packen",
        MENU_PACK_AND_LOAD              = "Packen und laden",
        MENU_PACK_AND_LOAD_ALL          = "In der Nähe packen und laden",
        MENU_PACK_AND_REMOVE            = "Packen und entfernen",
        MENU_PACK_AND_REMOVE_ALL        = "In der Nähe packen und entfernen",
        MENU_REMOVE_CRATES              = "Kisten entfernen",
        MENU_REMOVE_CRATES_NEARBY       = "Nahe Kisten entfernen",
        MENU_LIST_CRATES_NEARBY         = "Nahe Kisten auflisten",
        MENU_CRATES_NEEDED              = "%d Kiste%s %s (%dkg)",
        -- ============================================================
        -- Menübezeichnungen - Einheiten (C-130)
        -- ============================================================
        MENU_GET_UNITS                  = "Einheiten holen",
        MENU_REMOVE_UNITS_NEARBY        = "Nahe Einheiten entfernen",
        -- ============================================================
        -- Menübezeichnungen - Info / Fracht
        -- ============================================================
        MENU_LIST_BOARDED_CARGO         = "Geladene Fracht anzeigen",
        MENU_INVENTORY                  = "Inventar",
        MENU_LIST_ZONE_BEACONS          = "Aktive Zonenfeuer anzeigen",
        -- ============================================================
        -- Menübezeichnungen - Rauch / Leuchtfeuer / Baken
        -- ============================================================
        MENU_SMOKES_FLARES_BEACONS      = "Rauch, Leuchtfeuer, Baken",
        MENU_SMOKE_ZONES_NEARBY         = "Nahe Zonen einrauchen",
        MENU_DROP_SMOKE_NOW             = "Rauch jetzt setzen",
        MENU_RED_SMOKE                  = "Roter Rauch",
        MENU_BLUE_SMOKE                 = "Blauer Rauch",
        MENU_GREEN_SMOKE                = "Grüner Rauch",
        MENU_ORANGE_SMOKE               = "Oranger Rauch",
        MENU_WHITE_SMOKE                = "Weißer Rauch",
        MENU_FLARE_ZONES_NEARBY         = "Nahe Zonen befeuern",
        MENU_FIRE_FLARE_NOW             = "Leuchtfeuer jetzt abfeuern",
        MENU_DROP_BEACON_NOW            = "Bake jetzt setzen",
        -- ============================================================
        -- Menübezeichnungen - Parameter
        -- ============================================================
        MENU_SHOW_FLIGHT_PARAMS         = "Flugparameter anzeigen",
        MENU_SHOW_HOVER_PARAMS          = "Schwebeparameter anzeigen",
        STOCK_NONE                      = "keiner",
        STOCK_UNLIMITED                 = "unbegrenzt",
        BUILD_YES                       = "JA",
        BUILD_NO                        = "NEIN",
},
FR = {
        --- ============================================================
        -- Chargement caisse / fret
        -- ============================================================
        CRATE_LOADED_GROUNDCREW         = "Caisse(s) %s chargée(s) par l'équipe au sol !",
        CRATE_UNLOADED_GROUNDCREW       = "Caisse(s) %s déchargée(s) par l'équipe au sol !",
        CRATE_LOADED_ID                 = "Caisse(s) ID %d pour %s chargée(s) !",
        LOADED_FULL                     = "%d %s chargé(s).",
        LOADED_SETS_LEFTOVER            = "%d %s chargé(s), %d caisse(s) restante(s).",
        LOADED_SETS                     = "%d %s chargé(s).",
        LOADED_PARTIAL                  = "Seulement %d/%d caisse(s) de %s chargée(s).",
        LOADED_PARTIAL_LIMIT            = "Seulement %d/%d caisse(s) de %s chargée(s). Limite de fret atteinte !",
        LOADED_BATCH                    = "%d %s chargé(s).",
        LOADED_BATCH_PARTIAL            = "Certains ensembles n'ont pas pu être complètement chargés.",
        -- ============================================================
        -- Largage / Déchargement
        -- ============================================================
        DROPPED_FULL                    = "%d %s largué(s).",
        DROPPED_SETS_LEFTOVER           = "%d %s largué(s), %d caisse(s) restante(s).",
        DROPPED_SETS                    = "%d %s largué(s).",
        DROPPED_PARTIAL                 = "%d/%d caisse(s) de %s larguée(s).",
        DROPPED_INTO_ACTION             = "%s engagé(s) en action !",
        DROPPED_BEACON                  = "%s largué | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
        CRATES_POSITIONED               = "%d caisses pour %s positionnées près de vous !",
        CRATES_DROPPED                  = "%d caisses pour %s larguées !",
        -- ============================================================
        -- Troupes
        -- ============================================================
        BOARDED                         = "%s embarqué(s) !",
        BOARDING                        = "%s en cours d'embarquement !",
        TROOPS_RETURNED                 = "Les troupes sont retournées à la base !",
        -- ============================================================
        -- Déploiement
        -- ============================================================
        DEPLOYED_NEAR_YOU               = "%s déployé(s) près de vous !",
        UNITS_REMOVED                   = "%s supprimé(s)",
        -- ============================================================
        -- Construction / Réparation
        -- ============================================================
        BUILD_STARTED                   = "Construction démarrée, prête dans %d secondes !",
        REPAIR_STARTED                  = "Réparation démarrée avec %s, durée %d sec.",
        NO_UNIT_TO_REPAIR               = "Aucune unité(s) assez proche pour être réparée(s) !",
        CANT_REPAIR_WITH                = "Impossible de réparer cette unité avec %s",
        CRATES_MOVE_BEFORE_BUILD        = "*** Les caisses doivent être déplacées avant la construction !",
        -- ============================================================
        -- Erreurs - Hélicoptère / Poids / Capacité
        -- ============================================================
        CHOPPER_CANNOT_CARRY            = "Cet hélicoptère ne peut pas transporter de caisses !",
        TOO_HEAVY                       = "Désolé, c'est trop lourd à charger !",
        FULLY_LOADED                    = "Désolé, capacité maximale atteinte !",
        CRAMMED                         = "Désolé, nous sommes déjà au complet !",
        NO_CAPACITY_NOW                 = "Aucune capacité de chargement disponible pour le moment !",
        NO_MORE_CAPACITY                = "Plus de capacité pour charger des caisses !",
        CANNOT_LOAD_NONE_OR_FULL        = "Chargement impossible : aucune caisse trouvée ou capacité épuisée.",
        -- ============================================================
        -- Erreurs - Position
        -- ============================================================
        NEED_TO_LAND_OR_HOVER_LOAD      = "Vous devez atterrir ou rester en vol stationnaire pour charger !",
        HOVER_OVER_CRATES               = "Survolez les caisses en stationnaire pour les récupérer !",
        LAND_OR_HOVER_OVER_CRATES       = "Atterrissez ou survolez les caisses en stationnaire pour les récupérer !",
        MUST_LAND_OR_HOVER_CRATES       = "Vous devez atterrir ou rester en stationnaire pour charger les caisses !",
        NEED_TO_LAND_BUILD              = "Vous devez atterrir / vous arrêter pour construire quelque chose, Pilote !",
        NOT_CLOSE_ENOUGH_LOGISTICS      = "Vous n'êtes pas assez proche d'une zone logistique !",
        NOT_CLOSE_ENOUGH_DROP           = "Vous n'êtes pas assez proche d'une zone de largage !",
        NOT_CLOSE_ENOUGH_ZONE_NM        = "Négatif, vous devez être à moins de %d nm d'une zone !",
        CANNOT_BUILD_LOADING_AREA       = "Vous ne pouvez pas construire dans une zone de chargement, Pilote !",
        -- ============================================================
        -- Erreurs - Portes
        -- ============================================================
        OPEN_DOORS_LOAD_CARGO           = "Vous devez ouvrir la/les porte(s) pour charger du fret !",
        OPEN_DOORS_LOAD_TROOPS          = "Vous devez ouvrir la/les porte(s) pour embarquer des troupes !",
        OPEN_DOORS_EXTRACT_TROOPS       = "Vous devez ouvrir la/les porte(s) pour extraire des troupes !",
        OPEN_DOORS_UNLOAD_TROOPS        = "Vous devez ouvrir la/les porte(s) pour débarquer des troupes !",
        OPEN_DOORS_DROP_CARGO           = "Vous devez ouvrir la/les porte(s) pour larguer du fret !",
        -- ============================================================
        -- Erreurs - Stock / Disponibilité
        -- ============================================================
        ALL_GONE                        = "Désolé, tous les %s sont épuisés !",
        RAN_OUT_OF                      = "Désolé, nous n'avons plus de %s !",
        CARGO_NOT_AVAILABLE_ZONE        = "Le fret demandé n'est pas disponible dans cette zone !",
        ENOUGH_CRATES_NEARBY            = "Il y a déjà suffisamment de caisses à proximité ! Occupez-vous d'abord de celles-ci !",
        NO_CRATES_WITHIN                = "Aucune caisse (chargeable) dans un rayon de %d mètres !",
        NO_CRATES_WITHIN_PLAIN          = "Aucune caisse dans un rayon de %d mètres !",
        NO_CRATES_IN_RANGE              = "Aucune caisse trouvée à portée !",
        NO_NAMED_CRATES_IN_RANGE        = "Aucune caisse \"%s\" trouvée à portée !",
        NO_LOADABLE_CRATES              = "Désolé, aucune caisse chargeable à proximité ou poids maximum atteint !",
        NO_UNITS_TO_EXTRACT             = "Aucune unité assez proche pour être extraite !",
        NO_UNIT_CONFIG                  = "Aucune configuration d'unité trouvée pour %s",
        CANT_ONBOARD                    = "Impossible d'embarquer %s",
        TOO_MANY_UNITS_NEARBY           = "Vous avez déjà %d unités à proximité !",
        NO_CRATE_GROUPS                 = "Aucun groupe de caisses trouvé pour cette unité !",
        NO_CRATE_SET                    = "Aucun ensemble de caisses trouvé ou index invalide !",
        NO_CRATE_IN_SET                 = "Aucune caisse trouvée dans cet ensemble !",
        NO_TROOP_CHUNK                  = "Aucun bloc de fret de troupes trouvé pour l'ID %d !",
        TROOP_CHUNK_EMPTY               = "Le bloc de fret de troupes pour l'ID %d est vide !",
        -- ============================================================
        -- Rien de chargé / en stock
        -- ============================================================
        NOTHING_LOADED                  = "Rien de chargé !\nLimite de troupes : %d | Limite de caisses : %d | Limite en poids : %d kg",
        NOTHING_LOADED_AIRDROP          = "Rien de chargé ou paramètres de largage non respectés !",
        NOTHING_LOADED_HOVER            = "Rien de chargé ou paramètres de vol stationnaire non respectés !",
        NOTHING_IN_STOCK                = "Rien en stock !",
        NOTHING_TO_PACK                 = "Rien à charger à cette distance, Pilote !",
        NOTHING_TO_REMOVE               = "Rien à retirer à cette distance, Pilote !",
        -- ============================================================
        -- Zone / Info
        -- ============================================================
        ROGER_ZONE                      = "Compris, zone %s %s !",
        -- ============================================================
        -- Rapport : Paramètres stationnaire / vol
        -- ============================================================
        HOVER_PARAMS_METRIC             = "Paramètres stationnaires (autochargement/largage) :\n - Hauteur min. %dm \n - Hauteur max. %dm \n - Vitesse max. 2m/s \n - Dans les paramètres : %s",
        HOVER_PARAMS_IMPERIAL           = "Paramètres stationnaires (autochargement/largage) :\n - Hauteur min. %dft \n - Hauteur max. %dft \n - Vitesse max. 6ft/s \n - Dans les paramètres : %s",
        FLIGHT_PARAMS_IMPERIAL          = "Paramètres de vol (largage aérien) :\n - Hauteur min. %dft \n - Hauteur max. %dft \n - Dans les paramètres : %s",
        FLIGHT_PARAMS_METRIC            = "Paramètres de vol (largage aérien) :\n - Hauteur min. %dm \n - Hauteur max. %dm \n - Dans les paramètres : %s",
        -- ============================================================
        -- Titres de rapport
        -- ============================================================
        REPORT_CRATES_FOUND             = "Caisses trouvées à proximité :",
        REPORT_REMOVING_CRATES          = "Suppression des caisses à proximité :",
        REPORT_TRANSPORT_CHECKOUT       = "Fiche de contrôle transport",
        REPORT_INVENTORY                = "Fiche d'inventaire",
        REPORT_BUILD_CHECKLIST          = "Checklist caisses constructibles",
        REPORT_REPAIR_CHECKLIST         = "Checklist réparations",
        REPORT_BEACONS                  = "Balises de zone actives",
        -- ============================================================
        -- En-têtes de sections de rapport
        -- ============================================================
        REPORT_SECTION_TROOPS           = "        -- TROUPES --",
        REPORT_SECTION_CRATES           = "       -- CAISSES --",
        REPORT_SECTION_CRATES_GC        = "       -- CAISSES chargées via équipe au sol --",
        REPORT_SECTION_NONE             = "        A U C U N",
        REPORT_SECTION_NONE_ALT         = "     --- Aucun trouvé ! ---",
        REPORT_SECTION_NONE_REPAIR      = "     --- Aucun trouvé ---",
        REPORT_GC_LOADABLE_HINT         = "Probablement chargeable via l’équipe au sol (F8)",
        REPORT_TOTAL_MASS               = "Masse totale : %s kg. Chargeable : %s kg.",
        REPORT_TROOPS_CRATES_COUNT      = "Troupes : %d(%d), Caisses : %d(%d)",
        REPORT_TROOPS_CRATETYPES_COUNT  = "Troupes : %d, Types de caisses : %d",
        -- ============================================================
        -- Modèles de lignes de rapport
        -- ============================================================
        REPORT_ROW_TROOP                = "Troupe : %s taille %d",
        REPORT_ROW_CRATE                = "Caisse : %s %d/%d",
        REPORT_ROW_CRATE_SIZE1          = "Caisse : %s taille 1",
        REPORT_ROW_GC_CRATE             = "Caisses chargées par l'équipe au sol : %s taille 1",
        REPORT_ROW_DROPPED_CRATE        = "Caisses larguées pour %s, %dkg",
        REPORT_ROW_CRATE_KG             = "Caisses pour %s, %dkg",
        REPORT_ROW_CRATE_REMOVED        = "Caisses pour %s, %dkg retirées",
        REPORT_ROW_UNIT_STOCK           = "Unités : %s | Soldats : %d | Stock : %s",
        REPORT_ROW_TYPE_CRATE_STOCK     = "Type : %s | Caisses par ensemble : %d | Stock : %s",
        REPORT_ROW_TYPE_STOCK           = "Type : %s | Stock : %s",
        REPORT_ROW_BUILD_CHECK          = "Type : %s | Requis : %d | Trouvé : %d | Constructible : %s",
        REPORT_ROW_REPAIR_CHECK         = "Type : %s | Requis : %d | Trouvé : %d | Réparable : %s",
        REPORT_ROW_BEACON               = " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
        -- ============================================================
        -- Tokens limite poids / caisses
        -- ============================================================
        WEIGHT_LIMIT                    = "Limite de poids atteinte",
        CRATE_LIMIT                     = "Limite de caisses atteinte",
        -- ============================================================
        -- Libellés de menu - Niveau supérieur
        -- ============================================================
        MENU_CTLD                       = "CTLD",
        MENU_MANAGE_TROOPS              = "Gérer les troupes",
        MENU_MANAGE_CRATES              = "Gérer les caisses",
        MENU_MANAGE_UNITS               = "Gérer les unités",
        -- ============================================================
        -- Libellés de menu - Troupes
        -- ============================================================
        MENU_LOAD_TROOPS                = "Embarquer troupes",
        MENU_DROP_TROOPS                = "Déposer troupes",
        MENU_DROP_ALL_TROOPS            = "Déposer TOUTES les troupes",
        MENU_EXTRACT_TROOPS             = "Extraire troupes",
        MENU_DROP_N_TROOPS              = "Déposer (%d) %s",
        -- ============================================================
        -- Libellés de menu - Caisses : Récupérer
        -- ============================================================
        MENU_GET_CRATES                 = "Récupérer caisses",
        MENU_GET                        = "Récupérer",
        MENU_GET_AND_LOAD               = "Récupérer et charger",
        MENU_GET_ANYWAY                 = "Récupérer quand même",
        MENU_PARTIALLY_LOAD             = "Chargement partiel",
        MENU_OUT_OF_STOCK               = "Rupture de stock",
        MENU_TROOP_LIMIT                = "Limite de troupes atteinte",
        -- ============================================================
        -- Libellés de menu - Caisses : Charger
        -- ============================================================
        MENU_LOAD_CRATES                = "Charger caisses",
        MENU_LOAD_ALL                   = "Tout charger",
        MENU_SHOW_LOADABLE_CRATES       = "Afficher caisses chargeables",
        MENU_NO_CRATES_FOUND_RESCAN     = "Aucune caisse trouvée ! Rescanner ?",
        MENU_USE_C130_LOAD              = "Utiliser le système de chargement C-130",
        MENU_LOAD_SINGLE                = "Charger",
        -- ============================================================
        -- Libellés de menu - Caisses : Larguer
        -- ============================================================
        MENU_DROP_CRATES                = "Larguer caisses",
        MENU_DROP_ALL_CRATES            = "Larguer TOUTES les caisses",
        MENU_DROP                       = "Larguer",
        MENU_DROP_AND_BUILD             = "Larguer et construire",
        MENU_DROP_N_SETS                = "Larguer %d ensemble%s",
        MENU_NO_CRATES_TO_DROP          = "Aucune caisse à larguer !",
        -- ============================================================
        -- Libellés de menu - Caisses : Construire / Réparer / Emballer / Retirer
        -- ============================================================
        MENU_BUILD_CRATES               = "Construire caisses",
        MENU_REPAIR                     = "Réparer",
        MENU_PACK_CRATES                = "Emballer caisses",
        MENU_PACK                       = "Emballer",
        MENU_SCAN_PACKABLE_UNITS        = "Scanner unités emballables à proximité",
        MENU_NO_PACKABLE_UNITS_FOUND_RESCAN = "Aucune unité emballable trouvée ! Rescanner ?",
        MENU_PACK_ALL                   = "Emballer à proximité",
        MENU_PACK_AND_LOAD              = "Emballer et charger",
        MENU_PACK_AND_LOAD_ALL          = "Emballer et charger à proximité",
        MENU_PACK_AND_REMOVE            = "Emballer et retirer",
        MENU_PACK_AND_REMOVE_ALL        = "Emballer et retirer à proximité",
        MENU_REMOVE_CRATES              = "Retirer caisses",
        MENU_REMOVE_CRATES_NEARBY       = "Retirer caisses proches",
        MENU_LIST_CRATES_NEARBY         = "Lister caisses proches",
        MENU_CRATES_NEEDED              = "%d caisse%s %s (%dkg)",
        -- ============================================================
        -- Libellés de menu - Unités (C-130)
        -- ============================================================
        MENU_GET_UNITS                  = "Récupérer unités",
        MENU_REMOVE_UNITS_NEARBY        = "Retirer les unités proches",
        -- ============================================================
        -- Libellés de menu - Info / Fret
        -- ============================================================
        MENU_LIST_BOARDED_CARGO         = "Lister le fret embarqué",
        MENU_INVENTORY                  = "Inventaire",
        MENU_LIST_ZONE_BEACONS          = "Lister les balises de zones actives",
        -- ============================================================
        -- Libellés de menu - Fumigènes / Fusées / Balises
        -- ============================================================
        MENU_SMOKES_FLARES_BEACONS      = "Fumigènes, Fusées, Balises",
        MENU_SMOKE_ZONES_NEARBY         = "Fumigène sur les zones proches",
        MENU_DROP_SMOKE_NOW             = "Poser fumigène maintenant",
        MENU_RED_SMOKE                  = "Fumigène rouge",
        MENU_BLUE_SMOKE                 = "Fumigène bleu",
        MENU_GREEN_SMOKE                = "Fumigène vert",
        MENU_ORANGE_SMOKE               = "Fumigène orange",
        MENU_WHITE_SMOKE                = "Fumigène blanc",
        MENU_FLARE_ZONES_NEARBY         = "Baliser zones proches",
        MENU_FIRE_FLARE_NOW             = "Tirer une fusée maintenant",
        MENU_DROP_BEACON_NOW            = "Poser une balise maintenant",
        -- ============================================================
        -- Libellés de menu - Paramètres
        -- ============================================================
        MENU_SHOW_FLIGHT_PARAMS         = "Afficher paramètres de vol",
        MENU_SHOW_HOVER_PARAMS          = "Afficher les paramètres stationnaire",
        STOCK_NONE                      = "aucun",
        STOCK_UNLIMITED                 = "illimité",
        BUILD_YES                       = "OUI",
        BUILD_NO                        = "NON",
    },
    ES={
      CRATE_LOADED_GROUNDCREW="Contenedor %s cargado por el quipo de tierra.",
      CRATE_UNLOADED_GROUNDCREW="Contenedor %s descargado por el quipo de tierra.",
      CRATE_LOADED_ID="Contenedor ID %d para %s cargado.",
      LOADED_FULL="Cargado %d %s.",
      LOADED_SETS_LEFTOVER="Cargado %d %s(s), de %d contenedor(es) restante(s).",
      LOADED_SETS="Cargado %d %s(s).",
      LOADED_PARTIAL="Cargado sólo %d/%d contenedor(es) de %s.",
      LOADED_PARTIAL_LIMIT="Cargado sólo %d/%d contenedor(es) de %s. Límite de carga alcanzado.",
      LOADED_BATCH="Cargado %d %s.",
      LOADED_BATCH_PARTIAL="Some sets could not be fully loaded.",
      DROPPED_FULL="Entregado %d %s.",
      DROPPED_SETS_LEFTOVER="Entregado %d %s(s), de %d conenedor(es) restante(s).",
      DROPPED_SETS="Entregado %d %s(s).",
      DROPPED_PARTIAL="Entregado %d/%d contenedor(es) de %s.",
      DROPPED_INTO_ACTION="¡Soltados %s en acción!",
      DROPPED_BEACON="Entregado %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
      CRATES_POSITIONED="%d contenedores para %s servidos cerca de ti.",
      CRATES_DROPPED="%d contenedores para %s han sido entregados.",
      BOARDED="¡%s a bordo!",
      BOARDING="¡%s entrando!",
      TROOPS_RETURNED="¡Las tropas han vuelto a la base!",
      DEPLOYED_NEAR_YOU="%s han sido servidas cerca de tí.",
      UNITS_REMOVED="%s ha sido eliminado",
      BUILD_STARTED="Construcción comenzada, listo en %d segundos.",
      REPAIR_STARTED="Reparación comenzaza usando %s, tardará %d segundos.",
      NO_UNIT_TO_REPAIR="No hay unidades cercanas que necesiten reparación.",
      CANT_REPAIR_WITH="No se puede reparar esta unidad con %s",
      CRATES_MOVE_BEFORE_BUILD="*** Los contenedores deben ser movidos antes de construirse.",
      CHOPPER_CANNOT_CARRY="Lo siento, este helicóptero no puede transportar contenedores.",
      TOO_HEAVY="Lo siento, esta carga es muy pesada.",
      FULLY_LOADED="Lo siento, vamos hasta arriba.",
      CRAMMED="Lo siento, vamos a tope.",
      NO_CAPACITY_NOW="No queda capacidad para cargar más.",
      NO_MORE_CAPACITY="No queda capacidad para cargar más contenedores.",
      CANNOT_LOAD_NONE_OR_FULL="No se pueden cargar contenedores: n hay o no queda capacidad.",
      NEED_TO_LAND_OR_HOVER_LOAD="Necesitas aterrizar o mantenerte en estacionario para cargar.",
      HOVER_OVER_CRATES="Mantente en estacionario sobre el contenedor para recogerlo.",
      LAND_OR_HOVER_OVER_CRATES="Aterriza o mantente en estacionario para recoger el contenedor.",
      MUST_LAND_OR_HOVER_CRATES="Necesitas aterrizar o mantenerte en eestacionario para cargar contenedores.",
      NEED_TO_LAND_BUILD="Necesitas aterrizar/parar para construir algo.",
      NOT_CLOSE_ENOUGH_LOGISTICS="No estás cerca de una zona de logística.",
      NOT_CLOSE_ENOUGH_DROP="No estás en una zona de entrega.",
      NOT_CLOSE_ENOUGH_ZONE_NM="Negativo, tienes que estar a menos de %dnm de la zona.",
      CANNOT_BUILD_LOADING_AREA="No puedes construir en la zona de carga.",
      OPEN_DOORS_LOAD_CARGO="Necesitas abrir las puertas para cargar.",
      OPEN_DOORS_LOAD_TROOPS="Necesitas abrir las puertas para que embarquen las tropas.",
      OPEN_DOORS_EXTRACT_TROOPS="Necesitas abrir las puertas para poder sacar a las tropas de aquí.",
      OPEN_DOORS_UNLOAD_TROOPS="Necesitas abrir las puertas para que desembarquen las tropas.",
      OPEN_DOORS_DROP_CARGO="Necesitas abrir las puertas para descargar la carga.",
      ALL_GONE="Lo siento, todos %s se han servido.",
      RAN_OUT_OF="Lo siento, nos hemos quedad sin %s",
      CARGO_NOT_AVAILABLE_ZONE="La carga solicitada no está disponible en esta zona.",
      ENOUGH_CRATES_NEARBY="Hay contenedores cerca de ti listas. Encárgate primero de ellos.",
      NO_CRATES_WITHIN="No ha contenedores (cargables) en %d metros.",
      NO_CRATES_WITHIN_PLAIN="No hay contenedores en %d metros.",
      NO_CRATES_IN_RANGE="No se han encontrado contenedores en rango.",
      NO_NAMED_CRATES_IN_RANGE="No se han encontrado \"%s\" conenedores en rango.",
      NO_LOADABLE_CRATES="Lo siento, no hay contenedores cercanos o se ha alcanzado el peso máximo.",
      NO_UNITS_TO_EXTRACT="No hay unidades cercanas para extracción.",
      NO_UNIT_CONFIG="No se ha encontrado configuración de unidad para %s",
      CANT_ONBOARD="No puede subir %s",
      TOO_MANY_UNITS_NEARBY="Ya tienes %d unidades próximas.",
      NO_CRATE_GROUPS="No se han encontrado grupos de contendeores para esta unidad.",
      NO_CRATE_SET="No se ha encontrado contenedor o su index es inválido.",
      NO_CRATE_IN_SET="No se ha encontrado contenedor para este set.",
      NO_TROOP_CHUNK="No se han encontrado tropas para el id %d!",
      TROOP_CHUNK_EMPTY="Troop chunk is empty for ID %d!",
      NOTHING_LOADED="Nada cargado.\nLímite tropas: %d | Límite contenedores: %d | Peso límite: %d kg.",
      NOTHING_LOADED_AIRDROP="Nada cargado o no estás en parámetros de lanzamiento aéreo.",
      NOTHING_LOADED_HOVER="Nada cargado o no estás en parámetros de estacionario.",
      NOTHING_IN_STOCK="¡Nada en stock!",
      NOTHING_TO_PACK="Nada para empaquetar a esta distancia.",
      NOTHING_TO_REMOVE="Nada para eliminar a esta distancia.",
      ROGER_ZONE="Recibido, %s cona %s!",
      HOVER_PARAMS_METRIC="Parámetros en estacionario (autocarga/suelta):\n - Altura mínima %dm \n - Altura máxima %dm \n - Velocidad máxima 2mps \n - En parámetros: %s",
      HOVER_PARAMS_IMPERIAL="Parámetros en estacionario (autocarga/suelta):\n - Altura mínima  %dft \n - Altura máxima %dft \n - Velocidad máxima 6ftps \n - En parámetros: %s",
      FLIGHT_PARAMS_IMPERIAL="Parámetros vuelo (lanzamiento aéreo):\n - Altura mínima  %dft \n - Altura máxima %dft \n - En parámetros: %s",
      FLIGHT_PARAMS_METRIC="Parámetros vuelo (lanzamiento aéreo):\n - Altura mínima  %dm \n - Altura máxima %dm \n - En parámetros: %s",
      REPORT_CRATES_FOUND="Contenedores encontrados cerca:",
      REPORT_REMOVING_CRATES="Contenedores eliminados cerca:",
      REPORT_TRANSPORT_CHECKOUT="Informe de transporte",
      REPORT_INVENTORY="Inventario",
      REPORT_BUILD_CHECKLIST="Contenedores construibles",
      REPORT_REPAIR_CHECKLIST="Reparaciones",
      REPORT_BEACONS="Active Zone Beacons",
      REPORT_SECTION_TROOPS="        -- TROPAS --",
      REPORT_SECTION_CRATES="    -- CONTENEDORES --",
      REPORT_SECTION_CRATES_GC="       -- Contenedores cargados por equipo de tierra --",
      REPORT_SECTION_NONE="        N A D A",
      REPORT_SECTION_NONE_ALT="     --- Nada encontrado ---",
      REPORT_SECTION_NONE_REPAIR="     --- Nada encontrado ---",
      REPORT_GC_LOADABLE_HINT="Probablemente cargable por el equipo de tierra (F8)",
      REPORT_TOTAL_MASS="Peso total: %s kg. Cargable: %s kg.",
      REPORT_TROOPS_CRATES_COUNT="Tropas: %d(%d), Contenedores: %d(%d)",
      REPORT_TROOPS_CRATETYPES_COUNT="Tropas: %d, Tipos contenedores: %d",
      REPORT_ROW_TROOP="Tropas: %s tamaño %d",
      REPORT_ROW_CRATE="Contenedores: %s %d/%d",
      REPORT_ROW_CRATE_SIZE1="Contenedores: %s tamaño 1",
      REPORT_ROW_GC_CRATE="Contenedores cargados: %s tamaño 1",
      REPORT_ROW_DROPPED_CRATE="Entregado contenedor para %s, %dkg",
      REPORT_ROW_CRATE_KG="Contenedor para %s, %dkg",
      REPORT_ROW_CRATE_REMOVED="Contenedor para %s, %dkg eliminado",
      REPORT_ROW_UNIT_STOCK="Unidad: %s | Soldados: %d | Stock: %s",
      REPORT_ROW_TYPE_CRATE_STOCK="Tipo: %s | Contenedores por set: %d | Stock: %s",
      REPORT_ROW_TYPE_STOCK="Tipo: %s | Stock: %s",
      REPORT_ROW_BUILD_CHECK="Tipo: %s | Rquiere %d | Encontrados %d | Construible %s",
      REPORT_ROW_REPAIR_CHECK="Tipo: %s | Requiere %d | Encontrados %d | Reparable %s",
      REPORT_ROW_BEACON=" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
      WEIGHT_LIMIT="Alcanzado límite de pesoWeight limit reached",
      CRATE_LIMIT="Alcanzado límite contenedores",
      MENU_CTLD="CTLD",
      MENU_MANAGE_TROOPS="Gestionar tropas",
      MENU_MANAGE_CRATES="Gestionar contenedores",
      MENU_MANAGE_UNITS="Gestionar unidades",
      MENU_LOAD_TROOPS="Cargar tropas",
      MENU_DROP_TROOPS="Entregar tropas",
      MENU_DROP_ALL_TROOPS="Entregar TODAS tropas",
      MENU_EXTRACT_TROOPS="Extraer tropas",
      MENU_DROP_N_TROOPS="Soltar (%d) %s",
      MENU_GET_CRATES="Solicitar contenedores",
      MENU_GET="Solicitar",
      MENU_GET_AND_LOAD="Solicitar y cargar",
      MENU_GET_ANYWAY="Solicitar de todas formas",
      MENU_PARTIALLY_LOAD="Carga parcial",
      MENU_OUT_OF_STOCK="Sin stock",
      MENU_TROOP_LIMIT="Limite de tropas alcanzado",
      MENU_LOAD_CRATES="Cargar contenedores",
      MENU_LOAD_ALL="Cargar TODO",
      MENU_SHOW_LOADABLE_CRATES="Mostrar contenedores carbables",
      MENU_NO_CRATES_FOUND_RESCAN="Contenedores no encontrados, ¿buscar?",
      MENU_USE_C130_LOAD="Usar sistmea de carga del C-130",
      MENU_LOAD_SINGLE="Cargar",
      MENU_DROP_CRATES="Soltar cargas",
      MENU_DROP_ALL_CRATES="Soltar TODAS cargas",
      MENU_DROP="Soltar",
      MENU_DROP_AND_BUILD="Soltar y constuir",
      MENU_DROP_N_SETS="Soltar %d Set %s",
      MENU_NO_CRATES_TO_DROP="No hay cargas para soltar",
      MENU_BUILD_CRATES="Construir contenedores",
      MENU_REPAIR="Reparar",
      MENU_PACK_CRATES="Empaquetar cargas",
      MENU_PACK="Empaquetar",
      MENU_SCAN_PACKABLE_UNITS="Buscar unidades empaquetables cercanas",
      MENU_NO_PACKABLE_UNITS_FOUND_RESCAN="No se encontraron unidades empaquetables. ¿Buscar de nuevo?",
      MENU_PACK_ALL="Empaquetar cercanas",
      MENU_PACK_AND_LOAD="Empaquetar y cargar",
      MENU_PACK_AND_LOAD_ALL="Empaquetar y cargar cercanas",
      MENU_PACK_AND_REMOVE="Empaquetar y eliminar",
      MENU_PACK_AND_REMOVE_ALL="Empaquetar y eliminar cercanas",
      MENU_REMOVE_CRATES="Eliminar cargas",
      MENU_REMOVE_CRATES_NEARBY="Eliminar cargas cercanas",
      MENU_LIST_CRATES_NEARBY="Listar cargas cercanas",
      MENU_CRATES_NEEDED="%d contenedor%s %s (%dkg)",
      MENU_GET_UNITS="Obtener unidades",
      MENU_REMOVE_UNITS_NEARBY="Eliminar unidades cercanas",
      MENU_LIST_BOARDED_CARGO="Lista de cargas a bordo",
      MENU_INVENTORY="Inventario",
      MENU_LIST_ZONE_BEACONS="Lista de balizas activas",
      MENU_SMOKES_FLARES_BEACONS="Humos, Bengalas, Balizas",
      MENU_SMOKE_ZONES_NEARBY="Humo en zonas cercanas",
      MENU_DROP_SMOKE_NOW="Lanzar humo ahora",
      MENU_RED_SMOKE="Humo rojo",
      MENU_BLUE_SMOKE="Humo azul",
      MENU_GREEN_SMOKE="Humo verde",
      MENU_ORANGE_SMOKE="Humo naranja",
      MENU_WHITE_SMOKE="Humo blanco",
      MENU_FLARE_ZONES_NEARBY="Bengalas en zonas cercanas",
      MENU_FIRE_FLARE_NOW="Disparar bengala ahora",
      MENU_DROP_BEACON_NOW="Soltar baliza ahora",
      MENU_SHOW_FLIGHT_PARAMS="Mostrar parámetros de vuelo",
      MENU_SHOW_HOVER_PARAMS="Mostrar parámetros estacionario",
      STOCK_NONE="Nada",
      STOCK_UNLIMITED="ilimitado",
      BUILD_YES="SI",
      BUILD_NO="NO",
      },
  }
2026-04-15 20:08:45 +02:00
leka1986 0b2f07ca6c Update CTLD.lua
- **Changed** the pack menu layout so pack actions now open into dedicated `Pack`, `Pack and Load`, and `Pack and Remove` submenus.
- **Added** scanning for nearby packable units within each pack submenu.
- **Added** direct selection of individual nearby packable units from the pack menus.
- **Changed** the pack menu order so detected nearby units appear first, followed by the nearby bulk action and the scan action.
- **Added** nearby bulk actions in each pack submenu (`Pack nearby`, `Pack and Load nearby`, `Pack and Remove nearby`).
- **Changed** pack menu refresh behavior so nearby packable units are rebuilt after major logistics actions and after build completion.
- **Fixed** selected `Pack and Load` so it only loads the crates created by the chosen packed unit.
- **Fixed** selected `Pack and Remove` so it only removes the crates created by the chosen packed unit.
- **Changed** pre-pack event handling so before-pack callbacks receive the specific group being packed.
- **Added** localization for the new pack menu wording in English, German, French, and Spanish.
2026-04-15 20:06:57 +02:00
Thomas 7c5d5543e0 Merge pull request #2576 from FlightControl-Master/master-ng
Update TARS.lua
2026-04-15 10:29:15 +02:00
Thomas 3d10716b1f Update TARS.lua
#TARS Documentation addition
2026-04-15 10:28:39 +02:00
Thomas c48a7d4400 Merge pull request #2575 from FlightControl-Master/master-ng
Merge
2026-04-14 06:29:09 +02:00
Thomas 79130d4fe2 Merge pull request #2574 from Rolln-dev/Rolln/AIRBOSS
[BUGFIX] AIRBOSS:_LSOGrades
2026-04-14 06:28:13 +02:00
Rolln-dev aebb6ac0db [BUGFIX] AIRBOSS:_LSOGrades When points are deducted for a 1-wire pass, we also need to change the grade according to the new points. 2026-04-13 19:20:20 -06:00
Applevangelist 3eb910b47e Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-12 13:13:21 +02:00
Applevangelist 6223b8e1a7 #TARS - docu fixes 2026-04-12 13:12:58 +02:00
Applevangelist 533a4dfa6c Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-12 12:58:33 +02:00
Applevangelist dbc752253b #TARS Initial Release 2026-04-12 12:56:15 +02:00
Applevangelist 7f074781f4 xx 2026-04-12 12:51:13 +02:00
Applevangelist ab77fcbc1f Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-08 15:18:20 +02:00
Applevangelist 56520092c3 #EASYGCICAP - Fix restart function to restart, not start, the Airwings, and call Status itself afterwards 2026-04-08 15:17:54 +02:00
Applevangelist 252a1b0761 Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-08 14:44:20 +02:00
Applevangelist 51e5f53965 #LEGION Fix for MinDistance when nil and spawning on a ship 2026-04-08 14:43:53 +02:00
Applevangelist b0976d1613 Merge remote-tracking branch 'origin/develop' into develop 2026-04-07 12:22:27 +02:00
Applevangelist a737ca71e6 Merge remote-tracking branch 'origin/master-ng' into develop 2026-04-07 12:22:24 +02:00
Applevangelist 1071300a7f #MSRS - Added HOUND/OpenA combo 2026-04-07 12:19:37 +02:00
Thomas 2490958090 Merge pull request #2573 from FlightControl-Master/master-ng
#INTEL add rcs info to contact data
2026-04-06 19:07:23 +02:00
Applevangelist b78b76e579 #INTEL add rcs info to contact data 2026-04-06 18:49:53 +02:00
Thomas efff22fc18 Merge pull request #2571 from FlightControl-Master/master-ng
Merge
2026-03-31 19:35:34 +02:00
Thomas 4b27e92210 Merge pull request #2569 from FlightControl-Master/master-ng
Merge
2026-03-30 08:38:19 +02:00
Applevangelist f2063f41f6 Merge remote-tracking branch 'origin/master-ng' into develop 2026-03-27 17:42:30 +01:00
Thomas 12241e931a Merge pull request #2567 from FlightControl-Master/master-ng
merge
2026-03-27 15:18:15 +01:00
Thomas 4cb6debef8 Merge pull request #2565 from FlightControl-Master/master-ng
Merge from master
2026-03-26 06:45:10 +01:00
Thomas efe4bf3912 Merge pull request #2563 from FlightControl-Master/master-ng
Merge pull request #2562 from FlightControl-Master/develop
2026-03-24 13:32:26 +01:00
27 changed files with 6235 additions and 516 deletions
+1 -1
View File
@@ -855,7 +855,7 @@ end
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event.
function BASE:CreateEventTakeoff( EventTime, Initiator )
self:F( { EventTime, Initiator } )
--self:F( { EventTime, Initiator } )
local Event = {
id = world.event.S_EVENT_TAKEOFF,
+8 -7
View File
@@ -1380,12 +1380,12 @@ function DATABASE:_RegisterDynamicGroup(Groupname)
local DCSUnitName = DCSUnit:getName()
-- Add unit.
self:I(string.format("Register Unit: %s", tostring(DCSUnitName)))
self:T(string.format("Register Unit: %s", tostring(DCSUnitName)))
self:AddUnit( tostring(DCSUnitName), true )
end
else
self:E({"Group does not exist: ", DCSGroup})
self:T({"Group does not exist: ", DCSGroup})
end
return self
end
@@ -1488,27 +1488,28 @@ end
-- @return #DATABASE self
function DATABASE:_RegisterAirbase(airbase)
local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false
local countHSyria = 0
--local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false
--local countHSyria = 0
if airbase then
-- Get the airbase name.
local DCSAirbaseName = airbase:getName()
-- DCS 2.9.8.1107 added 143 helipads all named H with the same object ID ..
--[[ DCS 2.9.8.1107 added 143 helipads all named H with the same object ID ..
if IsSyria and DCSAirbaseName == "H" and countHSyria > 0 then
--[[
--
local p = airbase:getPosition().p
local mgrs = COORDINATE:New(p.x,p.z,p.y):ToStringMGRS()
self:I("Airbase on Syria map named H @ "..mgrs)
countHSyria = countHSyria + 1
if countHSyria > 1 then return self end
--]]
--
return self
elseif IsSyria and DCSAirbaseName == "H" and countHSyria == 0 then
countHSyria = countHSyria + 1
end
--]]
-- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now.
local airbaseID=airbase:getID()
+6 -1
View File
@@ -1453,7 +1453,12 @@ function EVENT:onEvent( Event )
-- Weapon.
if Event.weapon and type(Event.weapon) == "table" and Event.weapon.isExist and Event.weapon:isExist() then
Event.Weapon = Event.weapon
Event.WeaponName = Event.weapon:isExist() and Event.weapon:getTypeName() or "Unknown Weapon"
if Event.weapon_name == "ZELL Booster" then
Event.WeaponName = "ZELL Booster"
else
Event.WeaponName = Event.weapon:isExist() and Event.weapon.getTypeName and Event.weapon:getTypeName() or "Unknown Weapon"
end
if Event.weapon_name == "ZELL Booster" then Event.WeaponName = "ZELL Booster" end
Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit!
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName()
--Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
+14 -3
View File
@@ -869,10 +869,21 @@ do
local MenuTable = {}
for MenuText, Menu in pairs( self.Menus or {} ) do
local tag = Menu.MenuTag or math.random(1,10000)
MenuTable[#MenuTable+1] = {Tag=tag, Enty=Menu}
MenuTable[#MenuTable+1] = {Tag=tag, Entry=Menu}
end
table.sort(MenuTable, function (k1, k2) return k1.tag < k2.tag end )
for _, Menu in pairs( MenuTable ) do
local function SortTable(k1,k2)
if not k1 then
if not k2 then return true else return false end
elseif not k2 then
if not k1 then return true else return false end
else
return (k1.Tag or 15) <= (k2.Tag or 15)
end
return false
end
table.sort(MenuTable, SortTable)
--table.sort(MenuTable, function (k1, k2) return (k1.tag or 15) <= (k2.tag or 15) end )
for _, Menu in ipairs( MenuTable ) do
Menu.Entry:Refresh()
end
end
@@ -375,3 +375,13 @@ function SCHEDULEDISPATCHER:NoTrace( Scheduler )
Scheduler.ShowTrace = false
end
--- Helper for memory cleanup for self stopping schedulers
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
-- @param #string CallID (Optional) Scheduler Call ID.
function SCHEDULEDISPATCHER:_Reclaim( Scheduler, CallID )
self:Stop( Scheduler, CallID ) -- remove DCS timer, nil ScheduleID
if self.Schedule[Scheduler] then self.Schedule[Scheduler][CallID] = nil end
self.ObjectSchedulers[CallID] = nil
self.PersistentSchedulers[CallID] = nil
end
+145 -22
View File
@@ -62,7 +62,8 @@ do -- SET_BASE
-- @field #table List Unused table.
-- @field Core.Scheduler#SCHEDULER CallScheduler
-- @field #SET_BASE.Filters Filter Filters
-- @field #booleaen filterNoRegex If true, FilterPrefix ignores special characters and evaluates plain string.
-- @field #boolean filterNoRegex If true, FilterPrefix ignores special characters and evaluates plain string. Defaults to false.
-- @field #boolean filterReplaceDash If true then if filterNoRegex is false, replace dashes with regex pattern friendly pattern. Defaults to true
-- @extends Core.Base#BASE
--- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects.
@@ -102,6 +103,7 @@ do -- SET_BASE
},
},
filterNoRegex=false,
filterReplaceDash=true,
}
--- Filters
@@ -256,7 +258,8 @@ do -- SET_BASE
-- @return #boolean Returns `true`, if the pattern is contained in the name and false otherwise.
function SET_BASE:_SearchPattern(Name, Pattern, NoRegex, ReplaceDash)
NoRegex=NoRegex or self.filterNoRegex
if ReplaceDash==true then
ReplaceDash=ReplaceDash or self.filterReplaceDash
if ReplaceDash==true and NoRegex ~= true then
-- Not sure why "-" is replaced by "%-" ?! - So we can still match group names with a dash in them
-- reason is that the string is interpreted as a pattern and "-" is a special character then. For interpreting it as a string, fourth parameter needs to be set to true.
Pattern=Pattern:gsub("-", "%%-")
@@ -265,6 +268,16 @@ do -- SET_BASE
return contain
end
---Set Regex Options for FilterPrefix function.
-- @param #SET_BASE self
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
-- @return #SET_BASE self
function SET_BASE:FilterSetRegex(NoRegex,ReplaceDash)
if NoRegex ~= nil then self.filterNoRegex = NoRegex end
self.filterReplaceDash = ReplaceDash or true
return self
end
--- Gets the Set.
-- @param #SET_BASE self
@@ -1198,7 +1211,21 @@ do
-- @param #SET_GROUP self
-- @return #SET_GROUP self
---Set Regex Options for FilterPrefix function.
-- @function [parent=#SET_GROUP] FilterSetRegex
-- @param #SET_GROUP self
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
-- @return #SET_GROUP self
--- Builds a set of objects of same coalitions.
-- Possible current coalitions are red, blue and neutral.
-- @function [parent=#SET_GROUP] FilterCoalitions
-- @param #SET_GROUP self
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" and coalition.side.RED, coalition.side.BLUE,coalition.side.NEUTRAL
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_GROUP self
end
--- Get a *new* set table that only contains alive groups.
@@ -2116,7 +2143,7 @@ do
if self.Filter.GroupPrefixes and MGroupInclude then
local MGroupPrefix = false
for GroupPrefixId, GroupPrefix in pairs(self.Filter.GroupPrefixes) do
if self:_SearchPattern(MGroup:GetName(), GroupPrefix, false, true) then
if self:_SearchPattern(MGroup:GetName(), GroupPrefix, self.filterNoRegex, self.filterReplaceDash) then
MGroupPrefix = true
end
end
@@ -2337,6 +2364,26 @@ do -- SET_UNIT
-- @param #SET_UNIT self
-- @return #SET_UNIT self
--- Filter the set once
-- @function [parent=#SET_UNIT] FilterOnce
-- @param #SET_UNIT self
-- @return #SET_UNIT self
---Set Regex Options for FilterPrefix function.
-- @function [parent=#SET_UNIT] FilterSetRegex
-- @param #SET_UNIT self
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
-- @return #SET_UNIT self
--- Builds a set of objects of same coalitions.
-- Possible current coalitions are red, blue and neutral.
-- @function [parent=#SET_UNIT] FilterCoalitions
-- @param #SET_UNIT self
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" and coalition.side.RED, coalition.side.BLUE,coalition.side.NEUTRAL
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_UNIT self
return self
end
@@ -3348,7 +3395,7 @@ do -- SET_UNIT
if self.Filter.UnitPrefixes and MUnitInclude then
local MUnitPrefix = false
for UnitPrefixId, UnitPrefix in pairs(self.Filter.UnitPrefixes) do
if self:_SearchPattern(MUnit:GetName(), UnitPrefix, false, true) then
if self:_SearchPattern(MUnit:GetName(), UnitPrefix, self.filterNoRegex, self.filterReplaceDash) then
MUnitPrefix = true
end
end
@@ -3544,7 +3591,27 @@ do -- SET_STATIC
-- Inherits from BASE
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.STATICS)) -- Core.Set#SET_STATIC
--- Filter the set once
-- @function [parent=#SET_STATIC] FilterOnce
-- @param #SET_STATIC self
-- @return #SET_STATIC self
---Set Regex Options for FilterPrefix function.
-- @function [parent=#SET_STATIC] FilterSetRegex
-- @param #SET_STATIC self
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
-- @return #SET_STATIC self
--- Builds a set of objects of same coalitions.
-- Possible current coalitions are red, blue and neutral.
-- @function [parent=#SET_STATIC] FilterCoalitions
-- @param #SET_STATIC self
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" and coalition.side.RED, coalition.side.BLUE,coalition.side.NEUTRAL
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_STATIC self
return self
end
@@ -4119,7 +4186,7 @@ do -- SET_STATIC
if self.Filter.StaticPrefixes then
local MStaticPrefix = false
for StaticPrefixId, StaticPrefix in pairs(self.Filter.StaticPrefixes) do
if self:_SearchPattern(MStatic:GetName(), StaticPrefix, false, true) then
if self:_SearchPattern(MStatic:GetName(), StaticPrefix, self.filterNoRegex, self.filterReplaceDash) then
MStaticPrefix = true
end
end
@@ -4303,7 +4370,27 @@ do -- SET_CLIENT
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.CLIENTS)) -- #SET_CLIENT
self:FilterActive(false)
--- Filter the set once
-- @function [parent=#SET_CLIENT] FilterOnce
-- @param #SET_CLIENT self
-- @return #SET_CLIENT self
---Set Regex Options for FilterPrefix function.
-- @function [parent=#SET_CLIENT] FilterSetRegex
-- @param #SET_CLIENT self
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
-- @return #SET_CLIENT self
--- Builds a set of objects of same coalitions.
-- Possible current coalitions are red, blue and neutral.
-- @function [parent=#SET_CLIENT] FilterCoalitions
-- @param #SET_CLIENT self
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" and coalition.side.RED, coalition.side.BLUE,coalition.side.NEUTRAL
-- @param #boolean Clear If `true`, clear any previously defined filters.
-- @return #SET_CLIENT self
return self
end
@@ -4902,7 +4989,7 @@ do -- SET_CLIENT
if self.Filter.ClientPrefixes and MClientInclude then
local MClientPrefix = false
for ClientPrefixId, ClientPrefix in pairs(self.Filter.ClientPrefixes) do
if self:_SearchPattern(MClient.UnitName, ClientPrefix) then
if self:_SearchPattern(MClient.UnitName, ClientPrefix, self.filterNoRegex, self.filterReplaceDash) then
MClientPrefix = true
end
end
@@ -4926,7 +5013,7 @@ do -- SET_CLIENT
local playername = MClient:GetPlayerName() or "Unknown"
--self:T(playername)
for _,_Playername in pairs(self.Filter.Playernames) do
if playername and self:_SearchPattern(playername,_Playername) then
if playername and self:_SearchPattern(playername,_Playername,self.filterNoRegex, self.filterReplaceDash) then
MClientPlayername = true
end
end
@@ -4939,7 +5026,7 @@ do -- SET_CLIENT
local callsign = MClient:GetCallsign()
--self:I(callsign)
for _,_Callsign in pairs(self.Filter.Callsigns) do
if callsign and self:_SearchPattern(callsign,_Callsign, true) then
if callsign and self:_SearchPattern(callsign,_Callsign, self.filterNoRegex, self.filterReplaceDash) then
MClientCallsigns = true
end
end
@@ -5360,7 +5447,7 @@ do -- SET_PLAYER
if self.Filter.ClientPrefixes then
local MClientPrefix = false
for ClientPrefixId, ClientPrefix in pairs(self.Filter.ClientPrefixes) do
if self:_SearchPattern(MClient.UnitName,ClientPrefix) then
if self:_SearchPattern(MClient.UnitName,ClientPrefix,self.filterNoRegex, self.filterReplaceDash) then
MClientPrefix = true
end
end
@@ -5830,7 +5917,19 @@ do -- SET_ZONE
function SET_ZONE:New()
-- Inherits from BASE
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.ZONES))
--- Filter the set once
-- @function [parent=#SET_ZONE] FilterOnce
-- @param #SET_ZONE self
-- @return #SET_ZONE self
---Set Regex Options for FilterPrefix function.
-- @function [parent=#SET_ZONE] FilterSetRegex
-- @param #SET_ZONE self
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
-- @return #SET_ZONE self
return self
end
@@ -6068,7 +6167,7 @@ do -- SET_ZONE
if self.Filter.Prefixes then
local MZonePrefix = false
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do
if self:_SearchPattern(MZoneName, ZonePrefix, false, true) then
if self:_SearchPattern(MZoneName, ZonePrefix, self.filterNoRegex, self.filterReplaceDash) then
MZonePrefix = true
end
end
@@ -6577,7 +6676,7 @@ do -- SET_ZONE_GOAL
if self.Filter.Prefixes then
local MZonePrefix = false
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do
if self:_SearchPattern(MZoneName, ZonePrefix, false, true) then
if self:_SearchPattern(MZoneName, ZonePrefix, self.filterNoRegex, self.filterReplaceDash) then
MZonePrefix = true
end
end
@@ -6713,7 +6812,19 @@ do -- SET_OPSZONE
-- Inherits from BASE
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.OPSZONES))
--- Filter the set once
-- @function [parent=#SET_OPSZONE] FilterOnce
-- @param #SET_OPSZONE self
-- @return #SET_OPSZONE self
---Set Regex Options for FilterPrefix function.
-- @function [parent=#SET_OPSZONE] FilterSetRegex
-- @param #SET_OPSZONE self
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
-- @return #SET_OPSZONE self
return self
end
@@ -6938,7 +7049,7 @@ do -- SET_OPSZONE
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do
-- Prefix
if self:_SearchPattern(MZoneName, ZonePrefix, false, true) then
if self:_SearchPattern(MZoneName, ZonePrefix, self.filterNoRegex, self.filterReplaceDash) then
MZonePrefix = true
break --Break the loop as we found the prefix.
end
@@ -7732,7 +7843,7 @@ function SET_OPSGROUP:_EventOnBirth(Event)
local MGroupPrefix = false
for GroupPrefixId, GroupPrefix in pairs(self.Filter.GroupPrefixes) do
if self:_SearchPattern(MGroup:GetName(), GroupPrefix, false, true) then
if self:_SearchPattern(MGroup:GetName(), GroupPrefix, self.filterNoRegex, self.filterReplaceDash) then
MGroupPrefix = true
end
end
@@ -8066,7 +8177,7 @@ do -- SET_SCENERY
if self.Filter.Prefixes then
local MSceneryPrefix = false
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do
if self:_SearchPattern(MSceneryName, ZonePrefix, false, true) then
if self:_SearchPattern(MSceneryName, ZonePrefix, self.filterNoRegex, self.filterReplaceDash) then
MSceneryPrefix = true
end
end
@@ -8289,7 +8400,19 @@ do -- SET_DYNAMICCARGO
--- Inherits from BASE
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.DYNAMICCARGO)) -- Core.Set#SET_DYNAMICCARGO
--- Filter the set once
-- @function [parent=#SET_DYNAMICCARGO] FilterOnce
-- @param #SET_DYNAMICCARGO self
-- @return #SET_DYNAMICCARGO self
---Set Regex Options for FilterPrefix function.
-- @function [parent=#SET_DYNAMICCARGO] FilterSetRegex
-- @param #SET_DYNAMICCARGO self
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
-- @return #SET_DYNAMICCARGO self
return self
end
@@ -8337,7 +8460,7 @@ do -- SET_DYNAMICCARGO
if self.Filter.StaticPrefixes then
local DCargoPrefix = false
for StaticPrefixId, StaticPrefix in pairs(self.Filter.StaticPrefixes) do
if self:_SearchPattern(DCargo:GetName(), StaticPrefix, false, true) then
if self:_SearchPattern(DCargo:GetName(), StaticPrefix, self.filterNoRegex, self.filterReplaceDash) then
DCargoPrefix = true
end
end
@@ -8505,7 +8628,7 @@ do -- SET_DYNAMICCARGO
function SET_DYNAMICCARGO:FilterCurrentOwner(PlayerName)
self:FilterFunction(
function(cargo)
if cargo and cargo.Owner and self:_SearchPattern(cargo.Owner, PlayerName, true) then
if cargo and cargo.Owner and self:_SearchPattern(cargo.Owner, PlayerName, self.filterNoRegex, self.filterReplaceDash) then
return true
else
return false
+96 -25
View File
@@ -1184,6 +1184,7 @@ end
--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
-- @param #SPAWN self
-- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If nil or empty, the method returns self without effect.
-- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Core.Zone}s objects.
-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone.
-- @return #SPAWN self
@@ -1201,6 +1202,9 @@ end
function SPAWN:InitRandomizeZones( SpawnZoneTable, RandomizePositionInZone )
--self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } )
if not SpawnZoneTable then
return self
end
local temptable = {}
for _,_temp in pairs(SpawnZoneTable) do
temptable[#temptable+1] = _temp
@@ -2149,6 +2153,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
local parkingspots = {}
local parkingindex = {}
local spots
local useexplicitspots = false
-- Spawn happens on ground, i.e. at an airbase, a FARP or a ship.
if spawnonground and not SpawnTemplate.parked then
@@ -2178,15 +2183,41 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
local scanscenery = false
local verysafe = false
-- Use exact parking data when provided, otherwise let helicopters on ships/FARPs
-- use the smarter parking search before falling back to the procedural queue path.
-- Number of free parking spots at the airbase.
if autoparking then
if Parkingdata~=nil then
-- Parking data explicitly set by user as input parameter.
nfree = #Parkingdata
spots = Parkingdata
useexplicitspots = true
elseif autoparking and AirbaseCategory == Airbase.Category.HELIPAD and ishelo then
if termtype == nil then
-- Helo is spawned. Try exclusive helo spots first.
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
nfree = #spots
if nfree < nunits then
-- Not enough helo ports. Let's try also other terminal types.
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
nfree = #spots
end
else
-- Terminal type explicitly given.
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
nfree = #spots
end
if nfree >= nunits then
useexplicitspots = true
else
-- These places work procedural and have some kind of build in queue ==> Less effort.
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
end
elseif autoparking then
-- These places work procedural and have some kind of build in queue ==> Less effort.
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
elseif Parkingdata~=nil then
-- Parking data explicitly set by user as input parameter. (This was commented out for some unknown reason. But I need it this way.)
nfree=#Parkingdata
spots=Parkingdata
else
if ishelo then
if termtype == nil then
@@ -2238,7 +2269,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
local _notenough = false
-- Need to differentiate some cases again.
if autoparking then
if autoparking and not useexplicitspots then
-- On free spot required in these cases.
if nfree >= 1 then
@@ -2271,6 +2302,10 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
end
end
if useexplicitspots and parkingspots[1] then
PointVec3 = parkingspots[1]
end
-- Not enough spots ==> Prepare airstart.
if _notenough then
@@ -2296,9 +2331,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
Takeoff = GROUP.Takeoff.Air
spawnonground = false
else
else
if not Takeoff == GROUP.Takeoff.Runway then
self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
return nil
else
Takeoff = GROUP.Takeoff.Runway
spawnonground = false
self:E( string.format( "WARNING: Group %s set to runway spawning at %s, this only works in Single Player!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
end
end
end
@@ -2339,7 +2380,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
if spawnonground then
-- Ships and FARPS seem to have a build in queue.
if autoparking then
if autoparking and not useexplicitspots then
-- Spawn on ship. We take only the position of the ship.
SpawnTemplate.units[UnitID].x = PointVec3.x -- TX
@@ -2357,7 +2398,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
else
-- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set.
-- Spawn in air as requested initially. Original template orientation is preserved, altitude is already correctly set.
SpawnTemplate.units[UnitID].x = TX
SpawnTemplate.units[UnitID].y = TY
SpawnTemplate.units[UnitID].alt = PointVec3.y
@@ -2390,7 +2431,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
if Takeoff == GROUP.Takeoff.Air then
for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do
--SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) --No need to create a new SCHEDULER instance every time!
self:ScheduleOnce(5, BASE.CreateEventTakeoff, {GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject()})
self:ScheduleOnce(5, BASE.CreateEventTakeoff, GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject())
end
end
@@ -2582,21 +2623,51 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
local scanscenery = false
local verysafe = false
-- Number of free parking spots at the airbase.
if spawnonship or spawnonfarp or spawnonrunway then
-- These places work procedural and have some kind of build in queue ==> Less effort.
--self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
--[[
elseif Parkingdata~=nil then
-- Parking data explicitly set by user as input parameter.
nfree=#Parkingdata
spots=Parkingdata
]]
else
if ishelo then
-- Use exact parking data when provided, otherwise let helicopters on FARPs
-- use the smarter parking search before falling back to the procedural queue path.
local useexplicitspots = false
-- Number of free parking spots at the airbase.
if Parkingdata~=nil then
-- Parking data explicitly set by user as input parameter.
nfree = #Parkingdata
spots = Parkingdata
useexplicitspots = true
elseif spawnonfarp and ishelo then
if termtype == nil then
-- Helo is spawned. Try exclusive helo spots first.
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
nfree = #spots
if nfree < nunits then
-- Not enough helo ports. Let's try also other terminal types.
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
nfree = #spots
end
else
-- Terminal type explicitly given.
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
nfree = #spots
end
if nfree >= nunits then
useexplicitspots = true
else
-- These places work procedural and have some kind of build in queue ==> Less effort.
--self:T2( string.format( "Group %s is spawned on farp %s using procedural spots.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
end
elseif spawnonship or spawnonfarp or spawnonrunway then
-- These places work procedural and have some kind of build in queue ==> Less effort.
--self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
else
if ishelo then
if termtype == nil then
-- Helo is spawned. Try exclusive helo spots first.
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) )
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata )
@@ -2659,7 +2730,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
local _notenough = false
-- Need to differentiate some cases again.
if spawnonship or spawnonfarp or spawnonrunway then
if (spawnonship or spawnonfarp or spawnonrunway) and not useexplicitspots then
-- On free spot required in these cases.
if nfree >= 1 then
@@ -1848,7 +1848,7 @@ do -- DETECTION_BASE
return self
end
--- Validate if the detected item is locked.
--- Validate if the detected item is locked (not in the radar sense, though!).
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @return #boolean
@@ -1858,7 +1858,7 @@ do -- DETECTION_BASE
end
--- Lock a detected item.
--- Lock a detected item (not in the radar sense, though!).
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @return #DETECTION_BASE
@@ -1869,7 +1869,7 @@ do -- DETECTION_BASE
return self
end
--- Unlock a detected item.
--- Unlock a detected item (not in the radar sense, though!).
-- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @return #DETECTION_BASE
+862 -12
View File
@@ -116,7 +116,7 @@
-- * TOR M2
-- * C-RAM
-- * Silkworm (though strictly speaking this is a surface to ship missile)
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19, SA-21, S-300VM, S-300V4, S-400
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19, S-300VM, S-300V4, S-400 (SA-21)
-- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!)
-- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2
-- * Other Mods: Nike
@@ -135,8 +135,8 @@
-- * SA-3 (with V601P missile, e.g. "Red SAM SA-3 HDS")
-- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS" with 5P85CE launcher)
-- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS" with 5P85SE launcher)
-- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS 2" for the 9A82 variant and "Red SAM SA-12 HDS 1" for the 9A83 variant)
-- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS 2" for the 9A82ME variant and "Red SAM SA-23 HDS 1" for the 9A83ME variant)
-- * SA-12 (e.g. "Red SAM SA-12 HDS")
-- * SA-23 (e.g. "Red SAM SA-23 HDS")
-- * SAMP/T (launcher dependent range, e.g. "Blue SAM SAMPT Block 1 HDS" for Block 1, "Blue SAM SAMPT Block 1INT HDS", "Blue SAM SAMPT Block 2 HDS")
--
-- The other HDS types work like the rest of the known SAM systems.
@@ -404,10 +404,9 @@ MANTIS.SamData = {
["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 },
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1", ARMCapacity=16},
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2", ARMCapacity=18},
["SA-21"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E"},
["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E", ARMCapacity=4},
["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32ME", ARMCapacity=4}, -- SA-23 Gladiator/Giant
["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M-1E", ARMCapacity=4}, -- SA-23B
["S-400"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E", ARMCapacity=4}, -- SA-21 Growler
["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" },
["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" },
@@ -417,6 +416,7 @@ MANTIS.SamData = {
["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true", ARMCapacity=3 },
["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=4 },
["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM", ARMCapacity=12 }, -- 4 per starter, usually 3 starters in a battery
["SON-9"] = { Range=20, Blindspot=0, Height=14, Type="Point", Radar="SON_9", Point="true" }, -- Fire Can FCR for S-60/KS-19 AAA
}
--- SAM data HDS
@@ -435,10 +435,8 @@ MANTIS.SamDataHDS = {
["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln", ARMCapacity=8}, -- V55RUD
["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln", ARMCapacity=3}, -- V55RUD
["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 },
["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l", ARMCapacity=12},
["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l", ARMCapacity=12},
["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME", ARMCapacity=14 },
["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME", ARMCapacity=14 },
["SA-12 HDS"] = { Range=100, Blindspot=6, Height=30, Type="Long" , Radar="S-300V 9A82 l", ARMCapacity=12},
["SA-23 HDS"] = { Range=200, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A82ME", ARMCapacity=14 },
["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
["SAMPT Block 1 HDS"] = { Range=120, Blindspot=1, Height=20, Type="long", Radar="SAMPT_MLT_Blk1" }, -- Block 1 Launcher
["SAMPT Block 1INT HDS"] = { Range=150, Blindspot=1, Height=25, Type="long", Radar="SAMPT_MLT_Blk1NT" }, -- Block 1-INT Launcher
@@ -526,6 +524,187 @@ MANTIS.SamDataCH = {
["Lvkv9040M CHM"] = { Range=2, Blindspot=0.1, Height=1.2, Type="Point", Radar="LvKv9040",Point="true" },
}
-----------------------------------------------------------------------
-- MANTIS Jammer Extension v2.0.0
-- Standoff Jamming (SOJ) aircraft support for MANTIS IADS networks
-- Physics-based jamming: asymmetric Gaussian rise + exponential decay
-- Compatible with v7 jammer curves
-----------------------------------------------------------------------
--- Jammer Loadout Configurations (v8: Max 3 Pods)
-- @type MANTIS.JammerLoadouts
MANTIS.JammerLoadouts = {
["1xALQ99"]={name="1x AN/ALQ-99",description="Baseline single pod. Full 64MHz-20GHz spectrum.",mult_LOW=1.12,mult_S=1.10,mult_IJ=1.12,mult_OPT=1.00,bt_mod=1.00,range_mod=1.00,tier="ALQ99"},
["2xALQ99"]={name="2x AN/ALQ-99",description="Two pods. Improved ERP with log stacking.",mult_LOW=1.40,mult_S=1.36,mult_IJ=1.40,mult_OPT=1.02,bt_mod=1.00,range_mod=1.03,tier="ALQ99"},
["3xALQ99"]={name="3x AN/ALQ-99 (Maximum Legacy)",description="Max legacy barrage. Highest broadband ERP.",mult_LOW=1.58,mult_S=1.52,mult_IJ=1.58,mult_OPT=1.04,bt_mod=1.00,range_mod=1.06,tier="ALQ99"},
["1xALQ249"]={name="1x AN/ALQ-249 (AESA)",description="AESA 2-18GHz. High peak+wider window. Blind <2GHz.",mult_LOW=0.15,mult_S=1.82,mult_IJ=1.70,mult_OPT=1.00,bt_mod=1.28,range_mod=1.16,tier="ALQ249"},
["2xALQ249"]={name="2x AN/ALQ-249",description="Two AESA pods. Strong S/IJ dominance.",mult_LOW=0.15,mult_S=2.30,mult_IJ=2.15,mult_OPT=1.00,bt_mod=1.38,range_mod=1.26,tier="ALQ249"},
["3xALQ249"]={name="3x AN/ALQ-249 (Maximum AESA)",description="Max AESA ERP. Extreme S/IJ. No LOW coverage.",mult_LOW=0.15,mult_S=2.75,mult_IJ=2.58,mult_OPT=1.00,bt_mod=1.48,range_mod=1.35,tier="ALQ249"},
["1xALQ99_1xALQ249"]={name="1x ALQ-99 + 1x ALQ-249",description="Balanced coverage. AESA S/IJ + ALQ-99 LOW-band.",mult_LOW=1.14,mult_S=1.90,mult_IJ=1.80,mult_OPT=1.01,bt_mod=1.22,range_mod=1.12,tier="Mixed"},
["1xALQ99_2xALQ249"]={name="1x ALQ-99 + 2x ALQ-249 [Recommended]",description="Best all-around. High AESA ERP + LOW coverage.",mult_LOW=1.10,mult_S=2.38,mult_IJ=2.22,mult_OPT=1.01,bt_mod=1.35,range_mod=1.24,tier="Mixed"},
["2xALQ99_1xALQ249"]={name="2x ALQ-99 + 1x ALQ-249",description="Strong LOW-band + AESA S/IJ boost.",mult_LOW=1.42,mult_S=2.10,mult_IJ=2.04,mult_OPT=1.03,bt_mod=1.25,range_mod=1.15,tier="Mixed"},
}
--- Tiered loadout keys for F10 menu (v8: 3/3/3)
-- @type MANTIS.JammerLoadoutTiers
MANTIS.JammerLoadoutTiers = {
ALQ99 = {"1xALQ99","2xALQ99","3xALQ99"},
ALQ249 = {"1xALQ249","2xALQ249","3xALQ249"},
Mixed = {"1xALQ99_1xALQ249","1xALQ99_2xALQ249","2xALQ99_1xALQ249"},
}
--- Jitter configuration: ±10% per evaluation (EW Fundamentals Ch.9)
-- @type MANTIS.JammerJitterPercent
MANTIS.JammerJitterPercent = 0.10
--- Jammer SAM Parameters — v9 curves (v8.1) {peak%, mu_nm, sigma_L, tail_dist, band, floor%}
-- v9 changes from v8:
-- * Width rework: curves now track radar detection/track envelope (not just missile range)
-- * Long-range systems widened (SA-5, SA-21, SA-23, Nike, PAC-2, S-300/400 family)
-- * Short-range systems tightened (SA-19, SON-9, HQ-7, Rapier)
-- * HAWK widened to match CW illuminator envelope (~90nm)
-- floor: residual effectiveness inside burnthrough (now tapers past mu with R^2 falloff)
-- 5 = legacy radar, no ECCM | 3 = moderate ECCM | 2 = advanced ECCM/AESA | 0 = optical/IR
-- @type MANTIS.JammerSAMParams
MANTIS.JammerSAMParams = {
-- Standard SamData (canonical entries — aliases below point to these)
["Nike"] ={peak=78,mu=55,sigma_L=22,tail_dist=110,band="S", floor=5},
["Hawk"] ={peak=30,mu=22,sigma_L=9, tail_dist=55, band="IJ", floor=3},
["SA-2"] ={peak=75,mu=40,sigma_L=16,tail_dist=90, band="S", floor=5},
["SA-3"] ={peak=45,mu=22,sigma_L=10,tail_dist=52, band="IJ", floor=5},
["SA-5"] ={peak=52,mu=75,sigma_L=26,tail_dist=145,band="S", floor=5},
["SA-6"] ={peak=33,mu=18,sigma_L=8, tail_dist=48, band="IJ", floor=3},
["SA-8"] ={peak=38,mu=10,sigma_L=4, tail_dist=24, band="IJ", floor=3},
["SA-9"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["SA-10"] ={peak=32,mu=50,sigma_L=20,tail_dist=95, band="S", floor=3},
["SA-10B"] ={peak=30,mu=52,sigma_L=20,tail_dist=100,band="S", floor=3},
["SA-11"] ={peak=52,mu=28,sigma_L=12,tail_dist=58, band="IJ", floor=3},
["SA-13"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["SA-15"] ={peak=30,mu=11,sigma_L=5, tail_dist=22, band="IJ", floor=3},
["SA-17"] ={peak=24,mu=32,sigma_L=14,tail_dist=68, band="IJ", floor=2},
["SA-19"] ={peak=25,mu=8, sigma_L=3.5,tail_dist=16,band="IJ", floor=2},
["SA-20A"] ={peak=22,mu=65,sigma_L=24,tail_dist=115,band="S", floor=2},
["SA-20B"] ={peak=20,mu=68,sigma_L=24,tail_dist=120,band="S", floor=2},
["S-300VM"] ={peak=16,mu=80,sigma_L=30,tail_dist=135,band="S", floor=2}, -- SA-23 canonical
["S-300V4"] ={peak=14,mu=85,sigma_L=30,tail_dist=145,band="S", floor=2}, -- SA-23B canonical
["S-400"] ={peak=18,mu=80,sigma_L=30,tail_dist=150,band="S", floor=2}, -- SA-21 canonical
["NASAMS"] ={peak=25,mu=28,sigma_L=12,tail_dist=52, band="IJ", floor=2},
["Patriot"] ={peak=32,mu=62,sigma_L=24,tail_dist=110,band="S", floor=3},
["Rapier"] ={peak=12,mu=6, sigma_L=2.5,tail_dist=14,band="IJ", floor=0},
["Gepard"] ={peak=18,mu=6, sigma_L=2, tail_dist=18, band="IJ", floor=0},
["Roland"] ={peak=35,mu=5, sigma_L=2, tail_dist=12, band="IJ", floor=3},
["HQ-7"] ={peak=38,mu=8, sigma_L=3.5,tail_dist=18,band="IJ", floor=3},
["HQ-2"] ={peak=70,mu=38,sigma_L=15,tail_dist=80, band="S", floor=5},
["C-RAM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["Avenger"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["Chaparral"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["Linebacker"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["Silkworm"] ={peak=35,mu=20,sigma_L=8, tail_dist=40, band="IJ", floor=3},
["Dog Ear"] ={peak=40,mu=10,sigma_L=4, tail_dist=20, band="IJ", floor=5},
["Pantsir S1"] ={peak=10,mu=9, sigma_L=4, tail_dist=20, band="IJ", floor=0}, -- SA-22 canonical
["Tor M2"] ={peak=28,mu=14,sigma_L=6, tail_dist=30, band="IJ", floor=3},
["IRIS-T SLM"] ={peak=18,mu=22,sigma_L=10,tail_dist=50, band="IJ", floor=2},
["SON-9"] ={peak=48,mu=14,sigma_L=6, tail_dist=30, band="IJ", floor=5},
-- IDF Mod
["TAMIR IDFA"] ={peak=19,mu=25,sigma_L=12,tail_dist=52,band="S", floor=2},
["STUNNER IDFA"]={peak=16,mu=45,sigma_L=18,tail_dist=80,band="S", floor=2},
-- HDS Mod (canonical for keys with no base equivalent)
["SA-10C HDS"] ={peak=30,mu=50,sigma_L=20,tail_dist=92, band="S", floor=3},
["SA-12 HDS"] ={peak=35,mu=50,sigma_L=18,tail_dist=90, band="S", floor=3},
["SAMPT Block 1 HDS"] ={peak=28,mu=45,sigma_L=18,tail_dist=85, band="S", floor=3},
["SAMPT Block 1INT HDS"]={peak=26,mu=48,sigma_L=18,tail_dist=88,band="S", floor=3},
["SAMPT Block 2 HDS"] ={peak=22,mu=52,sigma_L=20,tail_dist=92, band="S", floor=2},
-- SMA Mod (canonical for SMA-only keys)
["RBS70 SMA"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0}, -- canonical for RBS70 family
["RBS90 SMA"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0}, -- canonical for RBS90 family
["RBS98M SMA"] ={peak=25,mu=12,sigma_L=5, tail_dist=25,band="IJ", floor=3}, -- canonical
["RBS103A SMA"] ={peak=20,mu=55,sigma_L=20,tail_dist=90,band="S", floor=2}, -- canonical
["RBS103B SMA"] ={peak=22,mu=45,sigma_L=18,tail_dist=80,band="S", floor=2}, -- canonical
["Lvkv9040M SMA"]={peak=15,mu=3, sigma_L=1, tail_dist=8, band="OPT",floor=0}, -- canonical
-- CH Mod (canonical for CHM-only keys)
["2S38 CHM"] ={peak=8, mu=3, sigma_L=1, tail_dist=8, band="OPT",floor=0},
["PGL-625 CHM"] ={peak=12,mu=6, sigma_L=2.5,tail_dist=14,band="OPT",floor=0}, -- Type-08
["HQ-17A CHM"] ={peak=30,mu=11,sigma_L=5, tail_dist=24, band="IJ", floor=3},
["M903PAC3 CHM"] ={peak=15,mu=58,sigma_L=22,tail_dist=105,band="S", floor=2},
["TorM2M CHM"] ={peak=26,mu=16,sigma_L=6, tail_dist=32, band="IJ", floor=3}, -- variant
["NASAMS3-AMRAAMER CHM"] ={peak=20,mu=35,sigma_L=14,tail_dist=65, band="IJ", floor=2},
["NASAMS3-AIM9X2 CHM"] ={peak=10,mu=15,sigma_L=6, tail_dist=30, band="IJ", floor=0},
["PGZ-09 CHM"] ={peak=22,mu=5, sigma_L=2, tail_dist=14, band="IJ", floor=0}, -- Type-09
["PGZ-95 CHM"] ={peak=15,mu=4, sigma_L=2, tail_dist=10, band="OPT",floor=0},
["S350-9M100 CHM"] ={peak=18,mu=35,sigma_L=15,tail_dist=78, band="IJ", floor=2}, -- SA-28 canonical
["HQ-22 CHM"] ={peak=20,mu=70,sigma_L=26,tail_dist=120,band="S", floor=2},
["IRIS-T SLM CHM"] ={peak=18,mu=22,sigma_L=10,tail_dist=50, band="IJ", floor=2}, -- will be aliased
["Skynex CHM"] ={peak=12,mu=4, sigma_L=2, tail_dist=10, band="OPT",floor=0},
["Skyshield CHM"] ={peak=12,mu=4, sigma_L=2, tail_dist=10, band="OPT",floor=0},
["BukM3-9M317M CHM"] ={peak=22,mu=38,sigma_L=16,tail_dist=75, band="IJ", floor=2}, -- SA-27 canonical
["SkySabre CHM"] ={peak=18,mu=18,sigma_L=7, tail_dist=40, band="IJ", floor=2},
["Stormer CHM"] ={peak=10,mu=5, sigma_L=2, tail_dist=12, band="OPT",floor=0},
["THAAD CHM"] ={peak=10,mu=90,sigma_L=38,tail_dist=140,band="IJ", floor=0},
["WieselOzelot CHM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["USInfantryFIM92K CHM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
}
-- Alias entries: point alias keys to the canonical entry's table.
-- Modifying any canonical entry above automatically updates all its aliases.
-- This prevents parameter drift when updating values.
do
local p = MANTIS.JammerSAMParams
-- NATO designation aliases
p["SA-21"] = p["S-400"] -- SA-21 Growler
p["SA-22"] = p["Pantsir S1"] -- SA-22 Greyhound
p["SA-23"] = p["S-300VM"] -- SA-23 Gladiator
p["SA-23B"] = p["S-300V4"] -- SA-23B Antey-2500 (improved)
p["SA-27"] = p["BukM3-9M317M CHM"] -- SA-27 Gollum (Buk-M3)
p["SA-28"] = p["S350-9M100 CHM"] -- SA-28 Vityaz (S-350)
-- Buk-M3 variants (same radar)
p["BukM3-9M317MA CHM"] = p["BukM3-9M317M CHM"]
-- S-350 variants (same radar)
p["S350-9M96D CHM"] = p["S350-9M100 CHM"]
-- Pantsir variants
p["PantsirS1 CHM"] = p["Pantsir S1"]
p["PantsirS2 CHM"] = p["Pantsir S1"]
-- Tor-M2 variants
p["TorM2 CHM"] = p["Tor M2"]
p["TorM2K CHM"] = p["Tor M2"]
-- Patriot PAC-2 (same MPQ-65 radar)
p["M903PAC2 CHM"] = p["Patriot"]
p["M903PAC2KAT1 CHM"] = p["Patriot"]
-- IRIS-T (base and CHM are same system)
p["IRIS-T SLM CHM"] = p["IRIS-T SLM"]
-- HDS variants (same radar as base entry)
p["SA-2 HDS"] = p["SA-2"]
p["SA-3 HDS"] = p["SA-3"]
p["SA-10B HDS"] = p["SA-10B"]
p["SA-17 HDS"] = p["SA-17"]
p["SA-23 HDS"] = p["S-300VM"]
p["HQ-2 HDS"] = p["HQ-2"]
-- Roland / FlaRakRad (same system)
p["FlaRakRad CHM"] = p["Roland"]
-- Phalanx-class CIWS (Ka-band, essentially unjammable)
p["C-RAM CHM"] = p["C-RAM"]
p["LD-3000 CHM"] = p["C-RAM"]
p["LD-3000M CHM"] = p["C-RAM"]
-- SMA / CHM Swedish duplicates (CH mod re-implements SMA assets)
p["RBS70 CHM"] = p["RBS70 SMA"]
p["RBS70M SMA"] = p["RBS70 SMA"]
p["RBS70M CHM"] = p["RBS70 SMA"]
p["RBS90 CHM"] = p["RBS90 SMA"]
p["RBS90M SMA"] = p["RBS90 SMA"]
p["RBS90M CHM"] = p["RBS90 SMA"]
p["RBS98M CHM"] = p["RBS98M SMA"]
p["RBS103A CHM"] = p["RBS103A SMA"]
p["RBS103AM SMA"] = p["RBS103A SMA"]
p["RBS103AM CHM"] = p["RBS103A SMA"]
p["RBS103B CHM"] = p["RBS103B SMA"]
p["RBS103BM SMA"] = p["RBS103B SMA"]
p["RBS103BM CHM"] = p["RBS103B SMA"]
p["Lvkv9040M CHM"]= p["Lvkv9040M SMA"]
-- IR MANPADS (all near-zero, different systems but share params)
p["LAV-AD CHM"] = p["SA-9"]
-- NOTE: SA-13, Avenger, Chaparral, Linebacker kept separate (different real systems)
end
-----------------------------------------------------------------------
-- MANTIS System
-----------------------------------------------------------------------
@@ -908,7 +1087,8 @@ do
local function SwitchSAMOn(Name,Group)
local suppressed = self.SuppressedGroups[Name] or false
if not suppressed and self.SamStateTracker[Name] == "GREEN" then
local jammed = self._jammerEnabled and self._jammedSAMs and self._jammedSAMs[Name] or false
if not suppressed and not jammed and self.SamStateTracker[Name] == "GREEN" then
self.SamStateTracker[Name] = "RED"
if self.UseEmOnOff then
-- DONE: add emissions on/off
@@ -2132,6 +2312,18 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea
self:T(string.format("MANTIS:SeadAllowSuppression REQUEST | target=%s | weapon=%s | tti=%s | delay=%s",tostring(targetName),
tostring(weaponName),tostring(tti),tostring(delay)))
----------------------------------------------------------------
-- JAMMER CHECK: if SAM is currently jammed, deny SEAD suppression.
-- The jammer is already keeping the SAM off. SEAD suppression would
-- cause SuppressionStop to briefly re-enable the radar and relocate
-- the SAM (potentially out of jammer range). Let the jammer handle it.
----------------------------------------------------------------
if self._jammerEnabled and self._jammedSAMs and self._jammedSAMs[targetName] then
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> DENIED (JAMMED %.0f%%) | target=%s",
self._jammedSAMs[targetName] * 100, tostring(targetName)))
return false
end
----------------------------------------------------------------
-- LOOK UP ARM CAPACITY FOR THIS SAM
----------------------------------------------------------------
@@ -2694,6 +2886,664 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea
return self
end
-----------------------------------------------------------------------
-- MANTIS Jammer Extension v2.0.0 - Functions
-----------------------------------------------------------------------
--- Add client jammer aircraft support. Call BEFORE :Start().
-- @param #MANTIS self
-- @param Core.Set#SET_GROUP clientSet SET_GROUP of client jammer aircraft, or a prefix string
-- @param #string defaultLoadout Default loadout key (default "1xALQ99_2xALQ249")
-- @return #MANTIS self
function MANTIS:AddJammer(clientSet, defaultLoadout)
self:T(self.lid .. "AddJammer")
self._jammerEnabled = true
-- Allow string prefix shorthand
if type(clientSet) == "string" then
clientSet = SET_GROUP:New():FilterPrefixes(clientSet):FilterActive(true):FilterStart()
end
self._jammerClientSet = clientSet
self._jammerDefaultLoadout = defaultLoadout or "1xALQ99_2xALQ249"
self._jammerAircraft = self._jammerAircraft or {}
self._jammerAISets = self._jammerAISets or {}
self._jammerSnapshot = self._jammerSnapshot or {}
self._jammedSAMs = self._jammedSAMs or {}
self._jammerMenusBuilt = self._jammerMenusBuilt or {}
if not self._jammerHasTransitions then
self:AddTransition("*", "JammerSuppression", "*")
self:AddTransition("*", "JammerActivated", "*")
self:AddTransition("*", "JammerDeactivated", "*")
self._jammerHasTransitions = true
end
-- Start fast client menu scheduler (5s interval) so menus appear quickly
-- instead of waiting for the next MANTIS Status cycle (~30s)
if not self._jammerMenuScheduler then
self._jammerMenuScheduler = SCHEDULER:New(nil, function()
if not self._jammerEnabled then return end
if self._jammerClientSet then
self:_IterateJammerSet(self._jammerClientSet, function(unit, group)
local unitName = unit:GetName()
if not self._jammerAircraft[unitName] then
self._jammerAircraft[unitName] = {
loadout = self._jammerDefaultLoadout,
active = false,
isClient = unit:IsPlayer(),
hasLoadout = false,
}
elseif not self._jammerAircraft[unitName].isClient and unit:IsPlayer() then
self._jammerAircraft[unitName].isClient = true
end
if self._jammerAircraft[unitName].isClient and not self._jammerMenusBuilt[unitName] then
self:_SetupJammerMenu(unit, group)
end
end)
end
end, {}, 2, 5) -- start in 2s, repeat every 5s
end
self:I(string.format("%sJammer configured | default=%s", self.lid, self._jammerDefaultLoadout))
return self
end
--- Add AI jammer aircraft. Call BEFORE :Start(). Can be called multiple times.
-- AI jammers start OFF; use SetJammerActive(unitName, true) to activate via script.
-- @param #MANTIS self
-- @param Core.Set#SET_GROUP aiSet SET_GROUP or prefix string for AI jammer aircraft
-- @param #string loadout Loadout key for these AI aircraft (default "1xALQ99_2xALQ249")
-- @return #MANTIS self
function MANTIS:AddJammerAI(aiSet, loadout)
self:T(self.lid .. "AddJammerAI")
self._jammerEnabled = true
self._jammerAircraft = self._jammerAircraft or {}
self._jammerAISets = self._jammerAISets or {}
self._jammerSnapshot = self._jammerSnapshot or {}
self._jammedSAMs = self._jammedSAMs or {}
self._jammerMenusBuilt = self._jammerMenusBuilt or {}
local actualSet = aiSet
if type(aiSet) == "string" then
actualSet = SET_GROUP:New():FilterPrefixes(aiSet):FilterActive(true):FilterStart()
end
table.insert(self._jammerAISets, { set = actualSet, loadout = loadout or "1xALQ99_2xALQ249" })
if not self._jammerHasTransitions then
self:AddTransition("*", "JammerSuppression", "*")
self:AddTransition("*", "JammerActivated", "*")
self:AddTransition("*", "JammerDeactivated", "*")
self._jammerHasTransitions = true
end
self:I(string.format("%sJammer AI configured | loadout=%s", self.lid, loadout or "1xALQ99_2xALQ249"))
return self
end
--- [Internal] Core jamming probability curve (v9 / v8.1 engine).
-- Features: decoupled bt_mod/range_mod, tapering residual floor, ±10% jitter.
-- @param #MANTIS self
-- @param #number d Distance in nautical miles
-- @param #table params {peak, mu, sigma_L, tail_dist, band, floor}
-- @param #string loadoutKey Key into JammerLoadouts
-- @return #number Jamming probability as fraction (0.0-0.95)
function MANTIS:_JamGaussianExp(d, params, loadoutKey)
if d < 0 or d > 200 then return 0 end
if not params then return 0 end
local cfg = self.JammerLoadouts[loadoutKey]
if not cfg then return 0 end
local peak, mu, sigma_L, tail_dist, band = params.peak, params.mu, params.sigma_L, params.tail_dist, params.band
local floor = params.floor or 0
local bm
if band == "LOW" then bm = cfg.mult_LOW
elseif band == "S" then bm = cfg.mult_S
elseif band == "IJ" then bm = cfg.mult_IJ
else bm = cfg.mult_OPT end
-- Decoupled window modifiers
local eff_sigma_L = sigma_L / cfg.bt_mod
local eff_tail_dist = tail_dist * cfg.range_mod
local eff_peak = math.min(95, peak * bm)
local raw
if d < mu then
raw = eff_peak * math.exp(-0.5 * ((d - mu) / eff_sigma_L) ^ 2)
else
local lambda = math.log(100.0) / eff_tail_dist
raw = eff_peak * math.exp(-lambda * (d - mu))
end
-- Tapering residual floor (v9): floor full inside engagement band (d <= mu),
-- attenuates with R^2-like falloff past mu over 2.5x the main decay distance.
-- Prevents artificial hard cliff at 200nm cap; matches real radar/jammer physics.
local eff_floor = floor * bm
if eff_floor > 0 and d > mu then
local floorLambda = math.log(100.0) / (eff_tail_dist * 2.5)
eff_floor = eff_floor * math.exp(-floorLambda * (d - mu))
end
raw = math.max(eff_floor, raw)
-- Stochastic jitter ±10% applied AFTER floor (v9), so residual also varies.
-- Prevents gameable flat long-range value. Reflects S/N, RCS, atmospheric fluctuation.
local jitter = 1.0 + (math.random() * 2 - 1) * (self.JammerJitterPercent or 0.10)
raw = raw * jitter
return math.max(0, math.min(95, raw)) / 100.0
end
--- [Internal] Build cached sorted key lists for the resolver.
-- Called once on first use. Avoids re-scanning/re-sorting JammerSAMParams every cycle.
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:_BuildJammerKeyCache()
self._jammerSortedHDS = {}
self._jammerSortedSMA = {}
self._jammerSortedCHM = {}
self._jammerSortedBase = {}
for key, _ in pairs(self.JammerSAMParams) do
if string.find(key, "HDS", 1, true) then table.insert(self._jammerSortedHDS, key)
elseif string.find(key, "SMA", 1, true) then table.insert(self._jammerSortedSMA, key)
elseif string.find(key, "CHM", 1, true) then table.insert(self._jammerSortedCHM, key)
else table.insert(self._jammerSortedBase, key) end
end
local byLenDesc = function(a, b) return #a > #b end
table.sort(self._jammerSortedHDS, byLenDesc)
table.sort(self._jammerSortedSMA, byLenDesc)
table.sort(self._jammerSortedCHM, byLenDesc)
table.sort(self._jammerSortedBase, byLenDesc)
self._jammerResolverCache = {}
return self
end
--- [Internal] Resolve a SAM group name to its jammer parameters.
-- Uses cached sorted key lists and per-name memoization for performance.
-- Substring matching with digit-boundary protection prevents false matches
-- (e.g. "SA-22 Pantsir" no longer matches "SA-2").
-- @param #MANTIS self
-- @param #string grpname The SAM group name
-- @return #table params or nil
function MANTIS:_ResolveJammerParams(grpname)
if not grpname then return nil end
-- Lazy-build cache on first use
if not self._jammerSortedBase then self:_BuildJammerKeyCache() end
-- Memoization: same group name resolves to same params (params don't change at runtime)
local cached = self._jammerResolverCache[grpname]
if cached ~= nil then
if cached == false then return nil end -- false sentinel = "no match"
return cached
end
-- Helper: check key match with digit-boundary protection
local function safeMatch(name, key)
local startPos, endPos = string.find(name, key, 1, true)
if not startPos then return false end
local nextChar = string.sub(name, endPos + 1, endPos + 1)
if nextChar == "" then return true end
if string.match(nextChar, "%d") then return false end
return true
end
-- Detect mod type and select pre-sorted key list
local keyList
if string.find(grpname, "HDS", 1, true) then keyList = self._jammerSortedHDS
elseif string.find(grpname, "SMA", 1, true) then keyList = self._jammerSortedSMA
elseif string.find(grpname, "CHM", 1, true) then keyList = self._jammerSortedCHM end
-- First pass: mod-specific keys
if keyList then
for i = 1, #keyList do
if safeMatch(grpname, keyList[i]) then
local params = self.JammerSAMParams[keyList[i]]
self._jammerResolverCache[grpname] = params
return params
end
end
end
-- Second pass: base keys
local baseKeys = self._jammerSortedBase
for i = 1, #baseKeys do
if safeMatch(grpname, baseKeys[i]) then
local params = self.JammerSAMParams[baseKeys[i]]
self._jammerResolverCache[grpname] = params
return params
end
end
self._jammerResolverCache[grpname] = false -- cache "no match" too
return nil
end
--- [Internal] Count table entries
function MANTIS:_CountTable(t)
local c = 0
for _ in pairs(t) do c = c + 1 end
return c
end
--- [Internal] Iterate any supported SET type, calling fn(unit, group) for each alive unit.
-- Supports SET_GROUP (via ForEachGroupAlive) and SET_CLIENT/SET_PLAYER (via ForEachClient).
-- Note: callback receives ALL alive units — caller must check InAir() for snapshot logic.
-- Menu setup happens for ground units too so clients see the menu before takeoff.
-- @param #MANTIS self
-- @param set The SET object (SET_GROUP, SET_CLIENT, or SET_PLAYER)
-- @param #function fn Callback receiving (unit, group)
-- @return #MANTIS self
function MANTIS:_IterateJammerSet(set, fn)
if not set then return self end
if type(set.ForEachClient) == "function" then
set:ForEachClient(function(client)
if not client then return end
local unit = client:GetClientGroupUnit()
if not unit or not unit:IsAlive() then return end
local group = unit:GetGroup()
if not group then return end
fn(unit, group)
end)
elseif type(set.ForEachGroupAlive) == "function" then
set:ForEachGroupAlive(function(group)
if not group then return end
local units = group:GetUnits()
if not units then return end
for _, unit in pairs(units) do
if unit and unit:IsAlive() then
fn(unit, group)
end
end
end)
else
self:E(self.lid .. "ERROR: jammer set is not SET_GROUP, SET_CLIENT, or SET_PLAYER (no ForEachGroupAlive or ForEachClient method).")
end
return self
end
--- [Internal] Update jammer aircraft states and build snapshot.
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:_UpdateJammers()
if not self._jammerEnabled then return self end
self._jammerSnapshot = {}
-- Process client aircraft
if self._jammerClientSet then
self:_IterateJammerSet(self._jammerClientSet, function(unit, group)
local unitName = unit:GetName()
if not self._jammerAircraft[unitName] then
self._jammerAircraft[unitName] = {
loadout = self._jammerDefaultLoadout,
active = false,
isClient = unit:IsPlayer(),
hasLoadout = false,
}
else
-- Re-check IsPlayer in case a human took the slot after first detection
if not self._jammerAircraft[unitName].isClient and unit:IsPlayer() then
self._jammerAircraft[unitName].isClient = true
end
end
-- Setup menu for clients (works on ground or airborne)
if self._jammerAircraft[unitName].isClient and not self._jammerMenusBuilt[unitName] then
self:_SetupJammerMenu(unit, group)
end
-- Snapshot only includes airborne armed jammers
local state = self._jammerAircraft[unitName]
if state.active and unit:InAir() then
local coord = unit:GetCoordinate()
if coord then
table.insert(self._jammerSnapshot, { coord = coord, loadout = state.loadout, name = unitName })
end
end
end)
end
-- Process AI aircraft
for _, aiEntry in ipairs(self._jammerAISets or {}) do
self:_IterateJammerSet(aiEntry.set, function(unit, group)
local unitName = unit:GetName()
if not self._jammerAircraft[unitName] then
self._jammerAircraft[unitName] = {
loadout = aiEntry.loadout,
active = false,
isClient = false,
hasLoadout = true,
}
end
-- Snapshot only includes airborne armed jammers
local state = self._jammerAircraft[unitName]
if state.active and unit:InAir() then
local coord = unit:GetCoordinate()
if coord then
table.insert(self._jammerSnapshot, { coord = coord, loadout = state.loadout, name = unitName })
end
end
end)
end
-- Clean up dead units only (keep ground-bound clients so menus persist)
-- Respawn detection: when client respawns, slot may briefly disappear then reappear
local toRemove = {}
for unitName, state in pairs(self._jammerAircraft) do
local unit = UNIT:FindByName(unitName)
if not unit or not unit:IsAlive() then
table.insert(toRemove, unitName)
end
end
for _, unitName in ipairs(toRemove) do
if self._jammerAircraft[unitName] and self._jammerAircraft[unitName].active then
self:__JammerDeactivated(1, unitName)
end
self._jammerAircraft[unitName] = nil
self._jammerMenusBuilt[unitName] = nil
end
return self
end
--- [Internal] Compute which SAMs are jammed this cycle.
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:_ComputeJammedSAMs()
self._jammedSAMs = {}
if not self._jammerEnabled then return self end
if #self._jammerSnapshot == 0 then return self end
local M_TO_NM = 1.0 / 1852.0
local allSAMs = {}
local tables = self.automode
and { self.SAM_Table_Long, self.SAM_Table_Medium, self.SAM_Table_Short, self.SAM_Table_PointDef }
or { self.SAM_Table }
for _, samTable in ipairs(tables) do
for _, _data in pairs(samTable) do
if not allSAMs[_data[1]] then allSAMs[_data[1]] = _data[2] end
end
end
for samName, samCoord in pairs(allSAMs) do
local params = self:_ResolveJammerParams(samName)
if params then
local survival = 1.0
for _, jammer in ipairs(self._jammerSnapshot) do
local distNM = samCoord:Get2DDistance(jammer.coord) * M_TO_NM
local pJam = self:_JamGaussianExp(distNM, params, jammer.loadout)
if pJam > 0 then survival = survival * (1.0 - pJam) end
end
local combinedProb = 1.0 - survival
if combinedProb > 0 and math.random() < combinedProb then
self._jammedSAMs[samName] = combinedProb
end
end
end
-- Cycle debug logging (mirrors JammerDebug output, gated on MANTIS debug/verbose flags)
if self.debug or self.verbose then
local activeCount = #(self._jammerSnapshot or {})
local jamCount = self:_CountTable(self._jammedSAMs)
-- Only log if there's something interesting to report
if activeCount > 0 or jamCount > 0 then
local lines = {}
table.insert(lines, string.format("%sJammer cycle: %d active aircraft, %d SAMs jammed",
self.lid, activeCount, jamCount))
-- List active jammers
for _, jammer in ipairs(self._jammerSnapshot) do
local cfg = self.JammerLoadouts[jammer.loadout]
table.insert(lines, string.format(" ACTIVE: %s | loadout=%s",
jammer.name, cfg and cfg.name or jammer.loadout))
end
-- List jammed SAMs
for samName, prob in pairs(self._jammedSAMs) do
table.insert(lines, string.format(" JAMMED: %s @ %.0f%%", samName, prob * 100))
end
local text = table.concat(lines, "\n")
self:I(text)
if self.debug then
MESSAGE:New(text, 10, "MANTIS"):ToAll()
end
end
end
return self
end
--- [Internal] Set up tiered F10 jammer menu for a player unit.
-- @param #MANTIS self
-- @param Wrapper.Unit#UNIT unit
-- @param Wrapper.Group#GROUP group
-- @return #MANTIS self
function MANTIS:_SetupJammerMenu(unit, group)
local unitName = unit:GetName()
local groupName = group:GetName()
if self._jammerMenusBuilt[unitName] then return self end
self._jammerMenusBuilt[unitName] = true
-- If this group already has a menu tree (e.g. respawn), remove it first
if self._jammerGroupMenus and self._jammerGroupMenus[groupName] then
self._jammerGroupMenus[groupName]:Remove()
self._jammerGroupMenus[groupName] = nil
end
self._jammerGroupMenus = self._jammerGroupMenus or {}
local rootMenu = MENU_GROUP:New(group, "Jammer Controls")
self._jammerGroupMenus[groupName] = rootMenu
-- Tiered loadout submenus
local alq99Menu = MENU_GROUP:New(group, "ALQ-99 Loadouts", rootMenu)
local alq249Menu = MENU_GROUP:New(group, "ALQ-249 Loadouts", rootMenu)
local mixedMenu = MENU_GROUP:New(group, "Mixed Loadouts", rootMenu)
local tierMenus = { ALQ99 = alq99Menu, ALQ249 = alq249Menu, Mixed = mixedMenu }
for tierName, keys in pairs(self.JammerLoadoutTiers) do
local parentMenu = tierMenus[tierName]
for _, loadoutKey in ipairs(keys) do
local cfg = self.JammerLoadouts[loadoutKey]
if cfg then
MENU_GROUP_COMMAND:New(group, cfg.name, parentMenu,
self._JammerMenuSetLoadout, self, unitName, loadoutKey, group, rootMenu)
end
end
end
MESSAGE:New("JAMMER ONLINE\nSelect a loadout from the Jammer Controls menu.", 15, "JAMMER"):ToGroup(group)
return self
end
--- [Internal] F10 callback: Set loadout and show Music toggle
function MANTIS:_JammerMenuSetLoadout(unitName, loadoutKey, group, rootMenu)
local state = self._jammerAircraft[unitName]
if not state then return end
state.loadout = loadoutKey
state.hasLoadout = true
local cfg = self.JammerLoadouts[loadoutKey]
MESSAGE:New(string.format("LOADOUT SELECTED: %s\n%s", cfg and cfg.name or loadoutKey, cfg and cfg.description or ""), 12, "JAMMER"):ToGroup(group)
-- Add Music toggle if not already present
if not state._musicMenuAdded then
state._musicMenuAdded = true
state._musicOnMenu = nil
state._musicOffMenu = nil
self:_ShowMusicOn(unitName, group, rootMenu)
end
end
--- [Internal] Show "Music On" menu entry (jammer is OFF, click to turn ON)
function MANTIS:_ShowMusicOn(unitName, group, rootMenu)
local state = self._jammerAircraft[unitName]
if not state then return end
if state._musicOffMenu then state._musicOffMenu:Remove() state._musicOffMenu = nil end
state._musicOnMenu = MENU_GROUP_COMMAND:New(group, "Music On", rootMenu,
function()
local s = self._jammerAircraft[unitName]
if not s then return end
s.active = true
MESSAGE:New(string.format("JAMMER ACTIVE\n%s", self.JammerLoadouts[s.loadout] and self.JammerLoadouts[s.loadout].name or s.loadout), 10, "JAMMER"):ToGroup(group)
self:__JammerActivated(1, unitName, s.loadout)
self:_ShowMusicOff(unitName, group, rootMenu)
end)
end
--- [Internal] Show "Music Off" menu entry (jammer is ON, click to turn OFF)
function MANTIS:_ShowMusicOff(unitName, group, rootMenu)
local state = self._jammerAircraft[unitName]
if not state then return end
if state._musicOnMenu then state._musicOnMenu:Remove() state._musicOnMenu = nil end
state._musicOffMenu = MENU_GROUP_COMMAND:New(group, "Music Off", rootMenu,
function()
local s = self._jammerAircraft[unitName]
if not s then return end
s.active = false
MESSAGE:New("JAMMER SAFE", 10, "JAMMER"):ToGroup(group)
self:__JammerDeactivated(1, unitName)
self:_ShowMusicOn(unitName, group, rootMenu)
end)
end
--- On After "JammerSuppression" event
function MANTIS:onafterJammerSuppression(From, Event, To, Group, Name, Probability)
self:T({From, Event, To, Name, Probability})
return self
end
--- On After "JammerActivated" event
function MANTIS:onafterJammerActivated(From, Event, To, UnitName, Loadout)
self:T({From, Event, To, UnitName, Loadout})
return self
end
--- On After "JammerDeactivated" event
function MANTIS:onafterJammerDeactivated(From, Event, To, UnitName)
self:T({From, Event, To, UnitName})
return self
end
--- Get table of currently jammed SAM group names.
-- @param #MANTIS self
-- @return #table {samName = probability}
function MANTIS:GetJammedSAMs()
return self._jammedSAMs or {}
end
--- Get number of active jammer aircraft.
-- @param #MANTIS self
-- @return #number
function MANTIS:GetActiveJammerCount()
return self._jammerSnapshot and #self._jammerSnapshot or 0
end
--- Print a debug report of the jammer system to dcs.log and screen.
-- Lists registered aircraft, their states, snapshot count, and currently
-- jammed SAMs with their probabilities. Use this to diagnose menu issues,
-- missing aircraft, or unexpected SAM matching.
-- @param #MANTIS self
-- @param #boolean toScreen If true, also display report on screen (default false)
-- @return #MANTIS self
function MANTIS:JammerDebug(toScreen)
local lines = {}
table.insert(lines, "=== MANTIS JAMMER DEBUG REPORT ===")
table.insert(lines, string.format("Enabled: %s | Default loadout: %s",
tostring(self._jammerEnabled), tostring(self._jammerDefaultLoadout)))
table.insert(lines, string.format("Has client set: %s | AI sets: %d",
tostring(self._jammerClientSet ~= nil), #(self._jammerAISets or {})))
-- Aircraft state
local count = 0
for unitName, state in pairs(self._jammerAircraft or {}) do
count = count + 1
table.insert(lines, string.format(" [%s] active=%s isClient=%s loadout=%s hasLoadout=%s",
unitName, tostring(state.active), tostring(state.isClient),
tostring(state.loadout), tostring(state.hasLoadout)))
end
table.insert(lines, string.format("Total tracked aircraft: %d", count))
table.insert(lines, string.format("Active jammer snapshot: %d aircraft airborne+armed", #(self._jammerSnapshot or {})))
-- Jammed SAMs
local jcount = 0
for samName, prob in pairs(self._jammedSAMs or {}) do
jcount = jcount + 1
table.insert(lines, string.format(" JAMMED: %s @ %.0f%%", samName, prob * 100))
end
table.insert(lines, string.format("Currently jammed SAMs: %d", jcount))
table.insert(lines, "=== END REPORT ===")
local report = table.concat(lines, "\n")
self:I(report)
if toScreen then
MESSAGE:New(report, 30, "JAMMER DEBUG"):ToAll()
end
return self
end
--- Test the SAM resolver against a hypothetical group name.
-- Useful for verifying naming convention before mission start.
-- @param #MANTIS self
-- @param #string testName The hypothetical SAM group name to test
-- @return #string Matched JammerSAMParams key, or "NO MATCH"
function MANTIS:JammerTestResolver(testName)
local params = self:_ResolveJammerParams(testName)
if not params then
self:I(string.format("[JammerTestResolver] '%s' -> NO MATCH", testName))
return "NO MATCH"
end
-- Find which key matched
for key, p in pairs(self.JammerSAMParams) do
if p == params then
self:I(string.format("[JammerTestResolver] '%s' -> '%s' {peak=%d, mu=%d, band=%s, floor=%d}",
testName, key, params.peak, params.mu, params.band, params.floor or 0))
return key
end
end
return "MATCHED (key unknown)"
end
--- Manually set a jammer aircraft loadout (AI scripted control).
-- @param #MANTIS self
-- @param #string unitName DCS unit name
-- @param #string loadoutKey Key into JammerLoadouts
-- @return #MANTIS self
function MANTIS:SetJammerLoadout(unitName, loadoutKey)
if self._jammerAircraft and self._jammerAircraft[unitName] then
self._jammerAircraft[unitName].loadout = loadoutKey
self._jammerAircraft[unitName].hasLoadout = true
end
return self
end
--- Manually activate/deactivate a jammer aircraft (AI scripted control).
-- @param #MANTIS self
-- @param #string unitName DCS unit name
-- @param #boolean active true=on, false=off
-- @return #MANTIS self
function MANTIS:SetJammerActive(unitName, active)
if self._jammerAircraft and self._jammerAircraft[unitName] then
local wasActive = self._jammerAircraft[unitName].active
self._jammerAircraft[unitName].active = active
if active and not wasActive then
self:__JammerActivated(1, unitName, self._jammerAircraft[unitName].loadout)
elseif not active and wasActive then
self:__JammerDeactivated(1, unitName)
end
end
return self
end
-----------------------------------------------------------------------
-- MANTIS Jammer Extension - Hook Overrides
-----------------------------------------------------------------------
-- Guard: only save originals once (prevents infinite recursion if file loaded twice)
if not MANTIS._CheckLoopOriginal then
MANTIS._CheckLoopOriginal = MANTIS._CheckLoop
end
if not MANTIS._onbeforeStatusOriginal then
MANTIS._onbeforeStatusOriginal = MANTIS.onbeforeStatus
end
--- [Internal] Override: _CheckLoop with jammer suppression.
function MANTIS:_CheckLoop(samset, detset, dlink, limit)
local r, g, s = self:_CheckLoopOriginal(samset, detset, dlink, limit)
if self._jammerEnabled and self._jammedSAMs then
for _, _data in pairs(samset) do
local name = _data[1]
if self._jammedSAMs[name] and self.SamStateTracker[name] == "RED" then
local samgroup = GROUP:FindByName(name)
if samgroup and samgroup:IsAlive() then
if self.UseEmOnOff then
samgroup:EnableEmission(false)
else
samgroup:OptionAlarmStateGreen()
end
self.SamStateTracker[name] = "GREEN"
self:__JammerSuppression(1, samgroup, name, self._jammedSAMs[name])
if self.ShoradLink then
local Shorad = self.Shorad
local shoradradius = self.checkradius
local ontime = self.ShoradTime
Shorad:WakeUpShorad(name, shoradradius, ontime, nil, true)
self:__ShoradActivated(1, name, shoradradius, ontime)
end
if self.debug or self.verbose then
self:T(string.format("%sJAMMED: %s forced GREEN (%.1f%%)", self.lid, name, self._jammedSAMs[name] * 100))
end
end
end
end
end
return r, g, s
end
--- [Internal] Override: onbeforeStatus with jammer update.
function MANTIS:onbeforeStatus(From, Event, To)
if self._jammerEnabled then
self:_UpdateJammers()
self:_ComputeJammedSAMs()
end
return self:_onbeforeStatusOriginal(From, Event, To)
end
end
-----------------------------------------------------------------------
-- MANTIS end
+13 -8
View File
@@ -795,15 +795,20 @@ do
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord1 = tgtgrp1:GetCoordinate()
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord2 = tgtgrp2:GetCoordinate()
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
if dist1 < dist2 then
targetunit = tgtgrp1
targetcat = Object.Category.UNIT
if tgtgrp2 then
local tgtcoord2 = tgtgrp2:GetCoordinate()
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
if dist1 < dist2 then
targetunit = tgtgrp1
targetcat = Object.Category.UNIT
else
targetunit = tgtgrp2
targetcat = Object.Category.UNIT
end
else
targetunit = tgtgrp2
targetunit = tgtgrp1
targetcat = Object.Category.UNIT
end
end
@@ -1485,7 +1485,7 @@ function SUPPRESSION:onafterStop(Controllable, From, Event, To)
local text=string.format("Stopping SUPPRESSION for group %s", self.Controllable:GetName())
MESSAGE:New(text, 10):ToAllIf(self.Debug)
sefl:T(self.lid..text)
self:T(self.lid..text)
-- Clear all pending schedules
self.CallScheduler:Clear()
@@ -4097,7 +4097,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
-- Get name of template group.
local templategroupname=group:GetName()
local unit = group:GetUnit(1)
local Descriptors= (unit and unit:IsAlive()) and unit:GetDesc() or {}
local Descriptors= (unit and unit:IsAlive()~=nil) and unit:GetDesc() or {}
local Category=group:GetCategory()
local TypeName=group:GetTypeName() or "none"
local SpeedMax=group:GetSpeedMax()
@@ -4115,7 +4115,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
for _i,_unit in pairs(group:GetUnits()) do
local unit=_unit --Wrapper.Unit#UNIT
local Desc=unit:GetDesc()
-- Weight. We sum up all units in the group.
local unitweight=forceweight or Desc.massEmpty
if unitweight then
+1
View File
@@ -115,6 +115,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyA2G.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/TARS.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/ShapeBase.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Circle.lua' )
+60 -22
View File
@@ -2717,17 +2717,27 @@ function AIRBOSS:DeleteRecoveryWindow( Window, Delay )
self:ScheduleOnce( Delay, self.DeleteRecoveryWindow, self, Window )
else
for i, _recovery in pairs( self.recoverytimes ) do
local recovery = _recovery -- #AIRBOSS.Recovery
if not Window then
return
end
if Window and Window.ID == recovery.ID then
if Window.OPEN then
-- Window is currently open.
self:RecoveryStop()
else
table.remove( self.recoverytimes, i )
end
-- If this window is currently open, stop recovery first. Mark it OVER so the
-- recovery time check cannot re-open it before/while we remove it.
if Window.OPEN then
Window.OPEN = false
Window.OVER = true
if self:IsRecovering() then
self:RecoveryStop()
end
end
-- Remove the window from the queue by its unique ID. Iterate over a numerically
-- indexed copy of the keys and remove via ipairs-safe reverse loop so that the
-- removal does not corrupt traversal (the window may appear once).
for i = #self.recoverytimes, 1, -1 do
local recovery = self.recoverytimes[i] -- #AIRBOSS.Recovery
if recovery and recovery.ID == Window.ID then
table.remove( self.recoverytimes, i )
end
end
end
@@ -4078,15 +4088,23 @@ function AIRBOSS:_CheckRecoveryTimes()
if self:IsRecovering() then
-- Carrier is already recovering.
state = "in progress"
else
-- Start recovery.
elseif not recovery.OVER then
-- Start recovery. Only if the window has not already been closed/cancelled.
-- The OVER guard prevents a window that was stopped manually (e.g. via the
-- Skipper "Stop Recovery" menu) from being immediately re-opened on the next
-- status tick while its [START,STOP) range is still active.
self:RecoveryStart( recovery.CASE, recovery.OFFSET )
state = "starting now"
recovery.OPEN = true
else
-- Window was already closed/cancelled within its active time range.
state = "cancelled"
end
-- Set current recovery window.
currwindow = recovery
-- Set current recovery window (unless this window has been cancelled).
if not recovery.OVER then
currwindow = recovery
end
else -- Stop time HAS passed.
@@ -4183,13 +4201,16 @@ function AIRBOSS:_CheckRecoveryTimes()
-- Time into the wind 1 day or if longer recovery time + the 5 min early.
local t = math.max( nextwindow.STOP - nextwindow.START + self.dTturn, 60 * 60 * 24 )
-- Recovery wind on deck in knots.
-- Recovery wind on deck in knots.
-- NOTE: Do NOT clamp the desired wind-over-deck (WOD) to the carrier's max hull
-- speed here. WOD = carrier speed + headwind, so a WOD target above the hull's
-- top speed is achievable whenever there is wind. CarrierTurnIntoWind ->
-- GetHeadingIntoWind already converts the WOD target into the required hull
-- speed and caps THAT at the carrier's max speed (Vmax) internally. Clamping the
-- WOD target itself capped achievable WOD at ~30 kts (the supercarrier's max
-- hull speed) even in strong wind, which made high-WOD recovery windows fall
-- short of their requested value.
local v = UTILS.KnotsToMps( nextwindow.SPEED )
-- Check that we do not go above max possible speed.
local vmax = self.carrier:GetSpeedMax() / 3.6 -- convert to m/s
v = math.min( v, vmax )
-- Route carrier into the wind. Sets self.turnintowind=true
self:CarrierTurnIntoWind( t, v, uturn )
@@ -4361,11 +4382,20 @@ function AIRBOSS:onafterRecoveryStop( From, Event, To )
self:CarrierResumeRoute( coord )
end
-- Delete current recovery window if open.
if self.recoverywindow and self.recoverywindow.OPEN == true then
-- Mark the current recovery window closed and cancelled, then remove it from the
-- queue. We do NOT gate this on Window.OPEN: that flag is only set by
-- _CheckRecoveryTimes (not by RecoveryStart), and the recovery time check nils and
-- rebuilds self.recoverywindow every status tick, so OPEN is not a reliable signal
-- here. Setting OVER=true is what actually prevents the window from being re-opened
-- on the next tick while its [START,STOP) range is still active.
--
-- The removal is deferred by one tick (Delay>0) so it does not mutate the
-- recoverytimes table while _CheckRecoveryTimes may be iterating over it (the natural
-- close path calls RecoveryStop() from inside that loop).
if self.recoverywindow then
self.recoverywindow.OPEN = false
self.recoverywindow.OVER = true
self:DeleteRecoveryWindow( self.recoverywindow )
self:DeleteRecoveryWindow( self.recoverywindow, 1 )
end
-- Check recovery windows. This sets self.recoverywindow to the next window.
@@ -12903,6 +12933,14 @@ function AIRBOSS:_LSOgrade( playerData )
-- Circuit edit only take points awary from a 1 wire if there are more than 4 other deviations
if playerData.wire == 1 and points >= 3 and N > 4 then
points = points -1
-- We also need to change the grade based on the new points.
if points == 4 then
grade = "OK"
elseif points == 3 then
grade = "(OK)"
elseif points == 2 then
grade = "--"
end
end
env.info("Returning: " .. grade .. " " .. points .. " " .. G)
+1 -1
View File
@@ -6610,7 +6610,7 @@ function AUFTRAG:GetDCSMissionTask(MissionGroup)
local SeadUnitSet = SET_UNIT:New()
for _,_unit in pairs (ScanUnitSet.Set) do
local unit = _unit -- Wrapper.Unit#UNTI
if unit and unit:IsAlive() and unit:HasSEAD() then
if unit and unit:IsAlive() and unit.HasSEAD and unit:HasSEAD() then
self:T("Adding UNIT for SEAD: "..unit:GetName())
local task = CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318)
table.insert(DCStasks, task)
+8 -2
View File
@@ -504,6 +504,9 @@ CSAR.AircraftType["OH58D"] = 2
CSAR.AircraftType["CH-47Fbl1"] = 31
CSAR.AircraftType["AH-6J"] = 2
CSAR.AircraftType["MH-6J"] = 2
CSAR.AircraftType["Ka-50_3"] = 0
CSAR.AircraftType["Ka-50"] = 0
CSAR.AircraftType["AV8BNA"] = 0
--- CSAR class version.
-- @field #string version
@@ -2538,8 +2541,11 @@ function CSAR:_AddMedevacMenuItem()
if _unit then
--self:T("Unitname ".._unit:GetName().." IsAlive "..tostring(_unit:IsAlive()).." IsPlayer "..tostring(_unit:IsPlayer()))
if _unit:IsAlive() and _unit:IsPlayer() then
local unitName = _unit:GetName()
_UnitList[unitName] = unitName
local _maxUnits = self.AircraftType[_unit:GetTypeName()]
if _maxUnits == nil or _maxUnits > 0 then
local unitName = _unit:GetName()
_UnitList[unitName] = unitName
end
end -- end isAlive
end -- end if _unit
end -- end for
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+46 -15
View File
@@ -21,7 +21,7 @@
--
-------------------------------------------------------------------------
-- Date: September 2023
-- Last Update: Mar 2026
-- Last Update: June 2026
-------------------------------------------------------------------------
--
--- **Ops** - Easy GCI & CAP Manager
@@ -135,6 +135,7 @@
-- local mywing = EASYGCICAP:New("Blue CAP Operations",AIRBASE.Caucasus.Kutaisi,"blue","Blue EWR")
--
-- -- Add a CAP patrol point belonging to our airbase, we'll be at 30k ft doing 400 kn, initial direction 90 degrees (East), leg 20NM
-- -- NOTE - Skip this function and do not create CAP Points if you want GCI behaviour only.
-- mywing:AddPatrolPointCAP(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone 1"):GetCoordinate(),30000,400,90,20)
--
-- -- Add a Squadron with template "Blue Sq1 M2000c", 20 airframes, skill good, Modex starting with 102 and skin "Vendee Jeanne"
@@ -190,9 +191,10 @@
-- * @{#EASYGCICAP.SetDefaultResurrection}: Set how many seconds the AirWing stays inoperable after the AirWing STATIC HQ ist destroyed, default 900 secs.
-- * @{#EASYGCICAP.SetDefaultCAPSpeed}: Set how many knots the CAP flights should do (will be altitude corrected), default 300 kn.
-- * @{#EASYGCICAP.SetDefaultCAPAlt}: Set at which altitude (ASL) the CAP planes will fly, default 25,000 ft.
-- * @{#EASYGCICAP.SetDefaultINTERCEPTAlt}: Set at which altitude (ASL) the Intercept planes will fly, default 25,000 ft.
-- * @{#EASYGCICAP.SetDefaultCAPDirection}: Set the initial direction from the CAP point the planes will fly in degrees, default is 90°.
-- * @{#EASYGCICAP.SetDefaultCAPLeg}: Set the length of the CAP leg, default is 15 NM.
-- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2.
-- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CAP/GCI), defaults to 2.
-- * @{#EASYGCICAP.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100.
-- * @{#EASYGCICAP.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
-- * @{#EASYGCICAP.SetDefaultEngageRange}: Set max engage range for CAP flights if they detect intruders, defaults to 50.
@@ -286,7 +288,7 @@ EASYGCICAP = {
--- EASYGCICAP class version.
-- @field #string version
EASYGCICAP.version="0.1.35"
EASYGCICAP.version="0.1.38"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--
@@ -587,6 +589,17 @@ function EASYGCICAP:SetDefaultCAPAlt(Altitude)
return self
end
--- Set default INTERCEPT Altitude in feet
-- @param #EASYGCICAP self
-- @param #number Altitude (Optional) Altitude defaults to 25000
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultINTERCEPTAlt(Altitude)
self:T(self.lid.."SetDefaultINTERCEPTAlt")
self.interceptalt = Altitude or 25000
return self
end
--
--- Set default CAP lieg initial direction in degrees
-- @param #EASYGCICAP self
-- @param #number Direction (Optional) Direction defaults to 90 (East)
@@ -754,6 +767,15 @@ end
function EASYGCICAP:_AddAirwing(Airbasename, Alias)
self:T(self.lid.."_AddAirwing "..Airbasename)
-- Gather Some Stats
local function counttable(tbl)
local count = 0
for _,_data in pairs(tbl) do
count = count + 1
end
return count
end
local CapFormation = self.CapFormation
local DespawnAfterLanding = self.DespawnAfterLanding
local DespawnAfterHolding = self.DespawnAfterHolding
@@ -772,7 +794,11 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
CAP_Wing:SetMarker(false)
CAP_Wing:SetAirbase(AIRBASE:FindByName(Airbasename))
CAP_Wing:SetRespawnAfterDestroyed()
CAP_Wing:SetNumberCAP(self.capgrouping)
--- #DONE avoid wings with no CAP points starting CAP anyhow; AirWing uses this to start CAP and creates points when there are none.
if counttable(self.ManagedCP) >0 then
CAP_Wing:SetNumberCAP(self.capgrouping)
end
CAP_Wing:SetCapCloseRaceTrack(true)
if self.showpatrolpointmarks then
@@ -854,16 +880,18 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
end
if self.noalert5 > 0 then
local alert
if self.ClassName == "EASYGCICAP" then
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
elseif self.ClassName == "EASYA2G" then
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.BAI)
for i=1,self.noalert5 do
local alert
if self.ClassName == "EASYGCICAP" then
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
elseif self.ClassName == "EASYA2G" then
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.BAI)
end
alert:SetRequiredAssets(self.capgrouping)
alert:SetRepeat(99)
CAP_Wing:AddMission(alert)
table.insert(self.ListOfAuftrag,alert)
end
alert:SetRequiredAssets(self.noalert5)
alert:SetRepeat(99)
CAP_Wing:AddMission(alert)
table.insert(self.ListOfAuftrag,alert)
end
self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename }
@@ -1037,7 +1065,7 @@ function EASYGCICAP:_SetCAPPatrolPoints()
MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Wing = self.wings[data.AirbaseName][1] -- Ops.AirWing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
@@ -1503,6 +1531,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
local overhead = self.overhead
local capspeed = self.capspeed + 100
local capalt = self.capalt
local interalt = self.interceptalt or self.capalt
local maxsize = self.maxinterceptsize
local repeatsonfailure = self.repeatsonfailure
@@ -1590,6 +1619,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
:SetRepeatOnFailure(repeats)
:SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt))
:SetMissionAltitude(capalt)
:SetEngageAltitude(interalt)
if nogozoneset:Count() > 0 then
InterceptAuftrag:AddConditionSuccess(
@@ -1835,9 +1865,10 @@ function EASYGCICAP:onafterRestart(From,Event,To)
-- self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename }
for _,_wing in pairs(self.wings or {}) do
for _,_aw in pairs(_wing) do
_wing[1]:Start()
_wing[1]:Restart()
end
end
end
self:__Status(5)
return self
end
@@ -1270,6 +1270,15 @@ function INTEL:_UpdateContact(Contact)
else
Contact.maneuvering = false
end
local typename = Contact.group:GetTypeName()
local base_rcs = INTEL.RCS_Table[typename]
if not base_rcs then
-- Fallback: category default
local cat = Contact.group:GetCategory()
base_rcs = (cat and INTEL.RCS_CategoryDefault[cat]) or INTEL.RCS_Reference
end
Contact.rcs = base_rcs
end
end
+1 -1
View File
@@ -2581,7 +2581,7 @@ end
-- @return #boolean Returns `true` if given cohort can meet all requirements.
function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properties, WeaponTypes, TargetVec2, RangeMax, RefuelSystem, CargoWeight, MaxWeight, RangeMin)
RangeMin = RangeMin or 0
RangeMin = RangeMin or -1
--- Function to check category.
local function CheckCategory(_cohort)
File diff suppressed because it is too large Load Diff
+109 -32
View File
@@ -61,24 +61,21 @@
--
-- # The MSRS Concept
--
-- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS).
-- This class allows to broadcast sound files or text to speech via Simple Radio Standalone (SRS).
--
-- ## Prerequisites
--
-- * This script needs SRS version >= 1.9.6
-- * You need to de-sanitize os, io and lfs in the missionscripting.lua
-- * Optional: DCS-gRPC as backend to communicate with SRS (via infra)
-- * Optional: HOUNDTTS as backend to communicate with SRS (via infra)
-- * Optional but recommended: HOUND TTS as backend to communicate with SRS (via plugin)
-- * Optional: DCS-gRPC as backend to communicate with SRS (via plugin)
--
-- ## Known Issues
--
-- ## Knwon Issues
--
-- ### Pop-up Window
-- ### Pop-up Window (with backend windows)
--
-- The text-to-speech conversion of SRS is done via an external exe file. When this file is called, a windows `cmd` window is briefly opended. That puts DCS out of focus, which is annoying,
-- expecially in VR but unavoidable (if you have a solution, please feel free to share!).
-- expecially in VR, but unavoidable unless you use HOUD or gRPC as backends.
--
-- NOTE that this is not an issue if the mission is running on a server.
-- Also NOTE that using DCS-gRPC and Hound-TTS as backend create no pop-up window.
--
-- # Play Sound Files
--
@@ -137,16 +134,17 @@
--
-- See @{#MSRS.LoadConfigFile} for details on how to set this up.
--
-- ## TTS Providers
--
-- The default provider for generating speech from text is the native Windows TTS service. Note that you need to install the voices you want to use.
-- ## TTS (text-to-speech) Providers
--
-- The **currently recommended** backend is HOUND TTS (no pop-ups), which has a wide variety of TTS providers.
-- The *default* provider for generating speech from text is the native Windows TTS service. Note that you need to install the voices you want to use.
--
-- **Pro-Tip** - use the command line with power shell to call `DCS-SR-ExternalAudio.exe` - it will tell you what is missing
-- and also the Google Console error, in case you have missed a step in setting up your Google TTS.
-- For example, `.\DCS-SR-ExternalAudio.exe -t "Text Message" -f 255 -m AM -c 2 -s 2 -z -G "Path_To_You_Google.Json"`
-- plays a message on 255 MHz AM for the blue coalition in-game.
--
-- ### Google
-- ### Google (best speech synthesis)
--
-- In order to use Google Cloud for TTS you need to use @{#MSRS.SetProvider} and @{#MSRS.SetProviderOptionsGoogle} functions:
--
@@ -162,7 +160,7 @@
-- For more information on setting up a cloud account, visit: https://cloud.google.com/text-to-speech
-- Google's supported SSML reference: https://cloud.google.com/text-to-speech/docs/ssml
--
-- ### Amazon Web Service [Only DCS-gRPC backend]
-- ### Amazon Web Service [Only HOUDN and DCS-gRPC backend]
--
-- In order to use Amazon Web Service (AWS) for TTS you need to use @{#MSRS.SetProvider} and @{#MSRS.SetProviderOptionsAmazon} functions:
--
@@ -173,7 +171,7 @@
--
-- You can set the voice to use with AWS via @{#MSRS.SetVoiceAmazon}.
--
-- ### Microsoft Azure [Only DCS-gRPC backend]
-- ### Microsoft Azure [Only HOUND and DCS-gRPC backend]
--
-- In order to use Microsoft Azure for TTS you need to use @{#MSRS.SetProvider} and @{#MSRS.SetProviderOptionsAzure} functions:
--
@@ -184,6 +182,12 @@
--
-- You can set the voice to use with Azure via @{#MSRS.SetVoiceAzure}.
--
-- ### Piper, OpenAI and further provider options [Only HOUND backend]
--
-- You can use further providers with HOUND TTS as a backend, see [Hound-TTS](https://github.com/uriba107/HoundTTS/releases) for setup.
--
-- msrs:SetProvider(MSRS.Provider.PIPER) -- e.g. for piper
--
-- ## Backend
--
-- The default interface to SRS is via calling the 'DCS-SR-ExternalAudio.exe'. As noted above, this has the unavoidable drawback that a pop-up briefly appears
@@ -191,13 +195,13 @@
--
-- ## Hound TTS as an alternative to 'DCS-SR-ExternalAudio.exe' for TTS
--
-- An alternative interface to SRS is [Hound-TTS](https://github.com/uriba107/HoundTTS/releases). This does not call an exe file and therefore avoids the annoying pop-up window.
-- the recommended alternative interface to SRS is [Hound-TTS](https://github.com/uriba107/HoundTTS/releases). This does not call an exe file and therefore avoids the annoying pop-up window.
-- In addition to Windows and Google cloud, it also offers Piper local voice creation and others as providers for TTS.
--
-- Use @{#MSRS.SetDefaultBackendHound} to enable [Hound-TTS](https://github.com/uriba107/HoundTTS/releases) as an alternate backend.
-- This can be useful if the popup should be avoided or to use Piper or others for TTS. Please note, only text-to-speech is supported and it it cannot be used to transmit audio files.
--
-- Hound TTS must be installed and configured per the [Hound TTS](https://github.com/uriba107/HoundTTS#installation) prior to use.
-- Hound TTS must be installed and configured per the [Hound TTS](https://github.com/uriba107/HoundTTS#installation) GitHub prior to use.
-- If a cloud TTS provider is being used, the API key(s) must be set as per the documentation.
-- Hound TTS can be used both with DCS dedicated server and regular DCS installations.
--
@@ -280,7 +284,7 @@ MSRS = {
--- MSRS class version.
-- @field #string version
MSRS.version="0.3.5"
MSRS.version="0.3.7"
--- Voices
-- @type MSRS.Voices
@@ -731,14 +735,16 @@ MSRS.Backend = {
-- @field #string AZURE Microsoft Azure (`azure`). Only possible with DCS-gRPC backend.
-- @field #string AMAZON Amazon Web Service (`aws`). Only possible with DCS-gRPC backend.
-- @field #string PIPER Piper local voice service. Only possible with Hound-TTS backend.
-- @field #string KITTEN Kitten voice server. Only possible with Hound-TTS backend.
-- @field #string KITTEN Kitten voice server. Only possible with Hound-TTS backend. Superseded by OpenAI
-- @field #string OPENAI OpenAI voice server. Only possible with Hound-TTS backend.
MSRS.Provider = {
WINDOWS = "win",
GOOGLE = "gcloud",
AZURE = "azure",
AMAZON = "aws",
PIPER = "piper",
KITTEN = "kitten",
KITTEN = "openai",
OPENAI = "openai",
}
--- Function for UUID.
@@ -1471,6 +1477,15 @@ function MSRS:SetTTSProviderKitten()
return self
end
--- Use OpenAI to provide text-to-speech. Only supported if used in combination with Hound-TTS as backend.
-- @param #MSRS self
-- @return #MSRS self
function MSRS:SetTTSProviderOpenAI()
self:F()
self:SetProvider(MSRS.Provider.OPENAI)
return self
end
--- Print SRS help to DCS log file.
-- @param #MSRS self
-- @return #MSRS self
@@ -1605,7 +1620,7 @@ function MSRS:PlayText(Text, Delay, Coordinate, Speed, Speaker)
else
local speaker = Speaker or self.Speaker
if self.backend==MSRS.Backend.GRPC then
self:T(self.lid.."Transmitting")
self:_DCSgRPCtts(Text, nil, nil , nil, nil, nil, nil, Coordinate)
@@ -2088,19 +2103,16 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co
return
end
Frequencies = UTILS.EnsureTable(Frequencies)
Modulations = UTILS.EnsureTable(Modulations)
Frequencies = UTILS.EnsureTable(Frequencies or self.frequencies)
Modulations = UTILS.EnsureTable(Modulations or self.modulations)
local ffs = {}
for _,_f in pairs(Frequencies or self.frequencies) do
table.insert(ffs,string.format("%.1f",_f))
for _,_f in pairs(Frequencies) do
table.insert(ffs,string.format("%.3f",_f))
end
local freqs = table.concat(ffs, ",")
local modus = table.concat(Modulations or self.modulations, ",")
local modus = table.concat(Modulations, ",")
local coal=Coalition or self.coalition
local gender=Gender or self.gender
@@ -2144,7 +2156,7 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co
gender = gender,
speaker = Speaker or self.Speaker,
}
local speechtime = HoundTTS.Transmit(Message, TransmissionP, ProviderP)
return speechtime
@@ -2202,7 +2214,7 @@ function MSRS:_HoundTestTone(Frequencies, Modulations, Coalition)
local ffs = {}
for _,_f in pairs(Frequencies or self.frequencies) do
table.insert(ffs,string.format("%.1f",_f))
table.insert(ffs,string.format("%.3f",_f))
end
local freqs = table.concat(ffs, ",")
@@ -2258,6 +2270,71 @@ function MSRS._HoundTranslate(Message,Parameters,CallbackFunction)
return
end
--- Create and run a radio jammer for a number of seconds on given frequencies. Requires HOUND backend v0.2.0-beta5 or better.
-- @param #MSRS self
-- @param #table Frequencies The table of frequencies to use.
-- @param #table Modulations The table of modulations to use.
-- @param #number Coalition (Optional) The coalition to use. Defaults to previously set coalition on this instance.
-- @param #string Noisetype (Optional) One of "white" (default) | "chirp" | "harsh" | "jam".
-- @param #number Volume (Optional) Volume 0.01.0 (default 1.0).
-- @param #number Seconds (Optional) How long in seconds this jammer runs. Defaults to 30 seconds.
-- @param #string Label (Optional) The label to use, defaults to "MSRS".
-- @param DCS#Vec3 Vec3 (Optional) Vec3 of the sender's position.
-- @param #boolean Encrypt (Optional) If true, use the SRS encrypt option.
-- @param #number EncKey (Optional) If encrypt is true, use this encrypt key.
-- @return #number ID The Jammer ID to switch the jamming off again with `MSRS.RadioJammerOff()`
function MSRS:RadioJammerOn(Frequencies, Modulations, Coalition, Noisetype, Volume, Seconds, Label, Vec3, Encrypt, EncKey)
self:T(self.lid.."RadioJammerOn")
Frequencies = UTILS.EnsureTable(Frequencies)
Modulations = UTILS.EnsureTable(Modulations)
local ffs = {}
for _,_f in pairs(Frequencies or self.frequencies) do
table.insert(ffs,string.format("%.3f",_f))
end
local freqs = table.concat(ffs, ",")
local modus = table.concat(Modulations or self.modulations, ",")
-- Replace modulation
modus=modus:gsub("0", "AM")
modus=modus:gsub("1", "FM")
local coal=Coalition or self.coalition or coalition.side.RED
local secs = Seconds or 30
local TransmissionP = {}
local ProviderP = {}
TransmissionP.transmitter = "srs"
TransmissionP.freqs = freqs
TransmissionP.modulations = modus
TransmissionP.coalition = coal or self.coalition -- number 0=spectator, 1=red, 2=blue
TransmissionP.name = Label or self.Label -- string client name shown in SRS
TransmissionP.point = Vec3 -- DCS Vec3 (optional) — initial transmitter position
TransmissionP.encrypt = Encrypt -- bool
TransmissionP.encKey = EncKey -- number 0255
ProviderP.noiseType = Noisetype or "white"
ProviderP.volume = Volume or 1
local ID = HoundTTS.TransmitNoise(TransmissionP, ProviderP)
self.NoiseID = ID
self:ScheduleOnce(secs,MSRS.RadioJammerOff,self,ID)
return ID
end
--- Switch off a running Jammer using its ID.
-- @param #MSRS self
-- @param #number ID
function MSRS:RadioJammerOff(ID)
return HoundTTS.KillSession(ID or self.NoiseID)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Config File
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+2 -2
View File
@@ -523,7 +523,7 @@ ENUMS.ReportingName =
Tornado = "Tornado",
-- Transport / Bomber / Others
Atlas = "A400",
Lancer = "B1-B",
Lancer = "B-1B",
Stratofortress = "B-52H",
Herc = "C-130",
Hercules = "C-130J-30",
@@ -568,7 +568,7 @@ ENUMS.ReportingName =
Apache = "AH-64",
Chinook = "CH-47",
Sea_Stallion = "CH-53",
Kiowa = "OH-58",
Kiowa = "OH58D",
Seahawk = "SH-60",
Blackhawk = "UH-60",
Sea_King = "S-61",
@@ -2378,6 +2378,11 @@ function UTILS.IsLoadingDoorOpen( unit_name )
BASE:T(unit_name .. " front door(s) are open or doors removed")
return true
end
if type_name == "Ka-50_3" and unit:getDrawArgumentValue(38) == 1 then
BASE:T(unit_name .. " cockpit door is open")
return true
end
if type_name=="C-130J-30" and (unit:getDrawArgumentValue(86)==1)then
BASE:T(unit_name.." rear doors are open")
+392 -72
View File
@@ -23,6 +23,7 @@
-- @field #boolean isAirdrome Airbase is an airdrome.
-- @field #boolean isHelipad Airbase is a helipad.
-- @field #boolean isShip Airbase is a ship.
-- @field #boolean isZell Airbase is a ZELL Booster.
-- @field #table parking Parking spot data.
-- @field #table parkingByID Parking spot data table with ID as key.
-- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning.
@@ -458,115 +459,420 @@ AIRBASE.TheChannel = {
--- Airbases of the Syria map
--
-- * `AIRBASE.Syria.Abu_al_Duhur` Abu al-Duhur
-- * `AIRBASE.Syria.Adana_Sakirpasa` Adana Sakirpasa
-- * `AIRBASE.Syria.Akrotiri` Akrotiri
-- * `AIRBASE.Syria.Al_Qusayr` Al Qusayr
-- * `AIRBASE.Syria.Al_Dumayr` Al-Dumayr
-- * `AIRBASE.Syria.Aleppo` Aleppo
-- * `AIRBASE.Syria.An_Nasiriyah` An Nasiriyah
-- * `AIRBASE.Syria.At_Tanf` At Tanf
-- * `AIRBASE.Syria.Bassel_Al_Assad` Bassel Al-Assad
-- * `AIRBASE.Syria.Beirut_Rafic_Hariri` Beirut-Rafic Hariri
-- * `AIRBASE.Syria.Ben_Gurion` Ben Gurion
-- * `AIRBASE.Syria.Damascus` Damascus
-- * `AIRBASE.Syria.Deir_ez_Zor` Deir ez-Zor
-- * `AIRBASE.Syria.Ercan` Ercan
-- * `AIRBASE.Syria.Eyn_Shemer` Eyn Shemer
-- * `AIRBASE.Syria.Gaziantep` Gaziantep
-- * `AIRBASE.Syria.Gazipasa` Gazipasa
-- * `AIRBASE.Syria.Gecitkale` Gecitkale
-- * `AIRBASE.Syria.H` H
-- * `AIRBASE.Syria.H3` H3
-- * `AIRBASE.Syria.H3_Northwest` H3 Northwest
-- * `AIRBASE.Syria.H3_Southwest` H3 Southwest
-- * `AIRBASE.Syria.H4` H4
-- * `AIRBASE.Syria.Haifa` Haifa
-- * `AIRBASE.Syria.Hama` Hama
-- * `AIRBASE.Syria.Hatay` Hatay
-- * `AIRBASE.Syria.Hatzor` Hatzor
-- * `AIRBASE.Syria.Herzliya` Herzliya
-- * `AIRBASE.Syria.Incirlik` Incirlik
-- * `AIRBASE.Syria.Jirah` Jirah
-- * `AIRBASE.Syria.Khalkhalah` Khalkhalah
-- * `AIRBASE.Syria.Kharab_Ishk` Kharab Ishk
-- * `AIRBASE.Syria.King_Abdullah_II` King Abdullah II
-- * `AIRBASE.Syria.King_Hussein_Air_College` King Hussein Air College
-- * `AIRBASE.Syria.Kingsfield` Kingsfield
-- * `AIRBASE.Syria.Kiryat_Shmona` Kiryat Shmona
-- * `AIRBASE.Syria.Kuweires` Kuweires
-- * `AIRBASE.Syria.Lakatamia` Lakatamia
-- * `AIRBASE.Syria.Larnaca` Larnaca
-- * `AIRBASE.Syria.Marj_Ruhayyil` Marj Ruhayyil
-- * `AIRBASE.Syria.Marj_as_Sultan_North` Marj as Sultan North
-- * `AIRBASE.Syria.Marj_as_Sultan_South` Marj as Sultan South
-- * `AIRBASE.Syria.Marka` Marka
-- * `AIRBASE.Syria.Megiddo` Megiddo
-- * `AIRBASE.Syria.Mezzeh` Mezzeh
-- * `AIRBASE.Syria.Minakh` Minakh
-- * `AIRBASE.Syria.Muwaffaq_Salti` Muwaffaq Salti
-- * `AIRBASE.Syria.Naqoura` Naqoura
-- * `AIRBASE.Syria.Nicosia` Nicosia
-- * `AIRBASE.Syria.Palmachim` Palmachim
-- * `AIRBASE.Syria.Palmyra` Palmyra
-- * `AIRBASE.Syria.Paphos` Paphos
-- * `AIRBASE.Syria.Pinarbashi` Pinarbashi
-- * `AIRBASE.Syria.Prince_Hassan` Prince Hassan
-- * `AIRBASE.Syria.Qabr_as_Sitt` Qabr as Sitt
-- * `AIRBASE.Syria.Ramat_David` Ramat David
-- * `AIRBASE.Syria.Rayak` Rayak
-- * `AIRBASE.Syria.Rene_Mouawad` Rene Mouawad
-- * `AIRBASE.Syria.Rosh_Pina` Rosh Pina
-- * `AIRBASE.Syria.Ruwayshid` Ruwayshid
-- * `AIRBASE.Syria.Sanliurfa` Sanliurfa
-- * `AIRBASE.Syria.Sayqal` Sayqal
-- * `AIRBASE.Syria.Shayrat` Shayrat
-- * `AIRBASE.Syria.Tabqa` Tabqa
-- * `AIRBASE.Syria.Taftanaz` Taftanaz
-- * `AIRBASE.Syria.Tal_Siman` Tal Siman
-- * `AIRBASE.Syria.Tel_Nof` Tel Nof
-- * `AIRBASE.Syria.Tha_lah` Tha'lah
-- * `AIRBASE.Syria.Tiyas` Tiyas
-- * `AIRBASE.Syria.Wujah_Al_Hajar` Wujah Al Hajar
-- * AIRBASE.Syria.Abu_al_Duhur
-- * AIRBASE.Syria.Adana_Sakirpasa
-- * AIRBASE.Syria.Adiyaman
-- * AIRBASE.Syria.Akrotiri
-- * AIRBASE.Syria.Al_Dumayr
-- * AIRBASE.Syria.Al_Qusayr
-- * AIRBASE.Syria.Aleppo
-- * AIRBASE.Syria.An_Nasiriyah
-- * AIRBASE.Syria.At_Tanf
-- * AIRBASE.Syria.Bassel_Al_Assad
-- * AIRBASE.Syria.Beirut_Rafic_Hariri
-- * AIRBASE.Syria.Ben_Gurion
-- * AIRBASE.Syria.Chukurova
-- * AIRBASE.Syria.Damascus
-- * AIRBASE.Syria.Deir_ez_Zor
-- * AIRBASE.Syria.Diyarbakir
-- * AIRBASE.Syria.Ercan
-- * AIRBASE.Syria.Eyn_Shemer
-- * AIRBASE.Syria.Gaziantep
-- * AIRBASE.Syria.Gazipasa
-- * AIRBASE.Syria.Gecitkale
-- * AIRBASE.Syria.Gulechoba
-- * AIRBASE.Syria.H3
-- * AIRBASE.Syria.H3_Northwest
-- * AIRBASE.Syria.H3_Southwest
-- * AIRBASE.Syria.H4
-- * AIRBASE.Syria.H4_Emergency
-- * AIRBASE.Syria.HC01
-- * AIRBASE.Syria.HC02
-- * AIRBASE.Syria.HC03
-- * AIRBASE.Syria.HC04
-- * AIRBASE.Syria.HC05
-- * AIRBASE.Syria.HC06
-- * AIRBASE.Syria.HI01
-- * AIRBASE.Syria.HI02
-- * AIRBASE.Syria.HI03
-- * AIRBASE.Syria.HI05
-- * AIRBASE.Syria.HI06
-- * AIRBASE.Syria.HI07
-- * AIRBASE.Syria.HI08
-- * AIRBASE.Syria.HI09
-- * AIRBASE.Syria.HI11
-- * AIRBASE.Syria.HI12
-- * AIRBASE.Syria.HI13
-- * AIRBASE.Syria.HI14
-- * AIRBASE.Syria.HI15
-- * AIRBASE.Syria.HI16
-- * AIRBASE.Syria.HI17
-- * AIRBASE.Syria.HI18
-- * AIRBASE.Syria.HI20
-- * AIRBASE.Syria.HI21
-- * AIRBASE.Syria.HI22
-- * AIRBASE.Syria.HI23
-- * AIRBASE.Syria.HI24
-- * AIRBASE.Syria.HI25
-- * AIRBASE.Syria.HI26
-- * AIRBASE.Syria.HJ01
-- * AIRBASE.Syria.HJ02
-- * AIRBASE.Syria.HJ03
-- * AIRBASE.Syria.HJ04
-- * AIRBASE.Syria.HL01
-- * AIRBASE.Syria.HL02
-- * AIRBASE.Syria.HL03
-- * AIRBASE.Syria.HL04
-- * AIRBASE.Syria.HL05
-- * AIRBASE.Syria.HL06
-- * AIRBASE.Syria.HL07
-- * AIRBASE.Syria.HL08
-- * AIRBASE.Syria.HL09
-- * AIRBASE.Syria.HL10
-- * AIRBASE.Syria.HL11
-- * AIRBASE.Syria.HL12
-- * AIRBASE.Syria.HL13
-- * AIRBASE.Syria.HMed00
-- * AIRBASE.Syria.HMed01
-- * AIRBASE.Syria.HMed02
-- * AIRBASE.Syria.HMed03
-- * AIRBASE.Syria.HMed04
-- * AIRBASE.Syria.HMed05
-- * AIRBASE.Syria.HMed06
-- * AIRBASE.Syria.HMed07
-- * AIRBASE.Syria.HMed08
-- * AIRBASE.Syria.HMed09
-- * AIRBASE.Syria.HMed10
-- * AIRBASE.Syria.HMed11
-- * AIRBASE.Syria.HMed12
-- * AIRBASE.Syria.HMed13
-- * AIRBASE.Syria.HMed14
-- * AIRBASE.Syria.HMed15
-- * AIRBASE.Syria.HMed16
-- * AIRBASE.Syria.HMed17
-- * AIRBASE.Syria.HMed18
-- * AIRBASE.Syria.HMed19
-- * AIRBASE.Syria.HMed20
-- * AIRBASE.Syria.HMed21
-- * AIRBASE.Syria.HMed22
-- * AIRBASE.Syria.HMed23
-- * AIRBASE.Syria.HMed24
-- * AIRBASE.Syria.HMed25
-- * AIRBASE.Syria.HMed26
-- * AIRBASE.Syria.HMed27
-- * AIRBASE.Syria.HMed28
-- * AIRBASE.Syria.HMed29
-- * AIRBASE.Syria.HMed30
-- * AIRBASE.Syria.HOil01
-- * AIRBASE.Syria.HOil02
-- * AIRBASE.Syria.HOil03
-- * AIRBASE.Syria.HOil04
-- * AIRBASE.Syria.HOil05
-- * AIRBASE.Syria.HOil06
-- * AIRBASE.Syria.HS02
-- * AIRBASE.Syria.HS03
-- * AIRBASE.Syria.HS04
-- * AIRBASE.Syria.HS05
-- * AIRBASE.Syria.HS06
-- * AIRBASE.Syria.HS07
-- * AIRBASE.Syria.HS08
-- * AIRBASE.Syria.HS09
-- * AIRBASE.Syria.HS10
-- * AIRBASE.Syria.HS11
-- * AIRBASE.Syria.HS12
-- * AIRBASE.Syria.HS13
-- * AIRBASE.Syria.HS14
-- * AIRBASE.Syria.HS15
-- * AIRBASE.Syria.HS16
-- * AIRBASE.Syria.HS17
-- * AIRBASE.Syria.HS18
-- * AIRBASE.Syria.HS19
-- * AIRBASE.Syria.HS20
-- * AIRBASE.Syria.HS21
-- * AIRBASE.Syria.HS22
-- * AIRBASE.Syria.HS23
-- * AIRBASE.Syria.HS24
-- * AIRBASE.Syria.HS25
-- * AIRBASE.Syria.HS26
-- * AIRBASE.Syria.HS27
-- * AIRBASE.Syria.HS28
-- * AIRBASE.Syria.HS29
-- * AIRBASE.Syria.HS30
-- * AIRBASE.Syria.HS31
-- * AIRBASE.Syria.HS32
-- * AIRBASE.Syria.HS33
-- * AIRBASE.Syria.HS34
-- * AIRBASE.Syria.HS35
-- * AIRBASE.Syria.HS36
-- * AIRBASE.Syria.HS37
-- * AIRBASE.Syria.HS38
-- * AIRBASE.Syria.HS39
-- * AIRBASE.Syria.HS40
-- * AIRBASE.Syria.HS41
-- * AIRBASE.Syria.HS42
-- * AIRBASE.Syria.HStad01
-- * AIRBASE.Syria.HStad02
-- * AIRBASE.Syria.HStad03
-- * AIRBASE.Syria.HStad04
-- * AIRBASE.Syria.HStad05
-- * AIRBASE.Syria.HStad06
-- * AIRBASE.Syria.HT01
-- * AIRBASE.Syria.HT02
-- * AIRBASE.Syria.H_med_orig_01
-- * AIRBASE.Syria.H_med_orig_02
-- * AIRBASE.Syria.H_med_orig_03
-- * AIRBASE.Syria.H_med_orig_04
-- * AIRBASE.Syria.H_med_orig_05
-- * AIRBASE.Syria.H_med_orig_06
-- * AIRBASE.Syria.H_med_orig_07
-- * AIRBASE.Syria.H_med_orig_08
-- * AIRBASE.Syria.H_med_orig_09
-- * AIRBASE.Syria.Haifa
-- * AIRBASE.Syria.Hama
-- * AIRBASE.Syria.Hatay
-- * AIRBASE.Syria.Hatzerim
-- * AIRBASE.Syria.Hatzor
-- * AIRBASE.Syria.Herzliya
-- * AIRBASE.Syria.Incirlik
-- * AIRBASE.Syria.Jirah
-- * AIRBASE.Syria.Kahramanmaras
-- * AIRBASE.Syria.Kedem
-- * AIRBASE.Syria.Khalkhalah
-- * AIRBASE.Syria.Kharab_Ishk
-- * AIRBASE.Syria.King_Abdullah_II
-- * AIRBASE.Syria.King_Hussein_Air_College
-- * AIRBASE.Syria.Kingsfield
-- * AIRBASE.Syria.Kiryat_Shmona
-- * AIRBASE.Syria.Konya
-- * AIRBASE.Syria.Kuweires
-- * AIRBASE.Syria.Lakatamia
-- * AIRBASE.Syria.Larnaca
-- * AIRBASE.Syria.Marj_Ruhayyil
-- * AIRBASE.Syria.Marj_as_Sultan_North
-- * AIRBASE.Syria.Marj_as_Sultan_South
-- * AIRBASE.Syria.Marka
-- * AIRBASE.Syria.Megiddo
-- * AIRBASE.Syria.Mezzeh
-- * AIRBASE.Syria.Minakh
-- * AIRBASE.Syria.Muwaffaq_Salti
-- * AIRBASE.Syria.Naqoura
-- * AIRBASE.Syria.Nevatim
-- * AIRBASE.Syria.Nicosia
-- * AIRBASE.Syria.Palmachim
-- * AIRBASE.Syria.Palmyra
-- * AIRBASE.Syria.Paphos
-- * AIRBASE.Syria.Pinarbashi
-- * AIRBASE.Syria.Prince_Hassan
-- * AIRBASE.Syria.Qabr_as_Sitt
-- * AIRBASE.Syria.Ramat_David
-- * AIRBASE.Syria.Rayak
-- * AIRBASE.Syria.Rene_Mouawad
-- * AIRBASE.Syria.Rosh_Pina
-- * AIRBASE.Syria.Ruwayshid
-- * AIRBASE.Syria.Sanliurfa
-- * AIRBASE.Syria.Sanliurfa_Heliport
-- * AIRBASE.Syria.Sayqal
-- * AIRBASE.Syria.Shayrat
-- * AIRBASE.Syria.T2
-- * AIRBASE.Syria.T3
-- * AIRBASE.Syria.Tabqa
-- * AIRBASE.Syria.Taftanaz
-- * AIRBASE.Syria.Tal_Siman
-- * AIRBASE.Syria.Tel_Nof
-- * AIRBASE.Syria.Teyman
-- * AIRBASE.Syria.Tha_lah
-- * AIRBASE.Syria.Tiyas
-- * AIRBASE.Syria.Wujah_Al_Hajar
-- * AIRBASE.Syria.Zarqa
--
-- @field Syria
AIRBASE.Syria = {
["Abu_al_Duhur"] = "Abu al-Duhur",
["Adana_Sakirpasa"] = "Adana Sakirpasa",
["Adiyaman"] = "Adiyaman",
["Akrotiri"] = "Akrotiri",
["Al_Qusayr"] = "Al Qusayr",
["Al_Dumayr"] = "Al-Dumayr",
["Al_Qusayr"] = "Al Qusayr",
["Aleppo"] = "Aleppo",
["An_Nasiriyah"] = "An Nasiriyah",
["At_Tanf"] = "At Tanf",
["Bassel_Al_Assad"] = "Bassel Al-Assad",
["Beirut_Rafic_Hariri"] = "Beirut-Rafic Hariri",
["Ben_Gurion"] = "Ben Gurion",
["Chukurova"] = "Chukurova",
["Damascus"] = "Damascus",
["Deir_ez_Zor"] = "Deir ez-Zor",
["Diyarbakir"] = "Diyarbakir",
["Ercan"] = "Ercan",
["Eyn_Shemer"] = "Eyn Shemer",
["Gaziantep"] = "Gaziantep",
["Gazipasa"] = "Gazipasa",
["Gecitkale"] = "Gecitkale",
["H"] = "H",
["Gulechoba"] = "Gulechoba",
["H3"] = "H3",
["H3_Northwest"] = "H3 Northwest",
["H3_Southwest"] = "H3 Southwest",
["H4"] = "H4",
["H4_Emergency"] = "H4 Emergency",
["HC01"] = "HC01",
["HC02"] = "HC02",
["HC03"] = "HC03",
["HC04"] = "HC04",
["HC05"] = "HC05",
["HC06"] = "HC06",
["HI01"] = "HI01",
["HI02"] = "HI02",
["HI03"] = "HI03",
["HI05"] = "HI05",
["HI06"] = "HI06",
["HI07"] = "HI07",
["HI08"] = "HI08",
["HI09"] = "HI09",
["HI11"] = "HI11",
["HI12"] = "HI12",
["HI13"] = "HI13",
["HI14"] = "HI14",
["HI15"] = "HI15",
["HI16"] = "HI16",
["HI17"] = "HI17",
["HI18"] = "HI18",
["HI20"] = "HI20",
["HI21"] = "HI21",
["HI22"] = "HI22",
["HI23"] = "HI23",
["HI24"] = "HI24",
["HI25"] = "HI25",
["HI26"] = "HI26",
["HJ01"] = "HJ01",
["HJ02"] = "HJ02",
["HJ03"] = "HJ03",
["HJ04"] = "HJ04",
["HL01"] = "HL01",
["HL02"] = "HL02",
["HL03"] = "HL03",
["HL04"] = "HL04",
["HL05"] = "HL05",
["HL06"] = "HL06",
["HL07"] = "HL07",
["HL08"] = "HL08",
["HL09"] = "HL09",
["HL10"] = "HL10",
["HL11"] = "HL11",
["HL12"] = "HL12",
["HL13"] = "HL13",
["HMed00"] = "HMed00",
["HMed01"] = "HMed01",
["HMed02"] = "HMed02",
["HMed03"] = "HMed03",
["HMed04"] = "HMed04",
["HMed05"] = "HMed05",
["HMed06"] = "HMed06",
["HMed07"] = "HMed07",
["HMed08"] = "HMed08",
["HMed09"] = "HMed09",
["HMed10"] = "HMed10",
["HMed11"] = "HMed11",
["HMed12"] = "HMed12",
["HMed13"] = "HMed13",
["HMed14"] = "HMed14",
["HMed15"] = "HMed15",
["HMed16"] = "HMed16",
["HMed17"] = "HMed17",
["HMed18"] = "HMed18",
["HMed19"] = "HMed19",
["HMed20"] = "HMed20",
["HMed21"] = "HMed21",
["HMed22"] = "HMed22",
["HMed23"] = "HMed23",
["HMed24"] = "HMed24",
["HMed25"] = "HMed25",
["HMed26"] = "HMed26",
["HMed27"] = "HMed27",
["HMed28"] = "HMed28",
["HMed29"] = "HMed29",
["HMed30"] = "HMed30",
["HOil01"] = "HOil01",
["HOil02"] = "HOil02",
["HOil03"] = "HOil03",
["HOil04"] = "HOil04",
["HOil05"] = "HOil05",
["HOil06"] = "HOil06",
["HS02"] = "HS02",
["HS03"] = "HS03",
["HS04"] = "HS04",
["HS05"] = "HS05",
["HS06"] = "HS06",
["HS07"] = "HS07",
["HS08"] = "HS08",
["HS09"] = "HS09",
["HS10"] = "HS10",
["HS11"] = "HS11",
["HS12"] = "HS12",
["HS13"] = "HS13",
["HS14"] = "HS14",
["HS15"] = "HS15",
["HS16"] = "HS16",
["HS17"] = "HS17",
["HS18"] = "HS18",
["HS19"] = "HS19",
["HS20"] = "HS20",
["HS21"] = "HS21",
["HS22"] = "HS22",
["HS23"] = "HS23",
["HS24"] = "HS24",
["HS25"] = "HS25",
["HS26"] = "HS26",
["HS27"] = "HS27",
["HS28"] = "HS28",
["HS29"] = "HS29",
["HS30"] = "HS30",
["HS31"] = "HS31",
["HS32"] = "HS32",
["HS33"] = "HS33",
["HS34"] = "HS34",
["HS35"] = "HS35",
["HS36"] = "HS36",
["HS37"] = "HS37",
["HS38"] = "HS38",
["HS39"] = "HS39",
["HS40"] = "HS40",
["HS41"] = "HS41",
["HS42"] = "HS42",
["HStad01"] = "HStad01",
["HStad02"] = "HStad02",
["HStad03"] = "HStad03",
["HStad04"] = "HStad04",
["HStad05"] = "HStad05",
["HStad06"] = "HStad06",
["HT01"] = "HT01",
["HT02"] = "HT02",
["H_med_orig_01"] = "H_med_orig_01",
["H_med_orig_02"] = "H_med_orig_02",
["H_med_orig_03"] = "H_med_orig_03",
["H_med_orig_04"] = "H_med_orig_04",
["H_med_orig_05"] = "H_med_orig_05",
["H_med_orig_06"] = "H_med_orig_06",
["H_med_orig_07"] = "H_med_orig_07",
["H_med_orig_08"] = "H_med_orig_08",
["H_med_orig_09"] = "H_med_orig_09",
["Haifa"] = "Haifa",
["Hama"] = "Hama",
["Hatay"] = "Hatay",
["Hatzerim"] = "Hatzerim",
["Hatzor"] = "Hatzor",
["Herzliya"] = "Herzliya",
["Incirlik"] = "Incirlik",
["Jirah"] = "Jirah",
["Kahramanmaras"] = "Kahramanmaras",
["Kedem"] = "Kedem",
["Khalkhalah"] = "Khalkhalah",
["Kharab_Ishk"] = "Kharab Ishk",
["King_Abdullah_II"] = "King Abdullah II",
["King_Hussein_Air_College"] = "King Hussein Air College",
["Kingsfield"] = "Kingsfield",
["Kiryat_Shmona"] = "Kiryat Shmona",
["Konya"] = "Konya",
["Kuweires"] = "Kuweires",
["Lakatamia"] = "Lakatamia",
["Larnaca"] = "Larnaca",
@@ -579,6 +885,7 @@ AIRBASE.Syria = {
["Minakh"] = "Minakh",
["Muwaffaq_Salti"] = "Muwaffaq Salti",
["Naqoura"] = "Naqoura",
["Nevatim"] = "Nevatim",
["Nicosia"] = "Nicosia",
["Palmachim"] = "Palmachim",
["Palmyra"] = "Palmyra",
@@ -592,15 +899,20 @@ AIRBASE.Syria = {
["Rosh_Pina"] = "Rosh Pina",
["Ruwayshid"] = "Ruwayshid",
["Sanliurfa"] = "Sanliurfa",
["Sanliurfa_Heliport"] = "Sanliurfa Heliport",
["Sayqal"] = "Sayqal",
["Shayrat"] = "Shayrat",
["T2"] = "T2",
["T3"] = "T3",
["Tabqa"] = "Tabqa",
["Taftanaz"] = "Taftanaz",
["Tal_Siman"] = "Tal Siman",
["Tel_Nof"] = "Tel Nof",
["Teyman"] = "Teyman",
["Tha_lah"] = "Tha'lah",
["Tiyas"] = "Tiyas",
["Wujah_Al_Hajar"] = "Wujah Al Hajar",
["Zarqa"] = "Zarqa",
}
--- Airbases of the Mariana Islands map
@@ -1673,6 +1985,7 @@ elseif self.category==Airbase.Category.SHIP then
self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName)
end
if self:GetTypeName() == "Zell" then self.isZell = true end
else
self:E("ERROR: Unknown airbase category!")
end
@@ -2122,6 +2435,13 @@ function AIRBASE:IsShip()
return self.isShip
end
--- Check if airbase is a ZELL booster.
-- @param #AIRBASE self
-- @return #boolean If true, airbase is a ZELL booster.
function AIRBASE:IsZell()
return self.isZell
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Parking
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+1
View File
@@ -112,6 +112,7 @@ Ops/OpsZone.lua
Ops/ArmyGroup.lua
Ops/OpsTransport.lua
Ops/Target.lua
Ops/TARS.lua
Sound/UserSound.lua
Sound/SoundOutput.lua