mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-17 18:22:19 +00:00
Compare commits
482 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c335881b2d | |||
| 993fc45e83 | |||
| 130d358f4a | |||
| 49476abb9f | |||
| a02b99036b | |||
| dab6f48fe9 | |||
| 23c9ea0961 | |||
| d0b04ebbbd | |||
| bd64d084e9 | |||
| c91b59f8cd | |||
| a699300f5f | |||
| 6e12c5c1d0 | |||
| 852fc1d2d5 | |||
| c2c957b859 | |||
| f20c22ab49 | |||
| 1e200511cb | |||
| 80f2a0ef39 | |||
| ae156259c0 | |||
| 39e3f8b646 | |||
| ac61c3a55c | |||
| 1054ffa7ac | |||
| 41503ae625 | |||
| 1ee90e67b2 | |||
| d0ce7f1aa6 | |||
| c387749a81 | |||
| b980c5d93a | |||
| 49332fafec | |||
| 3ab3278db4 | |||
| 7a3ea2918d | |||
| 9c0394f035 | |||
| 9536d368b0 | |||
| 9426905959 | |||
| 99b7b4b057 | |||
| 2fdbdb4058 | |||
| 898405e15f | |||
| 7259038b1c | |||
| 3886351490 | |||
| 3aedf8e5d0 | |||
| aed2f1d8a9 | |||
| 3504797147 | |||
| 3cfaa60ebb | |||
| 2d930ff2dd | |||
| 11a8792398 | |||
| f7556fac0e | |||
| 394f3ffd88 | |||
| a3d7b4eba9 | |||
| b94f36dfdf | |||
| 8eae7ac26b | |||
| 7bac1d3b92 | |||
| b31b11cda8 | |||
| d000ad76f8 | |||
| 29b244d843 | |||
| 1eb6dbc7f2 | |||
| 945b9bdf31 | |||
| b75e383956 | |||
| 15de1d58b0 | |||
| 79ce3871ea | |||
| 84c68ccf14 | |||
| 93d0a8b674 | |||
| 8a33ccd356 | |||
| 9473cc2b27 | |||
| 448d668b44 | |||
| 8c0e021e82 | |||
| a72d3afd1c | |||
| 68aab10902 | |||
| b712c44567 | |||
| 12a118d08f | |||
| b1b9db7333 | |||
| 5f920a46fd | |||
| 23e3aefaf1 | |||
| 74e38f9755 | |||
| 69a781a710 | |||
| 2c82513e54 | |||
| 8eeff7efa2 | |||
| 7fcac6159c | |||
| a5eef3087b | |||
| af891f3fc2 | |||
| 8e47a17c96 | |||
| c2c78dfe78 | |||
| 26742432f5 | |||
| 6a13e593ec | |||
| 8d8b886ad6 | |||
| 2e6cc61450 | |||
| 6859dc9142 | |||
| b871f728df | |||
| 87b87fa2ca | |||
| a39f39646d | |||
| 157f9a82b1 | |||
| 795f58dc9b | |||
| 976f92e5b1 | |||
| 25daa67554 | |||
| 33ff723bd8 | |||
| 34f3e24d3a | |||
| b6a90d4633 | |||
| 0e960b77b9 | |||
| 093526f4ff | |||
| 8160aad1bf | |||
| a88750908e | |||
| aca3080c9a | |||
| 948158e85a | |||
| 98539eee87 | |||
| 223c1650d9 | |||
| aa2133e72a | |||
| 498d58599b | |||
| a0c0fe36ba | |||
| 76b977ff98 | |||
| 878eaf4301 | |||
| 240d3460d7 | |||
| ae0d405350 | |||
| 45fc0eb9da | |||
| 3327c3b24d | |||
| c6521d9c38 | |||
| 049529ecf0 | |||
| c2b8eea010 | |||
| da8856c907 | |||
| 04f7bb7cc7 | |||
| 75c01fbe86 | |||
| 03b79351db | |||
| 33429766a0 | |||
| 83464090d6 | |||
| d77b2655b1 | |||
| 31a537748e | |||
| e873b097cf | |||
| 07a3095392 | |||
| e1b5c7f9c2 | |||
| f005cdbc71 | |||
| 7fa360f32c | |||
| 30c6937910 | |||
| 28724f396c | |||
| fb3ca6d9d5 | |||
| 6e75e58952 | |||
| 0abb0db2a3 | |||
| edb42bbbe9 | |||
| 3bebe7f69c | |||
| 7605b582d1 | |||
| 6ed031762b | |||
| 945efc0516 | |||
| 4e342a59be | |||
| 8e517bb9cf | |||
| dacf5125bf | |||
| 60b561449b | |||
| 88396b5a5e | |||
| a0c5466f7f | |||
| b62b218257 | |||
| 7a3c23527f | |||
| 2f002703ee | |||
| b0c6ed1d59 | |||
| 950e97b6d7 | |||
| 6d54e5a47f | |||
| 87d65123ba | |||
| 35c8803f8b | |||
| d9c8542993 | |||
| d7a6809fec | |||
| f3c38a4288 | |||
| 626b03c93a | |||
| cf5c55209a | |||
| 19921414cf | |||
| c4de2b6990 | |||
| 7907e4b830 | |||
| 9e55118d3e | |||
| a5076eddba | |||
| af11264841 | |||
| 5955c2d61f | |||
| 95e2bf4a21 | |||
| 6f22534b8d | |||
| 5d1123e7df | |||
| 5929b7b0ed | |||
| 0de95b1830 | |||
| ccaf926c6d | |||
| 26fbae8672 | |||
| cdbf1e147e | |||
| 5089e7df25 | |||
| 1de19731d0 | |||
| 0c0d1cc794 | |||
| 21b968a9c6 | |||
| d857e3acad | |||
| d7b0b3c898 | |||
| 704bb8668f | |||
| 763bfb4bb1 | |||
| e43173de60 | |||
| 16706cd483 | |||
| 505c86399c | |||
| 65ddff13f7 | |||
| bedd105649 | |||
| c10e57ec3b | |||
| 046bd37fd5 | |||
| 55c3e7aba6 | |||
| fbf83b3aed | |||
| ea60795e84 | |||
| 4a5e262695 | |||
| 22097987dc | |||
| 94617e573f | |||
| 98d02d7532 | |||
| 7b44e7a3f6 | |||
| aa4cf37aed | |||
| caf2b4e736 | |||
| da516a0167 | |||
| 1267a64fcb | |||
| 17d1cf673d | |||
| 74c3b9fbcb | |||
| 1fcd70b0d0 | |||
| 832941494b | |||
| 581d812cd4 | |||
| 56dbf6bbe1 | |||
| d5792fb02a | |||
| 0e82cb977e | |||
| e9237146a3 | |||
| b72899b500 | |||
| 2f39e38264 | |||
| db54672fbd | |||
| 30d9a7d703 | |||
| 4ade4c78ba | |||
| 5a088a740e | |||
| be220b6c1d | |||
| e194d6073f | |||
| 38858cd01f | |||
| 645c4a4dbc | |||
| c2a630dabd | |||
| 30cf1f5713 | |||
| 33f7a7084c | |||
| 07a14bc176 | |||
| 3c57177a11 | |||
| 7cc18b8082 | |||
| bef805d694 | |||
| 3479d08193 | |||
| 8ca9474c1c | |||
| 2047e818ef | |||
| 94b8835340 | |||
| 801b4f01b6 | |||
| 37a5f68339 | |||
| 6058160160 | |||
| 479dfc28b9 | |||
| e3cee8dafe | |||
| 3686e0de63 | |||
| 2931b32ce6 | |||
| b28e66fe6d | |||
| 8392788cdb | |||
| 42221f406a | |||
| 4a1b23c52a | |||
| 1965e24860 | |||
| 55242edbde | |||
| ebbe64f279 | |||
| 654f752ca0 | |||
| 2c5d9f043e | |||
| 2b2acbe244 | |||
| 7d4e103660 | |||
| 3da6187782 | |||
| 0bd79d518f | |||
| 86798ae9ea | |||
| dfc63de745 | |||
| 2830e6d199 | |||
| 8c07573f8f | |||
| f3af0262df | |||
| d7841b47a5 | |||
| 0f270a6a35 | |||
| 92db68c36b | |||
| 8432f46e48 | |||
| faccee88e7 | |||
| e8246b3b90 | |||
| eb4e7b9281 | |||
| e91bcdfe5e | |||
| 1f1a068d45 | |||
| a57c0a8c7d | |||
| 1e2190a6cc | |||
| 1644c1dc5b | |||
| 5fc3798c42 | |||
| dcae8eb7f3 | |||
| 3fa5c92058 | |||
| 512c3c57e9 | |||
| 3a95e75c2d | |||
| fae81cdb1b | |||
| dbae37f151 | |||
| f7d58a0b76 | |||
| 08fb458aa4 | |||
| 2e0d1fd90f | |||
| 3694e9649d | |||
| 2c79fa6492 | |||
| 69e6497655 | |||
| aca713b8d9 | |||
| ef8c7c9084 | |||
| e4eaf72769 | |||
| 5138ced630 | |||
| 534f445f8c | |||
| a2da4f0e77 | |||
| b2cc3e5329 | |||
| efde321616 | |||
| 3a61581608 | |||
| 3a10f0b946 | |||
| 6dc6972bdb | |||
| 5910c9502a | |||
| 405235a59d | |||
| bd8ac9551c | |||
| 9c148625e4 | |||
| a08d82a3d9 | |||
| 48b51f21de | |||
| c850ea10f5 | |||
| 3260279cb7 | |||
| 2fcef1f277 | |||
| 1e60a0a32a | |||
| 7707522ce5 | |||
| 91e26adc6a | |||
| 146f869aaa | |||
| dc863be38a | |||
| 0bd35727f4 | |||
| 5183fcc316 | |||
| bf7596521c | |||
| a85b6c960c | |||
| 8cb91477cf | |||
| a8a69fdbca | |||
| a1aebf0575 | |||
| 0f42218681 | |||
| 5404f9ef19 | |||
| 8a84be19df | |||
| fd4ea81e46 | |||
| 025bd3237c | |||
| 3df79aedb1 | |||
| 4141aa35ba | |||
| 6e45ee558e | |||
| e867ce4c73 | |||
| db138be5f3 | |||
| 5ae6495e69 | |||
| d8281b0103 | |||
| 58f1bc5531 | |||
| 935b52c489 | |||
| aace98545a | |||
| f39236c8fd | |||
| 43b4a6834b | |||
| aa3978b04d | |||
| b51e758516 | |||
| 5a92fb1f65 | |||
| 4a870d0183 | |||
| 5be1832c09 | |||
| 362652ac6c | |||
| aec69884dc | |||
| 47ddc64239 | |||
| 2a32be40f6 | |||
| 8fb4d4c7c6 | |||
| b17748cfeb | |||
| b5524b9a69 | |||
| ec0ff7afcd | |||
| c761d5020b | |||
| d0cf68c2e2 | |||
| 3beb98a5e9 | |||
| dd83ebe0e2 | |||
| 6fdf3957bd | |||
| 608fa1ca6c | |||
| f3d586d455 | |||
| c2f0ce0fa2 | |||
| 8535c39c2c | |||
| da8f023f5b | |||
| 354490d149 | |||
| c5b0be5d21 | |||
| 7123562f85 | |||
| 6301f5a939 | |||
| 5e8676cf8a | |||
| b320ba597a | |||
| 12d16f355b | |||
| f4264cd149 | |||
| 65d1c4187e | |||
| 58b169cb03 | |||
| 04fcb985fe | |||
| 5414adc8d8 | |||
| 95210366e9 | |||
| f73dc6c3be | |||
| 74383e3111 | |||
| 3f8954d21d | |||
| ebf393c58b | |||
| cda1432d04 | |||
| 09e5fca1a5 | |||
| 9c3c27f809 | |||
| 03275938e7 | |||
| a90e321a91 | |||
| e38d73df8b | |||
| 7df90b2d30 | |||
| e531f12f94 | |||
| a917ee8f1e | |||
| 44f3c776eb | |||
| 5d3f5dac1e | |||
| 873879ff79 | |||
| 2f46a63041 | |||
| 6c1907f7e0 | |||
| f43dd389e2 | |||
| 42ecdd3b14 | |||
| 297164a0ee | |||
| 323f09b06c | |||
| e003b91bbe | |||
| efb1d79e77 | |||
| ee23daa651 | |||
| e590701c01 | |||
| 9f1777ca9b | |||
| f1636fc5a9 | |||
| db053398d2 | |||
| 527c4d0de4 | |||
| 2479147fd9 | |||
| 7b7a8c1bab | |||
| 417caf1b62 | |||
| 2ff630d394 | |||
| b6d725920a | |||
| cc76851614 | |||
| 1ddb2aaec0 | |||
| c30d517a00 | |||
| 27fe314c1e | |||
| 2c12cfe4fd | |||
| 9adf342dd8 | |||
| facac82130 | |||
| c96249906e | |||
| 3e095711f4 | |||
| ca1ddb4013 | |||
| 8169235d2f | |||
| 0a56624657 | |||
| 4553785918 | |||
| 78b3efcf00 | |||
| 67cb844550 | |||
| 1b169f007c | |||
| 8efcad123c | |||
| b9cf1e46af | |||
| 47409b11cb | |||
| 4a04d7cce7 | |||
| 5ef6383c63 | |||
| 35f15435a3 | |||
| 38dcd04334 | |||
| b32a8a2899 | |||
| 8cac4dbf9e | |||
| ef2bec72a1 | |||
| 03c3a20b1b | |||
| 4c97d966a2 | |||
| f075c02db5 | |||
| 32e61a7a09 | |||
| 674c6eec81 | |||
| da535eaddc | |||
| c75c3d8777 | |||
| 4fa63986dc | |||
| 029f7a3f5c | |||
| eb6eba25f3 | |||
| e9194c59f4 | |||
| c8d693c8e7 | |||
| 72d8b29699 | |||
| 2341014882 | |||
| eb15fadcfe | |||
| 1e7203320f | |||
| e3b587aa95 | |||
| 13fa8f373e | |||
| c3dc055fb2 | |||
| 4e024f7173 | |||
| 1248006f3d | |||
| b318e8ae13 | |||
| 1af4d44ca5 | |||
| 7e963bef41 | |||
| 933000ffc7 | |||
| 9b217e1c97 | |||
| 324f4944b4 | |||
| b9d738c1ad | |||
| f735f1eb53 | |||
| 7149226283 | |||
| 4164a5288a | |||
| 1992276b07 | |||
| 21a7023b7b | |||
| bd054b26c0 | |||
| f094716b73 | |||
| b3100d2f5e | |||
| 4b1888a34d | |||
| f4cd214823 | |||
| b9be3aa7f8 | |||
| bb6db2b7f8 | |||
| fd2dacaefb | |||
| cc60e85901 | |||
| 8d6d4b3765 | |||
| 7ddb72885d | |||
| 1d04f7c945 | |||
| baa3f3234e | |||
| ab5a4c43c3 | |||
| ce61f454bf | |||
| 4772dafe7f | |||
| 629925e2d8 | |||
| 17f672dad4 | |||
| 0a9717a8c2 | |||
| e0049bea2b | |||
| 73525feb68 | |||
| 7d3bffcfef | |||
| 5b76ec6b99 | |||
| eeca95c77f | |||
| e406fb0c88 |
@@ -105,7 +105,7 @@ jobs:
|
||||
if: ${{ vars.SKIP_LUACHECK != true }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
luacheck --std=lua51c --config=.luacheckrc -gurasqq "Moose Development/Moose"
|
||||
luacheck --std=lua51c --config=.luacheckrc -guras "Moose Development/Moose"
|
||||
|
||||
- name: Run LuaSrcDiet
|
||||
run: |
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
--
|
||||
-- @module Core.ClientMenu
|
||||
-- @image Core_Menu.JPG
|
||||
-- last change: Jan 2025
|
||||
-- last change: Sept 2025
|
||||
|
||||
-- TODO
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
@@ -417,7 +417,7 @@ end
|
||||
CLIENTMENUMANAGER = {
|
||||
ClassName = "CLIENTMENUMANAGER",
|
||||
lid = "",
|
||||
version = "0.1.6",
|
||||
version = "0.1.7",
|
||||
name = nil,
|
||||
clientset = nil,
|
||||
menutree = {},
|
||||
@@ -806,6 +806,16 @@ function CLIENTMENUMANAGER:ResetMenuComplete()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove the entry and all entries below the given entry from the client's F10 menus.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry to remove
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:DeleteEntry(Entry,Client)
|
||||
self:T(self.lid.."DeleteEntry")
|
||||
return self:DeleteF10Entry(Entry,Client)
|
||||
end
|
||||
|
||||
--- Remove the entry and all entries below the given entry from the client's F10 menus.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry to remove
|
||||
|
||||
@@ -577,13 +577,19 @@ do -- Zones and Pathlines
|
||||
-- For a rectangular polygon drawing, we have the width (y) and height (x).
|
||||
local w=objectData.width
|
||||
local h=objectData.height
|
||||
local rotation = UTILS.ToRadian(objectData.angle or 0)
|
||||
|
||||
-- Create points from center using with and height (width for y and height for x is a bit confusing, but this is how ED implemented it).
|
||||
local points={}
|
||||
points[1]={x=vec2.x-h/2, y=vec2.y+w/2} --Upper left
|
||||
points[2]={x=vec2.x+h/2, y=vec2.y+w/2} --Upper right
|
||||
points[3]={x=vec2.x+h/2, y=vec2.y-w/2} --Lower right
|
||||
points[4]={x=vec2.x-h/2, y=vec2.y-w/2} --Lower left
|
||||
local sinRot = math.sin(rotation)
|
||||
local cosRot = math.cos(rotation)
|
||||
local dx = h / 2
|
||||
local dy = w / 2
|
||||
|
||||
local points = {
|
||||
{ x = -dx * cosRot - (-dy * sinRot) + vec2.x, y = -dx * sinRot + (-dy * cosRot) + vec2.y },
|
||||
{ x = dx * cosRot - (-dy * sinRot) + vec2.x, y = dx * sinRot + (-dy * cosRot) + vec2.y },
|
||||
{ x = dx * cosRot - (dy * sinRot) + vec2.x, y = dx * sinRot + (dy * cosRot) + vec2.y },
|
||||
{ x = -dx * cosRot - (dy * sinRot) + vec2.x, y = -dx * sinRot + (dy * cosRot) + vec2.y },
|
||||
}
|
||||
|
||||
--local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY")
|
||||
|
||||
@@ -1814,7 +1820,7 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
-- Player entering a CA slot
|
||||
if Event.IniObjectCategory == 1 and Event.IniGroup and Event.IniGroup:IsGround() then
|
||||
if Event.IniObjectCategory == 1 and Event.IniUnit and Event.IniUnit:IsGround() then
|
||||
|
||||
local IsPlayer = Event.IniDCSUnit:getPlayerName()
|
||||
if IsPlayer then
|
||||
|
||||
@@ -184,6 +184,7 @@ EVENT = {
|
||||
ClassName = "EVENT",
|
||||
ClassID = 0,
|
||||
MissionEnd = false,
|
||||
CreateMarkCoordinateOnEvent = false,
|
||||
}
|
||||
|
||||
world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
|
||||
@@ -339,14 +340,18 @@ EVENTS = {
|
||||
-- @field #string WeaponName Name of the weapon.
|
||||
-- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon.
|
||||
--
|
||||
-- @field Cargo.Cargo#CARGO Cargo The cargo object.
|
||||
-- @field #string CargoName The name of the cargo object.
|
||||
--
|
||||
-- @field Core.Zone#ZONE Zone The zone object.
|
||||
-- @field #string ZoneName The name of the zone.
|
||||
--
|
||||
-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object.
|
||||
-- @field #string IniDynamicCargoName The dynamic cargo unit name.
|
||||
--
|
||||
-- @field #number MarkCoalition Coalition of a marker (if any)
|
||||
-- @field DCS#Vec3 MarkVec3 Position of a marker
|
||||
-- @field #string MarkText Text content of a marker, if any
|
||||
-- @field #number MarkID ID of the marker, for deletion
|
||||
-- @field #number MarkGroupID Group ID of the group that created the marker
|
||||
-- @field Core.Point#COORDINATE Coordinate object of the marker, only filled if EVENT.CreateMarkCoordinateOnEvent is set to true (off by default)
|
||||
|
||||
|
||||
|
||||
@@ -1063,36 +1068,6 @@ end
|
||||
|
||||
do -- Event Creation
|
||||
|
||||
--- Creation of a New Cargo Event.
|
||||
-- @param #EVENT self
|
||||
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
|
||||
function EVENT:CreateEventNewCargo( Cargo )
|
||||
self:F( { Cargo } )
|
||||
|
||||
local Event = {
|
||||
id = EVENTS.NewCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a Cargo Deletion Event.
|
||||
-- @param #EVENT self
|
||||
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
|
||||
function EVENT:CreateEventDeleteCargo( Cargo )
|
||||
self:F( { Cargo } )
|
||||
|
||||
local Event = {
|
||||
id = EVENTS.DeleteCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a New Zone Event.
|
||||
-- @param #EVENT self
|
||||
-- @param Core.Zone#ZONE_BASE Zone The Zone created.
|
||||
@@ -1361,7 +1336,8 @@ function EVENT:onEvent( Event )
|
||||
Event.IniDynamicCargoName = Event.IniUnitName
|
||||
Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+")
|
||||
else
|
||||
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||
--Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||
Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false )
|
||||
end
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
@@ -1374,7 +1350,9 @@ function EVENT:onEvent( Event )
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = ( Event.IniDCSUnit and Event.IniDCSUnit.getName ) and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000)
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
|
||||
local ID = (Event.IniDCSUnit and Event.IniDCSUnit.getID) and Event.IniDCSUnit:getID() or Event.IniDCSUnitName
|
||||
Event.IniUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil
|
||||
--Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
|
||||
Event.IniCategory = (Event.IniDCSUnit and Event.IniDCSUnit.getDesc ) and Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = (Event.initiator and Event.initiator.isExist
|
||||
and Event.initiator:isExist() and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName) and Event.IniDCSUnit:getTypeName() or "SCENERY"
|
||||
@@ -1475,10 +1453,12 @@ function EVENT:onEvent( Event )
|
||||
-- SCENERY
|
||||
---
|
||||
Event.TgtDCSUnit = Event.target
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName() or nil
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or nil
|
||||
if Event.TgtDCSUnitName~=nil then
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
local ID = (Event.TgtDCSUnit and Event.TgtDCSUnit.getID) and Event.TgtDCSUnit:getID() or Event.TgtDCSUnitName
|
||||
--Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
Event.TgtUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
end
|
||||
@@ -1508,7 +1488,9 @@ function EVENT:onEvent( Event )
|
||||
else
|
||||
if Event.place:isExist() and Object.getCategory(Event.place) ~= Object.Category.SCENERY then
|
||||
Event.Place=AIRBASE:Find(Event.place)
|
||||
Event.PlaceName=Event.Place:GetName()
|
||||
if Event.Place then
|
||||
Event.PlaceName=Event.Place:GetName()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1517,7 +1499,9 @@ function EVENT:onEvent( Event )
|
||||
if Event.idx then
|
||||
Event.MarkID=Event.idx
|
||||
Event.MarkVec3=Event.pos
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
if self.CreateMarkCoordinateOnEvent == true then
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
end
|
||||
Event.MarkText=Event.text
|
||||
Event.MarkCoalition=Event.coalition
|
||||
Event.IniCoalition=Event.coalition
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
|
||||
do -- FSM
|
||||
|
||||
--- FSM class
|
||||
-- @type FSM
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field Core.Scheduler#SCHEDULER CallScheduler Call scheduler.
|
||||
|
||||
@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.1.4",
|
||||
version = "0.1.5",
|
||||
debug = false,
|
||||
Casesensitive = true,
|
||||
}
|
||||
@@ -59,9 +59,8 @@ MARKEROPS_BASE = {
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Tagname Name to identify us from the event text.
|
||||
-- @param #table Keywords Table of keywords recognized from the event text.
|
||||
-- @param #boolean Casesensitive (Optional) Switch case sensitive identification of Tagname. Defaults to true.
|
||||
-- @return #MARKEROPS_BASE self
|
||||
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
function MARKEROPS_BASE:New(Tagname,Keywords)
|
||||
-- Inherit FSM
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
|
||||
|
||||
@@ -72,11 +71,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
|
||||
self.debug = false
|
||||
self.Casesensitive = true
|
||||
|
||||
if Casesensitive and Casesensitive == false then
|
||||
self.Casesensitive = false
|
||||
end
|
||||
|
||||
|
||||
-----------------------
|
||||
--- FSM Transitions ---
|
||||
-----------------------
|
||||
@@ -145,7 +140,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
|
||||
end
|
||||
|
||||
--- (internal) Handle events.
|
||||
--- (Internal) Handle events.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function MARKEROPS_BASE:OnEventMark(Event)
|
||||
@@ -201,15 +196,17 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
||||
end
|
||||
end
|
||||
|
||||
--- (internal) Match tag.
|
||||
--- (Internal) Match tag.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #boolean
|
||||
function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||
local matches = false
|
||||
if not self.Casesensitive then
|
||||
--self:I(self.lid .. "Casesensitive "..tostring(self.Casesensitive))
|
||||
if self.Casesensitive == false then
|
||||
--self:I(self.lid .. "Marker non-casesensitive "..Eventtext)
|
||||
local type = string.lower(self.Tag) -- #string
|
||||
if string.find(string.lower(Eventtext),type) then
|
||||
if string.find(string.lower(Eventtext),type,1,true) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
else
|
||||
@@ -221,7 +218,7 @@ function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||
return matches
|
||||
end
|
||||
|
||||
--- (internal) Match keywords table.
|
||||
--- (Internal) Match keywords table.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #table
|
||||
@@ -286,6 +283,22 @@ function MARKEROPS_BASE:onenterStopped(From,Event,To)
|
||||
self:UnHandleEvent(EVENTS.MarkRemoved)
|
||||
end
|
||||
|
||||
--- Switch off case sensitive matching
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @return self
|
||||
function MARKEROPS_BASE:SwitchCaseSensitiveOff()
|
||||
self.Casesensitive = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch on case sensitive matching
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @return self
|
||||
function MARKEROPS_BASE:SwitchCaseSensitiveOn()
|
||||
self.Casesensitive = true
|
||||
return self
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition End.
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
--
|
||||
-- # Mark on F10 map
|
||||
--
|
||||
-- The ponints of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
|
||||
-- The points of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
|
||||
-- water depth.
|
||||
--
|
||||
-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`).
|
||||
@@ -69,11 +69,12 @@ PATHLINE = {
|
||||
-- @field #number landHeight Land height in meters.
|
||||
-- @field #number depth Water depth in meters.
|
||||
-- @field #number markerID Marker ID.
|
||||
-- @field #number lineID Line marker ID.
|
||||
|
||||
|
||||
--- PATHLINE class version.
|
||||
-- @field #string version
|
||||
PATHLINE.version="0.1.1"
|
||||
PATHLINE.version="0.2.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -301,18 +302,42 @@ function PATHLINE:GetPoint2DFromIndex(n)
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Calculate the length of the line.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #boolean Project2D Calculate 2D distance between points.
|
||||
-- @return #number Length in meters.
|
||||
function PATHLINE:GetLength(Project2D)
|
||||
local l=0
|
||||
|
||||
local np=#self.points
|
||||
|
||||
for i=1,np-1 do
|
||||
local p1=self.points[i] --#PATHLINE.Point
|
||||
local p2=self.points[i+1] --#PATHLINE.Point
|
||||
|
||||
if Project2D then
|
||||
l=l+UTILS.VecDist2D(p1.vec2, p2.vec2)
|
||||
else
|
||||
l=l+UTILS.VecDist3D(p1.vec3, p2.vec3)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return l
|
||||
end
|
||||
|
||||
|
||||
--- Mark points on F10 map.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #boolean Switch If `true` or nil, set marks. If `false`, remove marks.
|
||||
-- @return <DCS#Vec3> List of DCS#Vec3 points.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:MarkPoints(Switch)
|
||||
for i,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
if Switch==false then
|
||||
|
||||
if point.markerID then
|
||||
UTILS.RemoveMark(point.markerID, Delay)
|
||||
UTILS.RemoveMark(point.markerID)
|
||||
end
|
||||
|
||||
else
|
||||
@@ -329,6 +354,56 @@ function PATHLINE:MarkPoints(Switch)
|
||||
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Draw line on F10 map.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #number Recipient Recipent of the line: -1=All.
|
||||
-- @param #table Color Color as RGB table plus alpha value. Default {1, 0, 0, 1.0}.
|
||||
-- @param #number LineType Line type: 1=Solid (default).
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:DrawLine(Recipient, Color, LineType)
|
||||
|
||||
-- Input
|
||||
Recipient= Recipient or -1
|
||||
Color= Color or {1,0,0, 1.0}
|
||||
LineType=LineType or 1
|
||||
local ReadOnly=false
|
||||
|
||||
|
||||
local np=#self.points
|
||||
|
||||
for i=1,np-1 do
|
||||
local p1=self.points[i] --#PATHLINE.Point
|
||||
local p2=self.points[i+1] --#PATHLINE.Point
|
||||
|
||||
p1.lineID = UTILS.GetMarkID()
|
||||
|
||||
trigger.action.lineToAll(Recipient, p1.lineID, p1.vec3, p2.vec3, Color, LineType, ReadOnly, "")
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove line on F10 map.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #number Delay Delay in seconds before line is removed.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:UnDrawLine(Delay)
|
||||
|
||||
|
||||
local np=#self.points
|
||||
|
||||
for _,_point in pairs(self.points) do
|
||||
local p=_point --#PATHLINE.Point
|
||||
if p.lineID then
|
||||
UTILS.RemoveMark(p.lineID, Delay)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provides a COORDINATE class, which allows to manage points in 3D space and perform various operations on it.
|
||||
-- * Provides a POINT\_VEC2 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a Lat/Lon and Altitude perspective.
|
||||
-- * Provides a POINT\_VEC3 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a X, Z and Y vector perspective.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -777,7 +775,9 @@ do -- COORDINATE
|
||||
-- @return DCS#Vec2 Vec2
|
||||
function COORDINATE:GetRandomVec2InRadius( OuterRadius, InnerRadius )
|
||||
self:F2( { OuterRadius, InnerRadius } )
|
||||
|
||||
math.random()
|
||||
math.random()
|
||||
math.random()
|
||||
local Theta = 2 * math.pi * math.random()
|
||||
local Radials = math.random() + math.random()
|
||||
if Radials > 1 then
|
||||
@@ -1635,6 +1635,7 @@ do -- COORDINATE
|
||||
if AirbaseCategory == Airbase.Category.SHIP or AirbaseCategory == Airbase.Category.HELIPAD then
|
||||
RoutePoint.linkUnit = AirbaseID
|
||||
RoutePoint.helipadId = AirbaseID
|
||||
RoutePoint.airdromeId = airbase:IsAirdrome() and AirbaseID or nil
|
||||
elseif AirbaseCategory == Airbase.Category.AIRDROME then
|
||||
RoutePoint.airdromeId = AirbaseID
|
||||
else
|
||||
@@ -2057,6 +2058,40 @@ do -- COORDINATE
|
||||
|
||||
return Path, Way, GotPath
|
||||
end
|
||||
|
||||
--- Returns a table of coordinates to a destination using only roads or railroads.
|
||||
-- The first point is the closest point on road of the given coordinate.
|
||||
-- By default, the last point is the closest point on road of the ToCoord. Hence, the coordinate itself and the final ToCoord are not necessarily included in the path.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE ToCoord Coordinate of destination.
|
||||
-- @param #boolean IncludeEndpoints (Optional) Include the coordinate itself and the ToCoordinate in the path.
|
||||
-- @param #boolean Railroad (Optional) If true, path on railroad is returned. Default false.
|
||||
-- @return Core.Pathline#PATHLINE Pathline containing the points on road. If no path on road can be found, nil is returned or just the endpoints.
|
||||
function COORDINATE:GetPathlineOnRoad(ToCoord, IncludeEndpoints, Railroad)
|
||||
|
||||
-- Set road type.
|
||||
local RoadType="roads"
|
||||
if Railroad==true then
|
||||
RoadType="railroads"
|
||||
end
|
||||
|
||||
-- DCS API function returning a table of vec2.
|
||||
local path = land.findPathOnRoads(RoadType, self.x, self.z, ToCoord.x, ToCoord.z)
|
||||
|
||||
if IncludeEndpoints then
|
||||
path=path or {}
|
||||
table.insert(path, 1, self:GetVec2())
|
||||
table.insert(path, ToCoord:GetVec2())
|
||||
end
|
||||
|
||||
local pathline=nil
|
||||
if path then
|
||||
pathline=PATHLINE:NewFromVec2Array(RoadType, path)
|
||||
end
|
||||
|
||||
return pathline
|
||||
end
|
||||
|
||||
|
||||
--- Gets the surface type at the coordinate.
|
||||
-- @param #COORDINATE self
|
||||
@@ -3246,7 +3281,6 @@ do -- COORDINATE
|
||||
return delta/60
|
||||
end
|
||||
|
||||
|
||||
--- Return a BR string from a COORDINATE to the COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||
@@ -3260,6 +3294,20 @@ do -- COORDINATE
|
||||
local Distance = self:Get2DDistance( FromCoordinate )
|
||||
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision )
|
||||
end
|
||||
|
||||
--- Return a Bearing string from a COORDINATE to the (self) COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||
-- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object.
|
||||
-- @param #boolean MagVar If true, also get angle in MagVar for BR/BRA
|
||||
-- @param #number Precision Rounding precision, currently full km as default (=0)
|
||||
-- @return #string The BR text.
|
||||
function COORDINATE:ToStringBearing( FromCoordinate, Settings, MagVar, Precision )
|
||||
local DirectionVec3 = FromCoordinate:GetDirectionVec3( self )
|
||||
local AngleRadians = self:GetAngleRadians( DirectionVec3 )
|
||||
--local Distance = self:Get2DDistance( FromCoordinate )
|
||||
return self:GetBearingText(AngleRadians,Precision,Settings,MagVar)
|
||||
end
|
||||
|
||||
--- Return a BRA string from a COORDINATE to the COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
@@ -3838,171 +3886,3 @@ do -- COORDINATE
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do
|
||||
|
||||
--- The POINT_VEC3 class
|
||||
-- @type POINT_VEC3
|
||||
-- @field #number x The x coordinate in 3D space.
|
||||
-- @field #number y The y coordinate in 3D space.
|
||||
-- @field #number z The z COORDINATE in 3D space.
|
||||
-- @field Utilities.Utils#SMOKECOLOR SmokeColor
|
||||
-- @field Utilities.Utils#FLARECOLOR FlareColor
|
||||
-- @field #POINT_VEC3.RoutePointAltType RoutePointAltType
|
||||
-- @field #POINT_VEC3.RoutePointType RoutePointType
|
||||
-- @field #POINT_VEC3.RoutePointAction RoutePointAction
|
||||
-- @extends #COORDINATE
|
||||
|
||||
|
||||
--- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space.
|
||||
--
|
||||
-- **DEPRECATED - PLEASE USE COORDINATE!**
|
||||
--
|
||||
-- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts.
|
||||
-- In order to keep the credibility of the the author,
|
||||
-- I want to emphasize that the formulas embedded in the MIST framework were created by Grimes or previous authors,
|
||||
-- who you can find on the Eagle Dynamics Forums.
|
||||
--
|
||||
--
|
||||
-- ## POINT_VEC3 constructor
|
||||
--
|
||||
-- A new POINT_VEC3 object can be created with:
|
||||
--
|
||||
-- * @{#POINT_VEC3.New}(): a 3D point.
|
||||
-- * @{#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{DCS#Vec3}.
|
||||
--
|
||||
--
|
||||
-- ## Manupulate the X, Y, Z coordinates of the POINT_VEC3
|
||||
--
|
||||
-- A POINT_VEC3 class works in 3D space. It contains internally an X, Y, Z coordinate.
|
||||
-- Methods exist to manupulate these coordinates.
|
||||
--
|
||||
-- The current X, Y, Z axis can be retrieved with the methods @{#POINT_VEC3.GetX}(), @{#POINT_VEC3.GetY}(), @{#POINT_VEC3.GetZ}() respectively.
|
||||
-- The methods @{#POINT_VEC3.SetX}(), @{#POINT_VEC3.SetY}(), @{#POINT_VEC3.SetZ}() change the respective axis with a new value.
|
||||
-- The current axis values can be changed by using the methods @{#POINT_VEC3.AddX}(), @{#POINT_VEC3.AddY}(), @{#POINT_VEC3.AddZ}()
|
||||
-- to add or substract a value from the current respective axis value.
|
||||
-- Note that the Set and Add methods return the current POINT_VEC3 object, so these manipulation methods can be chained... For example:
|
||||
--
|
||||
-- local Vec3 = PointVec3:AddX( 100 ):AddZ( 150 ):GetVec3()
|
||||
--
|
||||
--
|
||||
-- ## 3D calculation methods
|
||||
--
|
||||
-- Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method:
|
||||
--
|
||||
--
|
||||
-- ## Point Randomization
|
||||
--
|
||||
-- Various methods exist to calculate random locations around a given 3D point.
|
||||
--
|
||||
-- * @{#POINT_VEC3.GetRandomPointVec3InRadius}(): Provides a random 3D point around the current 3D point, in the given inner to outer band.
|
||||
--
|
||||
--
|
||||
-- @field #POINT_VEC3
|
||||
POINT_VEC3 = {
|
||||
ClassName = "POINT_VEC3",
|
||||
Metric = true,
|
||||
RoutePointAltType = {
|
||||
BARO = "BARO",
|
||||
},
|
||||
RoutePointType = {
|
||||
TakeOffParking = "TakeOffParking",
|
||||
TurningPoint = "Turning Point",
|
||||
},
|
||||
RoutePointAction = {
|
||||
FromParkingArea = "From Parking Area",
|
||||
TurningPoint = "Turning Point",
|
||||
},
|
||||
}
|
||||
|
||||
--- RoutePoint AltTypes
|
||||
-- @type POINT_VEC3.RoutePointAltType
|
||||
-- @field BARO "BARO"
|
||||
|
||||
--- RoutePoint Types
|
||||
-- @type POINT_VEC3.RoutePointType
|
||||
-- @field TakeOffParking "TakeOffParking"
|
||||
-- @field TurningPoint "Turning Point"
|
||||
|
||||
--- RoutePoint Actions
|
||||
-- @type POINT_VEC3.RoutePointAction
|
||||
-- @field FromParkingArea "From Parking Area"
|
||||
-- @field TurningPoint "Turning Point"
|
||||
|
||||
-- Constructor.
|
||||
|
||||
--- Create a new POINT_VEC3 object.
|
||||
-- @param #POINT_VEC3 self
|
||||
-- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North.
|
||||
-- @param DCS#Distance y The y coordinate of the Vec3 point, pointing Upwards.
|
||||
-- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the Right.
|
||||
-- @return Core.Point#POINT_VEC3
|
||||
function POINT_VEC3:New( x, y, z )
|
||||
|
||||
local self = BASE:Inherit( self, COORDINATE:New( x, y, z ) ) -- Core.Point#POINT_VEC3
|
||||
self:F2( self )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do
|
||||
|
||||
--- @type POINT_VEC2
|
||||
-- @field DCS#Distance x The x coordinate in meters.
|
||||
-- @field DCS#Distance y the y coordinate in meters.
|
||||
-- @extends Core.Point#COORDINATE
|
||||
|
||||
--- Defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified.
|
||||
--
|
||||
-- **DEPRECATED - PLEASE USE COORDINATE!**
|
||||
--
|
||||
-- ## POINT_VEC2 constructor
|
||||
--
|
||||
-- A new POINT_VEC2 instance can be created with:
|
||||
--
|
||||
-- * @{Core.Point#POINT_VEC2.New}(): a 2D point, taking an additional height parameter.
|
||||
-- * @{Core.Point#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{DCS#Vec2}.
|
||||
--
|
||||
-- ## Manupulate the X, Altitude, Y coordinates of the 2D point
|
||||
--
|
||||
-- A POINT_VEC2 class works in 2D space, with an altitude setting. It contains internally an X, Altitude, Y coordinate.
|
||||
-- Methods exist to manupulate these coordinates.
|
||||
--
|
||||
-- The current X, Altitude, Y axis can be retrieved with the methods @{#POINT_VEC2.GetX}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetY}() respectively.
|
||||
-- The methods @{#POINT_VEC2.SetX}(), @{#POINT_VEC2.SetAlt}(), @{#POINT_VEC2.SetY}() change the respective axis with a new value.
|
||||
-- The current Lat(itude), Alt(itude), Lon(gitude) values can also be retrieved with the methods @{#POINT_VEC2.GetLat}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetLon}() respectively.
|
||||
-- The current axis values can be changed by using the methods @{#POINT_VEC2.AddX}(), @{#POINT_VEC2.AddAlt}(), @{#POINT_VEC2.AddY}()
|
||||
-- to add or substract a value from the current respective axis value.
|
||||
-- Note that the Set and Add methods return the current POINT_VEC2 object, so these manipulation methods can be chained... For example:
|
||||
--
|
||||
-- local Vec2 = PointVec2:AddX( 100 ):AddY( 2000 ):GetVec2()
|
||||
--
|
||||
-- @field #POINT_VEC2
|
||||
POINT_VEC2 = {
|
||||
ClassName = "POINT_VEC2",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- POINT_VEC2 constructor.
|
||||
-- @param #POINT_VEC2 self
|
||||
-- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North.
|
||||
-- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right.
|
||||
-- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height.
|
||||
-- @return Core.Point#POINT_VEC2
|
||||
function POINT_VEC2:New( x, y, LandHeightAdd )
|
||||
|
||||
local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } )
|
||||
|
||||
LandHeightAdd = LandHeightAdd or 0
|
||||
LandHeight = LandHeight + LandHeightAdd
|
||||
|
||||
local self = BASE:Inherit( self, COORDINATE:New( x, LandHeight, y ) ) -- Core.Point#POINT_VEC2
|
||||
self:F2( self )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
+1293
-1348
File diff suppressed because it is too large
Load Diff
@@ -1049,6 +1049,23 @@ function SPAWN:InitSetUnitAbsolutePositions(Positions)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Uses Disposition and other fallback logic to find better ground positions for ground units.
|
||||
--- NOTE: This is not a spawn randomizer.
|
||||
--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area.
|
||||
--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit.
|
||||
-- @param #SPAWN self
|
||||
-- @param #boolean OnOff Enable/disable the feature.
|
||||
-- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the units.
|
||||
-- @param #number Spacing (Optional) Minimum spacing between units in meters. Default is 5% of the search radius or 5 meters, whichever is larger.
|
||||
-- @return #SPAWN
|
||||
function SPAWN:InitValidateAndRepositionGroundUnits(OnOff, MaxRadius, Spacing)
|
||||
self.SpawnValidateAndRepositionGroundUnits = OnOff
|
||||
self.SpawnValidateAndRepositionGroundUnitsRadius = MaxRadius
|
||||
self.SpawnValidateAndRepositionGroundUnitsSpacing = Spacing
|
||||
return self
|
||||
end
|
||||
|
||||
--- This method is rather complicated to understand. But I'll try to explain.
|
||||
-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
|
||||
-- but they will all follow the same Template route and have the same prefix name.
|
||||
@@ -1650,6 +1667,9 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
|
||||
|
||||
-- If RandomizeUnits, then Randomize the formation at the start point.
|
||||
if self.SpawnRandomizeUnits then
|
||||
if self.SpawnRandomizePosition then
|
||||
PointVec3 = COORDINATE:New( SpawnTemplate.x, SpawnTemplate.route.points[1].alt, SpawnTemplate.y )
|
||||
end
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
|
||||
if (SpawnZone) then
|
||||
@@ -1829,7 +1849,13 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
|
||||
if self.SpawnHiddenOnMap then
|
||||
SpawnTemplate.hidden=self.SpawnHiddenOnMap
|
||||
end
|
||||
|
||||
|
||||
if self.SpawnValidateAndRepositionGroundUnits then
|
||||
local units = SpawnTemplate.units
|
||||
local gPos = { x = SpawnTemplate.x, y = SpawnTemplate.y }
|
||||
UTILS.ValidateAndRepositionGroundUnits(units, gPos, self.SpawnValidateAndRepositionGroundUnitsRadius, self.SpawnValidateAndRepositionGroundUnitsSpacing)
|
||||
end
|
||||
|
||||
-- Set country, coalition and category.
|
||||
SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID
|
||||
SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID
|
||||
@@ -3495,6 +3521,11 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
|
||||
max = 18
|
||||
ctable = CALLSIGN.F15E
|
||||
end
|
||||
if SpawnTemplate.units[1].type == "A6E" then
|
||||
min = 4
|
||||
max = 18
|
||||
ctable = CALLSIGN.Intruder
|
||||
end
|
||||
local callsignnr = math.random(min,max)
|
||||
local callsignname = "Enfield"
|
||||
for name, value in pairs(ctable) do
|
||||
|
||||
@@ -334,6 +334,31 @@ function SPAWNSTATIC:InitCargo(IsCargo)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hide the static on the map view (visible to game master slots!).
|
||||
-- @param #SPAWN self
|
||||
-- @param #boolean OnOff Defaults to true
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWNSTATIC:InitHiddenOnMap(OnOff)
|
||||
self.SpawnHiddenOnMap = OnOff == false and false or true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hide the static on MFDs (visible to game master slots!).
|
||||
-- @param #SPAWN self
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWNSTATIC:InitHiddenOnMFD()
|
||||
self.SpawnHiddenOnMFD = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hide the static on planner (visible to game master slots!).
|
||||
-- @param #SPAWN self
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWNSTATIC:InitHiddenOnPlanner()
|
||||
self.SpawnHiddenOnPlanner = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize as dead.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #boolean IsDead If true, this static is dead.
|
||||
@@ -378,6 +403,20 @@ function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Uses Disposition and other fallback logic to find a better and valid ground spawn position.
|
||||
--- NOTE: This is not a spawn randomizer.
|
||||
--- It will try to a find clear ground location avoiding trees, water, roads, runways, map scenery, other statics and other units in the area.
|
||||
--- Uses the initial position if it's a valid location.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #boolean OnOff Enable/disable the feature.
|
||||
-- @param #number MaxRadius (Optional) Max radius to search for a valid ground location in meters. Default is 10 times the max radius of the static.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff, MaxRadius)
|
||||
self.ValidateAndRepositionStatic = OnOff
|
||||
self.ValidateAndRepositionStaticMaxRadius = MaxRadius
|
||||
return self
|
||||
end
|
||||
|
||||
--- Allows to place a CallFunction hook when a new static spawns.
|
||||
-- The provided method will be called when a new group is spawned, including its given parameters.
|
||||
-- The first parameter of the SpawnFunction is the @{Wrapper.Static#STATIC} that was spawned.
|
||||
@@ -519,6 +558,19 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
Template.mass=self.InitStaticCargoMass
|
||||
end
|
||||
|
||||
-- hiding options
|
||||
if self.SpawnHiddenOnPlanner then
|
||||
Template.hiddenOnPlanner=true
|
||||
end
|
||||
|
||||
if self.SpawnHiddenOnMFD then
|
||||
Template.hiddenOnMFD=true
|
||||
end
|
||||
|
||||
if self.SpawnHiddenOnMap then
|
||||
Template.hidden=self.SpawnHiddenOnMap
|
||||
end
|
||||
|
||||
if self.InitLinkUnit then
|
||||
Template.linkUnit=self.InitLinkUnit:GetID()
|
||||
Template.linkOffset=true
|
||||
@@ -544,6 +596,14 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
-- Add static to the game.
|
||||
local Static=nil --DCS#StaticObject
|
||||
|
||||
if self.ValidateAndRepositionStatic then
|
||||
local validPos = UTILS.ValidateAndRepositionStatic(CountryID, Template.category, Template.type, Template, Template.shape_name, self.ValidateAndRepositionStaticMaxRadius)
|
||||
if validPos then
|
||||
Template.x = validPos.x
|
||||
Template.y = validPos.y
|
||||
end
|
||||
end
|
||||
|
||||
if self.InitFarp then
|
||||
|
||||
local TemplateGroup={}
|
||||
@@ -611,16 +671,15 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
|
||||
if self.StaticCopyFrom ~= nil then
|
||||
mystatic.StaticCopyFrom = self.StaticCopyFrom
|
||||
if not _DATABASE.Templates.Statics[Template.name] then
|
||||
local TemplateGroup={}
|
||||
TemplateGroup.units={}
|
||||
TemplateGroup.units[1]=Template
|
||||
TemplateGroup.x=Template.x
|
||||
TemplateGroup.y=Template.y
|
||||
TemplateGroup.name=Template.name
|
||||
_DATABASE:_RegisterStaticTemplate( TemplateGroup, self.CoalitionID, self.CategoryID, CountryID )
|
||||
end
|
||||
end
|
||||
|
||||
local TemplateGroup={}
|
||||
TemplateGroup.units={}
|
||||
TemplateGroup.units[1]=Template
|
||||
TemplateGroup.x=Template.x
|
||||
TemplateGroup.y=Template.y
|
||||
TemplateGroup.name=Template.name
|
||||
_DATABASE:_RegisterStaticTemplate( TemplateGroup, self.CoalitionID, self.CategoryID, CountryID )
|
||||
|
||||
return mystatic
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -70,6 +70,7 @@
|
||||
-- @field #table Table of any trigger zone properties from the ME. The key is the Name of the property, and the value is the property's Value.
|
||||
-- @field #number Surface Type of surface. Only determined at the center of the zone!
|
||||
-- @field #number Checktime Check every Checktime seconds, used for ZONE:Trigger()
|
||||
-- @field #boolean PartlyInside When called, a GROUP is considered inside as soon as any of its units enters the zone even if they are far apart.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@@ -612,6 +613,8 @@ end
|
||||
--
|
||||
-- -- Stop watching the zone after 1 hour
|
||||
-- triggerzone:__TriggerStop(3600)
|
||||
-- -- Call :SetPartlyInside() if you use SET_GROUP to count as inside when any of their units enters even when they are far apart.
|
||||
-- -- Make sure to call :SetPartlyInside() before :Trigger()!
|
||||
function ZONE_BASE:Trigger(Objects)
|
||||
--self:I("Added Zone Trigger")
|
||||
self:SetStartState("TriggerStopped")
|
||||
@@ -680,6 +683,16 @@ function ZONE_BASE:Trigger(Objects)
|
||||
|
||||
end
|
||||
|
||||
--- Toggle “partly-inside” handling for this zone. To be used before :Trigger().
|
||||
-- * Default:* flag is **false** until you call the method.
|
||||
-- * Call with no argument or with **true** → enable.
|
||||
-- * Call with **false** → disable again (handy if it was enabled before).
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #ZONE_BASE self
|
||||
function ZONE_BASE:SetPartlyInside(state)
|
||||
self.PartlyInside = state or not ( state == false )
|
||||
return self
|
||||
end
|
||||
--- (Internal) Check the assigned objects for being in/out of the zone
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param #boolean fromstart If true, do the init of the objects
|
||||
@@ -718,7 +731,12 @@ function ZONE_BASE:_TriggerCheck(fromstart)
|
||||
obj.TriggerInZone[self.ZoneName] = false
|
||||
end
|
||||
-- is obj in zone?
|
||||
local inzone = self:IsCoordinateInZone(obj:GetCoordinate())
|
||||
local inzone
|
||||
if self.PartlyInside and obj.ClassName == "GROUP" then
|
||||
inzone = obj:IsAnyInZone(self) -- TRUE if any unit is inside
|
||||
else
|
||||
inzone = self:IsCoordinateInZone(obj:GetCoordinate()) -- original barycentre test
|
||||
end
|
||||
--self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone))
|
||||
if inzone and obj.TriggerInZone[self.ZoneName] then
|
||||
-- just count
|
||||
@@ -765,7 +783,7 @@ end
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string to
|
||||
-- @param #string To
|
||||
-- @return #ZONE_BASE self
|
||||
function ZONE_BASE:onafterTriggerRunCheck(From,Event,To)
|
||||
if self:GetState() ~= "TriggerStopped" then
|
||||
@@ -1140,17 +1158,17 @@ end
|
||||
-- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
|
||||
-- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition )
|
||||
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||
|
||||
|
||||
self.ScanData = {}
|
||||
self.ScanData.Coalitions = {}
|
||||
self.ScanData.Scenery = {}
|
||||
self.ScanData.SceneryTable = {}
|
||||
self.ScanData.Units = {}
|
||||
|
||||
local ZoneCoord = self:GetCoordinate()
|
||||
local ZoneCoord = self:GetCoordinate():SetAlt()
|
||||
local ZoneRadius = self:GetRadius()
|
||||
|
||||
--self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
|
||||
--self:I({x = ZoneCoord.x, y=ZoneCoord.y, z=ZoneCoord.z, ZoneRadius = ZoneRadius})
|
||||
|
||||
local SphereSearch = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
@@ -1162,15 +1180,14 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||
|
||||
local function EvaluateZone( ZoneObject )
|
||||
--if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5
|
||||
if ZoneObject then
|
||||
|
||||
if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then
|
||||
|
||||
-- Get object category.
|
||||
local ObjectCategory = Object.getCategory(ZoneObject)
|
||||
|
||||
if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then
|
||||
|
||||
local CoalitionDCSUnit = ZoneObject:getCoalition()
|
||||
|
||||
local Include = false
|
||||
if not UnitCategories then
|
||||
-- Anything found is included.
|
||||
@@ -1249,38 +1266,48 @@ end
|
||||
|
||||
--- Get a set of scanned units.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number Coalition (optional) Filter for this coalition only.
|
||||
-- @return Core.Set#SET_UNIT Set of units and statics inside the zone.
|
||||
function ZONE_RADIUS:GetScannedSetUnit()
|
||||
|
||||
local SetUnit = SET_UNIT:New()
|
||||
function ZONE_RADIUS:GetScannedSetUnit(Coalition)
|
||||
|
||||
self.SetUnit = self.SetUnit or SET_UNIT:New()
|
||||
self.SetUnit:Clear(false)
|
||||
self.SetUnit.Set={}
|
||||
|
||||
if self.ScanData then
|
||||
for ObjectID, UnitObject in pairs( self.ScanData.Units ) do
|
||||
local UnitObject = UnitObject -- DCS#Unit
|
||||
if UnitObject:isExist() then
|
||||
local FoundUnit = UNIT:FindByName( UnitObject:getName() )
|
||||
if FoundUnit then
|
||||
SetUnit:AddUnit( FoundUnit )
|
||||
local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil
|
||||
local includeoncoalition = true
|
||||
if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end
|
||||
if Coalition == nil then includeoncoalition = true end
|
||||
--self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition)))
|
||||
if FoundUnit and includeoncoalition then
|
||||
self.SetUnit:AddUnit( FoundUnit )
|
||||
else
|
||||
local FoundStatic = STATIC:FindByName( UnitObject:getName(), false )
|
||||
if FoundStatic then
|
||||
SetUnit:AddUnit( FoundStatic )
|
||||
self.SetUnit:AddUnit( FoundStatic )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return SetUnit
|
||||
return self.SetUnit
|
||||
end
|
||||
|
||||
--- Get a set of scanned groups.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number Coalition (optional) Filter for this coalition only.
|
||||
-- @return Core.Set#SET_GROUP Set of groups.
|
||||
function ZONE_RADIUS:GetScannedSetGroup()
|
||||
function ZONE_RADIUS:GetScannedSetGroup(Coalition)
|
||||
|
||||
self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP
|
||||
|
||||
|
||||
self.ScanSetGroup:Clear(false)
|
||||
self.ScanSetGroup.Set={}
|
||||
|
||||
if self.ScanData then
|
||||
@@ -1289,7 +1316,12 @@ function ZONE_RADIUS:GetScannedSetGroup()
|
||||
if UnitObject:isExist() then
|
||||
|
||||
local FoundUnit=UNIT:FindByName(UnitObject:getName())
|
||||
if FoundUnit then
|
||||
local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil
|
||||
local includeoncoalition = true
|
||||
if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end
|
||||
if Coalition == nil then includeoncoalition = true end
|
||||
--self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition)))
|
||||
if FoundUnit and includeoncoalition then
|
||||
local group=FoundUnit:GetGroup()
|
||||
self.ScanSetGroup:AddGroup(group)
|
||||
end
|
||||
@@ -1553,6 +1585,10 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
|
||||
local Vec2 = self:GetVec2()
|
||||
local _inner = inner or 0
|
||||
local _outer = outer or self:GetRadius()
|
||||
|
||||
math.random()
|
||||
math.random()
|
||||
math.random()
|
||||
|
||||
if surfacetypes and type(surfacetypes)~="table" then
|
||||
surfacetypes={surfacetypes}
|
||||
@@ -1914,6 +1950,21 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Updates the current location from a @{Wrapper.Group}.
|
||||
-- @param #ZONE_UNIT self
|
||||
-- @param Wrapper.Group#GROUP Group (optional) Update from this Unit, if nil, update from the UNIT this zone is based on.
|
||||
-- @return self
|
||||
function ZONE_UNIT:UpdateFromUnit(Unit)
|
||||
if Unit and Unit:IsAlive() then
|
||||
local vec2 = Unit:GetVec2()
|
||||
self.LastVec2 = vec2
|
||||
elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive() then
|
||||
local ZoneVec2 = self.ZoneUNIT:GetVec2()
|
||||
self.LastVec2 = ZoneVec2
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Returns the current location of the @{Wrapper.Unit#UNIT}.
|
||||
-- @param #ZONE_UNIT self
|
||||
@@ -2051,6 +2102,22 @@ function ZONE_GROUP:GetVec2()
|
||||
return ZoneVec2
|
||||
end
|
||||
|
||||
--- Updates the current location from a @{Wrapper.Group}.
|
||||
-- @param #ZONE_GROUP self
|
||||
-- @param Wrapper.Group#GROUP Group (optional) Update from this Group, if nil, update from the GROUP this zone is based on.
|
||||
-- @return self
|
||||
function ZONE_GROUP:UpdateFromGroup(Group)
|
||||
if Group and Group:IsAlive() then
|
||||
local vec2 = Group:GetVec2()
|
||||
self.Vec2 = vec2
|
||||
elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive() then
|
||||
local ZoneVec2 = self._.ZoneGROUP:GetVec2()
|
||||
self.Vec2 = ZoneVec2
|
||||
self._.ZoneVec2Cache = ZoneVec2
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns a random location within the zone of the @{Wrapper.Group}.
|
||||
-- @param #ZONE_GROUP self
|
||||
-- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location.
|
||||
@@ -2918,6 +2985,11 @@ end
|
||||
function ZONE_POLYGON_BASE:GetRandomVec2()
|
||||
-- make sure we assign weights to the triangles based on their surface area, otherwise
|
||||
-- we'll be more likely to generate random points in smaller triangles
|
||||
|
||||
math.random()
|
||||
math.random()
|
||||
math.random()
|
||||
|
||||
local weights = {}
|
||||
for _, triangle in pairs(self._Triangles) do
|
||||
weights[triangle] = triangle.SurfaceArea / self.SurfaceArea
|
||||
@@ -3281,14 +3353,12 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
|
||||
|
||||
local function EvaluateZone( ZoneObject )
|
||||
|
||||
if ZoneObject then
|
||||
if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then
|
||||
|
||||
local ObjectCategory = Object.getCategory(ZoneObject)
|
||||
|
||||
if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then
|
||||
|
||||
local CoalitionDCSUnit = ZoneObject:getCoalition()
|
||||
|
||||
local Include = false
|
||||
if not UnitCategories then
|
||||
-- Anything found is included.
|
||||
@@ -3320,13 +3390,13 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
|
||||
end
|
||||
|
||||
-- trying with box search
|
||||
if ObjectCategory == Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint()) then
|
||||
if ObjectCategory == Object.Category.SCENERY then
|
||||
local SceneryType = ZoneObject:getTypeName()
|
||||
local SceneryName = ZoneObject:getName()
|
||||
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
|
||||
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
|
||||
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName])
|
||||
--self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
|
||||
--self:I( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ### Contributions: FlightControl
|
||||
-- ### Contributions: FlightControl, Applevangelist
|
||||
--
|
||||
-- ====
|
||||
-- @module Functional.Artillery
|
||||
@@ -780,7 +780,7 @@ ARTY.db={
|
||||
|
||||
--- Arty script version.
|
||||
-- @field #string version
|
||||
ARTY.version="1.3.3"
|
||||
ARTY.version="1.3.4"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -2268,7 +2268,7 @@ function ARTY._FuncTrack(weapon, self, target)
|
||||
if _dist<target.radius then
|
||||
|
||||
-- Get random coordinate within a certain radius.
|
||||
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
|
||||
local _cr=_coord:GetRandomCoordinateInRadius(target.radius)
|
||||
|
||||
-- Fire smoke at this coordinate.
|
||||
_cr:Smoke(self.smokeColor)
|
||||
|
||||
@@ -693,7 +693,7 @@ end
|
||||
function AUTOLASE:GetLosFromUnit(Unit)
|
||||
local lasedistance = self.LaseDistance
|
||||
local unitheight = Unit:GetHeight()
|
||||
local coord = Unit:GetCoordinate()
|
||||
local coord = Unit:GetCoord()
|
||||
local landheight = coord:GetLandHeight()
|
||||
local asl = unitheight - landheight
|
||||
if asl > 100 then
|
||||
@@ -845,7 +845,7 @@ function AUTOLASE:ShowStatus(Group,Unit)
|
||||
locationstring = entry.coordinate:ToStringLLDDM(settings)
|
||||
elseif settings:IsA2G_BR() then
|
||||
-- attention this is the distance from the ASKING unit to target, not from RECCE to target!
|
||||
local startcoordinate = Unit:GetCoordinate() or Group:GetCoordinate()
|
||||
local startcoordinate = Unit:GetCoord() or Group:GetCoord()
|
||||
locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision)
|
||||
end
|
||||
end
|
||||
@@ -963,8 +963,8 @@ function AUTOLASE:CanLase(Recce,Unit)
|
||||
end
|
||||
end
|
||||
-- calculate LOS
|
||||
local reccecoord = Recce:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local reccecoord = Recce:GetCoord()
|
||||
local unitcoord = Unit:GetCoord()
|
||||
local islos = reccecoord:IsLOS(unitcoord,2.5)
|
||||
-- calculate distance
|
||||
local distance = math.floor(reccecoord:Get3DDistance(unitcoord))
|
||||
@@ -1020,8 +1020,8 @@ function AUTOLASE:_Prescient()
|
||||
self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName())
|
||||
for _,_static in pairs(Statics) do -- DCS static object here
|
||||
local static = STATIC:Find(_static)
|
||||
if static and static:GetCoalition() ~= self.coalition and static:GetCoordinate() then
|
||||
local IsLOS = position:IsLOS(static:GetCoordinate())
|
||||
if static and static:GetCoalition() ~= self.coalition and static:GetCoord() then
|
||||
local IsLOS = position:IsLOS(static:GetCoord())
|
||||
if IsLOS then
|
||||
unit:KnowUnit(static,true,true)
|
||||
end
|
||||
@@ -1081,7 +1081,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
local threat = contact.threatlevel or 0
|
||||
local reccegrp = UNIT:FindByName(reccename)
|
||||
if reccegrp then
|
||||
local reccecoord = reccegrp:GetCoordinate()
|
||||
local reccecoord = reccegrp:GetCoord()
|
||||
local distance = math.floor(reccecoord:Get3DDistance(coord))
|
||||
local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel)
|
||||
report:Add(text)
|
||||
@@ -1120,7 +1120,6 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local threat = unit:GetThreatLevel()
|
||||
local coord = unit:GetCoordinate()
|
||||
if threat >= self.minthreatlevel then
|
||||
local unitname = unit:GetName()
|
||||
-- prefer radar units
|
||||
@@ -1176,16 +1175,16 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
local code = self:GetLaserCode(reccename)
|
||||
local spot = SPOT:New(recce)
|
||||
spot:LaseOn(unit,code,self.LaseDuration)
|
||||
local locationstring = unit:GetCoordinate():ToStringLLDDM()
|
||||
local locationstring = unit:GetCoord():ToStringLLDDM()
|
||||
if _SETTINGS:IsA2G_MGRS() then
|
||||
local precision = _SETTINGS:GetMGRS_Accuracy()
|
||||
local settings = {}
|
||||
settings.MGRS_Accuracy = precision
|
||||
locationstring = unit:GetCoordinate():ToStringMGRS(settings)
|
||||
locationstring = unit:GetCoord():ToStringMGRS(settings)
|
||||
elseif _SETTINGS:IsA2G_LL_DMS() then
|
||||
locationstring = unit:GetCoordinate():ToStringLLDMS(_SETTINGS)
|
||||
locationstring = unit:GetCoord():ToStringLLDMS(_SETTINGS)
|
||||
elseif _SETTINGS:IsA2G_BR() then
|
||||
locationstring = unit:GetCoordinate():ToStringBULLS(self.coalition,_SETTINGS)
|
||||
locationstring = unit:GetCoord():ToStringBULLS(self.coalition,_SETTINGS)
|
||||
end
|
||||
|
||||
local laserspot = { -- #AUTOLASE.LaserSpot
|
||||
@@ -1198,7 +1197,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
unitname = unitname,
|
||||
reccename = reccename,
|
||||
unittype = unit:GetTypeName(),
|
||||
coordinate = unit:GetCoordinate(),
|
||||
coordinate = unit:GetCoord(),
|
||||
}
|
||||
if self.smoketargets then
|
||||
local coord = unit:GetCoordinate()
|
||||
|
||||
@@ -0,0 +1,999 @@
|
||||
--- **Functional** - Build large airborne formations of aircraft.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Build in-air formations consisting of more than 40 aircraft as one group.
|
||||
-- * Build different formation types.
|
||||
-- * Assign a group leader that will guide the large formation path.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Additional Material:
|
||||
--
|
||||
-- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/FORMATION)
|
||||
-- * **Guides:** None
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **Applevangelist** (refactor)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Functional.FORMATION
|
||||
-- @image AI_Large_Formations.JPG
|
||||
|
||||
--- FORMATION class
|
||||
-- @type FORMATION
|
||||
-- @extends Core.Fsm#FSM_SET
|
||||
-- @field Wrapper.Unit#UNIT FollowUnit
|
||||
-- @field Core.Set#SET_GROUP FollowGroupSet
|
||||
-- @field #string FollowName
|
||||
-- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class.
|
||||
-- @field #number FollowDistance The current follow distance.
|
||||
-- @field DCS#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup.
|
||||
-- @field DCS#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup.
|
||||
-- @field #number dtFollow Time step between position updates.
|
||||
|
||||
|
||||
--- Build (large) formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader.
|
||||
--
|
||||
-- FORMATION makes AI @{Wrapper.Group#GROUP}s fly in formation of various compositions.
|
||||
-- The FORMATION class models formations in a different manner than the internal DCS formation logic!!!
|
||||
-- The purpose of the class is to:
|
||||
--
|
||||
-- * Make formation building a process that can be managed while in flight, rather than a task.
|
||||
-- * Human players can guide formations, consisting of larget planes.
|
||||
-- * Build large formations (like a large bomber field).
|
||||
-- * Form formations that DCS does not support off-the-shelve.
|
||||
--
|
||||
-- A few remarks:
|
||||
--
|
||||
-- * Depending on the type of plane, the change in direction by the leader may result in the formation getting disentangled while in flight and needs to be rebuild.
|
||||
-- * Formations are vulnerable to collissions, but is depending on the type of plane, the distance between the planes and the speed and angle executed by the leader.
|
||||
-- * Formations may take a while to build up.
|
||||
--
|
||||
-- As a result, the FORMATION is not perfect, but is very useful to:
|
||||
--
|
||||
-- * Model large formations when flying straight line. You can build close formations when doing this.
|
||||
-- * Make humans guide a large formation, when the planes are wide from each other.
|
||||
--
|
||||
-- ## FORMATION construction
|
||||
--
|
||||
-- Create a new SPAWN object with the @{#FORMATION.New} method:
|
||||
--
|
||||
-- * @{#FORMATION.New}(): Creates a new FORMATION object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT} or a @{Wrapper.Unit#UNIT}, with an optional briefing text.
|
||||
--
|
||||
-- ## Formation methods
|
||||
--
|
||||
-- The following methods can be used to set or change the formation:
|
||||
--
|
||||
-- * @{#FORMATION.FormationLine}(): Form a line formation (core formation function).
|
||||
-- * @{#FORMATION.FormationTrail}(): Form a trail formation.
|
||||
-- * @{#FORMATION.FormationLeftLine}(): Form a left line formation.
|
||||
-- * @{#FORMATION.FormationRightLine}(): Form a right line formation.
|
||||
-- * @{#FORMATION.FormationRightWing}(): Form a right wing formation.
|
||||
-- * @{#FORMATION.FormationLeftWing}(): Form a left wing formation.
|
||||
-- * @{#FORMATION.FormationCenterWing}(): Form a center wing formation.
|
||||
-- * @{#FORMATION.FormationCenterVic}(): Form a Vic formation (same as CenterWing).
|
||||
-- * @{#FORMATION.FormationCenterBoxed}(): Form a center boxed formation.
|
||||
--
|
||||
-- ## Randomization
|
||||
--
|
||||
-- Use the method @{FORMATION#FORMATION.SetFlightRandomization}() to simulate the formation flying errors that pilots make while in formation. Is a range set in meters.
|
||||
--
|
||||
-- @usage
|
||||
-- local FollowGroupSet = SET_GROUP:New():FilterCategories("plane"):FilterCoalitions("blue"):FilterPrefixes("Follow"):FilterStart()
|
||||
-- local LeaderUnit = UNIT:FindByName( "Leader" )
|
||||
-- local LargeFormation = FORMATION:New( LeaderUnit, FollowGroupSet, "Center Wing Formation", "Briefing" )
|
||||
-- LargeFormation:FormationCenterWing( 500, 50, 0, 250, 250 )
|
||||
-- LargeFormation:__Start( 1 )
|
||||
--
|
||||
-- @field #FORMATION
|
||||
FORMATION = {
|
||||
ClassName = "FORMATION",
|
||||
FollowName = nil, -- The Follow Name
|
||||
FollowUnit = nil,
|
||||
FollowGroupSet = nil,
|
||||
FollowScheduler = nil,
|
||||
OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE,
|
||||
OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
|
||||
dtFollow = 0.5,
|
||||
}
|
||||
|
||||
--- @type FORMATION.Formation
|
||||
-- @field #number None
|
||||
-- @field #number Line
|
||||
-- @field #number Trail
|
||||
-- @field #number Stack
|
||||
-- @field #number LeftLine
|
||||
-- @field #number RightLine
|
||||
-- @field #number LeftWing
|
||||
-- @field #number RightWing
|
||||
-- @field #number Vic
|
||||
-- @field #number Box
|
||||
FORMATION.Formation = {
|
||||
None = 1,
|
||||
Line = 2,
|
||||
Trail = 3,
|
||||
Stack = 4,
|
||||
LeftLine = 5,
|
||||
RightLine = 6,
|
||||
LeftWing = 7,
|
||||
RightWing = 8,
|
||||
Vic = 9,
|
||||
Box = 10,
|
||||
}
|
||||
|
||||
--- FORMATION class constructor for an AI group
|
||||
-- @param #FORMATION self
|
||||
-- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet.
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string FollowName Name of the escort.
|
||||
-- @return #FORMATION self
|
||||
function FORMATION:New( FollowUnit, FollowGroupSet, FollowName )
|
||||
local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) )
|
||||
self:F( { FollowUnit, FollowGroupSet, FollowName } )
|
||||
|
||||
self.FollowUnit = FollowUnit -- Wrapper.Unit#UNIT
|
||||
self.FollowGroupSet = FollowGroupSet -- Core.Set#SET_GROUP
|
||||
|
||||
self:SetFlightRandomization( 2 )
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
self:AddTransition( "*", "Stop", "Stopped" )
|
||||
|
||||
self:AddTransition( {"None", "Stopped"}, "Start", "Following" )
|
||||
|
||||
self:AddTransition( "*", "FormationLine", "*" )
|
||||
|
||||
--- Start Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] Start
|
||||
-- @param #FORMATION self
|
||||
|
||||
--- Start Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __Start
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
|
||||
--- Stop Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] Stop
|
||||
-- @param #FORMATION self
|
||||
|
||||
--- Stop Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __Stop
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
|
||||
--- FormationLine Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationLine
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLine Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationLine
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLine Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationLine
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationTrail", "*" )
|
||||
|
||||
--- FormationTrail Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationTrail
|
||||
-- @param #FORMATION self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
|
||||
--- FormationTrail Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationTrail
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
|
||||
--- FormationTrail Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationTrail
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
|
||||
self:AddTransition( "*", "FormationStack", "*" )
|
||||
|
||||
--- FormationStack Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationStack
|
||||
-- @param #FORMATION self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
|
||||
--- FormationStack Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationStack
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
|
||||
--- FormationStack Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationStack
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationLeftLine", "*" )
|
||||
|
||||
--- FormationLeftLine Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationLeftLine
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLeftLine Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationLeftLine
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLeftLine Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationLeftLine
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationRightLine", "*" )
|
||||
|
||||
--- FormationRightLine Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationRightLine
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationRightLine Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationRightLine
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationRightLine Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationRightLine
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationLeftWing", "*" )
|
||||
|
||||
--- FormationLeftWing Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationLeftWing
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLeftWing Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationLeftWing
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLeftWing Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationLeftWing
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationRightWing", "*" )
|
||||
|
||||
--- FormationRightWing Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationRightWing
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationRightWing Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationRightWing
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationRightWing Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationRightWing
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationCenterWing", "*" )
|
||||
|
||||
--- FormationCenterWing Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationCenterWing
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationCenterWing Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationCenterWing
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationCenterWing Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationCenterWing
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationVic", "*" )
|
||||
|
||||
--- FormationVic Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationVic
|
||||
-- @param #FORMATION self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationVic Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationVic
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationVic Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationVic
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationBox", "*" )
|
||||
|
||||
--- FormationBox Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationBox
|
||||
-- @param #FORMATION self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @param #number ZLevels The amount of levels on the Z-axis.
|
||||
|
||||
--- FormationBox Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] FormationBox
|
||||
-- @param #FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @param #number ZLevels The amount of levels on the Z-axis.
|
||||
|
||||
--- FormationBox Asynchronous Trigger for FORMATION
|
||||
-- @function [parent=#FORMATION] __FormationBox
|
||||
-- @param #FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @param #number ZLevels The amount of levels on the Z-axis.
|
||||
|
||||
self:AddTransition( "*", "Follow", "Following" )
|
||||
|
||||
self:FormationLeftLine( 500, 0, 250, 250 )
|
||||
|
||||
self.FollowName = FollowName
|
||||
|
||||
self.CT1 = 0
|
||||
self.GT1 = 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set time interval between updates of the formation.
|
||||
-- @param #FORMATION self
|
||||
-- @param #number dt Time step in seconds between formation updates. Default is every 0.5 seconds.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:SetFollowTimeInterval(dt)
|
||||
self.dtFollow=dt or 0.5
|
||||
return self
|
||||
end
|
||||
|
||||
--- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to.
|
||||
-- This allows to visualize where the escort is flying to.
|
||||
-- @param #FORMATION self
|
||||
-- @param #boolean SmokeDirection If true, then the direction vector will be smoked.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:TestSmokeDirectionVector( SmokeDirection )
|
||||
self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false
|
||||
return self
|
||||
end
|
||||
|
||||
--- FormationLine Handler OnAfter for FORMATION
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation )
|
||||
self:F( { FollowGroupSet, From , Event ,To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation } )
|
||||
|
||||
XStart = XStart or self.XStart
|
||||
XSpace = XSpace or self.XSpace
|
||||
YStart = YStart or self.YStart
|
||||
YSpace = YSpace or self.YSpace
|
||||
ZStart = ZStart or self.ZStart
|
||||
ZSpace = ZSpace or self.ZSpace
|
||||
|
||||
local FollowSet = FollowGroupSet:GetSet()
|
||||
|
||||
local i = 1 --FF i=0 caused first unit to have no XSpace! Probably needs further adjustments. This is just a quick work around.
|
||||
|
||||
for FollowID, FollowGroup in pairs( FollowSet ) do
|
||||
|
||||
local PointVec3 = COORDINATE:New()
|
||||
PointVec3:SetX( XStart + i * XSpace )
|
||||
PointVec3:SetY( YStart + i * YSpace )
|
||||
PointVec3:SetZ( ZStart + i * ZSpace )
|
||||
|
||||
local Vec3 = PointVec3:GetVec3()
|
||||
FollowGroup:SetState( self, "FormationVec3", Vec3 )
|
||||
i = i + 1
|
||||
|
||||
FollowGroup:SetState( FollowGroup, "Formation", Formation )
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
--- FormationTrail Handler OnAfter for FORMATION
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart )
|
||||
|
||||
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0, self.Formation.Trail )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- FormationStack Handler OnAfter for FORMATION
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace )
|
||||
|
||||
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0, self.Formation.Stack )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- FormationLeftLine Handler OnAfter for FORMATION
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace )
|
||||
|
||||
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace, self.Formation.LeftLine )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- FormationRightLine Handler OnAfter for FORMATION
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace )
|
||||
|
||||
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.Formation.RightLine)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- FormationLeftWing Handler OnAfter for FORMATION
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
function FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace )
|
||||
|
||||
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.Formation.LeftWing)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- FormationRightWing Handler OnAfter for FORMATION
|
||||
-- @function [parent=#FORMATION] OnAfterFormationRightWing
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
function FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace )
|
||||
|
||||
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.Formation.RightWing)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- FormationCenterWing Handler OnAfter for FORMATION
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
function FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace )
|
||||
|
||||
local FollowSet = FollowGroupSet:GetSet()
|
||||
|
||||
local i = 0
|
||||
|
||||
for FollowID, FollowGroup in pairs( FollowSet ) do
|
||||
|
||||
local PointVec3 = COORDINATE:New()
|
||||
|
||||
local Side = ( i % 2 == 0 ) and 1 or -1
|
||||
local Row = i / 2 + 1
|
||||
|
||||
PointVec3:SetX( XStart + Row * XSpace )
|
||||
PointVec3:SetY( YStart )
|
||||
PointVec3:SetZ( Side * ( ZStart + i * ZSpace ) )
|
||||
|
||||
local Vec3 = PointVec3:GetVec3()
|
||||
FollowGroup:SetState( self, "FormationVec3", Vec3 )
|
||||
i = i + 1
|
||||
FollowGroup:SetState( FollowGroup, "Formation", self.Formation.Vic )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- FormationVic Handle for FORMATION
|
||||
-- @param #FORMATION self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace )
|
||||
|
||||
self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- FormationBox Handler OnAfter for FORMATION
|
||||
-- @param #FORMATION self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @param #number ZLevels The amount of levels on the Z-axis.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels )
|
||||
|
||||
local FollowSet = FollowGroupSet:GetSet()
|
||||
|
||||
local i = 0
|
||||
|
||||
for FollowID, FollowGroup in pairs( FollowSet ) do
|
||||
|
||||
local PointVec3 = COORDINATE:New()
|
||||
|
||||
local ZIndex = i % ZLevels
|
||||
local XIndex = math.floor( i / ZLevels )
|
||||
local YIndex = math.floor( i / ZLevels )
|
||||
|
||||
PointVec3:SetX( XStart + XIndex * XSpace )
|
||||
PointVec3:SetY( YStart + YIndex * YSpace )
|
||||
PointVec3:SetZ( -ZStart - (ZSpace * ZLevels / 2 ) + ZSpace * ZIndex )
|
||||
|
||||
local Vec3 = PointVec3:GetVec3()
|
||||
FollowGroup:SetState( self, "FormationVec3", Vec3 )
|
||||
i = i + 1
|
||||
FollowGroup:SetState( FollowGroup, "Formation", self.Formation.Box )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Use the method @{FORMATION#FORMATION.SetFlightRandomization}() to make the air units in your formation randomize their flight a bit while in formation.
|
||||
-- @param #FORMATION self
|
||||
-- @param #number FlightRandomization The formation flying errors that pilots can make while in formation. Is a range set in meters.
|
||||
-- @return #FORMATION
|
||||
function FORMATION:SetFlightRandomization( FlightRandomization )
|
||||
self.FlightRandomization = FlightRandomization
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stop function. Formation will not be updated any more.
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To The to state.
|
||||
function FORMATION:onafterStop(FollowGroupSet, From, Event, To)
|
||||
self:E("Stopping formation.")
|
||||
end
|
||||
|
||||
--- Follow event fuction. Check if coming from state "stopped". If so the transition is rejected.
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To The to state.
|
||||
function FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To )
|
||||
if From=="Stopped" then
|
||||
return false -- Deny transition.
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Enter following state.
|
||||
-- @param #FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To The to state.
|
||||
function FORMATION:onenterFollowing( FollowGroupSet )
|
||||
|
||||
if self.FollowUnit:IsAlive() then
|
||||
|
||||
local ClientUnit = self.FollowUnit
|
||||
|
||||
local CT1, CT2, CV1, CV2
|
||||
CT1 = ClientUnit:GetState( self, "CT1" )
|
||||
|
||||
local CuVec3=ClientUnit:GetVec3()
|
||||
|
||||
if CT1 == nil or CT1 == 0 then
|
||||
ClientUnit:SetState( self, "CV1", CuVec3)
|
||||
ClientUnit:SetState( self, "CT1", timer.getTime() )
|
||||
else
|
||||
CT1 = ClientUnit:GetState( self, "CT1" )
|
||||
CT2 = timer.getTime()
|
||||
CV1 = ClientUnit:GetState( self, "CV1" )
|
||||
CV2 = CuVec3
|
||||
|
||||
ClientUnit:SetState( self, "CT1", CT2 )
|
||||
ClientUnit:SetState( self, "CV1", CV2 )
|
||||
end
|
||||
|
||||
--FollowGroupSet:ForEachGroupAlive( bla, self, ClientUnit, CT1, CV1, CT2, CV2)
|
||||
|
||||
for _,_group in pairs(FollowGroupSet:GetSet()) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
if group and group:IsAlive() then
|
||||
self:FollowMe(group, ClientUnit, CT1, CV1, CT2, CV2)
|
||||
end
|
||||
end
|
||||
|
||||
self:__Follow( -self.dtFollow )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Follow me.
|
||||
-- @param #FORMATION self
|
||||
-- @param Wrapper.Group#GROUP FollowGroup Follow group.
|
||||
-- @param Wrapper.Unit#UNIT ClientUnit Client Unit.
|
||||
-- @param DCS#Time CT1 Time
|
||||
-- @param DCS#Vec3 CV1 Vec3
|
||||
-- @param DCS#Time CT2 Time
|
||||
-- @param DCS#Vec3 CV2 Vec3
|
||||
function FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2)
|
||||
|
||||
if not self:Is("Stopped") then
|
||||
|
||||
self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )})
|
||||
|
||||
FollowGroup:OptionROTEvadeFire()
|
||||
FollowGroup:OptionROEReturnFire()
|
||||
|
||||
local GroupUnit = FollowGroup:GetUnit( 1 )
|
||||
|
||||
local GuVec3=GroupUnit:GetVec3()
|
||||
|
||||
local FollowFormation = FollowGroup:GetState( self, "FormationVec3" )
|
||||
|
||||
if FollowFormation then
|
||||
local FollowDistance = FollowFormation.x
|
||||
|
||||
local GT1 = GroupUnit:GetState( self, "GT1" )
|
||||
|
||||
if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then
|
||||
GroupUnit:SetState( self, "GV1", GuVec3)
|
||||
GroupUnit:SetState( self, "GT1", timer.getTime() )
|
||||
else
|
||||
local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5
|
||||
local CT = CT2 - CT1
|
||||
|
||||
local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6
|
||||
|
||||
local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z }
|
||||
local Ca = math.atan2( CDv.x, CDv.z )
|
||||
|
||||
local GT1 = GroupUnit:GetState( self, "GT1" )
|
||||
local GT2 = timer.getTime()
|
||||
|
||||
local GV1 = GroupUnit:GetState( self, "GV1" )
|
||||
local GV2 = GuVec3
|
||||
|
||||
GV2.x=GV2.x+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 )
|
||||
GV2.y=GV2.y+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 )
|
||||
GV2.z=GV2.z+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 )
|
||||
|
||||
GroupUnit:SetState( self, "GT1", GT2 )
|
||||
GroupUnit:SetState( self, "GV1", GV2 )
|
||||
|
||||
local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5
|
||||
local GT = GT2 - GT1
|
||||
|
||||
-- Calculate the distance
|
||||
local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z }
|
||||
local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z )
|
||||
local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T
|
||||
local Position = math.cos( Alpha_R )
|
||||
local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5
|
||||
local Distance = GD * Position + - CS * 0.5
|
||||
|
||||
-- Calculate the group direction vector
|
||||
local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z }
|
||||
|
||||
-- Calculate GH2, GH2 with the same height as CV2.
|
||||
local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z }
|
||||
|
||||
-- Calculate the angle of GV to the orthonormal plane
|
||||
local alpha = math.atan2( GV.x, GV.z )
|
||||
|
||||
local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca )
|
||||
local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca )
|
||||
|
||||
-- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2.
|
||||
-- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2))
|
||||
local Inclination = ( Distance + FollowFormation.x ) / 10
|
||||
if Inclination < -30 then
|
||||
Inclination = - 30
|
||||
end
|
||||
|
||||
local CVI = {
|
||||
x = CV2.x + CS * 10 * math.sin(Ca),
|
||||
y = GH2.y + Inclination, -- + FollowFormation.y,
|
||||
z = CV2.z + CS * 10 * math.cos(Ca),
|
||||
}
|
||||
|
||||
-- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction.
|
||||
local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z }
|
||||
|
||||
-- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s.
|
||||
-- We need to calculate this vector to predict the point the escort group needs to fly to according its speed.
|
||||
-- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right...
|
||||
local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance }
|
||||
|
||||
-- Now we can calculate the group destination vector GDV.
|
||||
local GDV = { x = CVI.x, y = CVI.y, z = CVI.z }
|
||||
|
||||
local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha)
|
||||
local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha)
|
||||
|
||||
local GDV_Formation = {
|
||||
x = GDV.x - GVx,
|
||||
y = GDV.y,
|
||||
z = GDV.z - GVz
|
||||
}
|
||||
|
||||
-- Debug smoke.
|
||||
if self.SmokeDirectionVector == true then
|
||||
trigger.action.smoke( GDV, trigger.smokeColor.Green )
|
||||
trigger.action.smoke( GDV_Formation, trigger.smokeColor.White )
|
||||
end
|
||||
|
||||
local Time = 120
|
||||
|
||||
local Speed = - ( Distance + FollowFormation.x ) / Time
|
||||
|
||||
if Distance > -10000 then
|
||||
Speed = - ( Distance + FollowFormation.x ) / 60
|
||||
end
|
||||
|
||||
if Distance > -2500 then
|
||||
Speed = - ( Distance + FollowFormation.x ) / 20
|
||||
end
|
||||
|
||||
local GS = Speed + CS
|
||||
|
||||
--self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } )
|
||||
|
||||
-- Now route the escort to the desired point with the desired speed.
|
||||
FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -22,7 +22,7 @@
|
||||
-- @module Functional.Mantis
|
||||
-- @image Functional.Mantis.jpg
|
||||
--
|
||||
-- Last Update: July 2025
|
||||
-- Last Update: January 2026
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **MANTIS** class, extends Core.Base#BASE
|
||||
@@ -61,10 +61,13 @@
|
||||
-- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range.
|
||||
-- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects.
|
||||
-- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range.
|
||||
-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White
|
||||
-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White.
|
||||
-- @field #number checkcounter Counter for SAM Table refreshes.
|
||||
-- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay.
|
||||
-- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true
|
||||
-- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true.
|
||||
-- @field #boolean DetectAccoustic Set if we can also detect units accousticly.
|
||||
-- @field #number DetectAccousticRadius We can hear in this range.
|
||||
-- @field #table DetectAccousticCategories We can hear these categories.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -108,10 +111,15 @@
|
||||
-- * Patriot
|
||||
-- * Rapier
|
||||
-- * Roland
|
||||
-- * IRIS-T SLM
|
||||
-- * Pantsir S1
|
||||
-- * TOR M2
|
||||
-- * C-RAM
|
||||
-- * Silkworm (though strictly speaking this is a surface to ship missile)
|
||||
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19
|
||||
-- * SA-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
|
||||
-- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!)
|
||||
-- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2
|
||||
-- * Other Mods: Nike
|
||||
--
|
||||
-- * From SMA: RBS98M, RBS70, RBS90, RBS90M, RBS103A, RBS103B, RBS103AM, RBS103BM, Lvkv9040M
|
||||
-- **NOTE** If you are using the Swedish Military Assets (SMA), please note that the **group name** for RBS-SAM types also needs to contain the keyword "SMA"
|
||||
@@ -275,7 +283,7 @@
|
||||
MANTIS = {
|
||||
ClassName = "MANTIS",
|
||||
name = "mymantis",
|
||||
version = "0.9.32",
|
||||
version = "0.9.44",
|
||||
SAM_Templates_Prefix = "",
|
||||
SAM_Group = nil,
|
||||
EWR_Templates_Prefix = "",
|
||||
@@ -326,6 +334,11 @@ MANTIS = {
|
||||
checkcounter = 1,
|
||||
DLinkCacheTime = 120,
|
||||
logsamstatus = false,
|
||||
DetectAccoustic = false,
|
||||
DetectAccousticRadius = 2000,
|
||||
DetectAccousticCategories = {Unit.Category.HELICOPTER},
|
||||
ARMWeaponSeen = {},
|
||||
InboundARMs = {},
|
||||
}
|
||||
|
||||
--- Advanced state enumerator
|
||||
@@ -361,38 +374,49 @@ MANTIS.radiusscale[MANTIS.SamType.POINT] = 3
|
||||
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
|
||||
-- @field #string Radar Radar typename on unit level (used as key)
|
||||
-- @field #string Point Point defense capable
|
||||
-- @field ARMCapacit ARMCapacity ie how many (H)ARMs the system can defend at the same time
|
||||
MANTIS.SamData = {
|
||||
["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km
|
||||
["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B
|
||||
["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS", ARMCapacity=1 }, -- AIM 120B
|
||||
["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" },
|
||||
["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" },
|
||||
["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" },
|
||||
["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" },
|
||||
["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" },
|
||||
["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" },
|
||||
["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"},
|
||||
["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4", ARMCapacity=4},
|
||||
["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" },
|
||||
["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland" },
|
||||
["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland", ARMCapacity=1 },
|
||||
["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" },
|
||||
["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" },
|
||||
["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" },
|
||||
["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" },
|
||||
["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" },
|
||||
["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true" },
|
||||
["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true", ARMCapacity=2 },
|
||||
["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" },
|
||||
["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" },
|
||||
["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" },
|
||||
["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" },
|
||||
["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" },
|
||||
["HEMTT_C-RAM_Phalanx"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" },
|
||||
["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" },
|
||||
-- units from HDS Mod, multi launcher options is tricky
|
||||
["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"},
|
||||
["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" },
|
||||
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"},
|
||||
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"},
|
||||
["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B", 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-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2", ARMCapacity=18},
|
||||
["SA-21"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E"},
|
||||
["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
|
||||
["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
|
||||
["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E", ARMCapacity=4},
|
||||
["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
|
||||
["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" },
|
||||
["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" },
|
||||
["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" },
|
||||
["Nike"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" },
|
||||
["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" },
|
||||
-- CH Added to DCS core 2.9.19.x
|
||||
["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true", ARMCapacity=3 },
|
||||
["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=4 },
|
||||
["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM", ARMCapacity=12 }, -- 4 per starter, usually 3 starters in a battery
|
||||
}
|
||||
|
||||
--- SAM data HDS
|
||||
@@ -408,13 +432,13 @@ MANTIS.SamDataHDS = {
|
||||
-- group name MUST contain HDS to ID launcher type correctly!
|
||||
["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" },
|
||||
["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" },
|
||||
["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD
|
||||
["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD
|
||||
["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " },
|
||||
["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"},
|
||||
["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"},
|
||||
["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" },
|
||||
["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" },
|
||||
["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln", ARMCapacity=8}, -- V55RUD
|
||||
["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln", ARMCapacity=3}, -- V55RUD
|
||||
["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 },
|
||||
["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l", ARMCapacity=12},
|
||||
["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l", ARMCapacity=12},
|
||||
["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME", ARMCapacity=14 },
|
||||
["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME", ARMCapacity=14 },
|
||||
["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
|
||||
["SAMPT Block 1 HDS"] = { Range=120, Blindspot=1, Height=20, Type="long", Radar="SAMPT_MLT_Blk1" }, -- Block 1 Launcher
|
||||
["SAMPT Block 1INT HDS"] = { Range=150, Blindspot=1, Height=25, Type="long", Radar="SAMPT_MLT_Blk1NT" }, -- Block 1-INT Launcher
|
||||
@@ -458,21 +482,21 @@ MANTIS.SamDataCH = {
|
||||
-- https://www.currenthill.com/
|
||||
-- group name MUST contain CHM to ID launcher type correctly!
|
||||
["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" },
|
||||
["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" },
|
||||
["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" },
|
||||
["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true", ARMCapacity=3 },
|
||||
["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2", ARMCapacity=4 },
|
||||
["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" },
|
||||
["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" },
|
||||
["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" },
|
||||
["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" },
|
||||
["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" },
|
||||
["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" },
|
||||
["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" },
|
||||
["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=3 },
|
||||
["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true", ARMCapacity=4 },
|
||||
["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true", ARMCapacity=4 },
|
||||
["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" },
|
||||
["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" },
|
||||
["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" },
|
||||
["PGZ-09 CHM"] = { Range=4, Blindspot=0.5, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" },
|
||||
["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" },
|
||||
["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" },
|
||||
["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100", ARMCapacity=20 },
|
||||
["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D", ARMCapacity=20 },
|
||||
["LAV-AD CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_LAVAD" },
|
||||
["HQ-22 CHM"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" },
|
||||
["PGZ-95 CHM"] = { Range=2.5, Blindspot=0.5, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" },
|
||||
@@ -484,8 +508,8 @@ MANTIS.SamDataCH = {
|
||||
["Skynex CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" },
|
||||
["Skyshield CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" },
|
||||
["WieselOzelot CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" },
|
||||
["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" },
|
||||
["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" },
|
||||
["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M", ARMCapacity=20 },
|
||||
["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA", ARMCapacity=20 },
|
||||
["SkySabre CHM"] = { Range=30, Blindspot=0.5, Height=10, Type="Medium", Radar="CH_SkySabreLN" },
|
||||
["Stormer CHM"] = { Range=7.5, Blindspot=0.3, Height=7, Type="Short", Radar="CH_StormerHVM" },
|
||||
["THAAD CHM"] = { Range=200, Blindspot=40, Height=150, Type="Long", Radar="CH_THAAD_M1120" },
|
||||
@@ -513,7 +537,7 @@ do
|
||||
--@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..."
|
||||
--@param #string ewrprefix Prefixes for the EWR groups from the ME, e.g. all groups starting with "Red EWR..."
|
||||
--@param #string hq Group name of your HQ (optional)
|
||||
--@param #string coalition Coalition side of your setup, e.g. "blue", "red" or "neutral"
|
||||
--@param #string Coalition Coalition side of your setup, e.g. "blue", "red" or "neutral"
|
||||
--@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional)
|
||||
--@param #string awacs Group name of your Awacs (optional)
|
||||
--@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional)
|
||||
@@ -537,7 +561,7 @@ do
|
||||
-- mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||
-- mybluemantis:Start()
|
||||
--
|
||||
function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs, EmOnOff, Padding, Zones)
|
||||
function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs, EmOnOff, Padding, Zones)
|
||||
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
@@ -550,12 +574,14 @@ do
|
||||
-- DONE: Treat Awacs separately, since they might be >80km off site
|
||||
-- DONE: Allow tables of prefixes for the setup
|
||||
-- DONE: Auto-Mode with range setups for various known SAM types.
|
||||
-- DONE: Added reaction on HIT and UNIT LOST events.
|
||||
|
||||
self.name = name or "mymantis"
|
||||
self.SAM_Templates_Prefix = samprefix or "Red SAM"
|
||||
self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
|
||||
self.HQ_Template_CC = hq or nil
|
||||
self.Coalition = coalition or "red"
|
||||
self.Coalition = Coalition or "red"
|
||||
self.coalition = Coalition == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.SAM_Table = {}
|
||||
self.SAM_Table_Long = {}
|
||||
self.SAM_Table_Medium = {}
|
||||
@@ -602,6 +628,8 @@ do
|
||||
self.autoshorad = true
|
||||
self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP
|
||||
self.FilterZones = Zones
|
||||
self.LastThreatEval = {}
|
||||
self.InboundARMs = {}
|
||||
|
||||
self.SkateZones = nil
|
||||
self.SkateNumber = 3
|
||||
@@ -705,6 +733,8 @@ do
|
||||
self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group.
|
||||
self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group.
|
||||
self:AddTransition("*", "SeadSuppressionPlanned", "*") -- SEAD has planned a suppression.
|
||||
self:AddTransition("*", "SAMUnitHit", "*") -- A SAM unit was hit
|
||||
self:AddTransition("*", "SAMUnitLost", "*") -- A SAM Unit was lost
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
------------------------
|
||||
@@ -815,6 +845,24 @@ do
|
||||
-- @param Wrapper.Group#GROUP Group The suppressed GROUP object
|
||||
-- @param #string Name Name of the suppressed group
|
||||
|
||||
--- On After "SAMUnitHit" event. A SAM Unit was hit.
|
||||
-- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd
|
||||
-- @param #MANTIS self
|
||||
-- @param #string From The From State
|
||||
-- @param #string Event The Event
|
||||
-- @param #string To The To State
|
||||
-- @param Wrapper.Group#GROUP Group The GROUP of the hit UNIT object
|
||||
-- @param #string Name Name of the suppressed group
|
||||
|
||||
--- On After "SAMUnitLoast" event. A SAM Unit was lost.
|
||||
-- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd
|
||||
-- @param #MANTIS self
|
||||
-- @param #string From The From State
|
||||
-- @param #string Event The Event
|
||||
-- @param #string To The To State
|
||||
-- @param Wrapper.Group#GROUP Group The GROUP of the lost UNIT object
|
||||
-- @param #string Name Name of the suppressed group
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -822,6 +870,138 @@ do
|
||||
-- MANTIS helper functions
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
--- Set to accept accoustic detection. Set this *before* MANTIS starts!
|
||||
-- @param #MANTIS self
|
||||
-- @param #number Radius Radius in which we can "hear" units. Defaults to 2000 meters.
|
||||
-- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.HELICOPTER}`
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories)
|
||||
self.DetectAccoustic = true
|
||||
self.DetectAccousticRadius = Radius or 2000
|
||||
self.DetectAccousticCategories = UnitCategories or {Unit.Category.HELICOPTER}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch off accoustic detection.
|
||||
-- @param #MANTIS self
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetAccousticDetectionOff()
|
||||
self.DetectAccoustic = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Function to manage hits on SAM units
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Event#EVENTDATA EventData The EVENT data
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_EventHandler(EventData)
|
||||
self:T(self.lid .. "_EventHandler")
|
||||
|
||||
local function IsOneOfOurs(name)
|
||||
for _,_name in pairs(self.ewr_templates) do
|
||||
if string.find(name,_name,1,true) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function SwitchSAMOn(Name,Group)
|
||||
local suppressed = self.SuppressedGroups[Name] or false
|
||||
if not suppressed and self.SamStateTracker[Name] == "GREEN" then
|
||||
self.SamStateTracker[Name] = "RED"
|
||||
if self.UseEmOnOff then
|
||||
-- DONE: add emissions on/off
|
||||
Group:EnableEmission(true)
|
||||
elseif (not self.UseEmOnOff) then
|
||||
Group:OptionAlarmStateRed()
|
||||
end
|
||||
self:__RedState(1,Group)
|
||||
if self.SmokeDecoy == true then
|
||||
self:_SmokeUnits(Group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local coordinate -- Core.Point#COORDINATE
|
||||
local Name -- #string
|
||||
local Group -- Wrapper.Group#GROUP
|
||||
local lasthit = 0
|
||||
local firsthit = false
|
||||
local alerton = false
|
||||
|
||||
-- Check if we can get a location
|
||||
|
||||
local data = EventData -- Core.Event#EVENTDATA
|
||||
if data.id == EVENTS.Hit then
|
||||
-- Unit hit, one of ours?
|
||||
if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName) then
|
||||
self:T("Unit hit in group: "..data.TgtGroupName)
|
||||
if data.TgtGroup then
|
||||
lasthit = data.TgtGroup:GetProperty("MANTIS_LASTHIT")
|
||||
firsthit = (lasthit==nil) and true or false
|
||||
if firsthit == true then alerton = true end
|
||||
if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end
|
||||
coordinate = data.TgtGroup:GetCoordinate()
|
||||
Name = data.TgtGroupName
|
||||
Group = data.TgtGroup
|
||||
if alerton == true then
|
||||
self:__SAMUnitHit(1,Group,Name)
|
||||
SwitchSAMOn(Name,Group)
|
||||
end
|
||||
if coordinate and self.debug then
|
||||
local text = coordinate:ToStringMGRS()
|
||||
self:I("Location: "..text)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if data.id == EVENTS.UnitLost then
|
||||
if data.IniGroupName and IsOneOfOurs(data.IniGroupName) then
|
||||
self:T("Unit lost in group: "..data.IniGroupName)
|
||||
if data.IniGroup then
|
||||
lasthit = data.IniGroup:GetProperty("MANTIS_LASTHIT")
|
||||
firsthit = (lasthit==nil) and true or false
|
||||
if firsthit == true then alerton = true end
|
||||
if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end
|
||||
coordinate = data.IniGroup:GetCoordinate()
|
||||
Name = data.IniGroupName
|
||||
Group = data.IniGroup
|
||||
alerton = true
|
||||
SwitchSAMOn(Name,Group)
|
||||
self:__SAMUnitLost(1,Group,Name)
|
||||
if coordinate and self.debug then
|
||||
local text = coordinate:ToStringMGRS()
|
||||
self:I("Location: "..text)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if firsthit == true or alerton == true then
|
||||
Group:SetProperty("MANTIS_LASTHIT",timer.getTime())
|
||||
end
|
||||
|
||||
|
||||
if coordinate ~= nil and Name ~= nil and Group ~=nil and alerton == true then
|
||||
if self.ShoradLink then
|
||||
self:T("Shorad activated for: "..Name)
|
||||
local Shorad = self.Shorad -- Functional.Shorad#SHORAD
|
||||
local radius = self.checkradius
|
||||
local ontime = self.ShoradTime
|
||||
Shorad:WakeUpShorad(Name, radius, ontime, nil, true)
|
||||
self:__ShoradActivated(1,Name, radius, ontime)
|
||||
end
|
||||
if self.autorelocate and Group then
|
||||
Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- [Internal] Function to get the self.SAM_Table
|
||||
-- @param #MANTIS self
|
||||
-- @return #table table
|
||||
@@ -882,19 +1062,121 @@ do
|
||||
self.AcceptZones = AcceptZones or {}
|
||||
self.RejectZones = RejectZones or {}
|
||||
self.ConflictZones = ConflictZones or {}
|
||||
if #self.AcceptZones > 0 or #self.RejectZones > 0 or #self.ConflictZones > 0 then
|
||||
self.AcceptZonesNo = UTILS.TableLength(self.AcceptZones)
|
||||
self.RejectZonesNo = UTILS.TableLength(self.RejectZones)
|
||||
self.ConflictZonesNo = UTILS.TableLength(self.ConflictZones)
|
||||
self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo))
|
||||
if self.AcceptZonesNo > 0 or self.RejectZonesNo > 0 or self.ConflictZonesNo > 0 then
|
||||
self.usezones = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a single reject zone to MANTIS.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Zone#ZONE Zone The zone to be added
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddRejectZone(Zone)
|
||||
if Zone and Zone:IsInstanceOf("ZONE_BASE") then
|
||||
table.insert(self.RejectZones,Zone)
|
||||
self.usezones = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a single accept zone to MANTIS.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Zone#ZONE Zone The zone to be added
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddAcceptZone(Zone)
|
||||
if Zone and Zone:IsInstanceOf("ZONE_BASE") then
|
||||
table.insert(self.AcceptZones,Zone)
|
||||
self.usezones = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a single conflict zone to MANTIS.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Zone#ZONE Zone The zone to be added
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddConflictZone(Zone)
|
||||
if Zone and Zone:IsInstanceOf("ZONE_BASE") then
|
||||
table.insert(self.ConflictZones,Zone)
|
||||
self.usezones = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zones.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetCorridorZones(CorridorZones)
|
||||
self:T(self.lid .. "SetCorridorZones")
|
||||
if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then
|
||||
self.corridorzones = CorridorZones
|
||||
self.usecorridors = true
|
||||
elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then
|
||||
if not self.corridorzones then self.corridorzones = SET_ZONE:New() end
|
||||
self.corridorzones:AddZone(CorridorZones)
|
||||
self.usecorridors = true
|
||||
end
|
||||
if self.intelset then
|
||||
for _,_intel in pairs(self.intelset) do
|
||||
_intel:SetCorridorZones(self.corridorzones)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to add one corridor zone.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddCorridorZone(CorridorZone)
|
||||
self:T(self.lid .. "AddCorridorZone")
|
||||
self:SetCorridorZones(CorridorZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in FEET.
|
||||
-- @param #MANTIS self
|
||||
-- @param #number Floor Floor altitude ASL in feet.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in feet.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
|
||||
self.corridorfloor = UTILS.FeetToMeters(Floor)
|
||||
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
|
||||
if self.intelset then
|
||||
for _,_intel in pairs(self.intelset) do
|
||||
_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in METERS.
|
||||
-- @param #MANTIS self
|
||||
-- @param #number Floor Floor altitude ASL in meters.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in meters.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
|
||||
self.corridorfloor = Floor
|
||||
self.corridorceiling = Ceiling
|
||||
if self.intelset then
|
||||
for _,_intel in pairs(self.intelset) do
|
||||
_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set the detection radius of the EWR in meters. (Deprecated, SAM range is used)
|
||||
-- @param #MANTIS self
|
||||
-- @param #number radius Radius of the EWR detection zone
|
||||
function MANTIS:SetEWRRange(radius)
|
||||
self:T(self.lid .. "SetEWRRange")
|
||||
--local radius = radius or 80000
|
||||
-- self.acceptrange = radius
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1274,7 +1556,8 @@ do
|
||||
self:T(self.lid.."_CheckCoordinateInZones")
|
||||
local inzone = false
|
||||
-- acceptzones
|
||||
if #self.AcceptZones > 0 then
|
||||
self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo))
|
||||
if self.AcceptZonesNo > 0 then
|
||||
for _,_zone in pairs(self.AcceptZones) do
|
||||
local zone = _zone -- Core.Zone#ZONE
|
||||
if zone:IsCoordinateInZone(coord) then
|
||||
@@ -1285,7 +1568,7 @@ do
|
||||
end
|
||||
end
|
||||
-- rejectzones
|
||||
if #self.RejectZones > 0 and inzone then -- maybe in accept zone, but check the overlaps
|
||||
if self.RejectZonesNo > 0 then
|
||||
for _,_zone in pairs(self.RejectZones) do
|
||||
local zone = _zone -- Core.Zone#ZONE
|
||||
if zone:IsCoordinateInZone(coord) then
|
||||
@@ -1296,7 +1579,7 @@ do
|
||||
end
|
||||
end
|
||||
-- conflictzones
|
||||
if #self.ConflictZones > 0 and not inzone then -- if not already accepted, might be in conflict zones
|
||||
if self.ConflictZonesNo > 0 then
|
||||
for _,_zone in pairs(self.ConflictZones) do
|
||||
local zone = _zone -- Core.Zone#ZONE
|
||||
if zone:IsCoordinateInZone(coord) then
|
||||
@@ -1309,11 +1592,12 @@ do
|
||||
return inzone
|
||||
end
|
||||
|
||||
--- [Internal] Function to prefilter height based
|
||||
--- [Internal] Function to prefilter height based and check for Helo activity.
|
||||
-- @param #MANTIS self
|
||||
-- @param #number height
|
||||
-- @param Core.Point#COORDINATE SamCoordinate
|
||||
-- @return #table set
|
||||
function MANTIS:_PreFilterHeight(height)
|
||||
function MANTIS:_PreFilterHeight(height,SamCoordinate)
|
||||
self:T(self.lid.."_PreFilterHeight")
|
||||
local set = {}
|
||||
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
|
||||
@@ -1322,8 +1606,22 @@ do
|
||||
local contact = _contact -- Ops.Intel#INTEL.Contact
|
||||
local grp = contact.group -- Wrapper.Group#GROUP
|
||||
if grp:IsAlive() then
|
||||
if grp:GetHeight(true) < height then
|
||||
local coord = grp:GetCoordinate()
|
||||
local coord = grp:GetCoordinate()
|
||||
local dist = 0
|
||||
local include = true
|
||||
if grp:IsGround() then include = false end
|
||||
if grp:GetCoalition() == self.coalition then include = false end
|
||||
if coord and SamCoordinate and grp:IsHelicopter() then
|
||||
dist = coord:Get2DDistance(SamCoordinate) or 0
|
||||
if dist > self.ShoradActDistance then include = false end -- we do not want long range shooting at helos
|
||||
end
|
||||
if self.debug then
|
||||
local text = "Looking at Group: "..grp:GetName() or "N/A"
|
||||
text = text .. " Include = "..tostring(include)
|
||||
MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog()
|
||||
end
|
||||
local grpalt = grp:GetHeight(true)
|
||||
if grpalt < height and grpalt > 10 and include == true then
|
||||
table.insert(set,coord)
|
||||
end
|
||||
end
|
||||
@@ -1347,7 +1645,7 @@ do
|
||||
local set = dectset
|
||||
if dlink then
|
||||
-- DEBUG
|
||||
set = self:_PreFilterHeight(height)
|
||||
set = self:_PreFilterHeight(height,samcoordinate)
|
||||
end
|
||||
--self.friendlyset -- Core.Set#SET_GROUP
|
||||
if self.checkforfriendlies == true and self.friendlyset == nil then
|
||||
@@ -1362,6 +1660,7 @@ do
|
||||
end
|
||||
-- check accept/reject zones
|
||||
local zonecheck = true
|
||||
self:T("self.usezones = "..tostring(self.usezones))
|
||||
if self.usezones then
|
||||
-- DONE
|
||||
zonecheck = self:_CheckCoordinateInZones(coord)
|
||||
@@ -1427,14 +1726,30 @@ do
|
||||
|
||||
self.intelset = {}
|
||||
|
||||
local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne")
|
||||
--IntelOne:SetClusterAnalysis(true,true)
|
||||
--IntelOne:SetClusterRadius(5000)
|
||||
local IntelOne = INTEL:New(groupset,self.coalition,self.name.." IntelOne")
|
||||
IntelOne.DetectAccoustic = self.DetectAccoustic
|
||||
IntelOne.DetectAccousticRadius = self.DetectAccousticRadius or 2000
|
||||
IntelOne.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER}
|
||||
--IntelOne:SetClusterAnalysis(true,true,true)
|
||||
if self.usecorridors == true then
|
||||
IntelOne:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorfloor or self.corridorceiling then
|
||||
IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
IntelOne:Start()
|
||||
|
||||
local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo")
|
||||
--IntelTwo:SetClusterAnalysis(true,true)
|
||||
--IntelTwo:SetClusterRadius(5000)
|
||||
local IntelTwo = INTEL:New(samset,self.coalition,self.name.." IntelTwo")
|
||||
IntelTwo.DetectAccoustic = self.DetectAccoustic
|
||||
IntelTwo.DetectAccousticRadius = self.DetectAccousticRadius or 2000
|
||||
IntelTwo.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER}
|
||||
--IntelTwo:SetClusterAnalysis(true,true,true)
|
||||
if self.usecorridors == true then
|
||||
IntelTwo:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorfloor or self.corridorceiling then
|
||||
IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
IntelTwo:Start()
|
||||
|
||||
local CacheTime = self.DLinkCacheTime or 120
|
||||
@@ -1494,6 +1809,7 @@ do
|
||||
local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP
|
||||
local units = group:GetUnits()
|
||||
local SAMData = self.SamData
|
||||
local ARMCapacity
|
||||
if mod then
|
||||
SAMData = self.SamDataHDS
|
||||
elseif sma then
|
||||
@@ -1501,22 +1817,23 @@ do
|
||||
elseif chm then
|
||||
SAMData = self.SamDataCH
|
||||
end
|
||||
--self:I("Looking to auto-match for "..grpname)
|
||||
self:T("Looking to auto-match for "..grpname)
|
||||
for _,_unit in pairs(units) do
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
local type = string.lower(unit:GetTypeName())
|
||||
--self:I(string.format("Matching typename: %s",type))
|
||||
local typename = string.lower(unit:GetTypeName())
|
||||
self:T(string.format("Matching typename: %s",typename))
|
||||
for idx,entry in pairs(SAMData) do
|
||||
local _entry = entry -- #MANTIS.SamData
|
||||
local _radar = string.lower(_entry.Radar)
|
||||
--self:I(string.format("Trying typename: %s",_radar))
|
||||
if string.find(type,_radar,1,true) then
|
||||
self:T(string.format("Trying typename: %s",_radar))
|
||||
if string.find(typename,_radar,1,true) then
|
||||
type = _entry.Type
|
||||
radiusscale = self.radiusscale[type]
|
||||
range = _entry.Range * 1000 * radiusscale -- max firing range used as switch-on
|
||||
height = _entry.Height * 1000 -- max firing height
|
||||
blind = _entry.Blindspot * 100 -- blind spot range
|
||||
--self:I(string.format("Match: %s - %s",_radar,type))
|
||||
blind = _entry.Blindspot * 100 -- blind spot range
|
||||
ARMCapacity = _entry.ARMCapacity
|
||||
self:T(string.format("Match: %s - %s",_radar,type))
|
||||
found = true
|
||||
break
|
||||
end
|
||||
@@ -1537,7 +1854,7 @@ do
|
||||
if not found then
|
||||
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
||||
end
|
||||
return range, height, type, blind
|
||||
return range, height, type, blind, ARMCapacity
|
||||
end
|
||||
|
||||
--- [Internal] Function to get SAM firing data
|
||||
@@ -1558,6 +1875,7 @@ do
|
||||
local HDSmod = false
|
||||
local SMAMod = false
|
||||
local CHMod = false
|
||||
local ARMCapacity = 0
|
||||
if string.find(grpname,"HDS",1,true) then
|
||||
HDSmod = true
|
||||
elseif string.find(grpname,"SMA",1,true) then
|
||||
@@ -1575,6 +1893,7 @@ do
|
||||
range = _entry.Range * 1000 * radiusscale -- max firing range
|
||||
height = _entry.Height * 1000 -- max firing height
|
||||
blind = _entry.Blindspot
|
||||
ARMCapacity = _entry.ARMCapacity or 0
|
||||
self:T("Matching Groupname = " .. grpname .. " Range= " .. range)
|
||||
found = true
|
||||
break
|
||||
@@ -1594,14 +1913,14 @@ do
|
||||
end
|
||||
--- Tertiary filter if not found
|
||||
if (not found) or HDSmod or SMAMod or CHMod then
|
||||
range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod)
|
||||
range, height, type, blind, ARMCapacity = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod)
|
||||
elseif not found then
|
||||
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
||||
end
|
||||
if found and string.find(grpname,"SHORAD",1,true) then
|
||||
type = MANTIS.SamType.POINT -- force short on match
|
||||
end
|
||||
return range, height, type, blind
|
||||
return range, height, type, blind, ARMCapacity
|
||||
end
|
||||
|
||||
--- [Internal] Function to set the SAM start state
|
||||
@@ -1636,8 +1955,9 @@ do
|
||||
group:OptionEngageRange(engagerange) --default engagement will be 95% of firing range
|
||||
local grpname = group:GetName()
|
||||
local grpcoord = group:GetCoordinate()
|
||||
local grprange,grpheight,type,blind = self:_GetSAMRange(grpname)
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
local grprange,grpheight,type,blind,ARMCapacity = self:_GetSAMRange(grpname)
|
||||
if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity})
|
||||
--table.insert( SEAD_Grps, grpname )
|
||||
if type == MANTIS.SamType.LONG then
|
||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
@@ -1701,12 +2021,15 @@ do
|
||||
if group:IsGround() and group:IsAlive() then
|
||||
local grpname = group:GetName()
|
||||
local grpcoord = group:GetCoordinate()
|
||||
local grprange, grpheight,type,blind = self:_GetSAMRange(grpname)
|
||||
local grprange, grpheight,type,blind, ARMCapacity = self:_GetSAMRange(grpname)
|
||||
-- TODO the below might stop working at some point after some hours, needs testing
|
||||
--local radaralive = group:IsSAM()
|
||||
if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end
|
||||
local radaralive = true
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here
|
||||
table.insert( SEAD_Grps, grpname )
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) -- make the table lighter, as I don't really use the zone here
|
||||
if type ~= MANTIS.SamType.POINT then
|
||||
table.insert( SEAD_Grps, grpname )
|
||||
end
|
||||
if type == MANTIS.SamType.LONG and radaralive then
|
||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
self:T({grpname,grprange, grpheight})
|
||||
@@ -1763,10 +2086,122 @@ do
|
||||
self.ShoradLink = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Function to smoke a group in decoy.
|
||||
-- @param #MANTIS self
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_SmokeUnits(Group)
|
||||
self:T("Smoking")
|
||||
local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME") or 0
|
||||
local TNow = timer.getTime()
|
||||
if TNow - LastSmoketime > 290 then -- Smoking lasts 5 minutes
|
||||
Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow)
|
||||
local units = Group:GetUnits() or {}
|
||||
local smoke = self.SmokeDecoyColor or SMOKECOLOR.White
|
||||
for _,unit in pairs(units) do
|
||||
if unit and unit:IsAlive() then
|
||||
unit:GetCoordinate():Smoke(smoke)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- MANTIS main functions
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
--- [Internal] Check if a system can and should defend for HARMs itself
|
||||
-- @param #MANTIS self
|
||||
-- @param Wrapper.Group#GROUP targetGroup
|
||||
-- @param #string targetName
|
||||
-- @param Wrapper.Group#GROUP attackerGroup
|
||||
-- @param #string weaponName
|
||||
-- @param Wrapper.Weapon#WEAPON weaponWrapper
|
||||
-- @param #number tti
|
||||
-- @param #number delay
|
||||
-- @return #boolean Outcome
|
||||
function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, weaponName, weaponWrapper, tti, delay)
|
||||
self:T(self.lid.."SeadAllowSuppression")
|
||||
|
||||
--- Thanks to @Goon Jan 2026
|
||||
----------------------------------------------------------------
|
||||
-- LOG INCOMING REQUEST
|
||||
----------------------------------------------------------------
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression REQUEST | target=%s | weapon=%s | tti=%s | delay=%s",tostring(targetName),
|
||||
tostring(weaponName),tostring(tti),tostring(delay)))
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- LOOK UP ARM CAPACITY FOR THIS SAM
|
||||
----------------------------------------------------------------
|
||||
local armcap = targetGroup:GetProperty("ARMCapacity")
|
||||
|
||||
if not armcap then
|
||||
for _, sam in pairs(self.SAM_Table or {}) do
|
||||
if sam[1] == targetName then
|
||||
armcap = sam[7] -- ARMCapacity
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression SAM DATA | target=%s | ARMCapacity=%s",tostring(targetName),armcap and tostring(armcap) or "nil"))
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- TRACK SEAD THREATS (PER TARGET)
|
||||
----------------------------------------------------------------
|
||||
local THREAT_WINDOW = 0.1 -- seconds
|
||||
|
||||
self.LastThreatEval = self.LastThreatEval or {}
|
||||
self.InboundARMs = self.InboundARMs or {}
|
||||
|
||||
local now = timer.getTime()
|
||||
local last = self.LastThreatEval[targetName] or 0
|
||||
|
||||
if (now - last) >= THREAT_WINDOW then
|
||||
self.InboundARMs[targetName] = (self.InboundARMs[targetName] or 0) + 1
|
||||
self.LastThreatEval[targetName] = now
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression NEW threat accepted | Δt=%.3f",now - last))
|
||||
else
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression duplicate evaluation ignored | Δt=%.3f",now - last))
|
||||
end
|
||||
|
||||
local inbound = self.InboundARMs[targetName] or 0
|
||||
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression THREAT COUNT | target=%s | inboundThreats=%d",tostring(targetName),inbound))
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- DECISION GATE
|
||||
----------------------------------------------------------------
|
||||
|
||||
-- No missiles left over → legacy behavior
|
||||
if targetGroup and targetGroup:IsAlive() then
|
||||
local AmmotT, AmmoS, _, _,AmmoM = targetGroup:GetAmmunition()
|
||||
-- TODO Check C-RAM probably needs an exception as it is a gun, need to check its effectiveness
|
||||
if AmmoM and AmmoM == 0 then
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no MISSILES) | target=%s",tostring(targetName)))
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- No ARM capacity defined → legacy behavior
|
||||
if (not armcap) or armcap == 0 then
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no ARMCAP) | target=%s",tostring(targetName)))
|
||||
return true
|
||||
end
|
||||
|
||||
-- Suppress only once enough threats accumulated
|
||||
if inbound >= armcap then
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (inbound %d >= cap %d) | target=%s",inbound,armcap,tostring(targetName)))
|
||||
return true
|
||||
end
|
||||
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> DENIED (inbound %d < cap %d) | target=%s",inbound,armcap,tostring(targetName)))
|
||||
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
--- [Internal] Check detection function
|
||||
-- @param #MANTIS self
|
||||
@@ -1790,7 +2225,7 @@ do
|
||||
local radius = _data[3]
|
||||
local height = _data[4]
|
||||
local blind = _data[5] * 1.25 + 1
|
||||
local shortsam = (_data[6] == MANTIS.SamType.SHORT) and true or false
|
||||
local shortsam = (_data[6] ~= MANTIS.SamType.LONG) and true or false
|
||||
if not shortsam then
|
||||
shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false
|
||||
end
|
||||
@@ -1801,6 +2236,7 @@ do
|
||||
if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then
|
||||
activeshorad = true
|
||||
end
|
||||
if samgroup:GetProperty("SHORAD_ACTIVE") == true and activeshorad == false then activeshorad = true end
|
||||
if IsInZone and (not suppressed) and (not activeshorad) then --check any target in zone and not currently managed by SEAD
|
||||
if samgroup:IsAlive() then
|
||||
-- switch on SAM
|
||||
@@ -1816,19 +2252,13 @@ do
|
||||
switch = true
|
||||
end
|
||||
if self.SamStateTracker[name] ~= "RED" and switch then
|
||||
self:__RedState(1,samgroup)
|
||||
self.SamStateTracker[name] = "RED"
|
||||
self:__RedState(1,samgroup)
|
||||
end
|
||||
-- TODO doesn't work
|
||||
if shortsam == true and self.SmokeDecoy == true then
|
||||
-- DONE Restrict on Distance
|
||||
if shortsam == true and self.SmokeDecoy == true and Distance < self.DetectAccousticRadius*1.5 then
|
||||
self:T("Smoking")
|
||||
local units = samgroup:GetUnits() or {}
|
||||
local smoke = self.SmokeDecoyColor or SMOKECOLOR.White
|
||||
for _,unit in pairs(units) do
|
||||
if unit and unit:IsAlive() then
|
||||
unit:GetCoordinate():Smoke(smoke)
|
||||
end
|
||||
end
|
||||
self:_SmokeUnits(samgroup)
|
||||
end
|
||||
-- link in to SHORAD if available
|
||||
-- DONE: Test integration fully
|
||||
@@ -1855,8 +2285,8 @@ do
|
||||
samgroup:OptionAlarmStateGreen()
|
||||
end
|
||||
if self.SamStateTracker[name] ~= "GREEN" then
|
||||
self:__GreenState(1,samgroup)
|
||||
self.SamStateTracker[name] = "GREEN"
|
||||
self:__GreenState(1,samgroup)
|
||||
end
|
||||
if self.debug or self.verbose then
|
||||
local text = string.format("SAM %s in alarm state GREEN!", name)
|
||||
@@ -1866,12 +2296,13 @@ do
|
||||
end --end alive
|
||||
end --end check
|
||||
end --for loop
|
||||
--[[
|
||||
if self.debug or self.verbose or self.logsamstatus then
|
||||
for _,_status in pairs(self.SamStateTracker) do
|
||||
if _status == "GREEN" then
|
||||
instatusgreen=instatusgreen+1
|
||||
elseif _status == "RED" then
|
||||
instatusred=instatusred+1
|
||||
instatusred=instatusred+1
|
||||
end
|
||||
end
|
||||
if self.Shorad then
|
||||
@@ -1880,6 +2311,8 @@ do
|
||||
end
|
||||
end
|
||||
end
|
||||
self:T(self.lid..string.format("SAM State Count: GREEN %d | RED %d | SHORAD %d",instatusred, instatusgreen, activeshorads))
|
||||
--]]
|
||||
return instatusred, instatusgreen, activeshorads
|
||||
end
|
||||
|
||||
@@ -1911,13 +2344,29 @@ do
|
||||
local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
|
||||
local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange)
|
||||
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange)
|
||||
else
|
||||
local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic)
|
||||
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic)
|
||||
end
|
||||
|
||||
local function GetReport()
|
||||
|
||||
if self.debug or self.verbose or self.logsamstatus then
|
||||
for _,_status in pairs(self.SamStateTracker) do
|
||||
if _status == "GREEN" then
|
||||
instatusgreen=instatusgreen+1
|
||||
elseif _status == "RED" then
|
||||
instatusred=instatusred+1
|
||||
end
|
||||
end
|
||||
if self.Shorad then
|
||||
for _,_name in pairs(self.Shorad.ActiveGroups or {}) do
|
||||
activeshorads=activeshorads+1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local statusreport = REPORT:New("\nMANTIS Status "..self.name)
|
||||
statusreport:Add("+-----------------------------+")
|
||||
statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred))
|
||||
@@ -2013,21 +2462,22 @@ do
|
||||
else
|
||||
self.Detection = self:StartIntelDetection()
|
||||
end
|
||||
--[[
|
||||
if self.advAwacs and not self.automode then
|
||||
self.AWACS_Detection = self:StartAwacsDetection()
|
||||
end
|
||||
--]]
|
||||
|
||||
if self.autoshorad then
|
||||
self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff)
|
||||
self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.Coalition,self.UseEmOnOff,self.SmokeDecoy,self.SmokeDecoyColor)
|
||||
self.Shorad:SetDefenseLimits(80,95)
|
||||
self.ShoradLink = true
|
||||
self.Shorad.Groupset=self.ShoradGroupSet
|
||||
self.Shorad.debug = self.debug
|
||||
self.Shorad:AddCallBack(self)
|
||||
end
|
||||
if self.shootandscoot and self.SkateZones and self.Shorad then
|
||||
self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation)
|
||||
end
|
||||
|
||||
self:HandleEvent(EVENTS.Hit,self._EventHandler)
|
||||
self:HandleEvent(EVENTS.UnitLost,self._EventHandler)
|
||||
|
||||
self:__Status(-math.random(1,10))
|
||||
return self
|
||||
end
|
||||
@@ -2225,6 +2675,7 @@ do
|
||||
function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name)
|
||||
self:T({From, Event, To, Name})
|
||||
self.SuppressedGroups[Name] = false
|
||||
self.InboundARMs[Name] = 0
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ PSEUDOATC.id="PseudoATC | "
|
||||
|
||||
--- PSEUDOATC version.
|
||||
-- @field #number version
|
||||
PSEUDOATC.version="0.10.5"
|
||||
PSEUDOATC.version="0.10.6"
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -579,6 +579,9 @@ function PSEUDOATC:MenuAirports(GID,UID)
|
||||
-- Create menu reporting commands
|
||||
missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name)
|
||||
missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name)
|
||||
if self.radios then
|
||||
missionCommands.addCommandForGroup(GID, "Radios", submenu, self.ReportRadios, self, GID, UID, pos, name)
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, GID))
|
||||
@@ -705,7 +708,30 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location)
|
||||
|
||||
end
|
||||
|
||||
--- Report absolute bearing and range form player unit to airport.
|
||||
--- Report airport radio information.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
-- @param Core.Point#COORDINATE position Coordinate of the airport.
|
||||
-- @param #string location Name of the airport.
|
||||
function PSEUDOATC:ReportRadios(GID, UID, position, location)
|
||||
self:F({GID=GID, UID=UID, position=position, location=location})
|
||||
if self.radios then
|
||||
local Text=""
|
||||
local radio = self.radios:GetClosestRadio(position,9) --Navigation.Radios#RADIOS.Radio
|
||||
if radio then
|
||||
Text=self.radios:_GetMarkerText(radio)
|
||||
else
|
||||
Text=self.group[GID].player[UID].playername..", no radio information found!"
|
||||
end
|
||||
-- Send message
|
||||
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, Text, self.mdur, true)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Report absolute bearing and range from player unit to airport.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
@@ -979,3 +1005,22 @@ function PSEUDOATC:_myname(unitname)
|
||||
|
||||
return string.format("%s (%s)", csign, pname)
|
||||
end
|
||||
|
||||
--- Returns a string which consits of this callsign and the player name.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #string path Path to map data, e.g. `[[<DCS_Install_Directory>\Mods\terrains\<Map_Name>\Radio.lua]]` (replace with correct path).
|
||||
-- Needs `lfs` and `io` to be desanitized in the `MissionScripting.lua` in `<DCS_Install_Directory>\Scripts`
|
||||
-- @return #PSEUDOATC self
|
||||
-- @usage
|
||||
--
|
||||
-- mypseudoatc:SetUsingRadioInformationFromMap([[C:\Program Files\Eagle Dynamics\DCS World.Openbeta\Mods\terrains\Caucasus\Radio.lua]])
|
||||
--
|
||||
function PSEUDOATC:SetUsingRadioInformationFromMap(path)
|
||||
if RADIOS and lfs and io then
|
||||
self.radios = RADIOS:NewFromFile(path)
|
||||
else
|
||||
self:E("PSEUDOATC:SetUsingRadioInformationFromMap Needs `lfs`and `io` to be desanitized in the `MissionScripting.lua` in `<DCS_Install_Directory>/Scripts`")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -5584,7 +5584,7 @@ end
|
||||
-- @param #string dest Name of the destination airport.
|
||||
function RAT:_ATCAddFlight(name, dest)
|
||||
-- Debug info
|
||||
BASE:I(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.", dest, name, dest))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.", dest, name, dest))
|
||||
|
||||
-- Create new flight
|
||||
local flight={} --#RAT.AtcFlight
|
||||
@@ -5603,7 +5603,7 @@ end
|
||||
function RAT._ATCDelFlight(t,entry)
|
||||
for k,_ in pairs(t) do
|
||||
if k==entry then
|
||||
BASE:I(RAT.id..string.format("Removing flight %s from queue", entry))
|
||||
BASE:T(RAT.id..string.format("Removing flight %s from queue", entry))
|
||||
t[entry]=nil
|
||||
end
|
||||
end
|
||||
@@ -5614,7 +5614,7 @@ end
|
||||
-- @param #string name Group name of the flight.
|
||||
-- @param #number time Time the fight first registered.
|
||||
function RAT._ATCRegisterFlight(name, time)
|
||||
BASE:I(RAT.id..string.format("Flight %s registered at ATC for landing clearance.", name))
|
||||
BASE:T(RAT.id..string.format("Flight %s registered at ATC for landing clearance.", name))
|
||||
RAT.ATC.flight[name].Tarrive=time
|
||||
RAT.ATC.flight[name].holding=0
|
||||
end
|
||||
@@ -5649,15 +5649,16 @@ function RAT._ATCStatus()
|
||||
|
||||
-- Aircraft is holding.
|
||||
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy)
|
||||
BASE:I(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
elseif hold==RAT.ATC.onfinal then
|
||||
|
||||
-- Aircarft is on final approach for landing.
|
||||
local Tfinal=Tnow-flight.Tonfinal
|
||||
local Tonfinal = flight.Tonfinal or timer.getTime()-1
|
||||
local Tfinal=Tnow-Tonfinal
|
||||
|
||||
local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60)
|
||||
BASE:I(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
elseif hold==RAT.ATC.unregistered then
|
||||
|
||||
@@ -5711,13 +5712,13 @@ function RAT._ATCCheck()
|
||||
-- Debug message.
|
||||
local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.",
|
||||
airportname, flightname, qID, nqueue, flight.holding/60, flight.holding%60)
|
||||
BASE:I(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
else
|
||||
|
||||
local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.",
|
||||
airportname, flightname, flight.holding/60, flight.holding%60)
|
||||
BASE:I(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
-- Clear flight for landing.
|
||||
RAT._ATCClearForLanding(airportname, flightname)
|
||||
@@ -5772,7 +5773,7 @@ function RAT._ATCClearForLanding(airportname, flightname)
|
||||
|
||||
|
||||
-- Debug message.
|
||||
BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing", airportname, flightname))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing", airportname, flightname))
|
||||
|
||||
if string.find(flightname,"#") then
|
||||
flightname = string.match(flightname,"^(.+)#")
|
||||
@@ -5799,8 +5800,9 @@ function RAT._ATCFlightLanded(name)
|
||||
|
||||
-- Times for holding and final approach.
|
||||
local Tnow=timer.getTime()
|
||||
local Tfinal=Tnow-flight.Tonfinal
|
||||
local Thold=flight.Tonfinal-flight.Tarrive
|
||||
local Tonfinal = flight.Tonfinal or timer.getTime()-1
|
||||
local Tfinal=Tnow-Tonfinal
|
||||
local Thold=Tonfinal-flight.Tarrive
|
||||
|
||||
local airport=RAT.ATC.airport[dest] --#RAT.AtcAirport
|
||||
|
||||
@@ -5823,9 +5825,9 @@ function RAT._ATCFlightLanded(name)
|
||||
local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600
|
||||
|
||||
-- Debug info
|
||||
BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60))
|
||||
BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.", dest, airport.Nonfinal))
|
||||
BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, airport.traffic, TrafficPerHour))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.", dest, airport.Nonfinal))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, airport.traffic, TrafficPerHour))
|
||||
|
||||
if string.find(name,"#") then
|
||||
name = string.match(name,"^(.+)#")
|
||||
|
||||
@@ -109,7 +109,6 @@
|
||||
-- @field Core.Menu#MENU_MISSION menuF10root Specific user defined root F10 menu.
|
||||
-- @field #number ceilingaltitude Range ceiling altitude in ft MSL. Aircraft above this altitude are not considered to be in the range. Default is 20000 ft.
|
||||
-- @field #boolean ceilingenabled Range has a ceiling and is not unlimited. Default is false.
|
||||
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven
|
||||
@@ -1271,6 +1270,9 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
|
||||
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
|
||||
self.controlmsrs:SetLabel("RANGEC")
|
||||
self.controlmsrs:SetVolume(Volume or 1.0)
|
||||
if self.rangezone then
|
||||
self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate())
|
||||
end
|
||||
self.controlsrsQ = MSRSQUEUE:New("CONTROL")
|
||||
|
||||
self.instructmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 305, Modulation or radio.modulation.AM)
|
||||
@@ -1278,6 +1280,9 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
|
||||
self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE)
|
||||
self.instructmsrs:SetLabel("RANGEI")
|
||||
self.instructmsrs:SetVolume(Volume or 1.0)
|
||||
if self.rangezone then
|
||||
self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate())
|
||||
end
|
||||
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
|
||||
|
||||
if PathToGoogleKey then
|
||||
@@ -1316,8 +1321,13 @@ function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender
|
||||
self.rangecontrol = true
|
||||
if relayunitname then
|
||||
local unit = UNIT:FindByName(relayunitname)
|
||||
local Coordinate = unit:GetCoordinate()
|
||||
self.rangecontrolrelayname = relayunitname
|
||||
if unit then
|
||||
local Coordinate = unit:GetCoordinate()
|
||||
self.rangecontrolrelayname = relayunitname
|
||||
self.controlmsrs:SetCoordinate(Coordinate)
|
||||
else
|
||||
MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -1345,9 +1355,13 @@ function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gen
|
||||
self.instructor = true
|
||||
if relayunitname then
|
||||
local unit = UNIT:FindByName(relayunitname)
|
||||
local Coordinate = unit:GetCoordinate()
|
||||
self.instructmsrs:SetCoordinate(Coordinate)
|
||||
self.instructorrelayname = relayunitname
|
||||
if unit then
|
||||
local Coordinate = unit:GetCoordinate()
|
||||
self.instructmsrs:SetCoordinate(Coordinate)
|
||||
self.instructorrelayname = relayunitname
|
||||
else
|
||||
MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -90,7 +90,8 @@
|
||||
-- @image Scoring.JPG
|
||||
|
||||
--- @type SCORING
|
||||
-- @field Players A collection of the current players that have joined the game.
|
||||
-- @field #table Players A collection of the current players that have joined the game.
|
||||
-- @field Core.Set#SET_SCENERY ScoringScenery
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- SCORING class
|
||||
@@ -229,7 +230,10 @@ SCORING = {
|
||||
ClassID = 0,
|
||||
Players = {},
|
||||
AutoSave = true,
|
||||
version = "1.18.4"
|
||||
version = "1.18.5",
|
||||
ScoringScenery = nil, -- Core.Set#SET_SCENERY
|
||||
SceneryHitsInZone = false,
|
||||
LoadSave = false,
|
||||
}
|
||||
|
||||
local _SCORINGCoalition = {
|
||||
@@ -248,15 +252,16 @@ local _SCORINGCategory = {
|
||||
--- Creates a new SCORING object to administer the scoring achieved by players.
|
||||
-- @param #SCORING self
|
||||
-- @param #string GameName The name of the game. This name is also logged in the CSV score file.
|
||||
-- @param #string SavePath (Optional) Path where to save the CSV file, defaults to your **<User>\\Saved Games\\DCS\\Logs** folder.
|
||||
-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off.
|
||||
-- @param #string SavePath (Optional) Path where to save the CSV files, defaults to your **<User>\\Saved Games\\DCS\\Logs** folder. See next two options.
|
||||
-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off. This stores a detailed table which will never be loaded again by SCORING (for e.g. Discord purposes).
|
||||
-- @param #boolean LoadSave (Optional) If passed as `true` save summary scores per player, and load at restart of the mission.
|
||||
-- @return #SCORING self
|
||||
-- @usage
|
||||
--
|
||||
-- -- Define a new scoring object for the mission Gori Valley.
|
||||
-- ScoringObject = SCORING:New( "Gori Valley" )
|
||||
--
|
||||
function SCORING:New( GameName, SavePath, AutoSave )
|
||||
function SCORING:New( GameName, SavePath, AutoSave, LoadSave )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
|
||||
@@ -264,7 +269,7 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
||||
if GameName then
|
||||
self.GameName = GameName
|
||||
else
|
||||
error( "A game name must be given to register the scoring results" )
|
||||
error( "A game name must be given to register the scoring results!" )
|
||||
end
|
||||
|
||||
-- Additional Object scores
|
||||
@@ -299,6 +304,12 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
||||
self.penaltyoncoalitionchange = true
|
||||
|
||||
self:SetDisplayMessagePrefix()
|
||||
|
||||
self.SceneryHitsInZone = false
|
||||
|
||||
if LoadSave then
|
||||
self.LoadSave = LoadSave
|
||||
end
|
||||
|
||||
-- Event handlers
|
||||
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
|
||||
@@ -320,13 +331,67 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
||||
|
||||
-- Create the CSV file.
|
||||
self.AutoSavePath = SavePath
|
||||
self.AutoSave = AutoSave or true
|
||||
self:OpenCSV( GameName )
|
||||
self.AutoSave = (AutoSave == nil or AutoSave == true) and true or false
|
||||
if self.AutoSavePath and self.AutoSave == true then
|
||||
self:OpenCSV( GameName )
|
||||
end
|
||||
|
||||
self:I("SCORING "..tostring(GameName).." started! v"..self.version)
|
||||
|
||||
if LoadSave == true then
|
||||
self:_LoadPlayerSummaryScore()
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
--- [Internal] Helper to load scores from disk at scoring start
|
||||
-- @param #SCORING self
|
||||
-- @return #SCORING self
|
||||
function SCORING:_LoadPlayerSummaryScore()
|
||||
|
||||
if lfs and io and self.LoadSave == true then
|
||||
local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]]
|
||||
local filename = self.GameName or "PlayerScoresSummary"
|
||||
filename = filename..".csv"
|
||||
if UTILS.CheckFileExists(path,filename) then
|
||||
local ok, data = UTILS.LoadFromFile(path,filename)
|
||||
-- Playername;;Score;;Penalty
|
||||
table.remove(data,1)
|
||||
for _,_data in pairs(data) do
|
||||
local line = UTILS.Split(_data,";;")
|
||||
local playername = tostring(line[1])
|
||||
local score = tonumber(line[2])
|
||||
local penalty = tonumber(line[3])
|
||||
self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty))
|
||||
local PlayerData = self.Players[playername]
|
||||
if not PlayerData then
|
||||
PlayerData = {}
|
||||
PlayerData.Hit = {}
|
||||
PlayerData.Destroy = {}
|
||||
PlayerData.Goals = {}
|
||||
PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty}
|
||||
PlayerData.Mission = {}
|
||||
PlayerData.HitPlayers = {}
|
||||
PlayerData.Score = score
|
||||
PlayerData.Penalty = penalty
|
||||
PlayerData.PenaltyCoalition = 0
|
||||
PlayerData.PenaltyWarning = 0
|
||||
self.Players[playername] = PlayerData
|
||||
else
|
||||
PlayerData.Score = score
|
||||
PlayerData.Penalty =penalty
|
||||
self.Players[playername] = PlayerData
|
||||
PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set a prefix string that will be displayed at each scoring message sent.
|
||||
-- @param #SCORING self
|
||||
-- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string.
|
||||
@@ -375,7 +440,7 @@ function SCORING:AddUnitScore( ScoreUnit, Score )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a @{Wrapper.Unit} for additional scoring when the @{Wrapper.Unit} is destroyed.
|
||||
--- Removes a @{Wrapper.Unit} for scoring when the @{Wrapper.Unit} is destroyed.
|
||||
-- @param #SCORING self
|
||||
-- @param Wrapper.Unit#UNIT ScoreUnit The @{Wrapper.Unit} for which the Score needs to be given.
|
||||
-- @return #SCORING
|
||||
@@ -392,10 +457,26 @@ end
|
||||
-- Note that if there was already a @{Wrapper.Static} declared within the scoring with the same name,
|
||||
-- then the old @{Wrapper.Static} will be replaced with the new @{Wrapper.Static}.
|
||||
-- @param #SCORING self
|
||||
-- @param Wrapper.Static#UNIT ScoreStatic The @{Wrapper.Static} for which the Score needs to be given.
|
||||
-- @param Wrapper.Static#STATIC ScoreStatic The @{Wrapper.Static} for which the Score needs to be given.
|
||||
-- @param #number Score The Score value.
|
||||
-- @return #SCORING
|
||||
function SCORING:AddStaticScore( ScoreStatic, Score )
|
||||
return self:AddScoreStatic( ScoreStatic, Score )
|
||||
end
|
||||
|
||||
--- Add a @{Wrapper.Static} for additional scoring when the @{Wrapper.Static} is destroyed.
|
||||
-- Note that if there was already a @{Wrapper.Static} declared within the scoring with the same name,
|
||||
-- then the old @{Wrapper.Static} will be replaced with the new @{Wrapper.Static}.
|
||||
-- @param #SCORING self
|
||||
-- @param Wrapper.Static#STATIC ScoreStatic The @{Wrapper.Static} for which the Score needs to be given.
|
||||
-- @param #number Score The Score value.
|
||||
-- @return #SCORING
|
||||
function SCORING:AddScoreStatic( ScoreStatic, Score )
|
||||
|
||||
if ScoreStatic == nil then
|
||||
BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!")
|
||||
return self
|
||||
end
|
||||
|
||||
local StaticName = ScoreStatic:GetName()
|
||||
|
||||
@@ -404,7 +485,61 @@ function SCORING:AddStaticScore( ScoreStatic, Score )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a @{Wrapper.Static} for additional scoring when the @{Wrapper.Static} is destroyed.
|
||||
--- Add a @{Wrapper.Scenery} for additional scoring when the @{Wrapper.Scenery} is destroyed.
|
||||
-- @param #SCORING self
|
||||
-- @param Wrapper.Scenery#SCENERY ScoreScenery The @{Wrapper.Scenery} for which the Score needs to be given.
|
||||
-- @param #number Score The Score value.
|
||||
-- @return #SCORING
|
||||
function SCORING:AddScoreScenery( ScoreScenery, Score )
|
||||
|
||||
if ScoreScenery == nil then
|
||||
self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!")
|
||||
return self
|
||||
end
|
||||
|
||||
if not self.ScoringScenery then
|
||||
self.ScoringScenery = SET_SCENERY:New() -- Core.Set#SET_SCENERY
|
||||
end
|
||||
|
||||
local StaticName = ScoreScenery:GetName()
|
||||
self:T("Scenery name = ".. StaticName)
|
||||
|
||||
self.ScoringScenery:AddScenery(ScoreScenery)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a @{Wrapper.Scenery} for scoring when the @{Wrapper.Scenery} is destroyed.
|
||||
-- @param #SCORING self
|
||||
-- @param Wrapper.Scenery#SCENERY ScoreStatic The @{Wrapper.Scenery} for which the Score needs to be given.
|
||||
-- @return #SCORING
|
||||
function SCORING:RemoveSceneryScore( ScoreScenery )
|
||||
|
||||
local StaticName = ScoreScenery:GetName()
|
||||
|
||||
self.ScoringObjects[StaticName] = nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Specify a special additional score for a @{Core.Set#SET_SCENERY}.
|
||||
-- @param #SCORING self
|
||||
-- @param Core.Set#SET_SCENERY Set The @{Core.Set#SET_SCENERY} for which each @{Wrapper.Scenery} in the SET a Score is given.
|
||||
-- @param #number Score The Score value.
|
||||
-- @return #SCORING
|
||||
function SCORING:AddScoreSetScenery(Set, Score)
|
||||
local set = Set.Set
|
||||
|
||||
for _,_static in pairs (set) do
|
||||
if _static ~= nil then
|
||||
self:AddScoreScenery(_static,Score)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a @{Wrapper.Static} for scoring when the @{Wrapper.Static} is destroyed.
|
||||
-- @param #SCORING self
|
||||
-- @param Wrapper.Static#UNIT ScoreStatic The @{Wrapper.Static} for which the Score needs to be given.
|
||||
-- @return #SCORING
|
||||
@@ -459,6 +594,31 @@ function SCORING:AddScoreSetGroup(Set, Score)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Specify a special additional score for a @{Core.Set#SET_STATIC}.
|
||||
-- @param #SCORING self
|
||||
-- @param Core.Set#SET_STATIC Set The @{Core.Set#SET_STATIC} for which each @{Wrapper.Static} in the SET a Score is given.
|
||||
-- @param #number Score The Score value.
|
||||
-- @return #SCORING
|
||||
function SCORING:AddScoreSetStatic(Set, Score)
|
||||
local set = Set:GetSetObjects()
|
||||
|
||||
for _,_static in pairs (set) do
|
||||
if _static and _static:IsAlive() then
|
||||
self:AddStaticScore(_static,Score)
|
||||
end
|
||||
end
|
||||
|
||||
local function AddScore(static)
|
||||
self:AddStaticScore(static,Score)
|
||||
end
|
||||
|
||||
function Set:OnAfterAdded(From,Event,To,ObjectName,Object)
|
||||
AddScore(Object)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a @{Core.Zone} to define additional scoring when any object is destroyed in that zone.
|
||||
-- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced!
|
||||
-- This allows for a dynamic destruction zone evolution within your mission.
|
||||
@@ -478,7 +638,40 @@ function SCORING:AddZoneScore( ScoreZone, Score )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove a @{Core.Zone} for additional scoring.
|
||||
--- Allow Scenery hits in Zones to count (no specific(!) scenery targets). NOTE - Allowing this can spam your scoring display!
|
||||
-- @param #SCORING self
|
||||
-- @return #SCORING self
|
||||
function SCORING:EnableSceneryHitsinZones()
|
||||
self.SceneryHitsInZone = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Disallow Scenery hits in Zones to count (no specific(!) scenery targets).
|
||||
-- @param #SCORING self
|
||||
-- @return #SCORING self
|
||||
function SCORING:DisableSceneryHitsinZones()
|
||||
self.SceneryHitsInZone = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a @{Core.Set#SET_ZONE} to define additional scoring when any object is destroyed in that zone.
|
||||
-- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced!
|
||||
-- This allows for a dynamic destruction zone evolution within your mission.
|
||||
-- @param #SCORING self
|
||||
-- @param Core.Set#SET_ZONE ScoreZoneSet The @{Core.Set#SET_ZONE} which defines the destruction score perimeters.
|
||||
-- Note that a zone can be a polygon or a moving zone.
|
||||
-- @param #number Score The Score value.
|
||||
-- @return #SCORING
|
||||
function SCORING:AddZoneScoreSet( ScoreZoneSet, Score )
|
||||
|
||||
for _,_zone in pairs(ScoreZoneSet.Set or {}) do
|
||||
self:AddZoneScore(_zone,Score)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove a @{Core.Zone} for scoring.
|
||||
-- The scoring will search if any @{Core.Zone} is added with the given name, and will remove that zone from the scoring.
|
||||
-- This allows for a dynamic destruction zone evolution within your mission.
|
||||
-- @param #SCORING self
|
||||
@@ -759,6 +952,20 @@ function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score )
|
||||
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
|
||||
if PlayerName then
|
||||
local PlayerData = self.Players[PlayerName]
|
||||
if not PlayerData then
|
||||
PlayerData = {}
|
||||
PlayerData.Goals = {}
|
||||
PlayerData.Hit = {}
|
||||
PlayerData.Destroy = {}
|
||||
PlayerData.Goals = {}
|
||||
PlayerData.Mission = {}
|
||||
PlayerData.HitPlayers = {}
|
||||
PlayerData.Score = 0
|
||||
PlayerData.Penalty = 0
|
||||
PlayerData.PenaltyCoalition = 0
|
||||
PlayerData.PenaltyWarning = 0
|
||||
self.Players[PlayerName] = PlayerData
|
||||
end
|
||||
|
||||
PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 }
|
||||
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
|
||||
@@ -956,7 +1163,7 @@ end
|
||||
-- @param #SCORING self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function SCORING:_EventOnHit( Event )
|
||||
self:F( { Event } )
|
||||
--self:F( { Event } )
|
||||
|
||||
local InitUnit = nil
|
||||
local InitUNIT = nil
|
||||
@@ -985,6 +1192,8 @@ function SCORING:_EventOnHit( Event )
|
||||
local TargetUnitCoalition = nil
|
||||
local TargetUnitCategory = nil
|
||||
local TargetUnitType = nil
|
||||
local TargetIsScenery = false
|
||||
local TargetSceneryObject = nil
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
@@ -1006,7 +1215,7 @@ function SCORING:_EventOnHit( Event )
|
||||
InitUnitCategory = _SCORINGCategory[InitCategory]
|
||||
InitUnitType = InitType
|
||||
|
||||
self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType, InitUnitCoalition, InitUnitCategory, InitUnitType } )
|
||||
--self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType, InitUnitCoalition, InitUnitCategory, InitUnitType } )
|
||||
end
|
||||
|
||||
if Event.TgtDCSUnit then
|
||||
@@ -1024,12 +1233,23 @@ function SCORING:_EventOnHit( Event )
|
||||
-- TargetCategory = TargetUnit:getDesc().category
|
||||
TargetCategory = Event.TgtCategory
|
||||
TargetType = Event.TgtTypeName
|
||||
|
||||
|
||||
-- Scenery hit
|
||||
if TargetUNIT ~= nil and TargetUNIT:IsInstanceOf("SCENERY") then
|
||||
TargetCategory = Unit.Category.STRUCTURE
|
||||
TargetIsScenery = true
|
||||
TargetType = "Scenery"
|
||||
TargetSceneryObject = TargetUNIT
|
||||
self:T("***** Target is Scenery and TargetUNIT is SCENERY object!")
|
||||
--UTILS.PrintTableToLog(TargetSceneryObject)
|
||||
end
|
||||
|
||||
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
|
||||
TargetUnitCategory = _SCORINGCategory[TargetCategory]
|
||||
TargetUnitType = TargetType
|
||||
|
||||
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType, TargetUnitCoalition, TargetUnitCategory, TargetUnitType } )
|
||||
--self:T( { TargetUnitName=TargetUnitName, TargetGroupName=TargetGroupName, TargetPlayerName=TargetPlayerName, TargetCoalition=TargetCoalition, TargetCategory=TargetCategory, TargetType=TargetType, TargetUnitCoalition=TargetUnitCoalition, TargetUnitCategory=TargetUnitCategory, TargetUnitType=TargetUnitType } )
|
||||
end
|
||||
|
||||
if InitPlayerName ~= nil then -- It is a player that is hitting something
|
||||
@@ -1042,7 +1262,7 @@ function SCORING:_EventOnHit( Event )
|
||||
self:T( "Hitting Something" )
|
||||
|
||||
-- What is he hitting?
|
||||
if TargetCategory then
|
||||
if (TargetCategory ~=nil) and (TargetIsScenery == false) then
|
||||
|
||||
-- A target got hit, score it.
|
||||
-- Player contains the score data from self.Players[InitPlayerName]
|
||||
@@ -1062,7 +1282,7 @@ function SCORING:_EventOnHit( Event )
|
||||
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
|
||||
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
|
||||
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth
|
||||
if PlayerHit.UNIT.ThreatType == nil then
|
||||
if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType == nil then
|
||||
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
|
||||
-- if this fails for some reason, set a good default value
|
||||
if PlayerHit.ThreatType == nil or PlayerHit.ThreatType == "" then
|
||||
@@ -1117,17 +1337,22 @@ function SCORING:_EventOnHit( Event )
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
else
|
||||
elseif TargetIsScenery ~= true then
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
|
||||
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
elseif TargetIsScenery == true then
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object." .. " Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
end
|
||||
self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
end
|
||||
else -- A scenery object was hit.
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.",
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit nothing special.",
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
@@ -1143,21 +1368,21 @@ function SCORING:_EventOnHit( Event )
|
||||
|
||||
-- It is a weapon initiated by a player, that is hitting something
|
||||
-- This seems to occur only with scenery and static objects.
|
||||
if Event.WeaponPlayerName ~= nil then
|
||||
self:_AddPlayerFromUnit( Event.WeaponUNIT )
|
||||
if self.Players[Event.WeaponPlayerName] then -- This should normally not happen, but i'll test it anyway.
|
||||
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
|
||||
self:_AddPlayerFromUnit( TargetUNIT )
|
||||
end
|
||||
if Event.WeaponPlayerName ~= nil or TargetIsScenery == true then
|
||||
|
||||
local playername = Event.WeaponPlayerName or Event.IniPlayerName or "Ghost"
|
||||
|
||||
--self:_AddPlayerFromUnit( Event.WeaponUNIT )
|
||||
if self.Players[playername] then -- This should normally not happen, but i'll test it anyway.
|
||||
|
||||
self:T( "Hitting Scenery" )
|
||||
self:T( "Hitting Scenery or Static" )
|
||||
|
||||
-- What is he hitting?
|
||||
if TargetCategory then
|
||||
if Event.TgtObjectCategory then
|
||||
|
||||
-- A scenery or static got hit, score it.
|
||||
-- Player contains the score data from self.Players[WeaponPlayerName]
|
||||
local Player = self.Players[Event.WeaponPlayerName]
|
||||
local Player = self.Players[playername]
|
||||
|
||||
-- Ensure there is a hit table per TargetCategory and TargetUnitName.
|
||||
Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {}
|
||||
@@ -1165,15 +1390,17 @@ function SCORING:_EventOnHit( Event )
|
||||
|
||||
-- PlayerHit contains the score counters and data per unit that was hit.
|
||||
local PlayerHit = Player.Hit[TargetCategory][TargetUnitName]
|
||||
|
||||
|
||||
-- Init player scores
|
||||
PlayerHit.Score = PlayerHit.Score or 0
|
||||
PlayerHit.Penalty = PlayerHit.Penalty or 0
|
||||
PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
|
||||
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
|
||||
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
|
||||
|
||||
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth
|
||||
if PlayerHit.UNIT.ThreatType == nil then
|
||||
if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType == nil then
|
||||
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
|
||||
-- if this fails for some reason, set a good default value
|
||||
if PlayerHit.ThreatType == nil then
|
||||
@@ -1181,8 +1408,8 @@ function SCORING:_EventOnHit( Event )
|
||||
PlayerHit.ThreatType = "Unknown"
|
||||
end
|
||||
else
|
||||
PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel
|
||||
PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType
|
||||
PlayerHit.ThreatLevel = PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1
|
||||
PlayerHit.ThreatType = PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or "Unknown"
|
||||
end
|
||||
|
||||
-- Only grant hit scores if there was more than one second between the last hit.
|
||||
@@ -1190,50 +1417,69 @@ function SCORING:_EventOnHit( Event )
|
||||
PlayerHit.TimeStamp = timer.getTime()
|
||||
|
||||
local Score = 0
|
||||
|
||||
if InitCoalition then -- A coalition object was hit, probably a static.
|
||||
if InitCoalition == TargetCoalition then
|
||||
-- TODO: Penalty according scale
|
||||
local Penalty = 10
|
||||
Player.Penalty = Player.Penalty + Penalty --* self.ScaleDestroyPenalty
|
||||
PlayerHit.Penalty = PlayerHit.Penalty + Penalty --* self.ScaleDestroyPenalty
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty
|
||||
|
||||
MESSAGE
|
||||
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
|
||||
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
|
||||
"Penalty: -" .. Penalty .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update
|
||||
)
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
else
|
||||
|
||||
local TgtName = Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or "Unknown"
|
||||
|
||||
--if InitCoalition then -- A coalition object was hit, probably a static.
|
||||
if TargetIsScenery == true and self.ScoringScenery:IsInSet(TargetSceneryObject) then
|
||||
Player.Score = Player.Score + self.ScoreIncrementOnHit
|
||||
PlayerHit.Score = PlayerHit.Score + self.ScoreIncrementOnHit
|
||||
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. playername .. "' hit scenery target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
|
||||
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
|
||||
self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
end
|
||||
else -- A scenery object was hit.
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit scenery object.",
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
self:ScoreCSV( playername, TargetPlayerName, "HIT_SCORE", 1, 1, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
elseif TargetIsScenery == false and Event.TgtObjectCategory == Object.Category.STATIC and self.ScoringObjects[TgtName] then
|
||||
Player.Score = Player.Score + self.ScoreIncrementOnHit
|
||||
PlayerHit.Score = PlayerHit.Score + self.ScoreIncrementOnHit
|
||||
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. playername .. "' hit static target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
|
||||
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
|
||||
self:ScoreCSV( Event.WeaponPlayerName, "", "HIT_SCORE", 1, 0, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, "", "Scenery", TargetUnitType )
|
||||
end
|
||||
self:ScoreCSV( playername, TargetPlayerName, "HIT_SCORE", 1, 1, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
|
||||
else
|
||||
self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")")
|
||||
end
|
||||
--end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if there are Zones where the destruction happened.
|
||||
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
|
||||
self:F( { ScoringZone = ScoreZoneData } )
|
||||
local hit=Event.TgtUnit
|
||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||
local Score = ScoreZoneData.Score
|
||||
if TargetUNIT and ScoreZone:IsVec2InZone( TargetUNIT:GetVec2() ) then
|
||||
-- A scenery or static got hit, score it.
|
||||
-- Player contains the score data from self.Players[WeaponPlayerName]
|
||||
local PlayerName = Event.IniPlayerName or "Ghost"
|
||||
local Player = self.Players[PlayerName]
|
||||
if Player then
|
||||
Player.Score = Player.Score + Score
|
||||
Player.Score = Player.Score + self.ScoreIncrementOnHit
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||
|
||||
self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Track DEAD or CRASH events for the scoring.
|
||||
-- @param #SCORING self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
@@ -1291,7 +1537,7 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
local Destroyed = false
|
||||
|
||||
-- What is the player destroying?
|
||||
if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 and (TargetUnit.BirthTime == nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp > TargetUnit.BirthTime) then -- Was there a hit for this unit for this player before registered???
|
||||
if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 and TargetUnit and (TargetUnit.BirthTime == nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp > TargetUnit.BirthTime) then -- Was there a hit for this unit for this player before registered???
|
||||
|
||||
local TargetThreatLevel = Player.Hit[TargetCategory][TargetUnitName].ThreatLevel
|
||||
local TargetThreatType = Player.Hit[TargetCategory][TargetUnitName].ThreatType
|
||||
@@ -1411,22 +1657,24 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
|
||||
end
|
||||
else
|
||||
-- Check if there are Zones where the destruction happened.
|
||||
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
|
||||
self:F( { ScoringZone = ScoreZoneData } )
|
||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||
local Score = ScoreZoneData.Score
|
||||
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||
|
||||
self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
|
||||
Destroyed = true
|
||||
if self.SceneryHitsInZone == true then
|
||||
-- Check if there are Zones where the destruction happened.
|
||||
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
|
||||
self:F( { ScoringZone = ScoreZoneData } )
|
||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||
local Score = ScoreZoneData.Score
|
||||
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||
|
||||
self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
|
||||
Destroyed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1770,9 +2018,12 @@ end
|
||||
--- Report all players score
|
||||
-- @param #SCORING self
|
||||
-- @param Wrapper.Group#GROUP PlayerGroup The player group.
|
||||
function SCORING:ReportScoreAllSummary( PlayerGroup )
|
||||
-- @param #boolean JustScore If this is true, return just a table with playernames and overall scores.
|
||||
-- @return #table ReportTable Table returned if JustScore is true.
|
||||
function SCORING:ReportScoreAllSummary( PlayerGroup, JustScore )
|
||||
|
||||
local PlayerMessage = ""
|
||||
local ReportTable = {}
|
||||
|
||||
self:T( { "Summary Score Report of All Players", Players = self.Players } )
|
||||
|
||||
@@ -1804,20 +2055,28 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
|
||||
|
||||
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
|
||||
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
|
||||
|
||||
PlayerMessage =
|
||||
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
|
||||
PlayerName,
|
||||
PlayerScore - PlayerPenalty,
|
||||
PlayerScore,
|
||||
PlayerPenalty
|
||||
)
|
||||
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
|
||||
|
||||
if JustScore~=true then
|
||||
PlayerMessage =
|
||||
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
|
||||
PlayerName,
|
||||
PlayerScore - PlayerPenalty,
|
||||
PlayerScore,
|
||||
PlayerPenalty
|
||||
)
|
||||
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
|
||||
else
|
||||
ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ReportTable
|
||||
end
|
||||
|
||||
--- Opens a score CSV file to log the scores.
|
||||
-- @param #SCORING self
|
||||
-- @param #number sSeconds
|
||||
-- @return #string ClockString
|
||||
function SCORING:SecondsToClock( sSeconds )
|
||||
local nSeconds = sSeconds
|
||||
if nSeconds == 0 then
|
||||
@@ -1923,7 +2182,7 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes,
|
||||
TargetUnitType = TargetUnitType or ""
|
||||
TargetUnitName = TargetUnitName or ""
|
||||
|
||||
if lfs and io and os and self.AutoSave then
|
||||
if lfs and io and os and self.AutoSave == true and self.CSVFile ~= nil then
|
||||
self.CSVFile:write(
|
||||
'"' .. self.GameName .. '"' .. ',' ..
|
||||
'"' .. self.RunTime .. '"' .. ',' ..
|
||||
@@ -1945,6 +2204,23 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes,
|
||||
|
||||
self.CSVFile:write( "\n" )
|
||||
end
|
||||
|
||||
if lfs and io and self.LoadSave == true then
|
||||
local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]]
|
||||
local filename = self.GameName or "PlayerScoresSummary"
|
||||
filename = filename..".csv"
|
||||
local data = self:ReportScoreAllSummary("",true)
|
||||
local text = "-- Playername;;Score;;Penalty\n"
|
||||
for _playername,_data in pairs(data or {}) do
|
||||
-- ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty}
|
||||
local Playername = _playername or "Ghost"
|
||||
local Score = _data.Score or 0
|
||||
local Penalty = _data.Penalty or 0
|
||||
text = text..string.format("%s;;%d;;%d\n",Playername,Score,Penalty)
|
||||
end
|
||||
UTILS.SaveToFile(path,filename,text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Close CSV file
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
--
|
||||
-- ### Authors: **applevangelist**, **FlightControl**
|
||||
--
|
||||
-- Last Update: Dec 2024
|
||||
-- Last Update: January 2026
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -157,7 +157,7 @@ function SEAD:New( SEADGroupPrefixes, Padding )
|
||||
self:AddTransition("*", "ManageEvasion", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
self:I("*** SEAD - Started Version 0.4.9")
|
||||
self:I("*** SEAD - Started Version 0.4.11")
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -318,7 +318,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
|
||||
elseif height <= 12500 then
|
||||
Ropt = Ropt * 0.98
|
||||
end
|
||||
|
||||
local WeaponWrapper = WEAPON:New(SEADWeapon)
|
||||
-- look at a couple of zones across the trajectory
|
||||
for n=1,3 do
|
||||
local dist = Ropt - ((n-1)*20000)
|
||||
@@ -342,7 +342,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20)
|
||||
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20, WeaponWrapper)
|
||||
end
|
||||
--end
|
||||
end
|
||||
@@ -442,7 +442,18 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP
|
||||
local SuppressionStartTime = timer.getTime() + delay
|
||||
local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay
|
||||
local _targetgroupname = _targetgroup:GetName()
|
||||
if not self.SuppressedGroups[_targetgroupname] then
|
||||
local shoradactive = _targetgroup:GetProperty("SHORAD_ACTIVE")
|
||||
if not self.SuppressedGroups[_targetgroupname] and shoradactive ~= true then
|
||||
-- TODO: ask callback if suppression is allowed BEFORE scheduling timers
|
||||
local allow = true
|
||||
if self.UseCallBack and self.CallBack and self.CallBack.SeadAllowSuppression then
|
||||
allow = self.CallBack:SeadAllowSuppression(_targetgroup,_targetgroupname,SEADGroup,SEADWeaponName,Weapon,_tti,delay)
|
||||
end
|
||||
if not allow then
|
||||
self:T(string.format("*** SEAD - %s | Suppression vetoed by callback", _targetgroupname))
|
||||
-- Important: do NOT schedule SuppressionStart/Stop and do NOT flip SuppressedGroups true.
|
||||
return self
|
||||
end
|
||||
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
|
||||
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
|
||||
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: Nov 2021
|
||||
-- Last Update: Jan 2025
|
||||
-- Last Update: Jan 2026
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@@ -48,7 +48,9 @@
|
||||
-- @field #number minscootdist Min distance of the next zone
|
||||
-- @field #number maxscootdist Max distance of the next zone
|
||||
-- @field #boolean scootrandomcoord If true, use a random coordinate in the zone and not the center
|
||||
-- @field #string scootformation Formation to take for scooting, e.g. "Vee" or "Cone"
|
||||
-- @field #string scootformation Formation to take for scooting, e.g. "Vee" or "Cone"
|
||||
-- @field #boolean SmokeDecoy = false,
|
||||
-- @field #number SmokeDecoyColor = SMOKECOLOR.White
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -114,7 +116,9 @@ SHORAD = {
|
||||
SkateZones = nil,
|
||||
minscootdist = 100,
|
||||
maxscootdist = 3000,
|
||||
scootrandomcoord = false,
|
||||
scootrandomcoord = false,
|
||||
SmokeDecoy = false,
|
||||
SmokeDecoyColor = SMOKECOLOR.White
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
@@ -161,8 +165,10 @@ do
|
||||
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
|
||||
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
|
||||
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
|
||||
-- @param #boolean SmokeDecoy Throw smoke decoy when getting activated. Defaults to false.
|
||||
-- @param #number SmokeDecoyColor SMOLECOLOR to use. Defaults to SMOLECOLOR.White
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff, SmokeDecoy, SmokeDecoyColor)
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||
|
||||
@@ -171,6 +177,7 @@ do
|
||||
self.name = Name or "MyShorad"
|
||||
self.Prefixes = ShoradPrefix or "SAM SHORAD"
|
||||
self.Radius = Radius or 20000
|
||||
if type(Coalition) == "number" then Coalition = string.lower(UTILS.GetCoalitionName(Coalition)) end
|
||||
self.Coalition = Coalition or "blue"
|
||||
self.Samset = Samset or GroupSet
|
||||
self.ActiveTimer = ActiveTimer or 600
|
||||
@@ -181,8 +188,15 @@ do
|
||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
|
||||
self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
|
||||
if UseEmOnOff == false then self.UseEmOnOff = UseEmOnOff end
|
||||
self:I("*** SHORAD - Started Version 0.3.4")
|
||||
|
||||
if SmokeDecoy then
|
||||
self.SmokeDecoy = SmokeDecoy
|
||||
self.SmokeDecoyColor = SmokeDecoyColor or SMOKECOLOR.White
|
||||
end
|
||||
|
||||
self:I("*** SHORAD - Started Version 0.3.6")
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("SHORAD %s | ", self.name)
|
||||
self:_InitState()
|
||||
@@ -451,6 +465,43 @@ do
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Set an object to call back when going evasive.
|
||||
-- @param #SHORAD self
|
||||
-- @param #table Object The object to call.
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:AddCallBack(Object)
|
||||
self:T({Class=Object.ClassName})
|
||||
self.CallBack = Object
|
||||
self.UseCallBack = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Smoke a SHORAD Group
|
||||
-- @param #SHORAD self
|
||||
-- @param Wrapper.Group#GROUP Group The Shorad Group to Smoke
|
||||
-- @return self
|
||||
function SHORAD:_SmokeUnits(Group)
|
||||
if self.SmokeDecoy == true then
|
||||
if Group and Group:IsAlive() then
|
||||
local units = Group:GetUnits()
|
||||
for _,_unit in pairs(units) do
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local coordinate = unit:GetCoordinate()
|
||||
if coordinate then
|
||||
coordinate:SwitchSmokeOffsetOn()
|
||||
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,1,20)
|
||||
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,180,20)
|
||||
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,270,20)
|
||||
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,90,20)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Calculate if the missile shot is detected
|
||||
-- @param #SHORAD self
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
@@ -484,7 +535,56 @@ do
|
||||
-- mymantis:Start()
|
||||
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
--self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
|
||||
local TDiff = 4
|
||||
|
||||
-- local function to switch off shorad again
|
||||
local function SleepShorad(group)
|
||||
if group and group:IsAlive() then
|
||||
local groupname = group:GetName()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
if self.UseEmOnOff then
|
||||
group:EnableEmission(false)
|
||||
else
|
||||
group:OptionAlarmStateGreen()
|
||||
end
|
||||
group:SetProperty("SHORAD_ACTIVE",false)
|
||||
local text = string.format("Sleeping SHORAD %s", group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(1,group)
|
||||
else
|
||||
--group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function WakeUp(_group,groupname)
|
||||
-- shot at a group we protect
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
_group:SetProperty("SHORAD_ACTIVE",true)
|
||||
self:_SmokeUnits(_group)
|
||||
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
|
||||
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
|
||||
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
|
||||
self.ActiveGroups[groupname].Timer = TIMER:New(SleepShorad,_group):Start(endtime)
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(TDiff,_group)
|
||||
TDiff=TDiff+1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
local targetgroup = TargetGroup
|
||||
local targetvec2 = nil
|
||||
@@ -501,69 +601,41 @@ do
|
||||
local groupset = self.Groupset --Core.Set#SET_GROUP
|
||||
local shoradset = groupset:GetAliveSet() --#table
|
||||
|
||||
-- local function to switch off shorad again
|
||||
local function SleepShorad(group)
|
||||
if group and group:IsAlive() then
|
||||
local groupname = group:GetName()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
if self.UseEmOnOff then
|
||||
group:EnableEmission(false)
|
||||
else
|
||||
group:OptionAlarmStateGreen()
|
||||
end
|
||||
local text = string.format("Sleeping SHORAD %s", group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(1,group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- go through set and find the one(s) to activate
|
||||
local TDiff = 4
|
||||
|
||||
for _,_group in pairs (shoradset) do
|
||||
|
||||
local groupname = _group:GetName()
|
||||
|
||||
if groupname == TargetGroup and ShotAt==true then
|
||||
-- Shot at a SHORAD group
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(false)
|
||||
local allow = false
|
||||
if self.CallBack and self.UseCallBack == true then
|
||||
allow = self.CallBack:SeadAllowSuppression(_group,groupname)
|
||||
end
|
||||
_group:OptionAlarmStateGreen()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(1,_group)
|
||||
end
|
||||
|
||||
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
|
||||
-- shot at a group we protect
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
|
||||
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
|
||||
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
|
||||
self.ActiveGroups[groupname].Timer = TIMER:New(SleepShorad,_group):Start(endtime)
|
||||
if allow == true then
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(false)
|
||||
end
|
||||
_group:OptionAlarmStateGreen()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
self:_SmokeUnits(_group)
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(TDiff,_group)
|
||||
TDiff=TDiff+1
|
||||
self:__ShootAndScoot(1,_group)
|
||||
else
|
||||
_group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true)
|
||||
end
|
||||
else
|
||||
WakeUp(_group,groupname)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
|
||||
WakeUp(_group,groupname)
|
||||
end -- end if
|
||||
end -- end in pairs
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -679,11 +751,12 @@ do
|
||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
--self:T( { EventData.id } )
|
||||
self:T(self.lid .. " HandleEventShot")
|
||||
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local ShootingWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- get firing coalition
|
||||
if not EventData.IniGroup then return self end
|
||||
local weaponcoalition = EventData.IniGroup:GetCoalition()
|
||||
-- get detection probability
|
||||
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
|
||||
@@ -703,7 +776,7 @@ do
|
||||
-- Is there target data?
|
||||
if not targetdata or self.debug then
|
||||
if string.find(ShootingWeaponName,"AGM_88",1,true) then
|
||||
self:I("**** Tracking AGM-88 with no target data.")
|
||||
self:T("**** Tracking AGM-88 with no target data.")
|
||||
local pos0 = EventData.IniUnit:GetCoordinate()
|
||||
local fheight = EventData.IniUnit:GetHeight()
|
||||
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
---- #-- Author : **applevangelist ** (Optimized by AI)
|
||||
|
||||
---
|
||||
-- - @module Functional.Tiresias
|
||||
-- - @image Functional.Tiresias.jpg
|
||||
-- @module Functional.Tiresias
|
||||
-- @image Functional.Tiresias.jpg
|
||||
|
||||
--- Last Update: Dec 2023 (Optimized July 2025)
|
||||
--- Last Update: Oct 2025
|
||||
|
||||
--- **TIRESIAS** class, extends Core.Base#BASE
|
||||
-- @type TIRESIAS
|
||||
@@ -55,6 +55,7 @@
|
||||
-- @field #boolean SwitchAAA
|
||||
-- @field #string lid
|
||||
-- @field #table _cached_zones
|
||||
-- @field #table _cached_groupsets
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
---
|
||||
@@ -103,8 +104,8 @@
|
||||
-- @field #TIRESIAS
|
||||
TIRESIAS = {
|
||||
ClassName = "TIRESIAS",
|
||||
debug = true,
|
||||
version = " 0.0.6-OPT" ,
|
||||
debug = false,
|
||||
version = " 0.0.8" ,
|
||||
Interval = 20,
|
||||
GroundSet = nil,
|
||||
VehicleSet = nil,
|
||||
@@ -116,6 +117,7 @@ TIRESIAS = {
|
||||
PlaneSwitchRange = 25, -- NM
|
||||
SwitchAAA = true,
|
||||
_cached_zones = {}, -- Cache for zone objects
|
||||
_cached_groupsets = {}, -- Cache for group_set objects
|
||||
}
|
||||
|
||||
---
|
||||
@@ -138,7 +140,7 @@ function TIRESIAS:New()
|
||||
self:AddTransition("*", "Status", "*") -- TIRESIAS status update.
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
self.ExceptionSet = nil --SET_GROUP:New():Clear(false)
|
||||
self.ExceptionSet = SET_GROUP:New() --:Clear(false)
|
||||
self._cached_zones = {} -- Initialize zone cache
|
||||
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
|
||||
@@ -222,10 +224,10 @@ function TIRESIAS:AddExceptionSet(Set)
|
||||
|
||||
Set:ForEachGroupAlive(
|
||||
function(grp)
|
||||
local inAAASet = self.AAASet:IsIncludeObject(grp)
|
||||
local inVehSet = self.VehicleSet:IsIncludeObject(grp)
|
||||
local inSAMSet = self.SAMSet:IsIncludeObject(grp)
|
||||
if grp:IsGround() and (not grp.Tiresias) and (not inAAASet) and (not inVehSet) and (not inSAMSet) then
|
||||
--local inAAASet = self.AAASet:IsIncludeObject(grp)
|
||||
--local inVehSet = self.VehicleSet:IsIncludeObject(grp)
|
||||
--local inSAMSet = self.SAMSet:IsIncludeObject(grp)
|
||||
if grp:IsGround() and (not grp.Tiresias) then --and (not inAAASet) and (not inVehSet) and (not inSAMSet) then
|
||||
grp.Tiresias = exception_data
|
||||
exceptions:AddGroup(grp, true)
|
||||
BASE:T(" TIRESIAS: Added exception group: " .. grp:GetName())
|
||||
@@ -417,7 +419,8 @@ function TIRESIAS:_SwitchOnGroups(group, radius)
|
||||
-- Use cached zones to reduce object creation
|
||||
local group_name = group:GetName()
|
||||
local cache_key = group_name .. " _" .. radius
|
||||
local zone = self._cached_zones[cache_key]
|
||||
local zone = self._cached_zones[cache_key] -- Core.Zone#ZONE_RADIUS
|
||||
--local ground = self._cached_groupsets[cache_key] -- Core.Set#SET_GROUP
|
||||
|
||||
if not zone then
|
||||
zone = ZONE_GROUP:New(" Zone-" .. group_name, group, UTILS.NMToMeters(radius))
|
||||
@@ -427,7 +430,15 @@ function TIRESIAS:_SwitchOnGroups(group, radius)
|
||||
zone:UpdateFromGroup(group)
|
||||
end
|
||||
|
||||
local ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce()
|
||||
--if not ground then
|
||||
--ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce()
|
||||
--self._cached_groupsets[cache_key] = ground
|
||||
--else
|
||||
--ground:FilterZones({zone},true):FilterOnce()
|
||||
zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
|
||||
local ground = zone:GetScannedSetGroup()
|
||||
--end
|
||||
|
||||
local count = ground:CountAlive()
|
||||
|
||||
if self.debug then
|
||||
|
||||
@@ -1132,7 +1132,7 @@
|
||||
-- specify an "Assignment". This can be later used to identify the request and take the right actions.
|
||||
--
|
||||
-- Once the request is processed, the @{#WAREHOUSE.OnAfterSelfRequest} function is called. This is where we hook in and postprocess the spawned assets.
|
||||
-- In particular, we use the @{AI.AI_Formation#AI_FORMATION} class to make some nice escorts for our carrier.
|
||||
-- In particular, we use the @{Functional.Formation#FORMATION} class to make some nice escorts for our carrier.
|
||||
--
|
||||
-- When the resue helo is spawned, we can check that this is the correct asset and make the helo go into formation with the carrier.
|
||||
-- Once the helo runs out of fuel, it will automatically return to the ship and land. For the warehouse, this means that the "cargo", i.e. the helicopter
|
||||
@@ -1175,7 +1175,7 @@
|
||||
--
|
||||
-- -- Define AI Formation object.
|
||||
-- -- Note that this has to be a global variable or the garbage collector will remove it for some reason!
|
||||
-- CarrierFormationLeft = AI_FORMATION:New(Mother, groupset, "Left Formation with Carrier", "Escort Carrier.")
|
||||
-- CarrierFormationLeft = FORMATION:New(Mother, groupset, "Left Formation with Carrier")
|
||||
--
|
||||
-- -- Formation parameters.
|
||||
-- CarrierFormationLeft:FormationLeftWing(200 ,50, 0, 0, 500, 50)
|
||||
@@ -1190,7 +1190,7 @@
|
||||
--
|
||||
-- -- Define AI Formation object.
|
||||
-- -- Note that this has to be a global variable or the garbage collector will remove it for some reason!
|
||||
-- CarrierFormationRight = AI_FORMATION:New(Mother, groupset, "Right Formation with Carrier", "Escort Carrier.")
|
||||
-- CarrierFormationRight = FORMATION:New(Mother, groupset, "Right Formation with Carrier")
|
||||
--
|
||||
-- -- Formation parameters.
|
||||
-- CarrierFormationRight:FormationRightWing(200 ,50, 0, 0, 500, 50)
|
||||
@@ -1208,7 +1208,7 @@
|
||||
-- group:StartUncontrolled()
|
||||
--
|
||||
-- -- Define AI Formation object.
|
||||
-- CarrierFormationHelo = AI_FORMATION:New(Mother, groupset, "Helo Formation with Carrier", "Fly Formation.")
|
||||
-- CarrierFormationHelo = FORMATION:New(Mother, groupset, "Helo Formation with Carrier")
|
||||
--
|
||||
-- -- Formation parameters.
|
||||
-- CarrierFormationHelo:FormationCenterWing(-150, 50, 20, 50, 100, 50)
|
||||
@@ -3340,7 +3340,6 @@ function WAREHOUSE:FindAssetInDB(group)
|
||||
if aid~=nil then
|
||||
|
||||
local asset=_WAREHOUSEDB.Assets[aid]
|
||||
self:T2({asset=asset})
|
||||
if asset==nil then
|
||||
self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!", group:GetName()), 0)
|
||||
end
|
||||
@@ -3918,7 +3917,7 @@ end
|
||||
-- @param #string assignment A free to choose string specifying an assignment for the asset. This can be used with the @{#WAREHOUSE.OnAfterNewAsset} function.
|
||||
-- @param #table other (Optional) Table of other useful data. Can be collected via WAREHOUSE.OnAfterNewAsset() function for example
|
||||
function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribute, forcecargobay, forceweight, loadradius, skill, liveries, assignment, other)
|
||||
self:T({group=group, ngroups=ngroups, forceattribute=forceattribute, forcecargobay=forcecargobay, forceweight=forceweight})
|
||||
--self:T({group=group:GetName(), ngroups=ngroups, forceattribute=forceattribute, forcecargobay=forcecargobay, forceweight=forceweight})
|
||||
|
||||
-- Set default.
|
||||
local n=ngroups or 1
|
||||
@@ -4113,10 +4112,10 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
|
||||
|
||||
-- Get name of template group.
|
||||
local templategroupname=group:GetName()
|
||||
|
||||
local Descriptors=group:GetUnit(1):GetDesc()
|
||||
local unit = group:GetUnit(1)
|
||||
local Descriptors= (unit and unit:IsAlive()) and unit:GetDesc() or {}
|
||||
local Category=group:GetCategory()
|
||||
local TypeName=group:GetTypeName()
|
||||
local TypeName=group:GetTypeName() or "none"
|
||||
local SpeedMax=group:GetSpeedMax()
|
||||
local RangeMin=group:GetRange()
|
||||
local smax,sx,sy,sz=_GetObjectSize(Descriptors)
|
||||
@@ -4247,6 +4246,16 @@ function WAREHOUSE:_AssetItemInfo(asset)
|
||||
self:T3({Template=asset.template})
|
||||
end
|
||||
|
||||
--- This function uses Disposition and other fallback logic to find better ground positions for ground units.
|
||||
--- NOTE: This is not a spawn randomizer.
|
||||
--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table.
|
||||
--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit.
|
||||
--- Uses UTILS.ValidateAndRepositionGroundUnits.
|
||||
-- @param #boolean Enabled Enable/disable the feature.
|
||||
function WAREHOUSE:SetValidateAndRepositionGroundUnits(Enabled)
|
||||
self.ValidateAndRepositionGroundUnits = Enabled
|
||||
end
|
||||
|
||||
--- On after "NewAsset" event. A new asset has been added to the warehouse stock.
|
||||
-- @param #WAREHOUSE self
|
||||
-- @param #string From From state.
|
||||
@@ -4436,7 +4445,6 @@ end
|
||||
-- @param #WAREHOUSE.Queueitem Request Information table of the request.
|
||||
-- @return #boolean If true, request is granted.
|
||||
function WAREHOUSE:onbeforeRequest(From, Event, To, Request)
|
||||
self:T3({warehouse=self.alias, request=Request})
|
||||
|
||||
-- Distance from warehouse to requesting warehouse.
|
||||
local distance=self:GetCoordinate():Get2DDistance(Request.warehouse:GetCoordinate())
|
||||
@@ -5965,6 +5973,10 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, late
|
||||
template.y = coord.z
|
||||
template.alt = coord.y
|
||||
|
||||
if self.ValidateAndRepositionGroundUnits then
|
||||
UTILS.ValidateAndRepositionGroundUnits(template.units)
|
||||
end
|
||||
|
||||
-- Spawn group.
|
||||
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
|
||||
|
||||
@@ -6120,6 +6132,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
|
||||
unit.onboard_num=asset.modex[i]
|
||||
end
|
||||
if asset.callsign then
|
||||
--UTILS.PrintTableToLog(asset.callsign)
|
||||
unit.callsign=asset.callsign[i]
|
||||
end
|
||||
|
||||
@@ -6139,9 +6152,6 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
|
||||
-- Uncontrolled spawning.
|
||||
template.uncontrolled=uncontrolled
|
||||
|
||||
-- Debug info.
|
||||
self:T2({airtemplate=template})
|
||||
|
||||
-- Spawn group.
|
||||
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
|
||||
|
||||
@@ -8587,6 +8597,8 @@ function WAREHOUSE:_DeleteStockItem(stockitem)
|
||||
local item=self.stock[i] --#WAREHOUSE.Assetitem
|
||||
if item.uid==stockitem.uid then
|
||||
table.remove(self.stock,i)
|
||||
-- remove also from warehouse DB (not good! causes an error if the asset needs to be added to a requesting wherehouse)
|
||||
--_WAREHOUSEDB.Assets[stockitem.uid]=nil
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,6 +20,13 @@ _DATABASE:_RegisterCargos()
|
||||
_DATABASE:_RegisterZones()
|
||||
_DATABASE:_RegisterAirbases()
|
||||
|
||||
--- Function that writes to DCS log file
|
||||
-- @param #string text Formatted text.
|
||||
-- @param ... Format passed to string.format().
|
||||
function printf(text, ...)
|
||||
env.info(string.format(text, ...))
|
||||
end
|
||||
|
||||
--- Check if os etc is available.
|
||||
BASE:I("Checking de-sanitization of os, io and lfs:")
|
||||
local __na = false
|
||||
|
||||
@@ -32,6 +32,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/MarkerOps_Base.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/TextAndSound.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/Pathline.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/ClientMenu.lua')
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/Vector.lua')
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Object.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Identifiable.lua' )
|
||||
@@ -84,6 +85,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ZoneGoalCargo.lua'
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Tiresias.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Stratego.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ClientWatch.lua')
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Formation.lua')
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' )
|
||||
@@ -116,6 +118,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/PlayerRecce.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyA2G.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' )
|
||||
@@ -186,4 +189,9 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Dispatcher
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Zone.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Dispatcher.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Point.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Beacons.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Radios.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Towns.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Globals.lua' )
|
||||
|
||||
@@ -32,6 +32,7 @@ __Moose.Include( 'Core\\MarkerOps_Base.lua' )
|
||||
__Moose.Include( 'Core\\TextAndSound.lua' )
|
||||
__Moose.Include( 'Core\\Condition.lua' )
|
||||
__Moose.Include( 'Core\\ClientMenu.lua' )
|
||||
__Moose.Include( 'Core\\Vector.lua' )
|
||||
|
||||
__Moose.Include( 'Wrapper\\Object.lua' )
|
||||
__Moose.Include( 'Wrapper\\Identifiable.lua' )
|
||||
@@ -177,4 +178,9 @@ __Moose.Include( 'Tasking\\Task_Cargo_Dispatcher.lua' )
|
||||
__Moose.Include( 'Tasking\\Task_Capture_Zone.lua' )
|
||||
__Moose.Include( 'Tasking\\Task_Capture_Dispatcher.lua' )
|
||||
|
||||
__Moose.Include( 'Navigation\\Point.lua' )
|
||||
__Moose.Include( 'Navigation\\Beacons.lua' )
|
||||
__Moose.Include( 'Navigation\\Radios.lua' )
|
||||
__Moose.Include( 'Navigation\\Towns.lua' )
|
||||
|
||||
__Moose.Include( 'Globals.lua' )
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
--- **NAVIGATION** - Beacons of the map/theatre.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Access beacons of the map
|
||||
-- * Find closest beacon
|
||||
-- * Get frequencies and channels
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Beacons).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Navigation.Beacons
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- BEACONS class.
|
||||
-- @type BEACONS
|
||||
--
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field #table beacons Beacons.
|
||||
--
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *Hope is the beacon that guides lost ships back to the shore.*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The BEACONS Concept
|
||||
--
|
||||
-- This class is designed to make information about beacons of a map/theatre easier accessible. The information contains location, type and frequencies of all or specific beacons of the map.
|
||||
--
|
||||
-- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `beacons.lua` that can be
|
||||
-- found in the installation directory of DCS for each map or a table that the user needs to provide.
|
||||
-- **Note** your `MissionScripting` environment needs to be desanitized to read this data. `Package` als needs to be available.
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `BEACONS` object can be created with the @{#BEACONS.NewFromFile}(*beacons_lua_file*) function.
|
||||
--
|
||||
-- local beacons=BEACONS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\beacons.lua")
|
||||
-- beacons:MarkerShow()
|
||||
--
|
||||
-- This will load the beacons from the `<DCS_Install_Directory>` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file
|
||||
-- you provided is correct and all relevant beacons are present.
|
||||
--
|
||||
-- # User Functions
|
||||
--
|
||||
-- ## F10 Map Markers
|
||||
--
|
||||
-- ## Position
|
||||
--
|
||||
-- ## Get Closest Beacon
|
||||
--
|
||||
--
|
||||
-- @field #BEACONS
|
||||
BEACONS = {
|
||||
ClassName = "BEACONS",
|
||||
verbose = 1,
|
||||
beacons = {},
|
||||
}
|
||||
|
||||
--- Mission capability.
|
||||
-- @type BEACONS.Beacon
|
||||
-- @field #function display_name Function that returns the localized name.
|
||||
-- @field #number type Beacon type.
|
||||
-- @field #string beaconId Beacon ID.
|
||||
-- @field #string callsign Call sign.
|
||||
-- @field #number frequency Frequency in Hz.
|
||||
-- @field #number channel TACAN, RSBN or PRMG channel depending on type.
|
||||
-- @field #table position Position table.
|
||||
-- @field #number direction Direction in degrees.
|
||||
-- @field #table positionGeo Table with latitude and longitude.
|
||||
-- @field #table sceneObjects Table with scenery objects, e.g. `{t:393396742}`.
|
||||
-- @field #number chartOffsetX No idea what this offset is?!
|
||||
-- @field DCS#Vec3 vec3 Position vector 3D.
|
||||
-- @field #number markerID ID for the F10 marker.
|
||||
-- @field #string typeName Name of becon type.
|
||||
-- @field Wrapper.Scenery#SCENERY scenery The scenery object.
|
||||
|
||||
--- BEACONS class version.
|
||||
-- @field #string version
|
||||
BEACONS.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
-- DONE: TACAN channel from frequency (was already in beacon.lua as channel)
|
||||
-- DONE: Scenery object
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor(s)
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new BEACONS class instance from a given table.
|
||||
-- @param #BEACONS self
|
||||
-- @param #table BeaconTable Table with beacon info.
|
||||
-- @return #BEACONS self
|
||||
function BEACONS:NewFromTable(BeaconTable)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #BEACONS
|
||||
|
||||
for _,_beacon in pairs(BeaconTable) do
|
||||
local beacon=_beacon --#BEACONS.Beacon
|
||||
|
||||
-- Get 3D vector
|
||||
beacon.vec3={x=beacon.position[1], y=beacon.position[2], z=beacon.position[3]}
|
||||
|
||||
-- Get coordinate
|
||||
beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3)
|
||||
|
||||
-- Get type name
|
||||
beacon.typeName=self:_GetTypeName(beacon.type)
|
||||
|
||||
-- Find closest scenery object from scan
|
||||
beacon.scenery=beacon.coordinate:FindClosestScenery(20)
|
||||
|
||||
-- Debug stuff for scenery object
|
||||
if false then
|
||||
if beacon.scenery then
|
||||
env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s", beacon.callsign, beacon.beaconId, beacon.typeName, beacon.scenery:GetName(), beacon.scenery:GetTypeName() ))
|
||||
UTILS.PrintTableToLog(beacon.scenery.SceneryObject)
|
||||
UTILS.PrintTableToLog(beacon.sceneObjects)
|
||||
else
|
||||
env.info(string.format("FF NO scenery object %s %s %s ", beacon.callsign, beacon.beaconId, beacon.typeName))
|
||||
end
|
||||
end
|
||||
|
||||
-- Add to table
|
||||
table.insert(self.beacons, beacon)
|
||||
end
|
||||
|
||||
-- Debug output
|
||||
self:I(string.format("Added %d beacons", #self.beacons))
|
||||
|
||||
if self.verbose > 0 then
|
||||
local text="Beacon types:"
|
||||
for typeName,typeID in pairs(BEACON.Type) do
|
||||
local n=self:CountBeacons(typeID)
|
||||
text=text..string.format("\n%s = %d", typeName, n)
|
||||
end
|
||||
self:I(text)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new BEACONS class instance from a given file.
|
||||
-- @param #BEACONS self
|
||||
-- @param #string FileName Full path to the file containing the map beacons.
|
||||
-- @return #BEACONS self
|
||||
function BEACONS:NewFromFile(FileName)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #BEACONS
|
||||
|
||||
local exists=UTILS.FileExists(FileName)
|
||||
|
||||
if exists==false then
|
||||
self:E(string.format("ERROR: file with beacon info does not exist!"))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- This will create a global table `beacons`
|
||||
dofile(FileName)
|
||||
|
||||
-- Get beacons from table.
|
||||
self=self:NewFromTable(beacons)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get 3D position vector of a specific beacon.
|
||||
-- @param #BEACONS self
|
||||
-- @param #BEACONS.Beacon beacon The beacon data structure.
|
||||
-- @return DCS#Vec3 Position vector.
|
||||
function BEACONS:GetVec3(beacon)
|
||||
return beacon.vec3
|
||||
end
|
||||
|
||||
--- Get COORDINATE of a specific beacon.
|
||||
-- @param #BEACONS self
|
||||
-- @param #BEACONS.Beacon beacon The beacon data structure.
|
||||
-- @return Core.Point#COORDINATE The coordinate.
|
||||
function BEACONS:GetCoordinate(beacon)
|
||||
local coordinate=COORDINATE:NewFromVec3(beacon.vec3)
|
||||
return coordinate
|
||||
end
|
||||
|
||||
--- Find closest beacon to a given coordinate.
|
||||
-- @param #BEACONS self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
|
||||
-- @param #number TypeID (Optional) Only search for specific beacon types, *e.g.* `BEACON.Type.TACAN`.
|
||||
-- @param #number DistMax (Optional) Max search distance in meters.
|
||||
-- @param #table ExcludeList (Optional) List of beacons to exclude.
|
||||
-- @return #BEACONS.Beacon The closest beacon.
|
||||
function BEACONS:GetClosestBeacon(Coordinate, TypeID, DistMax, ExcludeList)
|
||||
|
||||
local beacon=nil --#BEACONS.Beacon
|
||||
local distmin=math.huge
|
||||
|
||||
ExcludeList=ExcludeList or {}
|
||||
|
||||
for _,_beacon in pairs(self.beacons) do
|
||||
local bc=_beacon --#BEACONS.Beacon
|
||||
|
||||
if (TypeID==nil or TypeID==bc.type) and (not UTILS.IsInTable(ExcludeList, bc, "beaconId")) then
|
||||
|
||||
local dist=Coordinate:Get2DDistance(bc.vec3)
|
||||
|
||||
if dist<distmin and (DistMax==nil or dist<=DistMax) then
|
||||
distmin=dist
|
||||
beacon=bc
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return beacon
|
||||
end
|
||||
|
||||
--- Find closest beacons to a given coordinate.
|
||||
-- @param #BEACONS self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
|
||||
-- @param #number Nmax Max number of beacons. Default 5.
|
||||
-- @param #number TypeID (Optional) Only search for specific beacon types, *e.g.* `BEACON.Type.TACAN`.
|
||||
-- @param #number DistMax (Optional) Max search distance in meters.
|
||||
-- @return #table Table of #BEACONS.Beacon closest beacons.
|
||||
function BEACONS:GetClosestBeacons(Coordinate, Nmax, TypeID, DistMax)
|
||||
|
||||
Nmax=Nmax or 5
|
||||
|
||||
local closest={}
|
||||
for i=1,Nmax do
|
||||
|
||||
local beacon=self:GetClosestBeacon(Coordinate, TypeID, DistMax, closest)
|
||||
|
||||
if beacon then
|
||||
table.insert(closest, beacon)
|
||||
else
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return closest
|
||||
end
|
||||
|
||||
--- Get table of all beacons, optionally of a given type.
|
||||
-- @param #BEACONS self
|
||||
-- @param #number TypeID (Optional) Only return specific beacon types, *e.g.* `BEACON.Type.TACAN`. Can be handed in as tanle of beacon types.
|
||||
-- @return #table Table of beacons. Each element is of type #BEACON.Beacon.
|
||||
function BEACONS:GetBeacons(TypeID)
|
||||
|
||||
local beacons={}
|
||||
local keys = {}
|
||||
|
||||
if TypeID~=nil and type(TypeID) ~= "table" then
|
||||
TypeID = {TypeID}
|
||||
end
|
||||
|
||||
for _,_typeid in pairs(TypeID or {}) do
|
||||
if _typeid ~= nil then
|
||||
keys[_typeid] = _typeid
|
||||
end
|
||||
end
|
||||
|
||||
for _,_beacon in pairs(self.beacons) do
|
||||
local bc=_beacon --#BEACONS.Beacon
|
||||
|
||||
if TypeID==nil or keys[bc.type] ~= nil then
|
||||
table.insert(beacons, bc)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return beacons
|
||||
end
|
||||
|
||||
--- Count beacons, optionally of a given type.
|
||||
-- @param #BEACONS self
|
||||
-- @param #number TypeID (Optional) Only count specific beacon types, *e.g.* `BEACON.Type.TACAN`.
|
||||
-- @return #number Number of beacons.
|
||||
function BEACONS:CountBeacons(TypeID)
|
||||
|
||||
local n=0
|
||||
|
||||
if TypeID then
|
||||
for _,_beacon in pairs(self.beacons) do
|
||||
local bc=_beacon --#BEACONS.Beacon
|
||||
|
||||
if TypeID==bc.type then
|
||||
n=n+1
|
||||
end
|
||||
end
|
||||
else
|
||||
n=#self.beacons
|
||||
end
|
||||
|
||||
return n
|
||||
end
|
||||
|
||||
--- Add markers for all beacons on the F10 map. Optionally, only a specific beacon or a certain beacon type can be marked.
|
||||
-- @param #BEACONS self
|
||||
-- @param #BEACONS.Beacon Beacon (Optional) Only this specifc beacon.
|
||||
-- @param #number TypeID (Optional) Only show specific beacon types, *e.g.* `BEACON.Type.TACAN`.
|
||||
-- @return #BEACONS self
|
||||
function BEACONS:MarkerShow(Beacon, TypeID)
|
||||
|
||||
for _,_beacon in pairs(self.beacons) do
|
||||
local beacon=_beacon --#BEACONS.Beacon
|
||||
if Beacon==nil or Beacon.beaconId==beacon.beaconId then
|
||||
if TypeID==nil or beacon.type==TypeID then
|
||||
local text=self:_GetMarkerText(beacon)
|
||||
local coord=COORDINATE:NewFromVec3(beacon.vec3)
|
||||
if beacon.markerID then
|
||||
UTILS.RemoveMark(beacon.markerID)
|
||||
end
|
||||
beacon.markerID=coord:MarkToAll(text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove markers of all beacons from the F10 map. Optionally, remove only marker of a specific beacon or a certain beacon type.
|
||||
-- @param #BEACONS self
|
||||
-- @param #BEACONS.Beacon Beacon (Optional) Only this specifc beacon.
|
||||
-- @param #number TypeID (Optional) Only show specific beacon types, *e.g.* `BEACON.Type.TACAN`.
|
||||
-- @return #BEACONS self
|
||||
function BEACONS:MarkerRemove(Beacon, TypeID)
|
||||
|
||||
for _,_beacon in pairs(self.beacons) do
|
||||
local beacon=_beacon --#BEACONS.Beacon
|
||||
if Beacon==nil or Beacon.beaconId==beacon.beaconId then
|
||||
if TypeID==nil or beacon.type==TypeID then
|
||||
if beacon.markerID then
|
||||
UTILS.RemoveMark(beacon.markerID)
|
||||
beacon.markerID=nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get text displayed in the F10 marker.
|
||||
-- @param #BEACONS self
|
||||
-- @param #BEACONS.Beacon beacon The beacon data structure.
|
||||
-- @return #string Marker text.
|
||||
function BEACONS:_GetMarkerText(beacon)
|
||||
|
||||
local frequency, funit=self:_GetFrequency(beacon.frequency)
|
||||
local direction=beacon.direction~=nil and beacon.direction or -1
|
||||
|
||||
local text=string.format("Beacon %s [ID=%s]", tostring(beacon.typeName), tostring(beacon.beaconId))
|
||||
text=text..string.format("\nCallsign: %s", tostring(beacon.callsign))
|
||||
if UTILS.IsInTable({BEACON.Type.TACAN, BEACON.Type.RSBN, BEACON.Type.PRMG_GLIDESLOPE, BEACON.Type.PRMG_LOCALIZER}, beacon.type) then
|
||||
text=text..string.format("\nChannel: %s", tostring(beacon.channel))
|
||||
end
|
||||
text=text..string.format("\nFrequency: %.3f %s", frequency, funit)
|
||||
text=text..string.format("\nDirection: %.1f°", direction)
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
|
||||
--- Get converted frequency.
|
||||
-- @param #BEACONS self
|
||||
-- @param #number freq Frequency in Hz.
|
||||
-- @return #number Frequency in better unit.
|
||||
-- @return #string Unit ("Hz", "kHz", "MHz").
|
||||
function BEACONS:_GetFrequency(freq)
|
||||
|
||||
freq=freq or 0
|
||||
local unit="Hz"
|
||||
|
||||
if freq>=1e6 then
|
||||
freq=freq/1e6
|
||||
unit="MHz"
|
||||
elseif freq>=1e3 then
|
||||
freq=freq/1e3
|
||||
unit="kHz"
|
||||
end
|
||||
|
||||
return freq, unit
|
||||
end
|
||||
|
||||
--- Get name of beacon type.
|
||||
-- @param #BEACONS self
|
||||
-- @param #number typeID Beacon type number.
|
||||
-- @return #string Type name.
|
||||
function BEACONS:_GetTypeName(typeID)
|
||||
|
||||
if typeID~=nil then
|
||||
for typeName,_typeID in pairs(BEACON.Type) do
|
||||
if _typeID==typeID then
|
||||
return typeName
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return "Unknown"
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,602 @@
|
||||
--- **NAVIGATION** - Navigation Airspace Points, Fixes and Aids.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Navigation Fixes
|
||||
-- * Navigation Aids
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20NavFix).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Navigation.Point
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- NAVFIX class.
|
||||
-- @type NAVFIX
|
||||
--
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field #string name Name of the point.
|
||||
-- @field #string typePoint Type of the point, *e.g. "Intersection", "VOR", "Airport".
|
||||
-- @field Core.Vector#VECTOR vector Position vector of the fix.
|
||||
-- @field Wrapper.Marker#MARKER marker Marker on F10 map.
|
||||
-- @field #number altMin Minimum altitude in meters.
|
||||
-- @field #number altMax Maximum altitude in meters.
|
||||
-- @field #number speedMin Minimum speed in knots.
|
||||
-- @field #number speedMax Maximum speed in knots.
|
||||
--
|
||||
-- @field #boolean isCompulsory Is this a compulsory fix.
|
||||
-- @field #boolean isFlyover Is this a flyover fix (`true`) or turning point otherwise.
|
||||
-- @field #boolean isFAF Is this a final approach fix.
|
||||
-- @field #boolean isIAF Is this an initial approach fix.
|
||||
-- @field #boolean isIF Is this an initial fix.
|
||||
-- @field #boolean isMAF Is this an initial fix.
|
||||
--
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The NAVFIX Concept
|
||||
--
|
||||
-- The NAVFIX class has a great concept!
|
||||
--
|
||||
-- A NAVFIX describes a geo position and can, *e.g.*, be part of a FLIGHTPLAN. It has a unique name and is of a certain type, *e.g.* "Intersection", "VOR", "Airbase" etc.
|
||||
-- It can also have further properties as min/max altitudes and speeds that aircraft need to obey when they pass the point.
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `NAVFIX` object can be created with the @{#NAVFIX.New}() function.
|
||||
--
|
||||
-- myNavPoint=NAVFIX:New()
|
||||
-- myTemplate:SetXYZ(X, Y, Z)
|
||||
--
|
||||
-- This is how it works.
|
||||
--
|
||||
-- @field #NAVFIX
|
||||
NAVFIX = {
|
||||
ClassName = "NAVFIX",
|
||||
verbose = 0,
|
||||
}
|
||||
|
||||
--- Type of point.
|
||||
-- @type NAVFIX.Type
|
||||
-- @field #string POINT Waypoint.
|
||||
-- @field #string INTERSECTION Intersection of airway.
|
||||
-- @field #string AIRPORT Airport.
|
||||
-- @field #string VOR Very High Frequency Omnidirectional Range Station.
|
||||
-- @field #string DME Distance Measuring Equipment.
|
||||
-- @field #string NDB Non-Directional Beacon.
|
||||
-- @field #string VORDME Combined VHF omnidirectional range (VOR) with a distance-measuring equipment (DME).
|
||||
-- @field #string LOC Localizer.
|
||||
-- @field #string ILS Instrument Landing System.
|
||||
-- @field #string TACAN TACtical Air Navigation System (TACAN).
|
||||
NAVFIX.Type={
|
||||
POINT="Point",
|
||||
INTERSECTION="Intersection",
|
||||
AIRPORT="Airport",
|
||||
NDB="NDB",
|
||||
VOR="VOR",
|
||||
DME="DME",
|
||||
VORDME="VOR/DME",
|
||||
LOC="Localizer",
|
||||
ILS="ILS",
|
||||
TACAN="TACAN"
|
||||
}
|
||||
|
||||
--- NAVFIX class version.
|
||||
-- @field #string version
|
||||
NAVFIX.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor(s)
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new NAVFIX class instance from a given VECTOR.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #string Name Name/ident of the point. Should be unique!
|
||||
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
|
||||
-- @param Core.Vector#VECTOR Vector Position vector of the navpoint.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:NewFromVector(Name, Type, Vector)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #NAVFIX
|
||||
|
||||
-- Vector of point.
|
||||
self.vector=Vector
|
||||
|
||||
-- Name of point.
|
||||
self.name=Name
|
||||
|
||||
-- Type of the point.
|
||||
self.typePoint=Type or NAVFIX.Type.POINT
|
||||
|
||||
local coord=COORDINATE:NewFromVec3(self.vector)
|
||||
|
||||
-- Marker on F10.
|
||||
self.marker=MARKER:New(coord, self:_GetMarkerText())
|
||||
|
||||
-- Log ID string.
|
||||
self.lid=string.format("NAVFIX %s [%s] | ", tostring(self.name), tostring(self.typePoint))
|
||||
|
||||
-- Debug info.
|
||||
self:I(self.lid..string.format("Created NAVFIX"))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new NAVFIX class instance from a given COORDINATE.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #string Name Name of the fix. Should be unique!
|
||||
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the point.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:NewFromCoordinate(Name, Type, Coordinate)
|
||||
|
||||
-- Create a VECTOR from the coordinate.
|
||||
local Vector=VECTOR:NewFromVec(Coordinate)
|
||||
|
||||
-- Create NAVFIX.
|
||||
self=NAVFIX:NewFromVector(Name, Type, Vector)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new NAVFIX instance from given latitude and longitude in degrees, minutes and seconds (DMS).
|
||||
-- @param #NAVFIX self
|
||||
-- @param #string Name Name of the fix. Should be unique!
|
||||
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
|
||||
-- @param #string Latitude Latitude in DMS as string.
|
||||
-- @param #string Longitude Longitude in DMS as string.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:NewFromLLDMS(Name, Type, Latitude, Longitude)
|
||||
|
||||
-- Create a VECTOR from the coordinate.
|
||||
local Vector=VECTOR:NewFromLLDMS(Latitude, Longitude)
|
||||
|
||||
-- Create NAVFIX.
|
||||
self=NAVFIX:NewFromVector(Name, Type, Vector)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new NAVFIX instance from given latitude and longitude in decimal degrees (DD).
|
||||
-- @param #NAVFIX self
|
||||
-- @param #string Name Name of the fix. Should be unique!
|
||||
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
|
||||
-- @param #number Latitude Latitude in DD.
|
||||
-- @param #number Longitude Longitude in DD.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:NewFromLLDD(Name, Type, Latitude, Longitude)
|
||||
|
||||
-- Create a VECTOR from the coordinate.
|
||||
local Vector=VECTOR:NewFromLLDD(Latitude, Longitude)
|
||||
|
||||
-- Create NAVFIX.
|
||||
self=NAVFIX:NewFromVector(Name, Type, Vector)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new NAVFIX class instance relative to a given other NAVFIX.
|
||||
-- You have to specify the distance and bearing from the new point to the given point. *E.g.*, for a distance of 5 NM and a bearing of 090° (West), the
|
||||
-- new nav point is created 5 NM East of the given nav point. The reason is that this corresponts to convention used in most maps.
|
||||
-- You can, however, use the `Reciprocal` switch to create the new point in the direction you specify.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #string Name Name of the fix. Should be unique!
|
||||
-- @param #string Type Type of navfix.
|
||||
-- @param #NAVFIX NavFix The given/existing navigation fix relative to which the new fix is created.
|
||||
-- @param #number Distance Distance from the given to the new point in nautical miles.
|
||||
-- @param #number Bearing Bearing [Deg] from the new point to the given one.
|
||||
-- @param #boolean Reciprocal If `true` the reciprocal `Bearing` is taken so it specifies the direction from the given point to the new one.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:NewFromNavFix(Name, Type, NavFix, Distance, Bearing, Reciprocal)
|
||||
|
||||
-- Convert magnetic to true bearing by adding magnetic declination, e.g. mag. bearing 10°M ==> true bearing 16°M (for 6° variation on Caucasus map)
|
||||
Bearing=Bearing+UTILS.GetMagneticDeclination()
|
||||
|
||||
if Reciprocal then
|
||||
Bearing=Bearing-180
|
||||
end
|
||||
|
||||
-- Translate.
|
||||
local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance), Bearing, true)
|
||||
|
||||
self=NAVFIX:NewFromVector(Name, Type, Vector)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new NAVFIX class instance from BEACONS.Beacon data.
|
||||
-- @param #NAVFIX self
|
||||
-- @param Navigation.Beacons#BEACONS.Beacon Beacon The beacon data.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:NewFromBeacon(Beacon)
|
||||
local frequency, unit = BEACONS:_GetFrequency(Beacon.frequency)
|
||||
frequency = string.format("%.3f",frequency)
|
||||
if Beacon.typeName == "TACAN" then
|
||||
frequency = Beacon.channel
|
||||
unit = "X"
|
||||
end
|
||||
self = NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3)
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set whether this is the intermediate fix (IF).
|
||||
-- @param #NAVFIX self
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetIntermediateFix(IntermediateFix)
|
||||
self.isIF=IntermediateFix
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether this is an initial approach fix (IAF).
|
||||
-- The IAF is the point where the initial approach segment of an instrument approach begins.
|
||||
-- It is usually a designated intersection, VHF omidirectional range (VOR) non-directional beacon (NDB)
|
||||
-- or distance measuring equipment (DME) fix.
|
||||
-- The IAF may be collocated with the intermediate fix (IF) of the instrument apprach an in such case they designate the
|
||||
-- beginning of the intermediate segment of the approach. When the IAF and the IF are combined, there is no inital approach segment.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #boolean IntermediateFix If `true`, this is an intermediate fix.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetInitialApproachFix(IntermediateFix)
|
||||
self.isIAF=IntermediateFix
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set whether this is the final approach fix (FAF).
|
||||
-- @param #NAVFIX self
|
||||
-- @param #boolean FinalApproachFix If `true`, this is a final approach fix.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetFinalApproachFix(FinalApproachFix)
|
||||
self.isFAF=FinalApproachFix
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether this is the final approach fix (FAF).
|
||||
-- @param #NAVFIX self
|
||||
-- @param #boolean FinalApproachFix If `true`, this is a final approach fix.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetMissedApproachFix(MissedApproachFix)
|
||||
self.isMAF=MissedApproachFix
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set minimum altitude.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #number Altitude Min altitude in feet.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetAltMin(Altitude)
|
||||
|
||||
self.altMin=Altitude
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set maximum altitude.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #number Altitude Max altitude in feet.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetAltMax(Altitude)
|
||||
|
||||
self.altMax=Altitude
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set mandatory altitude (min alt = max alt).
|
||||
-- @param #NAVFIX self
|
||||
-- @param #number Altitude Altitude in feet.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetAltMandatory(Altitude)
|
||||
|
||||
self.altMin=Altitude
|
||||
self.altMax=Altitude
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set minimum allowed speed at this fix.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #number Speed Min speed in knots.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetSpeedMin(Speed)
|
||||
|
||||
self.speedMin=Speed
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set maximum allowed speed at this fix.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #number Speed Max speed in knots.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetSpeedMax(Speed)
|
||||
|
||||
self.speedMax=Speed
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set mandatory speed (min speed = max speed) at this fix.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #number Speed Mandatory speed in knots.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetSpeedMandatory(Speed)
|
||||
|
||||
self.speedMin=Speed
|
||||
self.speedMax=Speed
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set whether this fix is compulsory.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #boolean Compulsory If `true`, this is a compulsory fix. If `false` or nil, it is non-compulsory.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetCompulsory(Compulsory)
|
||||
self.isCompulsory=Compulsory
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether this is a fly-over fix fix.
|
||||
-- @param #NAVFIX self
|
||||
-- @param #boolean FlyOver If `true`, this is a fly over fix. If `false` or nil, it is not.
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:SetFlyOver(FlyOver)
|
||||
self.isFlyover=FlyOver
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get the altitude in feet MSL. If min and max altitudes are set, it will return a random altitude between min and max.
|
||||
-- @param #NAVFIX self
|
||||
-- @return #number Altitude in feet MSL. Can be `nil`, if neither min nor max altitudes have beeen set.
|
||||
function NAVFIX:GetAltitude()
|
||||
|
||||
local alt=nil
|
||||
if self.altMin and self.altMax and self.altMin~=self.altMax then
|
||||
alt=math.random(self.altMin, self.altMax)
|
||||
elseif self.altMin then
|
||||
alt=self.altMin
|
||||
elseif self.altMax then
|
||||
alt=self.altMax
|
||||
end
|
||||
|
||||
return alt
|
||||
end
|
||||
|
||||
|
||||
--- Get the speed. If min and max speeds are set, it will return a random speed between min and max.
|
||||
-- @param #NAVFIX self
|
||||
-- @return #number Speed in knots. Can be `nil`, if neither min nor max speeds have beeen set.
|
||||
function NAVFIX:GetSpeed()
|
||||
|
||||
local speed=nil
|
||||
if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then
|
||||
speed=math.random(self.speedMin, self.speedMax)
|
||||
elseif self.speedMin then
|
||||
speed=self.speedMin
|
||||
elseif self.speedMax then
|
||||
speed=self.speedMax
|
||||
end
|
||||
|
||||
return speed
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Add marker the NAVFIX on the F10 map.
|
||||
-- @param #NAVFIX self
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:MarkerShow()
|
||||
|
||||
self.marker:ToAll()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove marker of the NAVFIX from the F10 map.
|
||||
-- @param #NAVFIX self
|
||||
-- @return #NAVFIX self
|
||||
function NAVFIX:MarkerRemove()
|
||||
|
||||
self.marker:Remove()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get text displayed in the F10 marker.
|
||||
-- @param #NAVFIX self
|
||||
-- @return #string Marker text.
|
||||
function NAVFIX:_GetMarkerText()
|
||||
|
||||
local altmin=self.altMin and tostring(self.altMin) or ""
|
||||
local altmax=self.altMax and tostring(self.altMax) or ""
|
||||
local speedmin=self.speedMin and tostring(self.speedMin) or ""
|
||||
local speedmax=self.speedMax and tostring(self.speedMax) or ""
|
||||
|
||||
|
||||
local text=string.format("NAVFIX %s", self.name)
|
||||
if self.isIAF then
|
||||
text=text..string.format(" (IAF)")
|
||||
end
|
||||
if self.isIF then
|
||||
text=text..string.format(" (IF)")
|
||||
end
|
||||
text=text..string.format("\nAltitude [ft]: %s - %s", altmin, altmax)
|
||||
text=text..string.format("\nSpeed [knots]: %s - %s", speedmin, speedmax)
|
||||
text=text..string.format("\nCompulsory: %s", tostring(self.isCompulsory))
|
||||
text=text..string.format("\nFly Over: %s", tostring(self.isFlyover))
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- NAVAID class.
|
||||
-- @type NAVAID
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @extends Navigation.Point#NAVFIX
|
||||
|
||||
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The NAVAID Concept
|
||||
--
|
||||
-- A NAVAID consists of one or multiple FLOTILLAs. These flotillas "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `NAVAID` object can be created with the @{#NAVAID.New}(`WarehouseName`, `FleetName`) function, where `WarehouseName` is the name of the static or unit object hosting the fleet
|
||||
-- and `FleetName` is the name you want to give the fleet. This must be *unique*!
|
||||
--
|
||||
-- myFleet=NAVAID:New("myWarehouseName", "1st Fleet")
|
||||
-- myFleet:SetPortZone(ZonePort1stFleet)
|
||||
-- myFleet:Start()
|
||||
--
|
||||
-- A fleet needs a *port zone*, which is set via the @{#NAVAID.SetPortZone}(`PortZone`) function. This is the zone where the naval assets are spawned and return to.
|
||||
--
|
||||
-- Finally, the fleet needs to be started using the @{#NAVAID.Start}() function. If the fleet is not started, it will not process any requests.
|
||||
--
|
||||
-- @field #NAVAID
|
||||
NAVAID = {
|
||||
ClassName = "NAVAID",
|
||||
verbose = 0,
|
||||
}
|
||||
|
||||
--- NAVAID class version.
|
||||
-- @field #string version
|
||||
NAVAID.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Add frequencies. Which unit MHz, kHz, Hz?
|
||||
-- TODO: Add radial function
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new NAVAID class instance.
|
||||
-- @param #NAVAID self
|
||||
-- @param #string Name Name/ident of this navaid.
|
||||
-- @param #string Type Type of the point. Default `NAVFIX.Type.POINT`.
|
||||
-- @param #string ZoneName Name of the zone to scan the scenery.
|
||||
-- @param #string SceneryName Name of the scenery object.
|
||||
-- @return #NAVAID self
|
||||
function NAVAID:NewFromScenery(Name, Type, ZoneName, SceneryName)
|
||||
|
||||
-- Get the zone.
|
||||
local zone=ZONE:FindByName(ZoneName)
|
||||
|
||||
-- Get coordinate.
|
||||
local Coordinate=zone:GetCoordinate()
|
||||
|
||||
-- Inherit everything from NAVFIX class.
|
||||
self=BASE:Inherit(self, NAVFIX:NewFromCoordinate(Name, Type, Coordinate)) -- #NAVAID
|
||||
|
||||
-- Set zone.
|
||||
self.zone=ZONE:FindByName(ZoneName)
|
||||
|
||||
-- Try to get the scenery object. Note not all can be found unfortunately.
|
||||
if SceneryName then
|
||||
self.scenery=SCENERY:FindByNameInZone(SceneryName, ZoneName)
|
||||
if not self.scenery then
|
||||
self:E(string.format("ERROR: Could not find scenery object %s in zone %s", SceneryName, ZoneName))
|
||||
end
|
||||
end
|
||||
|
||||
-- Alias.
|
||||
self.alias=string.format("%s %s %s", tostring(ZoneName), tostring(SceneryName), tostring(Type))
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("NAVAID %s | ", self.alias)
|
||||
|
||||
-- Debug info.
|
||||
self:I(self.lid..string.format("Created NAVAID!"))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set frequency the beacon transmits on.
|
||||
-- @param #NAVAID self
|
||||
-- @param #number Frequency Frequency in Hz.
|
||||
-- @return #NAVAID self
|
||||
function NAVAID:SetFrequency(Frequency)
|
||||
|
||||
self.frequency=Frequency
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set channel of, *e.g.*, TACAN beacons.
|
||||
-- @param #NAVAID self
|
||||
-- @param #number Channel The channel.
|
||||
-- @param #string Band The band either `"X"` (default) or `"Y"`.
|
||||
-- @return #NAVAID self
|
||||
function NAVAID:SetChannel(Channel, Band)
|
||||
|
||||
self.channel=Channel
|
||||
self.band=Band or "X"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- Add private CLASS functions here.
|
||||
-- No private NAVAID functions yet.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,423 @@
|
||||
--- **NAVIGATION** - Airbase radios.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Get radio frequencies of airbases
|
||||
-- * Find closest airbase radios
|
||||
-- * Mark radio frequencies on F10 map
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Radios).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Navigation.Radios
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- RADIOS class.
|
||||
-- @type RADIOS
|
||||
--
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field #table radios Radios.
|
||||
--
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *It's not true I had nothing on, I had the radio on.* -- *Marilyn Monroe*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The RADIOS Concept
|
||||
--
|
||||
-- This class is designed to make information about radios of a map/theatre easier accessible. The information contains mostly the frequencies of airbases of the map.
|
||||
--
|
||||
-- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `radio.lua` that can be
|
||||
-- found in the installation directory of DCS for each map or a table that the user needs to provide.
|
||||
-- **Note** your `MissionScripting` environment needs to be desanitized to read this data.
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `RADIOS` object can be created with the @{#RADIOS.NewFromFile}(*radio_lua_file*) function.
|
||||
--
|
||||
-- local radios=RADIOS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\Radio.lua")
|
||||
-- radios:MarkerShow()
|
||||
--
|
||||
-- This will load the radios from the `<DCS_Install_Directory>` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file
|
||||
-- you provided is correct and all relevant radios are present.
|
||||
--
|
||||
-- # User Functions
|
||||
--
|
||||
-- ## F10 Map Markers
|
||||
--
|
||||
-- ## Position
|
||||
--
|
||||
-- ## Closest Radio
|
||||
--
|
||||
--
|
||||
-- @field #RADIOS
|
||||
RADIOS = {
|
||||
ClassName = "RADIOS",
|
||||
verbose = 0,
|
||||
radios = {},
|
||||
}
|
||||
|
||||
--- Radio item data structure.
|
||||
-- @type RADIOS.Radio
|
||||
-- @field #string radioId Radio ID.
|
||||
-- @field #table role Roles of the radio (usually {"ground", "tower", "approach"}).
|
||||
-- @field #table callsign Callsigns of the radio (usually the airbase name).
|
||||
-- @field #table frequency Frequencies of the radios.
|
||||
-- @field #table position Position table.
|
||||
-- @field #table sceneObjects Scenery objects.
|
||||
-- @field #string name Name of the airbase.
|
||||
-- @field Wrapper.Airbase#AIRBASE airbase Airbase.
|
||||
-- @field Core.Point#COORDINATE coordinate The COORDINATE of the radio.
|
||||
-- @field DCS#Vec3 vec3 3D vector.
|
||||
-- @field #number markerID Marker ID.
|
||||
|
||||
--- Radio item data structure.
|
||||
-- @type RADIOS.Frequency
|
||||
-- @field #number modu Modulation type.
|
||||
-- @field #number freq Frequency in Hz.
|
||||
|
||||
|
||||
--- RADIOS class version.
|
||||
-- @field #string version
|
||||
RADIOS.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor(s)
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new RADIOS class instance from a given table.
|
||||
-- @param #RADIOS self
|
||||
-- @param #table RadioTable Table with radios info.
|
||||
-- @return #RADIOS self
|
||||
function RADIOS:NewFromTable(RadioTable)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #RADIOS
|
||||
|
||||
--local airbasenames=AIRBASE.GetAllAirbaseNames()
|
||||
|
||||
-- Get all airdromes
|
||||
local airdromes=AIRBASE.GetAllAirbases(nil, Airbase.Category.AIRDROME)
|
||||
|
||||
for _,_radio in pairs(RadioTable) do
|
||||
local radio=_radio --#RADIOS.Radio
|
||||
|
||||
-- The table structure of callsign is a bit awkward. We need to get the airbase name.
|
||||
-- Note that unfortunately, the callsign does not always correspond to the airbase name.
|
||||
if false then
|
||||
local cs=radio.callsign[1]
|
||||
if cs and cs.common then
|
||||
radio.name=cs.common[1]
|
||||
elseif cs and cs.nato then
|
||||
radio.name=cs.nato[1]
|
||||
else
|
||||
radio.name="Unknown"
|
||||
end
|
||||
radio.name=self:_GetAirbaseName(airbasenames, radio.name)
|
||||
radio.airbase=AIRBASE:FindByName(radio.name)
|
||||
end
|
||||
|
||||
-- Each radio item has a key radioId = 'airfield106_0', where 106 is the UID of the airbase.
|
||||
-- So we can use that to get the airbase.
|
||||
local aid = tonumber(string.match(radio.radioId, "airfield(%d+)_"))
|
||||
|
||||
-- Get airbase
|
||||
radio.airbase=self:_GetAirbaseByID(airdromes, aid)
|
||||
|
||||
-- Set other stuff
|
||||
if radio.airbase then
|
||||
radio.coordinate=radio.airbase:GetCoordinate()
|
||||
radio.vec3=radio.airbase:GetVec3()
|
||||
radio.name=radio.airbase:GetName()
|
||||
end
|
||||
|
||||
-- Add to table
|
||||
table.insert(self.radios, radio)
|
||||
end
|
||||
|
||||
-- Debug output
|
||||
self:I(string.format("Added %d radios", #self.radios))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new RADIOS class instance from a given file.
|
||||
-- @param #RADIOS self
|
||||
-- @param #string FileName Full path to the file containing the map radios.
|
||||
-- @return #RADIOS self
|
||||
function RADIOS:NewFromFile(FileName)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #RADIOS
|
||||
|
||||
local exists=UTILS.FileExists(FileName)
|
||||
|
||||
if exists==false then
|
||||
self:E(string.format("ERROR: file with radios info does not exist! File=%s", tostring(FileName)))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Backup DCS radio table
|
||||
local radiobak=UTILS.DeepCopy(radio)
|
||||
|
||||
-- This will create a global table `radio`
|
||||
dofile(FileName)
|
||||
|
||||
-- Get radios from table.
|
||||
self=self:NewFromTable(radio)
|
||||
|
||||
-- Restore DCS radio table
|
||||
radio=UTILS.DeepCopy(radiobak)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get 3D position vector of a specific radio.
|
||||
-- @param #RADIOS self
|
||||
-- @param #RADIOS.Radio radio The radio data structure.
|
||||
-- @return DCS#Vec3 Position vector.
|
||||
function RADIOS:GetVec3(radio)
|
||||
return radio.vec3
|
||||
end
|
||||
|
||||
--- Get COORDINATE of a specific radio.
|
||||
-- @param #RADIOS self
|
||||
-- @param #RADIOS.Radio radio The radio data structure.
|
||||
-- @return Core.Point#COORDINATE The coordinate.
|
||||
function RADIOS:GetCoordinate(radio)
|
||||
return radio.coordinate
|
||||
end
|
||||
|
||||
|
||||
--- Find closest radio to a given coordinate.
|
||||
-- @param #RADIOS self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
|
||||
-- @param #number DistMax (Optional) Max search distance in meters.
|
||||
-- @param #table ExcludeList (Optional) List of radios to exclude.
|
||||
-- @return #RADIOS.Radio The closest radio.
|
||||
function RADIOS:GetClosestRadio(Coordinate, DistMax, ExcludeList)
|
||||
|
||||
local radio=nil --#RADIOS.Radio
|
||||
local distmin=math.huge
|
||||
|
||||
ExcludeList=ExcludeList or {}
|
||||
|
||||
for _,_radio in pairs(self.radios) do
|
||||
local ra=_radio --#RADIOS.Radio
|
||||
|
||||
if (not UTILS.IsInTable(ExcludeList, ra, "radioId")) then
|
||||
|
||||
local dist=Coordinate:Get2DDistance(ra.coordinate)
|
||||
|
||||
if dist<distmin and (DistMax==nil or dist<=DistMax) then
|
||||
distmin=dist
|
||||
radio=ra
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return radio
|
||||
end
|
||||
|
||||
--- Find closest radios to a given coordinate.
|
||||
-- @param #RADIOS self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
|
||||
-- @param #number Nmax Max number of radios. Default 5.
|
||||
-- @param #number DistMax (Optional) Max search distance in meters.
|
||||
-- @return #table Table of #RADIOS.Radio closest radios.
|
||||
function RADIOS:GetClosestRadios(Coordinate, Nmax, DistMax)
|
||||
|
||||
Nmax=Nmax or 5
|
||||
|
||||
local closest={}
|
||||
for i=1,Nmax do
|
||||
|
||||
local radio=self:GetClosestRadio(Coordinate, DistMax, closest)
|
||||
|
||||
if radio then
|
||||
table.insert(closest, radio)
|
||||
else
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return closest
|
||||
end
|
||||
|
||||
|
||||
--- Add markers for all radios on the F10 map.
|
||||
-- @param #RADIOS self
|
||||
-- @param #RADIOS.Radio Radio (Optional) Only this specifc radio.
|
||||
-- @return #RADIOS self
|
||||
function RADIOS:MarkerShow(Radio)
|
||||
|
||||
for _,_radio in pairs(self.radios) do
|
||||
local radio=_radio --#RADIOS.Radio
|
||||
if Radio==nil or Radio.radioId==radio.radioId then
|
||||
local coord=self:GetCoordinate(radio)
|
||||
if coord then
|
||||
local text=self:_GetMarkerText(radio)
|
||||
if radio.markerID then
|
||||
UTILS.RemoveMark(radio.markerID)
|
||||
end
|
||||
radio.markerID=coord:MarkToAll(text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove markers of all radios from the F10 map.
|
||||
-- @param #RADIOS self
|
||||
-- @param #RADIOS.Radio Radio (Optional) Only this specifc radio.
|
||||
-- @return #RADIOS self
|
||||
function RADIOS:MarkerRemove(Radio)
|
||||
|
||||
for _,_radio in pairs(self.radios) do
|
||||
local radio=_radio --#RADIOS.Radio
|
||||
if Radio==nil or Radio.radioId==radio.radioId then
|
||||
if radio.markerID then
|
||||
UTILS.RemoveMark(radio.markerID)
|
||||
radio.markerID=nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get text displayed in the F10 marker.
|
||||
-- @param #RADIOS self
|
||||
-- @param #RADIOS.Radio radio The radio data structure.
|
||||
-- @return #string Marker text.
|
||||
function RADIOS:_GetMarkerText(radio)
|
||||
|
||||
local text=string.format("Radio %s", tostring(radio.name))
|
||||
for b,f in pairs(radio.frequency) do
|
||||
local frequency=f --#RADIOS.Frequency
|
||||
local mod=frequency[1]
|
||||
local fre=frequency[2]
|
||||
local freq, funit=self:_GetFrequency(fre)
|
||||
--UTILS.PrintTableToLog(frequency)
|
||||
local band=self:_GetBandName(b)
|
||||
text=text..string.format("\n%s: %.3f %s", band, freq, funit)
|
||||
end
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
|
||||
--- Get converted frequency.
|
||||
-- @param #RADIOS self
|
||||
-- @param #number freq Frequency in Hz.
|
||||
-- @return #number Frequency in better unit.
|
||||
-- @return #string Unit ("Hz", "kHz", "MHz").
|
||||
function RADIOS:_GetFrequency(freq)
|
||||
|
||||
freq=freq or 0
|
||||
local unit="Hz"
|
||||
|
||||
if freq>=1e6 then
|
||||
freq=freq/1e6
|
||||
unit="MHz"
|
||||
elseif freq>=1e3 then
|
||||
freq=freq/1e3
|
||||
unit="kHz"
|
||||
end
|
||||
|
||||
return freq, unit
|
||||
end
|
||||
|
||||
--- Get name of frequency band.
|
||||
-- @param #RADIOS self
|
||||
-- @param #number BandNumber Band as number.
|
||||
-- @return #string Band name.
|
||||
function RADIOS:_GetBandName(BandNumber)
|
||||
|
||||
if BandNumber~=nil then
|
||||
for bandName,bandNumber in pairs(ENUMS.FrequencyBand) do
|
||||
if bandNumber==BandNumber then
|
||||
return bandName
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return "Unknown"
|
||||
end
|
||||
|
||||
--- Get name of frequency band.
|
||||
-- @param #RADIOS self
|
||||
-- @param #table airbasenames Names of all airbases.
|
||||
-- @param #string name Name of airbase.
|
||||
-- @return #string Name of airbase
|
||||
function RADIOS:_GetAirbaseName(airbasenames, name)
|
||||
|
||||
local airbase=AIRBASE:FindByName(name)
|
||||
|
||||
if airbase then
|
||||
return name
|
||||
else
|
||||
for _,airbasename in pairs(airbasenames) do
|
||||
if string.find(airbasename, name) then
|
||||
return airbasename
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return "Unknown"
|
||||
end
|
||||
|
||||
--- Get name of frequency band.
|
||||
-- @param #RADIOS self
|
||||
-- @param #table airbases Table of airbases.
|
||||
-- @param #number aid Airbase ID.
|
||||
-- @return Wrapper.Airbase#AIRBASE Airbase matching the ID or nil.
|
||||
function RADIOS:_GetAirbaseByID(airbases, aid)
|
||||
|
||||
for _,_airbase in pairs(airbases) do
|
||||
local airbase=_airbase --Wrapper.Airbase#AIRBASE
|
||||
local id=airbase:GetID(true)
|
||||
if id==aid then
|
||||
return airbase
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,332 @@
|
||||
--- **NAVIGATION** - Towns of the map/theatre.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Find towns of map
|
||||
-- * Road and rail connections
|
||||
-- * Find closest town to a given coordinate
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Towns).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Navigation.Towns
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- TOWNS class.
|
||||
-- @type TOWNS
|
||||
--
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field #table towns Towns.
|
||||
--
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *Hope is the beacon that guides lost ships back to the shore.*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The TOWNS Concept
|
||||
--
|
||||
-- This class is designed to make information about towns of a map/theatre easier accessible. The information contains location and road/rail connections of the towns.
|
||||
--
|
||||
-- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `towns.lua` that can be
|
||||
-- found in the installation directory of DCS for each map or a table that the user needs to provide.
|
||||
-- **Note** your `MissionScripting` environment needs to be desanitized to read this data.
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `TOWNS` object can be created with the @{#TOWNS.NewFromFile}(*towns_lua_file*) function.
|
||||
--
|
||||
-- local towns=TOWNS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\map\towns.lua")
|
||||
-- towns:MarkerShow()
|
||||
--
|
||||
-- This will load the towns from the `<DCS_Install_Directory>` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file
|
||||
-- you provided is correct and all relevant towns are present.
|
||||
--
|
||||
-- # User Functions
|
||||
--
|
||||
-- ## F10 Map Markers
|
||||
--
|
||||
-- ## Position
|
||||
--
|
||||
-- ## Get Closest Town
|
||||
--
|
||||
--
|
||||
-- @field #TOWNS
|
||||
TOWNS = {
|
||||
ClassName = "TOWNS",
|
||||
verbose = 0,
|
||||
towns = {},
|
||||
}
|
||||
|
||||
--- Town data.
|
||||
-- @type TOWNS.Town
|
||||
-- @field #string display_name Displayed name.
|
||||
-- @field #string name Name of the town.
|
||||
-- @field #number latitude Latitude.
|
||||
-- @field #number longitude Longitude
|
||||
-- @field DCS#Vec3 vec3 Position vector 3D.
|
||||
-- @field Core.Point#COORDINATE coordinate The coordinate.
|
||||
-- @field Core.Point#COORDINATE coordRoad The coordinate of the closest road.
|
||||
-- @field Core.Point#COORDINATE coordRail The coordinate of the closest railway.
|
||||
-- @field #number markerID ID for the F10 marker.
|
||||
|
||||
--- TOWNS class version.
|
||||
-- @field #string version
|
||||
TOWNS.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
-- DONE: Road connection
|
||||
-- DONE: Rail connection
|
||||
-- DONE: Connection between towns
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor(s)
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new TOWNS class instance from a given table.
|
||||
-- @param #TOWNS self
|
||||
-- @param #table TownTable Table with all towns data.
|
||||
-- @return #TOWNS self
|
||||
function TOWNS:NewFromTable(TownTable)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #TOWNS
|
||||
|
||||
for TownName,_town in pairs(TownTable) do
|
||||
local town=_town --#TOWNS.Town
|
||||
|
||||
town.name=TownName
|
||||
|
||||
-- Get coordinate
|
||||
town.coordinate=COORDINATE:NewFromLLDD(town.latitude, town.longitude)
|
||||
|
||||
-- Get coordinate of closest road
|
||||
town.coordRoad=town.coordinate:GetClosestPointToRoad()
|
||||
|
||||
-- Get coordinate of closest rail
|
||||
town.coordRail=town.coordinate:GetClosestPointToRoad(true)
|
||||
|
||||
-- Add to table
|
||||
table.insert(self.towns, town)
|
||||
end
|
||||
|
||||
-- Debug output
|
||||
self:I(string.format("Added %d towns", #self.towns))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new TOWNS class instance from a given file.
|
||||
-- @param #TOWNS self
|
||||
-- @param #string FileName Full path to the file containing the towns data.
|
||||
-- @return #TOWNS self
|
||||
function TOWNS:NewFromFile(FileName)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #TOWNS
|
||||
|
||||
local exists=UTILS.FileExists(FileName)
|
||||
|
||||
if exists==false then
|
||||
self:E(string.format("ERROR: file with towns info does not exist!"))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- This will create a global table `towns`
|
||||
dofile(FileName)
|
||||
|
||||
-- Get towns from table.
|
||||
self=self:NewFromTable(towns)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get 3D position vector of a specific town.
|
||||
-- @param #TOWNS self
|
||||
-- @param #TOWNS.Town town The town data structure.
|
||||
-- @return DCS#Vec3 Position vector.
|
||||
function TOWNS:GetVec3(town)
|
||||
return town.vec3
|
||||
end
|
||||
|
||||
--- Get COORDINATE of a specific town.
|
||||
-- @param #TOWNS self
|
||||
-- @param #TOWNS.Town town The town data structure.
|
||||
-- @return Core.Point#COORDINATE The coordinate.
|
||||
function TOWNS:GetCoordinate(town)
|
||||
return town.coordinate
|
||||
end
|
||||
|
||||
--- Get closest road coordinate of a town.
|
||||
-- @param #TOWNS self
|
||||
-- @param #TOWNS.Town town The town data structure.
|
||||
-- @return Core.Point#COORDINATE The closest road coordinate.
|
||||
function TOWNS:GetCoordRoad(town)
|
||||
return town.coordRoad
|
||||
end
|
||||
|
||||
--- Get closest rail coordinate of a town.
|
||||
-- @param #TOWNS self
|
||||
-- @param #TOWNS.Town town The town data structure.
|
||||
-- @return Core.Point#COORDINATE The closest rail coordinate.
|
||||
function TOWNS:GetCoordRail(town)
|
||||
return town.coordRail
|
||||
end
|
||||
|
||||
--- Get road or rail connection between two towns.
|
||||
-- @param #TOWNS self
|
||||
-- @param #TOWNS.Town townA The town data structure.
|
||||
-- @param #TOWNS.Town townB The town data structure.
|
||||
-- @param #boolean Railroad If `true`, find rail road connection
|
||||
-- @return Core.Pathline#PATHLINE Pathline connecting the two towns on road.
|
||||
function TOWNS:GetConnectionRoad(townA, townB, Railroad)
|
||||
|
||||
local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad, false, Railroad)
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
--- Find closest town to a given coordinate.
|
||||
-- @param #TOWNS self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
|
||||
-- @param #number DistMax (Optional) Max search distance in meters.
|
||||
-- @param #table ExcludeList (Optional) List of towns excluded from the search.
|
||||
-- @return #TOWNS.Town The closest town.
|
||||
function TOWNS:GetClosestTown(Coordinate, DistMax, ExcludeList)
|
||||
|
||||
local Town=nil --#TOWNS.Town
|
||||
local distmin=math.huge
|
||||
ExcludeList=ExcludeList or {}
|
||||
|
||||
for _,_town in pairs(self.towns) do
|
||||
local town=_town --#TOWNS.Town
|
||||
|
||||
if (not UTILS.IsInTable(ExcludeList, town, "name")) then
|
||||
|
||||
local dist=Coordinate:Get2DDistance(town.coordinate)
|
||||
|
||||
if dist<distmin then
|
||||
distmin=dist
|
||||
Town=town
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Town
|
||||
end
|
||||
|
||||
--- Find closest towns to a given coordinate.
|
||||
-- @param #TOWNS self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate.
|
||||
-- @param #number Nmax Max number of towns. Default 5.
|
||||
-- @param #number DistMax (Optional) Max search distance in meters.
|
||||
-- @return #table Table of #TOWNS.Town closest towns.
|
||||
function TOWNS:GetClosestTowns(Coordinate, Nmax, DistMax)
|
||||
|
||||
Nmax=Nmax or 5
|
||||
|
||||
local closest={}
|
||||
for i=1,Nmax do
|
||||
|
||||
local town=self:GetClosestTown(Coordinate, DistMax, closest)
|
||||
|
||||
if town then
|
||||
table.insert(closest, town)
|
||||
else
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return closest
|
||||
end
|
||||
|
||||
|
||||
--- Get table of all towns, optionally of a given type.
|
||||
-- @param #TOWNS self
|
||||
-- @return #table Table of towns. Each element is of type #TOWN.Town.
|
||||
function TOWNS:GetTowns()
|
||||
return self.towns
|
||||
end
|
||||
|
||||
--- Add markers for all towns on the F10 map.
|
||||
-- @param #TOWNS self
|
||||
-- @param #TOWNS.Town Town (Optional) Only this specifc town.
|
||||
-- @return #TOWNS self
|
||||
function TOWNS:MarkerShow(Town)
|
||||
|
||||
for _,_town in pairs(self.towns) do
|
||||
local town=_town --#TOWNS.Town
|
||||
if Town==nil or Town.name==town.name then
|
||||
local text=self:_GetMarkerText(town)
|
||||
local coord=town.coordinate
|
||||
if town.markerID then
|
||||
UTILS.RemoveMark(town.markerID)
|
||||
end
|
||||
town.markerID=coord:MarkToAll(text)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove markers of all towns from the F10 map.
|
||||
-- @param #TOWNS self
|
||||
-- @param #TOWNS.Town Town (Optional) Only this specifc town.
|
||||
-- @return #TOWNS self
|
||||
function TOWNS:MarkerRemove(Town)
|
||||
|
||||
for _,_town in pairs(self.towns) do
|
||||
local town=_town --#TOWNS.Town
|
||||
if Town==nil or Town.name==town.name then
|
||||
if town.markerID then
|
||||
UTILS.RemoveMark(town.markerID)
|
||||
town.markerID=nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get text displayed in the F10 marker.
|
||||
-- @param #TOWNS self
|
||||
-- @param #TOWNS.Town town The town data structure.
|
||||
-- @return #string Marker text.
|
||||
function TOWNS:_GetMarkerText(town)
|
||||
|
||||
local text=string.format("Town %s", town.name)
|
||||
--text=text..string.format("\nCallsign: %s", tostring(beacon.callsign))
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -159,6 +159,8 @@ AIRWING = {
|
||||
-- @field #number refuelsystem Refueling system type: `0=Unit.RefuelingSystem.BOOM_AND_RECEPTACLE`, `1=Unit.RefuelingSystem.PROBE_AND_DROGUE`.
|
||||
-- @field #number noccupied Number of flights on this patrol point.
|
||||
-- @field Wrapper.Marker#MARKER marker F10 marker.
|
||||
-- @field #boolean IsZonePoint flag for using a (moving) zone as point for patrol etc.
|
||||
-- @field Core.Zone#ZONE_BASE patrolzone in case Patrol coordinate was handed as zone, store here.
|
||||
|
||||
--- Patrol zone.
|
||||
-- @type AIRWING.PatrolZone
|
||||
@@ -187,13 +189,14 @@ AIRWING = {
|
||||
|
||||
--- AIRWING class version.
|
||||
-- @field #string version
|
||||
AIRWING.version="0.9.6"
|
||||
AIRWING.version="0.9.7"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Check that airbase has enough parking spots if a request is BIG.
|
||||
-- DONE: Allow (moving) zones as base for patrol points.
|
||||
-- DONE: Spawn in air ==> Needs WAREHOUSE update.
|
||||
-- DONE: Spawn hot.
|
||||
-- DONE: Make special request to transfer squadrons to anther airwing (or warehouse).
|
||||
@@ -323,6 +326,42 @@ function AIRWING:AddSquadron(Squadron)
|
||||
if Squadron:IsStopped() then
|
||||
Squadron:Start()
|
||||
end
|
||||
|
||||
-- if storage is limited, add the amount of aircraft needed
|
||||
local airbasename = self:GetAirbaseName()
|
||||
|
||||
if airbasename then
|
||||
local group = Squadron.templategroup
|
||||
if group then
|
||||
local Nunits = 1
|
||||
local units
|
||||
if group then units = group:GetUnits() end
|
||||
if units then Nunits = #units end
|
||||
local typename = Squadron.aircrafttype or "none"
|
||||
local NAssets = Squadron.Ngroups * Nunits
|
||||
local storage = STORAGE:New(airbasename)
|
||||
|
||||
self:T(self.lid.."Adding "..typename.." #"..NAssets)
|
||||
if storage and storage.warehouse and storage:IsLimitedAircraft() and typename ~= "none" then
|
||||
local NInStore = storage:GetItemAmount(typename) or 0
|
||||
if NAssets > NInStore then
|
||||
storage:AddItem(typename,NAssets)
|
||||
end
|
||||
end
|
||||
|
||||
local unit = group:GetUnit(1)
|
||||
-- if storage is limited, add the amount of liquids needed
|
||||
if unit and storage and storage.warehouse and storage:IsLimitedLiquids() and typename ~= "none" then
|
||||
local fuel = unit:GetFuelMassMax()
|
||||
local neededfuel = (fuel*NAssets) -- kgs of fuel
|
||||
local NInStore = storage:GetLiquidAmount(STORAGE.Liquid.JETFUEL) or 0
|
||||
self:T(string.format(self.lid.."Fuel Needed: %dt | Fuel in store: %dt",neededfuel/1000,NInStore/1000))
|
||||
if neededfuel > NInStore then
|
||||
storage:AddLiquid(STORAGE.Liquid.JETFUEL,neededfuel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -807,13 +846,22 @@ function AIRWING:_PatrolPointMarkerText(point)
|
||||
end
|
||||
|
||||
--- Update marker of the patrol point.
|
||||
-- @param #AIRWING self
|
||||
-- @param #AIRWING.PatrolData point Patrol point table.
|
||||
function AIRWING:UpdatePatrolPointMarker(point)
|
||||
if self.markpoints then -- sometimes there's a direct call from #OPSGROUP
|
||||
|
||||
if self and self.markpoints then -- sometimes there's a direct call from #OPSGROUP
|
||||
local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts",
|
||||
point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed)
|
||||
|
||||
point.marker:UpdateText(text, 1)
|
||||
|
||||
if point.IsZonePoint and point.IsZonePoint == true and point.patrolzone then
|
||||
-- update position
|
||||
local Coordinate = point.patrolzone:GetCoordinate()
|
||||
point.marker:UpdateCoordinate(Coordinate)
|
||||
point.marker:UpdateText(text, 1.5)
|
||||
else
|
||||
point.marker:UpdateText(text, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -821,7 +869,7 @@ end
|
||||
--- Create a new generic patrol point.
|
||||
-- @param #AIRWING self
|
||||
-- @param #string Type Patrol point type, e.g. "CAP" or "AWACS". Default "Unknown".
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Default 10-15 NM away from the location of the airwing.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Default 10-15 NM away from the location of the airwing. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Orbit altitude in feet. Default random between Angels 10 and 20.
|
||||
-- @param #number Heading Heading in degrees. Default random (0, 360] degrees.
|
||||
-- @param #number LegLength Length of race-track orbit in NM. Default 15 NM.
|
||||
@@ -830,14 +878,16 @@ end
|
||||
-- @return #AIRWING.PatrolData Patrol point table.
|
||||
function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem)
|
||||
|
||||
-- Check if a zone was passed instead of a coordinate.
|
||||
if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
Coordinate=Coordinate:GetCoordinate()
|
||||
end
|
||||
|
||||
local patrolpoint={} --#AIRWING.PatrolData
|
||||
patrolpoint.type=Type or "Unknown"
|
||||
patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10, 15)), math.random(360))
|
||||
if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
patrolpoint.IsZonePoint = true
|
||||
patrolpoint.patrolzone = Coordinate
|
||||
patrolpoint.coord = patrolpoint.patrolzone:GetCoordinate()
|
||||
else
|
||||
patrolpoint.IsZonePoint = false
|
||||
end
|
||||
patrolpoint.heading=Heading or math.random(360)
|
||||
patrolpoint.leg=LegLength or 15
|
||||
patrolpoint.altitude=Altitude or math.random(10,20)*1000
|
||||
@@ -847,7 +897,7 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL
|
||||
|
||||
if self.markpoints then
|
||||
patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll()
|
||||
AIRWING.UpdatePatrolPointMarker(patrolpoint)
|
||||
self:UpdatePatrolPointMarker(patrolpoint)
|
||||
end
|
||||
|
||||
return patrolpoint
|
||||
@@ -855,7 +905,7 @@ end
|
||||
|
||||
--- Add a patrol Point for CAP missions.
|
||||
-- @param #AIRWING self
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Orbit altitude in feet.
|
||||
-- @param #number Speed Orbit speed in knots.
|
||||
-- @param #number Heading Heading in degrees.
|
||||
@@ -872,7 +922,7 @@ end
|
||||
|
||||
--- Add a patrol Point for RECON missions.
|
||||
-- @param #AIRWING self
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Orbit altitude in feet.
|
||||
-- @param #number Speed Orbit speed in knots.
|
||||
-- @param #number Heading Heading in degrees.
|
||||
@@ -889,7 +939,7 @@ end
|
||||
|
||||
--- Add a patrol Point for TANKER missions.
|
||||
-- @param #AIRWING self
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Orbit altitude in feet.
|
||||
-- @param #number Speed Orbit speed in knots.
|
||||
-- @param #number Heading Heading in degrees.
|
||||
@@ -907,7 +957,7 @@ end
|
||||
|
||||
--- Add a patrol Point for AWACS missions.
|
||||
-- @param #AIRWING self
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Orbit altitude in feet.
|
||||
-- @param #number Speed Orbit speed in knots.
|
||||
-- @param #number Heading Heading in degrees.
|
||||
@@ -1176,6 +1226,10 @@ function AIRWING:_GetPatrolData(PatrolPoints, RefuelSystem)
|
||||
|
||||
for _,_patrolpoint in pairs(PatrolPoints) do
|
||||
local patrolpoint=_patrolpoint --#AIRWING.PatrolData
|
||||
if patrolpoint.IsZonePoint and patrolpoint.IsZonePoint == true and patrolpoint.patrolzone then
|
||||
-- update
|
||||
patrolpoint.coord = patrolpoint.patrolzone:GetCoordinate()
|
||||
end
|
||||
if (RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem) or RefuelSystem==nil or patrolpoint.refuelsystem==nil then
|
||||
return patrolpoint
|
||||
end
|
||||
@@ -1235,7 +1289,7 @@ function AIRWING:CheckCAP()
|
||||
|
||||
patrol.noccupied=patrol.noccupied+1
|
||||
|
||||
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
|
||||
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
|
||||
|
||||
self:AddMission(missionCAP)
|
||||
|
||||
@@ -1287,7 +1341,7 @@ function AIRWING:CheckRECON()
|
||||
|
||||
patrol.noccupied=patrol.noccupied+1
|
||||
|
||||
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
|
||||
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
|
||||
|
||||
self:AddMission(missionRECON)
|
||||
|
||||
@@ -1332,7 +1386,7 @@ function AIRWING:CheckTANKER()
|
||||
|
||||
patrol.noccupied=patrol.noccupied+1
|
||||
|
||||
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
|
||||
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
|
||||
|
||||
self:AddMission(mission)
|
||||
|
||||
@@ -1351,7 +1405,7 @@ function AIRWING:CheckTANKER()
|
||||
|
||||
patrol.noccupied=patrol.noccupied+1
|
||||
|
||||
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
|
||||
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
|
||||
|
||||
self:AddMission(mission)
|
||||
|
||||
@@ -1389,7 +1443,7 @@ function AIRWING:CheckAWACS()
|
||||
|
||||
patrol.noccupied=patrol.noccupied+1
|
||||
|
||||
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
|
||||
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
|
||||
|
||||
self:AddMission(mission)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-- * Multiple carrier support due to object oriented approach.
|
||||
-- * Unlimited number of players.
|
||||
-- * Persistence of player results (optional). LSO grading data is saved to csv file.
|
||||
-- * Trap sheet (optional).
|
||||
-- * Trap sheet (optional).
|
||||
-- * Finite State Machine (FSM) implementation.
|
||||
--
|
||||
-- **Supported Carriers:**
|
||||
@@ -39,11 +39,13 @@
|
||||
-- * [USS America](https://en.wikipedia.org/wiki/USS_America_\(LHA-6\)) (LHA-6)
|
||||
-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61)
|
||||
-- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_\(L02\)) (L02)
|
||||
-- * BONHOMMERICHARD [VWV Mod]
|
||||
-- * ENTERPRISE66 [VWV Mod]
|
||||
--
|
||||
-- **Supported Aircraft:**
|
||||
--
|
||||
-- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI)
|
||||
-- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
|
||||
-- * [F-14A/B/A Early Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
|
||||
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
|
||||
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI)
|
||||
-- * [T-45C Goshawk](https://forum.dcs.world/topic/203816-vnao-t-45-goshawk/) (VNAO mod) (Player & AI)
|
||||
@@ -61,7 +63,7 @@
|
||||
-- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier.
|
||||
-- However, if no offset is used for the holding radial this provides a very close representation of the V/STOL Case III, allowing for an approach to over the deck and a vertical landing.
|
||||
--
|
||||
-- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version.
|
||||
-- Heatblur's mighty F-14A/B/A Early Tomcat has been added as well.
|
||||
--
|
||||
-- The [DCS Supercarriers](https://www.digitalcombatsimulator.com/de/shop/modules/supercarrier/) are also supported.
|
||||
--
|
||||
@@ -1274,6 +1276,7 @@ AIRBOSS = {
|
||||
-- @field #string A4EC A-4E Community mod.
|
||||
-- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics.
|
||||
-- @field #string F14A F-14A by Heatblur.
|
||||
-- @field #string F14A_Early F-14A-135-GR-Early by Heatblur.
|
||||
-- @field #string F14B F-14B by Heatblur.
|
||||
-- @field #string F14A_AI F-14A Tomcat (AI).
|
||||
-- @field #string FA18C F/A-18C Hornet (AI).
|
||||
@@ -1292,6 +1295,7 @@ AIRBOSS.AircraftCarrier={
|
||||
HORNET="FA-18C_hornet",
|
||||
A4EC="A-4E-C",
|
||||
F14A="F-14A-135-GR",
|
||||
F14A_Early="F-14A-135-GR-Early",
|
||||
F14B="F-14B",
|
||||
F14A_AI="F-14A",
|
||||
FA18C="F/A-18C",
|
||||
@@ -1317,6 +1321,10 @@ AIRBOSS.AircraftCarrier={
|
||||
-- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module]
|
||||
-- @field #string VINSON USS Carl Vinson (CVN-70) [Deprecated!]
|
||||
-- @field #string ESSEX Essex class carrier (e.g. USS Yorktown (CV-10)) [Magnitude 3 Carrier Module]
|
||||
-- @field #string BONHOMMERICHARD USS Bon Homme Richard carrier [VWV Mod]
|
||||
-- @field #string ESSEXSCB125 Generic Essex class carrier with angled deck (SCB-125 upgrade) [VWV Mod]
|
||||
-- @field #string ENTERPRISE66 USS Enterprise in the 1966 configuration [VWV Mod]
|
||||
-- @field #string ENTERPRISEMODERN USS Enterprise in a modern configuration [Derived VWV Mod]
|
||||
-- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier]
|
||||
-- @field #string INVINCIBLE HMS Invincible (R05) [V/STOL Carrier]
|
||||
-- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier]
|
||||
@@ -1331,8 +1339,12 @@ AIRBOSS.CarrierType = {
|
||||
TRUMAN = "CVN_75",
|
||||
STENNIS = "Stennis",
|
||||
FORRESTAL = "Forrestal",
|
||||
ENTERPRISE66 = "USS Enterprise 1966",
|
||||
ENTERPRISEMODERN = "cvn-65",
|
||||
VINSON = "VINSON",
|
||||
ESSEX = "Essex",
|
||||
BONHOMMERICHARD = "USS Bon Homme Richard",
|
||||
ESSEXSCB125 = "essex_scb125",
|
||||
HERMES = "HERMES81",
|
||||
INVINCIBLE = "hms_invincible",
|
||||
TARAWA = "LHA_Tarawa",
|
||||
@@ -1756,7 +1768,7 @@ AIRBOSS.MenuF10Root = nil
|
||||
|
||||
--- Airboss class version.
|
||||
-- @field #string version
|
||||
AIRBOSS.version = "1.4.1"
|
||||
AIRBOSS.version = "1.4.2"
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -2019,11 +2031,19 @@ function AIRBOSS:New( carriername, alias )
|
||||
self:_InitNimitz()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.FORRESTAL then
|
||||
self:_InitForrestal()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.ENTERPRISE66 then
|
||||
self:_InitEnterprise()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.ENTERPRISEMODERN then
|
||||
self:_InitEnterprise()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.VINSON then
|
||||
-- Carl Vinson is legacy now.
|
||||
self:_InitStennis()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.ESSEX then
|
||||
self:_InitEssex()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.BONHOMMERICHARD then
|
||||
self:_InitBonHommeRichard()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.ESSEXSCB125 then
|
||||
self:_InitEssexSCB125()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.HERMES then
|
||||
-- Hermes parameters.
|
||||
self:_InitHermes()
|
||||
@@ -2412,6 +2432,16 @@ end
|
||||
-- USER API Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set the carrier illumination mode.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number Mode Options are: -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetCarrierIllumination(Mode)
|
||||
self.carrier:SetCarrierIlluminationMode(Mode)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set welcome messages for players.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #boolean Switch If true, display welcome message to player.
|
||||
@@ -3100,8 +3130,8 @@ function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volum
|
||||
self.SRS:SetCulture(Culture or "en-US")
|
||||
--self.SRS:SetFrequencies(Frequencies)
|
||||
self.SRS:SetGender(Gender or "male")
|
||||
self.SRS:SetPath(PathToSRS)
|
||||
self.SRS:SetPort(Port or 5002)
|
||||
--self.SRS:SetPath(PathToSRS)
|
||||
self.SRS:SetPort(Port or MSRS.port or 5002)
|
||||
self.SRS:SetLabel(self.AirbossRadio.alias or "AIRBOSS")
|
||||
self.SRS:SetCoordinate(self.carrier:GetCoordinate())
|
||||
self.SRS:SetVolume(Volume or 1)
|
||||
@@ -3112,7 +3142,10 @@ function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volum
|
||||
if Voice then
|
||||
self.SRS:SetVoice(Voice)
|
||||
end
|
||||
self.SRS:SetVolume(Volume or 1.0)
|
||||
if (not Voice) and self.SRS and self.SRS:GetProvider() == MSRS.Provider.GOOGLE then
|
||||
self.SRS.voice = MSRS.poptions["gcloud"].voice or MSRS.Voices.Google.Standard.en_US_Standard_B
|
||||
end
|
||||
--self.SRS:SetVolume(Volume or 1.0)
|
||||
-- SRSQUEUE
|
||||
self.SRSQ = MSRSQUEUE:New("AIRBOSS")
|
||||
self.SRSQ:SetTransmitOnlyWithPlayers(true)
|
||||
@@ -4643,6 +4676,26 @@ function AIRBOSS:_InitForrestal()
|
||||
|
||||
end
|
||||
|
||||
--- Init parameters for Enterprise carrier.
|
||||
-- @param #AIRBOSS self
|
||||
function AIRBOSS:_InitEnterprise()
|
||||
-- Using Forrestal as template
|
||||
self:_InitForrestal()
|
||||
|
||||
self.carrierparam.sterndist = -164.30
|
||||
self.carrierparam.deckheight = 19.52
|
||||
|
||||
self.carrierparam.totlength = 335
|
||||
self.carrierparam.rwylength = 223
|
||||
|
||||
-- Wires.
|
||||
self.carrierparam.wire1 = 57.7
|
||||
self.carrierparam.wire2 = 69.6
|
||||
self.carrierparam.wire3 = 79.5
|
||||
self.carrierparam.wire4 = 90.0
|
||||
|
||||
end
|
||||
|
||||
--- Init parameters for Essec class carriers.
|
||||
-- @param #AIRBOSS self
|
||||
function AIRBOSS:_InitEssex()
|
||||
@@ -4688,6 +4741,35 @@ function AIRBOSS:_InitEssex()
|
||||
|
||||
end
|
||||
|
||||
--- Init parameters for CVA-31 Bon Homme Richard carriers.
|
||||
-- @param #AIRBOSS self
|
||||
function AIRBOSS:_InitBonHommeRichard()
|
||||
-- Init Essex as default
|
||||
self:_InitEssex()
|
||||
|
||||
self.carrierparam.deckheight = 16.95
|
||||
|
||||
-- Landing runway.
|
||||
-- from BHR EssexRunwayAndRoutes.lua
|
||||
self.carrierparam.rwyangle = -11.4
|
||||
self.carrierparam.rwylength = 97
|
||||
self.carrierparam.rwywidth = 20
|
||||
|
||||
-- Wires.
|
||||
self.carrierparam.wire1 = 40.4 -- Distance from stern to first wire. Original from Frank - 42
|
||||
self.carrierparam.wire2 = 45
|
||||
self.carrierparam.wire3 = 51
|
||||
self.carrierparam.wire4 = 58.1
|
||||
end
|
||||
|
||||
--- Init parameters for Generic Essex SC125 class carriers.
|
||||
-- @param #AIRBOSS self
|
||||
function AIRBOSS:_InitEssexSCB125()
|
||||
-- Init Bon Homme Richard as default
|
||||
self:_InitBonHommeRichard()
|
||||
|
||||
end
|
||||
|
||||
--- Init parameters for R12 HMS Hermes carrier.
|
||||
-- @param #AIRBOSS self
|
||||
function AIRBOSS:_InitHermes()
|
||||
@@ -5393,7 +5475,7 @@ function AIRBOSS:_GetAircraftAoA( playerData )
|
||||
local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C
|
||||
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
|
||||
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early
|
||||
local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW
|
||||
|
||||
-- Table with AoA values.
|
||||
@@ -5418,15 +5500,15 @@ function AIRBOSS:_GetAircraftAoA( playerData )
|
||||
aoa.OnSpeedMin = self:_AoAUnit2Deg( playerData, 14.0 ) -- 14.17 --14.5 units -- VNAO Edit - Original value 14.5
|
||||
aoa.Fast = self:_AoAUnit2Deg( playerData, 13.5 ) -- 13.33 --14.0 units -- VNAO Edit - Original value 14
|
||||
aoa.FAST = self:_AoAUnit2Deg( playerData, 12.5 ) -- 11.67 --13.0 units -- VNAO Edit - Original value 13
|
||||
elseif goshawk then
|
||||
elseif goshawk then --These parameters edited by CIRCUIT to support new VNAO flight model
|
||||
-- T-45C Goshawk parameters.
|
||||
aoa.SLOW = 8.00 -- 19
|
||||
aoa.Slow = 7.75 -- 18
|
||||
aoa.OnSpeedMax = 7.25 -- 17.5
|
||||
aoa.OnSpeed = 7.00 -- 17
|
||||
aoa.OnSpeedMin = 6.75 -- 16.5
|
||||
aoa.Fast = 6.25 -- 16
|
||||
aoa.FAST = 6.00 -- 15
|
||||
aoa.SLOW = 9.5 -- 8.00 -- 19
|
||||
aoa.Slow = 9.25 -- 7.75 -- 18
|
||||
aoa.OnSpeedMax = 9.0 --7.25 -- 17.5
|
||||
aoa.OnSpeed = 8.5 -- 7.00 -- 17
|
||||
aoa.OnSpeedMin = 8.25 -- 6.75 -- 16.5
|
||||
aoa.Fast = 7.75 -- 6.25 -- 16
|
||||
aoa.FAST = 5.5 -- 6.00 -- 15
|
||||
elseif skyhawk then
|
||||
-- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390
|
||||
-- Note that these are arbitrary UNITS and not degrees. We need a conversion formula!
|
||||
@@ -5472,7 +5554,7 @@ function AIRBOSS:_AoAUnit2Deg( playerData, aoaunits )
|
||||
local degrees = aoaunits
|
||||
|
||||
-- Check aircraft type of player.
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
||||
|
||||
-------------
|
||||
-- F-14A/B --
|
||||
@@ -5515,7 +5597,7 @@ function AIRBOSS:_AoADeg2Units( playerData, degrees )
|
||||
local aoaunits = degrees
|
||||
|
||||
-- Check aircraft type of player.
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
||||
|
||||
-------------
|
||||
-- F-14A/B --
|
||||
@@ -6870,6 +6952,9 @@ function AIRBOSS:_AddMarshalGroup( flight, stack )
|
||||
-- Convert to clock string.
|
||||
local Ccharlie = UTILS.SecondsToClock( flight.Tcharlie )
|
||||
|
||||
-- Make sure brc is never above 360
|
||||
brc = brc % 360
|
||||
|
||||
-- Combined marshal call.
|
||||
self:_MarshalCallArrived( flight.onboard, flight.case, brc, alt, Ccharlie, P )
|
||||
|
||||
@@ -7523,7 +7608,7 @@ function AIRBOSS:_InitPlayer( playerData, step )
|
||||
playerData.landed = false
|
||||
playerData.Tlso = timer.getTime()
|
||||
playerData.Tgroove = nil
|
||||
playerData.TIG0 = nil
|
||||
playerData.TIG0 = 0 --changed to prevent errors in script when player is not in correct spot
|
||||
playerData.wire = nil
|
||||
playerData.flag = -100
|
||||
playerData.debriefschedulerID = nil
|
||||
@@ -8062,8 +8147,7 @@ end
|
||||
--- Check current player status.
|
||||
-- @param #AIRBOSS self
|
||||
function AIRBOSS:_CheckPlayerStatus()
|
||||
|
||||
-- Loop over all players.
|
||||
-- Loop over all players.
|
||||
for _playerName, _playerData in pairs( self.players ) do
|
||||
local playerData = _playerData -- #AIRBOSS.PlayerData
|
||||
|
||||
@@ -8079,8 +8163,14 @@ function AIRBOSS:_CheckPlayerStatus()
|
||||
-- TODO: This might cause problems if the CCA is set to be very small!
|
||||
if unit:IsInZone( self.zoneCCA ) then
|
||||
|
||||
-- VNAO Edit - Added wrapped up call to LSO grading
|
||||
if playerData.step==AIRBOSS.PatternStep.WAKE then-- VNAO Edit - Added
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
||||
|
||||
-- VNAO Edit - Added wrapped up call to LSO grading Hornet
|
||||
if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then-- VNAO Edit - Added
|
||||
if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then-- VNAO Edit - Added
|
||||
playerData.wrappedUpAtWakeLittle = true -- VNAO Edit - Added
|
||||
elseif math.abs(playerData.unit:GetRoll()) >40 and math.abs(playerData.unit:GetRoll())<=45 then-- VNAO Edit - Added
|
||||
@@ -8104,6 +8194,30 @@ function AIRBOSS:_CheckPlayerStatus()
|
||||
end -- VNAO Edit - Added
|
||||
end-- VNAO Edit - Added
|
||||
|
||||
-- VNAO Edit - Added wrapped up call to LSO grading Tomcat
|
||||
if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then-- VNAO Edit - Added
|
||||
if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then-- VNAO Edit - Added
|
||||
playerData.wrappedUpAtWakeLittle = true -- VNAO Edit - Added
|
||||
elseif math.abs(playerData.unit:GetRoll()) >40 and math.abs(playerData.unit:GetRoll())<=45 then-- VNAO Edit - Added
|
||||
playerData.wrappedUpAtWakeFull = true-- VNAO Edit - Added
|
||||
elseif math.abs(playerData.unit:GetRoll()) >45 then-- VNAO Edit - Added
|
||||
playerData.wrappedUpAtWakeUnderline = true -- VNAO Edit - Added
|
||||
elseif math.abs(playerData.unit:GetRoll()) <12 and math.abs(playerData.unit:GetRoll()) >=5 then -- VNAO Edit - Added a new AA comment based on discussion with Lipps today, and going to replace the AA at the X with the original LUL comments
|
||||
playerData.AAatWakeLittle = true -- VNAO Edit - Added
|
||||
elseif math.abs(playerData.unit:GetRoll()) <5 and math.abs(playerData.unit:GetRoll()) >=2 then -- VNAO Edit - Added a new AA comment based on discussion with Lipps today, and going to replace the AA at the X with the original LUL comments
|
||||
playerData.AAatWakeFull = true -- VNAO Edit - Added
|
||||
elseif math.abs(playerData.unit:GetRoll()) <2 then -- VNAO Edit - Added a new AA comment based on discussion with Lipps today, and going to replace the AA at the X with the original LUL comments
|
||||
playerData.AAatWakeUnderline = true -- VNAO Edit - Added
|
||||
else -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
|
||||
if math.abs(playerData.unit:GetAoA())>= 15 then -- VNAO Edit - Added
|
||||
playerData.AFU = true -- VNAO Edit - Added
|
||||
elseif math.abs(playerData.unit:GetAoA())<= 5 then -- VNAO Edit - Added
|
||||
playerData.AFU = true -- VNAO Edit - Added
|
||||
else -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
end-- VNAO Edit - Added
|
||||
|
||||
-- Display aircraft attitude and other parameters as message text.
|
||||
if playerData.attitudemonitor then
|
||||
@@ -8335,7 +8449,7 @@ end
|
||||
function AIRBOSS:_SetTimeInGroove( playerData )
|
||||
|
||||
-- Set time in the groove
|
||||
if playerData.TIG0 then
|
||||
if playerData.TIG0 then
|
||||
playerData.Tgroove = timer.getTime() - playerData.TIG0 - 1.5 -- VNAO Edit - Subtracting an extra 1.5
|
||||
else
|
||||
playerData.Tgroove = 999
|
||||
@@ -9564,7 +9678,7 @@ end
|
||||
--- Break entry for case I/II recoveries.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #AIRBOSS.PlayerData playerData Player data table.
|
||||
function AIRBOSS:_BreakEntry( playerData ) --Adam Edits begin 7/24/23
|
||||
function AIRBOSS:_BreakEntry( playerData )
|
||||
|
||||
-- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier)
|
||||
local X, Z = self:_GetDistances( playerData.unit )
|
||||
@@ -9575,111 +9689,16 @@ function AIRBOSS:_BreakEntry( playerData ) --Adam Edits begin 7/24/23
|
||||
return
|
||||
end
|
||||
|
||||
local stern = self:_GetSternCoord()
|
||||
local coord = playerData.unit:GetCoordinate()
|
||||
local dist = coord:Get2DDistance( stern )
|
||||
|
||||
--adam edits
|
||||
local playerCallsign = playerData.unit:GetCallsign()
|
||||
--trigger.action.outText(' Hornet is hook down on pre-break entry for testing hook argument ', 5)
|
||||
--trigger.action.outText(' Hornet callsign is '..playerCallsign, 5)
|
||||
local playerName = playerData.name
|
||||
local unit = playerData.unit
|
||||
|
||||
--local playerName = unit:GetName()
|
||||
--trigger.action.outText(' Hornet name is '..playerName, 5)
|
||||
local unitClient = Unit.getByName(unit:GetName())
|
||||
local hookArgument = unitClient:getDrawArgumentValue(25)
|
||||
local hookArgument_Tomcat = unitClient:getDrawArgumentValue(1305)
|
||||
local speedMPS = playerData.unit:GetVelocityMPS()
|
||||
local speedKTS = UTILS.MpsToKnots( speedMPS )
|
||||
local player_alt = playerData.unit:GetAltitude()
|
||||
|
||||
player_alt_feet = player_alt * 3.28
|
||||
player_alt_feet = player_alt_feet/10
|
||||
player_alt_feet = math.floor(player_alt_feet)*10
|
||||
|
||||
local player_velocity_round = speedKTS * 1.00
|
||||
player_velocity_round = player_velocity_round/10
|
||||
player_velocity_round = math.floor(player_velocity_round)*10
|
||||
|
||||
local player_alt_feet = player_alt * 3.28
|
||||
player_alt_feet = player_alt_feet/10
|
||||
player_alt_feet = math.floor(player_alt_feet)*10
|
||||
|
||||
local Play_SH_Sound = USERSOUND:New( "Airboss Soundfiles/GreatBallsOfFire.ogg" )
|
||||
local Play_666SH_Sound = USERSOUND:New( "Airboss Soundfiles/Runninwiththedevil.ogg" )
|
||||
local playerType = playerData.actype
|
||||
|
||||
|
||||
|
||||
if dist <1000 and clientSHBFlag == false then
|
||||
|
||||
if speedKTS > 450 and speedKTS < 590 then
|
||||
if player_alt_feet < 1500 then
|
||||
if hookArgument > 0 or hookArgument_Tomcat > 0 then
|
||||
--trigger.action.outText(' 1 - Hornet is hook down so SHB!!!! Hook argument is: '..hookArgument, 5)
|
||||
playerData.shb = true
|
||||
trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType, 10)
|
||||
local sh_message_to_discord = ('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**')
|
||||
HypeMan.sendBotMessage(sh_message_to_discord)
|
||||
Play_SH_Sound:ToAll()
|
||||
clientSHBFlag = true
|
||||
else
|
||||
--trigger.action.outText(' Hornet is hook up on initial and just fast so no SHB. Hook argument is: '..hookArgument, 5)
|
||||
playerData.shb = false
|
||||
end
|
||||
-- Next step: Early Break.
|
||||
else
|
||||
end
|
||||
elseif speedKTS > 589 then
|
||||
if player_alt_feet < 625 and player_alt_feet >575 then --SHB 666
|
||||
if hookArgument > 0 or hookArgument_Tomcat > 0 then
|
||||
--trigger.action.outText(' 1 - Hornet is hook down so SHB!!!! Hook argument is: '..hookArgument, 5)
|
||||
playerData.shb = true
|
||||
trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType, 10)
|
||||
local sh_message_to_discord = ('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**')
|
||||
HypeMan.sendBotMessage(sh_message_to_discord)
|
||||
Play_666SH_Sound:ToAll()
|
||||
clientSHBFlag = true
|
||||
else
|
||||
--trigger.action.outText(' Hornet is hook up on initial and just fast so no SHB. Hook argument is: '..hookArgument, 5)
|
||||
playerData.shb = false
|
||||
end
|
||||
else
|
||||
if hookArgument > 0 or hookArgument_Tomcat > 0 then
|
||||
--trigger.action.outText(' 1 - Hornet is hook down so SHB!!!! Hook argument is: '..hookArgument, 5)
|
||||
playerData.shb = true
|
||||
trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType, 10)
|
||||
local sh_message_to_discord = ('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**')
|
||||
HypeMan.sendBotMessage(sh_message_to_discord)
|
||||
Play_SH_Sound:ToAll()
|
||||
clientSHBFlag = true
|
||||
else
|
||||
--trigger.action.outText(' Hornet is hook up on initial and just fast so no SHB. Hook argument is: '..hookArgument, 5)
|
||||
playerData.shb = false
|
||||
end
|
||||
end
|
||||
else
|
||||
--trigger.action.outText(' Hornet is less than 400 kts so not SHB.... ', 5)
|
||||
end
|
||||
else
|
||||
--trigger.action.outText(' ******TEST OF of Break Entry and distance to CVN is: '..dist, 5)
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- Check if we are in front of the boat (diffX > 0).
|
||||
if self:_CheckLimits( X, Z, self.BreakEntry ) then
|
||||
--trigger.action.outText(' 2 - Hornet is hook down on break entry for testing hook argument ', 5)
|
||||
|
||||
-- Hint for player about altitude, AoA etc.
|
||||
self:_PlayerHint( playerData )
|
||||
-- Next step: Early Break.
|
||||
self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.EARLYBREAK )
|
||||
clientSHBFlag = false
|
||||
|
||||
end
|
||||
end--Adam Edits end 7/24/23
|
||||
end
|
||||
|
||||
--- Break.
|
||||
-- @param #AIRBOSS self
|
||||
@@ -10281,19 +10300,19 @@ function AIRBOSS:_Groove( playerData )
|
||||
if rho >= RAR and rho <= RIM then
|
||||
if gd.LUE > 0.22 and lineupError < -0.22 then
|
||||
env.info " Drift Right across centre ==> DR-"
|
||||
gd.Drift = " DR"
|
||||
gd.Drift = "DR"
|
||||
self:T( self.lid .. string.format( "Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) )
|
||||
elseif gd.LUE < -0.22 and lineupError > 0.22 then
|
||||
env.info " Drift Left ==> DL-"
|
||||
gd.Drift = " DL"
|
||||
gd.Drift = "DL"
|
||||
self:T( self.lid .. string.format( "Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) )
|
||||
elseif gd.LUE > 0.13 and lineupError < -0.14 then
|
||||
env.info " Little Drift Right across centre ==> (DR-)"
|
||||
gd.Drift = " (DR)"
|
||||
gd.Drift = "(DR)"
|
||||
self:T( self.lid .. string.format( "Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) )
|
||||
elseif gd.LUE < -0.13 and lineupError > 0.14 then
|
||||
env.info " Little Drift Left across centre ==> (DL-)"
|
||||
gd.Drift = " (DL)"
|
||||
gd.Drift = "(DL)"
|
||||
self:E( self.lid .. string.format( "Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) )
|
||||
end
|
||||
end
|
||||
@@ -11576,7 +11595,7 @@ function AIRBOSS:_AttitudeMonitor( playerData )
|
||||
|
||||
local unitClient = Unit.getByName(unit:GetName()) -- VNAO Edit - Added
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET -- VNAO Edit - Added
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B -- VNAO Edit - Added
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early -- VNAO Edit - Added
|
||||
|
||||
if hornet then -- VNAO Edit - Added
|
||||
local nozzlePosL = 0 -- VNAO Edit - Added
|
||||
@@ -11809,7 +11828,7 @@ function AIRBOSS:_NozzleArgumentLeft( unit ) -- VNAO Edit - Added
|
||||
else -- VNAO Edit - Added
|
||||
nozzlePosL = 0 -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" then -- VNAO Edit - Added
|
||||
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
|
||||
nozzlePosL = unitClient:getDrawArgumentValue(434) -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
|
||||
@@ -11834,7 +11853,7 @@ function AIRBOSS:_NozzleArgumentRight( unit ) -- VNAO Edit - Added
|
||||
else -- VNAO Edit - Added
|
||||
nozzlePosR = 0 -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" then -- VNAO Edit - Added
|
||||
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
|
||||
nozzlePosR = unitClient:getDrawArgumentValue(433) -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
return nozzlePosR -- VNAO Edit - Added
|
||||
@@ -11971,10 +11990,12 @@ function AIRBOSS:GetHeading( magnetic )
|
||||
hdg = hdg - self.magvar
|
||||
end
|
||||
|
||||
-- Adjust negative values.
|
||||
if hdg < 0 then
|
||||
hdg = hdg + 360
|
||||
end
|
||||
-- -- Adjust negative values.
|
||||
-- if hdg < 0 then
|
||||
-- hdg = hdg + 360
|
||||
-- end
|
||||
|
||||
hdg = hdg % 360 -- using this to replace the above function to prevent negative values and BRC higher than 360
|
||||
|
||||
return hdg
|
||||
end
|
||||
@@ -12240,8 +12261,8 @@ function AIRBOSS:GetHeadingIntoWind_new( vdeck, magnetic, coord )
|
||||
local magvar= magnetic and self.magvar or 0
|
||||
|
||||
-- Ship heading so cross wind is min for the given wind.
|
||||
-- local intowind = (540 + (windto - magvar + math.deg(theta) )) % 360 -- VNAO Edit: Using old heading into wind algorithm
|
||||
local intowind = self:GetHeadingIntoWind_old(vdeck) -- VNAO Edit: Using old heading into wind algorithm
|
||||
local intowind = (540 + (windto - magvar + math.deg(theta) )) % 360
|
||||
|
||||
|
||||
return intowind, v
|
||||
end
|
||||
@@ -12671,7 +12692,7 @@ function AIRBOSS:_LSOgrade( playerData )
|
||||
local TIG = ""
|
||||
-- Analyse flight data and convert to LSO text.
|
||||
if playerData.Tgroove and playerData.Tgroove <= 360 and playerData.case < 3 then --Circuit Added
|
||||
TIG = self:_EvalGrooveTime( playerData ) --Circuit Added
|
||||
TIG = self:_EvalGrooveTime( playerData ) or "N/A" --Circuit Added
|
||||
end --Circuit Added
|
||||
local GXX, nXX = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.XX )
|
||||
local GIM, nIM = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.IM )
|
||||
@@ -12693,7 +12714,8 @@ function AIRBOSS:_LSOgrade( playerData )
|
||||
local nL=count(G, '_')/2
|
||||
local nS=count(G, '%(')
|
||||
local nN=N-nS-nL
|
||||
|
||||
|
||||
if TIG=="_OK_" then nL = nL -1 end --Circuit added to prevent grade deduction for perfect groove
|
||||
|
||||
-- Groove time 15-18.99 sec for a unicorn. Or 60-65 for V/STOL unicorn.
|
||||
local Tgroove=playerData.Tgroove
|
||||
@@ -12723,7 +12745,6 @@ function AIRBOSS:_LSOgrade( playerData )
|
||||
else
|
||||
|
||||
if vtol then
|
||||
|
||||
-- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe.--Pene testing
|
||||
-- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation.
|
||||
|
||||
@@ -12865,16 +12886,21 @@ function AIRBOSS:_LSOgrade( playerData )
|
||||
|
||||
end
|
||||
|
||||
-- VNAO EDIT: Subtract 1pt from overall grade if it is a 1 wire. If it's already a 1pt pass, ignore.
|
||||
if playerData.wire == 1 and points > 1 then -- VNAO EDIT: added
|
||||
if points == 4 then -- VNAO EDIT: added
|
||||
points = 3 -- VNAO EDIT: added
|
||||
grade = "(OK)" -- VNAO EDIT: added
|
||||
elseif points == 3 then -- VNAO EDIT: added
|
||||
points = 2 -- VNAO EDIT: added
|
||||
grade = "--" -- VNAO EDIT: added
|
||||
end -- VNAO EDIT: added
|
||||
end -- VNAO EDIT: added
|
||||
-- -- VNAO EDIT: Subtract 1pt from overall grade if it is a 1 wire. If it's already a 1pt pass, ignore.
|
||||
-- if playerData.wire == 1 and points > 1 then -- VNAO EDIT: added
|
||||
-- if points == 4 then -- VNAO EDIT: added
|
||||
-- points = 3 -- VNAO EDIT: added
|
||||
-- grade = "(OK)" -- VNAO EDIT: added
|
||||
-- elseif points == 3 then -- VNAO EDIT: added
|
||||
-- points = 2 -- VNAO EDIT: added
|
||||
-- grade = "--" -- VNAO EDIT: added
|
||||
-- end -- VNAO EDIT: added
|
||||
-- end -- VNAO EDIT: added
|
||||
|
||||
-- Circuit edit only take points awary from a 1 wire if there are more than 4 other deviations
|
||||
if playerData.wire == 1 and points >= 3 and N > 4 then
|
||||
points = points -1
|
||||
end
|
||||
|
||||
env.info("Returning: " .. grade .. " " .. points .. " " .. G)
|
||||
|
||||
@@ -12950,6 +12976,7 @@ function AIRBOSS:_Flightdata2Text( playerData, groovestep )
|
||||
|
||||
-- Speed via AoA. Depends on aircraft type.
|
||||
local S = nil
|
||||
local A = nil --circuit moved this line to be seen outside of this scope
|
||||
if step~=AIRBOSS.PatternStep.GROOVE_IW then -- VNAO Edit - Added To avoid getting an AOA or GS grade in the wires... let's just check left or right in the wires
|
||||
if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff == true and playerData.owo == true then -- VNAO Edit - Added
|
||||
-- env.info('Adam MOOSE Edit -AR and waved off so do not add AOA or GS errors to comments ') -- VNAO Edit - Added
|
||||
@@ -12970,7 +12997,7 @@ function AIRBOSS:_Flightdata2Text( playerData, groovestep )
|
||||
end
|
||||
|
||||
-- Glideslope/altitude. Good [-0.3, 0.4] asymmetric!
|
||||
local A = nil
|
||||
|
||||
if GSE > self.gle.HIGH then
|
||||
A = underline( "H" )
|
||||
elseif GSE > self.gle.High then
|
||||
@@ -15041,7 +15068,7 @@ function AIRBOSS:_GetACNickname( actype )
|
||||
nickname = "Hawkeye"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.C2A then
|
||||
nickname = "Greyhound"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B or actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
||||
nickname = "Tomcat"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
|
||||
nickname = "Hornet"
|
||||
|
||||
@@ -839,8 +839,8 @@ function ARMYGROUP:Status()
|
||||
local ammo=self:GetAmmoElement(element)
|
||||
|
||||
-- Output text for element.
|
||||
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg",
|
||||
i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, element.weightCargo, element.weightMaxCargo)
|
||||
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg",
|
||||
i, name, status, life, life0, ammo.Guns, ammo.Cannons, ammo.Rockets, ammo.Missiles, element.weightCargo, element.weightMaxCargo)
|
||||
end
|
||||
if #self.elements==0 then
|
||||
text=text.." none!"
|
||||
@@ -1571,7 +1571,7 @@ end
|
||||
-- @param Core.Zone#ZONE Zone The zone to return to.
|
||||
-- @param #number Formation Formation of the group.
|
||||
function ARMYGROUP:onafterRTZ(From, Event, To, Zone, Formation)
|
||||
self:T2(self.lid.."onafterRTZ")
|
||||
self:T(self.lid.."onafterRTZ")
|
||||
|
||||
-- Zone.
|
||||
local zone=Zone or self.homezone
|
||||
@@ -1841,8 +1841,6 @@ function ARMYGROUP:_UpdateEngageTarget()
|
||||
-- Check if target moved more than 100 meters or we do not have line of sight.
|
||||
if dist>100 or los==false then
|
||||
|
||||
--env.info("FF Update Engage Target Moved "..self.engage.Target:GetName())
|
||||
|
||||
-- Update new position.
|
||||
self.engage.Coordinate:UpdateFromVec3(vec3)
|
||||
|
||||
@@ -1852,13 +1850,14 @@ function ARMYGROUP:_UpdateEngageTarget()
|
||||
-- Remove current waypoint
|
||||
self:RemoveWaypointByID(self.engage.Waypoint.uid)
|
||||
|
||||
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
|
||||
-- Get new coordinate where to go.
|
||||
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.95)
|
||||
|
||||
-- Add waypoint after current.
|
||||
self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Formation, true)
|
||||
|
||||
-- Set if we want to resume route after reaching the detour waypoint.
|
||||
self.engage.Waypoint.detour=0
|
||||
self.engage.Waypoint.detour=1
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -324,6 +324,10 @@
|
||||
--
|
||||
-- A ground attack mission can be created with the @{#AUFTRAG.NewGROUNDATTACK}() function.
|
||||
--
|
||||
-- ## NAVALENGAGEMENT
|
||||
--
|
||||
-- A naval engagement mission can be created with the @{#AUFTRAG.NewNAVALENGAGEMENT}() function.
|
||||
--
|
||||
-- # Assigning Missions
|
||||
--
|
||||
-- An AUFTRAG can be assigned to groups (FLIGHTGROUP, ARMYGROUP, NAVYGROUP), legions (AIRWING, BRIGADE, FLEET) or to a COMMANDER.
|
||||
@@ -443,6 +447,7 @@ _AUFTRAGSNR=0
|
||||
-- @field #string HOVER Hover.
|
||||
-- @field #string LANDATCOORDINATE Land at coordinate.
|
||||
-- @field #string GROUNDATTACK Ground attack.
|
||||
-- @field #string NAVALENGAGEMENT Naval engagement (similar to GROUNDATTACK).
|
||||
-- @field #string CARGOTRANSPORT Cargo transport.
|
||||
-- @field #string RELOCATECOHORT Relocate a cohort from one legion to another.
|
||||
-- @field #string AIRDEFENSE Air defense.
|
||||
@@ -491,6 +496,7 @@ AUFTRAG.Type={
|
||||
HOVER="Hover",
|
||||
LANDATCOORDINATE="Land at Coordinate",
|
||||
GROUNDATTACK="Ground Attack",
|
||||
NAVALENGAGEMENT="Naval Engagement",
|
||||
CARGOTRANSPORT="Cargo Transport",
|
||||
RELOCATECOHORT="Relocate Cohort",
|
||||
AIRDEFENSE="Air Defence",
|
||||
@@ -515,6 +521,7 @@ AUFTRAG.Type={
|
||||
-- @field #string BARRAGE Barrage.
|
||||
-- @field #string HOVER Hover.
|
||||
-- @field #string GROUNDATTACK Ground attack.
|
||||
-- @field #string NAVALENGAGEMENT Naval engagement.
|
||||
-- @field #string FERRY Ferry mission.
|
||||
-- @field #string RELOCATECOHORT Relocate cohort.
|
||||
-- @field #string AIRDEFENSE Air defense.
|
||||
@@ -537,6 +544,7 @@ AUFTRAG.SpecialTask={
|
||||
ARMORATTACK="AmorAttack",
|
||||
HOVER="Hover",
|
||||
GROUNDATTACK="Ground Attack",
|
||||
NAVALENGAGEMENT="Naval Engagement",
|
||||
FERRY="Ferry",
|
||||
RELOCATECOHORT="Relocate Cohort",
|
||||
AIRDEFENSE="Air Defense",
|
||||
@@ -666,7 +674,7 @@ AUFTRAG.Category={
|
||||
|
||||
--- AUFTRAG class version.
|
||||
-- @field #string version
|
||||
AUFTRAG.version="1.2.1"
|
||||
AUFTRAG.version="1.3.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -1321,13 +1329,19 @@ end
|
||||
-- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`.
|
||||
-- @param #number Speed Orbit indicated airspeed in knots at the set altitude ASL. Default 350 KIAS.
|
||||
-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West).
|
||||
-- @param #number Leg Length of race-track in NM. Default 10 NM.
|
||||
-- @param #number Leg Length of race-track in NM. Default 10 NM. Set to 0 for a simple circular orbit.
|
||||
-- @param #number RefuelSystem Refueling system (0=boom, 1=probe). This info is *only* for AIRWINGs so they launch the right tanker type.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewTANKER(Coordinate, Altitude, Speed, Heading, Leg, RefuelSystem)
|
||||
|
||||
|
||||
local mission
|
||||
if Leg == 0 then
|
||||
mission=AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed)
|
||||
else
|
||||
mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg)
|
||||
end
|
||||
-- Create ORBIT first.
|
||||
local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg)
|
||||
--local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg)
|
||||
|
||||
-- Mission type TANKER.
|
||||
mission.type=AUFTRAG.Type.TANKER
|
||||
@@ -1727,7 +1741,7 @@ function AUFTRAG:NewSEADInZone(TargetZone, Altitude, TargetTypes, Duration)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.SEAD)
|
||||
|
||||
mission:_TargetFromObject(TargetZone)
|
||||
--mission:_TargetFromObject(TargetZone)
|
||||
|
||||
-- DCS Task options:
|
||||
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
|
||||
@@ -2023,7 +2037,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier)
|
||||
|
||||
-- Mission options:
|
||||
mission.missionTask=ENUMS.MissionTask.NOTHING
|
||||
mission.missionFraction=0.5
|
||||
mission.missionFraction=0.9
|
||||
mission.optionROE=ENUMS.ROE.WeaponHold
|
||||
mission.optionROT=ENUMS.ROT.NoReaction
|
||||
|
||||
@@ -2204,7 +2218,7 @@ end
|
||||
-- **Note** that it is recommended to set the weapon range via the `OPSGROUP:AddWeaponRange()` function as this cannot be retrieved from the DCS API.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target Center of the firing solution.
|
||||
-- @param #number Nshots Number of shots to be fired. Default `#nil`.
|
||||
-- @param #number Nshots Number of shots to be fired. Default `#nil`. If value is in (0,1), it is interpreted as per cent of available ammo.
|
||||
-- @param #number Radius Radius of the shells in meters. Default 100 meters.
|
||||
-- @param #number Altitude Altitude in meters. Can be used to setup a Barrage. Default `#nil`.
|
||||
-- @return #AUFTRAG self
|
||||
@@ -2318,8 +2332,9 @@ end
|
||||
-- @param #number Speed Speed in knots.
|
||||
-- @param #number Altitude Altitude in feet. Only for airborne units. Default 2000 feet ASL.
|
||||
-- @param #string Formation Formation used by ground units during patrol. Default "Off Road".
|
||||
-- @param #number StayInZoneTime Stay this many seconds in the zone when done, only then drive back.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation)
|
||||
function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation, StayInZoneTime)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE)
|
||||
|
||||
@@ -2333,6 +2348,7 @@ function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation)
|
||||
mission.optionROE=ENUMS.ROE.ReturnFire
|
||||
mission.optionROT=ENUMS.ROT.PassiveDefense
|
||||
mission.optionAlarm=ENUMS.AlarmState.Auto
|
||||
mission.StayInZoneTime = StayInZoneTime
|
||||
|
||||
mission.missionFraction=0.1
|
||||
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil
|
||||
@@ -2374,11 +2390,13 @@ function AUFTRAG:NewARMORATTACK(Target, Speed, Formation)
|
||||
return mission
|
||||
end
|
||||
|
||||
--- **[GROUND]** Create a GROUNDATTACK mission. Ground group(s) will go to a target object and attack.
|
||||
--- **[GROUND]** Create a GROUNDATTACK mission. Ground group(s) will go to a target object and attack at their own discretion.
|
||||
-- Unfortunately, the "Attack Group" and "Attack Unit" tasks do not work for ground and naval groups (only for aircraft).
|
||||
-- Therefore, we resort to this workaround, which guides the attacking group to the vicinity of the target. Then they start shooting on their own, once they detect the target.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP, UNIT or STATIC object.
|
||||
-- @param #number Speed Speed in knots. Default max.
|
||||
-- @param #string Formation The attack formation, e.g. "Wedge", "Vee" etc. Default `ENUMS.Formation.Vehicle.Vee`.
|
||||
-- @param #string Formation The attack formation, e.g. "Wedge", "Vee" etc. Default `ENUMS.Formation.Vehicle.Vee`. Only working for ground, not naval!
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation)
|
||||
|
||||
@@ -2405,6 +2423,38 @@ function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation)
|
||||
return mission
|
||||
end
|
||||
|
||||
--- **[NAVAL]** Create a NAVALENGAGEMENT mission. Naval group(s) will go to a target object and attack at their own discretion.
|
||||
-- Unfortunately, the "Attack Group" and "Attack Unit" tasks do not work for ground and naval groups (only for aircraft).
|
||||
-- Therefore, we resort to this workaround, which guides the attacking group to the vicinity of the target. Then they start shooting on their own, once they detect the target.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP, UNIT or STATIC object.
|
||||
-- @param #number Speed Speed in knots. Default max.
|
||||
-- @param #number Depth The attack depth in meters. Only for submarines!
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewNAVALENGAGEMENT(Target, Speed, Depth)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT)
|
||||
|
||||
mission:_TargetFromObject(Target)
|
||||
|
||||
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT)
|
||||
|
||||
-- Defaults.
|
||||
mission.optionROE=ENUMS.ROE.OpenFire
|
||||
mission.optionAlarm=ENUMS.AlarmState.Auto
|
||||
mission.missionFraction=0.70
|
||||
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil
|
||||
mission.missionAltitude=Depth or 0
|
||||
|
||||
mission.categories={AUFTRAG.Category.NAVAL}
|
||||
|
||||
mission.DCStask=mission:GetDCSMissionTask()
|
||||
|
||||
mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil
|
||||
|
||||
return mission
|
||||
end
|
||||
|
||||
--- **[AIR, GROUND, NAVAL]** Create a RECON mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Set#SET_ZONE ZoneSet The recon zones.
|
||||
@@ -2697,33 +2747,35 @@ function AUFTRAG:NewFromTarget(Target, MissionType)
|
||||
local mission=nil --#AUFTRAG
|
||||
|
||||
if MissionType==AUFTRAG.Type.ANTISHIP then
|
||||
mission=self:NewANTISHIP(Target, Altitude)
|
||||
mission=self:NewANTISHIP(Target)
|
||||
elseif MissionType==AUFTRAG.Type.ARTY then
|
||||
mission=self:NewARTY(Target, Nshots, Radius)
|
||||
mission=self:NewARTY(Target, 0.3) -- use 30% of the available ammo
|
||||
elseif MissionType==AUFTRAG.Type.BAI then
|
||||
mission=self:NewBAI(Target, Altitude)
|
||||
mission=self:NewBAI(Target)
|
||||
elseif MissionType==AUFTRAG.Type.BOMBCARPET then
|
||||
mission=self:NewBOMBCARPET(Target, Altitude, CarpetLength)
|
||||
mission=self:NewBOMBCARPET(Target)
|
||||
elseif MissionType==AUFTRAG.Type.BOMBING then
|
||||
mission=self:NewBOMBING(Target, Altitude)
|
||||
mission=self:NewBOMBING(Target)
|
||||
elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then
|
||||
mission=self:NewBOMBRUNWAY(Target, Altitude)
|
||||
mission=self:NewBOMBRUNWAY(Target)
|
||||
elseif MissionType==AUFTRAG.Type.STRAFING then
|
||||
mission=self:NewSTRAFING(Target, Altitude)
|
||||
mission=self:NewSTRAFING(Target)
|
||||
elseif MissionType==AUFTRAG.Type.CAS then
|
||||
mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), Altitude, Speed, Target:GetAverageCoordinate(), Heading, Leg, TargetTypes)
|
||||
mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), nil, nil, Target:GetAverageCoordinate())
|
||||
elseif MissionType==AUFTRAG.Type.CASENHANCED then
|
||||
mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), Altitude, Speed, RangeMax, NoEngageZoneSet, TargetTypes)
|
||||
mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000))
|
||||
elseif MissionType==AUFTRAG.Type.INTERCEPT then
|
||||
mission=self:NewINTERCEPT(Target)
|
||||
elseif MissionType==AUFTRAG.Type.SEAD then
|
||||
mission=self:NewSEAD(Target, Altitude)
|
||||
mission=self:NewSEAD(Target)
|
||||
elseif MissionType==AUFTRAG.Type.STRIKE then
|
||||
mission=self:NewSTRIKE(Target, Altitude)
|
||||
mission=self:NewSTRIKE(Target)
|
||||
elseif MissionType==AUFTRAG.Type.ARMORATTACK then
|
||||
mission=self:NewARMORATTACK(Target, Speed)
|
||||
mission=self:NewARMORATTACK(Target)
|
||||
elseif MissionType==AUFTRAG.Type.GROUNDATTACK then
|
||||
mission=self:NewGROUNDATTACK(Target, Speed, Formation)
|
||||
mission=self:NewGROUNDATTACK(Target)
|
||||
elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then
|
||||
mission=self:NewNAVALENGAGEMENT(Target)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@@ -2840,7 +2892,7 @@ function AUFTRAG:NewAUTO(EngageGroup)
|
||||
if auftrag==AUFTRAG.Type.ANTISHIP then
|
||||
mission=AUFTRAG:NewANTISHIP(Target)
|
||||
elseif auftrag==AUFTRAG.Type.ARTY then
|
||||
mission=AUFTRAG:NewARTY(Target)
|
||||
mission=AUFTRAG:NewARTY(Target, 0.2)
|
||||
elseif auftrag==AUFTRAG.Type.AWACS then
|
||||
mission=AUFTRAG:NewAWACS(Coordinate, Altitude,Speed,Heading,Leg)
|
||||
elseif auftrag==AUFTRAG.Type.BAI then
|
||||
@@ -4010,6 +4062,23 @@ function AUFTRAG:IsOver()
|
||||
return over
|
||||
end
|
||||
|
||||
--- Check if mission is repeatable.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return #boolean If true, mission is repeatable.
|
||||
function AUFTRAG:IsRepeatable()
|
||||
local repeatmeS=self.repeatedSuccess<self.NrepeatSuccess or self.repeated<self.Nrepeat
|
||||
local repeatmeF=self.repeatedFailure<self.NrepeatFailure or self.repeated<self.Nrepeat
|
||||
if repeatmeS==true or repeatmeF==true then return true else return false end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if mission is NOT repeatable.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return #boolean If true, mission is NOT repeatable.
|
||||
function AUFTRAG:IsNotRepeatable()
|
||||
return not self:IsRepeatable()
|
||||
end
|
||||
|
||||
--- Check if mission is NOT over.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return #boolean If true, mission is NOT over yet.
|
||||
@@ -4374,7 +4443,7 @@ function AUFTRAG:onafterStatus(From, Event, To)
|
||||
-- Group info.
|
||||
if self.verbose>=3 then
|
||||
-- Data on assigned groups.
|
||||
local text=string.format("Assets [N=%d,Nassigned=%s, Ndead=%s]:", self.Nassets or 0, self.Nassigned or 0, self.Ndead or 0)
|
||||
local text=string.format("Assets [N=%d, Nassigned=%s, Ndead=%s]:", self.Nassets or 0, self.Nassigned or 0, self.Ndead or 0)
|
||||
for i,_asset in pairs(self.assets or {}) do
|
||||
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
text=text..string.format("\n[%d] %s: spawned=%s, requested=%s, reserved=%s", i, asset.spawngroupname, tostring(asset.spawned), tostring(asset.requested), tostring(asset.reserved))
|
||||
@@ -4415,7 +4484,7 @@ function AUFTRAG:Evaluate()
|
||||
local owndamage=self.Ncasualties/self.Nelements*100
|
||||
|
||||
-- Current number of mission targets.
|
||||
local Ntargets=self:CountMissionTargets()
|
||||
local Ntargets=self:CountMissionTargets(true)
|
||||
local Ntargets0=self:GetTargetInitialNumber()
|
||||
|
||||
local Life=self:GetTargetLife()
|
||||
@@ -4866,16 +4935,25 @@ function AUFTRAG:CheckGroupsDone()
|
||||
self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] and reinfoce=%d. Mission NOT DONE!", self.status, self:GetState(), self.reinforce))
|
||||
return false
|
||||
end
|
||||
|
||||
local NopsgroupsAlive=self:CountOpsGroups()
|
||||
local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED)
|
||||
|
||||
-- It could be that all groups were destroyed on the way to the mission execution waypoint.
|
||||
-- TODO: would be better to check if everybody is dead by now.
|
||||
if self:IsStarted() and self:CountOpsGroups()==0 then
|
||||
if self:IsStarted() and NopsgroupsAlive==0 then
|
||||
self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] but count of alive OPSGROUP is zero. Mission DONE!", self.status, self:GetState()))
|
||||
return true
|
||||
end
|
||||
|
||||
if (self:IsStarted() or self:IsExecuting()) and (fsmState == AUFTRAG.Status.STARTED or fsmState == AUFTRAG.Status.EXECUTING) and self:CountOpsGroups()>0 then
|
||||
self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState()))
|
||||
-- Every group alive is done or cancelled
|
||||
if NopsgroupsAlive==NopsgroupsDone then
|
||||
self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!", self.status, self:GetState(), NopsgroupsAlive))
|
||||
return true
|
||||
end
|
||||
|
||||
if (self:IsStarted() or self:IsExecuting()) and (fsmState == AUFTRAG.Status.STARTED or fsmState == AUFTRAG.Status.EXECUTING) and NopsgroupsAlive>0 then
|
||||
self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState()))
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -5469,8 +5547,9 @@ end
|
||||
|
||||
--- Count alive mission targets.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #boolean OnlyReallyAlive (Optional) If `true`, count only really alive targets (units, groups) but not coordinates or zones.
|
||||
-- @return #number Number of alive target units.
|
||||
function AUFTRAG:CountMissionTargets()
|
||||
function AUFTRAG:CountMissionTargets(OnlyReallyAlive)
|
||||
|
||||
local N=0
|
||||
|
||||
@@ -5478,7 +5557,7 @@ function AUFTRAG:CountMissionTargets()
|
||||
local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition, true) or nil
|
||||
|
||||
if self.engageTarget then
|
||||
N=self.engageTarget:CountTargets(Coalitions)
|
||||
N=self.engageTarget:CountTargets(Coalitions, OnlyReallyAlive)
|
||||
end
|
||||
|
||||
return N
|
||||
@@ -6617,6 +6696,26 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then
|
||||
|
||||
---------------------------
|
||||
-- NAVAL ENGAGEMENT Mission --
|
||||
---------------------------
|
||||
|
||||
local DCStask={}
|
||||
|
||||
DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT
|
||||
|
||||
-- We create a "fake" DCS task and pass the parameters to the NAVYGROUP.
|
||||
local param={}
|
||||
param.target=self:GetTargetData()
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
param.altitude=self.missionAltitude or 0
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
elseif self.type==AUFTRAG.Type.AMMOSUPPLY then
|
||||
|
||||
-------------------------
|
||||
|
||||
@@ -509,7 +509,7 @@ do
|
||||
-- @field #AWACS
|
||||
AWACS = {
|
||||
ClassName = "AWACS", -- #string
|
||||
version = "0.2.72", -- #string
|
||||
version = "0.2.74", -- #string
|
||||
lid = "", -- #string
|
||||
coalition = coalition.side.BLUE, -- #number
|
||||
coalitiontxt = "blue", -- #string
|
||||
@@ -1596,6 +1596,16 @@ function AWACS:SetLocale(Locale)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set own coordinate for BullsEye.
|
||||
-- @param #AWACS self
|
||||
-- @param Core.Point#COORDINATE
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetBullsCoordinate(Coordinate)
|
||||
self:T(self.lid.."SetBullsCoordinate")
|
||||
self.AOCoordinate = Coordinate
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set the max mission range flights can be away from their home base.
|
||||
-- @param #AWACS self
|
||||
-- @param #number NM Distance in nautical miles
|
||||
@@ -2021,7 +2031,9 @@ function AWACS:SetAdditionalZone(Zone, Draw)
|
||||
self.BorderZone = Zone
|
||||
if self.debug then
|
||||
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
|
||||
MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition)
|
||||
end
|
||||
elseif Draw then
|
||||
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
|
||||
end
|
||||
@@ -2041,11 +2053,62 @@ function AWACS:SetRejectionZone(Zone,Draw)
|
||||
--MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToAll()
|
||||
elseif self.debug then
|
||||
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
|
||||
MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zones.
|
||||
-- @param #AWACS self
|
||||
-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetCorridorZones(CorridorZones)
|
||||
self:T(self.lid .. "SetCorridorZones")
|
||||
if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then
|
||||
self.corridorzones = CorridorZones
|
||||
self.usecorridors = true
|
||||
elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then
|
||||
if not self.corridorzones then self.corridorzones = SET_ZONE:New() end
|
||||
self.corridorzones:AddZone(CorridorZones)
|
||||
self.usecorridors = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to add one corridor zone.
|
||||
-- @param #AWACS self
|
||||
-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added.
|
||||
-- @return #AWACS self
|
||||
function AWACS:AddCorridorZone(CorridorZone)
|
||||
self:T(self.lid .. "AddCorridorZone")
|
||||
self:SetCorridorZones(CorridorZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in FEET.
|
||||
-- @param #AWACS self
|
||||
-- @param #number Floor Floor altitude ASL in feet.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in feet.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
|
||||
self.corridorfloor = UTILS.FeetToMeters(Floor)
|
||||
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in METERS.
|
||||
-- @param #AWACS self
|
||||
-- @param #number Floor Floor altitude ASL in meters.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in meters.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
|
||||
self.corridorfloor = Floor
|
||||
self.corridorceiling = Ceiling
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Draw a line around the FEZ on the F10 map.
|
||||
-- @param #AWACS self
|
||||
-- @return #AWACS self
|
||||
@@ -4094,10 +4157,14 @@ function AWACS:_CreateAnchorStackFromMarker(Name,Coord)
|
||||
if self.debug then
|
||||
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
else
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
end
|
||||
|
||||
self.AnchorStacks:Push(AnchorStackOne,newname)
|
||||
@@ -4140,10 +4207,14 @@ function AWACS:_CreateAnchorStack()
|
||||
--self.AnchorStacks:Flush()
|
||||
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
else
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
end
|
||||
self.AnchorStacks:Push(AnchorStackOne,newname)
|
||||
else
|
||||
@@ -4167,10 +4238,14 @@ function AWACS:_CreateAnchorStack()
|
||||
if self.debug then
|
||||
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
else
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
end
|
||||
self.AnchorStacks:Push(AnchorStackOne,newname)
|
||||
end
|
||||
@@ -4310,6 +4385,14 @@ function AWACS:_StartIntel(awacs)
|
||||
intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
|
||||
end
|
||||
|
||||
-- Corridors
|
||||
if self.usecorridors == true then
|
||||
intel:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorceiling or self.corridorfloor then
|
||||
intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
|
||||
-- Callbacks
|
||||
local function NewCluster(Cluster)
|
||||
self:__NewCluster(5,Cluster)
|
||||
@@ -5102,10 +5185,14 @@ function AWACS:AddCAPAirWing(AirWing,Zone)
|
||||
if self.debug then
|
||||
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
else
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
end
|
||||
self.AnchorStacks:Push(AnchorStackOne,newname)
|
||||
AirWing.HasOwnStation = true
|
||||
@@ -5948,23 +6035,35 @@ function AWACS:onafterStart(From, Event, To)
|
||||
self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true)
|
||||
local AOCoordString = self.AOCoordinate:ToStringLLDDM()
|
||||
local Rocktag = string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString)
|
||||
MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition)
|
||||
end
|
||||
self.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
if not self.GCI then
|
||||
MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true)
|
||||
MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition)
|
||||
end
|
||||
end
|
||||
else
|
||||
local AOCoordString = self.AOCoordinate:ToStringLLDDM()
|
||||
local Rocktag = string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString)
|
||||
MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition)
|
||||
end
|
||||
if not self.GCI then
|
||||
MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition)
|
||||
end
|
||||
end
|
||||
local stationtag = string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM())
|
||||
MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
if self.AllowMarkers then
|
||||
MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
|
||||
end
|
||||
end
|
||||
|
||||
if not self.GCI then
|
||||
|
||||
@@ -599,7 +599,80 @@ function BRIGADE:onafterStatus(From, Event, To)
|
||||
text=text..string.format("\n* %s: spawned=%s", asset.spawngroupname, tostring(asset.spawned))
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
end
|
||||
|
||||
if self.verbose>=3 then
|
||||
|
||||
-- Count numbers
|
||||
local Ntotal=0
|
||||
local Nspawned=0
|
||||
local Nrequested=0
|
||||
local Nreserved=0
|
||||
local Nstock=0
|
||||
|
||||
local text="\n===========================================\n"
|
||||
text=text.."Assets:"
|
||||
local legion=self --Ops.Legion#LEGION
|
||||
|
||||
for _,_cohort in pairs(legion.cohorts) do
|
||||
local cohort=_cohort --Ops.Cohort#COHORT
|
||||
|
||||
for _,_asset in pairs(cohort.assets) do
|
||||
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
|
||||
local state="In Stock"
|
||||
if asset.flightgroup then
|
||||
state=asset.flightgroup:GetState()
|
||||
local mission=legion:GetAssetCurrentMission(asset)
|
||||
if mission then
|
||||
state=state..string.format(", Mission \"%s\" [%s]", mission:GetName(), mission:GetType())
|
||||
end
|
||||
else
|
||||
if asset.spawned then
|
||||
env.info("FF ERROR: asset has opsgroup but is NOT spawned!")
|
||||
end
|
||||
if asset.requested and asset.isReserved then
|
||||
env.info("FF ERROR: asset is requested and reserved. Should not be both!")
|
||||
state="Reserved+Requested!"
|
||||
elseif asset.isReserved then
|
||||
state="Reserved"
|
||||
elseif asset.requested then
|
||||
state="Requested"
|
||||
end
|
||||
end
|
||||
|
||||
-- Text.
|
||||
text=text..string.format("\n[UID=%03d] %s Legion=%s [%s]: State=%s [RID=%s]",
|
||||
asset.uid, asset.spawngroupname, legion.alias, cohort.name, state, tostring(asset.rid))
|
||||
|
||||
|
||||
if asset.spawned then
|
||||
Nspawned=Nspawned+1
|
||||
end
|
||||
if asset.requested then
|
||||
Nrequested=Nrequested+1
|
||||
end
|
||||
if asset.isReserved then
|
||||
Nreserved=Nreserved+1
|
||||
end
|
||||
if not (asset.spawned or asset.requested or asset.isReserved) then
|
||||
Nstock=Nstock+1
|
||||
end
|
||||
|
||||
Ntotal=Ntotal+1
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
text=text.."\n-------------------------------------------"
|
||||
text=text..string.format("\nNstock = %d", Nstock)
|
||||
text=text..string.format("\nNreserved = %d", Nreserved)
|
||||
text=text..string.format("\nNrequested = %d", Nrequested)
|
||||
text=text..string.format("\nNspawned = %d", Nspawned)
|
||||
text=text..string.format("\nNtotal = %d (=%d)", Ntotal, Nstock+Nspawned+Nrequested+Nreserved)
|
||||
text=text.."\n==========================================="
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+2100
-319
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@
|
||||
-- @field #number Nsuccess Number of successful missions.
|
||||
-- @field #number Nfailure Number of failed mission.
|
||||
-- @field #table assetNumbers Asset numbers. Each entry is a table of data type `#CHIEF.AssetNumber`.
|
||||
-- @extends Ops.Intel#INTEL
|
||||
-- @extends Ops.Intelligence#INTEL
|
||||
|
||||
--- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower
|
||||
--
|
||||
@@ -48,7 +48,7 @@
|
||||
--
|
||||
-- # Territory
|
||||
--
|
||||
-- The chief class allows you to define boarder zones, conflict zones and attack zones.
|
||||
-- The chief class allows you to define border zones, conflict zones and attack zones.
|
||||
--
|
||||
-- ## Border Zones
|
||||
--
|
||||
@@ -332,7 +332,7 @@ CHIEF.Strategy = {
|
||||
|
||||
--- CHIEF class version.
|
||||
-- @field #string version
|
||||
CHIEF.version="0.6.1"
|
||||
CHIEF.version="0.7.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -396,7 +396,8 @@ function CHIEF:New(Coalition, AgentSet, Alias)
|
||||
self.TransportCategories = {Group.Category.HELICOPTER}
|
||||
|
||||
-- Create a new COMMANDER.
|
||||
self.commander=COMMANDER:New(Coalition)
|
||||
self.commander=COMMANDER:New(Coalition, Alias)
|
||||
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
@@ -2897,6 +2898,8 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target)
|
||||
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30))
|
||||
|
||||
else
|
||||
|
||||
-- Everything else
|
||||
|
||||
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100))
|
||||
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK, 50))
|
||||
@@ -2912,6 +2915,8 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target)
|
||||
---
|
||||
|
||||
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ANTISHIP, 100))
|
||||
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT, 50))
|
||||
table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30))
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Unknown Group category!")
|
||||
@@ -3353,6 +3358,15 @@ function CHIEF._CheckAssetProperties(Asset, Properties)
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Checks whether or not any of the legions con run a mission.
|
||||
-- @param #CHIEF self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
-- @return #boolean If `true`, one of the cohorts can run the mission.
|
||||
function CHIEF:CanMission(Mission)
|
||||
return self.commander and self.commander:CanMission(Mission)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -88,7 +88,7 @@ COHORT = {
|
||||
|
||||
--- COHORT class version.
|
||||
-- @field #string version
|
||||
COHORT.version="0.3.6"
|
||||
COHORT.version="0.3.7"
|
||||
|
||||
--- Global variable to store the unique(!) cohort names
|
||||
_COHORTNAMES={}
|
||||
@@ -100,6 +100,7 @@ _COHORTNAMES={}
|
||||
-- DONE: Create FLOTILLA class.
|
||||
-- DONE: Added check for properties.
|
||||
-- DONE: Make general so that PLATOON and SQUADRON can inherit this class.
|
||||
-- DONE: Better setting of call signs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -515,10 +516,12 @@ end
|
||||
-- @param #COHORT self
|
||||
-- @param #number Callsign Callsign from CALLSIGN.Aircraft, e.g. "Chevy" for CALLSIGN.Aircraft.CHEVY.
|
||||
-- @param #number Index Callsign index, Chevy-**1**.
|
||||
-- @param #string CallsignString (optional) Set this for tasks like TANKER, AWACS or KIOWA and the like, which have special names. E.g. "Darkstar" or "Roughneck".
|
||||
-- @return #COHORT self
|
||||
function COHORT:SetCallsign(Callsign, Index)
|
||||
function COHORT:SetCallsign(Callsign, Index, CallsignString)
|
||||
self.callsignName=Callsign
|
||||
self.callsignIndex=Index
|
||||
self.callsignClearName=CallsignString
|
||||
self.callsign={}
|
||||
self.callsign.NumberSquad=Callsign
|
||||
self.callsign.NumberGroup=Index
|
||||
@@ -626,32 +629,42 @@ end
|
||||
--- Remove assets from pool. Not that assets must not be spawned or already reserved or requested.
|
||||
-- @param #COHORT self
|
||||
-- @param #number N Number of assets to be removed. Default 1.
|
||||
-- @param #number Delay Delay in seconds before assets are removed.
|
||||
-- @return #COHORT self
|
||||
function COHORT:RemoveAssets(N)
|
||||
function COHORT:RemoveAssets(N, Delay)
|
||||
self:T2(self.lid..string.format("Remove %d assets of Cohort", N))
|
||||
|
||||
N=N or 1
|
||||
|
||||
local n=0
|
||||
for i=#self.assets,1,-1 do
|
||||
local asset=self.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
if Delay and Delay>0 then
|
||||
-- Delayed call
|
||||
self:ScheduleOnce(Delay, COHORT.RemoveAssets, self, N, 0)
|
||||
else
|
||||
|
||||
self:T2(self.lid..string.format("Checking removing asset %s", asset.spawngroupname))
|
||||
if not (asset.requested or asset.spawned or asset.isReserved) then
|
||||
self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname))
|
||||
table.remove(self.assets, i)
|
||||
n=n+1
|
||||
else
|
||||
self:T2(self.lid..string.format("Could NOT Remove asset %s", asset.spawngroupname))
|
||||
N=N or 1
|
||||
|
||||
local n=0
|
||||
for i=#self.assets,1,-1 do
|
||||
local asset=self.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
|
||||
self:T2(self.lid..string.format("Checking removing asset %s", asset.spawngroupname))
|
||||
if not (asset.requested or asset.spawned or asset.isReserved) then
|
||||
self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname))
|
||||
-- Remove from warehouse and warehouse DB
|
||||
asset.legion:_DeleteStockItem(asset)
|
||||
table.remove(self.assets, i)
|
||||
n=n+1
|
||||
else
|
||||
self:T2(self.lid..string.format("Could NOT Remove asset %s", asset.spawngroupname))
|
||||
end
|
||||
|
||||
if n>=N then
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if n>=N then
|
||||
break
|
||||
end
|
||||
self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d", n, N, #self.assets))
|
||||
|
||||
end
|
||||
|
||||
self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d", n, N, #self.assets))
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -679,7 +692,16 @@ end
|
||||
function COHORT:GetCallsign(Asset)
|
||||
|
||||
if self.callsignName then
|
||||
|
||||
--[[
|
||||
["callsign"] =
|
||||
{
|
||||
[2] = 1,
|
||||
["name"] = "Darkstar11",
|
||||
[3] = 1,
|
||||
[1] = 5,
|
||||
[4] = "Darkstar11",
|
||||
}, -- end of ["callsign"]
|
||||
]]
|
||||
Asset.callsign={}
|
||||
|
||||
for i=1,Asset.nunits do
|
||||
@@ -695,12 +717,16 @@ function COHORT:GetCallsign(Asset)
|
||||
else
|
||||
self.callsigncounter=self.callsigncounter+1
|
||||
end
|
||||
callsign["name"] = self.callsignClearName or UTILS.GetCallsignName(self.callsignName) or "None"
|
||||
callsign["name"] = string.format("%s%d%d",callsign["name"],callsign[2],callsign[3])
|
||||
callsign[4] = callsign["name"]
|
||||
|
||||
Asset.callsign[i]=callsign
|
||||
|
||||
self:T3({callsign=callsign})
|
||||
|
||||
--TODO: there is also a table entry .name, which is a string.
|
||||
--DONE: there is also a table entry .name, which is a string.
|
||||
--UTILS.PrintTableToLog(callsign)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -1748,6 +1748,70 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation)
|
||||
return cohorts
|
||||
end
|
||||
|
||||
--- Checks whether or not any of the legions con run a mission.
|
||||
-- @param #COMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
-- @return #boolean If `true`, one of the cohorts can run the mission.
|
||||
function COMMANDER:CanMission(Mission)
|
||||
local commander = self
|
||||
|
||||
-- Target position.
|
||||
local TargetVec2 = Mission:GetTargetVec2()
|
||||
|
||||
local MaxWeight = nil
|
||||
|
||||
if Mission.NcarriersMin then
|
||||
|
||||
local legions = commander.legions
|
||||
local cohorts = nil
|
||||
if Mission.transportLegions or Mission.transportCohorts then
|
||||
legions = Mission.transportLegions
|
||||
cohorts = Mission.transportCohorts
|
||||
end
|
||||
|
||||
-- Get transport cohorts.
|
||||
local Cohorts = LEGION._GetCohorts(legions, cohorts)
|
||||
|
||||
-- Filter cohorts that can actually perform transport missions.
|
||||
local transportcohorts = {}
|
||||
for _, _cohort in pairs(Cohorts) do
|
||||
local cohort = _cohort --Ops.Cohort#COHORT
|
||||
|
||||
-- Check if cohort can perform transport to target.
|
||||
local can = LEGION._CohortCan(cohort, AUFTRAG.Type.OPSTRANSPORT, Mission.carrierCategories, Mission.carrierAttributes, Mission.carrierProperties, nil, TargetVec2)
|
||||
|
||||
-- MaxWeight of cargo assets is limited by the largets available cargo bay. We don't want to select, e.g., tanks that cannot be transported by APCs or helos.
|
||||
if can and (MaxWeight == nil or cohort.cargobayLimit > MaxWeight) then
|
||||
MaxWeight = cohort.cargobayLimit
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local legions = commander.legions
|
||||
local cohorts = nil
|
||||
if Mission.specialLegions or Mission.specialCohorts then
|
||||
legions = Mission.specialLegions
|
||||
cohorts = Mission.specialCohorts
|
||||
end
|
||||
|
||||
-- Get cohorts.
|
||||
local Cohorts = LEGION._GetCohorts(legions, cohorts, Mission.operation, commander.opsqueue)
|
||||
|
||||
for _, _cohort in pairs(Cohorts) do
|
||||
local cohort = _cohort --Ops.Cohort#COHORT
|
||||
|
||||
-- Check if cohort can do the mission.
|
||||
local can = LEGION._CohortCan(cohort, Mission.type, nil, Mission.attributes, Mission.properties, { Mission.engageWeaponType }, TargetVec2, Mission.engageRange, Mission.refuelSystem, nil, MaxWeight)
|
||||
if can then
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Recruit assets for a given mission.
|
||||
-- @param #COMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
@@ -0,0 +1,950 @@
|
||||
--- **Ops** - Create your A2G Defenses.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Automatically create and manage A2G defenses using an AirWing and Squadrons for one coalition
|
||||
-- * Easy set-up
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Easy A2G Engagement Class, based on OPS classes
|
||||
-------------------------------------------------------------------------
|
||||
--
|
||||
-- ## Documentation:
|
||||
--
|
||||
-- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyAG.html
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/EasyAG).
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Date: Dec 2025
|
||||
-- Last Update: Jan 2026
|
||||
-------------------------------------------------------------------------
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Automatically create and manage A2G defenses using an AirWing and Squadrons for one coalition
|
||||
-- * Easy set-up
|
||||
-- * Add additional AirWings on other airbases
|
||||
-- * Each wing can have more than one Squadron - tasking to Squadrons is done on a random basis per AirWing
|
||||
-- * Create borders and zones of engagement
|
||||
-- * Detection can be ground based and/or via AWACS
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### AUTHOR: **applevangelist**
|
||||
--
|
||||
-- @module Ops.EasyAG
|
||||
-- @image AI_Air_To_Ground_Dispatching.JPG
|
||||
|
||||
|
||||
--- EASYA2G Class
|
||||
-- @type EASYA2G
|
||||
-- @field #string ClassName
|
||||
-- @field #number overhead
|
||||
-- @field #number engagerange
|
||||
-- @field #number capgrouping
|
||||
-- @field #string airbasename
|
||||
-- @field Wrapper.Airbase#AIRBASE airbase
|
||||
-- @field #number coalition
|
||||
-- @field #string alias
|
||||
-- @field #table wings
|
||||
-- @field Ops.Intel#INTEL Intel
|
||||
-- @field #number resurrection
|
||||
-- @field #number capspeed
|
||||
-- @field #number capalt
|
||||
-- @field #number capdir
|
||||
-- @field #number capleg
|
||||
-- @field #number maxinterceptsize
|
||||
-- @field #number missionrange
|
||||
-- @field #number noalert5
|
||||
-- @field #table ManagedAW
|
||||
-- @field #table ManagedSQ
|
||||
-- @field #table ManagedCP
|
||||
-- @field #table ManagedTK
|
||||
-- @field #table ManagedEWR
|
||||
-- @field #table ManagedREC
|
||||
-- @field #number MaxAliveMissions
|
||||
-- @field #boolean debug
|
||||
-- @field #number repeatsonfailure
|
||||
-- @field Core.Set#SET_ZONE GoZoneSet
|
||||
-- @field Core.Set#SET_ZONE NoGoZoneSet
|
||||
-- @field Core.Set#SET_ZONE ConflictZoneSet
|
||||
-- @field #boolean Monitor
|
||||
-- @field #boolean TankerInvisible
|
||||
-- @field #number CapFormation
|
||||
-- @field #table ReadyFlightGroups
|
||||
-- @field #boolean DespawnAfterLanding
|
||||
-- @field #boolean DespawnAfterHolding
|
||||
-- @field #list<Ops.Auftrag#AUFTRAG> ListOfAuftrag
|
||||
-- @field #string defaulttakeofftype Take off type
|
||||
-- @field #number FuelLowThreshold
|
||||
-- @field #number FuelCriticalThreshold
|
||||
-- @field #boolean showpatrolpointmarks
|
||||
-- @field #table EngageTargetTypes
|
||||
-- @extends Ops.EasyGCICAP#EASYGCICAP
|
||||
|
||||
--- *“High-Threat Close-Air-Support is a Myth.”* -- Mike “Starbaby” Pietrucha.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The EasyAG Concept
|
||||
--
|
||||
-- The idea of this class is partially to make the OPS classes easier operational for an A2G defense network, and to replace the legacy AI_A2G_Dispatcher system - not to it's
|
||||
-- full extent, but make a basic system work very quickly.
|
||||
--
|
||||
-- # Setup
|
||||
--
|
||||
-- ## Basic understanding
|
||||
--
|
||||
-- The basics are, there is **one** and only **one** AirWing per airbase. Each AirWing has **at least** one Squadron, who will do A2G tasks. Squadrons will be randomly chosen for the task at hand.
|
||||
-- Each AirWing has **at least** one Conflict Zone that it manages. COnflict Zones will be covered by the AirWing automatically as long as airframes are available. Detected enemy ground forces will be assigned to **one**
|
||||
-- AirWing based on proximity (that is, if you have more than one).
|
||||
--
|
||||
-- ## Assignment of tasks for enemies
|
||||
--
|
||||
-- An exisiting plane or a newly spawned plane will take care of the intruders. Standard overhead is 0.1, i.e. a group of 10 intrudes will
|
||||
-- be managed by one planes from the assigned AirWing. There is an maximum missions limitation per AirWing, so we do not spam the skies.
|
||||
--
|
||||
-- ## Basic set-up code
|
||||
--
|
||||
-- ### Prerequisites
|
||||
--
|
||||
-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!**
|
||||
-- E.g. for Kutaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
|
||||
-- Next put a late activated template group for your A2G Squadron on the map. Last, put a zone on the map for the Defense operations, let's name it "Blue Zone 1". Size of the zone plays no role.
|
||||
-- Put a scout system on the map and name it aptly, like "Blue SCOUT".
|
||||
--
|
||||
-- ### Zones
|
||||
--
|
||||
-- For our example, you create a RED and a BLUE border, as a closed polygonal zone representing the borderlines. You can also have conflict zone, where - for our example - BLUE will attack
|
||||
-- RED groups, despite being on or close to RED territory. Think of a no-fly zone or an limited area of engagement. Conflict zones take precedence over borders, i.e. they can overlap all borders.
|
||||
--
|
||||
-- ### Code it
|
||||
--
|
||||
-- -- Set up a basic system for the blue side, we'll reside on Kutaisi, and use GROUP objects with "Blue SCOUT" in the name as Detecting Systems.
|
||||
-- local mywing = EASYA2G:New("A2G",AIRBASE.Caucasus.Kutaisi,"blue","SCOUT")
|
||||
--
|
||||
-- -- Add a holding/ingress point belonging to our airbase, we'll be at 5k ft doing 250 kn, initial direction 225 degrees (West), leg 5NM
|
||||
-- -- This will effectively be the ingress coordinate into the cnflict zone
|
||||
-- local Coordinate = ZONE:New("A2G Loitering"):GetCoordinate()
|
||||
-- mywing:AddHoldingPointA2G(AIRBASE.Caucasus.Kutaisi,Coordinate,5000,250,225,5)
|
||||
--
|
||||
-- -- Add a recon point over the conflict zone, we'll use a reaper for recon
|
||||
-- local Coordinate2 = ZONE:New("A2G Recon"):GetCoordinate()
|
||||
-- mywing:AddPatrolPointRecon(AIRBASE.Caucasus.Kutaisi,Coordinate2,15000,225,225,5)
|
||||
--
|
||||
-- -- Add three Squadrons with templates "Hero 1" and "Hero 2", 20 airframes, skill as set
|
||||
-- mywing:AddSquadron("A2G Flight", "Hero 1", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.GOOD, Modex, Livery)
|
||||
-- mywing:AddSquadron("A2G Helo", "Hero 2", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.HIGH, Modex, Livery)
|
||||
-- mywing:AddReconSquadron("Recon Drone", "SpyInTheSky SCOUT", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.EXCELLENT, Modex, Livery)
|
||||
--
|
||||
-- -- Ensure our reaper doesn't get immediately killed
|
||||
-- mywing:SetTankerAndScoutsInvisible(true)
|
||||
--
|
||||
-- -- Add a couple of zones
|
||||
-- -- We'll defend our own border
|
||||
-- mywing:AddAcceptZone(ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) ))
|
||||
-- -- We'll attack intruders also here - conflictzones can overlap borders(!) - limited zone of engagement
|
||||
-- mywing:AddConflictZone(ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" )))
|
||||
-- -- We'll leave the reds alone on their turf
|
||||
-- mywing:AddRejectZone(ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) ))
|
||||
--
|
||||
-- -- Optional - Draw the borders on the map so we see what's going on
|
||||
-- -- Set up borders on map
|
||||
-- local BlueBorder = ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) )
|
||||
-- BlueBorder:DrawZone(-1,{0,0,1},1,FillColor,FillAlpha,1,true)
|
||||
-- local ConflictZone = ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" ))
|
||||
-- ConflictZone:DrawZone(-1,{1,1,0},1,FillColor,FillAlpha,2,true)
|
||||
-- local BlueNoGoZone = ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) )
|
||||
-- BlueNoGoZone:DrawZone(-1,{1,0,0},1,FillColor,FillAlpha,4,true)
|
||||
--
|
||||
-- ### Add a second airwing with squads and own patrol point (optional)
|
||||
--
|
||||
-- -- Set this up at Sukhumi
|
||||
-- mywing:AddAirwing(AIRBASE.Caucasus.Sukhumi_Babushara,"Blue A2G Sukhumi")
|
||||
-- -- A2G Point "Blue Zone 2"
|
||||
-- mywing:AddPatrolPointA2G(AIRBASE.Caucasus.Sukhumi_Babushara,ZONE:FindByName("Blue Zone 2"):GetCoordinate(),30000,400,90,20)
|
||||
--
|
||||
-- -- This one has two squadrons to choose from
|
||||
-- mywing:AddSquadron("Blue Sq3 F16","A2G Sukhumi II",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,402,"JASDF 6th TFS 43-8526 Skull Riders")
|
||||
-- mywing:AddSquadron("Blue Sq2 F15","A2G Sukhumi I",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,202,"390th Fighter SQN")
|
||||
--
|
||||
-- ### Add a tanker (optional)
|
||||
--
|
||||
-- -- **Note** If you need different tanker types, i.e. Boom and Drogue, set them up at different AirWings!
|
||||
-- -- Add a tanker point
|
||||
-- mywing:AddPatrolPointTanker(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone Tanker"):GetCoordinate(),20000,280,270,50)
|
||||
-- -- Add a tanker squad - Radio 251 AM, TACAN 51Y
|
||||
-- mywing:AddTankerSquadron("Blue Tanker","Tanker Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.EXCELLENT,602,nil,251,radio.modulation.AM,51)
|
||||
--
|
||||
-- ### Add an AWACS (optional)
|
||||
--
|
||||
-- -- Add an AWACS point
|
||||
-- mywing:AddPatrolPointAwacs(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone AWACS"):GetCoordinate(),25000,300,270,50)
|
||||
-- -- Add an AWACS squad - Radio 251 AM, TACAN 51Y
|
||||
-- mywing:AddAWACSSquadron("Blue AWACS","AWACS Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.AVERAGE,702,nil,271,radio.modulation.AM)
|
||||
--
|
||||
-- # Fine-Tuning
|
||||
--
|
||||
-- ## Change Defaults
|
||||
--
|
||||
-- * @{#EASYA2G.SetDefaultResurrection}: Set how many seconds the AirWing stays inoperable after the AirWing STATIC HQ ist destroyed, default 900 secs.
|
||||
-- * @{#EASYA2G.SetDefaultA2GSpeed}: Set how many knots the A2G flights should do (will be altitude corrected), default 225 kn.
|
||||
-- * @{#EASYA2G.SetDefaultA2GAlt}: Set at which altitude (ASL) the A2G planes will fly, default 10,000 ft.
|
||||
-- * @{#EASYA2G.SetDefaultA2GDirection}: Set the initial direction from the A2G point the planes will fly in degrees, default is 90°.
|
||||
-- * @{#EASYA2G.SetDefaultA2GLeg}: Set the length of the A2G leg, default is 5 NM.
|
||||
-- * @{#EASYA2G.SetDefaultA2GGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 1.
|
||||
-- * @{#EASYA2G.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100.
|
||||
-- * @{#EASYA2G.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
|
||||
-- * @{#EASYA2G.SetDefaultEngageRange}: Set max engage range for A2G flights if they detect intruders, defaults to 50.
|
||||
-- * @{#EASYA2G.SetMaxAliveMissions}: Set max parallel missions can be done (A2G+GCI+Alert5+Tanker+AWACS), defaults to 8.
|
||||
-- * @{#EASYA2G.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3.
|
||||
-- * @{#EASYA2G.SetTankerAndScoutsInvisible}: Set Tanker and Scouts to be invisible to enemy AI eyes. Is set to `true` by default.
|
||||
--
|
||||
-- ## Debug and Monitor
|
||||
--
|
||||
-- mywing.debug = true -- log information
|
||||
-- mywing.Monitor = true -- show some statistics on screen
|
||||
--
|
||||
--
|
||||
-- @field #EASYA2G
|
||||
EASYA2G = {
|
||||
ClassName = "EASYA2G",
|
||||
overhead = 0.2,
|
||||
capgrouping = 1,
|
||||
airbasename = nil,
|
||||
airbase = nil,
|
||||
coalition = "blue",
|
||||
alias = nil,
|
||||
wings = {},
|
||||
Intel = nil,
|
||||
resurrection = 900,
|
||||
capspeed = 300,
|
||||
capalt = 25000,
|
||||
capdir = 45,
|
||||
capleg = 5,
|
||||
maxinterceptsize = 2,
|
||||
missionrange = 100,
|
||||
noalert5 = 2,
|
||||
ManagedAW = {},
|
||||
ManagedSQ = {},
|
||||
ManagedCP = {},
|
||||
ManagedTK = {},
|
||||
ManagedEWR = {},
|
||||
ManagedREC = {},
|
||||
MaxAliveMissions = 8,
|
||||
debug = false,
|
||||
engagerange = 50,
|
||||
repeatsonfailure = 3,
|
||||
GoZoneSet = nil,
|
||||
NoGoZoneSet = nil,
|
||||
ConflictZoneSet = nil,
|
||||
Monitor = false,
|
||||
TankerInvisible = true,
|
||||
CapFormation = nil,
|
||||
ReadyFlightGroups = {},
|
||||
DespawnAfterLanding = false,
|
||||
DespawnAfterHolding = true,
|
||||
ListOfAuftrag = {},
|
||||
defaulttakeofftype = "hot",
|
||||
FuelLowThreshold = 25,
|
||||
FuelCriticalThreshold = 10,
|
||||
showpatrolpointmarks = false,
|
||||
EngageTargetTypes = {"Ground"},
|
||||
}
|
||||
|
||||
--- Internal Squadron data type
|
||||
-- @type EASYA2G.Squad
|
||||
-- @field #string TemplateName
|
||||
-- @field #string SquadName
|
||||
-- @field #string AirbaseName
|
||||
-- @field #number AirFrames
|
||||
-- @field #string Skill
|
||||
-- @field #string Modex
|
||||
-- @field #string Livery
|
||||
-- @field #boolean Tanker
|
||||
-- @field #boolean AWACS
|
||||
-- @field #boolean RECON
|
||||
-- @field #number Frequency
|
||||
-- @field #number Modulation
|
||||
-- @field #number TACAN
|
||||
|
||||
--- Internal Wing data type
|
||||
-- @type EASYA2G.Wing
|
||||
-- @field #string AirbaseName
|
||||
-- @field #string Alias
|
||||
-- @field #string CapZoneName
|
||||
|
||||
--- Internal CapPoint data type
|
||||
-- @type EASYA2G.CapPoint
|
||||
-- @field #string AirbaseName
|
||||
-- @field Core.Point#COORDINATE Coordinate
|
||||
-- @field #number Altitude
|
||||
-- @field #number Speed
|
||||
-- @field #number Heading
|
||||
-- @field #number LegLength
|
||||
-- @field Core.Zone#ZONE_BASE Zone
|
||||
|
||||
--- EASYA2G class version.
|
||||
-- @field #string version
|
||||
EASYA2G.version="0.1.4"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: TBD
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new GCIA2G Manager
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string Alias A Name for this A2G Setup
|
||||
-- @param #string AirbaseName Name of the Home Airbase
|
||||
-- @param #string Coalition Coalition, e.g. "blue" or "red"
|
||||
-- @param #string ScoutName (Partial) group name of the detection system of the coalition, e.g. "Red SCOUT", can be handed in as table of names, e.g.{"SCOUT","DRONE","SAM"}
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName)
|
||||
-- Inherit everything from FSM class.
|
||||
|
||||
local self=BASE:Inherit(self, EASYGCICAP:New(Alias, AirbaseName, Coalition, ScoutName)) -- #EASYA2G
|
||||
|
||||
-- defaults
|
||||
self.alias = Alias or AirbaseName.." A2G Wing"
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("EASYA2G %s | ", self.alias)
|
||||
|
||||
self.coalitionname = string.lower(Coalition) or "blue"
|
||||
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.wings = {}
|
||||
if type(ScoutName) == "string" then ScoutName = {ScoutName} end
|
||||
self.EWRName = ScoutName --or self.coalitionname.." EWR"
|
||||
--self.CapZoneName = CapZoneName
|
||||
self.airbasename = AirbaseName
|
||||
self.airbase = AIRBASE:FindByName(self.airbasename)
|
||||
self.GoZoneSet = SET_ZONE:New()
|
||||
self.NoGoZoneSet = SET_ZONE:New()
|
||||
self.ConflictZoneSet = SET_ZONE:New()
|
||||
self.resurrection = 900
|
||||
self.capspeed = 225
|
||||
self.capalt = 5000
|
||||
self.capdir = 90
|
||||
self.capleg = 5
|
||||
self.capgrouping = 2
|
||||
self.missionrange = 100
|
||||
self.noalert5 = 2
|
||||
self.MaxAliveMissions = 8
|
||||
self.engagerange = 50
|
||||
self.repeatsonfailure = 3
|
||||
self.Monitor = false
|
||||
self.TankerInvisible = true
|
||||
self.CapFormation = ENUMS.Formation.FixedWing.FingerFour.Group
|
||||
self.DespawnAfterLanding = false
|
||||
self.DespawnAfterHolding = true
|
||||
self.ListOfAuftrag = {}
|
||||
self.defaulttakeofftype = "hot"
|
||||
self.FuelLowThreshold = 25
|
||||
self.FuelCriticalThreshold = 10
|
||||
self.showpatrolpointmarks = false
|
||||
self.EngageTargetTypes = {"Ground"}
|
||||
self:SetDefaultTurnoverTime()
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:SetStartState("Stopped")
|
||||
self:AddTransition("Stopped", "Start", "Running")
|
||||
self:AddTransition("Running", "Stop", "Stopped")
|
||||
self:AddTransition("*", "Status", "*")
|
||||
|
||||
--- On Before "Start" event.
|
||||
-- @function [parent=#EASYA2G] OnBeforeStart
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "Start" event.
|
||||
-- @function [parent=#EASYA2G] OnAfterStart
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On Before "Status" event.
|
||||
-- @function [parent=#EASYA2G] OnBeforeStatus
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "Status" event.
|
||||
-- @function [parent=#EASYA2G] OnAfterStatus
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
self:AddAirwing(self.airbasename,self.alias,self.CapZoneName)
|
||||
|
||||
self:I(self.lid.."Created new instance (v"..self.version..")")
|
||||
|
||||
self:__Start(math.random(6,12))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- Functions
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
--- Set Tanker and Scouts to be invisible to enemy AI eyes
|
||||
-- @param #EASYA2G self
|
||||
-- @param #boolean Switch Set to true or false, by default this is set to true already
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetTankerAndScoutsInvisible(Switch)
|
||||
self:T(self.lid.."SetTankerAndScoutsInvisible")
|
||||
self.TankerInvisible = Switch
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default A2G Speed in knots
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Speed Speed defaults to 300
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GSpeed(Speed)
|
||||
self:T(self.lid.."SetDefaultSpeed")
|
||||
self.capspeed = Speed or 300
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set A2G Flight formation.
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetA2GFormation(Formation)
|
||||
self:T(self.lid.."SetA2GFormation")
|
||||
self.CapFormation = Formation
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default A2G Altitude in feet
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Altitude Altitude defaults to 25000
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GAlt(Altitude)
|
||||
self:T(self.lid.."SetDefaultAltitude")
|
||||
self.capalt = Altitude or 25000
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default A2G lieg initial direction in degrees
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Direction Direction defaults to 90 (East)
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GDirection(Direction)
|
||||
self:T(self.lid.."SetDefaultDirection")
|
||||
self.capdir = Direction or 90
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default leg length in NM
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Leg Leg defaults to 5
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GLeg(Leg)
|
||||
self:T(self.lid.."SetDefaultLeg")
|
||||
self.capleg = Leg or 5
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default grouping, i.e. how many airplanes per A2G point
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Grouping Grouping defaults to 2
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GGrouping(Grouping)
|
||||
self:T(self.lid.."SetDefaultA2GGrouping")
|
||||
self.capgrouping = Grouping or 2
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set A2G mission start to vary randomly between Start end End seconds.
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Start
|
||||
-- @param #number End
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetA2GStartTimeVariation(Start, End)
|
||||
self.capOptionVaryStartTime = Start or 5
|
||||
self.capOptionVaryEndTime = End or 60
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set which target types A2G flights will prefer to engage, defaults to {"Ground"}
|
||||
-- @param #EASYA2G self
|
||||
-- @param #table types Table of comma separated #string entries, defaults to {"Ground"} (everything that is ground and is not a weapon). Useful other options are e.g. {"Armored vehicles"}, {"Tanks"},
|
||||
-- or {"APC"} or combinations like {"APC", "Tanks", "Artillery"}. See [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes).
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetA2GEngageTargetTypes(types)
|
||||
self.EngageTargetTypes = types or {"Ground"}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a A2G patrol/holding point to a Wing
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string AirbaseName Name of the Wing's airbase
|
||||
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Defaults to 25000 feet ASL.
|
||||
-- @param #number Speed Defaults to 300 knots TAS.
|
||||
-- @param #number Heading Defaults to 90 degrees (East).
|
||||
-- @param #number LegLength Defaults to 15 NM.
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
|
||||
self:T(self.lid.."AddHoldingPointA2G")--..Coordinate:ToStringLLDDM())
|
||||
local coordinate = Coordinate
|
||||
local EntryCAP = {} -- #EASYGCICAP.CapPoint
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
-- adjust coordinate and get the coordinate from the zone
|
||||
coordinate = Coordinate:GetCoordinate()
|
||||
EntryCAP.Zone = Coordinate
|
||||
end
|
||||
EntryCAP.AirbaseName = AirbaseName
|
||||
EntryCAP.Coordinate = coordinate
|
||||
EntryCAP.Altitude = Altitude or 25000
|
||||
EntryCAP.Speed = Speed or 300
|
||||
EntryCAP.Heading = Heading or 90
|
||||
EntryCAP.LegLength = LegLength or 5
|
||||
self.ManagedCP[#self.ManagedCP+1] = EntryCAP
|
||||
if self.debug then
|
||||
local mark = MARKER:New(coordinate,self.lid.."Holding Point"):ToAll()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- (Internal) Add a Squadron to an Airwing of the manager
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string TemplateName Name of the group template.
|
||||
-- @param #string SquadName Squadron name - must be unique!
|
||||
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
|
||||
-- @param #number AirFrames Number of available airframes, e.g. 20.
|
||||
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
|
||||
-- @param #string Modex (optional) Modex to be used,e.g. 402.
|
||||
-- @param #string Livery (optional) Livery name to be used.
|
||||
-- @param #number Frequency (optional) Radio Frequency to be used.
|
||||
-- @param #number Modulation (optional) Radio Modulation to be used, e.g. radio.modulation.AM or radio.modulation.FM
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery, Frequency, Modulation)
|
||||
self:T(self.lid.."_AddSquadron "..SquadName)
|
||||
-- Add Squadrons
|
||||
local Squadron_One = SQUADRON:New(TemplateName,AirFrames,SquadName)
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.CAS, AUFTRAG.Type.CASENHANCED, AUFTRAG.Type.BAI, AUFTRAG.Type.ALERT5, AUFTRAG.Type.BOMBING, AUFTRAG.Type.STRIKE})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
Squadron_One:SetMissionRange(self.missionrange)
|
||||
|
||||
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
|
||||
|
||||
wing:AddSquadron(Squadron_One)
|
||||
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAS, AUFTRAG.Type.CASENHANCED, AUFTRAG.Type.BAI, AUFTRAG.Type.ALERT5, AUFTRAG.Type.BOMBING, AUFTRAG.Type.STRIKE},75)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Try to assign the intercept to a FlightGroup already in air and ready.
|
||||
-- @param #EASYA2G self
|
||||
-- @param #table ReadyFlightGroups ReadyFlightGroups
|
||||
-- @param Ops.Auftrag#AUFTRAG Auftrag The Auftrag
|
||||
-- @param Wrapper.Group#GROUP Group The Target
|
||||
-- @param #number WingSize Calculated number of Flights
|
||||
-- @return #boolean assigned
|
||||
-- @return #number leftover
|
||||
function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize)
|
||||
self:T("_TryAssignMission for size "..WingSize or 1)
|
||||
local assigned = false
|
||||
local wingsize = WingSize or 1
|
||||
local mindist = 0
|
||||
local disttable = {}
|
||||
if Group and Group:IsAlive() then
|
||||
local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0)
|
||||
self:T(self.lid..string.format("Assignment for %s",Group:GetName()))
|
||||
for _name,_FG in pairs(ReadyFlightGroups or {}) do
|
||||
local FG = _FG -- Ops.FlightGroup#FLIGHTGROUP
|
||||
local fcoord = FG:GetCoordinate()
|
||||
local dist = math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1))
|
||||
self:T(self.lid..string.format("FG %s Distance %dkm",_name,dist))
|
||||
disttable[#disttable+1] = { FG=FG, dist=dist}
|
||||
if dist>mindist then mindist=dist end
|
||||
end
|
||||
|
||||
local function sortDistance(a, b)
|
||||
return a.dist < b.dist
|
||||
end
|
||||
|
||||
table.sort(disttable, sortDistance)
|
||||
|
||||
for _,_entry in ipairs(disttable) do
|
||||
local FG = _entry.FG -- Ops.FlightGroup#FLIGHTGROUP
|
||||
FG:AddMission(Auftrag)
|
||||
local cm = FG:GetMissionCurrent()
|
||||
if cm then cm:Cancel() end
|
||||
wingsize = wingsize - 1
|
||||
self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
|
||||
if wingsize == 0 then
|
||||
assigned = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return assigned, wingsize
|
||||
end
|
||||
|
||||
--- Find a holding point closest to the group to be attacked (if any set)
|
||||
-- @param #EASYA2G self
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return Core.Point#COORDINATE Point (can be nil!)
|
||||
function EASYA2G:_GetClosestHoldingPoint(Group)
|
||||
local point = nil
|
||||
local mindist = 0
|
||||
if Group and Group:IsAlive() then
|
||||
local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0)
|
||||
for _,_data in pairs(self.ManagedCP or {}) do
|
||||
local data = _data -- #EASYGCICAP.CapPoint
|
||||
--data.Coordinate
|
||||
local dist = math.floor(UTILS.Round(data.Coordinate:Get2DDistance(gcoord)/1000,1))
|
||||
self:T(self.lid..string.format("Holding Point Distance %dkm",dist))
|
||||
if dist>mindist then
|
||||
mindist=dist
|
||||
point=data.Coordinate
|
||||
end
|
||||
end
|
||||
end
|
||||
return point
|
||||
end
|
||||
|
||||
--- Here, we'll decide if we need to launch an attacking flight, and from where
|
||||
-- @param #EASYA2G self
|
||||
-- @param Ops.Intel#INTEL.Cluster Cluster
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:_AssignMission(Cluster)
|
||||
self:I(self.lid.."_AssignMission")
|
||||
-- Here, we'll decide if we need to launch an attacking flight, and from where
|
||||
local overhead = self.overhead
|
||||
local capspeed = self.capspeed + 100
|
||||
local capalt = self.capalt or 5000
|
||||
local maxsize = self.maxinterceptsize
|
||||
local repeatsonfailure = self.repeatsonfailure
|
||||
|
||||
local wings = self.wings
|
||||
local ctlpts = self.ManagedCP
|
||||
local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping
|
||||
local nogozoneset = self.NoGoZoneSet
|
||||
local conflictzoneset = self.ConflictZoneSet
|
||||
local ReadyFlightGroups = self.ReadyFlightGroups
|
||||
|
||||
-- Aircraft?
|
||||
if Cluster.ctype == INTEL.Ctype.AIRCRAFT then return end
|
||||
-- Threatlevel 0..10
|
||||
local contact = self.Intel:GetHighestThreatContact(Cluster)
|
||||
local name = contact.groupname --#string
|
||||
local threat = contact.threatlevel --#number
|
||||
local position = self.Intel:CalcClusterFuturePosition(Cluster,300)
|
||||
-- calculate closest zone
|
||||
local bestdistance = 2000*1000 -- 2000km
|
||||
local targetairwing = nil -- Ops.Airwing#AIRWING
|
||||
local targetawname = "" -- #string
|
||||
local clustersize = self.Intel:ClusterCountUnits(Cluster) or 1
|
||||
local wingsize = math.abs(overhead * (clustersize+1))
|
||||
if wingsize > maxsize then wingsize = maxsize end
|
||||
-- existing mission, and if so - done?
|
||||
local retrymission = true
|
||||
if Cluster.mission and (not Cluster.mission:IsOver()) then
|
||||
retrymission = false
|
||||
end
|
||||
if (retrymission) and (wingsize >= 1) then
|
||||
MESSAGE:New(string.format("**** %s Attackers need wingsize %d", UTILS.GetCoalitionName(self.coalition), wingsize),15,"A2G"):ToAllIf(self.debug):ToLog()
|
||||
for _,_data in pairs (wings) do
|
||||
local airwing = _data[1] -- Ops.Airwing#AIRWING
|
||||
local zone = _data[2] -- Core.Zone#ZONE
|
||||
local zonecoord = zone:GetCoordinate()
|
||||
local name = _data[3] -- #string
|
||||
local coa = AIRBASE:FindByName(name):GetCoalition()
|
||||
local distance = position:DistanceFromPointVec2(zonecoord)
|
||||
local airframes = airwing:CountAssets(true)
|
||||
local samecoalitionab = coa == self.coalition and true or false
|
||||
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
|
||||
bestdistance = distance
|
||||
targetairwing = airwing
|
||||
targetawname = name
|
||||
end
|
||||
end
|
||||
for _,_data in pairs (ctlpts) do
|
||||
--local airwing = _data[1] -- Ops.Airwing#AIRWING
|
||||
--local zone = _data[2] -- Core.Zone#ZONE
|
||||
--local zonecoord = zone:GetCoordinate()
|
||||
--local name = _data[3] -- #string
|
||||
|
||||
local data = _data -- #EASYGCICAP.CapPoint
|
||||
local name = data.AirbaseName
|
||||
local zonecoord = data.Coordinate
|
||||
if data.Zone then
|
||||
-- refresh coordinate in case we have a (moving) zone
|
||||
zonecoord = data.Zone:GetCoordinate()
|
||||
end
|
||||
local airwing = wings[name][1]
|
||||
local coa = AIRBASE:FindByName(name):GetCoalition()
|
||||
local samecoalitionab = coa == self.coalition and true or false
|
||||
local distance = position:DistanceFromPointVec2(zonecoord)
|
||||
local airframes = airwing:CountAssets(true)
|
||||
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
|
||||
bestdistance = distance
|
||||
targetairwing = airwing -- Ops.Airwing#AIRWING
|
||||
targetawname = name
|
||||
end
|
||||
end
|
||||
local text = string.format("Closest Airwing is %s", targetawname)
|
||||
local m = MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog()
|
||||
-- Do we have a matching airwing?
|
||||
if targetairwing then
|
||||
local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort)
|
||||
local missioncount = self:_CountAliveAuftrags()
|
||||
-- Enough airframes on mission already?
|
||||
self:T(self.lid.." Assets on Mission "..AssetCount)
|
||||
if missioncount < MaxAliveMissions then
|
||||
local repeats = repeatsonfailure
|
||||
local Vec1 = contact.group:GetVec2()
|
||||
local Vec2 = targetairwing:GetVec2()
|
||||
--local HoldingVec2 = UTILS.FindNearestPointOnCircle(Vec1,UTILS.NMToMeters(10),Vec2)
|
||||
local IngressCoordinate = self:_GetClosestHoldingPoint(contact.group)
|
||||
if IngressCoordinate == nil then
|
||||
local IngressVec2 = UTILS.FindNearestPointOnCircle(Vec1,UTILS.NMToMeters(10),Vec2)
|
||||
IngressCoordinate = COORDINATE:NewFromVec2(IngressVec2)
|
||||
end
|
||||
local InterceptAuftrag = AUFTRAG:NewBAI(contact.group,capalt)
|
||||
:SetMissionRange(150)
|
||||
:SetPriority(1,true,1)
|
||||
:SetRepeatDelay(300)
|
||||
--:SetRequiredAssets(wingsize)
|
||||
:SetRepeatOnFailure(repeats)
|
||||
:SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt))
|
||||
:SetMissionAltitude(capalt)
|
||||
-- TODO: Refine this
|
||||
--:SetMissionHoldingCoord(COORDINATE:NewFromVec2(HoldingVec2),capalt,capspeed,120)
|
||||
:SetMissionIngressCoord(IngressCoordinate,capalt,capspeed)
|
||||
--:SetMissionEgressCoord(COORDINATE:NewFromVec2(HoldingVec2),capalt,capspeed)
|
||||
|
||||
if nogozoneset:Count() > 0 then
|
||||
InterceptAuftrag:AddConditionSuccess(
|
||||
function(group,zoneset,conflictset)
|
||||
local success = false
|
||||
if group and group:IsAlive() then
|
||||
local coord = group:GetCoordinate()
|
||||
if coord and zoneset:Count() > 0 and zoneset:IsCoordinateInZone(coord) then
|
||||
success = true
|
||||
end
|
||||
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
|
||||
success = false
|
||||
end
|
||||
else
|
||||
success = true -- target dead
|
||||
end
|
||||
return success
|
||||
end,
|
||||
contact.group,
|
||||
nogozoneset,
|
||||
conflictzoneset
|
||||
)
|
||||
end
|
||||
|
||||
InterceptAuftrag:AddConditionFailure(
|
||||
function()
|
||||
local failure = false
|
||||
if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting() then failure = true end
|
||||
return failure
|
||||
end
|
||||
)
|
||||
|
||||
table.insert(self.ListOfAuftrag,InterceptAuftrag)
|
||||
local assigned, rest = self:_TryAssignMission(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
|
||||
if not assigned then
|
||||
InterceptAuftrag:SetRequiredAssets(rest)
|
||||
targetairwing:AddMission(InterceptAuftrag)
|
||||
end
|
||||
Cluster.mission = InterceptAuftrag
|
||||
end
|
||||
else
|
||||
MESSAGE:New("**** Not enough airframes available or max mission limit reached!",15,"EasyA2G"):ToAllIf(self.debug):ToLog()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) Start detection.
|
||||
-- @param #EASYA2G self
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:_StartIntel()
|
||||
self:T(self.lid.."_StartIntel")
|
||||
-- Border GCI Detection
|
||||
local BlueAir_DetectionSetGroup = SET_GROUP:New()
|
||||
BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName )
|
||||
BlueAir_DetectionSetGroup:FilterStart()
|
||||
|
||||
-- Intel type detection
|
||||
local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.alias)
|
||||
BlueIntel:SetClusterAnalysis(true,false,false)
|
||||
BlueIntel:SetForgetTime(300)
|
||||
BlueIntel:SetAcceptZones(self.GoZoneSet)
|
||||
BlueIntel:SetRejectZones(self.NoGoZoneSet)
|
||||
BlueIntel:SetConflictZones(self.ConflictZoneSet)
|
||||
BlueIntel:SetVerbosity(0)
|
||||
|
||||
if self.usecorridors == true then
|
||||
BlueIntel:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorfloor or self.corridorceiling then
|
||||
BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
|
||||
BlueIntel:Start()
|
||||
|
||||
if self.debug then
|
||||
BlueIntel.debug = true
|
||||
end
|
||||
|
||||
local function AssignCluster(Cluster)
|
||||
self:_AssignMission(Cluster)
|
||||
end
|
||||
|
||||
function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster)
|
||||
AssignCluster(Cluster)
|
||||
end
|
||||
|
||||
self.Intel = BlueIntel
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- TODO FSM Functions
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
--- (Internal) FSM Function onafterStart
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:onafterStart(From,Event,To)
|
||||
self:T({From,Event,To})
|
||||
self:_StartIntel()
|
||||
self:_CreateAirwings()
|
||||
self:_CreateSquads()
|
||||
--self:_SetCAPPatrolPoints()
|
||||
self:_SetTankerPatrolPoints()
|
||||
self:_SetAwacsPatrolPoints()
|
||||
self:_SetReconPatrolPoints()
|
||||
self:__Status(-10)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onafterStatus
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:onafterStatus(From,Event,To)
|
||||
self:T({From,Event,To})
|
||||
-- cleanup
|
||||
local cleaned = false
|
||||
local cleanlist = {}
|
||||
for _,_auftrag in pairs(self.ListOfAuftrag) do
|
||||
local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG
|
||||
if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then
|
||||
table.insert(cleanlist,auftrag)
|
||||
cleaned = true
|
||||
end
|
||||
end
|
||||
if cleaned == true then
|
||||
self.ListOfAuftrag = nil
|
||||
self.ListOfAuftrag = cleanlist
|
||||
end
|
||||
-- Gather Some Stats
|
||||
local function counttable(tbl)
|
||||
local count = 0
|
||||
for _,_data in pairs(tbl) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
local wings = counttable(self.ManagedAW)
|
||||
local squads = counttable(self.ManagedSQ)
|
||||
local caps = counttable(self.ManagedCP)
|
||||
local assets = 0
|
||||
local instock = 0
|
||||
local capmission = 0
|
||||
local interceptmission = 0
|
||||
local reconmission = 0
|
||||
local awacsmission = 0
|
||||
local tankermission = 0
|
||||
local alert5mission = 0
|
||||
for _,_wing in pairs(self.wings) do
|
||||
local count = _wing[1]:CountAssetsOnMission(MissionTypes,Cohort)
|
||||
local count2 = _wing[1]:CountAssets(true,MissionTypes,Attributes)
|
||||
--capmission = capmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.PATROLRACETRACK})
|
||||
interceptmission = interceptmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI})
|
||||
reconmission = reconmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON})
|
||||
awacsmission = awacsmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS})
|
||||
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
|
||||
alert5mission = alert5mission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5})
|
||||
assets = assets + count
|
||||
instock = instock + count2
|
||||
local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5})
|
||||
-- update ready groups
|
||||
self.ReadyFlightGroups = nil
|
||||
self.ReadyFlightGroups = {}
|
||||
for _,_asset in pairs(assetsonmission or {}) do
|
||||
local asset = _asset -- Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
local FG = asset.flightgroup -- Ops.FlightGroup#FLIGHTGROUP
|
||||
if FG then
|
||||
local name = FG:GetName()
|
||||
local engage = FG:IsEngaging()
|
||||
local hasmissiles = FG:CanAirToGround()
|
||||
--self:T("Is Alert5? "..tostring(FG:GetMissionCurrent().type))
|
||||
local isalert5 = (FG:GetMissionCurrent() ~= nil and FG:GetMissionCurrent().type == AUFTRAG.Type.ALERT5) and true or false
|
||||
local ready = hasmissiles and FG:IsFuelGood() and (FG:IsAirborne() or isalert5)
|
||||
self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()), tostring(isalert5)))
|
||||
if ready then
|
||||
self.ReadyFlightGroups[name] = FG
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if self.Monitor then
|
||||
local threatcount = #self.Intel.Clusters or 0
|
||||
local text = self.alias
|
||||
text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock
|
||||
text = text.."\nThreats: "..threatcount
|
||||
text = text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission
|
||||
--text = text.."\n - A2G Holding: "..capmission
|
||||
text = text.."\n - A2G Attack: "..interceptmission
|
||||
text = text.."\n - AWACS: "..awacsmission
|
||||
text = text.."\n - TANKER: "..tankermission
|
||||
text = text.."\n - Recon: "..reconmission
|
||||
text = text.."\n - Alert5 "..alert5mission
|
||||
text = text.."\nMission Limit: "..self.MaxAliveMissions
|
||||
MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug)
|
||||
end
|
||||
self:__Status(30)
|
||||
return self
|
||||
end
|
||||
@@ -1,13 +1,27 @@
|
||||
--- **Ops** - Create your A2A Defenses.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Automatically create and manage A2A defenses using an AirWing and Squadrons for one coalition
|
||||
-- * Easy set-up
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Easy CAP/GCI Class, based on OPS classes
|
||||
-------------------------------------------------------------------------
|
||||
-- Documentation
|
||||
--
|
||||
-- ## Documentation:
|
||||
--
|
||||
-- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyGCICAP.html
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/EasyGCICAP).
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Date: September 2023
|
||||
-- Last Update: July 2024
|
||||
-- Last Update: Jan 2026
|
||||
-------------------------------------------------------------------------
|
||||
--
|
||||
--- **Ops** - Easy GCI & CAP Manager
|
||||
@@ -71,6 +85,12 @@
|
||||
-- @field #boolean DespawnAfterHolding
|
||||
-- @field #list<Ops.Auftrag#AUFTRAG> ListOfAuftrag
|
||||
-- @field #string defaulttakeofftype Take off type
|
||||
-- @field #number FuelLowThreshold
|
||||
-- @field #number FuelCriticalThreshold
|
||||
-- @field #boolean showpatrolpointmarks
|
||||
-- @field #table EngageTargetTypes
|
||||
-- @field #number maintenancetime
|
||||
-- @field #number repairtime
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
|
||||
@@ -100,7 +120,7 @@
|
||||
-- ### Prerequisites
|
||||
--
|
||||
-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!**
|
||||
-- E.g. for Kuitaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
|
||||
-- E.g. for Kutaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
|
||||
-- Next put a late activated template group for your CAP/GCI Squadron on the map. Last, put a zone on the map for the CAP operations, let's name it "Blue Zone 1". Size of the zone plays no role.
|
||||
-- Put an EW radar system on the map and name it aptly, like "Blue EWR".
|
||||
--
|
||||
@@ -196,7 +216,7 @@ EASYGCICAP = {
|
||||
coalition = "blue",
|
||||
alias = nil,
|
||||
wings = {},
|
||||
Intel = nil,
|
||||
Intel = nil, -- Ops.Intel#INTEL
|
||||
resurrection = 900,
|
||||
capspeed = 300,
|
||||
capalt = 25000,
|
||||
@@ -226,6 +246,10 @@ EASYGCICAP = {
|
||||
DespawnAfterHolding = true,
|
||||
ListOfAuftrag = {},
|
||||
defaulttakeofftype = "hot",
|
||||
FuelLowThreshold = 25,
|
||||
FuelCriticalThreshold = 10,
|
||||
showpatrolpointmarks = false,
|
||||
EngageTargetTypes = {"Air"},
|
||||
}
|
||||
|
||||
--- Internal Squadron data type
|
||||
@@ -258,13 +282,15 @@ EASYGCICAP = {
|
||||
-- @field #number Speed
|
||||
-- @field #number Heading
|
||||
-- @field #number LegLength
|
||||
-- @field Core.Zone#ZONE_BASE Zone
|
||||
|
||||
--- EASYGCICAP class version.
|
||||
-- @field #string version
|
||||
EASYGCICAP.version="0.1.23"
|
||||
EASYGCICAP.version="0.1.34"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
--
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: TBD
|
||||
@@ -286,6 +312,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
|
||||
-- defaults
|
||||
self.alias = Alias or AirbaseName.." CAP Wing"
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("EASYGCICAP %s | ", self.alias)
|
||||
|
||||
self.coalitionname = string.lower(Coalition) or "blue"
|
||||
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.wings = {}
|
||||
@@ -315,10 +345,12 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
self.DespawnAfterHolding = true
|
||||
self.ListOfAuftrag = {}
|
||||
self.defaulttakeofftype = "hot"
|
||||
self.FuelLowThreshold = 25
|
||||
self.FuelCriticalThreshold = 10
|
||||
self.showpatrolpointmarks = false
|
||||
self.EngageTargetTypes = {"Air"}
|
||||
self:SetDefaultTurnoverTime()
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("EASYGCICAP %s | ", self.alias)
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:SetStartState("Stopped")
|
||||
@@ -332,6 +364,34 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
|
||||
self:__Start(math.random(6,12))
|
||||
|
||||
--- On Before "Start" event.
|
||||
-- @function [parent=#EASYGCICAP] OnBeforeStart
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "Start" event.
|
||||
-- @function [parent=#EASYGCICAP] OnAfterStart
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On Before "Status" event.
|
||||
-- @function [parent=#EASYGCICAP] OnBeforeStatus
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "Status" event.
|
||||
-- @function [parent=#EASYGCICAP] OnAfterStatus
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -339,6 +399,85 @@ end
|
||||
-- Functions
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
--- Get a specific managed AirWing by name
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string AirbaseName Airbase name of the home of this wing.
|
||||
-- @return Ops.AirWing#AIRWING Airwing or nil if not found
|
||||
function EASYGCICAP:GetAirwing(AirbaseName)
|
||||
self:T(self.lid.."GetAirwing")
|
||||
if self.wings[AirbaseName] then
|
||||
return self.wings[AirbaseName][1]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Add an agent to the underlying INTEL detection - caution, we need to be started first for this to work!
|
||||
-- Normally this isn't necessary when the Group name is correctly filled (see EWRName in `New()`).
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param Wrapper.Group#GROUP Group The group object to be added as Intel Agent.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:AddAgent(Group)
|
||||
self:T(self.lid.."AddAgent")
|
||||
if Group:IsInstanceOf("GROUP") and self.Intel ~= nil then
|
||||
self.Intel:AddAgent(Group)
|
||||
if self.TankerInvisible == true then
|
||||
Group:SetCommandInvisible(true)
|
||||
Group:OptionROEHoldFire()
|
||||
if Group:IsAir() then
|
||||
Group:OptionROTEvadeFire()
|
||||
else
|
||||
Group:OptionDisperseOnAttack(30)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get a table of all managed AirWings
|
||||
-- @param #EASYGCICAP self
|
||||
-- @return #table Table of Ops.AirWing#AIRWING Airwings
|
||||
function EASYGCICAP:GetAirwingTable()
|
||||
self:T(self.lid.."GetAirwingTable")
|
||||
local Wingtable = {}
|
||||
for _,_object in pairs(self.wings or {}) do
|
||||
table.insert(Wingtable,_object[1])
|
||||
end
|
||||
return Wingtable
|
||||
end
|
||||
|
||||
--- Set "fuel low" threshold for CAP and INTERCEPT flights.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 25.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetFuelLow(Percent)
|
||||
self:T(self.lid.."SetFuelLow")
|
||||
self.FuelLowThreshold = Percent or 25
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set markers on the map for Patrol Points.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #boolean onoff Set to true to switch markers on.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:ShowPatrolPointMarkers(onoff)
|
||||
if onoff then
|
||||
self.showpatrolpointmarks = true
|
||||
else
|
||||
self.showpatrolpointmarks = false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set "fuel critical" threshold for CAP and INTERCEPT flights.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 10.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetFuelCritical(Percent)
|
||||
self:T(self.lid.."SetFuelCritical")
|
||||
self.FuelCriticalThreshold = Percent or 10
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set CAP formation.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group
|
||||
@@ -359,7 +498,7 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Count alive missions in our internal stack.
|
||||
--- (internal) Count alive missions in our internal stack.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @return #number count
|
||||
function EASYGCICAP:_CountAliveAuftrags()
|
||||
@@ -473,6 +612,18 @@ function EASYGCICAP:SetDefaultMissionRange(Range)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default turnover times for squadrons in minutes
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 5 min.
|
||||
-- @param #number RepairTime Time in minutes it takes to repair a flight for each life point taken. Default is 10 min.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetDefaultTurnoverTime(MaintenanceTime,RepairTime)
|
||||
self:T(self.lid.."SetDefaultTurnoverTime")
|
||||
self.maintenancetime=MaintenanceTime or 5
|
||||
self.repairtime=RepairTime or 10
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default number of airframes standing by for intercept tasks (visible on the airfield)
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Airframes defaults to 2
|
||||
@@ -536,6 +687,16 @@ function EASYGCICAP:SetCapStartTimeVariation(Start, End)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set which target types CAP flights will prefer to engage, defaults to {"Air"}
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #table types Table of comma separated #string entries, defaults to {"Air"} (everything that flies and is not a weapon). Useful other options are e.g. {"Bombers"}, {"Fighters"},
|
||||
-- or {"Helicopters"} or combinations like {"Bombers", "Fighters", "UAVs"}. See [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes).
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetCAPEngageTargetTypes(types)
|
||||
self.EngageTargetTypes = types or {"Air"}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an AirWing to the manager
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string Airbasename
|
||||
@@ -570,7 +731,7 @@ function EASYGCICAP:_CreateAirwings()
|
||||
return self
|
||||
end
|
||||
|
||||
--- (internal) Create and add another AirWing to the manager
|
||||
--- (Internal) Create and add another AirWing to the manager
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string Airbasename
|
||||
-- @param #string Alias
|
||||
@@ -583,7 +744,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
local DespawnAfterHolding = self.DespawnAfterHolding
|
||||
|
||||
-- Check STATIC name
|
||||
local check = STATIC:FindByName(Airbasename,false)
|
||||
local check = STATIC:FindByName(Airbasename,false) or UNIT:FindByName(Airbasename)
|
||||
if check == nil then
|
||||
MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog()
|
||||
return
|
||||
@@ -598,6 +759,10 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
CAP_Wing:SetRespawnAfterDestroyed()
|
||||
CAP_Wing:SetNumberCAP(self.capgrouping)
|
||||
CAP_Wing:SetCapCloseRaceTrack(true)
|
||||
|
||||
if self.showpatrolpointmarks then
|
||||
CAP_Wing:ShowPatrolPointMarkers(true)
|
||||
end
|
||||
|
||||
if self.capOptionVaryStartTime then
|
||||
CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime)
|
||||
@@ -628,6 +793,9 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
local engagerange = self.engagerange
|
||||
local GoZoneSet = self.GoZoneSet
|
||||
local NoGoZoneSet = self.NoGoZoneSet
|
||||
local FuelLow = self.FuelLowThreshold or 25
|
||||
local FuelCritical = self.FuelCriticalThreshold or 10
|
||||
local EngageTypes = self.EngageTargetTypes or {"Air"}
|
||||
|
||||
function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission)
|
||||
local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP
|
||||
@@ -639,10 +807,15 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename))
|
||||
flightgroup:GetGroup():CommandEPLRS(true,5)
|
||||
flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch()
|
||||
flightgroup:GetGroup():SetOptionLandingOverheadBreak()
|
||||
if Mission.type ~= AUFTRAG.Type.TANKER and Mission.type ~= AUFTRAG.Type.AWACS and Mission.type ~= AUFTRAG.Type.RECON then
|
||||
flightgroup:SetDetection(true)
|
||||
flightgroup:SetEngageDetectedOn(engagerange,{"Air"},GoZoneSet,NoGoZoneSet)
|
||||
flightgroup:SetEngageDetectedOn(engagerange,EngageTypes,GoZoneSet,NoGoZoneSet)
|
||||
flightgroup:SetOutOfAAMRTB()
|
||||
flightgroup:SetFuelLowRTB(true)
|
||||
flightgroup:SetFuelLowThreshold(FuelLow)
|
||||
flightgroup:SetFuelCriticalRTB(true)
|
||||
flightgroup:SetFuelCriticalThreshold(FuelCritical)
|
||||
if CapFormation then
|
||||
flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation)
|
||||
end
|
||||
@@ -665,8 +838,13 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
end
|
||||
end
|
||||
|
||||
if self.noalert5 > 0 then
|
||||
local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
|
||||
if self.noalert5 > 0 then
|
||||
local alert
|
||||
if self.ClassName == "EASYGCICAP" then
|
||||
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
|
||||
elseif self.ClassName == "EASYA2G" then
|
||||
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.BAI)
|
||||
end
|
||||
alert:SetRequiredAssets(self.noalert5)
|
||||
alert:SetRepeat(99)
|
||||
CAP_Wing:AddMission(alert)
|
||||
@@ -681,24 +859,30 @@ end
|
||||
--- Add a CAP patrol point to a Wing
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string AirbaseName Name of the Wing's airbase
|
||||
-- @param Core.Point#COORDINATE Coordinate.
|
||||
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Defaults to 25000 feet ASL.
|
||||
-- @param #number Speed Defaults to 300 knots TAS.
|
||||
-- @param #number Heading Defaults to 90 degrees (East).
|
||||
-- @param #number LegLength Defaults to 15 NM.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
|
||||
self:T(self.lid.."AddPatrolPointCAP "..Coordinate:ToStringLLDDM())
|
||||
local EntryCAP = {} -- #EASYGCICAP.CapPoint
|
||||
self:T(self.lid.."AddPatrolPointCAP")--..Coordinate:ToStringLLDDM())
|
||||
local coordinate = Coordinate
|
||||
local EntryCAP = {} -- #EASYGCICAP.CapPoint
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
-- adjust coordinate and get the coordinate from the zone
|
||||
coordinate = Coordinate:GetCoordinate()
|
||||
EntryCAP.Zone = Coordinate
|
||||
end
|
||||
EntryCAP.AirbaseName = AirbaseName
|
||||
EntryCAP.Coordinate = Coordinate
|
||||
EntryCAP.Coordinate = coordinate
|
||||
EntryCAP.Altitude = Altitude or 25000
|
||||
EntryCAP.Speed = Speed or 300
|
||||
EntryCAP.Heading = Heading or 90
|
||||
EntryCAP.LegLength = LegLength or 15
|
||||
self.ManagedCP[#self.ManagedCP+1] = EntryCAP
|
||||
if self.debug then
|
||||
local mark = MARKER:New(Coordinate,self.lid.."Patrol Point"):ToAll()
|
||||
local mark = MARKER:New(coordinate,self.lid.."Patrol Point"):ToAll()
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -706,7 +890,7 @@ end
|
||||
--- Add a RECON patrol point to a Wing
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string AirbaseName Name of the Wing's airbase
|
||||
-- @param Core.Point#COORDINATE Coordinate.
|
||||
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Defaults to 25000 feet.
|
||||
-- @param #number Speed Defaults to 300 knots.
|
||||
-- @param #number Heading Defaults to 90 degrees (East).
|
||||
@@ -731,7 +915,7 @@ end
|
||||
--- Add a TANKER patrol point to a Wing
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string AirbaseName Name of the Wing's airbase
|
||||
-- @param Core.Point#COORDINATE Coordinate.
|
||||
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Defaults to 25000 feet.
|
||||
-- @param #number Speed Defaults to 300 knots.
|
||||
-- @param #number Heading Defaults to 90 degrees (East).
|
||||
@@ -756,7 +940,7 @@ end
|
||||
--- Add an AWACS patrol point to a Wing
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string AirbaseName Name of the Wing's airbase
|
||||
-- @param Core.Point#COORDINATE Coordinate.
|
||||
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Defaults to 25000 feet.
|
||||
-- @param #number Speed Defaults to 300 knots.
|
||||
-- @param #number Heading Defaults to 90 degrees (East).
|
||||
@@ -844,7 +1028,12 @@ function EASYGCICAP:_SetCAPPatrolPoints()
|
||||
local Speed = data.Speed
|
||||
local Heading = data.Heading
|
||||
local LegLength = data.LegLength
|
||||
Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength)
|
||||
local Zone = _data.Zone
|
||||
if Zone then
|
||||
Wing:AddPatrolPointCAP(Zone,Altitude,Speed,Heading,LegLength)
|
||||
else
|
||||
Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -910,7 +1099,7 @@ end
|
||||
-- @param #string SquadName Squadron name - must be unique!
|
||||
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
|
||||
-- @param #number AirFrames Number of available airframes, e.g. 20.
|
||||
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
|
||||
-- @param #string Skill (optional) Skill level, e.g. AI.Skill.AVERAGE
|
||||
-- @param #string Modex (optional) Modex to be used,e.g. 402.
|
||||
-- @param #string Livery (optional) Livery name to be used.
|
||||
-- @return #EASYGCICAP self
|
||||
@@ -1044,7 +1233,7 @@ function EASYGCICAP:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(10,20)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
@@ -1075,7 +1264,7 @@ function EASYGCICAP:_AddReconSquadron(TemplateName, SquadName, AirbaseName, AirF
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(10,20)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
@@ -1109,13 +1298,15 @@ function EASYGCICAP:_AddTankerSquadron(TemplateName, SquadName, AirbaseName, Air
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(10,20)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
Squadron_One:SetMissionRange(self.missionrange)
|
||||
Squadron_One:SetRadio(Frequency,Modulation)
|
||||
Squadron_One:AddTacanChannel(TACAN,TACAN)
|
||||
if TACAN then
|
||||
Squadron_One:AddTacanChannel(TACAN,TACAN)
|
||||
end
|
||||
|
||||
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
|
||||
|
||||
@@ -1144,7 +1335,7 @@ function EASYGCICAP:_AddAWACSSquadron(TemplateName, SquadName, AirbaseName, AirF
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(10,20)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
@@ -1189,6 +1380,54 @@ function EASYGCICAP:AddConflictZone(Zone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zones.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetCorridorZones(CorridorZones)
|
||||
self:T(self.lid .. "SetCorridorZones")
|
||||
if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then
|
||||
self.corridorzones = CorridorZones
|
||||
self.usecorridors = true
|
||||
elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then
|
||||
if not self.corridorzones then self.corridorzones = SET_ZONE:New() end
|
||||
self.corridorzones:AddZone(CorridorZones)
|
||||
self.usecorridors = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to add one corridor zone.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:AddCorridorZone(CorridorZone)
|
||||
self:T(self.lid .. "AddCorridorZone")
|
||||
self:SetCorridorZones(CorridorZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in FEET.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Floor Floor altitude ASL in feet.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in feet.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
|
||||
self.corridorfloor = UTILS.FeetToMeters(Floor)
|
||||
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in METERS.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Floor Floor altitude ASL in meters.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in meters.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
|
||||
self.corridorfloor = Floor
|
||||
self.corridorceiling = Ceiling
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Try to assign the intercept to a FlightGroup already in air and ready.
|
||||
-- @param #EASYGCICAP self
|
||||
@@ -1199,19 +1438,19 @@ end
|
||||
-- @return #boolean assigned
|
||||
-- @return #number leftover
|
||||
function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize)
|
||||
self:I("_TryAssignIntercept for size "..WingSize or 1)
|
||||
self:T("_TryAssignIntercept for size "..WingSize or 1)
|
||||
local assigned = false
|
||||
local wingsize = WingSize or 1
|
||||
local mindist = 0
|
||||
local disttable = {}
|
||||
if Group and Group:IsAlive() then
|
||||
local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0)
|
||||
self:I(self.lid..string.format("Assignment for %s",Group:GetName()))
|
||||
self:T(self.lid..string.format("Assignment for %s",Group:GetName()))
|
||||
for _name,_FG in pairs(ReadyFlightGroups or {}) do
|
||||
local FG = _FG -- Ops.FlightGroup#FLIGHTGROUP
|
||||
local fcoord = FG:GetCoordinate()
|
||||
local dist = math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1))
|
||||
self:I(self.lid..string.format("FG %s Distance %dkm",_name,dist))
|
||||
self:T(self.lid..string.format("FG %s Distance %dkm",_name,dist))
|
||||
disttable[#disttable+1] = { FG=FG, dist=dist}
|
||||
if dist>mindist then mindist=dist end
|
||||
end
|
||||
@@ -1228,7 +1467,7 @@ function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group
|
||||
local cm = FG:GetMissionCurrent()
|
||||
if cm then cm:Cancel() end
|
||||
wingsize = wingsize - 1
|
||||
self:I(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
|
||||
self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
|
||||
if wingsize == 0 then
|
||||
assigned = true
|
||||
break
|
||||
@@ -1258,7 +1497,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
local conflictzoneset = self.ConflictZoneSet
|
||||
local ReadyFlightGroups = self.ReadyFlightGroups
|
||||
|
||||
-- Aircraft?
|
||||
-- Aircraft?
|
||||
if Cluster.ctype ~= INTEL.Ctype.AIRCRAFT then return end
|
||||
-- Threatlevel 0..10
|
||||
local contact = self.Intel:GetHighestThreatContact(Cluster)
|
||||
@@ -1303,6 +1542,10 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
local data = _data -- #EASYGCICAP.CapPoint
|
||||
local name = data.AirbaseName
|
||||
local zonecoord = data.Coordinate
|
||||
if data.Zone then
|
||||
-- refresh coordinate in case we have a (moving) zone
|
||||
zonecoord = data.Zone:GetCoordinate()
|
||||
end
|
||||
local airwing = wings[name][1]
|
||||
local coa = AIRBASE:FindByName(name):GetCoalition()
|
||||
local samecoalitionab = coa == self.coalition and true or false
|
||||
@@ -1344,6 +1587,8 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
|
||||
success = false
|
||||
end
|
||||
else
|
||||
success = true
|
||||
end
|
||||
return success
|
||||
end,
|
||||
@@ -1353,6 +1598,14 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
)
|
||||
end
|
||||
|
||||
InterceptAuftrag:AddConditionFailure(
|
||||
function()
|
||||
local failure = false
|
||||
if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting() then failure = true end
|
||||
return failure
|
||||
end
|
||||
)
|
||||
|
||||
table.insert(self.ListOfAuftrag,InterceptAuftrag)
|
||||
local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
|
||||
if not assigned then
|
||||
@@ -1385,6 +1638,14 @@ function EASYGCICAP:_StartIntel()
|
||||
BlueIntel:SetRejectZones(self.NoGoZoneSet)
|
||||
BlueIntel:SetConflictZones(self.ConflictZoneSet)
|
||||
BlueIntel:SetVerbosity(0)
|
||||
|
||||
if self.usecorridors == true then
|
||||
BlueIntel:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorfloor or self.corridorceiling then
|
||||
BlueIntel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
|
||||
BlueIntel:Start()
|
||||
|
||||
if self.debug then
|
||||
@@ -1404,7 +1665,7 @@ function EASYGCICAP:_StartIntel()
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-- TODO FSM Functions
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
--- (Internal) FSM Function onafterStart
|
||||
@@ -1488,7 +1749,7 @@ function EASYGCICAP:onafterStatus(From,Event,To)
|
||||
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
|
||||
assets = assets + count
|
||||
instock = instock + count2
|
||||
local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})
|
||||
local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5, AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})
|
||||
-- update ready groups
|
||||
self.ReadyFlightGroups = nil
|
||||
self.ReadyFlightGroups = {}
|
||||
@@ -1499,8 +1760,9 @@ function EASYGCICAP:onafterStatus(From,Event,To)
|
||||
local name = FG:GetName()
|
||||
local engage = FG:IsEngaging()
|
||||
local hasmissiles = FG:IsOutOfMissiles() == nil and true or false
|
||||
local ready = hasmissiles and FG:IsFuelGood() and FG:IsAirborne()
|
||||
--self:I(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready)))
|
||||
local isalert5 = (FG:GetMissionCurrent() ~= nil and FG:GetMissionCurrent().type == AUFTRAG.Type.ALERT5) and true or false
|
||||
local ready = hasmissiles and FG:IsFuelGood() and (FG:IsAirborne() or isalert5)
|
||||
--self:T(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready)))
|
||||
if ready then
|
||||
self.ReadyFlightGroups[name] = FG
|
||||
end
|
||||
@@ -1535,5 +1797,8 @@ end
|
||||
function EASYGCICAP:onafterStop(From,Event,To)
|
||||
self:T({From,Event,To})
|
||||
self.Intel:Stop()
|
||||
for _,_wing in pairs(self.wings or {}) do
|
||||
_wing:Stop()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2464,7 +2464,7 @@ end
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Payer Menu
|
||||
-- Player Menu
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create player menu.
|
||||
|
||||
@@ -4667,10 +4667,12 @@ function FLIGHTGROUP:GetParking(airbase)
|
||||
local coords={}
|
||||
for clientname, client in pairs(clients) do
|
||||
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
|
||||
local units=template.units
|
||||
for i,unit in pairs(units) do
|
||||
local coord=COORDINATE:New(unit.x, unit.alt, unit.y)
|
||||
coords[unit.name]=coord
|
||||
if template then
|
||||
local units=template.units
|
||||
for i,unit in pairs(units) do
|
||||
local coord=COORDINATE:New(unit.x, unit.alt, unit.y)
|
||||
coords[unit.name]=coord
|
||||
end
|
||||
end
|
||||
end
|
||||
return coords
|
||||
@@ -5027,7 +5029,7 @@ function FLIGHTGROUP:_UpdateMenu(delay)
|
||||
|
||||
-- Message to group.
|
||||
MESSAGE:New(text, 5):ToGroup(self.group)
|
||||
self:I(self.lid..text)
|
||||
self:T(self.lid..text)
|
||||
end
|
||||
|
||||
-- Get current position of player.
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
-- @field Core.Set#SET_ZONE acceptzoneset Set of accept zones. If defined, only contacts in these zones are considered.
|
||||
-- @field Core.Set#SET_ZONE rejectzoneset Set of reject zones. Contacts in these zones are not considered, even if they are in accept zones.
|
||||
-- @field Core.Set#SET_ZONE conflictzoneset Set of conflict zones. Contacts in these zones are considered, even if they are not in accept zones or if they are in reject zones.
|
||||
-- @field Core.Set#SET_ZONE corridorzoneset Set of corridor zones. Contacts in these zones are never considered. Also see corridorfloorheight and corridorfloorceiling.
|
||||
-- @field #number corridorfloor [Air] Contacts below this height (ASL!) are considered, even if they are in a corridor zone.
|
||||
-- @field #number corridorceiling [Air] Contacts above this height (ASL!) are considered, even if they are in a corridor zone.
|
||||
-- @field #table Contacts Table of detected items.
|
||||
-- @field #table ContactsLost Table of lost detected items.
|
||||
-- @field #table ContactsUnknown Table of new detected items.
|
||||
@@ -39,6 +42,9 @@
|
||||
-- @field #number prediction Seconds default to be used with CalcClusterFuturePosition.
|
||||
-- @field #boolean detectStatics If `true`, detect STATIC objects. Default `false`.
|
||||
-- @field #number statusupdate Time interval in seconds after which the status is refreshed. Default 60 sec. Should be negative.
|
||||
-- @field #boolean DetectAccoustic If true, also detect by sound (ie proximity).
|
||||
-- @field #number DetectAccousticRadius Radius dfor accoustic detection, defaults to 2000 meters.
|
||||
-- @field #table DetectAccousticUnitTypes Types of units we can detect accousticly. Defaults to {Unit.Category.HELICOPTER}
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Top Secret!
|
||||
@@ -102,6 +108,9 @@ INTEL = {
|
||||
clusterarrows = false,
|
||||
prediction = 300,
|
||||
detectStatics = false,
|
||||
DetectAccoustic = false,
|
||||
DetectAccousticRadius = 1000,
|
||||
DetectAccousticUnitTypes = {Unit.Category.HELICOPTER},
|
||||
}
|
||||
|
||||
--- Detected item info.
|
||||
@@ -160,14 +169,14 @@ INTEL.Ctype={
|
||||
|
||||
--- INTEL class version.
|
||||
-- @field #string version
|
||||
INTEL.version="0.3.6"
|
||||
INTEL.version="0.3.10"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Add min cluster size. Only create new clusters if they have a certain group size.
|
||||
-- TODO: process detected set asynchroniously for better performance.
|
||||
-- NODO: process detected set asynchroniously for better performance.
|
||||
-- DONE: Add statics.
|
||||
-- DONE: Filter detection methods.
|
||||
-- DONE: Accept zones.
|
||||
@@ -175,6 +184,7 @@ INTEL.version="0.3.6"
|
||||
-- NOGO: SetAttributeZone --> return groups of generalized attributes in a zone.
|
||||
-- DONE: Loose units only if they remain undetected for a given time interval. We want to avoid fast oscillation between detected/lost states. Maybe 1-5 min would be a good time interval?!
|
||||
-- DONE: Combine units to groups for all, new and lost.
|
||||
-- DONE: Add corridor zones.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -266,6 +276,7 @@ function INTEL:New(DetectionSet, Coalition, Alias)
|
||||
self:SetForgetTime()
|
||||
self:SetAcceptZones()
|
||||
self:SetRejectZones()
|
||||
self:SetCorridorZones()
|
||||
self:SetConflictZones()
|
||||
|
||||
------------------------
|
||||
@@ -398,6 +409,26 @@ function INTEL:SetAcceptZones(AcceptZoneSet)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set to accept accoustic detection.
|
||||
-- @param #INTEL self
|
||||
-- @param #number Radius Radius in which we can "hear" units. Defaults to 1000 meters.
|
||||
-- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER}`
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetAccousticDetectionOn(Radius,UnitCategories)
|
||||
self.DetectAccoustic = true
|
||||
self.DetectAccousticRadius = Radius or 1000
|
||||
self.DetectAccousticUnitTypes = UnitCategories or {Unit.Category.HELICOPTER}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch off accoustic detection.
|
||||
-- @param #INTEL self
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetAccousticDetectionOff()
|
||||
self.DetectAccoustic = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an accept zone. Only contacts detected in this zone are considered.
|
||||
-- @param #INTEL self
|
||||
-- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set.
|
||||
@@ -475,7 +506,69 @@ function INTEL:RemoveConflictZone(ConflictZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- **OBSOLETE, will be removed in next version!** Set forget contacts time interval.
|
||||
--- Set corrdidor zones. Contacts detected in this/these zone(s) are never reported by the detection.
|
||||
-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights.
|
||||
-- @param #INTEL self
|
||||
-- @param Core.Set#SET_ZONE CorridorZoneSet Set of corrdidor zone(s).
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetCorridorZones(CorridorZoneSet)
|
||||
self.corridorzoneset=CorridorZoneSet or SET_ZONE:New()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a corrdidor zone. Contacts detected in this zone are corrdidored and not reported by the detection.
|
||||
-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights.
|
||||
-- @param #INTEL self
|
||||
-- @param Core.Zone#ZONE CorridorZone Add a zone to the corrdidor zone set.
|
||||
-- @return #INTEL self
|
||||
function INTEL:AddCorridorZone(CorridorZone)
|
||||
self.corridorzoneset:AddZone(CorridorZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove a corrdidor zone from the corrdidor zone set.
|
||||
-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights.
|
||||
-- @param #INTEL self
|
||||
-- @param Core.Zone#ZONE CorridorZone Remove a zone from the corrdidor zone set.
|
||||
-- @return #INTEL self
|
||||
function INTEL:RemoveCorridorZone(CorridorZone)
|
||||
self.corridorzoneset:Remove(CorridorZone:GetName(), true)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Add corrdidor zone floor and height. This is generally applicable to all(!) corridor zones. Considered as ASL (above sea level or barometric) values.
|
||||
-- Overrides corridor exception for objects flying outside this limitations.
|
||||
-- To set an individual ceiling/floor on any Core.Zone#ZONE you wish to use, set these properties on the Core.Zone#ZONE object:
|
||||
-- `mycorridorzone:SetProperty("CorridorFloor",500)` -- meters, case sensitivity matters!
|
||||
-- `mycorridorzone:SetProperty("CorridorCeiling",10000)` -- meters, case sensitivity matters!
|
||||
-- @param #INTEL self
|
||||
-- @param #number Floor Floor altitude in meters.
|
||||
-- @param #number Ceiling Ceiling altitude in meters.
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetCorridorLimits(Floor,Ceiling)
|
||||
self.corridorceiling = Ceiling or 10000
|
||||
self.corridorfloor = Floor or 1
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Add corrdidor zone floor and height. This is generally applicable to all(!) corridor zones. Considered as ASL (above sea level or barometric) values.
|
||||
-- Overrides corridor exception for objects flying outside this limitations.
|
||||
-- To set an individual ceiling/floor on any Core.Zone#ZONE you wish to use, set these properties on the Core.Zone#ZONE object:
|
||||
-- `mycorridorzone:SetProperty("CorridorFloor",UTILS.FeetToMeters(5000))` -- feet, case sensitivity matters!
|
||||
-- `mycorridorzone:SetProperty("CorridorCeiling",UTILS.FeetToMeters(20000))` -- feet, case sensitivity matters!
|
||||
-- @param #INTEL self
|
||||
-- @param #number Floor Floor altitude in feet.
|
||||
-- @param #number Ceiling Ceiling altitude in feet.
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetCorridorLimitsFeet(Floor,Ceiling)
|
||||
local Ceiling = Ceiling or 25000
|
||||
local Floor = Floor or 15000
|
||||
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
|
||||
self.corridorfloor = UTILS.FeetToMeters(Floor)
|
||||
return self
|
||||
end
|
||||
|
||||
--- **OBSOLETE, not functional!** Set forget contacts time interval.
|
||||
-- Previously known contacts that are not detected any more, are "lost" after this time.
|
||||
-- This avoids fast oscillations between a contact being detected and undetected.
|
||||
-- @param #INTEL self
|
||||
@@ -831,6 +924,18 @@ function INTEL:UpdateIntel()
|
||||
self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK)
|
||||
|
||||
end
|
||||
|
||||
if self.DetectAccoustic then
|
||||
local recce = group:GetFirstUnitAlive()
|
||||
local detectionzone = group:GetProperty("INTEL_DETECT_ACCZONE")
|
||||
if not detectionzone then
|
||||
detectionzone = ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000)
|
||||
group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone)
|
||||
end
|
||||
if recce and recce:IsGround() then
|
||||
self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -884,6 +989,36 @@ function INTEL:UpdateIntel()
|
||||
table.insert(remove, unitname)
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if unit is in any of the corridor zones.
|
||||
if self.corridorzoneset:Count()>0 then
|
||||
self:T("Corridorzone Check for unit "..unit:GetName())
|
||||
local inzone = false
|
||||
for _,_zone in pairs(self.corridorzoneset.Set) do
|
||||
local zone=_zone --Core.Zone#ZONE
|
||||
if unit:IsInZone(zone) then
|
||||
local corridorfloor = zone:GetProperty("CorridorFloor") or self.corridorfloor
|
||||
local corridorceiling = zone:GetProperty("CorridorCeiling") or self.corridorceiling
|
||||
local debugtext = "Corridorzone Check for unit "..unit:GetName().."\n"
|
||||
debugtext = debugtext .. string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())),
|
||||
tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling)))
|
||||
MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1)
|
||||
if unit:IsAir() and (corridorfloor ~= nil or corridorceiling ~= nil) then
|
||||
local alt = unit:GetAltitude()
|
||||
if corridorfloor and alt > corridorfloor then inzone = true end
|
||||
if corridorceiling and (inzone == true or corridorfloor == nil) and alt < corridorceiling then inzone = true else inzone = false end
|
||||
if inzone == true then break end
|
||||
else
|
||||
inzone=true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Unit is inside a corridor zone ==> remove!
|
||||
if inzone then
|
||||
table.insert(remove, unitname)
|
||||
end
|
||||
end
|
||||
|
||||
-- Filter unit categories. Added check that we have a UNIT and not a STATIC object because :GetUnitCategory() is only available for units.
|
||||
if #self.filterCategory>0 and unit:IsInstanceOf("UNIT") then
|
||||
@@ -1040,7 +1175,7 @@ function INTEL:_CreateContact(Positionable, RecceName)
|
||||
item.category=3 --static:GetCategory()
|
||||
item.categoryname=static:GetCategoryName() or "Unknown"
|
||||
item.threatlevel=static:GetThreatLevel() or 0
|
||||
item.position=static:GetCoordinate()
|
||||
item.position=static:GetCoord()
|
||||
item.velocity=static:GetVelocityVec3()
|
||||
item.speed=0
|
||||
item.recce=RecceName
|
||||
@@ -1106,7 +1241,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, DetectedStatics, RecceDetecti
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}.
|
||||
--- (Internal) Return the detected target groups of the controllable as a table.
|
||||
-- The optional parameters specify the detection methods that can be applied.
|
||||
-- If no detection method is given, the detection will use all the available methods by default.
|
||||
-- @param #INTEL self
|
||||
@@ -1147,15 +1282,15 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
|
||||
local DetectionAccepted = true
|
||||
|
||||
if self.RadarAcceptRange then
|
||||
local reccecoord = Unit:GetCoordinate()
|
||||
local coord = unit:GetCoordinate()
|
||||
local reccecoord = Unit:GetCoord()
|
||||
local coord = unit:GetCoord()
|
||||
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
|
||||
if dist > self.RadarAcceptRangeKilometers then DetectionAccepted = false end
|
||||
end
|
||||
|
||||
if self.RadarBlur then
|
||||
local reccecoord = Unit:GetCoordinate()
|
||||
local coord = unit:GetCoordinate()
|
||||
local reccecoord = Unit:GetCoord()
|
||||
local coord = unit:GetCoord()
|
||||
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
|
||||
local AGL = unit:GetAltitude(true)
|
||||
local minheight = self.RadarBlurMinHeight or 250 -- meters
|
||||
@@ -1202,6 +1337,33 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}.
|
||||
-- @param #INTEL self
|
||||
-- @param Wrapper.Unit#UNIT Recce The unit detecting.
|
||||
-- @param #table DetectedUnits Table of detected units to be filled.
|
||||
-- @param #table RecceDetecting Table of recce per unit to be filled.
|
||||
-- @param Core.Zone#ZONE_GROUP detectionzone The zone where to look.
|
||||
function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone)
|
||||
local othercoalition = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
|
||||
self:T("Other coalition = "..othercoalition)
|
||||
if detectionzone then
|
||||
-- Get detected units
|
||||
local reccename = Recce:GetName()
|
||||
local DetectAccousticUnitTypes = self.DetectAccousticUnitTypes or {Unit.Category.HELICOPTER}
|
||||
detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes)
|
||||
local unitset = detectionzone:GetScannedSetUnit(othercoalition) -- Core.Set#SET_UNIT
|
||||
self:T("Accoustic detection found #Units "..unitset:CountAlive())
|
||||
for _,_unit in pairs(unitset.Set or {}) do
|
||||
if _unit and _unit:IsAlive() and _unit:GetCoalition() ~= self.coalition then
|
||||
local name = _unit:GetName() or "none"
|
||||
DetectedUnits[name]=_unit
|
||||
RecceDetecting[name]=reccename
|
||||
self:T("Unit name = "..name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Events
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -2144,7 +2306,7 @@ function INTEL:GetClusterCoordinate(Cluster, Update)
|
||||
return Cluster.coordinate
|
||||
end
|
||||
|
||||
--- Check if the coorindate of the cluster changed.
|
||||
--- Check if the coordindate of the cluster changed.
|
||||
-- @param #INTEL self
|
||||
-- @param #INTEL.Cluster Cluster The cluster.
|
||||
-- @param #number Threshold in meters. Default 100 m.
|
||||
|
||||
@@ -662,6 +662,15 @@ function LEGION:CheckMissionQueue()
|
||||
if mission:IsNotOver() and mission:IsReadyToCancel() then
|
||||
mission:Cancel()
|
||||
end
|
||||
|
||||
-- Housekeeping
|
||||
local TNow = timer.getTime()
|
||||
if mission:IsOver() and mission:IsNotRepeatable() and mission.DeletionTimstamp == nil then
|
||||
mission.DeletionTimstamp = TNow
|
||||
end
|
||||
if mission.DeletionTimstamp ~= nil and TNow - mission.DeletionTimstamp > 1800 then
|
||||
mission = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Check that runway is operational and that carrier is not recovering.
|
||||
@@ -761,7 +770,7 @@ function LEGION:CheckMissionQueue()
|
||||
-- Reduce number of reinforcements.
|
||||
if reinforce then
|
||||
mission.reinforce=mission.reinforce-#assets
|
||||
self:I(self.lid..string.format("Reinforced with N=%d Nreinforce=%d", #assets, mission.reinforce))
|
||||
self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d", #assets, mission.reinforce))
|
||||
end
|
||||
|
||||
return true
|
||||
@@ -1823,6 +1832,7 @@ function LEGION:_CreateFlightGroup(asset)
|
||||
---
|
||||
|
||||
opsgroup=ARMYGROUP:New(asset.spawngroupname)
|
||||
opsgroup:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits)
|
||||
|
||||
elseif self:IsFleet() then
|
||||
|
||||
@@ -1891,7 +1901,7 @@ function LEGION:_TacticalOverview()
|
||||
for _,mtype in pairs(AUFTRAG.Type) do
|
||||
local n=self:CountMissionsInQueue(mtype)
|
||||
if n>0 then
|
||||
local N=self:CountMissionsInQueue(mtype)
|
||||
local N=self:CountMissionsInQueue(mtype, true)
|
||||
text=text..string.format(" - %s: %d [Running=%d]\n", mtype, n, N)
|
||||
end
|
||||
end
|
||||
@@ -2047,18 +2057,23 @@ end
|
||||
--- Count missions in mission queue.
|
||||
-- @param #LEGION self
|
||||
-- @param #table MissionTypes Types on mission to be checked. Default *all* possible types `AUFTRAG.Type`.
|
||||
-- @param #boolean OnlyRunning If `true`, only count running missions.
|
||||
-- @return #number Number of missions that are not over yet.
|
||||
function LEGION:CountMissionsInQueue(MissionTypes)
|
||||
function LEGION:CountMissionsInQueue(MissionTypes, OnlyRunning)
|
||||
|
||||
MissionTypes=MissionTypes or AUFTRAG.Type
|
||||
|
||||
local N=0
|
||||
for _,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
if (not OnlyRunning) or (mission.statusLegion~=AUFTRAG.Status.PLANNED) then
|
||||
|
||||
-- Check if this mission type is requested.
|
||||
if mission:IsNotOver() and AUFTRAG.CheckMissionType(mission.type, MissionTypes) then
|
||||
N=N+1
|
||||
-- Check if this mission type is requested.
|
||||
if mission:IsNotOver() and AUFTRAG.CheckMissionType(mission.type, MissionTypes) then
|
||||
N=N+1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -86,12 +86,14 @@ NAVYGROUP = {
|
||||
-- @field Ops.Target#TARGET Target The target.
|
||||
-- @field Core.Point#COORDINATE Coordinate Last known coordinate of the target.
|
||||
-- @field Ops.OpsGroup#OPSGROUP.Waypoint Waypoint the waypoint created to go to the target.
|
||||
-- @field #number Speed Speed in knots.
|
||||
-- @field #number Depth Depth of the engagement (submarines).
|
||||
-- @field #number roe ROE backup.
|
||||
-- @field #number alarmstate Alarm state backup.
|
||||
|
||||
--- NavyGroup version.
|
||||
-- @field #string version
|
||||
NAVYGROUP.version="1.0.3"
|
||||
NAVYGROUP.version="1.0.4"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -1081,7 +1083,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
|
||||
text=text..string.format("Elements = %d\n", #self.elements)
|
||||
text=text..string.format("Waypoints = %d\n", #self.waypoints)
|
||||
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
|
||||
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
|
||||
text=text..string.format("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Cannons, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
|
||||
text=text..string.format("FSM state = %s\n", self:GetState())
|
||||
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
|
||||
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
|
||||
@@ -1602,8 +1604,10 @@ end
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Wrapper.Group#GROUP Group the group to be engaged.
|
||||
function NAVYGROUP:onafterEngageTarget(From, Event, To, Target)
|
||||
-- @param Ops.Target#TARGET Target The target to be engaged. Can also be a GROUP or UNIT object.
|
||||
-- @param #number Speed Attack speed in knots.
|
||||
-- @param #number Depth The depth in meters. Only for submarins.
|
||||
function NAVYGROUP:onafterEngageTarget(From, Event, To, Target, Speed, Depth)
|
||||
self:T(self.lid.."Engaging Target")
|
||||
|
||||
if Target:IsInstanceOf("TARGET") then
|
||||
@@ -1615,11 +1619,9 @@ function NAVYGROUP:onafterEngageTarget(From, Event, To, Target)
|
||||
-- Target coordinate.
|
||||
self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate())
|
||||
|
||||
|
||||
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
|
||||
-- Get a coordinate close to the target.
|
||||
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.8)
|
||||
|
||||
|
||||
|
||||
-- Backup ROE and alarm state.
|
||||
self.engage.roe=self:GetROE()
|
||||
self.engage.alarmstate=self:GetAlarmstate()
|
||||
@@ -1629,11 +1631,17 @@ function NAVYGROUP:onafterEngageTarget(From, Event, To, Target)
|
||||
self:SwitchROE(ENUMS.ROE.OpenFire)
|
||||
|
||||
-- ID of current waypoint.
|
||||
local uid=self:GetWaypointCurrent().uid
|
||||
local uid=self:GetWaypointCurrentUID()
|
||||
|
||||
-- Set formation.
|
||||
self.engage.Depth=Depth or 0
|
||||
|
||||
-- Set speed.
|
||||
self.engage.Speed=Speed
|
||||
|
||||
-- Add waypoint after current.
|
||||
self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true)
|
||||
|
||||
self.engage.Waypoint=self:AddWaypoint(intercoord, Speed, uid, Depth, true)
|
||||
|
||||
-- Set if we want to resume route after reaching the detour waypoint.
|
||||
self.engage.Waypoint.detour=1
|
||||
|
||||
@@ -1656,24 +1664,22 @@ function NAVYGROUP:_UpdateEngageTarget()
|
||||
-- Check if target moved more than 100 meters.
|
||||
if dist>100 then
|
||||
|
||||
--env.info("FF Update Engage Target Moved "..self.engage.Target:GetName())
|
||||
|
||||
-- Update new position.
|
||||
self.engage.Coordinate:UpdateFromVec3(vec3)
|
||||
|
||||
-- ID of current waypoint.
|
||||
local uid=self:GetWaypointCurrent().uid
|
||||
local uid=self:GetWaypointCurrentUID()
|
||||
|
||||
-- Remove current waypoint
|
||||
self:RemoveWaypointByID(self.engage.Waypoint.uid)
|
||||
|
||||
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
|
||||
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.8)
|
||||
|
||||
-- Add waypoint after current.
|
||||
self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true)
|
||||
self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Depth, true)
|
||||
|
||||
-- Set if we want to resume route after reaching the detour waypoint.
|
||||
self.engage.Waypoint.detour=0
|
||||
self.engage.Waypoint.detour=1
|
||||
|
||||
end
|
||||
|
||||
@@ -1709,8 +1715,8 @@ function NAVYGROUP:onafterDisengage(From, Event, To)
|
||||
local task=self:GetTaskCurrent()
|
||||
|
||||
-- Get if current task is ground attack.
|
||||
if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then
|
||||
self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!")
|
||||
if task and (task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT) then
|
||||
self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!")
|
||||
self:TaskDone(task)
|
||||
end
|
||||
|
||||
|
||||
@@ -405,7 +405,9 @@ OPSGROUP.TaskType={
|
||||
--- Ammo data.
|
||||
-- @type OPSGROUP.Ammo
|
||||
-- @field #number Total Total amount of ammo.
|
||||
-- @field #number Guns Amount of gun shells.
|
||||
-- @field #number Shells Amount of shells (guns + cannons).
|
||||
-- @field #number Guns Amount of gun shells (caliber < 25).
|
||||
-- @field #number Cannons Amount of cannon shells (caliber >= 25).
|
||||
-- @field #number Bombs Amount of bombs.
|
||||
-- @field #number Rockets Amount of rockets.
|
||||
-- @field #number Torpedos Amount of torpedos.
|
||||
@@ -1189,8 +1191,8 @@ function OPSGROUP:GetDCSObject()
|
||||
return self.dcsgroup
|
||||
end
|
||||
|
||||
--- Set detection on or off.
|
||||
-- If detection is on, detected targets of the group will be evaluated and FSM events triggered.
|
||||
--- Make a target (unit, group, opsgroup) known to this group.
|
||||
-- This is useing the DCS function `controller.knowTarget`.
|
||||
-- @param #OPSGROUP self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE TargetObject The target object.
|
||||
-- @param #boolean KnowType Make type known.
|
||||
@@ -4300,7 +4302,27 @@ function OPSGROUP:_UpdateTask(Task, Mission)
|
||||
Mission=Mission or self:GetMissionByTaskID(self.taskcurrent)
|
||||
|
||||
if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then
|
||||
|
||||
|
||||
if Mission.type == AUFTRAG.Type.RESCUEHELO then
|
||||
self:T("**********")
|
||||
self:T("** RESCUEHELO USED")
|
||||
self:T("**********")
|
||||
local param=Task.dcstask.params
|
||||
local followUnit=UNIT:FindByName(param.unitname)
|
||||
local helogroupname = self:GetGroup():GetName()
|
||||
Task.formation = RESCUEHELO:New(followUnit,helogroupname)
|
||||
Task.formation:SetRespawnOnOff(false)
|
||||
Task.formation.respawninair=false
|
||||
Task.formation:SetTakeoffCold()
|
||||
Task.formation:SetHomeBase(followUnit)
|
||||
Task.formation.helo = self:GetGroup()
|
||||
-- Start formation FSM.
|
||||
Task.formation:Start()
|
||||
if self:IsFlightgroup() then
|
||||
self:SetDespawnAfterLanding()
|
||||
end
|
||||
else
|
||||
|
||||
-- Set of group(s) to follow Mother.
|
||||
local followSet=SET_GROUP:New():AddGroup(self.group)
|
||||
|
||||
@@ -4309,7 +4331,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
|
||||
local followUnit=UNIT:FindByName(param.unitname)
|
||||
|
||||
-- Define AI Formation object.
|
||||
Task.formation=AI_FORMATION:New(followUnit, followSet, AUFTRAG.SpecialTask.FORMATION, "Follow X at given parameters.")
|
||||
Task.formation=FORMATION:New(followUnit, followSet, AUFTRAG.SpecialTask.FORMATION)
|
||||
|
||||
-- Formation parameters.
|
||||
Task.formation:FormationCenterWing(-param.offsetX, 50, math.abs(param.altitude), 50, param.offsetZ, 50)
|
||||
@@ -4318,11 +4340,13 @@ function OPSGROUP:_UpdateTask(Task, Mission)
|
||||
Task.formation:SetFollowTimeInterval(param.dtFollow)
|
||||
|
||||
-- Formation mode.
|
||||
Task.formation:SetFlightModeFormation(self.group)
|
||||
--Task.formation:SetFlightModeFormation(self.group)
|
||||
|
||||
-- Start formation FSM.
|
||||
Task.formation:Start()
|
||||
|
||||
|
||||
end
|
||||
|
||||
elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then
|
||||
|
||||
---
|
||||
@@ -4513,6 +4537,25 @@ function OPSGROUP:_UpdateTask(Task, Mission)
|
||||
if target then
|
||||
self:EngageTarget(target, speed, Task.dcstask.params.formation)
|
||||
end
|
||||
|
||||
elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then
|
||||
|
||||
---
|
||||
-- Task "Naval Engagement" Mission.
|
||||
---
|
||||
|
||||
-- Engage target.
|
||||
local target=Task.dcstask.params.target --Ops.Target#TARGET
|
||||
|
||||
-- Set speed. Default max.
|
||||
local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax) or nil
|
||||
if Task.dcstask.params.speed then
|
||||
speed=UTILS.MpsToKnots(Task.dcstask.params.speed)
|
||||
end
|
||||
|
||||
if target then
|
||||
self:EngageTarget(target, speed, Task.dcstask.params.altitude)
|
||||
end
|
||||
|
||||
elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then
|
||||
|
||||
@@ -4631,7 +4674,12 @@ function OPSGROUP:_UpdateTask(Task, Mission)
|
||||
self:T(self.lid..string.format("Zone %s captured ==> Task DONE!", zoneCurr:GetName()))
|
||||
|
||||
-- Task done.
|
||||
self:TaskDone(Task)
|
||||
if Task.StayInZoneTime then
|
||||
local stay = Task.StayInZoneTime
|
||||
self:__TaskDone(stay,Task)
|
||||
else
|
||||
self:TaskDone(Task)
|
||||
end
|
||||
|
||||
else
|
||||
-- Current zone NOT captured yet ==> Find Target
|
||||
@@ -4734,7 +4782,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
|
||||
elseif weaponType==ENUMS.WeaponFlag.AnyRocket then
|
||||
nAmmo=ammo.Rockets
|
||||
elseif weaponType==ENUMS.WeaponFlag.Cannons then
|
||||
nAmmo=ammo.Guns
|
||||
nAmmo=ammo.Cannons
|
||||
end
|
||||
|
||||
--TODO: Update target location while we're at it anyway.
|
||||
@@ -4881,7 +4929,7 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task)
|
||||
done=true
|
||||
elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD or Task.dcstask.id==AUFTRAG.SpecialTask.ARMOREDGUARD then
|
||||
done=true
|
||||
elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then
|
||||
elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then
|
||||
done=true
|
||||
elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then
|
||||
done=true
|
||||
@@ -5713,7 +5761,7 @@ end
|
||||
function OPSGROUP:onafterMissionDone(From, Event, To, Mission)
|
||||
|
||||
-- Debug info.
|
||||
local text=string.format("Mission %s DONE!", Mission.name)
|
||||
local text=string.format("Mission DONE %s!", Mission.name)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Set group status.
|
||||
@@ -5730,7 +5778,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission)
|
||||
-- Decrease patrol data.
|
||||
if Mission.patroldata then
|
||||
Mission.patroldata.noccupied=Mission.patroldata.noccupied-1
|
||||
AIRWING.UpdatePatrolPointMarker(Mission.patroldata)
|
||||
AIRWING.UpdatePatrolPointMarker(self,Mission.patroldata)
|
||||
end
|
||||
|
||||
-- Switch auto engage detected off. This IGNORES that engage detected had been activated for the group!
|
||||
@@ -6226,11 +6274,12 @@ function OPSGROUP:RouteToMission(mission, delay)
|
||||
self:T(self.lid.."Already in mission zone ==> TaskExecute()")
|
||||
self:TaskExecute(waypointtask)
|
||||
-- TODO: Calling PassingWaypoint here is probably better as it marks the mission waypoint as passed!
|
||||
--self:PassingWaypoint(waypoint)
|
||||
self:PassingWaypoint(waypoint)
|
||||
return
|
||||
elseif d<25 then
|
||||
self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()")
|
||||
self:TaskExecute(waypointtask)
|
||||
self:PassingWaypoint(waypoint)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -7534,7 +7583,7 @@ end
|
||||
function OPSGROUP:onafterElementDead(From, Event, To, Element)
|
||||
|
||||
-- Debug info.
|
||||
self:I(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime()))
|
||||
self:T(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime()))
|
||||
|
||||
-- Set element status.
|
||||
self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD)
|
||||
@@ -7845,10 +7894,15 @@ function OPSGROUP:_Spawn(Delay, Template)
|
||||
self:ScheduleOnce(Delay, OPSGROUP._Spawn, self, 0, Template)
|
||||
else
|
||||
-- Debug output.
|
||||
self:T2({Template=Template})
|
||||
--self:T2({Template=Template})
|
||||
|
||||
if self:IsArmygroup() and self.ValidateAndRepositionGroundUnits then
|
||||
UTILS.ValidateAndRepositionGroundUnits(Template.units)
|
||||
end
|
||||
|
||||
-- Spawn new group.
|
||||
self.group=_DATABASE:Spawn(Template)
|
||||
self.group:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits)
|
||||
--local countryID=self.group:GetCountry()
|
||||
--local categoryID=self.group:GetCategory()
|
||||
--local dcsgroup=coalition.addGroup(countryID, categoryID, Template)
|
||||
@@ -7988,11 +8042,16 @@ function OPSGROUP:onafterDead(From, Event, To)
|
||||
-- Get asset.
|
||||
local asset=self.legion:GetAssetByName(self.groupname)
|
||||
|
||||
if asset then
|
||||
|
||||
-- Get request.
|
||||
local request=self.legion:GetRequestByID(asset.rid)
|
||||
|
||||
-- Trigger asset dead event.
|
||||
self.legion:AssetDead(asset, request)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Stop in 5 sec to give possible respawn attempts a chance.
|
||||
@@ -8085,7 +8144,7 @@ function OPSGROUP:onafterStop(From, Event, To)
|
||||
_DATABASE.FLIGHTGROUPS[self.groupname]=nil
|
||||
|
||||
-- Debug output.
|
||||
self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE")
|
||||
self:T(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE")
|
||||
end
|
||||
|
||||
--- On after "OutOfAmmo" event.
|
||||
@@ -11180,11 +11239,11 @@ function OPSGROUP:_CheckAmmoStatus()
|
||||
self:OutOfAmmo()
|
||||
end
|
||||
|
||||
-- Guns.
|
||||
if self.outofGuns and ammo.Guns>0 then
|
||||
-- Guns (changed to shells)
|
||||
if self.outofGuns and ammo.Shells>0 then
|
||||
self.outofGuns=false
|
||||
end
|
||||
if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then
|
||||
if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then
|
||||
self.outofGuns=true
|
||||
self:OutOfGuns()
|
||||
end
|
||||
@@ -12685,7 +12744,7 @@ end
|
||||
-- @return #OPSGROUP self
|
||||
function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber)
|
||||
|
||||
self:T(self.lid..string.format("Setting Default callsing %s-%s", tostring(CallsignName), tostring(CallsignNumber)))
|
||||
self:T(self.lid..string.format("Setting Default callsign %s-%s", tostring(CallsignName), tostring(CallsignNumber)))
|
||||
|
||||
self.callsignDefault={} --#OPSGROUP.Callsign
|
||||
self.callsignDefault.NumberSquad=CallsignName
|
||||
@@ -13360,7 +13419,9 @@ function OPSGROUP:GetAmmoTot()
|
||||
|
||||
local Ammo={} --#OPSGROUP.Ammo
|
||||
Ammo.Total=0
|
||||
Ammo.Shells=0
|
||||
Ammo.Guns=0
|
||||
Ammo.Cannons=0
|
||||
Ammo.Rockets=0
|
||||
Ammo.Bombs=0
|
||||
Ammo.Torpedos=0
|
||||
@@ -13381,7 +13442,9 @@ function OPSGROUP:GetAmmoTot()
|
||||
|
||||
-- Add up total.
|
||||
Ammo.Total=Ammo.Total+ammo.Total
|
||||
Ammo.Shells=Ammo.Shells+ammo.Shells
|
||||
Ammo.Guns=Ammo.Guns+ammo.Guns
|
||||
Ammo.Cannons=Ammo.Cannons+ammo.Cannons
|
||||
Ammo.Rockets=Ammo.Rockets+ammo.Rockets
|
||||
Ammo.Bombs=Ammo.Bombs+ammo.Bombs
|
||||
Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos
|
||||
@@ -13414,6 +13477,8 @@ function OPSGROUP:GetAmmoUnit(unit, display)
|
||||
-- Init counter.
|
||||
local nammo=0
|
||||
local nshells=0
|
||||
local nguns=0
|
||||
local ncannons=0
|
||||
local nrockets=0
|
||||
local nmissiles=0
|
||||
local nmissilesAA=0
|
||||
@@ -13436,10 +13501,10 @@ function OPSGROUP:GetAmmoUnit(unit, display)
|
||||
local ammotable=unit:GetAmmo()
|
||||
|
||||
if ammotable then
|
||||
|
||||
local weapons=#ammotable
|
||||
|
||||
--self:I(ammotable)
|
||||
--self:I(ammotable)
|
||||
--UTILS.PrintTableToLog(ammotable)
|
||||
|
||||
-- Loop over all weapons.
|
||||
for w=1,weapons do
|
||||
@@ -13447,9 +13512,9 @@ function OPSGROUP:GetAmmoUnit(unit, display)
|
||||
-- Number of current weapon.
|
||||
local Nammo=ammotable[w]["count"]
|
||||
|
||||
-- Range in meters. Seems only to exist for missiles (not shells).
|
||||
local rmin=ammotable[w]["desc"]["rangeMin"] or 0
|
||||
local rmax=ammotable[w]["desc"]["rangeMaxAltMin"] or 0
|
||||
-- Range in meters. Seems only to exist for missiles (not shells).
|
||||
local rmin=ammotable[w]["desc"]["rangeMin"] or 0
|
||||
local rmax=ammotable[w]["desc"]["rangeMaxAltMin"] or 0
|
||||
|
||||
-- Type name of current weapon.
|
||||
local Tammo=ammotable[w]["desc"]["typeName"]
|
||||
@@ -13471,6 +13536,16 @@ function OPSGROUP:GetAmmoUnit(unit, display)
|
||||
|
||||
-- Add up all shells.
|
||||
nshells=nshells+Nammo
|
||||
|
||||
-- Add small and large caliber shells for guns and cannons
|
||||
if ammotable[w]["desc"]["warhead"] and ammotable[w]["desc"]["warhead"]["caliber"] then
|
||||
local caliber=ammotable[w]["desc"]["warhead"]["caliber"]
|
||||
if caliber<25 then
|
||||
nguns=nguns+Nammo
|
||||
else
|
||||
ncannons=ncannons+Nammo
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
text=text..string.format("- %d shells of type %s, range=%d - %d meters\n", Nammo, _weaponName, rmin, rmax)
|
||||
@@ -13549,7 +13624,9 @@ function OPSGROUP:GetAmmoUnit(unit, display)
|
||||
|
||||
local ammo={} --#OPSGROUP.Ammo
|
||||
ammo.Total=nammo
|
||||
ammo.Guns=nshells
|
||||
ammo.Shells=nshells
|
||||
ammo.Guns=nguns
|
||||
ammo.Cannons=ncannons
|
||||
ammo.Rockets=nrockets
|
||||
ammo.Bombs=nbombs
|
||||
ammo.Torpedos=ntorps
|
||||
@@ -13955,6 +14032,15 @@ function OPSGROUP:_GetDetectedTarget()
|
||||
return targetgroup, targetdist
|
||||
end
|
||||
|
||||
--- This function uses Disposition and other fallback logic to find better ground positions for ground units.
|
||||
--- NOTE: This is not a spawn randomizer.
|
||||
--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table.
|
||||
--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit.
|
||||
--- Uses UTILS.ValidateAndRepositionGroundUnits.
|
||||
-- @param #boolean Enabled Enable/disable the feature.
|
||||
function OPSGROUP:SetValidateAndRepositionGroundUnits(Enabled)
|
||||
self.ValidateAndRepositionGroundUnits = Enabled
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-- ===
|
||||
-- @module Ops.PlayerTask
|
||||
-- @image OPS_PlayerTask.jpg
|
||||
-- @date Last Update May 2025
|
||||
-- @date Last Update Dec 2025
|
||||
|
||||
|
||||
do
|
||||
@@ -59,6 +59,9 @@ do
|
||||
-- @field #string FinalState
|
||||
-- @field #string TypeName
|
||||
-- @field #number PreviousCount
|
||||
-- @field #boolean CanSmoke
|
||||
-- @field #boolean ShowThreatDetails
|
||||
-- @field #boolean PersistMe
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@@ -94,11 +97,14 @@ PLAYERTASK = {
|
||||
NextTaskFailure = {},
|
||||
FinalState = "none",
|
||||
PreviousCount = 0,
|
||||
CanSmoke = true,
|
||||
ShowThreatDetails = true,
|
||||
PersistMe = false,
|
||||
}
|
||||
|
||||
--- PLAYERTASK class version.
|
||||
-- @field #string version
|
||||
PLAYERTASK.version="0.1.27"
|
||||
PLAYERTASK.version="0.1.31"
|
||||
|
||||
--- Generic task condition.
|
||||
-- @type PLAYERTASK.Condition
|
||||
@@ -231,6 +237,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType)
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #boolean Silent If true, suppress message output on cancel.
|
||||
|
||||
--- On After "Planned" event. Task has been planned.
|
||||
-- @function [parent=#PLAYERTASK] OnAfterPilotPlanned
|
||||
@@ -387,9 +394,26 @@ function PLAYERTASK:_CheckCaptureOpsZoneSuccess(OpsZone, CaptureSquadGroupNamePr
|
||||
return OpsZone:GetOwner() == Coalition and isClientInZone and isCaptureGroupInZone
|
||||
end
|
||||
|
||||
--- [User] Override this function in order to implement custom logic if a player can join a task or not.
|
||||
-- @param #PLAYERTASK self
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
-- @return #boolean Outcome True if player can join the task, false if not
|
||||
function PLAYERTASK:CanJoinTask(Group, Client)
|
||||
return true
|
||||
end
|
||||
|
||||
--- [User] Set this task for persistance, if persistance is enabled on the PLAYERTASKCONTROLLER instance.
|
||||
-- @param #PLAYERTASK self
|
||||
-- @return #PLAYERTASK self
|
||||
function PLAYERTASK:EnablePersistance()
|
||||
self.PersistMe = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Add a PLAYERTASKCONTROLLER for this task
|
||||
-- @param #PLAYERTASK self
|
||||
--
|
||||
-- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller
|
||||
-- @return #PLAYERTASK self
|
||||
function PLAYERTASK:_SetController(Controller)
|
||||
@@ -460,6 +484,26 @@ function PLAYERTASK:SetSubType(Type)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [USER] Set if a task can have a smoke marker.
|
||||
-- @param #PLAYERTASK self
|
||||
-- @param #boolean OnOff If true (default) it can be smoke, false if not.
|
||||
-- @return #PLAYERTASK self
|
||||
function PLAYERTASK:SetCanSmoke(OnOff)
|
||||
self:T(self.lid.."AddSSetCanSmokeubType")
|
||||
self.CanSmoke = OnOff
|
||||
return self
|
||||
end
|
||||
|
||||
--- [USER] Set if a task can show threat details.
|
||||
-- @param #PLAYERTASK self
|
||||
-- @param #boolean OnOff If true (default) it can be shown, false if not.
|
||||
-- @return #PLAYERTASK self
|
||||
function PLAYERTASK:SetShowThreatDetails(OnOff)
|
||||
self:T(self.lid.."SetShowThreatDetails")
|
||||
self.ShowThreatDetails = OnOff
|
||||
return self
|
||||
end
|
||||
|
||||
--- [USER] Get task sub type description from this task.
|
||||
-- @param #PLAYERTASK self
|
||||
-- @return #string Type or nil
|
||||
@@ -1055,7 +1099,6 @@ function PLAYERTASK:onafterStatus(From, Event, To)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- [Internal] On after progress call
|
||||
-- @param #PLAYERTASK self
|
||||
-- @param #string From
|
||||
@@ -1165,11 +1208,12 @@ end
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #boolean Silent
|
||||
-- @return #PLAYERTASK self
|
||||
function PLAYERTASK:onafterCancel(From, Event, To)
|
||||
function PLAYERTASK:onafterCancel(From, Event, To, Silent)
|
||||
self:T({From, Event, To})
|
||||
if self.TaskController then
|
||||
self.TaskController:__TaskCancelled(-1,self)
|
||||
self.TaskController:__TaskCancelled(-1,self, Silent)
|
||||
end
|
||||
self.timestamp = timer.getAbsTime()
|
||||
self.FinalState = "Cancelled"
|
||||
@@ -1227,7 +1271,10 @@ function PLAYERTASK:onafterFailed(From, Event, To)
|
||||
self.TargetMarker:Remove()
|
||||
end
|
||||
self.FinalState = "Failed"
|
||||
self:__Done(-1)
|
||||
if self.TaskController then
|
||||
self.TaskController:__TaskFailed(-1,self)
|
||||
end
|
||||
self:__Done(-1.5)
|
||||
end
|
||||
if self.TaskController.Scoring then
|
||||
local clients,count = self:GetClientObjects()
|
||||
@@ -1326,6 +1373,12 @@ do
|
||||
-- @field Core.ClientMenu#CLIENTMENU MenuNoTask
|
||||
-- @field #boolean InformationMenu Show Radio Info Menu
|
||||
-- @field #number TaskInfoDuration How long to show the briefing info on the screen
|
||||
-- @field #table TaskPersistance Table for persistance data
|
||||
-- @field #boolean TaskPersistanceSwitch Switch for persisting tasks
|
||||
-- @field #string TaskPersistancePath File path for persisting tasks
|
||||
-- @field #string TaskPersistanceFilename File name for persisting tasks
|
||||
-- @field #table TasksPersistable List of persistable tasks
|
||||
-- @field #number SceneryExplosivesAmount Kgs of TNT to explode scenery on task persistance loading
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
---
|
||||
@@ -1546,6 +1599,7 @@ do
|
||||
-- ELEVATION = "\nTarget Elevation: %s %s",
|
||||
-- METER = "meter",
|
||||
-- FEET = "feet",
|
||||
-- INTERCEPTCOURSE = "Intercept course",
|
||||
-- },
|
||||
--
|
||||
-- e.g.
|
||||
@@ -1595,7 +1649,7 @@ do
|
||||
--
|
||||
-- The event is triggered when a task was cancelled manually. Use @{#PLAYERTASKCONTROLLER.OnAfterTaskCancelled}()` to link into this event:
|
||||
--
|
||||
-- function taskmanager:OnAfterTaskCancelled(From, Event, To, Task)
|
||||
-- function taskmanager:OnAfterTaskCancelled(From, Event, To, Task, Silent)
|
||||
-- ... your code here ...
|
||||
-- end
|
||||
--
|
||||
@@ -1632,8 +1686,28 @@ do
|
||||
--
|
||||
-- Set a marker on the map and add the following text to create targets from it: "TARGET". This is effectively the same as adding a COORDINATE object as target.
|
||||
-- The marker can be deleted any time.
|
||||
--
|
||||
-- ## 9 Discussion
|
||||
--
|
||||
-- ## 9 Single Task Persistence for mission designer added tasks
|
||||
--
|
||||
-- The class can persist the state of single tasks of type BOMBING, PRECISIONBOMBING, ARTY and SEAD, i.e. tasks which have a GROUND(!) GROUP, UNIT, STATIC or SCENERY as target.
|
||||
-- This requires the task to have a unique(!) menu name set, a TARGET which already exists on the map at mission start(!), and a flag that this task is actually to be persisted.
|
||||
-- Also, you need to desanitize the mission scripting environment, i.e. "lfs" and "io" must be available so we can write to disk.
|
||||
--
|
||||
-- -- First, we need to enable on the PLAYERTASKCONTROLLER itself
|
||||
-- taskmanager:EnableTaskPersistance([[C:\Users\myname\Saved Games\DCS\Missions\MyMisionFolder\]],"Mission Tasks.csv") -- Path and Filename
|
||||
--
|
||||
-- -- Then, we can design a task marking mission progress that we want to persist
|
||||
-- local RussianRadios = SET_STATIC:New():FilterPrefixes("Comms Tower Russia"):FilterOnce()
|
||||
--
|
||||
-- local RadioTask = PLAYERTASK:New(AUFTRAG.Type.BOMBING,RussianRadios,true,5,"Bombing")
|
||||
-- RadioTask:SetMenuName("Neutralize Comms Towers") -- UNIQUE menu name so we can find the task later!
|
||||
-- RadioTask:AddFreetext("Find and neutralize the two communication towers near NB70 East of Fulda on Streufelsberg!")
|
||||
-- RadioTask:AddFreetextTTS("Find and neutralize the two communication towers naer N;B;7;zero; East of Fulda on Streufelsberg!")
|
||||
-- RadioTask:EnablePersistance() -- Enable persistence for this task
|
||||
--
|
||||
-- taskmanager:AddPlayerTaskToQueue(RadioTask,true,false)
|
||||
--
|
||||
-- ## 10 Discussion
|
||||
--
|
||||
-- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) #ops-playertask channel.
|
||||
--
|
||||
@@ -1683,6 +1757,12 @@ PLAYERTASKCONTROLLER = {
|
||||
MenuNoTask = nil,
|
||||
InformationMenu = false,
|
||||
TaskInfoDuration = 30,
|
||||
TaskPersistance = {},
|
||||
TaskPersistanceSwitch = false,
|
||||
TaskPersistancePath = nil,
|
||||
TaskPersistanceFilename = nil,
|
||||
TasksPersistable = {},
|
||||
SceneryExplosivesAmount = 300,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -1703,6 +1783,7 @@ AUFTRAG.Type.PRECISIONBOMBING = "Precision Bombing"
|
||||
AUFTRAG.Type.CTLD = "Combat Transport"
|
||||
AUFTRAG.Type.CSAR = "Combat Rescue"
|
||||
AUFTRAG.Type.CONQUER = "Conquer"
|
||||
|
||||
---
|
||||
-- @type Scores
|
||||
PLAYERTASKCONTROLLER.Scores = {
|
||||
@@ -1722,6 +1803,24 @@ PLAYERTASKCONTROLLER.Scores = {
|
||||
[AUFTRAG.Type.CAP] = 100,
|
||||
[AUFTRAG.Type.CAPTUREZONE] = 100,
|
||||
}
|
||||
|
||||
---
|
||||
-- @type TasksPersistable
|
||||
PLAYERTASKCONTROLLER.TasksPersistable = {
|
||||
[AUFTRAG.Type.PRECISIONBOMBING] = true,
|
||||
[AUFTRAG.Type.BOMBING] = true,
|
||||
[AUFTRAG.Type.ARTY] = true,
|
||||
[AUFTRAG.Type.SEAD] = true,
|
||||
}
|
||||
|
||||
---
|
||||
-- @type PersistenceData
|
||||
-- @field #number ID
|
||||
-- @field #string Name
|
||||
-- @field #string Type
|
||||
-- @field #number InitialTargets
|
||||
-- @field #number Targetsleft
|
||||
-- @field #boolean updated
|
||||
|
||||
---
|
||||
-- @type SeadAttributes
|
||||
@@ -1759,12 +1858,15 @@ PLAYERTASKCONTROLLER.Messages = {
|
||||
THREATMEDIUM = "medium",
|
||||
THREATLOW = "low",
|
||||
THREATTEXT = "%s\nThreat: %s\nTargets left: %d\nCoord: %s",
|
||||
NOTHREATTEXT = "%s\nNo target information available.",
|
||||
ELEVATION = "\nTarget Elevation: %s %s",
|
||||
METER = "meter",
|
||||
FEET = "feet",
|
||||
THREATTEXTTTS = "%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.",
|
||||
NOTHREATTEXTTTS = "%s, %s. No target information available.",
|
||||
MARKTASK = "%s, %s, copy, task %03d location marked on map!",
|
||||
SMOKETASK = "%s, %s, copy, task %03d location smoked!",
|
||||
NOSMOKETASK = "%s, %s, negative, task %03d location cannot be smoked!",
|
||||
FLARETASK = "%s, %s, copy, task %03d location illuminated!",
|
||||
ABORTTASK = "All stations, %s, %s has aborted %s task %03d!",
|
||||
UNKNOWN = "Unknown",
|
||||
@@ -1820,6 +1922,7 @@ PLAYERTASKCONTROLLER.Messages = {
|
||||
DESTROYER = "Destroyer",
|
||||
CARRIER = "Aircraft Carrier",
|
||||
RADIOS = "Radios",
|
||||
INTERCEPTCOURSE = "Intercept course",
|
||||
},
|
||||
DE = {
|
||||
TASKABORT = "Auftrag abgebrochen!",
|
||||
@@ -1843,12 +1946,15 @@ PLAYERTASKCONTROLLER.Messages = {
|
||||
THREATMEDIUM = "mittel",
|
||||
THREATLOW = "niedrig",
|
||||
THREATTEXT = "%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s",
|
||||
NOTHREATTEXT = "%s\nKeine Zielinformation verfügbar.",
|
||||
ELEVATION = "\nZiel Höhe: %s %s",
|
||||
METER = "Meter",
|
||||
FEET = "Fuss",
|
||||
THREATTEXTTTS = "%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.",
|
||||
NOTHREATTEXTTTS = "%s, %s. Keine Zielinformation verfügbar.",
|
||||
MARKTASK = "%s, %s, verstanden, Zielposition %03d auf der Karte markiert!",
|
||||
SMOKETASK = "%s, %s, verstanden, Zielposition %03d mit Rauch markiert!",
|
||||
NOSMOKETASK = "%s, %s, negativ, Zielposition %03d kann nicht markiert werden!",
|
||||
FLARETASK = "%s, %s, verstanden, Zielposition %03d beleuchtet!",
|
||||
ABORTTASK = "%s, an alle, %s hat Auftrag %s %03d abgebrochen!",
|
||||
UNKNOWN = "Unbekannt",
|
||||
@@ -1904,12 +2010,13 @@ PLAYERTASKCONTROLLER.Messages = {
|
||||
DESTROYER = "Zerstörer",
|
||||
CARRIER = "Flugzeugträger",
|
||||
RADIOS = "Frequenzen",
|
||||
INTERCEPTCOURSE = "Abfangkurs",
|
||||
},
|
||||
}
|
||||
|
||||
--- PLAYERTASK class version.
|
||||
-- @field #string version
|
||||
PLAYERTASKCONTROLLER.version="0.1.70"
|
||||
PLAYERTASKCONTROLLER.version="0.1.73"
|
||||
|
||||
--- Create and run a new TASKCONTROLLER instance.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
@@ -2016,7 +2123,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
||||
self:AddTransition("*", "Stop", "Stopped")
|
||||
|
||||
self:__Start(2)
|
||||
local starttime = math.random(5,10)
|
||||
local starttime = math.random(10,15)
|
||||
self:__Status(starttime)
|
||||
|
||||
self:I(self.lid..self.version.." Started.")
|
||||
@@ -2050,6 +2157,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.PlayerTask#PLAYERTASK Task
|
||||
-- @param #boolean Silent If true suppress message output.
|
||||
|
||||
--- On After "TaskFailed" event. Task has failed.
|
||||
-- @function [parent=#PLAYERTASKCONTROLLER] OnAfterTaskFailed
|
||||
@@ -2130,6 +2238,30 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
||||
|
||||
end
|
||||
|
||||
--- [User] Enable Task persistance (for specific gound target tasks)
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #string Path Path where to save the task data
|
||||
-- @param #string Filename File name under which to save the task data
|
||||
-- @param #number KgsOfTNT (Optional) Explosives kgs used to remove scenery for persistence, defaults to 300
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT)
|
||||
self.TaskPersistanceSwitch = true
|
||||
self.TaskPersistancePath = Path
|
||||
self.TaskPersistanceFilename = Filename
|
||||
self.SceneryExplosivesAmount = KgsOfTNT or 300
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Disable Task persistance (for specific gound target tasks)
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:DisableTaskPersistance()
|
||||
self.TaskPersistanceSwitch = false
|
||||
self.TaskPersistancePath = nil
|
||||
self.TaskPersistanceFilename = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set or create a SCORING object for this taskcontroller
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Functional.Scoring#SCORING Scoring (optional) the Scoring object
|
||||
@@ -2730,11 +2862,12 @@ end
|
||||
|
||||
--- [User] Manually cancel a specific task
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Ops.PlayerTask#PLAYERTASK Task The task to be cancelled
|
||||
-- @param Ops.PlayerTask#PLAYERTASK Task The task to be cancelled.
|
||||
-- @param #boolean Silent If true suppress message output.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:CancelTask(Task)
|
||||
function PLAYERTASKCONTROLLER:CancelTask(Task,Silent)
|
||||
self:T(self.lid.."CancelTask")
|
||||
Task:__Cancel(-1)
|
||||
Task:__Cancel(-1,Silent)
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2816,15 +2949,6 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType()
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
for _type,_data in pairs(tasktypes) do
|
||||
self:I("Task Type: ".._type)
|
||||
for _id,_task in pairs(_data) do
|
||||
self:I("Task Name: ".._task.Target:GetName())
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
return tasktypes
|
||||
end
|
||||
|
||||
@@ -3521,15 +3645,25 @@ function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter)
|
||||
PlayerTask:_SetController(self)
|
||||
PlayerTask:SetCoalition(self.Coalition)
|
||||
self.TaskQueue:Push(PlayerTask)
|
||||
if not Silent then
|
||||
self:__TaskAdded(10,PlayerTask)
|
||||
end
|
||||
--if not Silent then
|
||||
self:__TaskAdded(10,PlayerTask,Silent)
|
||||
--end
|
||||
else
|
||||
self:E(self.lid.."***** NO valid PAYERTASK object sent!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Override this function in order to implement custom logic if a player can join a task or not.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Ops.PlayerTask#PLAYERTASK Task
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
-- @return #boolean Outcome True if player can join the task, false if not
|
||||
function PLAYERTASKCONTROLLER:CanJoinTask(Task, Group, Client)
|
||||
return true
|
||||
end
|
||||
|
||||
--- [Internal] Join a player to a task
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Ops.PlayerTask#PLAYERTASK Task
|
||||
@@ -3540,6 +3674,15 @@ end
|
||||
function PLAYERTASKCONTROLLER:_JoinTask(Task, Force, Group, Client)
|
||||
self:T({Force, Group, Client})
|
||||
self:T(self.lid.."_JoinTask")
|
||||
|
||||
if not self:CanJoinTask(Task, Group, Client) then
|
||||
return self
|
||||
end
|
||||
|
||||
if not Task:CanJoinTask(Group, Client) then
|
||||
return self
|
||||
end
|
||||
|
||||
local force = false
|
||||
if type(Force) == "boolean" then
|
||||
force = Force
|
||||
@@ -3636,6 +3779,41 @@ function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group, Client)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Calculate group future position after given seconds.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Wrapper.Group#GROUP group The group to calculate for.
|
||||
-- @param #number seconds Time interval in seconds. Default is `self.prediction`.
|
||||
-- @return Core.Point#COORDINATE Calculated future position of the cluster.
|
||||
function PLAYERTASKCONTROLLER:_CalcGroupFuturePosition(group, seconds)
|
||||
|
||||
-- Get current position of the cluster.
|
||||
local p=group:GetCoordinate()
|
||||
|
||||
-- Velocity vector in m/s.
|
||||
local v=group:GetVelocityVec3()
|
||||
|
||||
-- Time in seconds.
|
||||
local t=seconds or self.prediction
|
||||
|
||||
-- Extrapolated vec3.
|
||||
local Vec3={x=p.x+v.x*t, y=p.y+v.y*t, z=p.z+v.z*t}
|
||||
|
||||
-- Future position.
|
||||
local futureposition=COORDINATE:NewFromVec3(Vec3)
|
||||
|
||||
-- Create an arrow pointing in the direction of the movement.
|
||||
if self.verbose == true then
|
||||
local markerID = group:GetProperty("PLAYERTASK_ARROW")
|
||||
if markerID then
|
||||
COORDINATE:RemoveMark(markerID)
|
||||
end
|
||||
markerID = p:ArrowToAll(futureposition, self.coalition, {1,0,0}, 1, {1,1,0}, 0.5, 2, true, "Position Calc")
|
||||
group:SetProperty("PLAYERTASK_ARROW",markerID)
|
||||
end
|
||||
|
||||
return futureposition
|
||||
end
|
||||
|
||||
--- [Internal] Flashing directional info for a client
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
@@ -3645,16 +3823,34 @@ function PLAYERTASKCONTROLLER:_FlashInfo()
|
||||
if _client and _client:IsAlive() then
|
||||
if self.TasksPerPlayer:HasUniqueID(_playername) then
|
||||
local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK
|
||||
local Coordinate = task.Target:GetCoordinate()
|
||||
local Coordinate = task.Target:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local CoordText = ""
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then
|
||||
CoordText = Coordinate:ToStringA2G(_client, nil, self.ShowMagnetic)
|
||||
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
|
||||
local text = targettxt..": "..CoordText
|
||||
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
|
||||
else
|
||||
CoordText = Coordinate:ToStringA2A(_client, nil, self.ShowMagnetic)
|
||||
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
|
||||
local text = targettxt..": "..CoordText
|
||||
-- calc intercept position
|
||||
local name=task.Target:GetName()
|
||||
local group = GROUP:FindByName(name)
|
||||
local clientcoord = _client:GetCoordinate()
|
||||
if group and clientcoord and group:IsAlive() and task.Type==AUFTRAG.Type.INTERCEPT then
|
||||
local speed = math.max(UTILS.KnotsToMps(350) or _client:GetVelocityMPS())
|
||||
local dist = Coordinate:Get3DDistance(clientcoord)
|
||||
local iTime = math.floor(dist/speed)+5
|
||||
if iTime < 10 then iTime = 10
|
||||
elseif iTime > 600 then iTime = 600 end
|
||||
local npos = self:_CalcGroupFuturePosition(group,iTime)
|
||||
local BR = npos:ToStringBearing(clientcoord,nil,self.ShowMagnetic,0 )
|
||||
local Intercepttext = self.gettext:GetEntry("INTERCEPTCOURSE",self.locale)
|
||||
text = text .. "\n"..Intercepttext.." "..BR
|
||||
end
|
||||
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
|
||||
end
|
||||
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
|
||||
local text = "Target: "..CoordText
|
||||
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3704,8 +3900,9 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
|
||||
local Elevation = Coordinate:GetLandHeight() or 0 -- meters
|
||||
local CoordText = ""
|
||||
local CoordTextLLDM = nil
|
||||
local ShowThreatInfo = task.ShowThreatDetails
|
||||
local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr)
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then
|
||||
CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic)
|
||||
else
|
||||
CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic)
|
||||
@@ -3727,7 +3924,12 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
|
||||
local clientlist, clientcount = task:GetClients()
|
||||
local ThreatGraph = "[" .. string.rep( "■", ThreatLevel ) .. string.rep( "□", 10 - ThreatLevel ) .. "]: "..ThreatLevel
|
||||
local ThreatLocaleText = self.gettext:GetEntry("THREATTEXT",self.locale)
|
||||
text = string.format(ThreatLocaleText, taskname, ThreatGraph, targets, CoordText)
|
||||
if ShowThreatInfo == true then
|
||||
text = string.format(ThreatLocaleText, taskname, ThreatGraph, targets, CoordText)
|
||||
else
|
||||
ThreatLocaleText = self.gettext:GetEntry("NOTHREATTEXT",self.locale)
|
||||
text = string.format(ThreatLocaleText, taskname)
|
||||
end
|
||||
local settings = _DATABASE:GetPlayerSettings(playername) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
local elevationmeasure = self.gettext:GetEntry("FEET",self.locale)
|
||||
if settings:IsMetric() then
|
||||
@@ -3737,8 +3939,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
|
||||
Elevation = math.floor(UTILS.MetersToFeet(Elevation))
|
||||
end
|
||||
-- ELEVATION = "\nTarget Elevation: %s %s",
|
||||
local elev = self.gettext:GetEntry("ELEVATION",self.locale)
|
||||
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
|
||||
if task.Type ~= AUFTRAG.Type.INTERCEPT then
|
||||
local elev = self.gettext:GetEntry("ELEVATION",self.locale)
|
||||
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
|
||||
end
|
||||
-- Prec bombing
|
||||
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
|
||||
if LasingDrone and LasingDrone.playertask then
|
||||
@@ -3850,9 +4054,19 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
|
||||
end
|
||||
--self:I(self.lid.." | ".. CoordText)
|
||||
end
|
||||
local ttstext
|
||||
local ThreatLocaleTextTTS = self.gettext:GetEntry("THREATTEXTTTS",self.locale)
|
||||
local ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText, targets, CoordText)
|
||||
-- THREATTEXT = "%s\nThreat: %s\nTargets left: %d\nCoord: %s",
|
||||
-- THREATTEXTTTS = "%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.",
|
||||
if ShowThreatInfo == true then
|
||||
ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText, targets, CoordText)
|
||||
else
|
||||
ThreatLocaleTextTTS = self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale)
|
||||
ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name)
|
||||
end
|
||||
|
||||
-- POINTERTARGETLASINGTTS = ". Pointer over target and lasing."
|
||||
|
||||
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
|
||||
if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing() then
|
||||
local lasingtext = self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale)
|
||||
@@ -3921,15 +4135,25 @@ function PLAYERTASKCONTROLLER:_SmokeTask(Group, Client)
|
||||
local text = ""
|
||||
if self.TasksPerPlayer:HasUniqueID(playername) then
|
||||
local task = self.TasksPerPlayer:ReadByID(playername) -- Ops.PlayerTask#PLAYERTASK
|
||||
task:SmokeTarget()
|
||||
local textmark = self.gettext:GetEntry("SMOKETASK",self.locale)
|
||||
text = string.format(textmark, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr)
|
||||
self:T(self.lid..text)
|
||||
--local m=MESSAGE:New(text,"10","Tasking"):ToAll()
|
||||
if self.UseSRS then
|
||||
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
|
||||
if task.CanSmoke == true then
|
||||
task:SmokeTarget()
|
||||
local textmark = self.gettext:GetEntry("SMOKETASK",self.locale)
|
||||
text = string.format(textmark, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr)
|
||||
self:T(self.lid..text)
|
||||
--local m=MESSAGE:New(text,"10","Tasking"):ToAll()
|
||||
if self.UseSRS then
|
||||
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
|
||||
end
|
||||
self:__TaskTargetSmoked(5,task)
|
||||
else
|
||||
local textmark = self.gettext:GetEntry("NOSMOKETASK",self.locale)
|
||||
text = string.format(textmark, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr)
|
||||
self:T(self.lid..text)
|
||||
--local m=MESSAGE:New(text,"10","Tasking"):ToAll()
|
||||
if self.UseSRS then
|
||||
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
|
||||
end
|
||||
end
|
||||
self:__TaskTargetSmoked(5,task)
|
||||
else
|
||||
text = self.gettext:GetEntry("NOACTIVETASK",self.locale)
|
||||
end
|
||||
@@ -4494,6 +4718,76 @@ function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Add an corridor zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Core.Zone#ZONE CorridorZone Add a zone to the corridor zone set.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone)
|
||||
self:T(self.lid.."AddCorridorZone")
|
||||
if self.Intel then
|
||||
self.Intel:AddCorridorZone(CorridorZone)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Add an corridor SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Core.Set#SET_ZONE CorridorZoneSet Add a SET_ZONE to the corridor zone set.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet)
|
||||
self:T(self.lid.."AddCorridorZoneSet")
|
||||
if self.Intel then
|
||||
self.Intel.corridorzoneset:AddSet(CorridorZoneSet)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Remove an corridor zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Core.Zone#ZONE CorridorZone Remove this zone from the corridor zone set.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone)
|
||||
self:T(self.lid.."RemoveCorridorZone")
|
||||
if self.Intel then
|
||||
self.Intel:RemoveCorridorZone(CorridorZone)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in FEET.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #number Floor Floor altitude ASL in feet.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in feet.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
|
||||
if self.Intel then
|
||||
self.Intel:SetCorridorLimitsFeet(Floor,Ceiling)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in METERS.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #number Floor Floor altitude ASL in meters.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in meters.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
|
||||
if self.Intel then
|
||||
self.Intel:SetCorridorLimits(Floor,Ceiling)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set the top menu name to a custom string.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #string Name The name to use as the top menu designation.
|
||||
@@ -4675,6 +4969,128 @@ function PLAYERTASKCONTROLLER:SetSRSBroadcast(Frequency,Modulation)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Ops.PlayerTask#PLASERTASK Task
|
||||
-- @param #number TargetsLeft
|
||||
function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft)
|
||||
self:T(self.lid.."_UpdateTargetsAlive")
|
||||
local delta = Task.Target:CountTargets() - TargetsLeft
|
||||
if delta > 0 then
|
||||
self:T("Delta targets to be removed: "..delta)
|
||||
local count = 0
|
||||
local targets = Task.Target:GetObjects()
|
||||
for _,_object in pairs(targets or {}) do
|
||||
if _object and _object.ClassName and (_object:IsInstanceOf("GROUP") or _object:IsInstanceOf("UNIT") or _object:IsInstanceOf("STATIC") or _object:IsInstanceOf("SCENERY")) then
|
||||
if count < delta then
|
||||
count = count + 1
|
||||
if not _object:IsInstanceOf("SCENERY") then
|
||||
_object:Destroy(true)
|
||||
else
|
||||
_object:Explode(self.SceneryExplosivesAmount)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:_LoadTasksPersisted()
|
||||
self:T(self.lid.."_LoadTasksPersisted")
|
||||
|
||||
local function MatchTask(Type,Name)
|
||||
local foundtask
|
||||
self.TaskQueue:ForEach(
|
||||
function(_task)
|
||||
local task = _task -- #PLAYERTASK
|
||||
if task.Type == Type and task.Target.name and task.Target.name == Name then
|
||||
foundtask = task
|
||||
end
|
||||
end
|
||||
)
|
||||
return foundtask
|
||||
end
|
||||
|
||||
if lfs and io then
|
||||
local ok,data = UTILS.LoadFromFile(self.TaskPersistancePath,self.TaskPersistanceFilename)
|
||||
if ok == true then
|
||||
table.remove(data, 1)
|
||||
for _,_entry in pairs(data) do
|
||||
-- "--ID;;Name;;InitialTargets;;Targetsleft;;Type\n"
|
||||
local dataset = UTILS.Split(_entry,";;")
|
||||
local Taskdata = {} -- #PersistenceData
|
||||
Taskdata.ID = tonumber(dataset[1])
|
||||
Taskdata.Name = tostring(dataset[2])
|
||||
Taskdata.InitialTargets = tonumber(dataset[3])
|
||||
Taskdata.Targetsleft = tonumber(dataset[4])
|
||||
Taskdata.Type = tostring(dataset[5])
|
||||
Taskdata.Task = MatchTask(Taskdata.Type,Taskdata.Name)
|
||||
if Taskdata.Task == nil then
|
||||
self:E(self.lid.."No actual task found for "..Taskdata.Name)
|
||||
else
|
||||
self:T(self.lid.."Task loaded and match found for "..Taskdata.Name)
|
||||
end
|
||||
Taskdata.updated = Taskdata.InitialTargets == Taskdata.Targetsleft and true or false
|
||||
if Taskdata.Task and Taskdata.updated == false then
|
||||
self:_UpdateTargetsAlive(Taskdata.Task,Taskdata.Targetsleft)
|
||||
Taskdata.updated = true
|
||||
end
|
||||
self.TaskPersistance[Taskdata.ID] = Taskdata
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Clear persisted data on disk.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:ClearPersistedData()
|
||||
if lfs and io then
|
||||
local text = "-- Data Cleared\n"
|
||||
UTILS.SaveToFile(self.TaskPersistancePath,self.TaskPersistanceFilename,text)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:_SaveTasksPersisted()
|
||||
if lfs and io then
|
||||
local text = "--ID;;Name;;InitialTargets;;Targetsleft;;Type\n"
|
||||
for _,_data in pairs(self.TaskPersistance) do
|
||||
local data = _data -- #PersistenceData
|
||||
data.Targetsleft = data.Task.Target:CountTargets() -- recount
|
||||
if data.Task and data.Task:IsDone() then data.Targetsleft = 0 end
|
||||
local tasktext = string.format("%d;;%s;;%d;;%d;;%s\n",data.ID,data.Name,data.InitialTargets,data.Targetsleft,data.Type)
|
||||
text = text..tasktext
|
||||
end
|
||||
UTILS.SaveToFile(self.TaskPersistancePath,self.TaskPersistanceFilename,text)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #PLAYERTASK Task
|
||||
function PLAYERTASKCONTROLLER:_AddPersistenceData(Task)
|
||||
local Taskdata = {} -- #PersistenceData
|
||||
if not self.TaskPersistance[Task.PlayerTaskNr] then
|
||||
Taskdata.ID = Task.PlayerTaskNr
|
||||
Taskdata.Name = Task.Target.name or "none"
|
||||
Taskdata.InitialTargets = Task.Target:CountTargets()
|
||||
Taskdata.Targetsleft = Taskdata.InitialTargets
|
||||
Taskdata.Type = Task.Type
|
||||
Taskdata.updated = true
|
||||
Taskdata.Task = Task
|
||||
self.TaskPersistance[Task.PlayerTaskNr] = Taskdata
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions PLAYERTASKCONTROLLER
|
||||
-- TODO: FSM Functions PLAYERTASKCONTROLLER
|
||||
@@ -4698,7 +5114,12 @@ function PLAYERTASKCONTROLLER:onafterStart(From, Event, To)
|
||||
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.UnitLost, self._EventHandler)
|
||||
self:SetEventPriority(5)
|
||||
self:SetEventPriority(5)
|
||||
-- Persistence
|
||||
if self.TaskPersistanceSwitch == true then
|
||||
self:ScheduleOnce(5,self._LoadTasksPersisted,self)
|
||||
--self:_LoadTasksPersisted()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -4738,6 +5159,11 @@ function PLAYERTASKCONTROLLER:onafterStatus(From, Event, To)
|
||||
self:I(text)
|
||||
end
|
||||
|
||||
-- Persistence
|
||||
if self.TaskPersistanceSwitch == true then
|
||||
self:_SaveTasksPersisted()
|
||||
end
|
||||
|
||||
if self:GetState() ~= "Stopped" then
|
||||
self:__Status(-30)
|
||||
end
|
||||
@@ -4763,22 +5189,27 @@ end
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Ops.PlayerTask#PLAYERTASK Task
|
||||
-- @param #boolean Silent If true, suppress message output on cancel.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:onafterTaskCancelled(From, Event, To, Task)
|
||||
function PLAYERTASKCONTROLLER:onafterTaskCancelled(From, Event, To, Task, Silent)
|
||||
self:T({From, Event, To})
|
||||
self:T(self.lid.."TaskCancelled")
|
||||
local canceltxt = self.gettext:GetEntry("TASKCANCELLED",self.locale)
|
||||
local canceltxttts = self.gettext:GetEntry("TASKCANCELLEDTTS",self.locale)
|
||||
local taskname = string.format(canceltxt, Task.PlayerTaskNr, tostring(Task.Type))
|
||||
if not self.NoScreenOutput then
|
||||
self:_SendMessageToClients(taskname,15)
|
||||
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
|
||||
if Silent ~= true then
|
||||
local canceltxt = self.gettext:GetEntry("TASKCANCELLED",self.locale)
|
||||
local canceltxttts = self.gettext:GetEntry("TASKCANCELLEDTTS",self.locale)
|
||||
local taskname = string.format(canceltxt, Task.PlayerTaskNr, tostring(Task.Type))
|
||||
|
||||
if self.NoScreenOutput ~= true then
|
||||
self:_SendMessageToClients(taskname,15)
|
||||
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
|
||||
end
|
||||
|
||||
if self.UseSRS then
|
||||
taskname = string.format(canceltxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType))
|
||||
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
|
||||
end
|
||||
|
||||
end
|
||||
if self.UseSRS then
|
||||
taskname = string.format(canceltxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType))
|
||||
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
|
||||
end
|
||||
|
||||
local clients=Task:GetClientObjects()
|
||||
for _,client in pairs(clients) do
|
||||
self:_RemoveMenuEntriesForTask(Task,client)
|
||||
@@ -4888,19 +5319,26 @@ end
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Ops.PlayerTask#PLAYERTASK Task
|
||||
-- @param #boolean Silent
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:onafterTaskAdded(From, Event, To, Task)
|
||||
function PLAYERTASKCONTROLLER:onafterTaskAdded(From, Event, To, Task, Silent)
|
||||
self:T({From, Event, To})
|
||||
self:T(self.lid.."TaskAdded")
|
||||
local addtxt = self.gettext:GetEntry("TASKADDED",self.locale)
|
||||
local taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.Type))
|
||||
if not self.NoScreenOutput then
|
||||
self:_SendMessageToClients(taskname,15)
|
||||
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
|
||||
if not Silent then
|
||||
if not self.NoScreenOutput then
|
||||
self:_SendMessageToClients(taskname,15)
|
||||
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
|
||||
end
|
||||
if self.UseSRS then
|
||||
taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType))
|
||||
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
|
||||
end
|
||||
end
|
||||
if self.UseSRS then
|
||||
taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType))
|
||||
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
|
||||
self:T(self.lid..string.format("Pers = %s | Type = %s | TypePers = %s | TaskFlag = %s",tostring(self.TaskPersistanceSwitch),tostring(Task.Type),tostring(self.TasksPersistable[Task.Type]),tostring(Task.PersistMe)))
|
||||
if self.TaskPersistanceSwitch == true and self.TasksPersistable[Task.Type] == true and Task.PersistMe == true then
|
||||
self:_AddPersistenceData(Task)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -310,7 +310,7 @@ _RECOVERYTANKERID=0
|
||||
|
||||
--- Class version.
|
||||
-- @field #string version
|
||||
RECOVERYTANKER.version="1.0.10"
|
||||
RECOVERYTANKER.version="1.0.11"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -923,6 +923,22 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
|
||||
Spawn:InitRadioCommsOnOff(true)
|
||||
Spawn:InitRadioFrequency(self.RadioFreq)
|
||||
Spawn:InitRadioModulation(self.RadioModu)
|
||||
|
||||
if self.callsignname and self.callsignnumber then
|
||||
local grp = GROUP:FindByName(self.tankergroupname)
|
||||
if grp then
|
||||
local typename = grp:GetTypeName() or ""
|
||||
--self:I(self.lid.."Typename: "..typename)
|
||||
local Name
|
||||
local enumerator = CALLSIGN.Tanker
|
||||
if typename == "A6E" then
|
||||
enumerator = CALLSIGN.Intruder
|
||||
end
|
||||
Name = self:_GetCallsignName(self.callsignname,enumerator)
|
||||
--self:I(self.lid.."CallsignName: "..Name)
|
||||
Spawn:InitCallSign(self.callsignname,Name,self.callsignnumber,self.callsignnumber)
|
||||
end
|
||||
end
|
||||
Spawn:InitModex(self.modex)
|
||||
|
||||
-- Spawn on carrier.
|
||||
@@ -1190,9 +1206,9 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
|
||||
-- Task combo.
|
||||
|
||||
-- Be a tanker or be an AWACS.
|
||||
local taskroll = self.tanker:EnRouteTaskTanker()
|
||||
local taskrole = self.tanker:EnRouteTaskTanker()
|
||||
if self.awacs then
|
||||
taskroll=self.tanker:EnRouteTaskAWACS()
|
||||
taskrole=self.tanker:EnRouteTaskAWACS()
|
||||
end
|
||||
|
||||
--local taskeplrs=self.tanker:TaskEPLRS(true, 2)
|
||||
@@ -1201,7 +1217,7 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
|
||||
local taskroute = self.tanker:TaskRoute(wp)
|
||||
|
||||
-- Note that the order is important here! tasktanker has to come first. Otherwise it does not work.
|
||||
local taskcombo = self.tanker:TaskCombo({taskroll, taskroute})
|
||||
local taskcombo = self.tanker:TaskCombo({taskrole, taskroute})
|
||||
|
||||
-- Set task.
|
||||
self.tanker:SetTask(taskcombo, 1)
|
||||
@@ -1450,6 +1466,20 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
|
||||
|
||||
end
|
||||
|
||||
--- Get clear name callsign for spawn from enumerator
|
||||
-- @param #RECOVERYTANKER self
|
||||
-- @param #number Callsign
|
||||
-- @param #table Enumerator The table of callsigns, e.g. `CALLSIGN.Tanker`
|
||||
-- @return #string Name Name or "" if not found
|
||||
function RECOVERYTANKER:_GetCallsignName(Callsign, Enumerator)
|
||||
for name, value in pairs(Enumerator or {}) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
--- A unit crashed or died.
|
||||
-- @param #RECOVERYTANKER self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- ### Contributions: Flightcontrol (@{AI.AI_Formation} class being used here)
|
||||
--
|
||||
-- @module Ops.RescueHelo
|
||||
-- @image Ops_RescueHelo.png
|
||||
@@ -37,7 +36,7 @@
|
||||
-- @field #number takeoff Takeoff type.
|
||||
-- @field Wrapper.Airbase#AIRBASE airbase The airbase object acting as home base of the helo.
|
||||
-- @field Core.Set#SET_GROUP followset Follow group set.
|
||||
-- @field AI.AI_Formation#AI_FORMATION formation AI_FORMATION object.
|
||||
-- @field Functional.Formation#FORMATION formation FORMATION object.
|
||||
-- @field #number lowfuel Low fuel threshold of helo in percent.
|
||||
-- @field #number altitude Altitude of helo in meters.
|
||||
-- @field #number offsetX Offset in meters to carrier in longitudinal direction.
|
||||
@@ -235,7 +234,7 @@ _RESCUEHELOID=0
|
||||
|
||||
--- Class version.
|
||||
-- @field #string version
|
||||
RESCUEHELO.version="1.1.0"
|
||||
RESCUEHELO.version="1.2.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -248,6 +247,7 @@ RESCUEHELO.version="1.1.0"
|
||||
-- DONE: Possibility to add already present/spawned aircraft, e.g. for warehouse.
|
||||
-- DONE: Add rescue event when aircraft crashes.
|
||||
-- DONE: Make offset input parameter.
|
||||
-- DONE: Make useable for AUFTRAG
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -337,6 +337,9 @@ function RESCUEHELO:New(carrierunit, helogroupname)
|
||||
self:AddTransition("*", "Status", "*")
|
||||
self:AddTransition("*", "Stop", "Stopped")
|
||||
|
||||
|
||||
self:I(self.lid.."Started.")
|
||||
|
||||
|
||||
--- Triggers the FSM event "Start" that starts the rescue helo. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#RESCUEHELO] Start
|
||||
@@ -871,15 +874,25 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
|
||||
-- Delay before formation is started.
|
||||
local delay=120
|
||||
|
||||
-- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine.
|
||||
local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias)
|
||||
|
||||
-- Set modex for spawn.
|
||||
Spawn:InitModex(self.modex)
|
||||
|
||||
local UsesAliveGroup=false
|
||||
local Spawn = GROUP:FindByName(self.helogroupname)
|
||||
if Spawn and Spawn:IsAlive() then
|
||||
self.helo=Spawn
|
||||
UsesAliveGroup = true
|
||||
delay = 1
|
||||
else
|
||||
|
||||
-- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine.
|
||||
Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias)
|
||||
|
||||
-- Set modex for spawn.
|
||||
Spawn:InitModex(self.modex)
|
||||
|
||||
end
|
||||
|
||||
-- Spawn in air or at airbase.
|
||||
if self.takeoff==SPAWN.Takeoff.Air then
|
||||
if UsesAliveGroup==false and self.takeoff==SPAWN.Takeoff.Air then
|
||||
|
||||
-- Carrier heading
|
||||
local hdg=self.carrier:GetHeading()
|
||||
@@ -899,7 +912,7 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
-- Start formation in 1 seconds
|
||||
delay=1
|
||||
|
||||
else
|
||||
elseif UsesAliveGroup==false and self.uncontrolledac then
|
||||
|
||||
-- Check if an uncontrolled helo group was requested.
|
||||
if self.uncontrolledac then
|
||||
@@ -919,9 +932,9 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
-- No group of that name!
|
||||
self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!", self.helogroupname))
|
||||
return
|
||||
end
|
||||
|
||||
else
|
||||
end
|
||||
end
|
||||
elseif UsesAliveGroup==false then
|
||||
|
||||
-- Spawn at airbase.
|
||||
self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable)
|
||||
@@ -935,9 +948,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
delay=60
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- Set of group(s) to follow Mother.
|
||||
self.followset=SET_GROUP:New()
|
||||
@@ -947,17 +959,13 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
self.HeloFuel0=self.helo:GetFuel()
|
||||
|
||||
-- Define AI Formation object.
|
||||
self.formation=AI_FORMATION:New(self.carrier, self.followset, "Helo Formation with Carrier", "Follow Carrier at given parameters.")
|
||||
|
||||
self.formation=FORMATION:New(self.carrier, self.followset, "Helo Formation with Carrier")
|
||||
-- Formation parameters.
|
||||
self.formation:FormationCenterWing(-self.offsetX, 50, math.abs(self.altitude), 50, self.offsetZ, 50)
|
||||
|
||||
-- Set follow time interval.
|
||||
self.formation:SetFollowTimeInterval(self.dtFollow)
|
||||
|
||||
-- Formation mode.
|
||||
self.formation:SetFlightModeFormation(self.helo)
|
||||
|
||||
-- Start formation FSM.
|
||||
self.formation:__Start(delay)
|
||||
|
||||
|
||||
@@ -2006,9 +2006,10 @@ end
|
||||
--- Count alive objects.
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object Target Target objective.
|
||||
-- @param #table Coalitions (Optional) Only count targets of the given coalition(s).
|
||||
-- @param #table Coalitions (Optional) Only count targets of the given coalition(s).
|
||||
-- @param #boolean OnlyReallyAlive (Optional) If `true`, count only really alive targets (units, groups) but not coordinates or zones.
|
||||
-- @return #number Number of alive target objects.
|
||||
function TARGET:CountObjectives(Target, Coalitions)
|
||||
function TARGET:CountObjectives(Target, Coalitions, OnlyReallyAlive)
|
||||
|
||||
local N=0
|
||||
|
||||
@@ -2067,13 +2068,17 @@ function TARGET:CountObjectives(Target, Coalitions)
|
||||
|
||||
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
|
||||
-- This is also more consitent with the life and is alive status.
|
||||
N=N+1
|
||||
if not OnlyReallyAlive then
|
||||
N=N+1
|
||||
end
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.ZONE then
|
||||
|
||||
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
|
||||
-- This is also more consitent with the life and is alive status.
|
||||
N=N+1
|
||||
if not OnlyReallyAlive then
|
||||
N=N+1
|
||||
end
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.OPSZONE then
|
||||
|
||||
@@ -2093,15 +2098,16 @@ end
|
||||
--- Count alive targets.
|
||||
-- @param #TARGET self
|
||||
-- @param #table Coalitions (Optional) Only count targets of the given coalition(s).
|
||||
-- @param #boolean OnlyReallyAlive (Optional) If `true`, count only really alive targets (units, groups) but not coordinates or zones.
|
||||
-- @return #number Number of alive target objects.
|
||||
function TARGET:CountTargets(Coalitions)
|
||||
function TARGET:CountTargets(Coalitions, OnlyReallyAlive)
|
||||
|
||||
local N=0
|
||||
|
||||
for _,_target in pairs(self.targets) do
|
||||
local Target=_target --#TARGET.Object
|
||||
|
||||
N=N+self:CountObjectives(Target, Coalitions)
|
||||
N=N+self:CountObjectives(Target, Coalitions, OnlyReallyAlive)
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ end
|
||||
|
||||
--- Checks if a point is contained within the circle.
|
||||
-- @param #table point The point to check
|
||||
-- @return #bool True if the point is contained, false otherwise
|
||||
-- @return #boolean True if the point is contained, false otherwise
|
||||
function CIRCLE:ContainsPoint(point)
|
||||
if ((point.x - self.CenterVec2.x) ^ 2 + (point.y - self.CenterVec2.y) ^ 2) ^ 0.5 <= self.Radius then
|
||||
return true
|
||||
@@ -226,6 +226,11 @@ end
|
||||
--- Returns a random Vec2 within the circle.
|
||||
-- @return #table The random Vec2
|
||||
function CIRCLE:GetRandomVec2()
|
||||
|
||||
math.random()
|
||||
math.random()
|
||||
math.random()
|
||||
|
||||
local angle = math.random() * 2 * math.pi
|
||||
|
||||
local rx = math.random(0, self.Radius) * math.cos(angle) + self.CenterVec2.x
|
||||
@@ -237,6 +242,11 @@ end
|
||||
--- Returns a random Vec2 on the border of the circle.
|
||||
-- @return #table The random Vec2
|
||||
function CIRCLE:GetRandomVec2OnBorder()
|
||||
|
||||
math.random()
|
||||
math.random()
|
||||
math.random()
|
||||
|
||||
local angle = math.random() * 2 * math.pi
|
||||
|
||||
local rx = self.Radius * math.cos(angle) + self.CenterVec2.x
|
||||
|
||||
@@ -352,6 +352,7 @@ end
|
||||
--- Returns a random Vec2 within the polygon. The Vec2 is weighted by the areas of the triangles that make up the polygon.
|
||||
-- @return #table The random Vec2
|
||||
function POLYGON:GetRandomVec2()
|
||||
|
||||
local weights = {}
|
||||
for _, triangle in pairs(self.Triangles) do
|
||||
weights[triangle] = triangle.SurfaceArea / self.SurfaceArea
|
||||
|
||||
@@ -73,6 +73,11 @@ end
|
||||
-- @param #table points The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it
|
||||
-- @return #table The random Vec2
|
||||
function TRIANGLE:GetRandomVec2(points)
|
||||
|
||||
math.random()
|
||||
math.random()
|
||||
math.random()
|
||||
|
||||
points = points or self.Points
|
||||
local pt = {math.random(), math.random()}
|
||||
table.sort(pt)
|
||||
|
||||
@@ -443,28 +443,32 @@ MSRS.Voices = {
|
||||
["en_AU_Standard_B"] = 'en-AU-Standard-B', -- [2] MALE
|
||||
["en_AU_Standard_C"] = 'en-AU-Standard-C', -- [3] FEMALE
|
||||
["en_AU_Standard_D"] = 'en-AU-Standard-D', -- [4] MALE
|
||||
["en_IN_Standard_A"] = 'en-IN-Standard-A', -- [5] FEMALE
|
||||
["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE
|
||||
["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE
|
||||
["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE
|
||||
-- IN
|
||||
["en_IN_Standard_A"] = 'en-IN-Standard-A', -- Female
|
||||
["en_IN_Standard_B"] = 'en-IN-Standard-B', -- Male
|
||||
["en_IN_Standard_C"] = 'en-IN-Standard-C', -- Male
|
||||
["en_IN_Standard_D"] = 'en-IN-Standard-D', -- Female
|
||||
["en_IN_Standard_E"] = 'en-IN-Standard-E', -- Female
|
||||
["en_IN_Standard_F"] = 'en-IN-Standard-F', -- Male
|
||||
-- 2025 changes
|
||||
["en_GB_Standard_A"] = 'en-GB-Standard-N', -- [9] FEMALE
|
||||
["en_GB_Standard_B"] = 'en-GB-Standard-O', -- [10] MALE
|
||||
["en_GB_Standard_C"] = 'en-GB-Standard-N', -- [11] FEMALE
|
||||
["en_GB_Standard_D"] = 'en-GB-Standard-O', -- [12] MALE
|
||||
["en_GB_Standard_F"] = 'en-GB-Standard-N', -- [13] FEMALE
|
||||
["en_GB_Standard_O"] = 'en-GB-Standard-O', -- [12] MALE
|
||||
["en_GB_Standard_N"] = 'en-GB-Standard-N', -- [13] FEMALE
|
||||
["en_US_Standard_A"] = 'en-US-Standard-A', -- [14] MALE
|
||||
["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE
|
||||
["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE
|
||||
["en_US_Standard_D"] = 'en-US-Standard-D', -- [17] MALE
|
||||
["en_US_Standard_E"] = 'en-US-Standard-E', -- [18] FEMALE
|
||||
["en_US_Standard_F"] = 'en-US-Standard-F', -- [19] FEMALE
|
||||
["en_US_Standard_G"] = 'en-US-Standard-G', -- [20] FEMALE
|
||||
["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE
|
||||
["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE
|
||||
["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE
|
||||
["en_GB_Standard_A"] = 'en-GB-Standard-A', -- Female
|
||||
["en_GB_Standard_B"] = 'en-GB-Standard-B', -- Male
|
||||
["en_GB_Standard_C"] = 'en-GB-Standard-C', -- Female
|
||||
["en_GB_Standard_D"] = 'en-GB-Standard-D', -- Male
|
||||
["en_GB_Standard_F"] = 'en-GB-Standard-F', -- Female
|
||||
["en_GB_Standard_N"] = 'en-GB-Standard-N', -- Female
|
||||
["en_GB_Standard_O"] = 'en-GB-Standard-O', -- Male
|
||||
-- US
|
||||
["en_US_Standard_A"] = 'en-US-Standard-A', -- Male
|
||||
["en_US_Standard_B"] = 'en-US-Standard-B', -- Male
|
||||
["en_US_Standard_C"] = 'en-US-Standard-C', -- Female
|
||||
["en_US_Standard_D"] = 'en-US-Standard-D', -- Male
|
||||
["en_US_Standard_E"] = 'en-US-Standard-E', -- Female
|
||||
["en_US_Standard_F"] = 'en-US-Standard-F', -- Female
|
||||
["en_US_Standard_G"] = 'en-US-Standard-G', -- Female
|
||||
["en_US_Standard_H"] = 'en-US-Standard-H', -- Female
|
||||
["en_US_Standard_I"] = 'en-US-Standard-I', -- Male
|
||||
["en_US_Standard_J"] = 'en-US-Standard-J', -- Male
|
||||
-- 2025 catalog changes
|
||||
["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female
|
||||
["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male
|
||||
@@ -474,14 +478,15 @@ MSRS.Voices = {
|
||||
["fr_FR_Standard_G"] = "fr-FR-Standard-G", -- Male
|
||||
["fr_FR_Standard_F"] = "fr-FR-Standard-F", -- Female
|
||||
-- 2025 catalog changes
|
||||
["de_DE_Standard_A"] = "de-DE-Standard-G", -- Female
|
||||
["de_DE_Standard_B"] = "de-DE-Standard-H", -- Male
|
||||
["de_DE_Standard_C"] = "de-DE-Standard-G", -- Female
|
||||
["de_DE_Standard_D"] = "de-DE-Standard-H", -- Male
|
||||
["de_DE_Standard_E"] = "de-DE-Standard-H", -- Male
|
||||
["de_DE_Standard_F"] = "de-DE-Standard-G", -- Female
|
||||
["de_DE_Standard_H"] = "de-DE-Standard-H", -- Male
|
||||
["de_DE_Standard_G"] = "de-DE-Standard-G", -- Female
|
||||
["de_DE_Standard_A"] = 'de-DE-Standard-A', -- Female
|
||||
["de_DE_Standard_B"] = 'de-DE-Standard-B', -- Male
|
||||
["de_DE_Standard_C"] = 'de-DE-Standard-C', -- Female
|
||||
["de_DE_Standard_D"] = 'de-DE-Standard-D', -- Male
|
||||
["de_DE_Standard_E"] = 'de-DE-Standard-E', -- Male
|
||||
["de_DE_Standard_F"] = 'de-DE-Standard-F', -- Female
|
||||
["de_DE_Standard_G"] = 'de-DE-Standard-G', -- Female
|
||||
["de_DE_Standard_H"] = 'de-DE-Standard-H', -- Male
|
||||
-- ES
|
||||
["es_ES_Standard_A"] = "es-ES-Standard-E", -- Female
|
||||
["es_ES_Standard_B"] = "es-ES-Standard-F", -- Male
|
||||
["es_ES_Standard_C"] = "es-ES-Standard-E", -- Female
|
||||
@@ -497,32 +502,36 @@ MSRS.Voices = {
|
||||
["it_IT_Standard_F"] = "it-IT-Standard-F", -- Male
|
||||
},
|
||||
Wavenet = {
|
||||
["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE
|
||||
["en_AU_Wavenet_B"] = 'en-AU-Wavenet-B', -- [2] MALE
|
||||
["en_AU_Wavenet_C"] = 'en-AU-Wavenet-C', -- [3] FEMALE
|
||||
["en_AU_Wavenet_D"] = 'en-AU-Wavenet-D', -- [4] MALE
|
||||
["en_IN_Wavenet_A"] = 'en-IN-Wavenet-A', -- [5] FEMALE
|
||||
["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE
|
||||
["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE
|
||||
["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE
|
||||
["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- Female
|
||||
["en_AU_Wavenet_B"] = 'en-AU-Wavenet-B', -- Male
|
||||
["en_AU_Wavenet_C"] = 'en-AU-Wavenet-C', -- Female
|
||||
["en_AU_Wavenet_D"] = 'en-AU-Wavenet-D', -- Male
|
||||
-- IN
|
||||
["en_IN_Wavenet_A"] = 'en-IN-Wavenet-A', -- Female
|
||||
["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- Male
|
||||
["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- Male
|
||||
["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- Female
|
||||
["en_IN_Wavenet_E"] = 'en-IN-Wavenet-E', -- Female
|
||||
["en_IN_Wavenet_F"] = 'en-IN-Wavenet-F', -- Male
|
||||
-- 2025 changes
|
||||
["en_GB_Wavenet_A"] = 'en-GB-Wavenet-N', -- [9] FEMALE
|
||||
["en_GB_Wavenet_B"] = 'en-GB-Wavenet-O', -- [10] MALE
|
||||
["en_GB_Wavenet_C"] = 'en-GB-Wavenet-N', -- [11] FEMALE
|
||||
["en_GB_Wavenet_D"] = 'en-GB-Wavenet-O', -- [12] MALE
|
||||
["en_GB_Wavenet_F"] = 'en-GB-Wavenet-N', -- [13] FEMALE
|
||||
["en_GB_Wavenet_A"] = 'en-GB-Wavenet-A', -- [9] FEMALE
|
||||
["en_GB_Wavenet_B"] = 'en-GB-Wavenet-B', -- [10] MALE
|
||||
["en_GB_Wavenet_C"] = 'en-GB-Wavenet-C', -- [11] FEMALE
|
||||
["en_GB_Wavenet_D"] = 'en-GB-Wavenet-D', -- [12] MALE
|
||||
["en_GB_Wavenet_F"] = 'en-GB-Wavenet-F', -- [13] FEMALE
|
||||
["en_GB_Wavenet_O"] = 'en-GB-Wavenet-O', -- [12] MALE
|
||||
["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE
|
||||
["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- [14] MALE
|
||||
["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- [15] MALE
|
||||
["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE
|
||||
["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE
|
||||
["en_US_Wavenet_E"] = 'en-US-Wavenet-E', -- [18] FEMALE
|
||||
["en_US_Wavenet_F"] = 'en-US-Wavenet-F', -- [19] FEMALE
|
||||
["en_US_Wavenet_G"] = 'en-US-Wavenet-G', -- [20] FEMALE
|
||||
["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE
|
||||
["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE
|
||||
["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE
|
||||
["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE
|
||||
-- US
|
||||
["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- Male
|
||||
["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- Male
|
||||
["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- Female
|
||||
["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- Male
|
||||
["en_US_Wavenet_E"] = 'en-US-Wavenet-E', -- Female
|
||||
["en_US_Wavenet_F"] = 'en-US-Wavenet-F', -- Female
|
||||
["en_US_Wavenet_G"] = 'en-US-Wavenet-G', -- Female
|
||||
["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- Female
|
||||
["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- Male
|
||||
["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- Male
|
||||
-- 2025 catalog changes
|
||||
["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female
|
||||
["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male
|
||||
@@ -532,14 +541,15 @@ MSRS.Voices = {
|
||||
["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male
|
||||
["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female
|
||||
-- 2025 catalog changes
|
||||
["de_DE_Wavenet_A"] = "de-DE-Wavenet-G", -- Female
|
||||
["de_DE_Wavenet_B"] = "de-DE-Wavenet-H", -- Male
|
||||
["de_DE_Wavenet_C"] = "de-DE-Wavenet-G", -- Female
|
||||
["de_DE_Wavenet_D"] = "de-DE-Wavenet-H", -- Male
|
||||
["de_DE_Wavenet_E"] = "de-DE-Wavenet-H", -- Male
|
||||
["de_DE_Wavenet_F"] = "de-DE-Wavenet-G", -- Female
|
||||
["de_DE_Wavenet_H"] = "de-DE-Wavenet-H", -- Male
|
||||
["de_DE_Wavenet_G"] = "de-DE-Wavenet-G", -- Female
|
||||
["de_DE_Wavenet_A"] = 'de-DE-Wavenet-A', -- Female
|
||||
["de_DE_Wavenet_B"] = 'de-DE-Wavenet-B', -- Male
|
||||
["de_DE_Wavenet_C"] = 'de-DE-Wavenet-C', -- Female
|
||||
["de_DE_Wavenet_D"] = 'de-DE-Wavenet-D', -- Male
|
||||
["de_DE_Wavenet_E"] = 'de-DE-Wavenet-E', -- Male
|
||||
["de_DE_Wavenet_F"] = 'de-DE-Wavenet-F', -- Female
|
||||
["de_DE_Wavenet_G"] = 'de-DE-Wavenet-G', -- Female
|
||||
["de_DE_Wavenet_H"] = 'de-DE-Wavenet-H', -- Male
|
||||
-- ES
|
||||
["es_ES_Wavenet_B"] = "es-ES-Wavenet-E", -- Male
|
||||
["es_ES_Wavenet_C"] = "es-ES-Wavenet-F", -- Female
|
||||
["es_ES_Wavenet_D"] = "es-ES-Wavenet-E", -- Female
|
||||
@@ -553,6 +563,134 @@ MSRS.Voices = {
|
||||
["it_IT_Wavenet_E"] = "it-IT-Wavenet-E", -- Female
|
||||
["it_IT_Wavenet_F"] = "it-IT-Wavenet-F", -- Male
|
||||
} ,
|
||||
Chirp3HD = {
|
||||
["en_GB_Chirp3_HD_Aoede"] = 'en-GB-Chirp3-HD-Aoede', -- Female
|
||||
["en_GB_Chirp3_HD_Charon"] = 'en-GB-Chirp3-HD-Charon', -- Male
|
||||
["en_GB_Chirp3_HD_Fenrir"] = 'en-GB-Chirp3-HD-Fenrir', -- Male
|
||||
["en_GB_Chirp3_HD_Kore"] = 'en-GB-Chirp3-HD-Kore', -- Female
|
||||
["en_GB_Chirp3_HD_Leda"] = 'en-GB-Chirp3-HD-Leda', -- Female
|
||||
["en_GB_Chirp3_HD_Orus"] = 'en-GB-Chirp3-HD-Orus', -- Male
|
||||
["en_GB_Chirp3_HD_Puck"] = 'en-GB-Chirp3-HD-Puck', -- Male
|
||||
["en_GB_Chirp3_HD_Zephyr"] = 'en-GB-Chirp3-HD-Zephyr', -- Female
|
||||
--["de_DE_Chirp3_HD_Aoede"] = 'de-DE-Chirp3-HD-Aoede', -- Female (Datenfehler im Original)
|
||||
["en_US_Chirp3_HD_Charon"] = 'en-US-Chirp3-HD-Charon', -- Male
|
||||
["en_US_Chirp3_HD_Fenrir"] = 'en-US-Chirp3-HD-Fenrir', -- Male
|
||||
["en_US_Chirp3_HD_Kore"] = 'en-US-Chirp3-HD-Kore', -- Female
|
||||
["en_US_Chirp3_HD_Leda"] = 'en-US-Chirp3-HD-Leda', -- Female
|
||||
["en_US_Chirp3_HD_Orus"] = 'en-US-Chirp3-HD-Orus', -- Male
|
||||
["en_US_Chirp3_HD_Puck"] = 'en-US-Chirp3-HD-Puck', -- Male
|
||||
--["de_DE_Chirp3_HD_Zephyr"] = 'de-DE-Chirp3-HD-Zephyr', -- Female (Datenfehler im Original)
|
||||
-- DE
|
||||
["de_DE_Chirp3_HD_Aoede"] = 'de-DE-Chirp3-HD-Aoede', -- Female
|
||||
["de_DE_Chirp3_HD_Charon"] = 'de-DE-Chirp3-HD-Charon', -- Male
|
||||
["de_DE_Chirp3_HD_Fenrir"] = 'de-DE-Chirp3-HD-Fenrir', -- Male
|
||||
["de_DE_Chirp3_HD_Kore"] = 'de-DE-Chirp3-HD-Kore', -- Female
|
||||
["de_DE_Chirp3_HD_Leda"] = 'de-DE-Chirp3-HD-Leda', -- Female
|
||||
["de_DE_Chirp3_HD_Orus"] = 'de-DE-Chirp3-HD-Orus', -- Male
|
||||
["de_DE_Chirp3_HD_Puck"] = 'de-DE-Chirp3-HD-Puck', -- Male
|
||||
["de_DE_Chirp3_HD_Zephyr"] = 'de-DE-Chirp3-HD-Zephyr', -- Female
|
||||
-- AU
|
||||
["en_AU_Chirp3_HD_Aoede"] = 'en-AU-Chirp3-HD-Aoede', -- Female
|
||||
["en_AU_Chirp3_HD_Charon"] = 'en-AU-Chirp3-HD-Charon', -- Male
|
||||
["en_AU_Chirp3_HD_Fenrir"] = 'en-AU-Chirp3-HD-Fenrir', -- Male
|
||||
["en_AU_Chirp3_HD_Kore"] = 'en-AU-Chirp3-HD-Kore', -- Female
|
||||
["en_AU_Chirp3_HD_Leda"] = 'en-AU-Chirp3-HD-Leda', -- Female
|
||||
["en_AU_Chirp3_HD_Orus"] = 'en-AU-Chirp3-HD-Orus', -- Male
|
||||
["en_AU_Chirp3_HD_Puck"] = 'en-AU-Chirp3-HD-Puck', -- Male
|
||||
["en_AU_Chirp3_HD_Zephyr"] = 'en-AU-Chirp3-HD-Zephyr', -- Female
|
||||
-- IN
|
||||
["en_IN_Chirp3_HD_Aoede"] = 'en-IN-Chirp3-HD-Aoede', -- Female
|
||||
["en_IN_Chirp3_HD_Charon"] = 'en-IN-Chirp3-HD-Charon', -- Male
|
||||
["en_IN_Chirp3_HD_Fenrir"] = 'en-IN-Chirp3-HD-Fenrir', -- Male
|
||||
["en_IN_Chirp3_HD_Kore"] = 'en-IN-Chirp3-HD-Kore', -- Female
|
||||
["en_IN_Chirp3_HD_Leda"] = 'en-IN-Chirp3-HD-Leda', -- Female
|
||||
["en_IN_Chirp3_HD_Orus"] = 'en-IN-Chirp3-HD-Orus', -- Male
|
||||
},
|
||||
ChirpHD = {
|
||||
["en_US_Chirp_HD_D"] = 'en-US-Chirp-HD-D', -- Male
|
||||
["en_US_Chirp_HD_F"] = 'en-US-Chirp-HD-F', -- Female
|
||||
["en_US_Chirp_HD_O"] = 'en-US-Chirp-HD-O', -- Female
|
||||
-- DE
|
||||
["de_DE_Chirp_HD_D"] = 'de-DE-Chirp-HD-D', -- Male
|
||||
["de_DE_Chirp_HD_F"] = 'de-DE-Chirp-HD-F', -- Female
|
||||
["de_DE_Chirp_HD_O"] = 'de-DE-Chirp-HD-O', -- Female
|
||||
-- AU
|
||||
["en_AU_Chirp_HD_D"] = 'en-AU-Chirp-HD-D', -- Male
|
||||
["en_AU_Chirp_HD_F"] = 'en-AU-Chirp-HD-F', -- Female
|
||||
["en_AU_Chirp_HD_O"] = 'en-AU-Chirp-HD-O', -- Female
|
||||
-- IN
|
||||
["en_IN_Chirp_HD_D"] = 'en-IN-Chirp-HD-D', -- Male
|
||||
["en_IN_Chirp_HD_F"] = 'en-IN-Chirp-HD-F', -- Female
|
||||
["en_IN_Chirp_HD_O"] = 'en-IN-Chirp-HD-O', -- Female
|
||||
},
|
||||
},
|
||||
Neural2 = {
|
||||
["en_GB_Neural2_A"] = 'en-GB-Neural2-A', -- Female
|
||||
["en_GB_Neural2_B"] = 'en-GB-Neural2-B', -- Male
|
||||
["en_GB_Neural2_C"] = 'en-GB-Neural2-C', -- Female
|
||||
["en_GB_Neural2_D"] = 'en-GB-Neural2-D', -- Male
|
||||
["en_GB_Neural2_F"] = 'en-GB-Neural2-F', -- Female
|
||||
["en_GB_Neural2_N"] = 'en-GB-Neural2-N', -- Female
|
||||
["en_GB_Neural2_O"] = 'en-GB-Neural2-O', -- Male
|
||||
-- US
|
||||
["en_US_Neural2_A"] = 'en-US-Neural2-A', -- Male
|
||||
["en_US_Neural2_C"] = 'en-US-Neural2-C', -- Female
|
||||
["en_US_Neural2_D"] = 'en-US-Neural2-D', -- Male
|
||||
["en_US_Neural2_E"] = 'en-US-Neural2-E', -- Female
|
||||
["en_US_Neural2_F"] = 'en-US-Neural2-F', -- Female
|
||||
["en_US_Neural2_G"] = 'en-US-Neural2-G', -- Female
|
||||
["en_US_Neural2_H"] = 'en-US-Neural2-H', -- Female
|
||||
["en_US_Neural2_I"] = 'en-US-Neural2-I', -- Male
|
||||
["en_US_Neural2_J"] = 'en-US-Neural2-J', -- Male
|
||||
-- DE
|
||||
["de_DE_Neural2_G"] = 'de-DE-Neural2-G', -- Female
|
||||
["de_DE_Neural2_H"] = 'de-DE-Neural2-H', -- Male
|
||||
-- AU
|
||||
["en_AU_Neural2_A"] = 'en-AU-Neural2-A', -- Female
|
||||
["en_AU_Neural2_B"] = 'en-AU-Neural2-B', -- Male
|
||||
["en_AU_Neural2_C"] = 'en-AU-Neural2-C', -- Female
|
||||
["en_AU_Neural2_D"] = 'en-AU-Neural2-D', -- Male
|
||||
-- IN
|
||||
["en_IN_Neural2_A"] = 'en-IN-Neural2-A', -- Female
|
||||
["en_IN_Neural2_B"] = 'en-IN-Neural2-B', -- Male
|
||||
["en_IN_Neural2_C"] = 'en-IN-Neural2-C', -- Male
|
||||
["en_IN_Neural2_D"] = 'en-IN-Neural2-D', -- Female
|
||||
},
|
||||
News = {
|
||||
["en_GB_News_G"] = 'en-GB-News-G', -- Female
|
||||
["en_GB_News_H"] = 'en-GB-News-H', -- Female
|
||||
["en_GB_News_I"] = 'en-GB-News-I', -- Female
|
||||
["en_GB_News_J"] = 'en-GB-News-J', -- Male
|
||||
["en_GB_News_K"] = 'en-GB-News-K', -- Male
|
||||
["en_GB_News_L"] = 'en-GB-News-L', -- Male
|
||||
["en_GB_News_M"] = 'en-GB-News-M', -- Male
|
||||
-- US
|
||||
["en_US_News_K"] = 'en-US-News-K', -- Female
|
||||
["en_US_News_L"] = 'en-US-News-L', -- Female
|
||||
["en_US_News_N"] = 'en-US-News-N', -- Male
|
||||
-- AU
|
||||
["en_AU_News_E"] = 'en-AU-News-E', -- Female
|
||||
["en_AU_News_F"] = 'en-AU-News-F', -- Female
|
||||
["en_AU_News_G"] = 'en-AU-News-G', -- Male
|
||||
},
|
||||
Casual = {
|
||||
["en_US_Casual_K"] = 'en-US-Casual-K', -- Male
|
||||
},
|
||||
Polyglot = {
|
||||
["en_US_Polyglot_1"] = 'en-US-Polyglot-1', -- Male
|
||||
["de_DE_Polyglot_1"] = 'de-DE-Polyglot-1', -- Male
|
||||
["en_AU_Polyglot_1"] = 'en-AU-Polyglot-1', -- Male
|
||||
},
|
||||
Studio = {
|
||||
-- Englisch (UK) - Studio
|
||||
["en_GB_Studio_B"] = 'en-GB-Studio-B', -- Male
|
||||
["en_GB_Studio_C"] = 'en-GB-Studio-C', -- Female
|
||||
-- Englisch (USA) - Studio
|
||||
["en_US_Studio_O"] = 'en-US-Studio-O', -- Female
|
||||
["en_US_Studio_Q"] = 'en-US-Studio-Q', -- Male
|
||||
-- DE
|
||||
["de_DE_Studio_B"] = 'de-DE-Studio-B', -- Male
|
||||
["de_DE_Studio_C"] = 'de-DE-Studio-C', -- Female
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -409,7 +409,7 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
|
||||
local Controller = self:_GetController()
|
||||
-- self:I( "Before SetTask" )
|
||||
Controller:setTask( DCSTask )
|
||||
-- AI_FORMATION class (used by RESCUEHELO) calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace.
|
||||
-- FORMATION class (used by RESCUEHELO) calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace.
|
||||
self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } )
|
||||
else
|
||||
BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } )
|
||||
@@ -2855,7 +2855,7 @@ do -- Route methods
|
||||
-- @param Core.Zone#ZONE Zone The zone where to route to.
|
||||
-- @param #boolean Randomize Defines whether to target point gets randomized within the Zone.
|
||||
-- @param #number Speed The speed in m/s. Default is 5.555 m/s = 20 km/h.
|
||||
-- @param Core.Base#FORMATION Formation The formation string.
|
||||
-- @param DCS#FORMATION Formation The formation string.
|
||||
function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation )
|
||||
self:F2( Zone )
|
||||
|
||||
@@ -2915,7 +2915,7 @@ do -- Route methods
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param DCS#Vec2 Vec2 The Vec2 where to route to.
|
||||
-- @param #number Speed The speed in m/s. Default is 5.555 m/s = 20 km/h.
|
||||
-- @param Core.Base#FORMATION Formation The formation string.
|
||||
-- @param DCS#FORMATION Formation The formation string.
|
||||
function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation )
|
||||
|
||||
local DCSControllable = self:GetDCSObject()
|
||||
@@ -4163,7 +4163,7 @@ function CONTROLLABLE:OptionRestrictBurner( RestrictBurner )
|
||||
|
||||
end
|
||||
|
||||
--- Sets Controllable Option for A2A attack range for AIR FIGHTER units.
|
||||
--- [AIR] Sets Controllable Option for A2A attack range for AIR FIGHTER units.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number range Defines the range
|
||||
-- @return #CONTROLLABLE self
|
||||
@@ -4188,6 +4188,66 @@ function CONTROLLABLE:OptionAAAttackRange( range )
|
||||
return nil
|
||||
end
|
||||
|
||||
--- [GROUND/AAA] Sets Controllable Option for Ground AAA minimum firing height.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number meters The minimum height in meters.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters)
|
||||
self:F2( { self.ControllableName } )
|
||||
local meters = meters or 20
|
||||
local DCSControllable = self:GetDCSObject()
|
||||
if DCSControllable then
|
||||
local Controller = self:_GetController()
|
||||
if Controller then
|
||||
if self:IsGround() then
|
||||
self:SetOption(27, meters)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- [GROUND/AAA] Sets Controllable Option for Ground AAA maximum firing height.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number meters The maximum height in meters.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters)
|
||||
self:F2( { self.ControllableName } )
|
||||
local meters = meters or 1000
|
||||
local DCSControllable = self:GetDCSObject()
|
||||
if DCSControllable then
|
||||
local Controller = self:_GetController()
|
||||
if Controller then
|
||||
if self:IsGround() then
|
||||
self:SetOption(29, meters)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- [GROUND/AAA] Sets Controllable Option for Ground AAA minimum firing height.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number feet The minimum height in feet.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet)
|
||||
self:F2( { self.ControllableName } )
|
||||
local feet = feet or 60
|
||||
return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet))
|
||||
end
|
||||
|
||||
--- [GROUND/AAA] Sets Controllable Option for Ground AAA maximum firing height.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number feet The maximum height in feet.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet)
|
||||
self:F2( { self.ControllableName } )
|
||||
local feet = feet or 3000
|
||||
return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet))
|
||||
end
|
||||
|
||||
--- Defines the range at which a GROUND unit/group is allowed to use its weapons automatically.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100.
|
||||
|
||||
@@ -108,6 +108,9 @@ DYNAMICCARGO.State = {
|
||||
-- @type DYNAMICCARGO.AircraftTypes
|
||||
DYNAMICCARGO.AircraftTypes = {
|
||||
["CH-47Fbl1"] = "CH-47Fbl1",
|
||||
["Mi-8MTV2"] = "Mi-8MTV2",
|
||||
["Mi-8MT"] = "Mi-8MT",
|
||||
["C-130J-30"] = "C-130J-30",
|
||||
}
|
||||
|
||||
--- Helo types possible.
|
||||
@@ -120,17 +123,36 @@ DYNAMICCARGO.AircraftDimensions = {
|
||||
["length"] = 11,
|
||||
["ropelength"] = 30,
|
||||
},
|
||||
["Mi-8MTV2"] = {
|
||||
["width"] = 6,
|
||||
["height"] = 6,
|
||||
["length"] = 15,
|
||||
["ropelength"] = 30,
|
||||
},
|
||||
["Mi-8MT"] = {
|
||||
["width"] = 6,
|
||||
["height"] = 6,
|
||||
["length"] = 15,
|
||||
["ropelength"] = 30,
|
||||
},
|
||||
["C-130J-30"] = {
|
||||
["width"] = 4,
|
||||
["height"] = 12,
|
||||
["length"] = 35,
|
||||
["ropelength"] = 0,
|
||||
},
|
||||
}
|
||||
|
||||
--- DYNAMICCARGO class version.
|
||||
-- @field #string version
|
||||
DYNAMICCARGO.version="0.0.7"
|
||||
DYNAMICCARGO.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
-- DONE: Added Mi-8 type and dimensions
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -512,7 +534,10 @@ function DYNAMICCARGO:_UpdatePosition()
|
||||
---------------
|
||||
-- REMOVED Cargo
|
||||
---------------
|
||||
if self.timer and self.timer:IsRunning() then self.timer:Stop() end
|
||||
if self.timer and self.timer:IsRunning() then
|
||||
self.timer:Stop()
|
||||
self.timer=nil
|
||||
end
|
||||
self:T(self.lid.." dead! " ..self.CargoState.."-> REMOVED")
|
||||
self.CargoState = DYNAMICCARGO.State.REMOVED
|
||||
_DATABASE:CreateEventDynamicCargoRemoved(self)
|
||||
@@ -520,6 +545,24 @@ function DYNAMICCARGO:_UpdatePosition()
|
||||
return self
|
||||
end
|
||||
|
||||
--- [USER] Destroy a DYNAMICCARGO object.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @param #boolean GenerateEvent Set to false to remove an item silently. Defaults to true.
|
||||
-- @return #boolean Return Returns nil if the object could not be found, else returns true.
|
||||
function DYNAMICCARGO:Destroy(GenerateEvent)
|
||||
local DCSObject = self:GetDCSObject()
|
||||
if DCSObject then
|
||||
local GenerateEvent = (GenerateEvent ~= nil and GenerateEvent == false) and false or true
|
||||
if GenerateEvent and GenerateEvent == true then
|
||||
self:CreateEventDead( timer.getTime(), DCSObject )
|
||||
end
|
||||
DCSObject:destroy()
|
||||
self:_UpdatePosition()
|
||||
return true
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- [Internal] Track helos for loaded/unloaded decision making.
|
||||
-- @param Wrapper.Client#CLIENT client
|
||||
-- @return #boolean IsIn
|
||||
|
||||
@@ -231,6 +231,7 @@ GROUP.Attribute = {
|
||||
GROUND_AAA="Ground_AAA",
|
||||
GROUND_SAM="Ground_SAM",
|
||||
GROUND_SHORAD="Ground_SHORAD",
|
||||
GROUND_BALLISTICMISSILE="Ground_BallisticMissile",
|
||||
GROUND_OTHER="Ground_OtherGround",
|
||||
NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier",
|
||||
NAVAL_WARSHIP="Naval_WarShip",
|
||||
@@ -1057,9 +1058,12 @@ function GROUP:GetTypeName()
|
||||
local DCSGroup = self:GetDCSObject()
|
||||
|
||||
if DCSGroup then
|
||||
local GroupTypeName = DCSGroup:getUnit(1):getTypeName()
|
||||
--self:T3( GroupTypeName )
|
||||
return( GroupTypeName )
|
||||
local unit = DCSGroup:getUnit(1)
|
||||
if unit then
|
||||
local GroupTypeName = unit:getTypeName()
|
||||
--self:T3( GroupTypeName )
|
||||
return( GroupTypeName )
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
@@ -1074,9 +1078,12 @@ function GROUP:GetNatoReportingName()
|
||||
local DCSGroup = self:GetDCSObject()
|
||||
|
||||
if DCSGroup then
|
||||
local GroupTypeName = DCSGroup:getUnit(1):getTypeName()
|
||||
--self:T3( GroupTypeName )
|
||||
return UTILS.GetReportingName(GroupTypeName)
|
||||
local unit = DCSGroup:getUnit(1)
|
||||
if unit then
|
||||
local GroupTypeName = unit:getTypeName()
|
||||
--self:T3( GroupTypeName )
|
||||
return UTILS.GetReportingName(GroupTypeName)
|
||||
end
|
||||
end
|
||||
|
||||
return "Bogey"
|
||||
@@ -1092,9 +1099,12 @@ function GROUP:GetPlayerName()
|
||||
local DCSGroup = self:GetDCSObject()
|
||||
|
||||
if DCSGroup then
|
||||
local PlayerName = DCSGroup:getUnit(1):getPlayerName()
|
||||
--self:T3( PlayerName )
|
||||
return( PlayerName )
|
||||
local unit = DCSGroup:getUnit(1)
|
||||
if unit then
|
||||
local PlayerName = unit:getPlayerName()
|
||||
--self:T3( PlayerName )
|
||||
return( PlayerName )
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
@@ -1110,9 +1120,12 @@ function GROUP:GetCallsign()
|
||||
local DCSGroup = self:GetDCSObject()
|
||||
|
||||
if DCSGroup then
|
||||
local GroupCallSign = DCSGroup:getUnit(1):getCallsign()
|
||||
--self:T3( GroupCallSign )
|
||||
return GroupCallSign
|
||||
local unit = DCSGroup:getUnit(1)
|
||||
if unit then
|
||||
local GroupCallSign = unit:getCallsign()
|
||||
--self:T3( GroupCallSign )
|
||||
return GroupCallSign
|
||||
end
|
||||
end
|
||||
|
||||
BASE:E( { "Cannot GetCallsign", Positionable = self, Alive = self:IsAlive() } )
|
||||
@@ -1151,6 +1164,23 @@ function GROUP:GetVec3()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the current {@Core.Vector#VECTOR} of the first Unit in the GROUP.
|
||||
-- @param #GROUP self
|
||||
-- @return Core.Vector#VECTOR Vector of the first Unit of the GROUP or nil if cannot be found.
|
||||
function GROUP:GetVector()
|
||||
|
||||
-- Get first unit.
|
||||
local unit=self:GetUnit(1)
|
||||
|
||||
if unit then
|
||||
local vector=unit:GetVector()
|
||||
return vector
|
||||
end
|
||||
|
||||
self:E("ERROR: Cannot get Vector of group "..tostring(self.GroupName))
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the average Vec3 vector of the Units in the GROUP.
|
||||
-- @param #GROUP self
|
||||
-- @return DCS#Vec3 Current Vec3 of the GROUP or nil if cannot be found.
|
||||
@@ -2231,6 +2261,10 @@ function GROUP:Respawn( Template, Reset )
|
||||
|
||||
--UTILS.PrintTableToLog(Template)
|
||||
|
||||
if self.ValidateAndRepositionGroundUnits then
|
||||
UTILS.ValidateAndRepositionGroundUnits(Template.units)
|
||||
end
|
||||
|
||||
-- Spawn new group.
|
||||
self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template)
|
||||
--_DATABASE:Spawn(Template)
|
||||
@@ -2405,7 +2439,7 @@ end
|
||||
-- @return #table The mission route defined by points.
|
||||
function GROUP:GetTaskRoute()
|
||||
--self:F2( self.GroupName )
|
||||
if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then
|
||||
if _DATABASE.Templates.Groups[self.GroupName] and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then
|
||||
return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points )
|
||||
else
|
||||
return {}
|
||||
@@ -2433,7 +2467,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius )
|
||||
|
||||
--self:T3( { GroupName } )
|
||||
|
||||
local Template = _DATABASE.Templates.Groups[GroupName].Template
|
||||
local Template = _DATABASE.Templates.Groups[GroupName] and _DATABASE.Templates.Groups[GroupName].Template or nil
|
||||
|
||||
if Template then
|
||||
if not Begin then
|
||||
@@ -2457,7 +2491,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius )
|
||||
end
|
||||
return Points
|
||||
else
|
||||
error( "Template not found for Group : " .. GroupName )
|
||||
BASE:E( "Template not found for Group : " .. GroupName )
|
||||
end
|
||||
|
||||
return nil
|
||||
@@ -2639,6 +2673,8 @@ function GROUP:GetAttribute()
|
||||
local artillery=self:HasAttribute("Artillery")
|
||||
local tank=self:HasAttribute("Old Tanks") or self:HasAttribute("Modern Tanks") or self:HasAttribute("Tanks")
|
||||
local aaa=self:HasAttribute("AAA") and (not self:HasAttribute("SAM elements"))
|
||||
local ballisticMissile=artillery and self:HasAttribute("SS_missile")
|
||||
local shorad=self:HasAttribute("SR SAM")
|
||||
local ewr=self:HasAttribute("EWR")
|
||||
local ifv=self:HasAttribute("IFV")
|
||||
local sam=self:HasAttribute("SAM elements") or self:HasAttribute("Optical Tracker")
|
||||
@@ -2680,6 +2716,8 @@ function GROUP:GetAttribute()
|
||||
attribute=GROUP.Attribute.GROUND_SAM
|
||||
elseif aaa then
|
||||
attribute=GROUP.Attribute.GROUND_AAA
|
||||
elseif artillery and ballisticMissile then
|
||||
attribute=GROUP.Attribute.GROUND_BALLISTICMISSILE
|
||||
elseif artillery then
|
||||
attribute=GROUP.Attribute.GROUND_ARTILLERY
|
||||
elseif tank then
|
||||
@@ -2906,6 +2944,16 @@ function GROUP:EnableEmission(switch)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set a formation for this group.
|
||||
-- @param #GROUP self
|
||||
-- @param #number Formation See. ENUMS.Formation or [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) for options.
|
||||
-- @return #GROUP self
|
||||
function GROUP:SetFormation(Formation)
|
||||
self:SetOption(AI.Option.Air.id.FORMATION,Formation)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch on/off invisible flag for the group.
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled.
|
||||
@@ -3192,3 +3240,60 @@ function GROUP:IsAAA()
|
||||
end
|
||||
return isAAA
|
||||
end
|
||||
|
||||
--- This function uses Disposition and other fallback logic to find better ground positions for ground units.
|
||||
--- NOTE: This is not a spawn randomizer.
|
||||
--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table.
|
||||
--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit.
|
||||
--- Uses UTILS.ValidateAndRepositionGroundUnits.
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean Enabled Enable/disable the feature.
|
||||
function GROUP:SetValidateAndRepositionGroundUnits(Enabled)
|
||||
self.ValidateAndRepositionGroundUnits = Enabled
|
||||
end
|
||||
|
||||
|
||||
--- Get the bounding box of the group combining UNIT:GetBoundingBox() units.
|
||||
-- @param #GROUP self
|
||||
-- @return DCS#Box3 The bounding box of the GROUP.
|
||||
-- @return #nil The GROUP does not have any alive units.
|
||||
function GROUP:GetBoundingBox()
|
||||
local bbox = { min = { x = math.huge, y = math.huge, z = math.huge },
|
||||
max = { x = -math.huge, y = -math.huge, z = -math.huge }
|
||||
}
|
||||
|
||||
local Units = self:GetUnits() or {}
|
||||
if #Units == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
for _, unit in pairs(Units) do
|
||||
if unit and unit:IsAlive() then
|
||||
local ubox = unit:GetBoundingBox()
|
||||
|
||||
if ubox then
|
||||
if ubox.min.x < bbox.min.x then
|
||||
bbox.min.x = ubox.min.x
|
||||
end
|
||||
if ubox.min.y < bbox.min.y then
|
||||
bbox.min.y = ubox.min.y
|
||||
end
|
||||
if ubox.min.z < bbox.min.z then
|
||||
bbox.min.z = ubox.min.z
|
||||
end
|
||||
|
||||
if ubox.max.x > bbox.max.x then
|
||||
bbox.max.x = ubox.max.x
|
||||
end
|
||||
if ubox.max.y > bbox.max.y then
|
||||
bbox.max.y = ubox.max.y
|
||||
end
|
||||
if ubox.max.z > bbox.max.z then
|
||||
bbox.max.z = ubox.max.z
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return bbox
|
||||
end
|
||||
|
||||
@@ -170,6 +170,27 @@ function IDENTIFIABLE:GetCoalition()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns true if identifiable is of RED coalition.
|
||||
-- @param #IDENTIFIABLE self
|
||||
-- @return #boolean If the identifiable is red.
|
||||
function IDENTIFIABLE:IsRed()
|
||||
return self:GetCoalition() == coalition.side.RED
|
||||
end
|
||||
|
||||
--- Returns true if identifiable is of BLUE coalition.
|
||||
-- @param #IDENTIFIABLE self
|
||||
-- @return #boolean If the identifiable is blue.
|
||||
function IDENTIFIABLE:IsBlue()
|
||||
return self:GetCoalition() == coalition.side.BLUE
|
||||
end
|
||||
|
||||
--- Returns true if identifiable is of NEUTRAL coalition.
|
||||
-- @param #IDENTIFIABLE self
|
||||
-- @return #boolean If the identifiable is neutral.
|
||||
function IDENTIFIABLE:IsNeutral()
|
||||
return self:GetCoalition() == coalition.side.NEUTRAL
|
||||
end
|
||||
|
||||
--- Returns the name of the coalition of the Identifiable.
|
||||
-- @param #IDENTIFIABLE self
|
||||
-- @return #string The name of the coalition.
|
||||
|
||||
@@ -485,7 +485,7 @@ function NET:GetPlayerIDByName(Name)
|
||||
if not Name then return nil end
|
||||
local playerList = net.get_player_list()
|
||||
for i=1,#playerList do
|
||||
local playerName = net.get_name(i)
|
||||
local playerName = net.get_name(playerList[i])
|
||||
if playerName == Name then
|
||||
return playerList[i]
|
||||
end
|
||||
|
||||
@@ -219,6 +219,25 @@ function POSITIONABLE:GetOrientationZ()
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the vectors of the orientation of the object:
|
||||
-- X is the orientation parallel to the movement of the object, Z perpendicular and Y vertical orientation.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @return Core.Vector#VECTOR X orientation, i.e. parallel to the direction of movement.
|
||||
-- @return Core.Vector#VECTOR Y orientation, i.e. vertical.
|
||||
-- @return Core.Vector#VECTOR Z orientation, i.e. perpendicular to the direction of movement.
|
||||
function POSITIONABLE:GetOrientationVectors()
|
||||
local position = self:GetPosition()
|
||||
if position then
|
||||
local vecx=VECTOR:NewFromVec(position.x)
|
||||
local vecy=VECTOR:NewFromVec(position.y)
|
||||
local vecz=VECTOR:NewFromVec(position.z)
|
||||
return vecx, vecy, vecz
|
||||
else
|
||||
BASE:E( { "Cannot GetOrientation", Positionable = self, Alive = self:IsAlive() } )
|
||||
return nil, nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @return DCS#Position The 3D position vectors of the POSITIONABLE.
|
||||
@@ -286,6 +305,27 @@ function POSITIONABLE:GetVec2()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the @{Core.Vector#VECTOR} indicating the 3D position of the object on the map.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @return Core.Vector#VECTOR The 3D vector.
|
||||
function POSITIONABLE:GetVector()
|
||||
|
||||
local DCSPositionable = self:GetDCSObject()
|
||||
|
||||
if DCSPositionable then
|
||||
|
||||
local Vec3 = DCSPositionable:getPoint() -- DCS#Vec3
|
||||
|
||||
local vector=VECTOR:NewFromVec(Vec3)
|
||||
|
||||
return vector
|
||||
end
|
||||
|
||||
self:E( { "Cannot GetVec2", Positionable = self, Alive = self:IsAlive() } )
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns a COORDINATE object indicating the point in 2D of the POSITIONABLE within the mission.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE.
|
||||
@@ -361,15 +401,17 @@ function POSITIONABLE:GetCoord()
|
||||
-- Get the current position.
|
||||
local PositionableVec3 = self:GetVec3()
|
||||
|
||||
if self.coordinate then
|
||||
-- Update COORDINATE from 3D vector.
|
||||
self.coordinate:UpdateFromVec3( PositionableVec3 )
|
||||
else
|
||||
-- New COORDINATE.
|
||||
self.coordinate = COORDINATE:NewFromVec3( PositionableVec3 )
|
||||
end
|
||||
if PositionableVec3 then
|
||||
if self.coordinate then
|
||||
-- Update COORDINATE from 3D vector.
|
||||
self.coordinate:UpdateFromVec3( PositionableVec3 )
|
||||
else
|
||||
-- New COORDINATE.
|
||||
self.coordinate = COORDINATE:NewFromVec3( PositionableVec3 )
|
||||
end
|
||||
|
||||
return self.coordinate
|
||||
return self.coordinate
|
||||
end
|
||||
end
|
||||
|
||||
-- Error message.
|
||||
@@ -687,16 +729,23 @@ end
|
||||
|
||||
--- Returns the altitude above sea level of the POSITIONABLE.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level.
|
||||
-- @return DCS#Distance The altitude of the POSITIONABLE.
|
||||
-- @return #nil The POSITIONABLE is not existing or alive.
|
||||
function POSITIONABLE:GetAltitude()
|
||||
function POSITIONABLE:GetAltitude(FromGround)
|
||||
self:F2()
|
||||
|
||||
local DCSPositionable = self:GetDCSObject()
|
||||
|
||||
if DCSPositionable then
|
||||
local PositionablePointVec3 = DCSPositionable:getPoint() -- DCS#Vec3
|
||||
return PositionablePointVec3.y
|
||||
local altitude = 0
|
||||
local point = DCSPositionable:getPoint() --DCS#Vec3
|
||||
altitude = point.y
|
||||
if FromGround then
|
||||
local land = land.getHeight({ x = point.x, y = point.z }) or 0
|
||||
altitude = altitude - land
|
||||
end
|
||||
return altitude
|
||||
end
|
||||
|
||||
BASE:E( { "Cannot GetAltitude", Positionable = self, Alive = self:IsAlive() } )
|
||||
@@ -912,6 +961,24 @@ function POSITIONABLE:GetVelocityVec3()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the velocity vector.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @return Core.Vector#VECTOR The velocity vector or `nil` if the object does not exist.
|
||||
function POSITIONABLE:GetVelocityVector()
|
||||
|
||||
local DCSPositionable = self:GetDCSObject()
|
||||
|
||||
if DCSPositionable and DCSPositionable:isExist() then
|
||||
local vec3 = DCSPositionable:getVelocity()
|
||||
local vector=VECTOR:NewFromVec(vec3)
|
||||
return vector
|
||||
end
|
||||
|
||||
BASE:E( { "Cannot GetVelocityVector", Positionable = self, Alive = self:IsAlive() } )
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get relative velocity with respect to another POSITIONABLE.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @param #POSITIONABLE Positionable Other POSITIONABLE.
|
||||
@@ -1863,6 +1930,7 @@ do -- Cargo
|
||||
["HL_DSHK"] = 6*POSITIONABLE.DefaultInfantryWeight,
|
||||
["CCKW_353"] = 16*POSITIONABLE.DefaultInfantryWeight, --GMC CCKW 2½-ton 6×6 truck, estimating 16 soldiers,
|
||||
["MaxxPro_MRAP"] = 7*POSITIONABLE.DefaultInfantryWeight,
|
||||
["Sd_Kfz_251"] = 10*POSITIONABLE.DefaultInfantryWeight,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,13 @@
|
||||
-- @field #string SceneryName Name of the scenery object.
|
||||
-- @field DCS#Object SceneryObject DCS scenery object.
|
||||
-- @field #number Life0 Initial life points.
|
||||
-- @field #table Properties
|
||||
-- @field #table Properties.
|
||||
-- @field #number ID.
|
||||
-- @field Core.Zone#ZONE_POLYGON SceneryZone.
|
||||
-- @field Core.Point#COORDINATE Coordinate.
|
||||
-- @field DCS#Vec2 Vec2.
|
||||
-- @field DCS#Vec3 Vec3.
|
||||
-- @field Core.Vector#VECTOR Vector.
|
||||
-- @extends Wrapper.Positionable#POSITIONABLE
|
||||
|
||||
|
||||
@@ -35,27 +41,79 @@ SCENERY = {
|
||||
ClassName = "SCENERY",
|
||||
}
|
||||
|
||||
_SCENERY = {}
|
||||
|
||||
--- Register scenery object as POSITIONABLE.
|
||||
--@param #SCENERY self
|
||||
--@param #string SceneryName Scenery name.
|
||||
--@param DCS#Object SceneryObject DCS scenery object.
|
||||
--@param Core.Zone#ZONE_POLYGON SceneryZone (optional) The zone object.
|
||||
--@return #SCENERY Scenery object.
|
||||
function SCENERY:Register( SceneryName, SceneryObject )
|
||||
|
||||
function SCENERY:Register( SceneryName, SceneryObject, SceneryZone )
|
||||
|
||||
local ID = (SceneryObject and SceneryObject.getID) and SceneryObject:getID() or SceneryName
|
||||
|
||||
if _SCENERY[ID] and _SCENERY[ID].SceneryObject == nil then
|
||||
|
||||
_SCENERY[ID].SceneryObject=SceneryObject
|
||||
SCENERY._UpdateFromDCSObject(_SCENERY[ID])
|
||||
|
||||
end
|
||||
|
||||
if _SCENERY[ID] then return _SCENERY[ID] end
|
||||
|
||||
local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) )
|
||||
|
||||
self.SceneryName = tostring(SceneryName)
|
||||
|
||||
self.ID = ID
|
||||
self.SceneryObject = SceneryObject
|
||||
self.SceneryZone = SceneryZone
|
||||
|
||||
if SceneryZone then
|
||||
self.Vec3 = SceneryZone:GetVec3()
|
||||
self.Vec2 = SceneryZone:GetVec2()
|
||||
self.Vector = (self.Vec3 and VECTOR) and VECTOR:NewFromVec(self.Vec3) or nil
|
||||
end
|
||||
|
||||
if SceneryObject and SceneryObject.getPoint then
|
||||
local vec3 = SceneryObject:getPoint()
|
||||
self.Vec3 = { x = vec3.x, y = vec3.y, z = vec3.z }
|
||||
self.Vec2 = { x = vec3.x, y = vec3.z }
|
||||
self.Vector = (self.Vec3 and VECTOR) and VECTOR:NewFromVec(self.Vec3) or nil
|
||||
end
|
||||
|
||||
if self.SceneryObject and self.SceneryObject.getLife then -- fix some objects do not have all functions
|
||||
self.Life0 = self.SceneryObject:getLife() or 0
|
||||
self.Life0 = self.SceneryObject:getLife() or 1
|
||||
else
|
||||
self.Life0 = 0
|
||||
self.Life0 = 1
|
||||
end
|
||||
|
||||
self.Properties = {}
|
||||
|
||||
_SCENERY[self.ID] = self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- [INTERNAL] Update data
|
||||
-- @param Wrapper.Scenery#SCENERY Scenery The object to update.
|
||||
function SCENERY._UpdateFromDCSObject(Scenery)
|
||||
env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName))
|
||||
local self=Scenery
|
||||
if self.Vec2 == nil and self.SceneryObject ~= nil then
|
||||
self.Vec3 = self.SceneryObject:getPoint()
|
||||
if self.Vec3 then
|
||||
self.Vec2 = {x=self.Vec3.x,y=self.Vec3.z}
|
||||
self.Vector = VECTOR:NewFromVec(self.Vec3)
|
||||
end
|
||||
end
|
||||
if not self.Life0 or self.Life0 == 1 then
|
||||
if self.SceneryObject and self.SceneryObject.getLife() then
|
||||
self.Life = self.SceneryObject:getLife() or 1
|
||||
self.Life0 = self.Life
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -99,6 +157,39 @@ function SCENERY:GetName()
|
||||
return self.SceneryName
|
||||
end
|
||||
|
||||
--- Obtain object coordinate.
|
||||
--@param #SCENERY self
|
||||
--@return Core.Point#COORDINATE Coordinate
|
||||
function SCENERY:GetCoordinate()
|
||||
if self.Coordinate then
|
||||
return self.Coordinate
|
||||
elseif self.Vec3 then
|
||||
self.Coordinate = COORDINATE:NewFromVec3(self.Vec3):SetAlt()
|
||||
end
|
||||
return self.Coordinate
|
||||
end
|
||||
|
||||
--- Obtain object coordinate.
|
||||
--@param #SCENERY self
|
||||
--@return DCS#Vec3 Vec3
|
||||
function SCENERY:GetVec3()
|
||||
return self.Vec3
|
||||
end
|
||||
|
||||
--- Obtain object coordinate.
|
||||
--@param #SCENERY self
|
||||
--@return DCS#Vec2 Vec2
|
||||
function SCENERY:GetVec2()
|
||||
return self.Vec2
|
||||
end
|
||||
|
||||
--- Obtain object coordinate.
|
||||
--@param #SCENERY self
|
||||
--@return Core.Vector#VECTOR Vector
|
||||
function SCENERY:GetVector()
|
||||
return self.Vector
|
||||
end
|
||||
|
||||
--- Obtain DCS Object from the SCENERY Object.
|
||||
--@param #SCENERY self
|
||||
--@return DCS#Object DCS scenery object.
|
||||
@@ -106,6 +197,13 @@ function SCENERY:GetDCSObject()
|
||||
return self.SceneryObject
|
||||
end
|
||||
|
||||
--- Obtain object ID.
|
||||
--@param #SCENERY self
|
||||
--@return #string Name
|
||||
function SCENERY:GetID()
|
||||
return self.ID
|
||||
end
|
||||
|
||||
--- Get current life points from the SCENERY Object. Note - Some scenery objects always have 0 life points.
|
||||
-- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the life0 value to 120%
|
||||
-- of the last life value if life exceeds life0 (initial life) at any point. Thus will will get a smooth percentage decrease, if you use this e.g. as success
|
||||
@@ -113,12 +211,14 @@ end
|
||||
--@param #SCENERY self
|
||||
--@return #number life
|
||||
function SCENERY:GetLife()
|
||||
local life = 0
|
||||
local life = 1
|
||||
if self.SceneryObject and self.SceneryObject.getLife then
|
||||
life = self.SceneryObject:getLife()
|
||||
if life > self.Life0 then
|
||||
self.Life0 = math.floor(life * 1.2)
|
||||
end
|
||||
elseif self.Life then
|
||||
life = self.Life
|
||||
end
|
||||
return life
|
||||
end
|
||||
@@ -176,12 +276,19 @@ end
|
||||
--- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first
|
||||
-- to find the correct object.
|
||||
--@param #SCENERY self
|
||||
--@param #string Name The name/id of the scenery object as taken from the ME. Ex. '595785449'
|
||||
--@param Core.Point#COORDINATE Coordinate Where to find the scenery object
|
||||
--@param #number Radius (optional) Search radius around coordinate, defaults to 100
|
||||
--@return #SCENERY Scenery Object or `nil` if it cannot be found
|
||||
function SCENERY:FindByName(Name, Coordinate, Radius, Role)
|
||||
|
||||
--@param #string Name The name/id of the scenery object as taken from the ME. Ex. '595785449'.
|
||||
--@param Core.Point#COORDINATE Coordinate Where to find the scenery object.
|
||||
--@param #number Radius (optional) Search radius around coordinate, defaults to 100.
|
||||
--@param #string Role (optional) The role if set on the zone object.
|
||||
--@param Core.Zone#ZONE_POLYGON Zone (optional) The Zone where the scenery is located.
|
||||
--@return #SCENERY Scenery Object or `nil` if it cannot be found.
|
||||
function SCENERY:FindByName(Name, Coordinate, Radius, Role, Zone)
|
||||
|
||||
--BASE:I("Coordinate x = "..Coordinate.x .. " y = "..Coordinate.y.." z = "..Coordinate.z)
|
||||
|
||||
local findme = self:_FindByName(Name)
|
||||
if findme then return findme end
|
||||
|
||||
local radius = Radius or 100
|
||||
local name = Name or "unknown"
|
||||
local scenery = nil
|
||||
@@ -193,12 +300,13 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role)
|
||||
local function SceneryScan(scoordinate, sradius, sname)
|
||||
if scoordinate ~= nil then
|
||||
local Vec2 = scoordinate:GetVec2()
|
||||
local scanzone = ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius,true)
|
||||
local scanzone = ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius)
|
||||
scanzone:Scan({Object.Category.SCENERY})
|
||||
local scanned = scanzone:GetScannedSceneryObjects()
|
||||
local rscenery = nil -- Wrapper.Scenery#SCENERY
|
||||
for _,_scenery in pairs(scanned) do
|
||||
local scenery = _scenery -- Wrapper.Scenery#SCENERY
|
||||
--BASE:I({tostring(scenery.SceneryName),tostring(sname)})
|
||||
if tostring(scenery.SceneryName) == tostring(sname) then
|
||||
rscenery = scenery
|
||||
if Role then rscenery:SetProperty("ROLE",Role) end
|
||||
@@ -211,13 +319,36 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role)
|
||||
end
|
||||
|
||||
if Coordinate then
|
||||
--BASE:I("Coordinate Scenery Scan")
|
||||
scenery = SceneryScan(Coordinate, radius, name)
|
||||
end
|
||||
|
||||
|
||||
if not scenery then scenery = SCENERY:Register(Name,nil,Zone) end
|
||||
|
||||
return scenery
|
||||
end
|
||||
|
||||
--- Find a SCENERY object that was previously registered(!) by it's ID.
|
||||
-- @param #SCENERY self
|
||||
-- @param #number ID
|
||||
-- @return Wrapper.Scenery#SCENERY Scenery or nil if it could not be found
|
||||
function SCENERY:FindByID(ID)
|
||||
return _SCENERY[ID]
|
||||
end
|
||||
|
||||
--- Find a SCENERY object that was previously registered(!) by it's name.
|
||||
-- @param #SCENERY self
|
||||
-- @param #string Name
|
||||
-- @return Wrapper.Scenery#SCENERY Scenery or nil if it could not be found
|
||||
function SCENERY:_FindByName(Name)
|
||||
for _id,_object in pairs(_SCENERY) do
|
||||
if _object and _object.GetName and _object:GetName() then
|
||||
local name = _object:GetName()
|
||||
if Name == name then return _object end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first
|
||||
-- to find the correct object.
|
||||
--@param #SCENERY self
|
||||
@@ -231,8 +362,8 @@ function SCENERY:FindByNameInZone(Name, Zone, Radius)
|
||||
if type(Zone) == "string" then
|
||||
Zone = ZONE:FindByName(Zone)
|
||||
end
|
||||
local coordinate = Zone:GetCoordinate()
|
||||
return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"))
|
||||
local coordinate = Zone:GetCoordinate():SetAlt()
|
||||
return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone)
|
||||
end
|
||||
|
||||
--- Find a SCENERY object from its zone name. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first
|
||||
@@ -241,14 +372,18 @@ end
|
||||
--@param #string ZoneName The name of the scenery zone as created with a right-click on the map in the mission editor and select "assigned to...". Can be handed over as ZONE object.
|
||||
--@return #SCENERY First found Scenery Object or `nil` if it cannot be found
|
||||
function SCENERY:FindByZoneName( ZoneName )
|
||||
--BASE:I(ZoneName)
|
||||
|
||||
local zone = ZoneName -- Core.Zone#ZONE_BASE
|
||||
|
||||
if type(ZoneName) == "string" then
|
||||
zone = ZONE:FindByName(ZoneName) -- Core.Zone#ZONE_POLYGON
|
||||
end
|
||||
|
||||
local _id = zone:GetProperty('OBJECT ID')
|
||||
--local properties = zone:GetAllProperties() or {}
|
||||
--BASE:I(string.format("Object ID is: %s",_id or "none"))
|
||||
--BASE:T("Object ID ".._id)
|
||||
|
||||
--BASE:I("Object ID ".._id)
|
||||
|
||||
if not _id then
|
||||
-- this zone has no object ID
|
||||
BASE:E("**** Zone without object ID: "..ZoneName.." | Type: "..tostring(zone.ClassName))
|
||||
@@ -257,23 +392,27 @@ function SCENERY:FindByZoneName( ZoneName )
|
||||
local scanned = zone:GetScannedSceneryObjects()
|
||||
for _,_scenery in (scanned) do
|
||||
local scenery = _scenery -- Wrapper.Scenery#SCENERY
|
||||
if scenery:IsAlive() then
|
||||
--if scenery:IsAlive() then
|
||||
local role = zone:GetProperty("ROLE")
|
||||
if role then scenery:SetProperty("ROLE",role) end
|
||||
return scenery
|
||||
end
|
||||
--end
|
||||
end
|
||||
return nil
|
||||
else
|
||||
return self:FindByName(_id, zone:GetCoordinate(),nil,zone:GetProperty("ROLE"))
|
||||
local coordinate = zone:GetCoordinate()
|
||||
coordinate:SetAlt()
|
||||
return self:FindByName(_id, coordinate,nil,zone:GetProperty("ROLE"),zone)
|
||||
end
|
||||
else
|
||||
return self:FindByName(_id, zone:GetCoordinate(),nil,zone:GetProperty("ROLE"))
|
||||
local coordinate = zone:GetCoordinate()
|
||||
coordinate:SetAlt()
|
||||
return self:FindByName(_id, coordinate,nil,zone:GetProperty("ROLE"),zone)
|
||||
end
|
||||
end
|
||||
|
||||
--- Scan and find all SCENERY objects from a zone by zone-name. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first
|
||||
-- to find the correct object.
|
||||
-- to find the correct object. Might return nil!
|
||||
--@param #SCENERY self
|
||||
--@param #string ZoneName The name of the zone, can be handed as ZONE_RADIUS or ZONE_POLYGON object
|
||||
--@return #table of SCENERY Objects, or `nil` if nothing found
|
||||
@@ -295,7 +434,7 @@ function SCENERY:FindAllByZoneName( ZoneName )
|
||||
return nil
|
||||
end
|
||||
else
|
||||
local obj = self:FindByName(_id, zone:GetCoordinate(),nil,zone:GetProperty("ROLE"))
|
||||
local obj = self:FindByName(_id, zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"), zone)
|
||||
if obj then
|
||||
return {obj}
|
||||
else
|
||||
|
||||
@@ -64,6 +64,9 @@ function STATIC:Register( StaticName )
|
||||
else
|
||||
self:E(string.format("Static object %s does not exist!", tostring(self.StaticName)))
|
||||
end
|
||||
|
||||
-- Cache position
|
||||
self._vec3 = self:GetVec3()
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -86,6 +89,30 @@ function STATIC:GetLife()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get the position of the STATIC even if it is not alive.
|
||||
-- @param #STATIC self
|
||||
-- @return DCS#Vec2 The 2D point vector of the POSITIONABLE.
|
||||
-- @return #nil The position was not cached.
|
||||
function STATIC:GetVec2Cached()
|
||||
local vec2 = self:GetVec2()
|
||||
if not vec2 and self._vec3 then
|
||||
vec2 = {x = self._vec3.x, y = self._vec3.z }
|
||||
end
|
||||
return vec2
|
||||
end
|
||||
|
||||
--- Get the position of the STATIC even if it is not alive.
|
||||
-- @param #STATIC self
|
||||
-- @return DCS#Vec3 The 3D point vector of the POSITIONABLE.
|
||||
-- @return #nil The position was not cached.
|
||||
function STATIC:GetVec3Cached()
|
||||
local vec3 = self:GetVec3()
|
||||
if not vec3 and self._vec3 then
|
||||
vec3 = self._vec3
|
||||
end
|
||||
return vec3
|
||||
end
|
||||
|
||||
--- Finds a STATIC from the _DATABASE using a DCSStatic object.
|
||||
-- @param #STATIC self
|
||||
-- @param DCS#StaticObject DCSStatic An existing DCS Static object reference.
|
||||
@@ -232,6 +259,8 @@ function STATIC:SpawnAt(Coordinate, Heading, Delay)
|
||||
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName)
|
||||
|
||||
SpawnStatic:SpawnFromPointVec2( Coordinate, Heading, self.StaticName )
|
||||
-- Cache position
|
||||
self._vec3 = self:GetVec3()
|
||||
|
||||
end
|
||||
|
||||
@@ -255,6 +284,8 @@ function STATIC:ReSpawn(CountryID, Delay)
|
||||
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, CountryID)
|
||||
|
||||
SpawnStatic:Spawn(nil, self.StaticName)
|
||||
-- Cache position
|
||||
self._vec3 = self:GetVec3()
|
||||
|
||||
end
|
||||
|
||||
@@ -278,6 +309,8 @@ function STATIC:ReSpawnAt(Coordinate, Heading, Delay)
|
||||
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, self:GetCountry())
|
||||
|
||||
SpawnStatic:SpawnFromCoordinate(Coordinate, Heading, self.StaticName)
|
||||
-- Cache position
|
||||
self._vec3 = self:GetVec3()
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -753,7 +753,7 @@ function STORAGE:LoadFromFile(Path,Filename)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv"))
|
||||
self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -773,7 +773,7 @@ function STORAGE:LoadFromFile(Path,Filename)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv"))
|
||||
self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -805,7 +805,7 @@ function STORAGE:LoadFromFile(Path,Filename)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv"))
|
||||
self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -377,6 +377,10 @@ function UNIT:ReSpawnAt(Coordinate, Heading)
|
||||
|
||||
--self:T( SpawnGroupTemplate )
|
||||
|
||||
if self.ValidateAndRepositionGroundUnits then
|
||||
UTILS.ValidateAndRepositionGroundUnits(SpawnGroupTemplate.units)
|
||||
end
|
||||
|
||||
_DATABASE:Spawn(SpawnGroupTemplate)
|
||||
end
|
||||
|
||||
@@ -1348,9 +1352,7 @@ function UNIT:GetThreatLevel()
|
||||
end
|
||||
|
||||
ThreatText = ThreatLevels[ThreatLevel + 1]
|
||||
end
|
||||
|
||||
if self:IsAir() then
|
||||
elseif self:IsAir() then
|
||||
|
||||
local ThreatLevels = {
|
||||
[1] = "Unarmed",
|
||||
@@ -1393,9 +1395,7 @@ function UNIT:GetThreatLevel()
|
||||
end
|
||||
|
||||
ThreatText = ThreatLevels[ThreatLevel + 1]
|
||||
end
|
||||
|
||||
if self:IsShip() then
|
||||
elseif self:IsShip() then
|
||||
|
||||
--["Aircraft Carriers"] = {"Heavy armed ships",},
|
||||
--["Cruisers"] = {"Heavy armed ships",},
|
||||
@@ -1931,3 +1931,44 @@ end
|
||||
function UNIT:SetLife(Percent)
|
||||
net.dostring_in("mission",string.format("a_unit_set_life_percentage(%d, %f)", self:GetID(), Percent))
|
||||
end
|
||||
|
||||
--- Set the carrier illumination mode. -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY
|
||||
-- @param #UNIT self
|
||||
-- @param #number Mode Illumination mode, can be -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY
|
||||
function UNIT:SetCarrierIlluminationMode(Mode)
|
||||
UTILS.SetCarrierIlluminationMode(self:GetID(), Mode)
|
||||
end
|
||||
|
||||
--- This function uses Disposition and other fallback logic to find better ground positions for ground units.
|
||||
--- NOTE: This is not a spawn randomizer.
|
||||
--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table.
|
||||
--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit.
|
||||
--- Uses UTILS.ValidateAndRepositionGroundUnits.
|
||||
-- @param #UNIT self
|
||||
-- @param #boolean Enabled Enable/disable the feature.
|
||||
function UNIT:SetValidateAndRepositionGroundUnits(Enabled)
|
||||
self.ValidateAndRepositionGroundUnits = Enabled
|
||||
end
|
||||
|
||||
--- Get the max kgs of fuel this unit can hold in its *internal* tank(s) and overall (with external tanks) in kgs.
|
||||
-- @param #UNIT self
|
||||
-- @return #number InternalFuel Max internal fuel in kgs. Zero if it cannot be determined.
|
||||
-- @return #number Overall Fuel Overall max in case there are external tanks in kgs. Zero if it cannot be determined.
|
||||
function UNIT:GetFuelMassMax()
|
||||
local Desc = self:GetDesc() or {}
|
||||
local massFuelMax=Desc.fuelMassMax or 0
|
||||
local relFuel=math.min(self:GetFuel() or 1.0, 1.0) -- We take 1.0 as max in case of external fuel tanks.
|
||||
local massFuel=massFuelMax*relFuel
|
||||
return massFuel, massFuelMax
|
||||
end
|
||||
|
||||
--- Get the current kgs of fuel this unit holds in all of its tanks.
|
||||
-- @param #UNIT self
|
||||
-- @param #number Filling Fuel in kgs.
|
||||
function UNIT:GetCurrentFuelKgs()
|
||||
local fuel, maxfuel = self:GetFuelMassMax()
|
||||
local relfuel = self:GetFuel()
|
||||
local maxfilling = math.max(fuel,maxfuel)
|
||||
local mass = maxfilling*relfuel
|
||||
return mass
|
||||
end
|
||||
|
||||
@@ -350,7 +350,7 @@ end
|
||||
-- myweapon:SetFuncImpact(OnImpact)
|
||||
--
|
||||
-- -- Start tracking.
|
||||
-- myweapon:Track()
|
||||
-- myweapon:StartTrack()
|
||||
--
|
||||
function WEAPON:SetFuncImpact(FuncImpact, ...)
|
||||
self.impactFunc=FuncImpact
|
||||
|
||||
@@ -75,6 +75,7 @@ Functional/ZoneGoalCargo.lua
|
||||
Functional/Tiresias.lua
|
||||
Functional/Stratego.lua
|
||||
Functional/ClientWatch.lua
|
||||
Functional/Formation.lua
|
||||
|
||||
Ops/Airboss.lua
|
||||
Ops/RecoveryTanker.lua
|
||||
@@ -103,6 +104,7 @@ Ops/FlightControl.lua
|
||||
Ops/PlayerTask.lua
|
||||
Ops/PlayerRecce.lua
|
||||
Ops/EasyGCICAP.lua
|
||||
Ops/EasyA2G.lua
|
||||
Ops/OpsZone.lua
|
||||
Ops/ArmyGroup.lua
|
||||
Ops/OpsTransport.lua
|
||||
|
||||
Reference in New Issue
Block a user