Compare commits

..

98 Commits

Author SHA1 Message Date
Frank e8f5214e0b Merge branch 'master-ng' into FF/Ops 2026-06-21 18:21:33 +02:00
Applevangelist b6ea6606fc #SCHEDULEDISPATCHER - Fixing a memory leak. Thanks to @Shazzer1951 2026-06-21 12:51:50 +02:00
Frank e4c7e809cb TARGET v0.8.0
- Added Element functions
- Improved handling of delayed dead events
2026-06-18 22:47:11 +02:00
Frank aab16dd966 Legion/Cohort Database
- moved to individual classes
2026-06-17 09:00:17 +02:00
Frank 8b356f0913 Merge branch 'master-ng' into FF/Ops 2026-06-15 20:48:57 +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
Frank c3086b0265 Update Database.lua
- fixed bug
2026-06-05 22:54:42 +02:00
Frank 599a2ba6fb Database
- Added COHORTs and LEGIONs to database
2026-06-05 22:50:52 +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
32 changed files with 6387 additions and 595 deletions
+1 -1
View File
@@ -855,7 +855,7 @@ end
-- @param DCS#Time EventTime The time stamp of the event. -- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event. -- @param DCS#Object Initiator The initiating object of the event.
function BASE:CreateEventTakeoff( EventTime, Initiator ) function BASE:CreateEventTakeoff( EventTime, Initiator )
self:F( { EventTime, Initiator } ) --self:F( { EventTime, Initiator } )
local Event = { local Event = {
id = world.event.S_EVENT_TAKEOFF, id = world.event.S_EVENT_TAKEOFF,
+44 -7
View File
@@ -92,6 +92,8 @@ DATABASE = {
ZONES_GOAL = {}, ZONES_GOAL = {},
WAREHOUSES = {}, WAREHOUSES = {},
FLIGHTGROUPS = {}, FLIGHTGROUPS = {},
COHORTS={},
LEGIONS={},
FLIGHTCONTROLS = {}, FLIGHTCONTROLS = {},
OPSZONES = {}, OPSZONES = {},
PATHLINES = {}, PATHLINES = {},
@@ -1380,12 +1382,12 @@ function DATABASE:_RegisterDynamicGroup(Groupname)
local DCSUnitName = DCSUnit:getName() local DCSUnitName = DCSUnit:getName()
-- Add unit. -- 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 ) self:AddUnit( tostring(DCSUnitName), true )
end end
else else
self:E({"Group does not exist: ", DCSGroup}) self:T({"Group does not exist: ", DCSGroup})
end end
return self return self
end end
@@ -1488,27 +1490,28 @@ end
-- @return #DATABASE self -- @return #DATABASE self
function DATABASE:_RegisterAirbase(airbase) function DATABASE:_RegisterAirbase(airbase)
local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false --local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false
local countHSyria = 0 --local countHSyria = 0
if airbase then if airbase then
-- Get the airbase name. -- Get the airbase name.
local DCSAirbaseName = airbase:getName() 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 if IsSyria and DCSAirbaseName == "H" and countHSyria > 0 then
--[[ --
local p = airbase:getPosition().p local p = airbase:getPosition().p
local mgrs = COORDINATE:New(p.x,p.z,p.y):ToStringMGRS() local mgrs = COORDINATE:New(p.x,p.z,p.y):ToStringMGRS()
self:I("Airbase on Syria map named H @ "..mgrs) self:I("Airbase on Syria map named H @ "..mgrs)
countHSyria = countHSyria + 1 countHSyria = countHSyria + 1
if countHSyria > 1 then return self end if countHSyria > 1 then return self end
--]] --
return self return self
elseif IsSyria and DCSAirbaseName == "H" and countHSyria == 0 then elseif IsSyria and DCSAirbaseName == "H" and countHSyria == 0 then
countHSyria = countHSyria + 1 countHSyria = countHSyria + 1
end end
--]]
-- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now. -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now.
local airbaseID=airbase:getID() local airbaseID=airbase:getID()
@@ -2086,6 +2089,40 @@ function DATABASE:FindOpsGroupFromUnit(unitname)
end end
end end
--
--- Add an OPS COHORT (SQUADRON, PLATOON, FLOTILLA) to the data base.
-- @param #DATABASE self
-- @param Ops.Cohort#COHORT cohort The cohort added to the DB.
function DATABASE:AddCohort(cohort)
self.COHORTS[cohort.name]=cohort
end
--- Find an OPS COHORT (SQUADRON, PLATOON, FLOTILLA) in the data base.
-- @param #DATABASE self
-- @param #string cohortname Name of the cohort.
-- @return Ops.Cohort#COHORT Cohort object.
function DATABASE:FindCohort(cohortname)
return self.COHORTS[cohortname]
end
--- Add an OPS LEGION (AIRWING, BRIGADE, FLEET) to the data base.
-- @param #DATABASE self
-- @param Ops.Legion#LEGION legion The legion added to the DB.
function DATABASE:AddLegion(legion)
self.LEGIONS[legion.alias]=legion
end
--- Find an OPS LEGION (AIRWING, BRIGADE, FLEET) in the data base.
-- @param #DATABASE self
-- @param #string legionname Name of the legion.
-- @return Ops.Legion#LEGION Legion object.
function DATABASE:FindLegion(legionname)
return self.LEGIONS[legionname]
end
--
--- Add a flight control to the data base. --- Add a flight control to the data base.
-- @param #DATABASE self -- @param #DATABASE self
-- @param OPS.FlightControl#FLIGHTCONTROL flightcontrol -- @param OPS.FlightControl#FLIGHTCONTROL flightcontrol
+6 -1
View File
@@ -1453,7 +1453,12 @@ function EVENT:onEvent( Event )
-- Weapon. -- Weapon.
if Event.weapon and type(Event.weapon) == "table" and Event.weapon.isExist and Event.weapon:isExist() then if Event.weapon and type(Event.weapon) == "table" and Event.weapon.isExist and Event.weapon:isExist() then
Event.Weapon = Event.weapon 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.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 and Event.Weapon:getPlayerName()
--Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName() --Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
+14 -3
View File
@@ -869,10 +869,21 @@ do
local MenuTable = {} local MenuTable = {}
for MenuText, Menu in pairs( self.Menus or {} ) do for MenuText, Menu in pairs( self.Menus or {} ) do
local tag = Menu.MenuTag or math.random(1,10000) local tag = Menu.MenuTag or math.random(1,10000)
MenuTable[#MenuTable+1] = {Tag=tag, Enty=Menu} MenuTable[#MenuTable+1] = {Tag=tag, Entry=Menu}
end end
table.sort(MenuTable, function (k1, k2) return k1.tag < k2.tag end ) local function SortTable(k1,k2)
for _, Menu in pairs( MenuTable ) do 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() Menu.Entry:Refresh()
end end
end end
@@ -246,11 +246,11 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
-- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) -- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
return ScheduleTime -- returns the next time the function needs to be called. return ScheduleTime -- returns the next time the function needs to be called.
else else
self:Stop( Scheduler, CallID ) self:_Reclaim( Scheduler, CallID )
end end
else else
self:Stop( Scheduler, CallID ) self:_Reclaim( Scheduler, CallID )
end end
else else
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" ) self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
@@ -375,3 +375,13 @@ function SCHEDULEDISPATCHER:NoTrace( Scheduler )
Scheduler.ShowTrace = false Scheduler.ShowTrace = false
end 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 #table List Unused table.
-- @field Core.Scheduler#SCHEDULER CallScheduler -- @field Core.Scheduler#SCHEDULER CallScheduler
-- @field #SET_BASE.Filters Filter Filters -- @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 -- @extends Core.Base#BASE
--- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. --- 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, filterNoRegex=false,
filterReplaceDash=true,
} }
--- Filters --- Filters
@@ -256,7 +258,8 @@ do -- SET_BASE
-- @return #boolean Returns `true`, if the pattern is contained in the name and false otherwise. -- @return #boolean Returns `true`, if the pattern is contained in the name and false otherwise.
function SET_BASE:_SearchPattern(Name, Pattern, NoRegex, ReplaceDash) function SET_BASE:_SearchPattern(Name, Pattern, NoRegex, ReplaceDash)
NoRegex=NoRegex or self.filterNoRegex 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 -- 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. -- 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("-", "%%-") Pattern=Pattern:gsub("-", "%%-")
@@ -265,6 +268,16 @@ do -- SET_BASE
return contain return contain
end 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. --- Gets the Set.
-- @param #SET_BASE self -- @param #SET_BASE self
@@ -1198,7 +1211,21 @@ do
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @return #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 end
--- Get a *new* set table that only contains alive groups. --- Get a *new* set table that only contains alive groups.
@@ -2116,7 +2143,7 @@ do
if self.Filter.GroupPrefixes and MGroupInclude then if self.Filter.GroupPrefixes and MGroupInclude then
local MGroupPrefix = false local MGroupPrefix = false
for GroupPrefixId, GroupPrefix in pairs(self.Filter.GroupPrefixes) do 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 MGroupPrefix = true
end end
end end
@@ -2337,6 +2364,26 @@ do -- SET_UNIT
-- @param #SET_UNIT self -- @param #SET_UNIT self
-- @return #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 return self
end end
@@ -3348,7 +3395,7 @@ do -- SET_UNIT
if self.Filter.UnitPrefixes and MUnitInclude then if self.Filter.UnitPrefixes and MUnitInclude then
local MUnitPrefix = false local MUnitPrefix = false
for UnitPrefixId, UnitPrefix in pairs(self.Filter.UnitPrefixes) do 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 MUnitPrefix = true
end end
end end
@@ -3544,7 +3591,27 @@ do -- SET_STATIC
-- Inherits from BASE -- Inherits from BASE
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.STATICS)) -- Core.Set#SET_STATIC 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 return self
end end
@@ -4119,7 +4186,7 @@ do -- SET_STATIC
if self.Filter.StaticPrefixes then if self.Filter.StaticPrefixes then
local MStaticPrefix = false local MStaticPrefix = false
for StaticPrefixId, StaticPrefix in pairs(self.Filter.StaticPrefixes) do 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 MStaticPrefix = true
end end
end end
@@ -4303,7 +4370,27 @@ do -- SET_CLIENT
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.CLIENTS)) -- #SET_CLIENT local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.CLIENTS)) -- #SET_CLIENT
self:FilterActive(false) 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 return self
end end
@@ -4902,7 +4989,7 @@ do -- SET_CLIENT
if self.Filter.ClientPrefixes and MClientInclude then if self.Filter.ClientPrefixes and MClientInclude then
local MClientPrefix = false local MClientPrefix = false
for ClientPrefixId, ClientPrefix in pairs(self.Filter.ClientPrefixes) do 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 MClientPrefix = true
end end
end end
@@ -4926,7 +5013,7 @@ do -- SET_CLIENT
local playername = MClient:GetPlayerName() or "Unknown" local playername = MClient:GetPlayerName() or "Unknown"
--self:T(playername) --self:T(playername)
for _,_Playername in pairs(self.Filter.Playernames) do 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 MClientPlayername = true
end end
end end
@@ -4939,7 +5026,7 @@ do -- SET_CLIENT
local callsign = MClient:GetCallsign() local callsign = MClient:GetCallsign()
--self:I(callsign) --self:I(callsign)
for _,_Callsign in pairs(self.Filter.Callsigns) do 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 MClientCallsigns = true
end end
end end
@@ -5360,7 +5447,7 @@ do -- SET_PLAYER
if self.Filter.ClientPrefixes then if self.Filter.ClientPrefixes then
local MClientPrefix = false local MClientPrefix = false
for ClientPrefixId, ClientPrefix in pairs(self.Filter.ClientPrefixes) do 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 MClientPrefix = true
end end
end end
@@ -5830,7 +5917,19 @@ do -- SET_ZONE
function SET_ZONE:New() function SET_ZONE:New()
-- Inherits from BASE -- Inherits from BASE
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.ZONES)) 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 return self
end end
@@ -6068,7 +6167,7 @@ do -- SET_ZONE
if self.Filter.Prefixes then if self.Filter.Prefixes then
local MZonePrefix = false local MZonePrefix = false
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do 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 MZonePrefix = true
end end
end end
@@ -6577,7 +6676,7 @@ do -- SET_ZONE_GOAL
if self.Filter.Prefixes then if self.Filter.Prefixes then
local MZonePrefix = false local MZonePrefix = false
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do 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 MZonePrefix = true
end end
end end
@@ -6713,7 +6812,19 @@ do -- SET_OPSZONE
-- Inherits from BASE -- Inherits from BASE
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.OPSZONES)) 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 return self
end end
@@ -6938,7 +7049,7 @@ do -- SET_OPSZONE
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do
-- Prefix -- Prefix
if self:_SearchPattern(MZoneName, ZonePrefix, false, true) then if self:_SearchPattern(MZoneName, ZonePrefix, self.filterNoRegex, self.filterReplaceDash) then
MZonePrefix = true MZonePrefix = true
break --Break the loop as we found the prefix. break --Break the loop as we found the prefix.
end end
@@ -7732,7 +7843,7 @@ function SET_OPSGROUP:_EventOnBirth(Event)
local MGroupPrefix = false local MGroupPrefix = false
for GroupPrefixId, GroupPrefix in pairs(self.Filter.GroupPrefixes) do 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 MGroupPrefix = true
end end
end end
@@ -8066,7 +8177,7 @@ do -- SET_SCENERY
if self.Filter.Prefixes then if self.Filter.Prefixes then
local MSceneryPrefix = false local MSceneryPrefix = false
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do 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 MSceneryPrefix = true
end end
end end
@@ -8289,7 +8400,19 @@ do -- SET_DYNAMICCARGO
--- Inherits from BASE --- Inherits from BASE
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.DYNAMICCARGO)) -- Core.Set#SET_DYNAMICCARGO 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 return self
end end
@@ -8337,7 +8460,7 @@ do -- SET_DYNAMICCARGO
if self.Filter.StaticPrefixes then if self.Filter.StaticPrefixes then
local DCargoPrefix = false local DCargoPrefix = false
for StaticPrefixId, StaticPrefix in pairs(self.Filter.StaticPrefixes) do 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 DCargoPrefix = true
end end
end end
@@ -8505,7 +8628,7 @@ do -- SET_DYNAMICCARGO
function SET_DYNAMICCARGO:FilterCurrentOwner(PlayerName) function SET_DYNAMICCARGO:FilterCurrentOwner(PlayerName)
self:FilterFunction( self:FilterFunction(
function(cargo) 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 return true
else else
return false 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. --- 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 #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 #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. -- @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 -- @return #SPAWN self
@@ -1201,6 +1202,9 @@ end
function SPAWN:InitRandomizeZones( SpawnZoneTable, RandomizePositionInZone ) function SPAWN:InitRandomizeZones( SpawnZoneTable, RandomizePositionInZone )
--self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) --self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } )
if not SpawnZoneTable then
return self
end
local temptable = {} local temptable = {}
for _,_temp in pairs(SpawnZoneTable) do for _,_temp in pairs(SpawnZoneTable) do
temptable[#temptable+1] = _temp temptable[#temptable+1] = _temp
@@ -2149,6 +2153,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
local parkingspots = {} local parkingspots = {}
local parkingindex = {} local parkingindex = {}
local spots local spots
local useexplicitspots = false
-- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship.
if spawnonground and not SpawnTemplate.parked then if spawnonground and not SpawnTemplate.parked then
@@ -2178,15 +2183,41 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
local scanscenery = false local scanscenery = false
local verysafe = 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. -- 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. -- These places work procedural and have some kind of build in queue ==> Less effort.
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
spots = SpawnAirbase:GetFreeParkingSpotsTable( 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 else
if ishelo then if ishelo then
if termtype == nil then if termtype == nil then
@@ -2238,7 +2269,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
local _notenough = false local _notenough = false
-- Need to differentiate some cases again. -- Need to differentiate some cases again.
if autoparking then if autoparking and not useexplicitspots then
-- On free spot required in these cases. -- On free spot required in these cases.
if nfree >= 1 then if nfree >= 1 then
@@ -2271,6 +2302,10 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
end end
end end
if useexplicitspots and parkingspots[1] then
PointVec3 = parkingspots[1]
end
-- Not enough spots ==> Prepare airstart. -- Not enough spots ==> Prepare airstart.
if _notenough then if _notenough then
@@ -2296,9 +2331,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
Takeoff = GROUP.Takeoff.Air Takeoff = GROUP.Takeoff.Air
spawnonground = false 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() ) ) 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 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
end end
@@ -2339,7 +2380,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
if spawnonground then if spawnonground then
-- Ships and FARPS seem to have a build in queue. -- 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. -- Spawn on ship. We take only the position of the ship.
SpawnTemplate.units[UnitID].x = PointVec3.x -- TX SpawnTemplate.units[UnitID].x = PointVec3.x -- TX
@@ -2357,7 +2398,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
else 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].x = TX
SpawnTemplate.units[UnitID].y = TY SpawnTemplate.units[UnitID].y = TY
SpawnTemplate.units[UnitID].alt = PointVec3.y SpawnTemplate.units[UnitID].alt = PointVec3.y
@@ -2390,7 +2431,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
if Takeoff == GROUP.Takeoff.Air then if Takeoff == GROUP.Takeoff.Air then
for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do 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! --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
end end
@@ -2582,21 +2623,51 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
local scanscenery = false local scanscenery = false
local verysafe = false local verysafe = false
-- Number of free parking spots at the airbase. -- Use exact parking data when provided, otherwise let helicopters on FARPs
if spawnonship or spawnonfarp or spawnonrunway then -- use the smarter parking search before falling back to the procedural queue path.
-- These places work procedural and have some kind of build in queue ==> Less effort. local useexplicitspots = false
--self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) -- Number of free parking spots at the airbase.
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) if Parkingdata~=nil then
--[[ -- Parking data explicitly set by user as input parameter.
elseif Parkingdata~=nil then nfree = #Parkingdata
-- Parking data explicitly set by user as input parameter. spots = Parkingdata
nfree=#Parkingdata useexplicitspots = true
spots=Parkingdata elseif spawnonfarp and ishelo then
]]
else
if ishelo then
if termtype == nil 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. -- 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 ) ) --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 ) 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 local _notenough = false
-- Need to differentiate some cases again. -- 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. -- On free spot required in these cases.
if nfree >= 1 then if nfree >= 1 then
@@ -1848,7 +1848,7 @@ do -- DETECTION_BASE
return self return self
end 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 self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @return #boolean -- @return #boolean
@@ -1858,7 +1858,7 @@ do -- DETECTION_BASE
end end
--- Lock a detected item. --- Lock a detected item (not in the radar sense, though!).
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @return #DETECTION_BASE -- @return #DETECTION_BASE
@@ -1869,7 +1869,7 @@ do -- DETECTION_BASE
return self return self
end end
--- Unlock a detected item. --- Unlock a detected item (not in the radar sense, though!).
-- @param #DETECTION_BASE self -- @param #DETECTION_BASE self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @return #DETECTION_BASE -- @return #DETECTION_BASE
+862 -12
View File
@@ -116,7 +116,7 @@
-- * TOR M2 -- * TOR M2
-- * C-RAM -- * C-RAM
-- * Silkworm (though strictly speaking this is a surface to ship missile) -- * 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 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 -- * 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 -- * Other Mods: Nike
@@ -135,8 +135,8 @@
-- * SA-3 (with V601P missile, e.g. "Red SAM SA-3 HDS") -- * 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-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-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-12 (e.g. "Red SAM SA-12 HDS")
-- * 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-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") -- * 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. -- 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-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-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-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="9S32ME", ARMCapacity=4}, -- SA-23 Gladiator/Giant
["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-1E", ARMCapacity=4}, -- SA-23B
["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4}, ["S-400"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E", ARMCapacity=4}, -- SA-21 Growler
["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E", ARMCapacity=4},
["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["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" }, ["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" }, ["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 }, ["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 }, ["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 ["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 --- 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-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-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-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"] = { Range=100, Blindspot=6, 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"] = { Range=200, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A82ME", ARMCapacity=14 },
["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 },
["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["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 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 ["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" }, ["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 -- MANTIS System
----------------------------------------------------------------------- -----------------------------------------------------------------------
@@ -908,7 +1087,8 @@ do
local function SwitchSAMOn(Name,Group) local function SwitchSAMOn(Name,Group)
local suppressed = self.SuppressedGroups[Name] or false 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" self.SamStateTracker[Name] = "RED"
if self.UseEmOnOff then if self.UseEmOnOff then
-- DONE: add emissions on/off -- 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), self:T(string.format("MANTIS:SeadAllowSuppression REQUEST | target=%s | weapon=%s | tti=%s | delay=%s",tostring(targetName),
tostring(weaponName),tostring(tti),tostring(delay))) 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 -- LOOK UP ARM CAPACITY FOR THIS SAM
---------------------------------------------------------------- ----------------------------------------------------------------
@@ -2694,6 +2886,664 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea
return self return self
end 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 end
----------------------------------------------------------------------- -----------------------------------------------------------------------
-- MANTIS end -- MANTIS end
+13 -8
View File
@@ -795,15 +795,20 @@ do
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord) local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord1 = tgtgrp1:GetCoordinate() local tgtcoord1 = tgtgrp1:GetCoordinate()
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord) local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord2 = tgtgrp2:GetCoordinate() if tgtgrp2 then
local dist1 = tgtcoord:Get2DDistance(tgtcoord1) local tgtcoord2 = tgtgrp2:GetCoordinate()
local dist2 = tgtcoord:Get2DDistance(tgtcoord2) local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
if dist1 < dist2 then
targetunit = tgtgrp1 if dist1 < dist2 then
targetcat = Object.Category.UNIT targetunit = tgtgrp1
targetcat = Object.Category.UNIT
else
targetunit = tgtgrp2
targetcat = Object.Category.UNIT
end
else else
targetunit = tgtgrp2 targetunit = tgtgrp1
targetcat = Object.Category.UNIT targetcat = Object.Category.UNIT
end end
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()) local text=string.format("Stopping SUPPRESSION for group %s", self.Controllable:GetName())
MESSAGE:New(text, 10):ToAllIf(self.Debug) MESSAGE:New(text, 10):ToAllIf(self.Debug)
sefl:T(self.lid..text) self:T(self.lid..text)
-- Clear all pending schedules -- Clear all pending schedules
self.CallScheduler:Clear() self.CallScheduler:Clear()
@@ -4097,7 +4097,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
-- Get name of template group. -- Get name of template group.
local templategroupname=group:GetName() local templategroupname=group:GetName()
local unit = group:GetUnit(1) 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 Category=group:GetCategory()
local TypeName=group:GetTypeName() or "none" local TypeName=group:GetTypeName() or "none"
local SpeedMax=group:GetSpeedMax() local SpeedMax=group:GetSpeedMax()
@@ -4115,7 +4115,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
for _i,_unit in pairs(group:GetUnits()) do for _i,_unit in pairs(group:GetUnits()) do
local unit=_unit --Wrapper.Unit#UNIT local unit=_unit --Wrapper.Unit#UNIT
local Desc=unit:GetDesc() local Desc=unit:GetDesc()
-- Weight. We sum up all units in the group. -- Weight. We sum up all units in the group.
local unitweight=forceweight or Desc.massEmpty local unitweight=forceweight or Desc.massEmpty
if unitweight then 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/Target.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.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/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/ShapeBase.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Circle.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Circle.lua' )
+45 -28
View File
@@ -290,6 +290,9 @@ function AIRWING:New(warehousename, airwingname)
-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FLIGHTGROUP on mission. -- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FLIGHTGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- Add legion to DB
_DATABASE:AddLegion(self)
return self return self
end end
@@ -541,6 +544,47 @@ function AIRWING:AddPayloadCapability(Payload, MissionTypes, Performance)
return self return self
end end
--- Filter available payloads for a given aircraft type and mission type.
-- @param #AIRWING self
-- @param #string UnitType The type of the unit.
-- @param #string MissionType The mission type.
-- @param #table Payloads Specific payloads only to be considered.
-- @return #AIRWING.Payload Payload table or *nil*.
function AIRWING:_FilterPlayloads(UnitType, MissionType, Payloads)
local function _checkPayloads(payload)
if Payloads then
for _,Payload in pairs(Payloads) do
if Payload.uid==payload.uid then
return true
end
end
else
-- Payload was not specified.
return nil
end
return false
end
-- Pre-selection: filter out only those payloads that are valid for the airframe and mission type and are available.
local payloads={}
for _,_payload in pairs(self.payloads) do
local payload=_payload --#AIRWING.Payload
local specialpayload=_checkPayloads(payload)
local compatible=AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities)
local goforit = specialpayload or (specialpayload==nil and compatible)
if payload.aircrafttype==UnitType and payload.navail>0 and goforit then
table.insert(payloads, payload)
end
end
return payloads
end
--- Fetch a payload from the airwing resources for a given unit and mission type. --- Fetch a payload from the airwing resources for a given unit and mission type.
-- The payload with the highest priority is preferred. -- The payload with the highest priority is preferred.
-- @param #AIRWING self -- @param #AIRWING self
@@ -589,34 +633,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads)
end end
end end
local function _checkPayloads(payload) local payloads=self:_FilterPlayloads(UnitType, MissionType, Payloads)
if Payloads then
for _,Payload in pairs(Payloads) do
if Payload.uid==payload.uid then
return true
end
end
else
-- Payload was not specified.
return nil
end
return false
end
-- Pre-selection: filter out only those payloads that are valid for the airframe and mission type and are available.
local payloads={}
for _,_payload in pairs(self.payloads) do
local payload=_payload --#AIRWING.Payload
local specialpayload=_checkPayloads(payload)
local compatible=AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities)
local goforit = specialpayload or (specialpayload==nil and compatible)
if payload.aircrafttype==UnitType and payload.navail>0 and goforit then
table.insert(payloads, payload)
end
end
-- Debug. -- Debug.
if self.verbose>=4 then if self.verbose>=4 then
+60 -22
View File
@@ -2717,17 +2717,27 @@ function AIRBOSS:DeleteRecoveryWindow( Window, Delay )
self:ScheduleOnce( Delay, self.DeleteRecoveryWindow, self, Window ) self:ScheduleOnce( Delay, self.DeleteRecoveryWindow, self, Window )
else else
for i, _recovery in pairs( self.recoverytimes ) do if not Window then
local recovery = _recovery -- #AIRBOSS.Recovery return
end
if Window and Window.ID == recovery.ID then -- If this window is currently open, stop recovery first. Mark it OVER so the
if Window.OPEN then -- recovery time check cannot re-open it before/while we remove it.
-- Window is currently open. if Window.OPEN then
self:RecoveryStop() Window.OPEN = false
else Window.OVER = true
table.remove( self.recoverytimes, i ) if self:IsRecovering() then
end 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 end
end end
@@ -4078,15 +4088,23 @@ function AIRBOSS:_CheckRecoveryTimes()
if self:IsRecovering() then if self:IsRecovering() then
-- Carrier is already recovering. -- Carrier is already recovering.
state = "in progress" state = "in progress"
else elseif not recovery.OVER then
-- Start recovery. -- 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 ) self:RecoveryStart( recovery.CASE, recovery.OFFSET )
state = "starting now" state = "starting now"
recovery.OPEN = true recovery.OPEN = true
else
-- Window was already closed/cancelled within its active time range.
state = "cancelled"
end end
-- Set current recovery window. -- Set current recovery window (unless this window has been cancelled).
currwindow = recovery if not recovery.OVER then
currwindow = recovery
end
else -- Stop time HAS passed. 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. -- 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 ) 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 ) 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 -- Route carrier into the wind. Sets self.turnintowind=true
self:CarrierTurnIntoWind( t, v, uturn ) self:CarrierTurnIntoWind( t, v, uturn )
@@ -4361,11 +4382,20 @@ function AIRBOSS:onafterRecoveryStop( From, Event, To )
self:CarrierResumeRoute( coord ) self:CarrierResumeRoute( coord )
end end
-- Delete current recovery window if open. -- Mark the current recovery window closed and cancelled, then remove it from the
if self.recoverywindow and self.recoverywindow.OPEN == true then -- 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.OPEN = false
self.recoverywindow.OVER = true self.recoverywindow.OVER = true
self:DeleteRecoveryWindow( self.recoverywindow ) self:DeleteRecoveryWindow( self.recoverywindow, 1 )
end end
-- Check recovery windows. This sets self.recoverywindow to the next window. -- 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 -- 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 if playerData.wire == 1 and points >= 3 and N > 4 then
points = points -1 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 end
env.info("Returning: " .. grade .. " " .. points .. " " .. G) env.info("Returning: " .. grade .. " " .. points .. " " .. G)
+1 -2
View File
@@ -5120,7 +5120,6 @@ end
-- FSM Functions -- FSM Functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "Planned" event. --- On after "Planned" event.
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param #string From From state. -- @param #string From From state.
@@ -6610,7 +6609,7 @@ function AUFTRAG:GetDCSMissionTask(MissionGroup)
local SeadUnitSet = SET_UNIT:New() local SeadUnitSet = SET_UNIT:New()
for _,_unit in pairs (ScanUnitSet.Set) do for _,_unit in pairs (ScanUnitSet.Set) do
local unit = _unit -- Wrapper.Unit#UNTI 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()) self:T("Adding UNIT for SEAD: "..unit:GetName())
local task = CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318) local task = CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318)
table.insert(DCStasks, task) table.insert(DCStasks, task)
+3
View File
@@ -150,6 +150,9 @@ function BRIGADE:New(WarehouseName, BrigadeName)
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission. -- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- Add legion to DB
_DATABASE:AddLegion(self)
return self return self
end end
+8 -2
View File
@@ -504,6 +504,9 @@ CSAR.AircraftType["OH58D"] = 2
CSAR.AircraftType["CH-47Fbl1"] = 31 CSAR.AircraftType["CH-47Fbl1"] = 31
CSAR.AircraftType["AH-6J"] = 2 CSAR.AircraftType["AH-6J"] = 2
CSAR.AircraftType["MH-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. --- CSAR class version.
-- @field #string version -- @field #string version
@@ -2538,8 +2541,11 @@ function CSAR:_AddMedevacMenuItem()
if _unit then if _unit then
--self:T("Unitname ".._unit:GetName().." IsAlive "..tostring(_unit:IsAlive()).." IsPlayer "..tostring(_unit:IsPlayer())) --self:T("Unitname ".._unit:GetName().." IsAlive "..tostring(_unit:IsAlive()).." IsPlayer "..tostring(_unit:IsPlayer()))
if _unit:IsAlive() and _unit:IsPlayer() then if _unit:IsAlive() and _unit:IsPlayer() then
local unitName = _unit:GetName() local _maxUnits = self.AircraftType[_unit:GetTypeName()]
_UnitList[unitName] = unitName if _maxUnits == nil or _maxUnits > 0 then
local unitName = _unit:GetName()
_UnitList[unitName] = unitName
end
end -- end isAlive end -- end isAlive
end -- end if _unit end -- end if _unit
end -- end for end -- end for
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15 -12
View File
@@ -21,7 +21,7 @@
-- --
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- Date: September 2023 -- Date: September 2023
-- Last Update: Mar 2026 -- Last Update: June 2026
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- --
--- **Ops** - Easy GCI & CAP Manager --- **Ops** - Easy GCI & CAP Manager
@@ -286,7 +286,7 @@ EASYGCICAP = {
--- EASYGCICAP class version. --- EASYGCICAP class version.
-- @field #string version -- @field #string version
EASYGCICAP.version="0.1.35" EASYGCICAP.version="0.1.36"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- --
@@ -854,16 +854,18 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
end end
if self.noalert5 > 0 then if self.noalert5 > 0 then
local alert for i=1,self.noalert5 do
if self.ClassName == "EASYGCICAP" then local alert
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) if self.ClassName == "EASYGCICAP" then
elseif self.ClassName == "EASYA2G" then alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) 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 end
alert:SetRequiredAssets(self.noalert5)
alert:SetRepeat(99)
CAP_Wing:AddMission(alert)
table.insert(self.ListOfAuftrag,alert)
end end
self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename } self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename }
@@ -1835,9 +1837,10 @@ function EASYGCICAP:onafterRestart(From,Event,To)
-- self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename } -- self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename }
for _,_wing in pairs(self.wings or {}) do for _,_wing in pairs(self.wings or {}) do
for _,_aw in pairs(_wing) do for _,_aw in pairs(_wing) do
_wing[1]:Start() _wing[1]:Restart()
end end
end end
end end
self:__Status(5)
return self return self
end end
+3
View File
@@ -167,6 +167,9 @@ function FLEET:New(WarehouseName, FleetName)
-- @param Ops.NavyGroup#NAVYGROUP NavyGroup The NAVYGROUP on mission. -- @param Ops.NavyGroup#NAVYGROUP NavyGroup The NAVYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- Add legion to DB
_DATABASE:AddLegion(self)
return self return self
end end
+3
View File
@@ -73,6 +73,9 @@ function FLOTILLA:New(TemplateGroupName, Ngroups, FlotillaName)
-- Get initial ammo. -- Get initial ammo.
self.ammo=self:_CheckAmmo() self.ammo=self:_CheckAmmo()
-- Add cohort to DB
_DATABASE:AddCohort(self)
return self return self
end end
@@ -1270,6 +1270,15 @@ function INTEL:_UpdateContact(Contact)
else else
Contact.maneuvering = false Contact.maneuvering = false
end 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
end end
+1 -2
View File
@@ -306,7 +306,6 @@ function LEGION:New(WarehouseName, LegionName)
-- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to. -- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to.
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned.
return self return self
end end
@@ -2581,7 +2580,7 @@ end
-- @return #boolean Returns `true` if given cohort can meet all requirements. -- @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) 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. --- Function to check category.
local function CheckCategory(_cohort) local function CheckCategory(_cohort)
+3
View File
@@ -71,6 +71,9 @@ function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName)
-- Get ammo. -- Get ammo.
self.ammo=self:_CheckAmmo() self.ammo=self:_CheckAmmo()
-- Add cohort to DB
_DATABASE:AddCohort(self)
return self return self
end end
+3
View File
@@ -116,6 +116,9 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
-- See COHORT class -- See COHORT class
-- Add cohort to DB
_DATABASE:AddCohort(self)
return self return self
end end
File diff suppressed because it is too large Load Diff
+94 -54
View File
@@ -153,7 +153,7 @@ _TARGETID=0
--- TARGET class version. --- TARGET class version.
-- @field #string version -- @field #string version
TARGET.version="0.7.1" TARGET.version="0.8.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -206,6 +206,9 @@ function TARGET:New(TargetObject)
self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "Status", "*") -- Status update.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self:AddTransition("*", "ElementDestroyed", "*") -- A target element was destroyed.
self:AddTransition("*", "ElementDead", "*") -- A target element is dead (destroyed or despawned).
self:AddTransition("*", "ObjectDamaged", "*") -- A target object was damaged. self:AddTransition("*", "ObjectDamaged", "*") -- A target object was damaged.
self:AddTransition("*", "ObjectDestroyed", "*") -- A target object was destroyed. self:AddTransition("*", "ObjectDestroyed", "*") -- A target object was destroyed.
self:AddTransition("*", "ObjectDead", "*") -- A target object is dead (destroyed or despawned). self:AddTransition("*", "ObjectDead", "*") -- A target object is dead (destroyed or despawned).
@@ -245,6 +248,19 @@ function TARGET:New(TargetObject)
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
--- Triggers the FSM event "ElementDestroyed".
-- @function [parent=#TARGET] ElementDestroyed
-- @param #TARGET self
-- @param #string ElementName Name of the element.
-- @param #TARGET.Object Target Target object.
--- Triggers the FSM event "ElementDead".
-- @function [parent=#TARGET] ElementDead
-- @param #TARGET self
-- @param #string ElementName Name of the element.
-- @param #TARGET.Object Target Target object.
--- Triggers the FSM event "ObjectDamaged". --- Triggers the FSM event "ObjectDamaged".
-- @function [parent=#TARGET] ObjectDamaged -- @function [parent=#TARGET] ObjectDamaged
-- @param #TARGET self -- @param #TARGET self
@@ -642,6 +658,19 @@ function TARGET:onafterStatus(From, Event, To)
-- FSM state. -- FSM state.
local fsmstate=self:GetState() local fsmstate=self:GetState()
-- First we check any target has been destroyed and the dead/unitlost event was not fired
for i,_target in pairs(self.targets) do
local target=_target --#TARGET.Object
local life=self:GetTargetLife(target)
if life<1 and target.Status~=TARGET.ObjectStatus.DEAD then
self:E(self.lid..string.format("FF life is zero but no object dead event fired ==> waiting for target object %s events!", tostring(target.Name)))
-- We wait 60 seconds for the events to occur
self:__Status(-60)
return self
end
end
-- Update damage. -- Update damage.
local damaged=false local damaged=false
@@ -736,6 +765,61 @@ end
-- FSM Events -- FSM Events
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "ElementDestroyed" event.
-- @param #TARGET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Name Name of the element.
-- @param #TARGET.Object Target Target object.
function TARGET:onafterElementDestroyed(From, Event, To, Name, Target)
-- Debug message.
self:T(self.lid..string.format("Element %s of target object %s destroyed", Name, Target.Name))
-- Increase destroyed counter.
Target.Ndestroyed=Target.Ndestroyed+1
-- Increase dead counter.
self.Ndestroyed=self.Ndestroyed+1
self:ElementDead(Name, Target)
return self
end
--- On after "ElementDead" event.
-- @param #TARGET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Name Name of the element.
-- @param #TARGET.Object Target Target object.
function TARGET:onafterElementDead(From, Event, To, Name, Target)
-- Debug message.
self:T(self.lid..string.format("Element %s of target object %s dead", Name, Target.Name))
-- Increase dead counter.
Target.Ndead=Target.Ndead+1
-- Increase dead counter.
self.Ndead=self.Ndead+1
-- All dead ==> Trigger event.
if Target.Ndestroyed==Target.N0 then
self:ObjectDestroyed(Target)
elseif Target.Ndead==Target.N0 then
self:ObjectDead(Target)
end
return self
end
--- On after "ObjectDamaged" event. --- On after "ObjectDamaged" event.
-- @param #TARGET self -- @param #TARGET self
-- @param #string From From state. -- @param #string From From state.
@@ -761,11 +845,6 @@ function TARGET:onafterObjectDestroyed(From, Event, To, Target)
-- Debug message. -- Debug message.
self:T(self.lid..string.format("Object %s destroyed", Target.Name)) self:T(self.lid..string.format("Object %s destroyed", Target.Name))
-- Increase destroyed counter.
self.Ndestroyed=self.Ndestroyed+1
Target.Ndestroyed=Target.Ndestroyed+1
Target.Life=0 Target.Life=0
-- Call object dead event. -- Call object dead event.
@@ -787,16 +866,10 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
-- Set target status. -- Set target status.
Target.Status=TARGET.ObjectStatus.DEAD Target.Status=TARGET.ObjectStatus.DEAD
-- Increase dead object counter
Target.Ndead=Target.Ndead+1
-- Set target object life to 0. -- Set target object life to 0.
Target.Life=0 Target.Life=0
-- Increase dead counter.
self.Ndead=self.Ndead+1
-- Check if anyone is alive? -- Check if anyone is alive?
local dead=true local dead=true
for _,_target in pairs(self.targets) do for _,_target in pairs(self.targets) do
@@ -889,54 +962,21 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
-- Add to the list of casualties. -- Add to the list of casualties.
table.insert(self.casualties, Name) table.insert(self.casualties, Name)
-- Try to get target Group. -- Get target from Group or Unit.
local target=self:GetTargetByName(EventData.IniGroupName) local target=self:GetTargetByName(EventData.IniGroupName) or self:GetTargetByName(EventData.IniUnitName)
-- Try unit target.
if not target then
target=self:GetTargetByName(EventData.IniUnitName)
end
-- Check if we could find a target object. -- Check if we could find a target object.
if target then if target then
local Ndead=target.Ndead -- Increase dead/destroyed counter
local Ndestroyed=target.Ndestroyed
if EventData.id==EVENTS.RemoveUnit then if EventData.id==EVENTS.RemoveUnit then
Ndead=Ndead+1 self:ElementDead(Name, target)
else else
Ndestroyed=Ndestroyed+1 self:ElementDestroyed(Name, target)
Ndead=Ndead+1
end
-- Check if ALL objects are dead
if Ndead==target.N0 then
if Ndestroyed>=target.N0 then
-- Debug message.
self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name)))
target.Life = 0
-- Trigger object destroyed event. This sets the Life to zero and increases Ndestroyed
self:ObjectDestroyed(target)
else
-- Debug message.
self:T2(self.lid..string.format("EVENT ID=%d: target %s removed ==> dead", EventData.id, tostring(target.Name)))
target.Life = 0
-- Trigger object dead event. This sets the Life to zero and increases Ndead counter
self:ObjectDead(target)
end
end end
else
self:E(self.lid..string.format("ERROR: Could not get target from IniGroup or IniUnit name when event Dead or UnitLost occured! Stats are not correctly updated :("))
end -- Event belongs to this TARGET end -- Event belongs to this TARGET
end end
+107 -30
View File
@@ -61,24 +61,21 @@
-- --
-- # The MSRS Concept -- # 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 -- ## Prerequisites
-- --
-- * This script needs SRS version >= 1.9.6
-- * You need to de-sanitize os, io and lfs in the missionscripting.lua -- * 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 but recommended: HOUND TTS as backend to communicate with SRS (via plugin)
-- * Optional: HOUNDTTS as backend to communicate with SRS (via infra) -- * Optional: DCS-gRPC as backend to communicate with SRS (via plugin)
--
-- ## Known Issues
-- --
-- ## Knwon Issues -- ### Pop-up Window (with backend windows)
--
-- ### Pop-up Window
-- --
-- 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, -- 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 -- # Play Sound Files
-- --
@@ -137,16 +134,17 @@
-- --
-- See @{#MSRS.LoadConfigFile} for details on how to set this up. -- See @{#MSRS.LoadConfigFile} for details on how to set this up.
-- --
-- ## TTS Providers -- ## TTS (text-to-speech) 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. -- 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 -- **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. -- 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"` -- 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. -- 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: -- 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 -- 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 -- 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: -- 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}. -- 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: -- 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}. -- 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 -- ## 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 -- 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 -- ## 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. -- 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. -- 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. -- 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. -- 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. -- Hound TTS can be used both with DCS dedicated server and regular DCS installations.
-- --
@@ -280,7 +284,7 @@ MSRS = {
--- MSRS class version. --- MSRS class version.
-- @field #string version -- @field #string version
MSRS.version="0.3.5" MSRS.version="0.3.7"
--- Voices --- Voices
-- @type MSRS.Voices -- @type MSRS.Voices
@@ -731,14 +735,16 @@ MSRS.Backend = {
-- @field #string AZURE Microsoft Azure (`azure`). Only possible with DCS-gRPC 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 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 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 = { MSRS.Provider = {
WINDOWS = "win", WINDOWS = "win",
GOOGLE = "gcloud", GOOGLE = "gcloud",
AZURE = "azure", AZURE = "azure",
AMAZON = "aws", AMAZON = "aws",
PIPER = "piper", PIPER = "piper",
KITTEN = "kitten", KITTEN = "openai",
OPENAI = "openai",
} }
--- Function for UUID. --- Function for UUID.
@@ -1471,6 +1477,15 @@ function MSRS:SetTTSProviderKitten()
return self return self
end 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. --- Print SRS help to DCS log file.
-- @param #MSRS self -- @param #MSRS self
-- @return #MSRS self -- @return #MSRS self
@@ -1605,7 +1620,7 @@ function MSRS:PlayText(Text, Delay, Coordinate, Speed, Speaker)
else else
local speaker = Speaker or self.Speaker local speaker = Speaker or self.Speaker
if self.backend==MSRS.Backend.GRPC then if self.backend==MSRS.Backend.GRPC then
self:T(self.lid.."Transmitting") self:T(self.lid.."Transmitting")
self:_DCSgRPCtts(Text, nil, nil , nil, nil, nil, nil, Coordinate) self:_DCSgRPCtts(Text, nil, nil , nil, nil, nil, nil, Coordinate)
@@ -2088,19 +2103,16 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co
return return
end end
Frequencies = UTILS.EnsureTable(Frequencies) Frequencies = UTILS.EnsureTable(Frequencies or self.frequencies)
Modulations = UTILS.EnsureTable(Modulations) Modulations = UTILS.EnsureTable(Modulations or self.modulations)
local ffs = {} local ffs = {}
for _,_f in pairs(Frequencies or self.frequencies) do for _,_f in pairs(Frequencies) do
table.insert(ffs,string.format("%.1f",_f)) table.insert(ffs,string.format("%.1f",_f))
end end
local freqs = table.concat(ffs, ",") local freqs = table.concat(ffs, ",")
local modus = table.concat(Modulations, ",")
local modus = table.concat(Modulations or self.modulations, ",")
local coal=Coalition or self.coalition local coal=Coalition or self.coalition
local gender=Gender or self.gender local gender=Gender or self.gender
@@ -2144,7 +2156,7 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co
gender = gender, gender = gender,
speaker = Speaker or self.Speaker, speaker = Speaker or self.Speaker,
} }
local speechtime = HoundTTS.Transmit(Message, TransmissionP, ProviderP) local speechtime = HoundTTS.Transmit(Message, TransmissionP, ProviderP)
return speechtime return speechtime
@@ -2258,6 +2270,71 @@ function MSRS._HoundTranslate(Message,Parameters,CallbackFunction)
return return
end 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("%.1f",_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 -- Config File
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+392 -72
View File
@@ -23,6 +23,7 @@
-- @field #boolean isAirdrome Airbase is an airdrome. -- @field #boolean isAirdrome Airbase is an airdrome.
-- @field #boolean isHelipad Airbase is a helipad. -- @field #boolean isHelipad Airbase is a helipad.
-- @field #boolean isShip Airbase is a ship. -- @field #boolean isShip Airbase is a ship.
-- @field #boolean isZell Airbase is a ZELL Booster.
-- @field #table parking Parking spot data. -- @field #table parking Parking spot data.
-- @field #table parkingByID Parking spot data table with ID as key. -- @field #table parkingByID Parking spot data table with ID as key.
-- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning. -- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning.
@@ -458,115 +459,420 @@ AIRBASE.TheChannel = {
--- Airbases of the Syria map --- Airbases of the Syria map
-- --
-- * `AIRBASE.Syria.Abu_al_Duhur` Abu al-Duhur -- * AIRBASE.Syria.Abu_al_Duhur
-- * `AIRBASE.Syria.Adana_Sakirpasa` Adana Sakirpasa -- * AIRBASE.Syria.Adana_Sakirpasa
-- * `AIRBASE.Syria.Akrotiri` Akrotiri -- * AIRBASE.Syria.Adiyaman
-- * `AIRBASE.Syria.Al_Qusayr` Al Qusayr -- * AIRBASE.Syria.Akrotiri
-- * `AIRBASE.Syria.Al_Dumayr` Al-Dumayr -- * AIRBASE.Syria.Al_Dumayr
-- * `AIRBASE.Syria.Aleppo` Aleppo -- * AIRBASE.Syria.Al_Qusayr
-- * `AIRBASE.Syria.An_Nasiriyah` An Nasiriyah -- * AIRBASE.Syria.Aleppo
-- * `AIRBASE.Syria.At_Tanf` At Tanf -- * AIRBASE.Syria.An_Nasiriyah
-- * `AIRBASE.Syria.Bassel_Al_Assad` Bassel Al-Assad -- * AIRBASE.Syria.At_Tanf
-- * `AIRBASE.Syria.Beirut_Rafic_Hariri` Beirut-Rafic Hariri -- * AIRBASE.Syria.Bassel_Al_Assad
-- * `AIRBASE.Syria.Ben_Gurion` Ben Gurion -- * AIRBASE.Syria.Beirut_Rafic_Hariri
-- * `AIRBASE.Syria.Damascus` Damascus -- * AIRBASE.Syria.Ben_Gurion
-- * `AIRBASE.Syria.Deir_ez_Zor` Deir ez-Zor -- * AIRBASE.Syria.Chukurova
-- * `AIRBASE.Syria.Ercan` Ercan -- * AIRBASE.Syria.Damascus
-- * `AIRBASE.Syria.Eyn_Shemer` Eyn Shemer -- * AIRBASE.Syria.Deir_ez_Zor
-- * `AIRBASE.Syria.Gaziantep` Gaziantep -- * AIRBASE.Syria.Diyarbakir
-- * `AIRBASE.Syria.Gazipasa` Gazipasa -- * AIRBASE.Syria.Ercan
-- * `AIRBASE.Syria.Gecitkale` Gecitkale -- * AIRBASE.Syria.Eyn_Shemer
-- * `AIRBASE.Syria.H` H -- * AIRBASE.Syria.Gaziantep
-- * `AIRBASE.Syria.H3` H3 -- * AIRBASE.Syria.Gazipasa
-- * `AIRBASE.Syria.H3_Northwest` H3 Northwest -- * AIRBASE.Syria.Gecitkale
-- * `AIRBASE.Syria.H3_Southwest` H3 Southwest -- * AIRBASE.Syria.Gulechoba
-- * `AIRBASE.Syria.H4` H4 -- * AIRBASE.Syria.H3
-- * `AIRBASE.Syria.Haifa` Haifa -- * AIRBASE.Syria.H3_Northwest
-- * `AIRBASE.Syria.Hama` Hama -- * AIRBASE.Syria.H3_Southwest
-- * `AIRBASE.Syria.Hatay` Hatay -- * AIRBASE.Syria.H4
-- * `AIRBASE.Syria.Hatzor` Hatzor -- * AIRBASE.Syria.H4_Emergency
-- * `AIRBASE.Syria.Herzliya` Herzliya -- * AIRBASE.Syria.HC01
-- * `AIRBASE.Syria.Incirlik` Incirlik -- * AIRBASE.Syria.HC02
-- * `AIRBASE.Syria.Jirah` Jirah -- * AIRBASE.Syria.HC03
-- * `AIRBASE.Syria.Khalkhalah` Khalkhalah -- * AIRBASE.Syria.HC04
-- * `AIRBASE.Syria.Kharab_Ishk` Kharab Ishk -- * AIRBASE.Syria.HC05
-- * `AIRBASE.Syria.King_Abdullah_II` King Abdullah II -- * AIRBASE.Syria.HC06
-- * `AIRBASE.Syria.King_Hussein_Air_College` King Hussein Air College -- * AIRBASE.Syria.HI01
-- * `AIRBASE.Syria.Kingsfield` Kingsfield -- * AIRBASE.Syria.HI02
-- * `AIRBASE.Syria.Kiryat_Shmona` Kiryat Shmona -- * AIRBASE.Syria.HI03
-- * `AIRBASE.Syria.Kuweires` Kuweires -- * AIRBASE.Syria.HI05
-- * `AIRBASE.Syria.Lakatamia` Lakatamia -- * AIRBASE.Syria.HI06
-- * `AIRBASE.Syria.Larnaca` Larnaca -- * AIRBASE.Syria.HI07
-- * `AIRBASE.Syria.Marj_Ruhayyil` Marj Ruhayyil -- * AIRBASE.Syria.HI08
-- * `AIRBASE.Syria.Marj_as_Sultan_North` Marj as Sultan North -- * AIRBASE.Syria.HI09
-- * `AIRBASE.Syria.Marj_as_Sultan_South` Marj as Sultan South -- * AIRBASE.Syria.HI11
-- * `AIRBASE.Syria.Marka` Marka -- * AIRBASE.Syria.HI12
-- * `AIRBASE.Syria.Megiddo` Megiddo -- * AIRBASE.Syria.HI13
-- * `AIRBASE.Syria.Mezzeh` Mezzeh -- * AIRBASE.Syria.HI14
-- * `AIRBASE.Syria.Minakh` Minakh -- * AIRBASE.Syria.HI15
-- * `AIRBASE.Syria.Muwaffaq_Salti` Muwaffaq Salti -- * AIRBASE.Syria.HI16
-- * `AIRBASE.Syria.Naqoura` Naqoura -- * AIRBASE.Syria.HI17
-- * `AIRBASE.Syria.Nicosia` Nicosia -- * AIRBASE.Syria.HI18
-- * `AIRBASE.Syria.Palmachim` Palmachim -- * AIRBASE.Syria.HI20
-- * `AIRBASE.Syria.Palmyra` Palmyra -- * AIRBASE.Syria.HI21
-- * `AIRBASE.Syria.Paphos` Paphos -- * AIRBASE.Syria.HI22
-- * `AIRBASE.Syria.Pinarbashi` Pinarbashi -- * AIRBASE.Syria.HI23
-- * `AIRBASE.Syria.Prince_Hassan` Prince Hassan -- * AIRBASE.Syria.HI24
-- * `AIRBASE.Syria.Qabr_as_Sitt` Qabr as Sitt -- * AIRBASE.Syria.HI25
-- * `AIRBASE.Syria.Ramat_David` Ramat David -- * AIRBASE.Syria.HI26
-- * `AIRBASE.Syria.Rayak` Rayak -- * AIRBASE.Syria.HJ01
-- * `AIRBASE.Syria.Rene_Mouawad` Rene Mouawad -- * AIRBASE.Syria.HJ02
-- * `AIRBASE.Syria.Rosh_Pina` Rosh Pina -- * AIRBASE.Syria.HJ03
-- * `AIRBASE.Syria.Ruwayshid` Ruwayshid -- * AIRBASE.Syria.HJ04
-- * `AIRBASE.Syria.Sanliurfa` Sanliurfa -- * AIRBASE.Syria.HL01
-- * `AIRBASE.Syria.Sayqal` Sayqal -- * AIRBASE.Syria.HL02
-- * `AIRBASE.Syria.Shayrat` Shayrat -- * AIRBASE.Syria.HL03
-- * `AIRBASE.Syria.Tabqa` Tabqa -- * AIRBASE.Syria.HL04
-- * `AIRBASE.Syria.Taftanaz` Taftanaz -- * AIRBASE.Syria.HL05
-- * `AIRBASE.Syria.Tal_Siman` Tal Siman -- * AIRBASE.Syria.HL06
-- * `AIRBASE.Syria.Tel_Nof` Tel Nof -- * AIRBASE.Syria.HL07
-- * `AIRBASE.Syria.Tha_lah` Tha'lah -- * AIRBASE.Syria.HL08
-- * `AIRBASE.Syria.Tiyas` Tiyas -- * AIRBASE.Syria.HL09
-- * `AIRBASE.Syria.Wujah_Al_Hajar` Wujah Al Hajar -- * 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 -- @field Syria
AIRBASE.Syria = { AIRBASE.Syria = {
["Abu_al_Duhur"] = "Abu al-Duhur", ["Abu_al_Duhur"] = "Abu al-Duhur",
["Adana_Sakirpasa"] = "Adana Sakirpasa", ["Adana_Sakirpasa"] = "Adana Sakirpasa",
["Adiyaman"] = "Adiyaman",
["Akrotiri"] = "Akrotiri", ["Akrotiri"] = "Akrotiri",
["Al_Qusayr"] = "Al Qusayr",
["Al_Dumayr"] = "Al-Dumayr", ["Al_Dumayr"] = "Al-Dumayr",
["Al_Qusayr"] = "Al Qusayr",
["Aleppo"] = "Aleppo", ["Aleppo"] = "Aleppo",
["An_Nasiriyah"] = "An Nasiriyah", ["An_Nasiriyah"] = "An Nasiriyah",
["At_Tanf"] = "At Tanf", ["At_Tanf"] = "At Tanf",
["Bassel_Al_Assad"] = "Bassel Al-Assad", ["Bassel_Al_Assad"] = "Bassel Al-Assad",
["Beirut_Rafic_Hariri"] = "Beirut-Rafic Hariri", ["Beirut_Rafic_Hariri"] = "Beirut-Rafic Hariri",
["Ben_Gurion"] = "Ben Gurion", ["Ben_Gurion"] = "Ben Gurion",
["Chukurova"] = "Chukurova",
["Damascus"] = "Damascus", ["Damascus"] = "Damascus",
["Deir_ez_Zor"] = "Deir ez-Zor", ["Deir_ez_Zor"] = "Deir ez-Zor",
["Diyarbakir"] = "Diyarbakir",
["Ercan"] = "Ercan", ["Ercan"] = "Ercan",
["Eyn_Shemer"] = "Eyn Shemer", ["Eyn_Shemer"] = "Eyn Shemer",
["Gaziantep"] = "Gaziantep", ["Gaziantep"] = "Gaziantep",
["Gazipasa"] = "Gazipasa", ["Gazipasa"] = "Gazipasa",
["Gecitkale"] = "Gecitkale", ["Gecitkale"] = "Gecitkale",
["H"] = "H", ["Gulechoba"] = "Gulechoba",
["H3"] = "H3", ["H3"] = "H3",
["H3_Northwest"] = "H3 Northwest", ["H3_Northwest"] = "H3 Northwest",
["H3_Southwest"] = "H3 Southwest", ["H3_Southwest"] = "H3 Southwest",
["H4"] = "H4", ["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", ["Haifa"] = "Haifa",
["Hama"] = "Hama", ["Hama"] = "Hama",
["Hatay"] = "Hatay", ["Hatay"] = "Hatay",
["Hatzerim"] = "Hatzerim",
["Hatzor"] = "Hatzor", ["Hatzor"] = "Hatzor",
["Herzliya"] = "Herzliya", ["Herzliya"] = "Herzliya",
["Incirlik"] = "Incirlik", ["Incirlik"] = "Incirlik",
["Jirah"] = "Jirah", ["Jirah"] = "Jirah",
["Kahramanmaras"] = "Kahramanmaras",
["Kedem"] = "Kedem",
["Khalkhalah"] = "Khalkhalah", ["Khalkhalah"] = "Khalkhalah",
["Kharab_Ishk"] = "Kharab Ishk", ["Kharab_Ishk"] = "Kharab Ishk",
["King_Abdullah_II"] = "King Abdullah II", ["King_Abdullah_II"] = "King Abdullah II",
["King_Hussein_Air_College"] = "King Hussein Air College", ["King_Hussein_Air_College"] = "King Hussein Air College",
["Kingsfield"] = "Kingsfield", ["Kingsfield"] = "Kingsfield",
["Kiryat_Shmona"] = "Kiryat Shmona", ["Kiryat_Shmona"] = "Kiryat Shmona",
["Konya"] = "Konya",
["Kuweires"] = "Kuweires", ["Kuweires"] = "Kuweires",
["Lakatamia"] = "Lakatamia", ["Lakatamia"] = "Lakatamia",
["Larnaca"] = "Larnaca", ["Larnaca"] = "Larnaca",
@@ -579,6 +885,7 @@ AIRBASE.Syria = {
["Minakh"] = "Minakh", ["Minakh"] = "Minakh",
["Muwaffaq_Salti"] = "Muwaffaq Salti", ["Muwaffaq_Salti"] = "Muwaffaq Salti",
["Naqoura"] = "Naqoura", ["Naqoura"] = "Naqoura",
["Nevatim"] = "Nevatim",
["Nicosia"] = "Nicosia", ["Nicosia"] = "Nicosia",
["Palmachim"] = "Palmachim", ["Palmachim"] = "Palmachim",
["Palmyra"] = "Palmyra", ["Palmyra"] = "Palmyra",
@@ -592,15 +899,20 @@ AIRBASE.Syria = {
["Rosh_Pina"] = "Rosh Pina", ["Rosh_Pina"] = "Rosh Pina",
["Ruwayshid"] = "Ruwayshid", ["Ruwayshid"] = "Ruwayshid",
["Sanliurfa"] = "Sanliurfa", ["Sanliurfa"] = "Sanliurfa",
["Sanliurfa_Heliport"] = "Sanliurfa Heliport",
["Sayqal"] = "Sayqal", ["Sayqal"] = "Sayqal",
["Shayrat"] = "Shayrat", ["Shayrat"] = "Shayrat",
["T2"] = "T2",
["T3"] = "T3",
["Tabqa"] = "Tabqa", ["Tabqa"] = "Tabqa",
["Taftanaz"] = "Taftanaz", ["Taftanaz"] = "Taftanaz",
["Tal_Siman"] = "Tal Siman", ["Tal_Siman"] = "Tal Siman",
["Tel_Nof"] = "Tel Nof", ["Tel_Nof"] = "Tel Nof",
["Teyman"] = "Teyman",
["Tha_lah"] = "Tha'lah", ["Tha_lah"] = "Tha'lah",
["Tiyas"] = "Tiyas", ["Tiyas"] = "Tiyas",
["Wujah_Al_Hajar"] = "Wujah Al Hajar", ["Wujah_Al_Hajar"] = "Wujah Al Hajar",
["Zarqa"] = "Zarqa",
} }
--- Airbases of the Mariana Islands map --- Airbases of the Mariana Islands map
@@ -1673,6 +1985,7 @@ elseif self.category==Airbase.Category.SHIP then
self.category=Airbase.Category.HELIPAD self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName) _DATABASE:AddStatic(AirbaseName)
end end
if self:GetTypeName() == "Zell" then self.isZell = true end
else else
self:E("ERROR: Unknown airbase category!") self:E("ERROR: Unknown airbase category!")
end end
@@ -2122,6 +2435,13 @@ function AIRBASE:IsShip()
return self.isShip return self.isShip
end 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 -- Parking
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+1
View File
@@ -112,6 +112,7 @@ Ops/OpsZone.lua
Ops/ArmyGroup.lua Ops/ArmyGroup.lua
Ops/OpsTransport.lua Ops/OpsTransport.lua
Ops/Target.lua Ops/Target.lua
Ops/TARS.lua
Sound/UserSound.lua Sound/UserSound.lua
Sound/SoundOutput.lua Sound/SoundOutput.lua