Compare commits

..

99 Commits

Author SHA1 Message Date
Frank
1f20b830b7 Merge branch 'develop' into FF/OpsDev 2025-04-17 21:40:41 +02:00
Frank
0531b8f57e Merge branch 'FF/Ops' into FF/OpsDev 2025-04-09 22:47:34 +02:00
Frank
98e2997e26 Merge branch 'develop' into FF/OpsDev 2025-04-09 22:43:24 +02:00
Frank
7d3bffcfef Naviation
- Added beacons class
- Added navpoints class
- Added vector class
2025-04-09 22:36:26 +02:00
Frank
5b76ec6b99 Merge branch 'develop' into FF/Ops 2025-04-09 21:17:49 +02:00
Frank
eeca95c77f Merge branch 'develop' into FF/Ops 2025-01-02 20:49:06 +01:00
Frank
e406fb0c88 Update Brigade.lua 2024-12-29 21:02:56 +01:00
Frank
bdec6eb82d Merge branch 'FF/Ops' into FF/OpsDev 2024-07-07 11:33:44 +02:00
Frank
9bdacfbcf7 Merge branch 'develop' into FF/OpsDev 2024-07-01 16:46:22 +02:00
Frank
be2eda25df Merge branch 'develop' into FF/OpsDev 2024-06-27 21:31:24 +02:00
Frank
fa8e1d2a56 Merge branch 'FF/Ops' into FF/OpsDev 2024-06-19 07:53:54 +02:00
Frank
5242754c60 Merge branch 'FF/Ops' into FF/OpsDev 2024-04-26 14:33:05 +02:00
Frank
3dbafd7a19 Update Astar.lua 2024-04-20 14:34:15 +02:00
Frank
56e751193a Merge branch 'FF/Ops' into FF/OpsDev 2024-04-04 09:35:53 +02:00
Frank
b2b258cff0 Merge branch 'FF/Ops' into FF/OpsDev 2024-03-30 22:32:42 +01:00
Frank
75068a2a26 Merge branch 'FF/Ops' into FF/OpsDev 2024-03-24 17:25:02 +01:00
Frank
d82df93eff Merge branch 'FF/Ops' into FF/OpsDev 2024-01-23 11:05:50 +01:00
Frank
5168380eac Update Modules.lua 2024-01-21 16:41:09 +01:00
Frank
a7ffbab850 Update Modules.lua 2024-01-16 19:44:08 +01:00
Frank
a238294079 Merge branch 'FF/Ops' into FF/OpsDev 2024-01-16 17:37:41 +01:00
Frank
accb4b45fb Merge branch 'FF/Ops' into FF/OpsDev 2024-01-07 21:17:17 +01:00
Frank
ba4ff5c9e9 Update Procedure.lua 2024-01-07 10:29:31 +01:00
Frank
171c450576 Merge branch 'FF/Ops' into FF/OpsDev 2024-01-04 15:14:25 +01:00
Frank
f35078b426 Merge branch 'FF/Ops' into FF/OpsDev 2024-01-02 17:07:15 +01:00
Frank
8921007805 Update Point.lua 2024-01-02 17:04:04 +01:00
Frank
8fd3034503 Merge branch 'FF/Ops' into FF/OpsDev 2024-01-01 21:55:52 +01:00
Frank
a20b6e8a62 Merge branch 'FF/Ops' into FF/OpsDev 2023-12-03 21:10:24 +01:00
Frank
4ce6cc7776 Merge branch 'FF/Ops' into FF/OpsDev 2023-11-17 18:06:17 +01:00
Frank
434296ab11 Merge branch 'FF/Ops' into FF/OpsDev 2023-11-07 00:06:35 +01:00
Frank
a5232f49c0 Navigation 2023-11-06 21:35:20 +01:00
Frank
befc8207f5 Update Nav 2023-11-02 23:42:59 +01:00
Frank
23947b7c30 Merge branch 'FF/Ops' into FF/OpsDev 2023-11-02 20:33:53 +01:00
Frank
da91b710a9 Merge branch 'FF/Ops' into FF/OpsDev 2023-10-31 20:17:10 +01:00
Frank
d656bbc014 Update Vector.lua 2023-10-19 21:49:45 +02:00
Frank
ed23f11e4a Merge branch 'FF/Ops' into FF/OpsDev 2023-10-13 22:00:57 +02:00
Frank
068465a8f2 Merge branch 'FF/Ops' into FF/OpsDev 2023-10-13 21:33:33 +02:00
Frank
4e24a7bf80 Navigation 2023-10-08 21:49:15 +02:00
Frank
1ece7238dc NAv 2023-10-05 16:52:41 +02:00
Frank
db53f427e3 Update Point.lua 2023-10-02 17:02:21 +02:00
Frank
d2e2c51275 Update FlightPlan.lua 2023-09-29 13:27:59 +02:00
Frank
691748082b Merge branch 'FF/Ops' into FF/OpsDev 2023-09-27 22:29:38 +02:00
Frank
b294ef10c8 Update NavyGroup.lua 2023-09-27 10:54:37 +02:00
Frank
5d40091947 NAVYGROUP
- Improved heading into wind
2023-09-27 10:53:19 +02:00
Frank
7453a6c55d navygroup
- into wind
2023-09-26 17:02:13 +02:00
Frank
153ef7cd08 utils 2023-09-26 01:44:30 +02:00
Frank
b6a550a247 Update Vector.lua 2023-09-25 16:59:41 +02:00
Frank
3568f27150 Vector 2023-09-24 22:46:50 +02:00
Frank
c46061466c VECTOR
- Added functions
2023-09-24 22:09:49 +02:00
Frank
9c7b5e8506 VECTOR
**VECTOR**
- New core class for vector algebra
2023-09-23 01:15:08 +02:00
Frank
b11df6b523 Navigation
Refactored names etc.
2023-09-21 23:37:25 +02:00
Frank
66dcd44fb8 Merge branch 'FF/OpsDev' of https://github.com/FlightControl-Master/MOOSE into FF/OpsDev 2023-09-21 17:48:12 +02:00
Frank
f35237f86f Update FlightControl.lua 2023-09-21 17:48:04 +02:00
Frank
f19c877a11 Navigation 2023-09-21 17:01:32 +02:00
Frank
792a487eeb Merge branch 'FF/Ops' into FF/OpsDev 2023-09-20 22:32:16 +02:00
Frank
f5e8bd0ffc Merge branch 'FF/Ops' into FF/OpsDev 2023-09-20 14:39:41 +02:00
Frank
cbdbf36f32 FC
- Improved taxi phrases
2023-09-20 11:52:14 +02:00
Frank
1116b19b00 FC 2023-09-19 17:04:37 +02:00
Frank
edb490fa97 Update Pathline.lua
- json
2023-09-19 03:01:47 +02:00
Frank
9fa16c6385 Update FlightControl.lua 2023-09-18 16:52:13 +02:00
Frank
a0ee957493 Update FlightControl.lua 2023-09-17 23:07:59 +02:00
Frank
f32c303c0a pathline 2023-09-17 22:58:53 +02:00
Frank
6febbbc8e6 FC 2023-09-17 21:55:29 +02:00
Frank
d5aa9eaf0f Merge branch 'FF/Ops' into FF/OpsDev 2023-09-17 16:26:23 +02:00
Frank
321b2d761d FLIGHTCONTROL
- Improved Taxi comms
2023-09-17 16:10:59 +02:00
Frank
18581e4a78 Merge branch 'FF/OpsDev' of https://github.com/FlightControl-Master/MOOSE into FF/OpsDev 2023-09-14 17:40:11 +02:00
Frank
b03905154d ASTAR Pathlines 2023-09-14 17:40:02 +02:00
Frank
2fda1709bc Update FlightControl.lua 2023-09-14 16:46:08 +02:00
Frank
39f626390a Merge branch 'FF/Ops' into FF/OpsDev 2023-09-13 11:14:55 +02:00
Frank
aa1e12163d ASTAR/PATHLINE 2023-09-12 23:57:50 +02:00
Frank
abb7f860ae Update Astar.lua 2023-09-12 03:21:23 +02:00
Frank
cffada1a1e AIRBASE
- Taxiways
2023-09-11 16:54:10 +02:00
Frank
f8d91798e3 Update FlightControl.lua 2023-09-10 23:55:40 +02:00
Frank
548aa8d5a9 Merge branch 'FF/Ops' into FF/OpsDev 2023-09-10 22:43:13 +02:00
Frank
1e929d1d19 ASTAR Taxi 2023-09-10 22:38:00 +02:00
Frank
d5f215505e Merge branch 'FF/Ops' into FF/OpsDev 2023-09-09 00:32:29 +02:00
Frank
851660c793 NAV 2023-09-08 16:11:11 +02:00
Frank
ee57b46c14 Update Astar.lua 2023-09-07 23:32:07 +02:00
Frank
36eac9fccc PATH 2023-09-06 22:50:24 +02:00
Frank
1f630ab490 Astar Pathline 2023-09-05 23:29:27 +02:00
Frank
8f0c6948ac Merge branch 'FF/Ops' into FF/OpsDev 2023-09-05 20:53:45 +02:00
Frank
eb2380c3f6 Merge branch 'FF/Ops' into FF/OpsDev 2023-09-04 23:35:05 +02:00
Frank
df3e182de4 Merge branch 'FF/OpsDev' of https://github.com/FlightControl-Master/MOOSE into FF/OpsDev 2023-09-04 23:34:26 +02:00
Frank
bda7988118 Astar 2023-09-04 23:34:23 +02:00
Frank
72deb9ee17 Merge branch 'FF/OpsDev' of https://github.com/FlightControl-Master/MOOSE into FF/OpsDev 2023-08-30 15:53:49 +02:00
Frank
ced1f30f3f Update DCS.lua 2023-08-30 15:53:35 +02:00
Frank
47c0006537 ASTAR & PATHLINE 2023-08-29 22:43:36 +02:00
Frank
21e5c124c0 Astar 2023-08-28 22:38:37 +02:00
Frank
3ff374e1b9 Merge branch 'FF/Ops' into FF/OpsDev 2023-08-27 20:39:51 +02:00
Frank
02000be9af Merge branch 'FF/Ops' into FF/OpsDev 2023-08-17 19:15:12 +02:00
Frank
663c6cead0 Update FlightControl.lua 2023-08-16 20:46:53 +02:00
Frank
6867df58c4 Merge branch 'FF/Ops' into FF/OpsDev 2023-08-13 18:22:12 +02:00
Frank
639625d3d5 Update FlightControl.lua 2023-08-13 14:16:40 +02:00
Frank
0ace200e5a Nav
- Small stuff
2023-08-04 00:08:16 +02:00
Frank
e76c26ff59 FLIGHTPLAN 2023-08-03 00:41:34 +02:00
Frank
cb11de6f9c Navigation
- Added NavFix
2023-08-02 10:53:28 +02:00
Frank
cdd78e5163 Update FlightPlan.lua 2023-08-01 00:25:08 +02:00
Frank
a8da02774a Update FlightPlan.lua 2023-08-01 00:24:49 +02:00
Frank
04258a69c4 Navigation
- Init stuff
2023-07-31 21:58:01 +02:00
Frank
49882f03d9 Update FlightControl.lua 2023-07-26 16:05:15 +02:00
117 changed files with 8203 additions and 6659 deletions

View File

@@ -1953,7 +1953,7 @@ local function refct_from_id(id) -- refct = refct_from_id(CTypeID)
unsigned = refct.unsigned,
size = bit.band(bit.rshift(ctype.info, 16), 127),
}
refct.bool, refct.const, refct.volatile, refct.unsigned = nil, nil, nil, nil
refct.bool, refct.const, refct.volatile, refct.unsigned = nil
end
if CT[4] then -- Merge sibling attributes onto this type.

View File

@@ -18,8 +18,6 @@
--- The AI_A2A_CAP class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_A2A_CAP is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event.

View File

@@ -33,8 +33,6 @@
--
-- ===
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # QUICK START GUIDE
--
-- There are basically two classes available to model an A2A defense system.

View File

@@ -19,8 +19,6 @@
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_A2A_GCI is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event.
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.

View File

@@ -15,8 +15,6 @@
--- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_PATROL\Dia3.JPG)
--
-- The AI_A2A_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event.

View File

@@ -16,8 +16,6 @@
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE

View File

@@ -18,8 +18,6 @@
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--

View File

@@ -36,8 +36,6 @@
--
-- # QUICK START GUIDE
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The following class is available to model an A2G defense system.
--
-- AI_A2G_DISPATCHER is the main A2G defense class that models the A2G defense system.

View File

@@ -19,8 +19,6 @@
--- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_A2G_SEAD is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_SEAD process can be started using the **Start** event.
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.

View File

@@ -15,7 +15,6 @@
--- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # 1) AI_AIR constructor
--

View File

@@ -36,8 +36,6 @@
--
-- # QUICK START GUIDE
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The following class is available to model an AIR defense system.
--
-- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system.

View File

@@ -13,14 +13,12 @@
--- @type AI_AIR_ENGAGE
-- @type AI_AIR_ENGAGE
-- @extends AI.AI_AIR#AI_AIR
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_AIR_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_ENGAGE process can be started using the **Start** event.
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.

View File

@@ -15,8 +15,6 @@
--- The AI_AIR_PATROL class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_AIR_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_PATROL process can be started using the **Start** event.

View File

@@ -13,7 +13,7 @@
--- @type AI_AIR_SQUADRON
-- @type AI_AIR_SQUADRON
-- @extends Core.Base#BASE
@@ -21,8 +21,6 @@
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--

View File

@@ -38,8 +38,6 @@
--- Implements the core functions to provide BattleGround Air Interdiction in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_BAI_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
--
-- ![HoldAndEngage](..\Presentations\AI_BAI\Dia3.JPG)

View File

@@ -33,9 +33,8 @@
-- @field Wrapper.Group#GROUP Test
-- @extends Core.Fsm#FSM_SET
--- ![Banner Image](..\Images\deprecated.png)
--
-- Monitors and manages as many replacement AI groups as there are
--- Monitors and manages as many replacement AI groups as there are
-- CLIENTS in a SET\_CLIENT collection, which are not occupied by human players.
-- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
--

View File

@@ -39,8 +39,6 @@
--- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_CAP_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event.

View File

@@ -38,9 +38,6 @@
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
--- Implements the core functions to provide Close Air Support in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
--
-- ![HoldAndEngage](..\Presentations\AI_CAS\Dia3.JPG)

View File

@@ -9,14 +9,12 @@
-- @module AI.AI_Cargo
-- @image Cargo.JPG
--- @type AI_CARGO
-- @type AI_CARGO
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Base class for the dynamic cargo handling capability for AI groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Carriers can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI_CARGO module uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI_CARGO object recognize the cargo.

View File

@@ -15,8 +15,6 @@
--- Brings a dynamic cargo handling capability for an AI vehicle group.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
-- The AI_CARGO_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.

View File

@@ -15,8 +15,6 @@
--- Brings a dynamic cargo handling capability for an AI airplane group.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Airplane carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation between airbases.
--
-- The AI_CARGO_AIRPLANE module uses the @{Cargo.Cargo} capabilities within the MOOSE framework.

View File

@@ -22,8 +22,6 @@
--
-- ===
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # The dispatcher concept.
--
-- Carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.

View File

@@ -36,8 +36,6 @@
--- A dynamic cargo transportation capability for AI groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
-- The AI_CARGO_DISPATCHER_APC module is derived from the AI_CARGO_DISPATCHER module.

View File

@@ -30,8 +30,6 @@
--- Brings a dynamic cargo handling capability for AI groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Airplanes can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
-- The AI_CARGO_DISPATCHER_AIRPLANE module is derived from the AI_CARGO_DISPATCHER module.

View File

@@ -31,8 +31,6 @@
--- A dynamic cargo handling capability for AI helicopter groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
--

View File

@@ -29,8 +29,6 @@
--- A dynamic cargo transportation capability for AI groups.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation.
--
-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module.

View File

@@ -15,8 +15,6 @@
--- Brings a dynamic cargo handling capability for an AI helicopter group.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Helicopter carriers can be mobilized to intelligently transport infantry and other cargo within the simulation.
--
-- The AI_CARGO_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.

View File

@@ -14,8 +14,6 @@
--- Brings a dynamic cargo handling capability for an AI naval group.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes.
-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo.

View File

@@ -25,8 +25,6 @@
--
-- Allows you to interact with escorting AI on your flight and take the lead.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10).
--
-- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes.

View File

@@ -23,8 +23,6 @@
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--

View File

@@ -21,8 +21,6 @@
--- Models the assignment of AI escorts to player flights upon request using the radio menu.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # Developer Note
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE

View File

@@ -25,8 +25,6 @@
--
-- Allows you to interact with escorting AI on your flight and take the lead.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10).
--
-- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes.

View File

@@ -41,8 +41,6 @@
--- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- AI_FORMATION makes AI @{Wrapper.Group#GROUP}s fly in formation of various compositions.
-- The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!!
-- The purpose of the class is to:

View File

@@ -48,8 +48,6 @@
--- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ![Process](..\Presentations\AI_PATROL\Dia3.JPG)
--
-- The AI_PATROL_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event.

View File

@@ -1,6 +1,6 @@
--- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occurring on UNITs.
--
-- ![Banner Image](..\Images\deprecated.png)
-- ![Banner Image](..\Presentations\ACT_ACCOUNT\Dia1.JPG)
--
-- ===
--
@@ -11,8 +11,6 @@ do -- ACT_ACCOUNT
--- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS}
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ## ACT_ACCOUNT state machine:
--
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
@@ -135,7 +133,7 @@ do -- ACT_ACCOUNT
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To )
function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event )
self:__NoMore( 1 )
end

View File

@@ -2,8 +2,6 @@
--
-- ===
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS}
--
-- ## ACT_ASSIGN state machine:

View File

@@ -1,6 +1,5 @@
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
--
-- ![Banner Image](..\Images\deprecated.png)
-- ## ACT_ASSIST state machine:
--
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.

View File

@@ -2,8 +2,6 @@
--
-- ===
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS}
--
-- ## ACT_ROUTE state machine:

View File

@@ -2,8 +2,6 @@
--
-- ===
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # 1) MOOSE Cargo System.
--
-- #### Those who have used the mission editor, know that the DCS mission editor provides cargo facilities.

View File

@@ -22,9 +22,6 @@ do -- CARGO_CRATE
-- @type CARGO_CRATE
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
---
-- ![Banner Image](..\Images\deprecated.png)
--
--- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_CRATE objects to and from carriers.
--

View File

@@ -26,8 +26,6 @@ do -- CARGO_GROUP
-- @extends Cargo.Cargo#CARGO_REPORTABLE
--- Defines a cargo that is represented by a @{Wrapper.Group} object within the simulator.
--
-- ![Banner Image](..\Images\deprecated.png)
-- The cargo can be Loaded, UnLoaded, Boarded, UnBoarded to and from Carriers.
--
-- The above cargo classes are used by the following AI_CARGO_ classes to allow AI groups to transport cargo:

View File

@@ -32,8 +32,6 @@ do -- CARGO_SLINGLOAD
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--

View File

@@ -30,8 +30,6 @@ do -- CARGO_UNIT
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--

View File

@@ -157,6 +157,8 @@ ASTAR = {
-- @field #number surfacetype Surface type.
-- @field #table valid Cached valid/invalid nodes.
-- @field #table cost Cached cost.
-- @field Core.Pathline#PATHLINE pathline Pathline that node is part of.
-- @field Core.Pathline#PATHLINE.Point pathpoint Pathline point.
--- ASTAR infinity.
-- @field #number INF
@@ -164,7 +166,7 @@ ASTAR.INF=1/0
--- ASTAR class version.
-- @field #string version
ASTAR.version="0.4.0"
ASTAR.version="0.5.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -172,6 +174,7 @@ ASTAR.version="0.4.0"
-- TODO: Add more valid neighbour functions.
-- TODO: Write docs.
-- DONE: Add pathlines for seach/valid neighbours.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@@ -246,6 +249,9 @@ function ASTAR:AddNode(Node)
self.nodes[Node.id]=Node
self.Nnodes=self.Nnodes+1
self:T3(self.lid..string.format("Adding node UID=%d", Node.id))
--Node.coordinate:MarkToAll(string.format("Node ID=%d", Node.id))
return self
end
@@ -262,6 +268,47 @@ function ASTAR:AddNodeFromCoordinate(Coordinate)
return node
end
--- Adds nodes to the table of grid nodes from a PATHLINE.
-- @param #ASTAR self
-- @param Core.Pathline#PATHLINE Pathline Pathline or name of pathline. Has to exist.
-- @return #ASTAR self
function ASTAR:AddNodeFromPathlineName(Pathline)
if type(Pathline)=="string" then
Pathline=PATHLINE:FindByName(Pathline)
end
if Pathline then
for i,_point in pairs(Pathline.points) do
local point=_point --Core.Pathline#PATHLINE.Point
-- Create node from point coordinate.
local node=self:AddNodeFromCoordinate(COORDINATE:NewFromVec3(point.vec3))
-- Add pathline parameters.
node.pathline=Pathline
node.pathpoint=point
-- Debug.
local name=node.pathline and node.pathline.name or "N/A"
local idx=node.pathline and node.pathline:_GetPointIndex(node.pathpoint) or "N/A"
-- Debug message.
self:T(self.lid..string.format("Adding node UID=%d pathline=%s [%s]", node.id, name, tostring(idx)))
-- Debug mark
--node.coordinate:MarkToAll(string.format("Node ID=%d\npathline=%s [%s]", node.id, name, tostring(idx)))
end
else
env.error("FF error pathline")
end
return self
end
--- Check if the coordinate of a node has is at a valid surface type.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added.
@@ -340,6 +387,18 @@ function ASTAR:SetValidNeighbourRoad(MaxDistance)
return self
end
--- Set valid neighbours to be on the same pathline or not further apart than 10 meters to jump from one pathline to another.
-- @param #ASTAR self
-- @param #number MaxDistance Max allowed distance between nodes of different pathlines in meters. Default is 10 m.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourPathline(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.Pathline, MaxDistance)
return self
end
--- Set the function which calculates the "cost" to go from one to another node.
-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments.
-- Very often the distance between nodes is a good measure for the cost.
@@ -384,7 +443,7 @@ end
-- @return #ASTAR self
function ASTAR:SetCostRoad()
self:SetCostFunction(ASTAR)
self:SetCostFunction(ASTAR.Road)
return self
end
@@ -544,6 +603,55 @@ function ASTAR.Road(nodeA, nodeB)
end
--- Function to check if two nodes are on the same pathline or if nodes are less than 10 meters apart.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @param #number distmax Max distance in meters. Default is 10 m.
-- @return #boolean If true, two nodes are connected.
function ASTAR.Pathline(nodeA, nodeB, distmax)
distmax=distmax or 10
if nodeA.pathline.name==nodeB.pathline.name then
-- Nodes are on the same pathline. We use the index to check if they are neighbours.
local pathline=nodeA.pathline
local idxA=pathline:_GetPointIndex(nodeA.pathpoint)
local idxB=pathline:_GetPointIndex(nodeB.pathpoint)
if math.abs(idxA-idxB)<=1 then
return true
end
else
-- Check if nodeB is close to pathline of nodeA.
local c, dist, segA=nodeA.pathline:GetClosestPoint3D(nodeB.coordinate)
local seg=segA --Core.Pathline#PATHLINE.Segment
if dist<distmax and (nodeA.pathpoint.uid==seg.p1.uid or nodeA.pathpoint.uid==seg.p2.uid) then
--env.info(string.format("FF NodeB=%d [pathline=%s] is close to NodeA=%d [pathline=%s] ==> valid neighbour", nodeB.id, nodeB.pathline.name, nodeA.id, nodeA.pathline.name))
return true
end
-- Check if nodeA is close to pathline of nodeB.
local c, dist, segB=nodeB.pathline:GetClosestPoint3D(nodeA.coordinate)
local seg=segB --Core.Pathline#PATHLINE.Segment
if dist<distmax and (nodeB.pathpoint.uid==seg.p1.uid or nodeB.pathpoint.uid==seg.p2.uid) then
--env.info(string.format("FF NodeA=%d [pathline=%s] is close to NodeB=%d [pathline=%s] ==> valid neighbour", nodeA.id, nodeA.pathline.name, nodeB.id, nodeB.pathline.name))
return true
end
end
return false
end
--- Function to check if distance between two nodes is less than a threshold distance.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
@@ -567,7 +675,9 @@ end
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.Dist2D(nodeA, nodeB)
local dist=nodeA.coordinate:Get2DDistance(nodeB)
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
--local text=string.format("FF Cost Dist2D NodeA=%d-->NodeB=%d = %.1f", nodeA.id, nodeB.id, dist)
--env.info(text)
return dist
end
@@ -604,7 +714,6 @@ function ASTAR.DistRoad(nodeA, nodeB)
return dist
end
return math.huge
end
@@ -614,10 +723,11 @@ end
--- Find the closest node from a given coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate.
-- @return #ASTAR.Node Cloest node to the coordinate.
-- @param Core.Point#COORDINATE Coordinate Reference coordinate.
-- @param #table ExcludeNodes Table of nodes that are excluded.
-- @return #ASTAR.Node Closest node to the coordinate.
-- @return #number Distance to closest node in meters.
function ASTAR:FindClosestNode(Coordinate)
function ASTAR:FindClosestNode(Coordinate, ExcludeNodes)
local distMin=math.huge
local closeNode=nil
@@ -625,6 +735,8 @@ function ASTAR:FindClosestNode(Coordinate)
for _,_node in pairs(self.nodes) do
local node=_node --#ASTAR.Node
if ExcludeNodes==nil or self:_IsNodeNotInTable(ExcludeNodes, node) then
local dist=node.coordinate:Get2DDistance(Coordinate)
if dist<distMin then
@@ -634,42 +746,168 @@ function ASTAR:FindClosestNode(Coordinate)
end
end
return closeNode, distMin
end
--- Find the closest pathline to a given reference coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate Reference coordinate.
-- @return Core.Pathline#PATHLINE Closest pathline
-- @return #number Distance in meters.
-- @return DCS#Vec3 Closest point on pathline to the ref coordinate.
-- @return Core.Pathline#PATHLINE.Segment Segment.
function ASTAR:FindClosestPathline(Coordinate)
local pathline=nil --Core.Pathline#PATHLINE
local dist=math.huge
local vec3=nil
local S=nil
for _,_node in pairs(self.nodes) do
local node=_node --#ASTAR.Node
if node.pathline then
local vec, d, s=node.pathline:GetClosestPoint3D(Coordinate)
if d<dist then
pathline=node.pathline
dist=d
vec3=vec
S=s
end
end
end
if pathline then
-- Debug info.
self:T(self.lid..string.format("Closest pathline %s: dist=%.1f", pathline.name, dist))
end
return pathline, dist, vec3, S
end
--- Find the closest node to the given coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coord Reference coordinate.
-- @param #table ExcludeNodes Nodes that are excluded.
-- @return #ASTAR.Node The node that was fround
function ASTAR:_FindClosestTerminalNode(Coord, ExcludeNodes)
-- Find the closest pathline to the ref coordinate.
local pathline, dist, vec3, s=self:FindClosestPathline(Coord)
-- Find the closest node to the given start coordinate.
local node, dist2=self:FindClosestNode(Coord)
if pathline and vec3 and dist and dist2>dist then
-- Create a node on the closest pathline so we first go straight there and then along the pathline.
local node=self:AddNodeFromCoordinate(COORDINATE:NewFromVec3(vec3))
-- We also need the pathline point.
local point=pathline:AddPointFromVec3(vec3, nil, s.p1)
node.pathline=pathline
node.pathpoint=point
self:T2(self.lid..string.format("Added new node=%d, which is closest to start coord. dist=%.1f m", node.id, dist))
end
-- Find the closest node to the given start coordinate.
local Node, dist3=self:FindClosestNode(Coord, ExcludeNodes)
-- Debug info.
self:T(self.lid..string.format("CLOSEST node ID=%d, distance=%.1f", Node.id, dist3))
return Node, dist3
end
--- Find the start node.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added to the nodes table.
-- @return #ASTAR self
function ASTAR:FindStartNode()
local node, dist=self:FindClosestNode(self.startCoord)
-- Find the closest pathline to the
local pathline, dist, vec3, s=self:FindClosestPathline(self.startCoord)
self.startNode=node
-- Find the closest node to the given start coordinate.
local node, dist2=self:FindClosestNode(self.startCoord)
if dist>1000 then
self:T(self.lid.."Adding start node to node grid!")
self:AddNode(node)
if pathline and vec3 and dist and dist2>dist then
-- Create a node on the closest pathline so we first go straight there and then along the pathline.
local node=self:AddNodeFromCoordinate(COORDINATE:NewFromVec3(vec3))
-- We also need the pathline point.
local point=pathline:AddPointFromVec3(vec3, nil, s.p1)
node.pathline=pathline
node.pathpoint=point
self:T2(self.lid..string.format("Added new node=%d, which is closest to start coord. dist=%.1f m", node.id, dist))
end
-- Find the closest node to the given start coordinate.
self.startNode, dist2=self:FindClosestNode(self.startCoord)
--self.startNode.coordinate:MarkToAll("Start Node")
-- Debug info.
self:T(self.lid..string.format("START node ID=%d", self.startNode.id))
-- Not sure why I did this. The node does not need to be added again as it is already contained in self.nodes!
-- if dist>1000 then
-- self:T(self.lid.."Adding start node to node grid!")
-- self:AddNode(node)
-- end
return self
end
--- Add a node.
--- Find the end node.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added to the nodes table.
-- @return #ASTAR self
function ASTAR:FindEndNode()
local node, dist=self:FindClosestNode(self.endCoord)
local pathline, dist, vec3, s=self:FindClosestPathline(self.endCoord)
self.endNode=node
-- Find the closest node to the given start coordinate.
local node, dist2=self:FindClosestNode(self.endCoord)
if dist>1000 then
self:T(self.lid.."Adding end node to node grid!")
self:AddNode(node)
if pathline and vec3 and dist and dist2>dist then
-- Create a node on the closest pathline so we first go straight there and then along the pathline.
local node=self:AddNodeFromCoordinate(COORDINATE:NewFromVec3(vec3))
-- We also need the point.
local point=pathline:AddPointFromVec3(vec3, nil, s.p1)
-- Add pathline parameters to node.
node.pathline=pathline
node.pathpoint=point
self:T2(self.lid..string.format("Added new node=%d, which is closest to END coord: dist=%.1f m", node.id, dist))
end
-- Find closest node to the end coordinate (exclude the start coordinate.
self.endNode, dist=self:FindClosestNode(self.endCoord, {self.startNode})
--self.endNode.coordinate:MarkToAll("End Node")
-- Debug info.
self:T(self.lid..string.format("END node ID=%d", self.endNode.id))
-- Not sure why I did this. The node does not need to be added again as it is already contained in self.nodes!
-- if dist>1000 then
-- self:T(self.lid.."Adding end node to node grid!")
-- self:AddNode(node)
-- end
return self
end
@@ -684,13 +922,22 @@ end
-- @return #table Table of nodes from start to finish.
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
self:FindStartNode()
self:FindEndNode()
-- self:FindStartNode()
-- self:FindEndNode()
-- Find start Node (closest node to start coordinate).
self.startNode=self:_FindClosestTerminalNode(self.startCoord)
-- Find end node, which is not the start node (excluded).
self.endNode=self:_FindClosestTerminalNode(self.endCoord, {self.startNode})
local nodes=self.nodes
local start=self.startNode
local goal=self.endNode
-- Debug info.
self:T(self.lid..string.format("GetPath Start Node=%d, End Node=%d", start.id, goal.id))
-- Sets.
local openset = {}
local closedset = {}
@@ -747,6 +994,11 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
end
text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache)
text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache)
text=text..string.format("\nNodes:")
for i,_node in ipairs(path) do
local node=_node --#ASTAR.Node
text=text..string.format("\n[%d] Node ID=%d", i, node.id)
end
self:T(self.lid..text)
return path
@@ -763,9 +1015,12 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
-- Loop over neighbours.
for _,neighbor in pairs(neighbors) do
-- Node is not in closed set.
if self:_NotIn(closedset, neighbor.id) then
local tentative_g_score=g_score[current.id]+self:_DistNodes(current, neighbor)
-- Calculate tentative_g_score.
--local tentative_g_score=g_score[current.id] + self:_DistNodes(current, neighbor)
local tentative_g_score=g_score[current.id] + self:_HeuristicCost(current, neighbor)
if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then
@@ -793,6 +1048,73 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
return nil -- no valid path
end
--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates.
-- It automatically creates a PATHLINE object that is returned in combination with the nodes of the optimal path.
-- @param #ASTAR self
-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it.
-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it.
-- @return Core.Pathline#PATHLINE Pathline.
-- @return #table Nodes of path.
function ASTAR:GetPathline(ExcludeStartNode, ExcludeEndNode)
local nodes=self:GetPath(ExcludeStartNode, ExcludeEndNode)
local pathline=nil --Core.Pathline#PATHLINE
if nodes then
pathline=PATHLINE:New("Astar")
for _,_node in pairs(nodes) do
local node=_node --#ASTAR.Node
local point=pathline:AddPointFromVec3(node.coordinate)
point.name=node.pathline.name
end
end
return pathline, nodes
end
--- Get pathlines from nodes.
-- @param #ASTAR self
-- @param #table Nodes Given nodes.
-- @return #table Table of PATHLINES used in the path.
function ASTAR:GetPathlinesFromNodes(Nodes)
local pathlines={}
--for _,_node in pairs(Nodes or {}) do
for i=1,#Nodes do
local node=Nodes[i] --#ASTAR.Node
-- Pathline.
local pathline=node.pathline
if pathline and i>1 and i<#Nodes then
-- Previous and next nodes.
local n=Nodes[i-1] --#ASTAR.Node
local N=Nodes[i+1] --#ASTAR.Node
-- Check if previous and next nodes are on the same pathline.
-- If only one point in beteen is of another pathline, this is a junction and we dont actually switch to the other pathline.
if n.pathline and N.pathline and n.pathline.name==N.pathline.name and n.pathline.name~=pathline.name then
pathline=n.pathline
end
end
-- We do not want to add the same pathline two times in a row.
if #pathlines==0 or (#pathlines>0 and pathlines[#pathlines].name~=pathline.name) then
table.insert(pathlines, pathline)
end
end
return pathlines
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- A* pathfinding helper functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -811,16 +1133,18 @@ function ASTAR:_HeuristicCost(nodeA, nodeB)
local cost=nodeA.cost[nodeB.id]
if cost~=nil then
self.ncostcache=self.ncostcache+1
self:T(self.lid..string.format("Cost nodeA=%d --> nodeB=%d = %.1f (Cashed!)", nodeA.id, nodeB.id, cost))
return cost
end
local cost=nil
if self.CostFunc then
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
else
cost=self:_DistNodes(nodeA, nodeB)
end
self:T(self.lid..string.format("Cost nodeA=%d --> nodeB=%d = %.1f", nodeA.id, nodeB.id, cost))
nodeA.cost[nodeB.id]=cost
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
@@ -834,9 +1158,10 @@ end
-- @return #boolean If true, transition between nodes is possible.
function ASTAR:_IsValidNeighbour(node, neighbor)
-- Counter.
-- Counter of function calls.
self.nvalid=self.nvalid+1
-- Check if neighbour is in cached set.
local valid=node.valid[neighbor.id]
if valid~=nil then
--env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id))
@@ -844,13 +1169,16 @@ function ASTAR:_IsValidNeighbour(node, neighbor)
return valid
end
-- Check if this is a valid neighbour.
local valid=nil
if self.ValidNeighbourFunc then
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
else
-- If no valid neighbour function is defined, we assume all nodes are valid neighbours.
valid=true
end
-- Cache valid neighbour.
node.valid[neighbor.id]=valid
neighbor.valid[node.id]=valid -- Symmetric problem.
@@ -884,6 +1212,9 @@ function ASTAR:_LowestFscore(set, f_score)
end
end
-- Debug info.
self:T(self.lid..string.format("Lowest Fscore=%.1f, Node=%s", lowest, tostring(bestNode)))
return self.nodes[bestNode]
end
@@ -928,16 +1259,46 @@ end
-- @param #table map Map.
-- @param #ASTAR.Node current_node The current node.
-- @return #table Unwinded path.
function ASTAR:_UnwindPath( flat_path, map, current_node )
function ASTAR:_UnwindPath(flat_path, map, current_node)
if map [current_node] then
table.insert (flat_path, 1, map[current_node])
return self:_UnwindPath(flat_path, map, map[current_node])
local previous_node=map[current_node]
if previous_node then
table.insert(flat_path, 1, previous_node)
return self:_UnwindPath(flat_path, map, previous_node)
else
-- No previous node ==> return path.
return flat_path
end
end
--- Function to check if a certain node is in a given table.
-- @param #ASTAR self
-- @param #table Nodes Nodes table.
-- @param #ASTAR.Node Node The node to check.
-- @return #boolean If true, the node is not in the set.
function ASTAR:_IsNodeInTable(Nodes, Node)
for _,_node in pairs(Nodes) do
local node=_node --#ASTAR.Node
if node.id==Node.id then
return true
end
end
return false
end
--- Function to check if a certain node is **not** in a given table.
-- @param #ASTAR self
-- @param #table Nodes Nodes table.
-- @param #ASTAR.Node Node The node to check.
-- @return #boolean If true, the node is not in the set.
function ASTAR:_IsNodeNotInTable(Nodes, Node)
local is=self:_IsNodeInTable(Nodes, Node)
return not is
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -974,7 +974,7 @@ do -- Scheduling
-- @param #BASE self
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #string The Schedule ID of the planned schedule.
function BASE:ScheduleOnce( Start, SchedulerFunction, ... )

View File

@@ -872,8 +872,6 @@ end
-- @return Wrapper.Group#GROUP The found GROUP.
function DATABASE:FindGroup( GroupName )
if type(GroupName) ~= "string" or GroupName == "" then return end
local GroupFound = self.GROUPS[GroupName]
if GroupFound == nil and GroupName ~= nil and self.Templates.Groups[GroupName] == nil then
@@ -1112,7 +1110,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
else
self.STNS[stn] = UnitTemplate.name
self:T("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
end
end
if UnitTemplate.AddPropAircraft.SADL_TN then
@@ -1121,7 +1119,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
else
self.SADL[sadl] = UnitTemplate.name
self:T("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
end
end
end
@@ -1382,7 +1380,7 @@ function DATABASE:GetCoalitionFromClientTemplate( ClientName )
if self.Templates.ClientsByName[ClientName] then
return self.Templates.ClientsByName[ClientName].CoalitionID
end
self:T("WARNING: Template does not exist for client "..tostring(ClientName))
self:E("WARNING: Template does not exist for client "..tostring(ClientName))
return nil
end
@@ -1394,7 +1392,7 @@ function DATABASE:GetCategoryFromClientTemplate( ClientName )
if self.Templates.ClientsByName[ClientName] then
return self.Templates.ClientsByName[ClientName].CategoryID
end
self:T("WARNING: Template does not exist for client "..tostring(ClientName))
self:E("WARNING: Template does not exist for client "..tostring(ClientName))
return nil
end
@@ -1406,7 +1404,7 @@ function DATABASE:GetCountryFromClientTemplate( ClientName )
if self.Templates.ClientsByName[ClientName] then
return self.Templates.ClientsByName[ClientName].CountryID
end
self:T("WARNING: Template does not exist for client "..tostring(ClientName))
self:E("WARNING: Template does not exist for client "..tostring(ClientName))
return nil
end
@@ -1699,7 +1697,7 @@ function DATABASE:_EventOnBirth( Event )
if PlayerName then
-- Debug info.
self:I(string.format("Player '%s' joined unit '%s' (%s) of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniTypeName), tostring(Event.IniDCSGroupName)))
self:I(string.format("Player '%s' joined unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName)))
-- Add client in case it does not exist already.
if client == nil or (client and client:CountPlayers() == 0) then

View File

@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
ClassName = "MARKEROPS",
Tag = "mytag",
Keywords = {},
version = "0.1.4",
version = "0.1.3",
debug = false,
Casesensitive = true,
}
@@ -154,7 +154,14 @@ function MARKEROPS_BASE:OnEventMark(Event)
self:E("Skipping onEvent. Event or Event.idx unknown.")
return true
end
--position
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
local coord=COORDINATE:NewFromVec3(vec3)
if self.debug then
local coordtext = coord:ToStringLLDDM()
local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
local coalition = Event.MarkCoalition
-- decision
if Event.id==world.event.S_EVENT_MARK_ADDED then
@@ -163,12 +170,6 @@ function MARKEROPS_BASE:OnEventMark(Event)
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
local coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z})
if self.debug then
local coordtext = coord:ToStringLLDDM()
local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
end
@@ -179,12 +180,6 @@ function MARKEROPS_BASE:OnEventMark(Event)
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
local coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z})
if self.debug then
local coordtext = coord:ToStringLLDDM()
local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark changed at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
end

View File

@@ -206,7 +206,7 @@ end
function MESSAGE:ToGroup( Group, Settings )
self:F( Group.GroupName )
if Group and Group:IsAlive() then
if Group then
if self.MessageType then
local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS
@@ -231,7 +231,7 @@ end
function MESSAGE:ToUnit( Unit, Settings )
self:F( Unit.IdentifiableName )
if Unit and Unit:IsAlive() then
if Unit then
if self.MessageType then
local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
@@ -452,7 +452,7 @@ end
_MESSAGESRS = {}
--- Set up MESSAGE generally to allow Text-To-Speech via SRS and TTS functions. `SetMSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
-- @param #string PathToSRS (optional) Path to SRS TTS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio" or your configuration file setting.
-- @param #string PathToSRS (optional) Path to SRS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" or your configuration file setting.
-- @param #number Port Port (optional) number of SRS, defaults to 5002 or your configuration file setting.
-- @param #string PathToCredentials (optional) Path to credentials file for Google.
-- @param #number Frequency Frequency in MHz. Can also be given as a #table of frequencies.
@@ -468,13 +468,13 @@ _MESSAGESRS = {}
-- @usage
-- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
-- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS()
--
function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend)
_MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
_MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
_MESSAGESRS.frequency = Frequency or MSRS.frequencies or 243
_MESSAGESRS.modulation = Modulation or MSRS.modulations or radio.modulation.AM
@@ -535,7 +535,7 @@ end
-- @usage
-- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
-- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS()
--
@@ -567,7 +567,7 @@ end
-- @usage
-- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
-- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSBlue()
--
@@ -589,7 +589,7 @@ end
-- @usage
-- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED)
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED)
-- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSRed()
--
@@ -611,7 +611,7 @@ end
-- @usage
-- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL)
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL)
-- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSAll()
--

View File

@@ -5,6 +5,8 @@
-- * Path from A to B
-- * Arbitrary number of points
-- * Automatically from lines drawtool
-- * Draw line or mark points on F10 map
-- * Find closest points to path
--
-- ===
--
@@ -21,6 +23,7 @@
-- @field #string lid Class id string for output to DCS log file.
-- @field #string name Name of the path line.
-- @field #table points List of 3D points defining the path.
-- @field #number counter Running number counting the point IDs.
-- @extends Core.Base#BASE
--- *The shortest distance between two points is a straight line.* -- Archimedes
@@ -59,27 +62,39 @@ PATHLINE = {
ClassName = "PATHLINE",
lid = nil,
points = {},
counter = 0,
}
--- Point of line.
-- @type PATHLINE.Point
-- @field #number uid Unique ID of this point.
-- @field #string mother Name of the pathline this point belongs to.
-- @field #string name Name of this point.
-- @field DCS#Vec3 vec3 3D position.
-- @field DCS#Vec2 vec2 2D position.
-- @field #number surfaceType Surface type.
-- @field #number landHeight Land height in meters.
-- @field #number depth Water depth in meters.
-- @field #number markerID Marker ID.
-- @field #number lineID Marker of pathline ID.
--- Segment of line.
-- @type PATHLINE.Segment
-- @field #PATHLINE.Point p1 First point.
-- @field #PATHLINE.Point p2 Second point.
--- PATHLINE class version.
-- @field #string version
PATHLINE.version="0.1.1"
PATHLINE.version="0.3.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-- TODO: Read/write to JSON file
-- TODO: Translate/rotate pathline
-- TODO: Add color.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@@ -96,7 +111,7 @@ function PATHLINE:New(Name)
self.name=Name or "Unknown Path"
self.lid=string.format("PATHLINE %s | ", Name)
self.lid=string.format("PATHLINE %s | ", self.name)
return self
end
@@ -151,15 +166,30 @@ end
--- Add a point to the path from a given 2D position. The third dimension is determined from the land height.
-- @param #PATHLINE self
-- @param DCS#Vec2 Vec2 The 2D vector (x,y) to add.
-- @param #number Index Index to add this point, *e.g.* 1 for first point or 2 for second point. Default is at the end.
-- @param #PATHLINE.Point Point Add point after given point. Default is at the end or at given index.
-- @return #PATHLINE self
function PATHLINE:AddPointFromVec2(Vec2)
function PATHLINE:AddPointFromVec2(Vec2, Index, Point)
if Vec2 then
-- Create a new point.
local point=self:_CreatePoint(Vec2)
if Index then
-- Add at given index.
table.insert(self.points, Index, point)
else
if Point then
-- Get index of given point.
local i=self:_GetPointIndex(Point)
-- Add new point after given point.
table.insert(self.points, i+1, point)
else
-- Add add the end.
table.insert(self.points, point)
end
end
end
return self
@@ -168,18 +198,32 @@ end
--- Add a point to the path from a given 3D position.
-- @param #PATHLINE self
-- @param DCS#Vec3 Vec3 The 3D vector (x,y) to add.
-- @return #PATHLINE self
function PATHLINE:AddPointFromVec3(Vec3)
-- @param #number Index Index to add this point, *e.g.* 1 for first point or 2 for second point. Default is at the end.
-- @param #PATHLINE.Point Point Add point after given point. Default is at the end or at given index.
-- @return #PATHLINE.Point Point that was added.
function PATHLINE:AddPointFromVec3(Vec3, Index, Point)
if Vec3 then
local point=self:_CreatePoint(Vec3)
if Index then
-- Add add given index.
table.insert(self.points, Index, point)
else
if Point then
local i=self:_GetPointIndex(Point)
table.insert(self.points, i+1, point)
else
-- Add add the end.
table.insert(self.points, point)
end
end
return self
return point
end
return nil
end
--- Get name of pathline.
@@ -199,14 +243,31 @@ end
--- Get points of pathline. Not that points are tables, that contain more information as just the 2D or 3D position but also the surface type etc.
-- @param #PATHLINE self
-- @return #list <#PATHLINE.Point> List of points.
-- @return #list <Core.Pathline#PATHLINE.Point> List of points.
function PATHLINE:GetPoints()
return self.points
end
--- Get segments of pathline.
-- @param #PATHLINE self
-- @return #list <Core.Pathline#PATHLINE.Segment> List of points.
function PATHLINE:GetSetments()
local segments={}
for i=1,#self.points-1 do
local segment={} --#PATHLINE.Segment
segment.p1=self.points[i]
segment.p2=self.points[i+1]
table.insert(segments, segment)
end
return segments
end
--- Get 3D points of pathline.
-- @param #PATHLINE self
-- @return <DCS#Vec3> List of DCS#Vec3 points.
-- @return #list <DCS#Vec3> List of DCS#Vec3 points.
function PATHLINE:GetPoints3D()
local vecs={}
@@ -221,7 +282,7 @@ end
--- Get 2D points of pathline.
-- @param #PATHLINE self
-- @return <DCS#Vec2> List of DCS#Vec2 points.
-- @return #list <DCS#Vec2> List of DCS#Vec2 points.
function PATHLINE:GetPoints2D()
local vecs={}
@@ -244,7 +305,7 @@ function PATHLINE:GetCoordinates()
for _,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
local coord=COORDINATE:NewFromVec3(point.vec3)
table.insert(vecs,coord)
table.insert(vecs, coord)
end
return vecs
@@ -305,14 +366,16 @@ 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
@@ -323,15 +386,294 @@ function PATHLINE:MarkPoints(Switch)
point.markerID=UTILS.GetMarkID()
local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m", self.name, i, point.surfaceType, point.landHeight, point.depth)
local text=string.format("Pathline %s: Point #%d [UID=%d]\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m", self.name, i, point.uid, point.surfaceType, point.landHeight, point.depth)
trigger.action.markToAll(point.markerID, text, point.vec3, "")
end
end
return self
end
--- Draw line on F10 map.
-- @param #PATHLINE self
-- @param #boolean Switch If `true` or nil, draw pathline. If `false`, remove drawing.
-- @param #number Coalition Coalition side. Default -1 for all.
-- @param #table Color RGB color and alpha `{r, g, b, a}`. Default {0, 1, 0, 0.5}.
-- @param #number LineType Line type. Default 1=solid.
-- @return #PATHLINE self
function PATHLINE:Draw(Switch, Coalition, Color, LineType)
Coalition=Coalition or -1
Color=Color or {0, 1, 0, 0.5}
LineType=LineType or 1
if Switch==false then
for i,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
if point.lineID then
UTILS.RemoveMark(point.lineID)
end
end
else
for i=2,#self.points do
local p1=self.points[i-1] --#PATHLINE.Point
local p2=self.points[i] --#PATHLINE.Point
if p2.lineID then
UTILS.RemoveMark(p2.lineID)
end
p2.lineID=UTILS.GetMarkID()
trigger.action.lineToAll(Coalition, p2.lineID, p1.vec3, p2.vec3, Color, LineType)
end
end
return self
end
--- Get the closest point on the pathline for a given reference point.
-- @param #PATHLINE self
-- @param DCS#Vec2 Vec2 Reference Point in 2D.
-- @return DCS#Vec2 Cloest point on pathline.
-- @return #number Distance from closest point to ref point in meters.
-- @return #PATHLINE.Segment Closest segment of ref point.
function PATHLINE:GetClosestPoint2D(Vec2)
local P=nil --DCS#Vec2
local D=math.huge
local S={} --#PATHLINE.Segment
for i=2,#self.points do
local A=self.points[i-1] --#PATHLINE.Point
local B=self.points[i] --#PATHLINE.Point
local a=A.vec2
local b=B.vec2
local ab=UTILS.Vec2Substract(b, a)
local ap=UTILS.Vec2Substract(Vec2, a)
local proj=UTILS.Vec2Dot(ap, ab)
local lab=UTILS.Vec2Norm(ab)
local f=proj/lab/lab
-- Debug info.
local text=string.format("FF Proj=%.1f, |ab|=%.1f, f=%.1f", proj, lab, f)
self:T(self.lid..text)
-- Cases for finite segment.
local p=nil --DCS#Vec2
if f<0 then
p=a
elseif f>1 then
p=b
else
local r=UTILS.Vec2Mult(ab, f)
p=UTILS.Vec2Add(a, r)
end
-- Distance.
local d=UTILS.VecDist2D(p, Vec2)
if d<=D then
D=d
P=p
S.p1=A
S.p2=B
end
end
return P, D, S
end
--- Get the closest point on the pathline for a given reference point.
-- This point does not necessarily is a node of the pathline. In general it will be somewhere in between the nodes defining the pathline.
-- @param #PATHLINE self
-- @param DCS#Vec3 Vec3 Reference Point in 3D. Can also be a `COORDINATE`.
-- @return DCS#Vec3 Closest point on pathline.
-- @return #number Distance from closest point to ref point in meters.
-- @return #PATHLINE.Segment Closest segment of ref point.
function PATHLINE:GetClosestPoint3D(Vec3)
local P=nil --DCS#Vec3
local D=math.huge
local S={} --#PATHLINE.Segment
if not Vec3 then
self:E(self.lid.."ERROR: input Vec3 is nil!")
return nil, nil, nil
end
for i=2,#self.points do
local A=self.points[i-1] --#PATHLINE.Point
local B=self.points[i] --#PATHLINE.Point
local a=A.vec3
local b=B.vec3
local ab=UTILS.VecSubstract(b, a)
local ap=UTILS.VecSubstract(Vec3, a)
local proj=UTILS.VecDot(ap, ab)
local lab=UTILS.VecNorm(ab)
local f=proj/lab/lab
-- Debug info.
self:T(self.lid..string.format("Proj=%.1f, |ab|=%.1f, f=%.1f", proj, lab, f))
-- Cases for finite segment.
local p=nil --DCS#Vec2
if f<0 then
p=a
elseif f>1 then
p=b
else
local r=UTILS.VecMult(ab, f)
p=UTILS.VecAdd(a, r)
end
-- Distance.
local d=UTILS.VecDist3D(p, Vec3)
if d<=D then
D=d
P=p
S.p1=A
S.p2=B
end
end
return P, D, S
end
--- Write PATHLINE to JSON file.
-- **NOTE**: Requires `io` and `lfs` to be de-sanitized!
-- @param #PATHLINE self
-- @param #string FileName Name of the file. Default is the name of the pathline.
-- @return #PATHLINE self
function PATHLINE:WriteJSON(FileName)
if io and lfs then
-- JSON script.
local json=loadfile("Scripts\\JSON.lua")()
local data={}
-- We store the name and the points.
data.name=self.name
data.points=self.points
for i,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
--point.markerID=nil
end
-- Encode data to raw JSON. Encode converts a lua table into JSON string that can be written to file.
local raw_json=json:encode(data)
-- Debug data.
self:T(data)
-- Write in "User/Saved Games/" Folder.
local filepath=lfs.writedir() .. FileName
-- Open file for writing.
local f = io.open(filepath, "wb")
if f then
f:write(raw_json)
f:close()
self:T(self.lid .. string.format("Saving PATHLINE %s file %s", self.name, tostring(filepath)))
else
self:E(self.lid .. string.format( "ERROR: Could not save PATHLINE to file %s", tostring(filepath)))
end
else
self:E(self.lid .. string.format( "ERROR: Could not save results because IO and/or LFS are not de-sanitized!"))
end
end
--- Read PATHLINE from JSON file.
-- **NOTE**: Requires `io` and `lfs` to be de-sanitized!
-- @param #PATHLINE self
-- @param #string FileName Name of the file.
-- @return #PATHLINE self
function PATHLINE:NewFromJSON(FileName)
if io and lfs then
-- JSON script.
local json=loadfile("Scripts\\JSON.lua")()
local data={}
-- Write in "User/Saved Games/" Folder.
local filepath=lfs.writedir() .. FileName
--env.info(filepath)
-- Open file in binary mode for reading.
local f = io.open(filepath, "rb")
if f then
data = f:read("*all")
f:close()
else
env.info(string.format("WARNING: Could not load PATHLINE from file %s!", tostring(filepath)))
return nil
end
-- Decode JSON data to get a lua table.
local data=json:decode(data)
if data and data.name then
-- Create a new pathline instance.
local self=PATHLINE:New(data.name)
for i=1,#data.points do
local point=data.points[i] --#PATHLINE.Point
-- Create new point from data.
local p=self:AddPointFromVec3(point.vec3)
-- Set name.
p.name=point.name
-- Remove marker ID.
p.markerID=nil
end
return self
else
BASE:E("ERROR: Cannot find pathline name in data from JSON file. File may be corrupted!")
end
else
BASE:E("ERROR: IO and/or LFS not de-sanitized! Cannot read file.")
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -339,11 +681,18 @@ end
--- Get 3D points of pathline.
-- @param #PATHLINE self
-- @param DCS#Vec3 Vec Position vector. Can also be a DCS#Vec2 in which case the altitude at landheight is taken.
-- @return #PATHLINE.Point
-- @return #PATHLINE.Point Pathline Point.
function PATHLINE:_CreatePoint(Vec)
local point={} --#PATHLINE.Point
self.counter=self.counter+1
point.uid=self.counter
point.mother=self.name
point.name=string.format("%s #%d", self.name, point.uid)
if Vec.z then
-- Given vec is 3D
point.vec3=UTILS.DeepCopy(Vec)
@@ -365,6 +714,23 @@ function PATHLINE:_CreatePoint(Vec)
return point
end
--- Get index of point in the lua table.
-- @param #PATHLINE self
-- @param #PATHLINE.Point Point Given point.
-- @return #number index
function PATHLINE:_GetPointIndex(Point)
for i,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
if point.uid==Point.uid then
return i
end
end
return nil
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -25,7 +25,7 @@
do -- COORDINATE
--- Coordinate class
---
-- @type COORDINATE
-- @field #string ClassName Name of the class
-- @field #number x Component of the 3D vector.
@@ -59,10 +59,6 @@ do -- COORDINATE
-- * @{#COORDINATE.SmokeOrange}(): To smoke the point in orange.
-- * @{#COORDINATE.SmokeWhite}(): To smoke the point in white.
-- * @{#COORDINATE.SmokeGreen}(): To smoke the point in green.
-- * @{#COORDINATE.SetSmokeOffsetDirection}(): To set an offset point direction for smoke.
-- * @{#COORDINATE.SetSmokeOffsetDistance}(): To set an offset point distance for smoke.
-- * @{#COORDINATE.SwitchSmokeOffsetOn}(): To set an offset point for smoke to on.
-- * @{#COORDINATE.SwitchSmokeOffsetOff}(): To set an offset point for smoke to off.
--
-- ## 2.2) Flare
--
@@ -457,6 +453,23 @@ do -- COORDINATE
end
--- Returns the coordinate from the latitude and longitude given in degrees, minutes and seconds (DMS).
-- @param #COORDINATE self
-- @param #string Latitude Latitude in DMS as string, e.g. "`42° 24' 14.3"`". Not that the characters `°`, `'` and `"` are important.
-- @param #string Longitude Longitude in DMS as string, e.g. "`42° 24' 14.3"`". Not that the characters `°`, `'` and `"` are important.
-- @param #number Altitude (Optional) Altitude in meters. Default is the land height at the coordinate.
-- @return #COORDINATE
function COORDINATE:NewFromLLDMS(Latitude, Longitude, Altitude)
local lat=UTILS.LLDMSstringToDD(Latitude)
local lon=UTILS.LLDMSstringToDD(Longitude)
self=COORDINATE:NewFromLLDD(lat, lon, Altitude)
return self
end
--- Returns if the 2 coordinates are at the same 2D position.
-- @param #COORDINATE self
-- @param #COORDINATE Coordinate
@@ -777,9 +790,7 @@ 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
@@ -839,26 +850,6 @@ do -- COORDINATE
return land.getHeight( Vec2 )
end
--- Returns a table of DCS#Vec3 points representing the terrain profile between two points.
-- @param #COORDINATE self
-- @param Destination DCS#Vec3 Ending point of the profile.
-- @return #table DCS#Vec3 table of the profile
function COORDINATE:GetLandProfileVec3(Destination)
return land.profile(self:GetVec3(), Destination)
end
--- Returns a table of #COORDINATE representing the terrain profile between two points.
-- @param #COORDINATE self
-- @param Destination #COORDINATE Ending coordinate of the profile.
-- @return #table #COORDINATE table of the profile
function COORDINATE:GetLandProfileCoordinates(Destination)
local points = self:GetLandProfileVec3(Destination:GetVec3())
local coords = {}
for _, point in ipairs(points) do
table.insert(coords, COORDINATE:NewFromVec3(point))
end
return coords
end
--- Set the heading of the coordinate, if applicable.
-- @param #COORDINATE self
@@ -2144,114 +2135,16 @@ do -- COORDINATE
end
--- Create colored smoke the point. The smoke we last up to 5 min (DCS limitation) but you can optionally specify a shorter duration or stop it manually.
--- Smokes the point in a color.
-- @param #COORDINATE self
-- @param #number SmokeColor Color of smoke, e.g. `SMOKECOLOR.Green` for green smoke.
-- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min.
-- @param #number Delay (Optional) Delay before the smoke is started in seconds.
-- @param #string Name (Optional) Name if you want to stop the smoke early (normal duration: 5mins)
-- @param #boolean Offset (Optional) If true, offset the smokle a bit.
-- @param #number Direction (Optional) If Offset is true this is the direction of the offset, 1-359 (degrees). Default random.
-- @param #number Distance (Optional) If Offset is true this is the distance of the offset in meters. Default random 10-20.
-- @return #COORDINATE self
function COORDINATE:Smoke( SmokeColor, Duration, Delay, Name, Offset,Direction,Distance)
self:F2( { SmokeColor, Name, Duration, Delay, Offset } )
SmokeColor=SmokeColor or SMOKECOLOR.Green
if Delay and Delay>0 then
self:ScheduleOnce(Delay, COORDINATE.Smoke, self, SmokeColor, Duration, 0, Name, Direction,Distance)
else
-- Create a name which is used to stop the smoke manually
self.firename = Name or "Smoke-"..math.random(1,100000)
-- Create smoke
if Offset or self.SmokeOffset then
local Angle = Direction or self:GetSmokeOffsetDirection()
local Distance = Distance or self:GetSmokeOffsetDistance()
local newpos = self:Translate(Distance,Angle,true,false)
local newvec3 = newpos:GetVec3()
trigger.action.smoke( newvec3, SmokeColor, self.firename )
else
-- @param Utilities.Utils#SMOKECOLOR SmokeColor
-- @param #string name (Optional) Name if you want to stop the smoke early (normal duration: 5mins)
function COORDINATE:Smoke( SmokeColor, name )
self:F2( { SmokeColor } )
self.firename = name or "Smoke-"..math.random(1,100000)
trigger.action.smoke( self:GetVec3(), SmokeColor, self.firename )
end
-- Stop smoke
if Duration and Duration>0 then
self:ScheduleOnce(Duration, COORDINATE.StopSmoke, self, self.firename )
end
end
return self
end
--- Get the offset direction when using `COORDINATE:Smoke()`.
-- @param #COORDINATE self
-- @return #number Direction in degrees.
function COORDINATE:GetSmokeOffsetDirection()
local direction = self.SmokeOffsetDirection or math.random(1,359)
return direction
end
--- Set the offset direction when using `COORDINATE:Smoke()`.
-- @param #COORDINATE self
-- @param #number Direction (Optional) This is the direction of the offset, 1-359 (degrees). Default random.
-- @return #COORDINATE self
function COORDINATE:SetSmokeOffsetDirection(Direction)
if self then
self.SmokeOffsetDirection = Direction or math.random(1,359)
return self
else
COORDINATE.SmokeOffsetDirection = Direction or math.random(1,359)
end
end
--- Get the offset distance when using `COORDINATE:Smoke()`.
-- @param #COORDINATE self
-- @return #number Distance Distance in meters.
function COORDINATE:GetSmokeOffsetDistance()
local distance = self.SmokeOffsetDistance or math.random(10,20)
return distance
end
--- Set the offset distance when using `COORDINATE:Smoke()`.
-- @param #COORDINATE self
-- @param #number Distance (Optional) This is the distance of the offset in meters. Default random 10-20.
-- @return #COORDINATE self
function COORDINATE:SetSmokeOffsetDistance(Distance)
if self then
self.SmokeOffsetDistance = Distance or math.random(10,20)
return self
else
COORDINATE.SmokeOffsetDistance = Distance or math.random(10,20)
end
end
--- Set the offset on when using `COORDINATE:Smoke()`.
-- @param #COORDINATE self
-- @return #COORDINATE self
function COORDINATE:SwitchSmokeOffsetOn()
if self then
self.SmokeOffset = true
return self
else
COORDINATE.SmokeOffset = true
end
end
--- Set the offset off when using `COORDINATE:Smoke()`.
-- @param #COORDINATE self
-- @return #COORDINATE self
function COORDINATE:SwitchSmokeOffsetOff()
if self then
self.SmokeOffset = false
return self
else
COORDINATE.SmokeOffset = false
end
end
--- Stops smoking the point in a color.
-- @param #COORDINATE self
-- @param #string name (Optional) Name if you want to stop the smoke early (normal duration: 5mins)
@@ -2261,83 +2154,49 @@ do -- COORDINATE
--- Smoke the COORDINATE Green.
-- @param #COORDINATE self
-- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min.
-- @param #number Delay (Optional) Delay before the smoke is started in seconds.
-- @return #COORDINATE self
function COORDINATE:SmokeGreen(Duration, Delay)
self:Smoke( SMOKECOLOR.Green, Duration, Delay )
return self
function COORDINATE:SmokeGreen()
self:F2()
self:Smoke( SMOKECOLOR.Green )
end
--- Smoke the COORDINATE Red.
-- @param #COORDINATE self
-- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min.
-- @param #number Delay (Optional) Delay before the smoke is started in seconds.
-- @return #COORDINATE self
function COORDINATE:SmokeRed(Duration, Delay)
self:Smoke( SMOKECOLOR.Red, Duration, Delay )
return self
function COORDINATE:SmokeRed()
self:F2()
self:Smoke( SMOKECOLOR.Red )
end
--- Smoke the COORDINATE White.
-- @param #COORDINATE self
-- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min.
-- @param #number Delay (Optional) Delay before the smoke is started in seconds.
-- @return #COORDINATE self
function COORDINATE:SmokeWhite(Duration, Delay)
self:Smoke( SMOKECOLOR.White, Duration, Delay )
return self
function COORDINATE:SmokeWhite()
self:F2()
self:Smoke( SMOKECOLOR.White )
end
--- Smoke the COORDINATE Orange.
-- @param #COORDINATE self
-- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min.
-- @param #number Delay (Optional) Delay before the smoke is started in seconds.
-- @return #COORDINATE self
function COORDINATE:SmokeOrange(Duration, Delay)
self:Smoke( SMOKECOLOR.Orange, Duration, Delay )
return self
function COORDINATE:SmokeOrange()
self:F2()
self:Smoke( SMOKECOLOR.Orange )
end
--- Smoke the COORDINATE Blue.
-- @param #COORDINATE self
-- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min.
-- @param #number Delay (Optional) Delay before the smoke is started in seconds.
-- @return #COORDINATE self
function COORDINATE:SmokeBlue(Duration, Delay)
self:Smoke( SMOKECOLOR.Blue, Duration, Delay )
return self
function COORDINATE:SmokeBlue()
self:F2()
self:Smoke( SMOKECOLOR.Blue )
end
--- Big smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @param #number Preset Smoke preset (1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke).
-- @param #number Density (Optional) Smoke density. Number in [0,...,1]. Default 0.5.
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds.
-- @param #string Name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
-- @return #COORDINATE self
function COORDINATE:BigSmokeAndFire( Preset, Density, Duration, Delay, Name )
self:F2( { preset=Preset, density=Density } )
Preset=Preset or BIGSMOKEPRESET.SmallSmokeAndFire
Density=Density or 0.5
if Delay and Delay>0 then
self:ScheduleOnce(Delay, COORDINATE.BigSmokeAndFire, self, Preset, Density, Duration, 0, Name)
else
self.firename = Name or "Fire-"..math.random(1,10000)
trigger.action.effectSmokeBig( self:GetVec3(), Preset, Density, self.firename )
-- Stop smoke
if Duration and Duration>0 then
self:ScheduleOnce(Duration, COORDINATE.StopBigSmokeAndFire, self, self.firename )
end
end
return self
-- @param Utilities.Utils#BIGSMOKEPRESET preset Smoke preset (1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke).
-- @param #number density (Optional) Smoke density. Number in [0,...,1]. Default 0.5.
-- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
function COORDINATE:BigSmokeAndFire( preset, density, name )
self:F2( { preset=preset, density=density } )
density=density or 0.5
self.firename = name or "Fire-"..math.random(1,10000)
trigger.action.effectSmokeBig( self:GetVec3(), preset, density, self.firename )
end
--- Stop big smoke and fire at the coordinate.
@@ -2350,98 +2209,82 @@ do -- COORDINATE
--- Small smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @param #number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds.
-- @param #string Name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
-- @return #COORDINATE self
function COORDINATE:BigSmokeAndFireSmall( Density, Duration, Delay, Name )
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, Density, Duration, Delay, Name)
return self
-- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
-- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
function COORDINATE:BigSmokeAndFireSmall( density, name )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, density, name)
end
--- Medium smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds.
-- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
-- @return #COORDINATE self
function COORDINATE:BigSmokeAndFireMedium( Density, Duration, Delay, Name )
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, Density, Duration, Delay, Name)
return self
function COORDINATE:BigSmokeAndFireMedium( density, name )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, density, name)
end
--- Large smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds.
-- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
-- @return #COORDINATE self
function COORDINATE:BigSmokeAndFireLarge( Density, Duration, Delay, Name )
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, Density, Duration, Delay, Name)
return self
function COORDINATE:BigSmokeAndFireLarge( density, name )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, density, name)
end
--- Huge smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds.
-- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
-- @return #COORDINATE self
function COORDINATE:BigSmokeAndFireHuge( Density, Duration, Delay, Name )
self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, Density, Duration, Delay, Name)
return self
function COORDINATE:BigSmokeAndFireHuge( density, name )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, density, name)
end
--- Small smoke at the coordinate.
-- @param #COORDINATE self
-- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds.
-- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
-- @return #COORDINATE self
function COORDINATE:BigSmokeSmall( Density, Duration, Delay, Name )
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, Density, Duration, Delay, Name)
return self
function COORDINATE:BigSmokeSmall( density, name )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, density, name)
end
--- Medium smoke at the coordinate.
-- @param #COORDINATE self
-- @param number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds.
-- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
-- @return #COORDINATE self
function COORDINATE:BigSmokeMedium( Density, Duration, Delay, Name )
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, Density, Duration, Delay, Name)
return self
function COORDINATE:BigSmokeMedium( density, name )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, density, name)
end
--- Large smoke at the coordinate.
-- @param #COORDINATE self
-- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds.
-- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
-- @return #COORDINATE self
function COORDINATE:BigSmokeLarge( Density, Duration, Delay, Name )
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, Density, Duration, Delay, Name)
return self
function COORDINATE:BigSmokeLarge( density, name )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, density,name)
end
--- Huge smoke at the coordinate.
-- @param #COORDINATE self
-- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
-- @param #number Duration (Optional) Duration of the smoke and fire in seconds.
-- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds.
-- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number.
-- @return #COORDINATE self
function COORDINATE:BigSmokeHuge( Density, Duration, Delay, Name )
self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, Density, Duration, Delay, Name)
return self
function COORDINATE:BigSmokeHuge( density, name )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, density,name)
end
--- Flares the point in a color.
@@ -3095,10 +2938,8 @@ do -- COORDINATE
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tdiff)
local sunset=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tdiff)
if type(sunrise) == "string" or type(sunset) == "string" then
if sunrise == "N/R" then return false end
if sunset == "N/S" then return true end
end
if sunrise == "N/S" then return true end
local time=UTILS.ClockToSeconds(clock)
@@ -3117,11 +2958,6 @@ do -- COORDINATE
-- Todays sun set in sec.
local sunset=self:GetSunset(true)
if type(sunrise) == "string" or type(sunset) == "string" then
if sunrise == "N/R" then return false end
if sunset == "N/S" then return true end
end
-- Seconds passed since midnight.
local time=UTILS.SecondsOfToday()
@@ -3820,25 +3656,6 @@ do -- COORDINATE
return COORDINATE:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) )
end
--- Search for clear zones in a given area. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #number SearchRadius Radius of the search area.
-- @param #number PosRadius Required clear radius around each position.
-- @param #number NumPositions Number of positions to find.
-- @return #table A table of Core.Point#COORDINATE that are clear of map objects within the given PosRadius. nil if no positions are found.
function COORDINATE:GetSimpleZones(SearchRadius, PosRadius, NumPositions)
local clearPositions = UTILS.GetSimpleZones(self:GetVec3(), SearchRadius, PosRadius, NumPositions)
if clearPositions and #clearPositions > 0 then
local coords = {}
for _, pos in pairs(clearPositions) do
local coord = COORDINATE:NewFromVec2(pos)
table.insert(coords, coord)
end
return coords
end
return nil
end
end
do

View File

@@ -175,7 +175,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
local Name = Info.name or "?"
local ErrorHandler = function( errmsg )
env.info( "Error in timer function: " .. errmsg or "" )
env.info( "Error in timer function: " .. errmsg )
if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() )
end
@@ -326,7 +326,7 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
-- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing.
if Schedule and Schedule.ScheduleID then
if Schedule.ScheduleID then
self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )

View File

@@ -959,25 +959,6 @@ do -- SET_BASE
return ObjectNames
end
--- Get a *new* set table that only contains alive objects.
-- @param #SET_BASE self
-- @return #table Set table of alive objects.
function SET_BASE:GetAliveSet()
--self:F2()
local AliveSet = {}
-- Clean the Set before returning with only the alive Objects.
for ObjectName, Object in pairs( self.Set ) do
if Object then
if Object:IsAlive() then
AliveSet[#AliveSet+1] = Object
end
end
end
return AliveSet or {}
end
end
do
@@ -1144,25 +1125,25 @@ do
end
--- Get a *new* set table that only contains alive groups.
--- Get a *new* set that only contains alive groups.
-- @param #SET_GROUP self
-- @return #table Set of alive groups.
-- @return #SET_GROUP Set of alive groups.
function SET_GROUP:GetAliveSet()
--self:F2()
--local AliveSet = SET_GROUP:New()
local AliveSet = {}
local AliveSet = SET_GROUP:New()
-- Clean the Set before returning with only the alive Groups.
for GroupName, GroupObject in pairs( self.Set ) do
local GroupObject = GroupObject -- Wrapper.Group#GROUP
if GroupObject then
if GroupObject:IsAlive() then
AliveSet[GroupName] = GroupObject
AliveSet:Add( GroupName, GroupObject )
end
end
end
return AliveSet or {}
return AliveSet.Set or {}
end
--- Returns a report of of unit types.
@@ -2614,7 +2595,7 @@ do -- SET_UNIT
--- Gets the alive set.
-- @param #SET_UNIT self
-- @return #table Table of alive UNIT objects
-- @return #table Table of SET objects
-- @return #SET_UNIT AliveSet
function SET_UNIT:GetAliveSet()
@@ -2622,8 +2603,10 @@ do -- SET_UNIT
-- Clean the Set before returning with only the alive Groups.
for GroupName, GroupObject in pairs(self.Set) do
local GroupObject=GroupObject --Wrapper.Client#CLIENT
if GroupObject and GroupObject:IsAlive() then
AliveSet[GroupName] = GroupObject
AliveSet:Add(GroupName, GroupObject)
end
end
@@ -4801,16 +4784,18 @@ do -- SET_CLIENT
-- @return #table Table of SET objects
function SET_CLIENT:GetAliveSet()
local AliveSet = {}
local AliveSet = SET_CLIENT:New()
-- Clean the Set before returning with only the alive Groups.
for GroupName, GroupObject in pairs(self.Set) do
local GroupObject=GroupObject --Wrapper.Client#CLIENT
if GroupObject and GroupObject:IsAlive() then
AliveSet[GroupName] = GroupObject
AliveSet:Add(GroupName, GroupObject)
end
end
return AliveSet or {}
return AliveSet.Set or {}
end
--- [User] Add a custom condition function.
@@ -6691,8 +6676,6 @@ do -- SET_ZONE
--
-- -- Stop watching after 1 hour
-- zoneset:__TriggerStop(3600)
-- -- Call :SetPartlyInside() on any zone (or SET_ZONE) if you want GROUPs to count as inside when any of their units enters even if they are far apart.
-- -- Make sure to call :SetPartlyInside() before :Trigger()!.
function SET_ZONE:Trigger(Objects)
--self:I("Added Set_Zone Trigger")
self:AddTransition("*","TriggerStart","TriggerRunning")
@@ -6743,20 +6726,6 @@ do -- SET_ZONE
-- @param Core.Zone#ZONE_BASE Zone The zone left.
end
--- Toggle “partly-inside” handling for every zone in the set when those zones are used with :Trigger().
-- * Call with no argument or **true** → enable for all.
-- * Call with **false** → disable again (handy if it was enabled before).
-- @param #SET_ZONE self
-- @return #SET_ZONE self
function SET_ZONE:SetPartlyInside(state)
for _,Zone in pairs(self.Set) do
if Zone.SetPartlyInside then
Zone:SetPartlyInside(state)
end
end
return self
end
--- (Internal) Check the assigned objects for being in/out of the zone
-- @param #SET_ZONE self
-- @param #boolean fromstart If true, do the init of the objects
@@ -6792,13 +6761,8 @@ do -- SET_ZONE
-- has not been tagged previously - wasn't in set!
obj.TriggerInZone[_zone.ZoneName] = false
end
-- is obj in this zone?
local inzone
if _zone.PartlyInside and obj.ClassName == "GROUP" then
inzone = obj:IsAnyInZone(_zone) -- TRUE as soon as any unit is inside
else
inzone = _zone:IsCoordinateInZone(obj:GetCoordinate()) -- original centroid test
end
-- is obj in zone?
local inzone = _zone:IsCoordinateInZone(obj:GetCoordinate())
--self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone))
if inzone and not obj.TriggerInZone[_zone.ZoneName] then
-- wasn't in zone before

View File

@@ -149,7 +149,6 @@ function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
self.CategoryID = CategoryID
self.CoalitionID = CoalitionID
self.SpawnIndex = 0
self.StaticCopyFrom = SpawnTemplateName
else
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
end
@@ -303,16 +302,12 @@ end
-- @param #number CallsignID Callsign ID. Default 1 (="London").
-- @param #number Frequency Frequency in MHz. Default 127.5 MHz.
-- @param #number Modulation Modulation 0=AM, 1=FM.
-- @param #boolean DynamicSpawns If true, allow Dynamic Spawns
-- @param #boolean DynamicHotStarts If true, and DynamicSpawns is true, then allow Dynamic Spawns with hot starts.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation, DynamicSpawns,DynamicHotStarts)
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation)
self.InitFarp=true
self.InitFarpCallsignID=CallsignID or 1
self.InitFarpFreq=Frequency or 127.5
self.InitFarpModu=Modulation or 0
self.InitFarpDynamicSpawns = DynamicSpawns
self.InitFarpDynamicHotStarts = (DynamicSpawns == true and DynamicHotStarts == true) and true or nil
return self
end
@@ -464,8 +459,7 @@ end
function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName)
-- Spawn the new static at the center of the zone.
--local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
local Static = self:SpawnFromCoordinate(Zone:GetCoordinate(), Heading, NewName)
local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
return Static
end
@@ -556,20 +550,12 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
TemplateGroup.y=Template.y
TemplateGroup.name=Template.name
if self.InitFarpDynamicSpawns == true then
TemplateGroup.units[1].dynamicSpawn = true
if self.InitFarpDynamicHotStarts == true then
TemplateGroup.units[1].allowHotStart = true
end
end
self:T("Spawning FARP")
self:T({Template=Template})
self:T({TemplateGroup=TemplateGroup})
-- ED's dirty way to spawn FARPS.
Static=coalition.addGroup(CountryID, -1, TemplateGroup)
--Static=coalition.addStaticObject(CountryID, Template)
-- Currently DCS 2.8 does not trigger birth events if FARPS are spawned!
-- We create such an event. The airbase is registered in Core.Event
@@ -609,18 +595,5 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments))
end
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
return mystatic
end

File diff suppressed because it is too large Load Diff

View File

@@ -70,7 +70,6 @@
-- @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
@@ -306,6 +305,20 @@ function ZONE_BASE:GetCoordinate( Height ) --R2.1
return self.Coordinate
end
--- Returns the @{Core.Vector#VECTOR} of the zone.
-- @param #ZONE_BASE self
-- @param DCS#Distance Height The height in meters to add to the land height where the center of the zone is located.
-- @return Core.Vector#VECTOR The vector of the zone.
function ZONE_BASE:GetVector( Height )
self:F2(self.ZoneName)
local Vec3 = self:GetVec3( Height )
local vector=VECTOR:NewFromVec(Vec3)
return vector
end
--- Get 2D distance to a coordinate.
-- @param #ZONE_BASE self
-- @param Core.Point#COORDINATE Coordinate Reference coordinate. Can also be a DCS#Vec2 or DCS#Vec3 object.
@@ -535,19 +548,6 @@ function ZONE_BASE:GetZoneProbability()
return self.ZoneProbability
end
--- Get the coordinate on the radius of the zone nearest to Outsidecoordinate. Useto e.g. find an ingress point.
-- @param #ZONE_BASE self
-- @param Core.Point#COORDINATE Outsidecoordinate The coordinate outside of the zone from where to look.
-- @return Core.Point#COORDINATE CoordinateOnRadius
function ZONE_BASE:FindNearestCoordinateOnRadius(Outsidecoordinate)
local Vec1 = self:GetVec2()
local Radius = self:GetRadius()
local Vec2 = Outsidecoordinate:GetVec2()
local Point = UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2)
local rc = COORDINATE:NewFromVec2(Point)
return rc
end
--- Get the zone taking into account the randomization probability of a zone to be selected.
-- @param #ZONE_BASE self
-- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor.
@@ -613,8 +613,6 @@ 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")
@@ -683,16 +681,6 @@ 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
@@ -731,12 +719,7 @@ function ZONE_BASE:_TriggerCheck(fromstart)
obj.TriggerInZone[self.ZoneName] = false
end
-- is obj in zone?
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
local inzone = self:IsCoordinateInZone(obj:GetCoordinate())
--self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone))
if inzone and obj.TriggerInZone[self.ZoneName] then
-- just count
@@ -1540,26 +1523,6 @@ function ZONE_RADIUS:IsVec3InZone( Vec3 )
return InZone
end
--- Search for clear ground spawn zones within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #ZONE_RADIUS self
-- @param #number PosRadius Required clear radius around each position.
-- @param #number NumPositions Number of positions to find.
-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. nil if no clear positions are found.
function ZONE_RADIUS:GetClearZonePositions(PosRadius, NumPositions)
return UTILS.GetClearZonePositions(self, PosRadius, NumPositions)
end
--- Search for a random clear ground spawn coordinate within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #ZONE_RADIUS self
-- @param #number PosRadius (Optional) Required clear radius around each position. (Default is math.min(Radius/10, 200))
-- @param #number NumPositions (Optional) Number of positions to find. (Default 50)
-- @return Core.Point#COORDINATE A random coordinate for a clear zone. nil if no clear positions are found.
-- @return #number Assigned radius for the found zones. nil if no clear positions are found.
function ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius, NumPositions)
return UTILS.GetRandomClearZoneCoordinate(self, PosRadius, NumPositions)
end
--- Returns a random Vec2 location within the zone.
-- @param #ZONE_RADIUS self
-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0.
@@ -1572,10 +1535,6 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
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}
end
@@ -2542,26 +2501,6 @@ function ZONE_POLYGON_BASE:Flush()
return self
end
--- Search for clear ground spawn zones within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #ZONE_POLYGON_BASE self
-- @param #number PosRadius Required clear radius around each position.
-- @param #number NumPositions Number of positions to find.
-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. nil if no clear positions are found.
function ZONE_POLYGON_BASE:GetClearZonePositions(PosRadius, NumPositions)
return UTILS.GetClearZonePositions(self, PosRadius, NumPositions)
end
--- Search for a random clear ground spawn coordinate within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #ZONE_POLYGON_BASE self
-- @param #number PosRadius (Optional) Required clear radius around each position. (Default is math.min(Radius/10, 200))
-- @param #number NumPositions (Optional) Number of positions to find. (Default 50)
-- @return Core.Point#COORDINATE A random coordinate for a clear zone. nil if no clear positions are found.
-- @return #number Assigned radius for the found zones. nil if no clear positions are found.
function ZONE_POLYGON_BASE:GetRandomClearZoneCoordinate(PosRadius, NumPositions)
return UTILS.GetRandomClearZoneCoordinate(self, PosRadius, NumPositions)
end
--- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self
-- @param #boolean UnBound If true, the tyres will be destroyed.
@@ -2940,11 +2879,6 @@ 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
@@ -3284,7 +3218,12 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
local vectors = self:GetBoundingSquare()
local ZoneRadius = UTILS.VecDist2D({x=vectors.x1, y=vectors.y1}, {x=vectors.x2, y=vectors.y2})/2
local minVec3 = {x=vectors.x1, y=0, z=vectors.y1}
local maxVec3 = {x=vectors.x2, y=0, z=vectors.y2}
local minmarkcoord = COORDINATE:NewFromVec3(minVec3)
local maxmarkcoord = COORDINATE:NewFromVec3(maxVec3)
local ZoneRadius = minmarkcoord:Get2DDistance(maxmarkcoord)/2
-- self:I("Scan Radius:" ..ZoneRadius)
local CenterVec3 = self:GetCoordinate():GetVec3()

View File

@@ -198,7 +198,7 @@ end -- env
do -- radio
--@type radio
---@type radio
-- @field #radio.modulation modulation
---

View File

@@ -22,7 +22,7 @@
-- ===
--
-- ### Author: **Applevangelist**
-- Last Update July 2025
-- Last Update Sept 2023
--
-- ===
-- @module Functional.AICSAR
@@ -57,8 +57,6 @@
-- @field #number Speed Default speed setting for the helicopter FLIGHTGROUP is 100kn.
-- @field #boolean UseEventEject In case Event LandingAfterEjection isn't working, use set this to true.
-- @field #number Delay In case of UseEventEject wait this long until we spawn a landed pilot.
-- @field #boolean UseRescueZone If true, use a rescue zone and not the max distance to FARP/MASH
-- @field Core.Zone#ZONE_RADIUS RescueZone Use this zone as operational area for the AICSAR instance.
-- @extends Core.Fsm#FSM
@@ -155,10 +153,10 @@
-- To set up AICSAR for SRS TTS output, add e.g. the following to your script:
--
-- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required)
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\google.json")
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\google.json")
--
-- -- alternatively for MS Desktop TTS (voices need to be installed locally first!)
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female")
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female")
--
-- -- define a different voice for the downed pilot(s)
-- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male")
@@ -179,7 +177,7 @@
--
-- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so:
--
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",270,radio.modulation.AM,nil,5002)
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,nil,5002)
--
-- or
--
@@ -193,7 +191,7 @@
-- @field #AICSAR
AICSAR = {
ClassName = "AICSAR",
version = "0.1.18",
version = "0.1.16",
lid = "",
coalition = coalition.side.BLUE,
template = "",
@@ -238,8 +236,6 @@ AICSAR = {
Altitude = 1500,
UseEventEject = false,
Delay = 100,
UseRescueZone = false,
RescueZone = nil,
}
-- TODO Messages
@@ -308,9 +304,8 @@ AICSAR.RadioLength = {
-- @param #string Helotemplate Helicopter template name.
-- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
-- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
-- @param #number Helonumber Max number of alive Ai Helos at the same time. Defaults to three.
-- @return #AICSAR self
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Helonumber)
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New())
@@ -378,7 +373,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Hel
-- limit number of available helos at the same time
self.limithelos = true
self.helonumber = Helonumber or 3
self.helonumber = 3
-- localization
self:InitLocalization()
@@ -529,20 +524,10 @@ function AICSAR:InitLocalization()
return self
end
--- [User] Use a defined zone as area of operation and not the distance to FARP.
-- @param #AICSAR self
-- @param Core.Zone#ZONE Zone The operational zone to use. Downed pilots in this area will be rescued. Can be any known #ZONE type.
-- @return #AICSAR self
function AICSAR:SetUsingRescueZone(Zone)
self.UseRescueZone = true
self.RescueZone = Zone
return self
end
--- [User] Switch sound output on and use SRS output for sound files.
-- @param #AICSAR self
-- @param #boolean OnOff Switch on (true) or off (false).
-- @param #string Path Path to your SRS Server External Audio Component, e.g. "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\\\ExternalAudio"
-- @param #string Path Path to your SRS Server Component, e.g. "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone"
-- @param #number Frequency Defaults to 243 (guard)
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM
-- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor.
@@ -553,7 +538,7 @@ function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
self.SRSRadio = OnOff and true
self.SRSTTSRadio = false
self.SRSFrequency = Frequency or 243
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.SRS:SetLabel("ACSR")
self.SRS:SetCoalition(self.coalition)
self.SRSModulation = Modulation or radio.modulation.AM
@@ -571,7 +556,7 @@ end
-- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()`
-- @param #AICSAR self
-- @param #boolean OnOff Switch on (true) or off (false).
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone"
-- @param #number Frequency (Optional) Defaults to 243 (guard)
-- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM
-- @param #number Port (Optional) Port of the SRS, defaults to 5002.
@@ -585,7 +570,7 @@ function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Cultur
self.SRSTTSRadio = OnOff and true
self.SRSRadio = false
self.SRSFrequency = Frequency or 243
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.SRSModulation = Modulation or radio.modulation.AM
self.SRSPort = Port or MSRS.port or 5002
if OnOff then
@@ -708,7 +693,7 @@ function AICSAR:_EjectEventHandler(EventData)
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
local _country = _event.initiator:getCountry()
local _coalition = coalition.getCountryCoalition( _country )
--local data = UTILS.DeepCopy(EventData)
local data = UTILS.DeepCopy(EventData)
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
self:ScheduleOnce(self.Delay,self._DelayedSpawnPilot,self,_LandingPos,_coalition)
end
@@ -724,13 +709,6 @@ end
function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition)
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
if self.UseRescueZone == true and self.RescueZone ~= nil then
if self.RescueZone:IsCoordinateInZone(_LandingPos) then
distancetofarp = self.maxdistance - 10
else
distancetofarp = self.maxdistance + 10
end
end
-- Mayday Message
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
local text = ""
@@ -817,13 +795,7 @@ function AICSAR:_EventHandler(EventData, FromEject)
-- DONE: add distance check
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
if self.UseRescueZone == true and self.RescueZone ~= nil then
if self.RescueZone:IsCoordinateInZone(_LandingPos) then
distancetofarp = self.maxdistance - 10
else
distancetofarp = self.maxdistance + 10
end
end
-- Mayday Message
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
local text = ""
@@ -845,6 +817,7 @@ function AICSAR:_EventHandler(EventData, FromEject)
if _coalition == self.coalition then
if self.verbose then
MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition)
-- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog()
end
if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
@@ -896,7 +869,6 @@ function AICSAR:_GetFlight()
:InitUnControlled(true)
:OnSpawnGroup(
function(Group)
Group:OptionPreferVerticalLanding()
self:__HeloOnDuty(1,Group)
end
)
@@ -962,10 +934,6 @@ function AICSAR:_InitMission(Pilot,Index)
helo:__UnloadingDone(5)
end
function helo:OnAfterLandAtAirbase(From,Event,To,airbase)
helo:Despawn(2)
end
self.helos[Index] = helo
return self
@@ -1016,9 +984,7 @@ function AICSAR:_CheckHelos()
local name = helo:GetName()
self:T("Helo group "..name.." in state "..state)
if state == "Arrived" then
--helo:__Stop(5)
helo.OnAfterDead = nil
helo:Despawn(35)
helo:__Stop(5)
self.helos[_index] = nil
end
else
@@ -1059,7 +1025,7 @@ function AICSAR:_CheckQueue(OpsGroup)
if self:_CheckInMashZone(_pilot) then
self:T("Pilot" .. _pilot.GroupName .. " rescued!")
if OpsGroup then
--OpsGroup:Despawn(10)
OpsGroup:Despawn(10)
else
_pilot:Destroy(true,10)
end

View File

@@ -105,7 +105,7 @@ AUTOLASE = {
debug = false,
smokemenu = true,
RoundingPrecision = 0,
increasegroundawareness = false,
increasegroundawareness = true,
MonitorFrequency = 30,
}
@@ -216,7 +216,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
self.smokemenu = true
self.threatmenu = true
self.RoundingPrecision = 0
self.increasegroundawareness = false
self.increasegroundawareness = true
self.MonitorFrequency = 30
self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)})
@@ -493,7 +493,7 @@ end
--- (User) Function enable sending messages via SRS.
-- @param #AUTOLASE self
-- @param #boolean OnOff Switch usage on and off
-- @param #string Path Path to SRS TTS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio
-- @param #string Path Path to SRS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalone
-- @param #number Frequency Frequency to send, e.g. 243
-- @param #number Modulation Modulation i.e. radio.modulation.AM or radio.modulation.FM
-- @param #string Label (Optional) Short label to be used on the SRS Client Overlay
@@ -508,7 +508,7 @@ end
function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey)
if OnOff then
self.useSRS = true
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.SRSFreq = Frequency or 271
self.SRSMod = Modulation or radio.modulation.AM
self.Gender = Gender or MSRS.gender or "male"
@@ -1020,7 +1020,7 @@ 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
if static and static:GetCoalition() ~= self.coalition then
local IsLOS = position:IsLOS(static:GetCoordinate())
if IsLOS then
unit:KnowUnit(static,true,true)

View File

@@ -22,7 +22,7 @@
-- @module Functional.Mantis
-- @image Functional.Mantis.jpg
--
-- Last Update: August 2025
-- Last Update: Mar 2025
-------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE
@@ -62,9 +62,7 @@
-- @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 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 #number checkcounter Counter for SAM Table refreshes
-- @extends Core.Base#BASE
@@ -76,9 +74,10 @@
--
-- * Moose derived Modular, Automatic and Network capable Targeting and Interception System.
-- * Controls a network of SAM sites. Uses detection to switch on the SAM site closest to the enemy.
-- * **Automatic mode** (default) will set-up your SAM site network automatically for you.
-- * Leverage evasiveness from SEAD, leverage attack range setting.
-- * Automatic setup of SHORAD based on groups of the class "short-range".
-- * **Automatic mode** (default since 0.8) will set-up your SAM site network automatically for you
-- * **Classic mode** behaves like before
-- * Leverage evasiveness from SEAD, leverage attack range setting
-- * Automatic setup of SHORAD based on groups of the class "short-range"
--
-- # 0. Base considerations and naming conventions
--
@@ -108,15 +107,10 @@
-- * 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
-- * 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 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
--
-- * 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"
@@ -130,20 +124,19 @@
-- * SA-2 (with V759 missile, e.g. "Red SAM SA-2 HDS")
-- * SA-2 (with HQ-2 launcher, use HQ-2 in the group name, e.g. "Red SAM HQ-2" )
-- * SA-3 (with V601P missile, e.g. "Red SAM SA-3 HDS")
-- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS" with 5P85CE launcher)
-- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS" with 5P85SE launcher)
-- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS 2" for the 9A82 variant and "Red SAM SA-12 HDS 1" for the 9A83 variant)
-- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS 2" for the 9A82ME variant and "Red SAM SA-23 HDS 1" for the 9A83ME variant)
-- * SAMP/T (launcher dependent range, e.g. "Blue SAM SAMPT Block 1 HDS" for Block 1, "Blue SAM SAMPT Block 1INT HDS", "Blue SAM SAMPT Block 2 HDS")
-- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS")
-- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS")
-- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS")
-- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS")
--
-- The other HDS types work like the rest of the known SAM systems.
--
-- # 0.1 Set-up in the mission editor
--
-- Set up your SAM sites in the mission editor. Name the groups using a systematic approach like above.Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc.
-- Set up your SAM sites in the mission editor. Name the groups using a systematic approach like above.
-- Set up your EWR system in the mission editor. Name the groups using a systematic approach like above. Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc.
-- Search Radars usually have "SR" or "STR" in their names. Use the encyclopedia in the mission editor to inform yourself.
-- Set up your SHORAD systems. They need to be **close** to (i.e. around) the SAM sites to be effective. Use **one unit ** per group (multiple groups) for the SAM location.
-- Else, evasive manoevers might club up all defenders in one place. Red SA-15 TOR systems offer a good missile defense.
-- Set up your SHORAD systems. They need to be **close** to (i.e. around) the SAM sites to be effective. Use **one** group per SAM location. SA-15 TOR systems offer a good missile defense.
--
-- [optional] Set up your HQ. Can be any group, e.g. a command vehicle.
--
@@ -195,7 +188,7 @@
--
-- ## 2.1 Auto mode features
--
-- ### 2.1.1 You can add Accept-, Reject- and Conflict-Zones to your setup, e.g. to consider borders or de-militarized zones:
-- ### 2.1.1 You can now add Accept-, Reject- and Conflict-Zones to your setup, e.g. to consider borders or de-militarized zones:
--
-- -- Parameters are tables of Core.Zone#ZONE objects!
-- -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when
@@ -213,6 +206,9 @@
--
-- ### 2.1.4 Advanced features
--
-- -- Option to switch off auto mode **before** you start MANTIS (not recommended)
-- mybluemantis.automode = false
--
-- -- Option to set the scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.
-- -- also see engagerange below.
-- self.radiusscale[MANTIS.SamType.LONG] = 1.1
@@ -225,12 +221,6 @@
-- -- For some scenarios, like Cold War, it might be useful not to activate SAMs if friendly aircraft are around to avoid death by friendly fire.
-- mybluemantis.checkforfriendlies = true
--
-- ### 2.1.6 Shoot & Scoot
--
-- -- Option to make the (driveable) SHORAD units drive around and shuffle positions
-- -- We use a SET_ZONE for that, number of zones to consider defaults to three, Random is true for random coordinates and Formation is e.g. "Vee".
-- mybluemantis:AddScootZones(ZoneSet, Number, Random, Formation)
--
-- # 3. Default settings [both modes unless stated otherwise]
--
-- By default, the following settings are active:
@@ -253,7 +243,25 @@
--
-- Use this option if you want to make use of or allow advanced SEAD tactics.
--
-- # 5. Integrated SEAD
-- # 5. Integrate SHORAD [classic mode, not necessary in automode, not recommended for manual setup]
--
-- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs manually. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in
-- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so:
--
-- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
-- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")
-- -- now set up MANTIS
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
-- mymantis:AddShorad(myshorad,720)
-- mymantis:Start()
--
-- If you systematically name your SHORAD groups starting with "Blue SHORAD" you'll need exactly **one** SHORAD instance to manage all SHORAD groups.
--
-- (Optionally) you can remove the link later on with
--
-- mymantis:RemoveShorad()
--
-- # 6. Integrated SEAD
--
-- MANTIS is using @{Functional.Sead#SEAD} internally to both detect and evade HARM attacks. No extra efforts needed to set this up!
-- Once a HARM attack is detected, MANTIS (via SEAD) will shut down the radars of the attacked SAM site and take evasive action by moving the SAM
@@ -280,7 +288,6 @@
MANTIS = {
ClassName = "MANTIS",
name = "mymantis",
version = "0.9.34",
SAM_Templates_Prefix = "",
SAM_Group = nil,
EWR_Templates_Prefix = "",
@@ -329,8 +336,6 @@ MANTIS = {
SmokeDecoy = false,
SmokeDecoyColor = SMOKECOLOR.White,
checkcounter = 1,
DLinkCacheTime = 120,
logsamstatus = false,
}
--- Advanced state enumerator
@@ -369,7 +374,7 @@ MANTIS.radiusscale[MANTIS.SamType.POINT] = 3
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
["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" },
["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot" },
["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" },
@@ -377,8 +382,7 @@ MANTIS.SamData = {
["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-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" },
["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland" },
["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" },
["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Point", Radar="Roland" },
["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" },
@@ -389,21 +393,14 @@ MANTIS.SamData = {
["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" },
["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-17"] = { Range=50, Blindspot=3, Height=30, 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"},
["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" },
["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" },
["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" },
["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" },
}
--- SAM data HDS
@@ -419,17 +416,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-10C HDS 2"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85DE ln"}, -- V55RUD
["SA-10C HDS 1"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD
["SA-12 HDS 2"] = { Range=100, Blindspot=10, Height=25, Type="Long" , Radar="S-300V 9A82 l"},
["SA-12 HDS 1"] = { Range=75, Blindspot=1, 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" },
["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
["SAMPT Block 2 HDS"] = { Range=200, Blindspot=10, Height=70, Type="long", Radar="SAMPT_MLT_Blk2" }, -- Block 2 Launcher
}
--- SAM data SMA
@@ -469,15 +462,15 @@ 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="Point", Radar="PantsirS1", Point="true" },
["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" },
["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="Point", Radar="TorM2", Point="true" },
["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true" },
["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true" },
["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" },
["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" },
@@ -632,7 +625,6 @@ do
self.advAwacs = false
end
self:SetDLinkCacheTime()
-- Set the string id for output to DCS.log file.
self.lid=string.format("MANTIS %s | ", self.name)
@@ -666,8 +658,6 @@ do
table.insert(self.ewr_templates,awacs)
end
self.logsamstatus = false
self:T({self.ewr_templates})
self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition)
@@ -697,6 +687,9 @@ do
-- counter for SAM table updates
self.checkcounter = 1
-- TODO Version
-- @field #string version
self.version="0.9.27"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions ---
@@ -893,11 +886,7 @@ do
self.AcceptZones = AcceptZones or {}
self.RejectZones = RejectZones or {}
self.ConflictZones = ConflictZones or {}
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
if #self.AcceptZones > 0 or #self.RejectZones > 0 or #self.ConflictZones > 0 then
self.usezones = true
end
return self
@@ -1051,16 +1040,6 @@ do
return self
end
--- Function to set how long INTEL DLINK remembers contacts.
-- @param #MANTIS self
-- @param #number seconds Remember this many seconds, at least 5 seconds.
-- @return #MANTIS self
function MANTIS:SetDLinkCacheTime(seconds)
self.DLinkCacheTime = math.abs(seconds or 120)
if self.DLinkCacheTime < 5 then self.DLinkCacheTime = 5 end
return self
end
--- Function to set the detection interval
-- @param #MANTIS self
-- @param #number interval The interval in seconds
@@ -1289,8 +1268,7 @@ do
self:T(self.lid.."_CheckCoordinateInZones")
local inzone = false
-- acceptzones
self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo))
if self.AcceptZonesNo > 0 then
if #self.AcceptZones > 0 then
for _,_zone in pairs(self.AcceptZones) do
local zone = _zone -- Core.Zone#ZONE
if zone:IsCoordinateInZone(coord) then
@@ -1301,7 +1279,7 @@ do
end
end
-- rejectzones
if self.RejectZonesNo > 0 then
if #self.RejectZones > 0 and inzone then -- maybe in accept zone, but check the overlaps
for _,_zone in pairs(self.RejectZones) do
local zone = _zone -- Core.Zone#ZONE
if zone:IsCoordinateInZone(coord) then
@@ -1312,7 +1290,7 @@ do
end
end
-- conflictzones
if self.ConflictZonesNo > 0 then
if #self.ConflictZones > 0 and not inzone then -- if not already accepted, might be in conflict zones
for _,_zone in pairs(self.ConflictZones) do
local zone = _zone -- Core.Zone#ZONE
if zone:IsCoordinateInZone(coord) then
@@ -1378,7 +1356,6 @@ 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)
@@ -1454,9 +1431,7 @@ do
--IntelTwo:SetClusterRadius(5000)
IntelTwo:Start()
local CacheTime = self.DLinkCacheTime or 120
local IntelDlink = INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime)
local IntelDlink = INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,300)
IntelDlink:__Start(1)
self:SetUsingDLink(IntelDlink)
@@ -1518,7 +1493,7 @@ 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())
@@ -1719,9 +1694,7 @@ do
local grpname = group:GetName()
local grpcoord = group:GetCoordinate()
local grprange, grpheight,type,blind = self:_GetSAMRange(grpname)
-- TODO the below might stop working at some point after some hours, needs testing
--local radaralive = group:IsSAM()
local radaralive = true
local radaralive = group:IsSAM()
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 )
if type == MANTIS.SamType.LONG and radaralive then
@@ -1818,7 +1791,7 @@ do
if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] 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 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
local switch = false
@@ -1850,7 +1823,7 @@ do
-- link in to SHORAD if available
-- DONE: Test integration fully
if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early
local Shorad = self.Shorad --Functional.Shorad#SHORAD
local Shorad = self.Shorad
local radius = self.checkradius
local ontime = self.ShoradTime
Shorad:WakeUpShorad(name, radius, ontime)
@@ -1883,7 +1856,7 @@ do
end --end alive
end --end check
end --for loop
if self.debug or self.verbose or self.logsamstatus then
if self.debug or self.verbose then
for _,_status in pairs(self.SamStateTracker) do
if _status == "GREEN" then
instatusgreen=instatusgreen+1
@@ -1904,9 +1877,8 @@ do
-- @param #MANTIS self
-- @param Functional.Detection#DETECTION_AREAS detection Detection object
-- @param #boolean dlink
-- @param #boolean reporttolog
-- @return #MANTIS self
function MANTIS:_Check(detection,dlink,reporttolog)
function MANTIS:_Check(detection,dlink)
self:T(self.lid .. "Check")
--get detected set
local detset = detection:GetDetectedItemCoordinates()
@@ -1933,8 +1905,7 @@ do
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)
end
local function GetReport()
if self.debug or self.verbose then
local statusreport = REPORT:New("\nMANTIS Status "..self.name)
statusreport:Add("+-----------------------------+")
statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred))
@@ -1943,15 +1914,7 @@ do
statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads))
end
statusreport:Add("+-----------------------------+")
return statusreport
end
if self.debug or self.verbose then
local statusreport = GetReport()
MESSAGE:New(statusreport:Text(),10):ToAll():ToLog()
elseif reporttolog == true then
local statusreport = GetReport()
MESSAGE:New(statusreport:Text(),10):ToLog()
end
return self
end
@@ -2059,7 +2022,7 @@ do
self:T({From, Event, To})
-- check detection
if not self.state2flag then
self:_Check(self.Detection,self.DLink,self.logsamstatus)
self:_Check(self.Detection,self.DLink)
end
local EWRAlive = self:_CheckAnyEWRAlive()
@@ -2130,7 +2093,7 @@ do
if self.debug and self.verbose then
self:I(self.lid .. "Status Report")
for _name,_state in pairs(self.SamStateTracker) do
self:I(string.format("Site %s | Status %s",_name,_state))
self:I(string.format("Site %s\tStatus %s",_name,_state))
end
end
local interval = self.detectinterval * -1

View File

@@ -53,8 +53,6 @@
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE.
-- Therefore, this class is considered to be deprecated and superseded by the [Functional.Fox](https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Functional.Fox.html) class, which provides the same functionality.
--

View File

@@ -603,7 +603,7 @@ RANGE.MenuF10Root = nil
--- Range script version.
-- @field #string version
RANGE.version = "2.8.1"
RANGE.version = "2.8.0"
-- TODO list:
-- TODO: Verbosity level for messages.
@@ -2032,10 +2032,10 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
-- Smoke impact point of bomb.
if playerData and playerData.smokebombimpact and insidezone then
if playerData.delaysmoke then
impactcoord:Smoke(playerData.smokecolor, 30, self.TdelaySmoke)
if playerData and playerData.delaysmoke then
timer.scheduleFunction( self._DelayedSmoke, { coord = impactcoord, color = playerData.smokecolor }, timer.getTime() + self.TdelaySmoke )
else
impactcoord:Smoke(playerData.smokecolor, 30)
impactcoord:Smoke( playerData.smokecolor )
end
end
@@ -2102,12 +2102,7 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
result.attackHdg = attackHdg
result.attackVel = attackVel
result.attackAlt = attackAlt
if os and os.date then
result.date=os.date()
else
self:E(self.lid.."os or os.date() not available")
result.date = "n/a"
end
result.date=os and os.date() or "n/a"
-- Add to table.
table.insert( _results, result )
@@ -2640,6 +2635,13 @@ end
-- Display Messages
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Start smoking a coordinate with a delay.
-- @param #table _args Argements passed.
function RANGE._DelayedSmoke( _args )
_args.coord:Smoke(_args.color)
--trigger.action.smoke( _args.coord:GetVec3(), _args.color )
end
--- Display top 10 stafing results of a specific player.
-- @param #RANGE self
-- @param #string _unitName Name of the player unit.

View File

@@ -985,7 +985,6 @@ function SCORING:_EventOnHit( Event )
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
local TargetIsScenery = false
if Event.IniDCSUnit then
@@ -1026,12 +1025,6 @@ function SCORING:_EventOnHit( Event )
TargetCategory = Event.TgtCategory
TargetType = Event.TgtTypeName
-- Scenery hit
if (not TargetCategory) and TargetUNIT ~= nil and TargetUnit:IsInstanceOf("SCENERY") then
TargetCategory = Unit.Category.STRUCTURE
TargetIsScenery = true
end
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
TargetUnitCategory = _SCORINGCategory[TargetCategory]
TargetUnitType = TargetType
@@ -1124,22 +1117,17 @@ function SCORING:_EventOnHit( Event )
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
elseif TargetIsScenery ~= true then
else
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 nothing special.",
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.",
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )

View File

@@ -1,10 +1,10 @@
----- **Functional** - TIRESIAS - manages AI behaviour (OPTIMIZED VERSION).
---- ===
--- The @{#TIRESIAS} class is working in the back to keep your large-scale ground units in check.
--- **Functional** - TIRESIAS - manages AI behaviour.
--
-- -- Features:
-- ===
--
-- The @{#TIRESIAS} class is working in the back to keep your large-scale ground units in check.
--
-- ## Features:
--
-- * Designed to keep CPU and Network usage lower on missions with a lot of ground units.
-- * Does not affect ships to keep the Navy guys happy.
@@ -16,29 +16,24 @@
--
-- ===
--
-- ## Optimizations Applied:
-- ## Missions:
--
-- * Cached frequently used functions and constants
-- * Reduced string concatenations and formatting
-- * Optimized loop structures and conditions
-- * Pre-allocated tables where possible
-- * Reduced function call overhead
-- * Improved memory management
-- ### [TIRESIAS](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master)
--
---- ===
-- ===
--
-- ### Author : **applevangelist **
--
---- #-- Author : **applevangelist ** (Optimized by AI)
---
-- @module Functional.Tiresias
-- @image Functional.Tiresias.jpg
--
-- Last Update: Dec 2023
--- Last Update: July 2025
-------------------------------------------------------------------------
--- **TIRESIAS** class, extends Core.Base#BASE
-- @type TIRESIAS
-- @field #string ClassName
-- @field #boolean debug
-- @field #booelan debug
-- @field #string version
-- @field #number Interval
-- @field Core.Set#SET_GROUP GroundSet
@@ -53,9 +48,6 @@
-- @field #number PlaneSwitchRange
-- @field Core.Set#SET_GROUP FlightSet
-- @field #boolean SwitchAAA
-- @field #string lid
-- @field #table _cached_zones
-- @field #table _cached_groupsets
-- @extends Core.Fsm#FSM
---
@@ -66,8 +58,8 @@
-- @field #boolean AIOff
-- @field #boolean exception
---
-- *Tiresias, Greek demi-god and shapeshifter, blinded by the Gods, works as oracle for you.* (Wiki)
--- *Tiresias, Greek demi-god and shapeshifter, blinded by the Gods, works as oracle for you.* (Wiki)
--
-- ===
--
@@ -82,17 +74,18 @@
-- * Easy setup.
--
-- ## Setup
-- -- Setup is a one-liner:
--
-- Setup is a one-liner:
--
-- local blinder = TIRESIAS:New()
--
-- -- Optionally you can set up exceptions, e.g. for convoys driving around
-- Optionally you can set up exceptions, e.g. for convoys driving around
--
-- local exceptionset = SET_GROUP:New():FilterCoalitions(" red" ):FilterPrefixes(" Convoy" ):FilterStart()
-- local exceptionset = SET_GROUP:New():FilterCoalitions("red"):FilterPrefixes("Convoy"):FilterStart()
-- local blinder = TIRESIAS:New()
-- blinder:AddExceptionSet(exceptionset)
--
-- -- Options
-- Options
--
-- -- Setup different radius for activation around helo and airplane groups (applies to AI and humans)
-- blinder:SetActivationRanges(10,25) -- defaults are 10, and 25
@@ -100,12 +93,11 @@
-- -- Setup engagement ranges for AAA (non-advanced SAM units like Flaks etc) and if you want them to be AIOff
-- blinder:SetAAARanges(60,true) -- defaults are 60, and true
--
---
-- @field #TIRESIAS
TIRESIAS = {
ClassName = "TIRESIAS",
debug = true,
version = " 0.0.7-OPT" ,
debug = false,
version = "0.0.5",
Interval = 20,
GroundSet = nil,
VehicleSet = nil,
@@ -116,12 +108,9 @@ TIRESIAS = {
HeloSwitchRange = 10, -- NM
PlaneSwitchRange = 25, -- NM
SwitchAAA = true,
_cached_zones = {}, -- Cache for zone objects
_cached_groupsets = {}, -- Cache for group_set objects
}
}
---
-- [USER] Create a new Tiresias object and start it up.
--- [USER] Create a new Tiresias object and start it up.
-- @param #TIRESIAS self
-- @return #TIRESIAS self
function TIRESIAS:New()
@@ -140,15 +129,13 @@ function TIRESIAS:New()
self:AddTransition("*", "Status", "*") -- TIRESIAS status update.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self.ExceptionSet = nil --SET_GROUP:New():Clear(false)
self._cached_zones = {} -- Initialize zone cache
self.ExceptionSet = SET_GROUP:New():Clear(false)
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
-- Cache the log identifier to avoid string concatenation in loops
self.lid = "TIRESIAS " .. self.version .. " | "
self.lid = string.format("TIRESIAS %s | ",self.version)
self:I(self.lid .. "Managing ground groups!")
self:I(self.lid.."Managing ground groups!")
--- Triggers the FSM event "Stop". Stops TIRESIAS and all its event handlers.
-- @function [parent=#TIRESIAS] Stop
@@ -169,26 +156,23 @@ function TIRESIAS:New()
-- @param #number delay Delay in seconds.
self:__Start(1)
return self
end
-----
---
-------------------------------------------------------------------------------------------------------------
--
-- Helper Functions
---
--
-------------------------------------------------------------------------------------------------------------
--- [USER] Set activation radius for Helos and Planes in Nautical Miles.
---[USER] Set activation radius for Helos and Planes in Nautical Miles.
-- @param #TIRESIAS self
-- @param #number HeloMiles Radius around a Helicopter in which AI ground units will be activated. Defaults to 10NM.
-- @param #number PlaneMiles Radius around an Airplane in which AI ground units will be activated. Defaults to 25NM.
-- @return #TIRESIAS self
function TIRESIAS:SetActivationRanges(HeloMiles, PlaneMiles)
function TIRESIAS:SetActivationRanges(HeloMiles,PlaneMiles)
self.HeloSwitchRange = HeloMiles or 10
self.PlaneSwitchRange = PlaneMiles or 25
-- Clear zone cache when ranges change
self._cached_zones = {}
return self
end
@@ -197,7 +181,7 @@ end
-- @param #number FiringRange The engagement range that AAA units will be set to. Can be 0 to 100 (percent). Defaults to 60.
-- @param #boolean SwitchAAA Decide if these system will have their AI switched off, too. Defaults to true.
-- @return #TIRESIAS self
function TIRESIAS:SetAAARanges(FiringRange, SwitchAAA)
function TIRESIAS:SetAAARanges(FiringRange,SwitchAAA)
self.AAARange = FiringRange or 60
self.SwitchAAA = (SwitchAAA == false) and false or true
return self
@@ -208,391 +192,330 @@ end
-- @param Core.Set#SET_GROUP Set to add to the exception list.
-- @return #TIRESIAS self
function TIRESIAS:AddExceptionSet(Set)
self:T(self.lid .. " AddExceptionSet" )
if not self.ExceptionSet then
self.ExceptionSet = SET_GROUP:New()
end
self:T(self.lid.."AddExceptionSet")
local exceptions = self.ExceptionSet
-- Cache the exception data structure for reuse
local exception_data = {
type = " Exception" ,
exception = true,
}
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
grp.Tiresias = exception_data
exceptions:AddGroup(grp, true)
BASE:T(" TIRESIAS: Added exception group: " .. grp:GetName())
if not grp.Tiresias then
grp.Tiresias = { -- #TIRESIAS.Data
type = "Exception",
exception = true,
}
exceptions:AddGroup(grp,true)
end
BASE:T("TIRESIAS: Added exception group: "..grp:GetName())
end
)
return self
end
--- [INTERNAL] Filter Function - Optimized with cached calls
--- [INTERNAL] Filter Function
-- @param Wrapper.Group#GROUP Group
-- @return #boolean isin
function TIRESIAS._FilterNotAAA(Group)
local grp = Group -- Wrapper.Group#GROUP
-- Cache method calls to reduce overhead
local is_air = grp:IsAir()
local is_ship = grp:IsShip()
local is_AAA = grp:IsAAA()
if is_air or grp:IsShip() then -- air or ship - no AAA
local isaaa = grp:IsAAA()
if isaaa == true and grp:IsGround() and not grp:IsShip() then
return false -- remove from SET
else
return true -- keep in SET
end
return not is_AAA -- remove AAA, keep others
end
--- [INTERNAL] Filter Function - Optimized with cached calls
--- [INTERNAL] Filter Function
-- @param Wrapper.Group#GROUP Group
-- @return #boolean isin
function TIRESIAS._FilterNotSAM(Group)
local grp = Group -- Wrapper.Group#GROUP
-- Cache method calls to reduce overhead
local is_air = grp:IsGround()
local is_ship = grp:IsShip()
local is_SAM = grp:IsSAM()
if is_air or grp:IsShip() then
local issam = grp:IsSAM()
if issam == true and grp:IsGround() and not grp:IsShip() then
return false -- remove from SET
else
return true -- keep in SET
end
return not is_SAM -- remove SAM, keep others
end
--- [INTERNAL] Filter Function - Optimized with cached calls
--- [INTERNAL] Filter Function
-- @param Wrapper.Group#GROUP Group
-- @return #boolean isin
function TIRESIAS._FilterAAA(Group)
local grp = Group -- Wrapper.Group#GROUP
-- Cache method calls to reduce overhead
local is_ground = grp:IsGround()
if (not is_ground) or grp:IsShip() then
return false -- not AAA
local isaaa = grp:IsAAA()
if isaaa == true and grp:IsGround() and not grp:IsShip() then
return true -- remove from SET
else
return false -- keep in SET
end
return grp:IsAAA() -- only AAA
end
--- [INTERNAL] Filter Function - Optimized with cached calls
--- [INTERNAL] Filter Function
-- @param Wrapper.Group#GROUP Group
-- @return #boolean isin
function TIRESIAS._FilterSAM(Group)
local grp = Group -- Wrapper.Group#GROUP
-- Cache method calls to reduce overhead
local is_ground = grp:IsGround()
if (not is_ground) or grp:IsShip() then
return false -- not SAM
local issam = grp:IsSAM()
if issam == true and grp:IsGround() and not grp:IsShip() then
return true -- remove from SET
else
return false -- keep in SET
end
return grp:IsSAM() -- only SAM
end
--- [INTERNAL] Init Groups - Optimized with reduced function calls
--- [INTERNAL] Init Groups
-- @param #TIRESIAS self
-- @return #TIRESIAS self
function TIRESIAS:_InitGroups()
self:T(self.lid .. " _InitGroups" )
-- Cache frequently used values
local EngageRange = self.AAARange
local SwitchAAA = self.SwitchAAA
-- Pre-create data structures to avoid repeated table creation
local aaa_data_template = {
type = " AAA" ,
invisible = true,
range = EngageRange,
exception = false,
AIOff = SwitchAAA,
}
local vehicle_data_template = {
type = " Vehicle" ,
invisible = true,
AIOff = true,
exception = false,
}
local sam_data_template = {
type = " SAM" ,
invisible = true,
exception = false,
}
--- AAA - Optimized loop
self.AAASet:ForEachGroupAlive(
self:T(self.lid.."_InitGroups")
-- Set all groups invisible/motionless
local EngageRange = self.AAARange
local SwitchAAA = self.SwitchAAA
--- AAA
self.AAASet:ForEachGroupAlive(
function(grp)
local tiresias_data = grp.Tiresias
if not tiresias_data then
if not grp.Tiresias then
grp:OptionEngageRange(EngageRange)
grp:SetCommandInvisible(true)
if SwitchAAA then
grp:SetAIOff()
grp:EnableEmission(false)
end
grp.Tiresias = aaa_data_template
elseif not tiresias_data.exception == true then
if not tiresias_data.invisible == true then
grp.Tiresias = { -- #TIRESIAS.Data
type = "AAA",
invisible = true,
range = EngageRange,
exception = false,
AIOff = SwitchAAA,
}
end
if grp.Tiresias and (not grp.Tiresias.exception == true) then
if grp.Tiresias.invisible == false then
grp:SetCommandInvisible(true)
tiresias_data.invisible = true
if SwitchAAA == true then
grp.Tiresias.invisible = true
if SwitchAAA then
grp:SetAIOff()
grp:EnableEmission(false)
tiresias_data.AIOff = true
grp.Tiresias.AIOff = true
end
end
end
--BASE:I(string.format("Init/Switch off AAA %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception)))
end
)
--- Vehicles - Optimized loop
self.VehicleSet:ForEachGroupAlive(
--- Vehicles
self.VehicleSet:ForEachGroupAlive(
function(grp)
local tiresias_data = grp.Tiresias
if not tiresias_data then
if not grp.Tiresias then
grp:SetAIOff()
grp:SetCommandInvisible(true)
grp.Tiresias = vehicle_data_template
elseif not tiresias_data.exception == true then
if not tiresias_data.invisible then
grp.Tiresias = { -- #TIRESIAS.Data
type = "Vehicle",
invisible = true,
AIOff = true,
exception = false,
}
end
if grp.Tiresias and (not grp.Tiresias.exception == true) then
if grp.Tiresias and grp.Tiresias.invisible == false then
grp:SetCommandInvisible(true)
grp:SetAIOff()
tiresias_data.invisible = true
tiresias_data.AIOff = true
grp.Tiresias.invisible = true
grp.Tiresias.AIOff = true
end
end
--BASE:I(string.format("Init/Switch off Vehicle %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception)))
end
)
--- SAM - Optimized loop
self.SAMSet:ForEachGroupAlive(
--- SAM
self.SAMSet:ForEachGroupAlive(
function(grp)
local tiresias_data = grp.Tiresias
if not tiresias_data then
if not grp.Tiresias then
grp:SetCommandInvisible(true)
grp.Tiresias = sam_data_template
elseif not tiresias_data.exception == true then
if not tiresias_data.invisible then
grp.Tiresias = { -- #TIRESIAS.Data
type = "SAM",
invisible = true,
exception = false,
}
end
if grp.Tiresias and (not grp.Tiresias.exception == true) then
if grp.Tiresias and grp.Tiresias.invisible == false then
grp:SetCommandInvisible(true)
tiresias_data.invisible = true
grp.Tiresias.invisible = true
end
end
--BASE:I(string.format("Init/Switch off SAM %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception)))
end
)
return self
return self
end
--- [INTERNAL] Event handler function - Optimized
--- [INTERNAL] Event handler function
-- @param #TIRESIAS self
-- @param Core.Event#EVENTDATA EventData
-- @return #TIRESIAS self
function TIRESIAS:_EventHandler(EventData)
self:T(string.format(" %s Event = %d" , self.lid, EventData.id))
self:T(string.format("%s Event = %d",self.lid, EventData.id))
local event = EventData -- Core.Event#EVENTDATA
if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then
--local _coalition = event.IniCoalition
--if _coalition ~= self.Coalition then
-- return --ignore!
--end
local unitname = event.IniUnitName or "none"
local _unit = event.IniUnit
local _group = event.IniGroup
if _group and _group:IsAlive() then
-- Cache the radius calculation
local radius = _group:IsHelicopter() and self.HeloSwitchRange or self.PlaneSwitchRange
self:_SwitchOnGroups(_group, radius)
local radius = self.PlaneSwitchRange
if _group:IsHelicopter() then
radius = self.HeloSwitchRange
end
self:_SwitchOnGroups(_group,radius)
end
end
return self
end
--- [INTERNAL] Switch Groups Behaviour - Optimized with zone caching
--- [INTERNAL] Switch Groups Behaviour
-- @param #TIRESIAS self
-- @param Wrapper.Group#GROUP group
-- @param #number radius Radius in NM
-- @return #TIRESIAS self
function TIRESIAS:_SwitchOnGroups(group, radius)
self:T(self.lid .. " _SwitchOnGroups " .. group:GetName() .. " Radius " .. radius .. " NM" )
-- 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 ground = self._cached_groupsets[cache_key]
if not zone then
zone = ZONE_GROUP:New(" Zone-" .. group_name, group, UTILS.NMToMeters(radius))
self._cached_zones[cache_key] = zone
else
-- Update zone center to current group position
zone:UpdateFromGroup(group)
end
if not ground then
ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce()
self._cached_groupsets[cache_key] = ground
else
ground:FilterZones({zone},true):FilterOnce()
end
function TIRESIAS:_SwitchOnGroups(group,radius)
self:T(self.lid.."_SwitchOnGroups "..group:GetName().." Radius "..radius.." NM")
local zone = ZONE_GROUP:New("Zone-"..group:GetName(),group,UTILS.NMToMeters(radius))
local ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce()
local count = ground:CountAlive()
if self.debug then
self:I(string.format(" There are %d groups around this plane or helo!" , count))
local text = string.format("There are %d groups around this plane or helo!",count)
self:I(text)
end
if count > 0 then
-- Cache values outside the loop
local SwitchAAA = self.SwitchAAA
local group_coalition = group:GetCoalition()
if ground:CountAlive() > 0 then
ground:ForEachGroupAlive(
function(grp)
local tiresias_data = grp.Tiresias
if grp:GetCoalition() ~= group_coalition
and tiresias_data
and tiresias_data.type
and not tiresias_data.exception == true then
-- Make group visible if invisible
if tiresias_data.invisible == true then
local name = grp:GetName()
if grp:GetCoalition() ~= group:GetCoalition()
and grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then
if grp.Tiresias.invisible == true then
grp:SetCommandInvisible(false)
tiresias_data.invisible = false
grp.Tiresias.invisible = false
end
-- Handle AI activation based on type
local grp_type = tiresias_data.type
if grp_type == "Vehicle" and tiresias_data.AIOff == true then
if grp.Tiresias.type == "Vehicle" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then
grp:SetAIOn()
tiresias_data.AIOff = false
elseif SwitchAAA == true and grp_type == "AAA" and tiresias_data.AIOff == true then
grp.Tiresias.AIOff = false
end
if SwitchAAA and grp.Tiresias.type == "AAA" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then
grp:SetAIOn()
grp:EnableEmission(true)
tiresias_data.AIOff = false
grp.Tiresias.AIOff = false
end
--BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception)))
else
BASE:T("TIRESIAS - This group " .. tostring(grp:GetName()) .. " has not been initialized or is an exception!")
BASE:T("TIRESIAS - This group "..tostring(name).. " has not been initialized or is an exception!")
end
end
)
end
return self
end
-----
---
-------------------------------------------------------------------------------------------------------------
--
-- FSM Functions
----
--
-------------------------------------------------------------------------------------------------------------
--- [INTERNAL] FSM Function - Optimized initialization
--- [INTERNAL] FSM Function
-- @param #TIRESIAS self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #TIRESIAS self
function TIRESIAS:onafterStart(From, Event, To)
self:T({From, Event, To})
self:T({From, Event, To})
-- Create sets with optimized filters
local VehicleSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart()
local AAASet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart()
local SAMSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart()
local OpsGroupSet = SET_OPSGROUP:New():FilterActive(true):FilterStart()
self.FlightSet = SET_GROUP:New():FilterCategories({" plane" ," helicopter" }):FilterStart()
local VehicleSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart()
local AAASet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart()
local SAMSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart()
local OpsGroupSet = SET_OPSGROUP:New():FilterActive(true):FilterStart()
self.FlightSet = SET_GROUP:New():FilterCategories({"plane","helicopter"}):FilterStart()
-- Cache frequently used values
local EngageRange = self.AAARange
local SwitchAAA = self.SwitchAAA
local ExceptionSet = self.ExceptionSet
local EngageRange = self.AAARange
-- Pre-create data templates to reduce object creation
local exception_data = {
type = " Exception" ,
local ExceptionSet = self.ExceptionSet
if self.ExceptionSet then
function ExceptionSet:OnAfterAdded(From,Event,To,ObjectName,Object)
BASE:I("TIRESIAS: EXCEPTION Object Added: "..Object:GetName())
if Object and Object:IsAlive() then
Object.Tiresias = { -- #TIRESIAS.Data
type = "Exception",
exception = true,
}
local vehicle_data = {
type = " Vehicle" ,
invisible = true,
AIOff = true,
exception = false,
}
local aaa_data = {
type = " AAA" ,
invisible = true,
range = EngageRange,
exception = false,
AIOff = SwitchAAA,
}
local sam_data = {
type = " SAM" ,
invisible = true,
exception = false,
}
if ExceptionSet then
function ExceptionSet:OnAfterAdded(From, Event, To, ObjectName, Object)
BASE:I(" TIRESIAS: EXCEPTION Object Added: " .. Object:GetName())
if Object and Object:IsAlive() then
Object.Tiresias = exception_data
Object:SetAIOn()
Object:SetCommandInvisible(false)
Object:EnableEmission(true)
end
end
-- Process existing OpsGroups more efficiently
local OGS = OpsGroupSet:GetAliveSet()
for _, _OG in pairs(OGS or {}) do
for _,_OG in pairs(OGS or {}) do
local OG = _OG -- Ops.OpsGroup#OPSGROUP
local grp = OG:GetGroup()
ExceptionSet:AddGroup(grp, true)
ExceptionSet:AddGroup(grp,true)
end
function OpsGroupSet:OnAfterAdded(From, Event, To, ObjectName, Object)
function OpsGroupSet:OnAfterAdded(From,Event,To,ObjectName,Object)
local grp = Object:GetGroup()
ExceptionSet:AddGroup(grp, true)
ExceptionSet:AddGroup(grp,true)
end
end
end
-- Optimized event handlers with pre-created data objects
function VehicleSet:OnAfterAdded(From, Event, To, ObjectName, Object)
BASE:T(" TIRESIAS: VEHICLE Object Added: " .. Object:GetName())
function VehicleSet:OnAfterAdded(From,Event,To,ObjectName,Object)
BASE:I("TIRESIAS: VEHCILE Object Added: "..Object:GetName())
if Object and Object:IsAlive() then
Object:SetAIOff()
Object:SetCommandInvisible(true)
Object.Tiresias = vehicle_data
Object.Tiresias = { -- #TIRESIAS.Data
type = "Vehicle",
invisible = true,
AIOff = true,
exception = false,
}
end
end
end
function AAASet:OnAfterAdded(From, Event, To, ObjectName, Object)
local SwitchAAA = self.SwitchAAA
function AAASet:OnAfterAdded(From,Event,To,ObjectName,Object)
if Object and Object:IsAlive() then
BASE:I(" TIRESIAS: AAA Object Added: " .. Object:GetName())
BASE:I("TIRESIAS: AAA Object Added: "..Object:GetName())
Object:OptionEngageRange(EngageRange)
Object:SetCommandInvisible(true)
if SwitchAAA then
Object:SetAIOff()
Object:EnableEmission(false)
end
Object.Tiresias = aaa_data
Object.Tiresias = { -- #TIRESIAS.Data
type = "AAA",
invisible = true,
range = EngageRange,
exception = false,
AIOff = SwitchAAA,
}
end
end
end
function SAMSet:OnAfterAdded(From, Event, To, ObjectName, Object)
function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object)
if Object and Object:IsAlive() then
BASE:T(" TIRESIAS: SAM Object Added: " .. Object:GetName())
BASE:I("TIRESIAS: SAM Object Added: "..Object:GetName())
Object:SetCommandInvisible(true)
Object.Tiresias = sam_data
Object.Tiresias = { -- #TIRESIAS.Data
type = "SAM",
invisible = true,
exception = false,
}
end
end
end
-- Store references
self.VehicleSet = VehicleSet
self.AAASet = AAASet
self.SAMSet = SAMSet
@@ -612,10 +535,13 @@ end
-- @return #TIRESIAS self
function TIRESIAS:onbeforeStatus(From, Event, To)
self:T({From, Event, To})
return self:GetState() ~= " Stopped"
if self:GetState() == "Stopped" then
return false
end
return self
end
--- [INTERNAL] FSM Function - Optimized status processing
--- [INTERNAL] FSM Function
-- @param #TIRESIAS self
-- @param #string From
-- @param #string Event
@@ -623,36 +549,28 @@ end
-- @return #TIRESIAS self
function TIRESIAS:onafterStatus(From, Event, To)
self:T({From, Event, To})
if self.debug then
local count = self.VehicleSet:CountAlive()
local AAAcount = self.AAASet:CountAlive()
local SAMcount = self.SAMSet:CountAlive()
self:I(string.format(" Overall: %d | Vehicles: %d | AAA: %d | SAM: %d" ,
count + AAAcount + SAMcount, count, AAAcount, SAMcount))
local text = string.format("Overall: %d | Vehicles: %d | AAA: %d | SAM: %d",count+AAAcount+SAMcount,count,AAAcount,SAMcount)
self:I(text)
end
self:_InitGroups()
-- Process flight groups more efficiently
local flight_count = self.FlightSet:CountAlive()
if flight_count > 0 then
if self.FlightSet:CountAlive() > 0 then
local Set = self.FlightSet:GetAliveSet()
-- Cache range values outside loop
local helo_range = self.HeloSwitchRange
local plane_range = self.PlaneSwitchRange
for _, _plane in pairs(Set or {}) do
for _,_plane in pairs(Set) do
local plane = _plane -- Wrapper.Group#GROUP
local radius = plane:IsHelicopter() and helo_range or plane_range
self:_SwitchOnGroups(plane, radius)
local radius = self.PlaneSwitchRange
if plane:IsHelicopter() then
radius = self.HeloSwitchRange
end
self:_SwitchOnGroups(_plane,radius)
end
end
if self:GetState() ~= " Stopped" then
if self:GetState() ~= "Stopped" then
self:__Status(self.Interval)
end
return self
end
@@ -665,11 +583,11 @@ end
function TIRESIAS:onafterStop(From, Event, To)
self:T({From, Event, To})
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
-- Clear zone cache on stop to free memory
self._cached_zones = {}
return self
end
-----
---- End
-----
-------------------------------------------------------------------------------------------------------------
--
-- End
--
-------------------------------------------------------------------------------------------------------------

View File

@@ -3153,7 +3153,7 @@ end
-- @param #WAREHOUSE self
-- @return Core.Point#COORDINATE The coordinate of the warehouse.
function WAREHOUSE:GetCoordinate()
return self.warehouse:GetCoord()
return self.warehouse:GetCoordinate()
end
--- Get 3D vector of warehouse static.
@@ -6893,7 +6893,7 @@ function WAREHOUSE:_CheckConquered()
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
local distance=coord:Get2DDistance(unit:GetCoord())
local distance=coord:Get2DDistance(unit:GetCoordinate())
-- Filter only alive groud units. Also check distance again, because the scan routine might give some larger distances.
if unit:IsGround() and unit:IsAlive() and distance <= radius then

View File

@@ -7,8 +7,6 @@
--
-- # Developer Note
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
-- Therefore, this class is considered to be deprecated
--

View File

@@ -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' )
@@ -186,4 +187,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/Beacons.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Point.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Procedure.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/FlightPlan.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Globals.lua' )

View File

@@ -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' )
@@ -115,6 +116,7 @@ __Moose.Include( 'Ops\\Operation.lua' )
__Moose.Include( 'Ops\\FlightControl.lua' )
__Moose.Include( 'Ops\\PlayerRecce.lua' )
__Moose.Include( 'Ops\\EasyGCICAP.lua' )
__Moose.Include( 'Ops\\EasyA2G.lua' )
__Moose.Include( 'AI\\AI_Balancer.lua' )
__Moose.Include( 'AI\\AI_Air.lua' )
@@ -177,4 +179,7 @@ __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( 'Globals.lua' )

View File

@@ -0,0 +1,139 @@
--- **NAVIGATION** - Beacons of the map/theatre.
--
-- **Main Features:**
--
-- * Beacons of the map
--
-- ===
--
-- ## 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 NAVIGATION_Beacons.png
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- 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
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
--
-- ===
--
-- # The BEACONS Concept
--
-- The NAVFIX class has a great concept!
--
-- Bla, bla...
--
-- # Basic Setup
--
-- A new `BEACONS` object can be created with the @{#BEACONS.New}() function.
--
-- local beacons=BEACONS:New("G:\Games\DCS World Testing\Mods\terrains\GermanyColdWar\beacons.lua")
--
-- This is how it works.
--
-- @field #BEACONS
BEACONS = {
ClassName = "BEACONS",
verbose = 0,
beacons = {},
}
--- BEACONS class version.
-- @field #string version
BEACONS.version="0.0.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor(s)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new BECAONS 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
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add marker all beacons on the F10 map.
-- @param #BEACONS self
-- @return #BEACONS self
function BEACONS:MarkerShow()
return self
end
--- Remove markers of all beacons from the F10 map.
-- @param #BEACONS self
-- @return #BEACONS self
function BEACONS:MarkerRemove()
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get text displayed in the F10 marker.
-- @param #BEACONS self
-- @return #string Marker text.
function BEACONS:_GetMarkerText(beacon)
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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,481 @@
--- **NAVIGATION** - Flight Plan.
--
-- **Main Features:**
--
-- * Manage navigation aids
-- * VOR, NDB
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops%20-%20FlightPlan).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Navigation.FlightPlan
-- @image NAVIGATION_FlightPlan.png
--- FLIGHTPLAN class.
-- @type FLIGHTPLAN
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field #table fixes Navigation fixes.
-- @field Core.Pathline#PATHLINE pathline Pathline of the plan.
-- @field Wrapper.Airbase#AIRBASE departureAirbase Departure airbase.
-- @field Wrapper.Airbase#AIRBASE destinationAirbase Destination airbase.
-- @field #number altitudeCruiseMin Minimum cruise altitude in feet MSL.
-- @field #number altitudeCruiseMax Maximum cruise altitude in feet MSL.
-- @extends Core.Pathline#PATHLINE
--- *Life is what happens to us while we are making other plans.* -- Allen Saunders
--
-- ===
--
-- # The FLIGHTPLAN Concept
--
-- This class has a great concept!
--
-- # Basic Setup
--
-- A new `FLIGHTPLAN` object can be created with the @{#FLIGHTPLAN.New}() function.
--
-- myFlightplan=FLIGHTPLAN:New("Plan A")
-- myFleet:SetPortZone(ZonePort1stFleet)
-- myFleet:Start()
--
-- A fleet needs a *port zone*, which is set via the @{#FLIGHTPLAN.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 @{#FLIGHTPLAN.Start}() function. If the fleet is not started, it will not process any requests.
--
-- @field #FLIGHTPLAN
FLIGHTPLAN = {
ClassName = "FLIGHTPLAN",
verbose = 0,
fixes = {}
}
--- Type of flightplan.
-- @type FLIGHTPLAN.Type
-- @field #string IFRH Instrument Flying Rules High Altitude.
-- @field #string IFRL Instrument Flying Rules Low Altitude.
-- @field #string VFR Visual Flight Rules.
FLIGHTPLAN.Type={
IFRH = "IFR High",
IFRL = "IFR Low",
VFR = "VFR",
}
--- FLIGHTPLAN class version.
-- @field #string version
FLIGHTPLAN.version="0.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: How to connect SID, STAR, ENROUTE, TRANSITION, APPROACH. Typical flightplan SID --> ENROUTE --> STAR --> APPROACH
-- TODO: Add approach.
-- DONE: How to handle the FLIGHTGROUP:_LandAtAirBase
-- TODO: Do we always need a holding pattern? https://www.faa.gov/air_traffic/publications/atpubs/aip_html/part2_enr_section_1.5.html#:~:text=If%20no%20holding%20pattern%20is,than%20that%20desired%20by%20ATC.
-- DOEN: Read from MSFS file.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new FLIGHTPLAN instance.
-- @param #FLIGHTPLAN self
-- @param #string Name Name of this flight plan.
-- @return #FLIGHTPLAN self
function FLIGHTPLAN:New(Name)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, PATHLINE:New(Name)) -- #FLIGHTPLAN
-- Set alias.
self.alias=tostring(Name)
-- Set some string id for output to DCS.log file.
self.lid=string.format("FLIGHTPLAN %s | ", self.alias)
--self.pathline=PATHLINE:New(Name)
-- Debug info.
self:I(self.lid..string.format("Created FLIGHTPLAN!"))
return self
end
--- Create a new FLIGHTPLAN instance from another FLIGHTPLAN acting as blue print.
-- The newly created flight plan is deep copied from the given one.
-- @param #FLIGHTPLAN self
-- @param #FLIGHTPLAN FlightPlan Blue print of the flight plan to copy.
-- @return #FLIGHTPLAN self
function FLIGHTPLAN:NewFromFlightPlan(FlightPlan)
self=UTILS.DeepCopy(FlightPlan)
return self
end
--- Create a new FLIGHTPLAN instance from a given file.
-- Currently, the file has to be an MSFS 2020 .pln file as, *e.g.*, exported from [Navigraph](https://navigraph.com/).
--
-- **Note** that the flight plan does only cover the departure, enroute and arrival portions but **not the approach** part!
-- @param #FLIGHTPLAN self
-- @param #string FileName Full path to file.
-- @return #FLIGHTPLAN self
function FLIGHTPLAN:NewFromFile(FileName)
if UTILS.FileExists(FileName) then
self=FLIGHTPLAN._ReadFileMSFS(FileName)
else
error(string.format("ERROR: File not found! File name=%s", tostring(FileName)))
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add navigation fix to the flight plan.
-- @param #FLIGHTPLAN self
-- @param Navigation.Point#NAVPOINT NavFix The nav fix.
-- @return #FLIGHTPLAN self
function FLIGHTPLAN:AddNavFix(NavFix)
table.insert(self.fixes, NavFix)
local point=self:AddPointFromVec3(NavFix.vector:GetVec3(true))
point.navpoint=NavFix
return self
end
--- Set departure airbase.
-- @param #FLIGHTPLAN self
-- @param #string AirbaseName Name of the airbase or AIRBASE object.
-- @return #FLIGHTPLAN self
function FLIGHTPLAN:SetDepartureAirbase(AirbaseName)
self.departureAirbase=AIRBASE:FindByName(AirbaseName)
return self
end
--- Set destination airbase.
-- @param #FLIGHTPLAN self
-- @param #string AirbaseName Name of the airbase or AIRBASE object.
-- @return #FLIGHTPLAN self
function FLIGHTPLAN:SetDestinationAirbase(AirbaseName)
self.destinationAirbase=AIRBASE:FindByName(AirbaseName)
return self
end
--- Set cruise altitude.
-- @param #FLIGHTPLAN self
-- @param #number AltMin Minimum altitude in feet MSL.
-- @param #number AltMax Maximum altitude in feet MSL. Default is `AltMin`.
-- @return #FLIGHTPLAN self
function FLIGHTPLAN:SetCruiseAltitude(AltMin, AltMax)
self.altitudeCruiseMin=AltMin
self.altitudeCruiseMax=AltMax or self.altitudeCruiseMin
return self
end
--- Set cruise speed.
-- @param #FLIGHTPLAN self
-- @param #number SpeedMin Minimum speed in knots.
-- @param #number SpeedMax Maximum speed in knots. Default is `SpeedMin`.
-- @return #FLIGHTPLAN self
function FLIGHTPLAN:SetCruiseSpeed(SpeedMin, SpeedMax)
self.speedCruiseMin=SpeedMin
self.speedCruiseMax=SpeedMax or self.speedCruiseMin
return self
end
--- Get the name of this flight plan.
-- @param #FLIGHTPLAN self
-- @return #string The name.
function FLIGHTPLAN:GetName()
return self.alias
end
--- Get cruise altitude. This returns a random altitude between the set min/max cruise altitudes.
-- @param #FLIGHTPLAN self
-- @return #number Cruise altitude in feet MSL.
function FLIGHTPLAN:GetCruiseAltitude()
local alt=10000
if self.altitudeCruiseMin and self.altitudeCruiseMax then
alt=math.random(self.altitudeCruiseMin, self.altitudeCruiseMax)
elseif self.altitudeCruiseMin then
alt=self.altitudeCruiseMin
elseif self.altitudeCruiseMax then
alt=self.altitudeCruiseMax
end
return alt
end
--- Get cruise speed. This returns a random speed between the set min/max cruise speeds.
-- @param #FLIGHTPLAN self
-- @return #number Cruise speed in knots.
function FLIGHTPLAN:GetCruiseSpeed()
local speed=250
if self.speedCruiseMin and self.speedCruiseMax then
speed=math.random(self.speedCruiseMin, self.speedCruiseMax)
elseif self.speedCruiseMin then
speed=self.speedCruiseMin
elseif self.altitudeCruiseMax then
speed=self.speedCruiseMax
end
return speed
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Read flight plan from a given MSFS 2020 .plt file.
-- @param #string FileName Name of the file.
-- @return #FLIGHTPLAN The flight plan.
function FLIGHTPLAN._ReadFileMSFS(FileName)
local function readfile(filename)
local lines = {}
-- Open file in read binary mode.
local file=assert(io.open(filename, "rb"), string.format("File not found! File name = %s", tostring(filename)))
for line in file:lines() do
lines[#lines+1] = line
end
-- Close file.
file:close()
-- Return data
return lines
end
--- This function returns an XML element, i.e. the string between <...> and </...>.
local function getXMLelement(line)
local element=string.match(line, ">(.+)<")
return element
end
--- This function returns Latitude and Longitude
local function getLatLong(line)
local latlong=getXMLelement(line)
-- The format is "N41° 38' 20.00",E41° 33' 19.00",+000000.00" so we still need to process that.
local lat,long=string.match(latlong, "(.+),(.+),")
return lat,long
end
-- Read data from file.
local data=readfile(FileName)
local flightplan={}
local waypoints={}
local wp=nil
local gotwaypoint=false
for i,line in pairs(data) do
--print(line)
-- Title
if string.find(line, "<Title>") then
flightplan.title=getXMLelement(line)
end
-- Departure ICAO
if string.find(line, "<DepartureID>") then
flightplan.departureICAO=getXMLelement(line)
end
-- Destination ICAO
if string.find(line, "<DestinationID>") then
flightplan.destinationICAO=getXMLelement(line)
end
-- FPType
if string.find(line, "<FPType>") then
flightplan.plantype=getXMLelement(line)
end
-- Route type
if string.find(line, "<RouteType>") then
flightplan.routetype=getXMLelement(line)
end
-- Cruise alt in feet
if string.find(line, "<CruisingAlt>") then
flightplan.altCruise=getXMLelement(line)
end
-- Departure LLA
if string.find(line, "<DepartureLLA>") then
local lat,long=getLatLong(line)
end
-- Destination LLA
if string.find(line, "<DestinationLLA>") then
local lat,long=getLatLong(line)
end
-- Departure Name
if string.find(line, "<DepartureName>") then
local DepartureName=getXMLelement(line)
end
-- DestinationName
if string.find(line, "<DestinationName>") then
local DestinationName=getXMLelement(line)
end
---
-- Waypoint stuff
---
-- New waypoint starts.
if string.find(line, "ATCWaypoint id") then
--Get string inside quotes " and ".
local wpid=string.match(line, [["(.+)"]])
-- Create a new wp table.
wp={}
-- Set waypoint name.
wp.name=wpid
end
-- Waypoint info ends.
if string.find(line, "</ATCWaypoint>") then
-- This is the end of the waypoint.
-- Add info to waypoints table.
table.insert(waypoints, wp)
-- Set waypoint to nil. We create an empty table if the next wp starts.
wp=nil
end
-- Waypoint type (Airport, Intersection, NDB, VORTAC)
if string.find(line, "<ATCWaypointType>") then
local wptype=getXMLelement(line)
wp.type=wptype
end
-- Waypoint position.
if string.find(line, "<WorldPosition>") then
wp.lat, wp.long=getLatLong(line)
end
-- Runway should exist for initial and final WP if it is an airport.
if string.find(line, "RunwayNumberFP") then
wp.runway=getXMLelement(line)
end
-- Runway designator: LEFT, RIGHT, CENTER
if string.find(line, "RunwayDesignatorFP") then
wp.runwayDesignator=getXMLelement(line)
end
-- Segment is Departure
if string.find(line, "<DepartureFP>") then
wp.segment="Departure"
end
-- Segment is Arrival
if string.find(line, "<ArrivalFP>") then
wp.segment="Arrival"
end
-- Segment is Enroute
if string.find(line, "<ATCAirway>") then
wp.segment="Enroute"
end
-- Approach type: VORDME, LOCALIZER
if string.find(line, "ApproachTypeFP") then
flightplan.approachtype=getXMLelement(line)
end
-- Approach type suffic: Z
if string.find(line, "SuffixFP") then
local SuffixFP=getXMLelement(line)
end
end
for key, value in pairs(flightplan) do
env.info(string.format("Flightplan %s=%s", key, tostring(value)))
end
env.info(string.format("Number of waypoints=%d", #waypoints))
for i,wp in pairs(waypoints) do
env.info(string.format("Waypoint name=%s type=%s segment=%s runway=%s lat=%s long=%s", wp.name, wp.type, tostring(wp.segment), tostring(wp.runway)..tostring(wp.runwayDesignator or ""), wp.lat, wp.long))
end
-- Create a new flightplan.
local fp=FLIGHTPLAN:New(flightplan.title)
-- Set cruise altitude.
fp:SetCruiseAltitude(flightplan.altCruise)
-- Set departure and destination airports.
fp:SetDepartureAirbase(flightplan.departureICAO)
fp:SetDestinationAirbase(flightplan.destinationICAO)
--TODO: Remove first and last waypoint if they are identical to the departure/destination airport!
for i,wp in pairs(waypoints) do
-- Create a navpoint.
local navpoint=NAVPOINT:NewFromLLDMS(wp.name, wp.type, wp.lat, wp.long)
navpoint:SetAltMin(flightplan.altCruise)
-- Add point to flightplan.
-- TODO: section departure, enroute, arrival.
fp:AddNavFix(navpoint)
end
return fp
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,587 @@
--- **NAVIGATION** - Navigation Airspace Points, Fixes and Aids.
--
-- **Main Features:**
--
-- * Stuff
-- * More Stuff
--
-- ===
--
-- ## 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 NAVIGATION_Point.png
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- 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.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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 compusory 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.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,333 @@
--- **NAVIGATION** - Prodedures for Departure (*e.g.* SID), Enroute, Arrival (*e.g.* STAR) and Approach.
--
-- **Main Features:**
--
-- * Stuff
-- * More Stuff
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Template).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Navigation.Procedure
-- @image NAVIGATION_Procedure.png
--- APPROACH class.
-- @type APPROACH
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field #string apptype Approach type (ILS, VOR, LOC).
-- @field Wrapper.Airbase#AIRBASE airbase Airbase of this approach.
-- @field Wrapper.Airbase#AIRBASE.Runway runway Runway of this approach.
-- @field Navigation.Point#NAVAID navaid Primary navigation aid.
-- @field #number wpcounter Running number counting the waypoints to generate its UID.
-- @list <#APPROACH.Waypoint> path Path of approach consisting of waypoints.
-- @extends Core.Base#BASE
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
--
-- ===
--
-- # The APPROACH Concept
--
-- A typical approach has (up to) four segments. It starts with the initial approach segment, followed by the intermediate approach segment, followed
-- by the final approach segment. In case something goes wrong during the final approach, the missed approach segment kicks in.
--
-- The initial approach segment starts at the initial approach fix (IAF). The segment can contain multiple other fixes, that need to be passed.
-- Note, that an approach procedure can have more than one intitial approach segment and IAF.
--
-- The intermediate approach segment starts at the intermediate fix (IF). The intermediate approach segment blends the initial approach segment into the final approach segment.
-- It is the segment in which aircraft configuration, speed, and positioning adjustments are made for entry into the final approach segment.
--
--
-- https://en.wikipedia.org/wiki/Visual_approach
-- https://en.wikipedia.org/wiki/Instrument_approach
--
-- # Basic Setup
--
-- A new `APPROACH` object can be created with the @{#APPROACH.New}() function.
--
-- myTemplate=APPROACH:New()
-- myTemplate:SetXYZ(X, Y, Z)
--
-- This is how it works.
--
-- @field #APPROACH
APPROACH = {
ClassName = "APPROACH",
verbose = 0,
wpcounter = 0,
}
--- Type of approach.
-- @type APPROACH.Type
-- @field #string VFR Visual Flight Rules.
-- @field #string VOR VOR
-- @field #string NDB NDB
APPROACH.Type={
VFR="VFR",
VOR="VOR",
ILS="ILS",
}
--- Setments of approach.
-- @type APPROACH.Segment
-- @field #string INITIAL Initial approach segment.
-- @field #string INTERMEDIATE Intermediate approach segment.
-- @field #string FINAL Final approach segment.
-- @field #string MISSED Missed approach segment.
APPROACH.Segment={
INITIAL="Initial",
INTERMEDIATE="Intermediate",
FINAL="Final",
MISSED="Missed",
}
--- Waypoint of the approach.
-- @type APPROACH.Waypoint
-- @field #number uid Unique ID of the point.
-- @field #string segment The segment this point belongs to.
-- @field Navigation.Point#NAVFIX navfix The navigation fix that determines the coordinates of this point.
--- APPROACH class version.
-- @field #string version
APPROACH.version="0.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-- Initial approach segment --> Intermediate approach segment: starts at IF --> Final approach segment
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new APPROACH class instance.
-- @param #APPROACH self
-- @param #string Type Type of approach (ILS, VOR, LOC).
-- @param Wrapper.Airbase#AIRBASE Airbase The airbase or name of the airbase.
-- @param Wrapper.Airbase#AIRBASE.Runway Runway The runway or name of the runway.
-- @return #APPROACH self
function APPROACH:New(Type, Airbase, Runway)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #APPROACH
-- Set approach type.
-- TODO: Check if this is a valid/known approach type.
self.apptype=Type
if type(Airbase)=="string" then
self.airbase=AIRBASE:FindByName(Airbase)
else
self.airbase=Airbase
end
if type(Runway)=="string" then
self.runway=self.airbase:GetRunwayByName(Runway)
else
self.runway=Runway
end
-- Debug info.
self:I("Created new approach for airbase %s: type=%s, runway=%s", self.airbase:GetName(), self.apptype, self.runway.name)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set the primary navigation aid used in the approach.
-- @param #APPROACH self
-- @param Navigation.Point#NAVAID NavAid The NAVAID.
-- @return #APPROACH self
function APPROACH:SetNavAid(NavAid)
self.navaid=NavAid
return self
end
--- Add a waypoint to the path of the approach.
-- @param #APPROACH self
-- @param Navigation.Point#NAVFIX NavFix The navigation fix.
-- @param #string Segment The approach segment this fix belongs to.
-- @return #APPROACH.Waypoint The waypoint data table.
function APPROACH:AddNavFix(NavFix, Segment)
self.wpcounter=self.wpcounter+1
local point={} --#APPROACH.Waypoint
point.uid=self.wpcounter
point.segment=Segment
point.navfix=NavFix
table.insert(self.path, point)
return point
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Add private functions here.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- DEPARTURE class.
-- @type DEPARTURE
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field #string apptype DEPARTURE type (ILS, VOR, LOC).
-- @field Wrapper.Airbase#AIRBASE airbase Airbase of this DEPARTURE.
-- @field Wrapper.Airbase#AIRBASE.Runway runway Runway of this DEPARTURE.
-- @field Navigation.Point#NAVAID navaid Primary navigation aid.
-- @field #number wpcounter Running number counting the waypoints to generate its UID.
-- @list <#DEPARTURE.Waypoint> path Path of DEPARTURE consisting of waypoints.
-- @extends Core.Base#BASE
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
--
-- ===
--
-- # The DEPARTURE Concept
--
-- Bla.
--
-- @field #DEPARTURE
DEPARTURE = {
ClassName = "DEPARTURE",
verbose = 0,
wpcounter = 0,
}
--- Type of DEPARTURE.
-- @type DEPARTURE.Type
-- @field #string VOR VOR
-- @field #string NDB NDB
DEPARTURE.Type={
VOR="VOR",
ILS="ILS",
}
--- Setments of DEPARTURE.
-- @type DEPARTURE.Segment
-- @field #string INITIAL Initial DEPARTURE segment.
-- @field #string INTERMEDIATE Intermediate DEPARTURE segment.
-- @field #string FINAL Final DEPARTURE segment.
-- @field #string MISSED Missed DEPARTURE segment.
DEPARTURE.Segment={
INITIAL="Initial",
INTERMEDIATE="Intermediate",
FINAL="Final",
MISSED="Missed",
}
--- Waypoint of the DEPARTURE.
-- @type DEPARTURE.Waypoint
-- @field #number uid Unique ID of the point.
-- @field #string segment The segment this point belongs to.
-- @field Navigation.Point#NAVFIX navfix The navigation fix that determines the coordinates of this point.
--- DEPARTURE class version.
-- @field #string version
DEPARTURE.version="0.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-- Initial DEPARTURE segment --> Intermediate DEPARTURE segment: starts at IF --> Final DEPARTURE segment
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new DEPARTURE class instance.
-- @param #DEPARTURE self
-- @param Wrapper.Airbase#AIRBASE Airbase The airbase or name of the airbase.
-- @param Wrapper.Airbase#AIRBASE.Runway Runway The runway or name of the runway.
-- @param #string Type Type of DEPARTURE (ILS, VOR, LOC).
-- @return #DEPARTURE self
function DEPARTURE:New(Airbase, Runway)
-- Inherit everything from BASE class.
self=BASE:Inherit(self, BASE:New()) -- #DEPARTURE
if type(Airbase)=="string" then
self.airbase=AIRBASE:FindByName(Airbase)
else
self.airbase=Airbase
end
if type(Runway)=="string" then
self.runway=self.airbase:GetRunwayByName(Runway)
else
self.runway=Runway
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set the primary navigation aid used in the DEPARTURE.
-- @param #DEPARTURE self
-- @param Navigation.Point#NAVAID NavAid The NAVAID.
-- @return #DEPARTURE self
function DEPARTURE:SetNavAid(NavAid)
self.navaid=NavAid
return self
end
--- Add a waypoint to the path of the DEPARTURE.
-- @param #DEPARTURE self
-- @param Navigation.Point#NAVFIX NavFix The navigation fix.
-- @param #string Segment The DEPARTURE segment this fix belongs to.
-- @return #DEPARTURE.Waypoint The waypoint data.
function DEPARTURE:AddWaypoint(NavFix, Segment)
self.wpcounter=self.wpcounter+1
local point={} --#DEPARTURE.Waypoint
point.uid=self.wpcounter
point.segment=Segment
point.navfix=NavFix
table.insert(self.path, point)
return point
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Add DEPARTURE private functions here.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,108 @@
--- **NAVIGATION** - Template.
--
-- **Main Features:**
--
-- * Stuff
-- * More Stuff
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Template).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Navigation.Template
-- @image NAVIGATION_Template.png
--- TEMPLATE class.
-- @type TEMPLATE
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @extends Core.Base#BASE
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
--
-- ===
--
-- # The TEMPLATE Concept
--
-- The TEMPLATE class has a great concept!
--
-- # Basic Setup
--
-- A new `TEMPLATE` object can be created with the @{#TEMPLATE.New}() function.
--
-- myTemplate=TEMPLATE:New()
-- myTemplate:SetXYZ(X, Y, Z)
--
-- This is how it works.
--
-- @field #TEMPLATE
TEMPLATE = {
ClassName = "TEMPLATE",
verbose = 0,
}
--- Type of navaid
-- @type TEMPLATE.Type
-- @field #string VOR VOR
-- @field #string NDB NDB
TEMPLATE.TYPE={
VOR="VOR",
NDB="NDB",
}
--- TEMPLATE class version.
-- @field #string version
TEMPLATE.version="0.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new TEMPLATE class instance.
-- @param #TEMPLATE self
-- @return #TEMPLATE self
function TEMPLATE:New()
-- Inherit everything from SCENERY class.
self=BASE:Inherit(self, BASE:New()) -- #TEMPLATE
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set frequency.
-- @param #TEMPLATE self
-- @param #number Frequency Frequency in Hz.
-- @return #TEMPLATE self
function TEMPLATE:SetFrequency(Frequency)
self.frequency=Frequency
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -2798,7 +2798,7 @@ function ATIS:onafterBroadcast( From, Event, To )
end
_RUNACT = subtitle
--alltext = alltext .. ";\n" .. subtitle
alltext = alltext .. ";\n" .. subtitle
-- Runway length.
if self.rwylength then

View File

@@ -159,8 +159,6 @@ 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
@@ -189,14 +187,13 @@ AIRWING = {
--- AIRWING class version.
-- @field #string version
AIRWING.version="0.9.7"
AIRWING.version="0.9.6"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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).
@@ -810,30 +807,21 @@ 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 and self.markpoints then -- sometimes there's a direct call from #OPSGROUP
if 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)
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
--- 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. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Default 10-15 NM away from the location of the airwing.
-- @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.
@@ -842,16 +830,14 @@ 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: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
@@ -861,7 +847,7 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL
if self.markpoints then
patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll()
self:UpdatePatrolPointMarker(patrolpoint)
AIRWING.UpdatePatrolPointMarker(patrolpoint)
end
return patrolpoint
@@ -869,7 +855,7 @@ end
--- Add a patrol Point for CAP missions.
-- @param #AIRWING self
-- @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 Core.Point#COORDINATE Coordinate Coordinate of the patrol point.
-- @param #number Altitude Orbit altitude in feet.
-- @param #number Speed Orbit speed in knots.
-- @param #number Heading Heading in degrees.
@@ -886,7 +872,7 @@ end
--- Add a patrol Point for RECON missions.
-- @param #AIRWING self
-- @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 Core.Point#COORDINATE Coordinate Coordinate of the patrol point.
-- @param #number Altitude Orbit altitude in feet.
-- @param #number Speed Orbit speed in knots.
-- @param #number Heading Heading in degrees.
@@ -903,7 +889,7 @@ end
--- Add a patrol Point for TANKER missions.
-- @param #AIRWING self
-- @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 Core.Point#COORDINATE Coordinate Coordinate of the patrol point.
-- @param #number Altitude Orbit altitude in feet.
-- @param #number Speed Orbit speed in knots.
-- @param #number Heading Heading in degrees.
@@ -921,7 +907,7 @@ end
--- Add a patrol Point for AWACS missions.
-- @param #AIRWING self
-- @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 Core.Point#COORDINATE Coordinate Coordinate of the patrol point.
-- @param #number Altitude Orbit altitude in feet.
-- @param #number Speed Orbit speed in knots.
-- @param #number Heading Heading in degrees.
@@ -988,46 +974,6 @@ function AIRWING:SetTakeoffAir()
return self
end
--- Set the aircraft of the AirWing to land straight in.
-- @param #AIRWING self
-- @return #FLIGHTGROUP self
function AIRWING:SetLandingStraightIn()
self.OptionLandingStraightIn = true
return self
end
--- Set the aircraft of the AirWing to land in pairs for groups > 1 aircraft.
-- @param #AIRWING self
-- @return #AIRWING self
function AIRWING:SetLandingForcePair()
self.OptionLandingForcePair = true
return self
end
--- Set the aircraft of the AirWing to NOT land in pairs.
-- @param #AIRWING self
-- @return #AIRWING self
function AIRWING:SetLandingRestrictPair()
self.OptionLandingRestrictPair = true
return self
end
--- Set the aircraft of the AirWing to land after overhead break.
-- @param #AIRWING self
-- @return #AIRWING self
function AIRWING:SetLandingOverheadBreak()
self.OptionLandingOverheadBreak = true
return self
end
--- [Helicopter] Set the aircraft of the AirWing to prefer vertical takeoff and landing.
-- @param #AIRWING self
-- @return #AIRWING self
function AIRWING:SetOptionPreferVerticalLanding()
self.OptionPreferVerticalLanding = true
return self
end
--- Set despawn after landing. Aircraft will be despawned after the landing event.
-- Can help to avoid DCS AI taxiing issues.
-- @param #AIRWING self
@@ -1190,10 +1136,6 @@ 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
@@ -1253,7 +1195,7 @@ function AIRWING:CheckCAP()
patrol.noccupied=patrol.noccupied+1
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
self:AddMission(missionCAP)
@@ -1305,7 +1247,7 @@ function AIRWING:CheckRECON()
patrol.noccupied=patrol.noccupied+1
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
self:AddMission(missionRECON)
@@ -1350,7 +1292,7 @@ function AIRWING:CheckTANKER()
patrol.noccupied=patrol.noccupied+1
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
self:AddMission(mission)
@@ -1369,7 +1311,7 @@ function AIRWING:CheckTANKER()
patrol.noccupied=patrol.noccupied+1
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
self:AddMission(mission)
@@ -1407,7 +1349,7 @@ function AIRWING:CheckAWACS()
patrol.noccupied=patrol.noccupied+1
if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end
self:AddMission(mission)
@@ -1523,20 +1465,6 @@ function AIRWING:onafterFlightOnMission(From, Event, To, FlightGroup, Mission)
if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then
self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission)
end
-- Landing Options
if self.OptionLandingForcePair then
FlightGroup:SetOptionLandingForcePair()
elseif self.OptionLandingOverheadBreak then
FlightGroup:SetOptionLandingOverheadBreak()
elseif self.OptionLandingRestrictPair then
FlightGroup:SetOptionLandingRestrictPair()
elseif self.OptionLandingStraightIn then
FlightGroup:SetOptionLandingStraightIn()
end
-- Landing Options Helo
if self.OptionPreferVerticalLanding then
FlightGroup:SetOptionPreferVertical()
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -397,7 +397,6 @@ AUFTRAG = {
conditionPush = {},
conditionSuccessSet = false,
conditionFailureSet = false,
repeatDelay = 1,
}
--- Global mission counter.
@@ -1429,7 +1428,7 @@ function AUFTRAG:NewCAP(ZoneCAP, Altitude, Speed, Coordinate, Heading, Leg, Targ
mission:_SetLogID()
-- DCS task parameters:
mission.engageZone=ZoneCAP or Coordinate
mission.engageZone=ZoneCAP
mission.engageTargetTypes=TargetTypes or {"Air"}
-- Mission options:
@@ -1716,45 +1715,9 @@ function AUFTRAG:NewSEAD(Target, Altitude)
return mission
end
--- **[AIR]** Create a SEAD in Zone mission.
-- @param #AUFTRAG self
-- @param Core.Zone#ZONE TargetZone The target zone to attack.
-- @param #number Altitude Engage altitude in feet. Default 25000 ft.
-- @param #table TargetTypes Table of string of DCS known target types, defaults to {"Air Defence"}. See [DCS Target Attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes)
-- @param #number Duration Engage this much time when the AUFTRAG starts executing.
-- @return #AUFTRAG self
function AUFTRAG:NewSEADInZone(TargetZone, Altitude, TargetTypes, Duration)
local mission=AUFTRAG:New(AUFTRAG.Type.SEAD)
--mission:_TargetFromObject(TargetZone)
-- DCS Task options:
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
mission.engageZone = TargetZone
mission.engageTargetTypes = TargetTypes or {"Air Defence"}
-- Mission options:
mission.missionTask=ENUMS.MissionTask.SEAD
mission.missionAltitude=mission.engageAltitude
mission.missionFraction=0.2
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
mission:SetDuration(Duration or 1800)
return mission
end
--- **[AIR]** Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate.
-- @param #AUFTRAG self
-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object.
-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object.
-- @param #number Altitude Engage altitude in feet. Default 2000 ft.
-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options.
-- @return #AUFTRAG self
@@ -1786,12 +1749,11 @@ end
--- **[AIR]** Create a BOMBING mission. Flight will drop bombs a specified coordinate.
-- See [DCS task bombing](https://wiki.hoggitworld.com/view/DCS_task_bombing).
-- @param #AUFTRAG self
-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object.
-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object.
-- @param #number Altitude Engage altitude in feet. Default 25000 ft.
-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options.
-- @param #boolean Divebomb If true, use a dive bombing attack approach.
-- @return #AUFTRAG self
function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType, Divebomb)
function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType)
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING)
@@ -1808,7 +1770,6 @@ function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType, Divebomb)
mission.missionFraction=0.5
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.NoReaction -- No reaction is better.
mission.optionDivebomb = Divebomb or nil
-- Evaluate result after 5 min. We might need time until the bombs have dropped and targets have been detroyed.
mission.dTevaluate=5*60
@@ -3005,16 +2966,6 @@ function AUFTRAG:SetRepeat(Nrepeat)
return self
end
--- **[LEGION, COMMANDER, CHIEF]** Set the repeat delay in seconds after a mission is successful/failed. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, FLEET) or higher level.
-- @param #AUFTRAG self
-- @param #number Nrepeat Repeat delay in seconds. Default 1.
-- @return #AUFTRAG self
function AUFTRAG:SetRepeatDelay(RepeatDelay)
self.repeatDelay = RepeatDelay
return self
end
--- **[LEGION, COMMANDER, CHIEF]** Set how many times the mission is repeated if it fails. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, FLEET) or higher level.
-- @param #AUFTRAG self
-- @param #number Nrepeat Number of repeats. Default 0.
@@ -4814,8 +4765,6 @@ end
-- @return #boolean If `true`, all groups are done with the mission.
function AUFTRAG:CheckGroupsDone()
local fsmState = self:GetState()
-- Check status of all OPS groups.
for groupname,data in pairs(self.groupdata) do
local groupdata=data --#AUFTRAG.GroupData
@@ -4874,11 +4823,6 @@ function AUFTRAG:CheckGroupsDone()
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()))
return false
end
return true
end
@@ -5216,7 +5160,7 @@ function AUFTRAG:onafterSuccess(From, Event, To)
-- Repeat mission.
self:T(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N))
self:__Repeat(self.repeatDelay)
self:Repeat()
else
@@ -5258,7 +5202,7 @@ function AUFTRAG:onafterFailed(From, Event, To)
-- Repeat mission.
self:T(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N))
self:__Repeat(self.repeatDelay)
self:Repeat()
else
@@ -6164,12 +6108,9 @@ function AUFTRAG:GetDCSMissionTask()
-- BOMBING Mission --
---------------------
local coords = self.engageTarget:GetCoordinates()
for _, coord in pairs(coords) do
local DCStask = CONTROLLABLE.TaskBombing(nil, coord:GetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, self.optionDivebomb)
local DCStask=CONTROLLABLE.TaskBombing(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, Divebomb)
table.insert(DCStasks, DCStask)
end
elseif self.type==AUFTRAG.Type.STRAFING then
@@ -6207,15 +6148,7 @@ function AUFTRAG:GetDCSMissionTask()
-- CAP Mission --
-----------------
local Vec2 = self.engageZone:GetVec2()
local Radius
if self.engageZone:IsInstanceOf("COORDINATE") then
Radius = UTILS.NMToMeters(20)
else
Radius = self.engageZone:GetRadius()
end
local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil, Vec2, Radius, self.engageTargetTypes, Priority)
local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil, self.engageZone:GetVec2(), self.engageZone:GetRadius(), self.engageTargetTypes, Priority)
table.insert(self.enrouteTasks, DCStask)
@@ -6370,46 +6303,17 @@ function AUFTRAG:GetDCSMissionTask()
--local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil, self.TargetType)
--table.insert(self.enrouteTasks, DCStask)
if self.engageZone then
--local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil, self.engageTargetTypes)
--table.insert(self.enrouteTasks, DCStask)
self.engageZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
local ScanUnitSet = self.engageZone:GetScannedSetUnit()
local SeadUnitSet = SET_UNIT:New()
for _,_unit in pairs (ScanUnitSet.Set) do
local unit = _unit -- Wrapper.Unit#UNTI
if unit and unit:IsAlive() and unit:HasSEAD() then
self:T("Adding UNIT for SEAD: "..unit:GetName())
local task = CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318)
table.insert(DCStasks, task)
SeadUnitSet:AddUnit(unit)
end
end
self.engageTarget = TARGET:New(SeadUnitSet)
--local OrbitTask = CONTROLLABLE.TaskOrbitCircle(nil,self.engageAltitude,self.missionSpeed,self.engageZone:GetCoordinate())
--local Point = self.engageZone:GetVec2()
--local OrbitTask = CONTROLLABLE.TaskOrbitCircleAtVec2(nil,Point,self.engageAltitude,self.missionSpeed)
--table.insert(DCStasks, OrbitTask)
else
self:_GetDCSAttackTask(self.engageTarget, DCStasks)
end
elseif self.type==AUFTRAG.Type.STRIKE then
--------------------
-- STRIKE Mission --
--------------------
local coords = self.engageTarget:GetCoordinates()
for _, coord in pairs(coords) do
local DCStask=CONTROLLABLE.TaskAttackMapObject(nil, coord:GetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType)
local DCStask=CONTROLLABLE.TaskAttackMapObject(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType)
table.insert(DCStasks, DCStask)
end
elseif self.type==AUFTRAG.Type.TANKER or self.type==AUFTRAG.Type.RECOVERYTANKER then

View File

@@ -17,7 +17,7 @@
-- ===
--
-- ### Author: **applevangelist**
-- @date Last Update July 2025
-- @date Last Update Jan 2025
-- @module Ops.AWACS
-- @image OPS_AWACS.jpg
@@ -237,7 +237,7 @@ do
-- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm.
-- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25)
-- -- Set up SRS on port 5010 - change the below to your path and port
-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio","female","en-GB",5010)
-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010)
-- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON"
-- testawacs:SetRejectionZone(ZONE:FindByName("Red Border"))
-- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS.
@@ -255,7 +255,7 @@ do
-- -- The CAP station zone is called "Fremont". We will be on 255 AM. Note the Orbit Zone is given as *nil* in the `New()`-Statement
-- local testawacs = AWACS:New("GCI Senaki",AwacsAW,"blue",AIRBASE.Caucasus.Senaki_Kolkhi,nil,ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM )
-- -- Set up SRS on port 5010 - change the below to your path and port
-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio","female","en-GB",5010)
-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010)
-- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON"
-- testawacs:SetRejectionZone(ZONE:FindByName("Red Border"))
-- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS.
@@ -509,7 +509,7 @@ do
-- @field #AWACS
AWACS = {
ClassName = "AWACS", -- #string
version = "0.2.72", -- #string
version = "0.2.71", -- #string
lid = "", -- #string
coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string
@@ -1123,7 +1123,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
self.EscortMissionReplacement = {}
-- SRS
self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.Gender = "female"
self.Culture = "en-GB"
self.Voice = nil
@@ -1242,8 +1242,6 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
self:AddTransition("*", "Intercept", "*")
self:AddTransition("*", "InterceptSuccess", "*")
self:AddTransition("*", "InterceptFailure", "*")
self:AddTransition("*", "VIDSuccess", "*")
self:AddTransition("*", "VIDFailure", "*")
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
@@ -1367,39 +1365,19 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
-- @param #string To To state.
--- On After "InterceptSuccess" event. Intercept successful.
-- @function [parent=#AWACS] OnAfterInterceptSuccess
-- @function [parent=#AWACS] OnAfterIntercept
-- @param #AWACS self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On After "InterceptFailure" event. Intercept failure.
-- @function [parent=#AWACS] OnAfterInterceptFailure
-- @function [parent=#AWACS] OnAfterIntercept
-- @param #AWACS self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On After "VIDSuccess" event. Intercept successful.
-- @function [parent=#AWACS] OnAfterVIDSuccess
-- @param #AWACS self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number GID Managed group ID (Player)
-- @param Wrapper.Group#GROUP Group (Player) Group done the VID
-- @param #AWACS.ManagedContact Contact The contact that was VID'd
--- On After "VIDFailure" event. Intercept failure.
-- @function [parent=#AWACS] OnAfterVIDFailure
-- @param #AWACS self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number GID Managed group ID (Player)
-- @param Wrapper.Group#GROUP Group (Player) Group done the VID
-- @param #AWACS.ManagedContact Contact The contact that was VID'd
return self
end
@@ -2113,7 +2091,7 @@ end
--- [User] Set AWACS SRS TTS details - see @{Sound.SRS} for details. `SetSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
-- @param #AWACS self
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone"
-- @param #string Gender Defaults to "male"
-- @param #string Culture Defaults to "en-US"
-- @param #number Port Defaults to 5002
@@ -2126,7 +2104,7 @@ end
-- @return #AWACS self
function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend)
self:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.Gender = Gender or MSRS.gender or "male"
self.Culture = Culture or MSRS.culture or "en-US"
self.Port = Port or MSRS.port or 5002
@@ -3285,14 +3263,12 @@ function AWACS:_VID(Group,Declaration)
local vidpos = self.gettext:GetEntry("VIDPOS",self.locale)
text = string.format(vidpos,Callsign,self.callsigntxt, Declaration)
self:T(text)
self:__VIDSuccess(3,GID,group,cluster)
else
-- too far away
self:T("Contact VID not close enough")
local vidneg = self.gettext:GetEntry("VIDNEG",self.locale)
text = string.format(vidneg,Callsign,self.callsigntxt)
self:T(text)
self:__VIDFailure(3,GID,group,cluster)
end
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true)
end

View File

@@ -601,6 +601,79 @@ function BRIGADE:onafterStatus(From, Event, To)
self:I(self.lid..text)
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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -31,7 +31,7 @@
-- @image OPS_CSAR.jpg
---
-- Last Update July 2025
-- Last Update Jan 2025
-------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@@ -130,7 +130,7 @@
-- ## 2.2 SRS Features and Other Features
--
-- mycsar.useSRS = false -- Set true to use FF\'s SRS integration
-- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\ExternalAudio\\" -- adjust your own path in your SRS installation -- server(!)
-- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!)
-- mycsar.SRSchannel = 300 -- radio channel
-- mycsar.SRSModulation = radio.modulation.AM -- modulation
-- mycsar.SRSport = 5002 -- and SRS Server port
@@ -263,7 +263,6 @@ CSAR = {
rescuedpilots = 0,
limitmaxdownedpilots = true,
maxdownedpilots = 10,
useFIFOLimitReplacement = false, -- If true, it will remove the oldest downed pilot when a new one is added, if the limit is reached.
allheligroupset = nil,
topmenuname = "CSAR",
ADFRadioPwr = 1000,
@@ -314,7 +313,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31
--- CSAR class version.
-- @field #string version
CSAR.version="1.0.33"
CSAR.version="1.0.30"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -469,7 +468,7 @@ function CSAR:New(Coalition, Template, Alias)
-- added 1.0.15
self.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane
self.ADFRadioPwr = 500
self.ADFRadioPwr = 1000
-- added 1.0.16
self.PilotWeight = 80
@@ -481,7 +480,7 @@ function CSAR:New(Coalition, Template, Alias)
-- for this to work you need to de-sanitize your mission environment in <DCS root>\Scripts\MissionScripting.lua
-- needs SRS => 1.9.6 to work (works on the *server* side)
self.useSRS = false -- Use FF\'s SRS integration
self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- adjust your own path in your server(!)
self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone" -- adjust your own path in your server(!)
self.SRSchannel = 300 -- radio channel
self.SRSModulation = radio.modulation.AM -- modulation
self.SRSport = 5002 -- port
@@ -1146,6 +1145,17 @@ function CSAR:_EventHandler(EventData)
return self
end
-- limit no of pilots in the field.
if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then
self:T("Maxed Downed Pilot!")
return self
end
-- TODO: Over water check --- EVENTS.LandingAfterEjection NOT triggered by DCS, so handle csarUsePara = true case
-- might create dual pilots in edge cases
local wetfeet = false
local initdcscoord = nil
local initcoord = nil
@@ -1159,36 +1169,6 @@ function CSAR:_EventHandler(EventData)
self:T({initdcscoord})
end
-- Remove downed pilot if already exists to replace with new one.
if _event.IniPlayerName then
local PilotTable = self.downedPilots --#CSAR.DownedPilot
local _foundPilot = nil
for _,_pilot in pairs(PilotTable) do
if _pilot.player == _event.IniPlayerName and _pilot.alive == true then
_foundPilot = _pilot
break
end
end
if _foundPilot then
self:T("Downed pilot already exists!")
_foundPilot.group:Destroy(false)
self:_RemoveNameFromDownedPilots(_foundPilot.name)
self:_CheckDownedPilotTable()
end
end
-- limit no of pilots in the field.
if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then
self:T("Maxed Downed Pilot!")
return self
end
-- TODO: Over water check --- EVENTS.LandingAfterEjection NOT triggered by DCS, so handle csarUsePara = true case
-- might create dual pilots in edge cases
local wetfeet = false
--local surface = _unit:GetCoordinate():GetSurfaceType()
local surface = initcoord:GetSurfaceType()
@@ -1199,7 +1179,7 @@ function CSAR:_EventHandler(EventData)
-- all checks passed, get going.
if self.csarUsePara == false or (self.csarUsePara and wetfeet ) then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land
local _freq = self:_GenerateADFFrequency()
self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, self.suppressmessages, "none")
self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none")
return self
end
@@ -1264,7 +1244,7 @@ function CSAR:_EventHandler(EventData)
if _coalition == self.coalition then
local _freq = self:_GenerateADFFrequency()
self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq})
self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, self.suppressmessages, "none")--shagrat add CSAR at Parachute location.
self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location.
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
end
@@ -2136,50 +2116,56 @@ end
--- (Internal) Determine distance to closest MASH.
-- @param #CSAR self
-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT
-- @return #number Distance in meters
-- @return #string MASH Name as string
-- @return #CSAR self
function CSAR:_GetClosestMASH(_heli)
self:T(self.lid .. " _GetClosestMASH")
local _mashset = self.mash -- Core.Set#SET_GROUP
local MashSets = {}
--local _mashes = _mashset.Set-- #table
table.insert(MashSets,_mashset.Set)
table.insert(MashSets,self.zonemashes.Set)
table.insert(MashSets,self.staticmashes.Set)
local _mashes = _mashset:GetSetObjects() -- #table
local _shortestDistance = -1
local _distance = 0
local _helicoord = _heli:GetCoordinate()
local MashName = nil
local function GetCloseAirbase(coordinate,Coalition,Category)
local a=coordinate:GetVec3()
local distmin=math.huge
local airbase=nil
for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do
local b=DCSairbase:getPoint()
local c=UTILS.VecSubstract(a,b)
local dist=UTILS.VecNorm(c)
if dist<distmin and (Category==nil or Category==DCSairbase:getDesc().category) then
distmin=dist
airbase=DCSairbase
end
end
return distmin
end
if self.allowFARPRescue then
local position = _heli:GetCoordinate()
local afb,distance = position:GetClosestAirbase(nil,self.coalition)
_shortestDistance = distance
MashName = (afb ~= nil) and afb:GetName() or "Unknown"
end
for _,_mashes in pairs(MashSets) do
for _, _mashUnit in pairs(_mashes or {}) do
local _mashcoord
if _mashUnit and (not _mashUnit:IsInstanceOf("ZONE_BASE")) and _mashUnit:IsAlive() then
_mashcoord = _mashUnit:GetCoordinate()
elseif _mashUnit and _mashUnit:IsInstanceOf("ZONE_BASE") then
_mashcoord = _mashUnit:GetCoordinate()
end
for _, _mashUnit in pairs(_mashes) do
if _mashUnit and _mashUnit:IsAlive() then
local _mashcoord = _mashUnit:GetCoordinate()
_distance = self:_GetDistance(_helicoord, _mashcoord)
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
_shortestDistance = _distance
MashName = _mashUnit:GetName() or "Unknown"
end
end
end
if _shortestDistance ~= -1 then
return _shortestDistance, MashName
return _shortestDistance
else
return -1
end
end
--- (Internal) Display onboarded rescued pilots.
@@ -2337,9 +2323,9 @@ end
-- @param #CSAR self
-- @param Wrapper.Group#GROUP _group Group #GROUP object.
-- @param #number _freq Frequency to use
-- @param #string BeaconName Beacon Name to use
-- @param #string _name Beacon Name to use
-- @return #CSAR self
function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName)
function CSAR:_AddBeaconToGroup(_group, _freq, _name)
self:T(self.lid .. " _AddBeaconToGroup")
if self.CreateRadioBeacons == false then return end
local _group = _group
@@ -2360,11 +2346,10 @@ function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName)
if _radioUnit then
local name = _radioUnit:GetName()
local Frequency = _freq -- Freq in Hertz
--local name = _radioUnit:GetName()
local name = _radioUnit:GetName()
local Sound = "l10n/DEFAULT/"..self.radioSound
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z))
trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,_name) -- Beacon in MP only runs for exactly 30secs straight
end
end
@@ -2385,13 +2370,9 @@ function CSAR:_RefreshRadioBeacons()
local group = pilot.group
local frequency = pilot.frequency or 0 -- thanks to @Thrud
local bname = pilot.BeaconName or pilot.name..math.random(1,100000)
--trigger.action.stopRadioTransmission(bname)
if group and group:IsAlive() and frequency > 0 then
--self:_AddBeaconToGroup(group,frequency,bname)
else
if frequency > 0 then
trigger.action.stopRadioTransmission(bname)
end
if group and group:IsAlive() and frequency > 0 then
self:_AddBeaconToGroup(group,frequency,bname)
end
end
end
@@ -2422,40 +2403,25 @@ function CSAR:_ReachedPilotLimit()
local islimited = self.limitmaxdownedpilots
local count = self:_CountActiveDownedPilots()
if islimited and (count >= limit) then
if self.useFIFOLimitReplacement then
local oldIndex = -1
local oldDownedPilot = nil
for _index, _downedpilot in pairs(self.downedPilots) do
oldIndex = _index
oldDownedPilot = _downedpilot
break
end
if oldDownedPilot then
oldDownedPilot.group:Destroy(false)
oldDownedPilot.alive = false
self:_CheckDownedPilotTable()
return false
end
end
return true
else
return false
end
end
--- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment.
-- Needs to be set before starting the CSAR instance.
-- @param #CSAR self
-- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups.
-- @return #CSAR self
function CSAR:SetOwnSetPilotGroups(Set)
--- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment.
-- Needs to be set before starting the CSAR instance.
-- @param #CSAR self
-- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups.
-- @return #CSAR self
function CSAR:SetOwnSetPilotGroups(Set)
self.UserSetGroup = Set
return self
end
end
------------------------------
--- FSM internal Functions ---
------------------------------
------------------------------
--- FSM internal Functions ---
------------------------------
--- (Internal) Function called after Start() event.
-- @param #CSAR self.
@@ -2488,10 +2454,9 @@ function CSAR:onafterStart(From, Event, To)
self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
self.staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
self.zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterStart()
local staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce()
local zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce()
--[[
if staticmashes:Count() > 0 then
for _,_mash in pairs(staticmashes.Set) do
self.mash:AddObject(_mash)
@@ -2499,13 +2464,10 @@ function CSAR:onafterStart(From, Event, To)
end
if zonemashes:Count() > 0 then
self:T("Adding zones to self.mash SET")
for _,_mash in pairs(zonemashes.Set) do
self.mash:AddObject(_mash)
end
self:T("Objects in SET: "..self.mash:Count())
end
--]]
if not self.coordinate then
local csarhq = self.mash:GetRandom()
@@ -3006,7 +2968,7 @@ function CSAR:onafterLoad(From, Event, To, path, filename)
local unitName = dataset[9]
local freq = tonumber(dataset[10])
self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, false, description, nil)
self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil)
end
return self

View File

@@ -20,12 +20,11 @@
--
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), bbirchnz (additional code!!)
-- ### Repack addition for crates: **Raiden**
-- ### Additional cool features: **Lekaa**
--
-- @module Ops.CTLD
-- @image OPS_CTLD.jpg
-- Last Update July 2025
-- Last Update April 2025
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -866,8 +865,6 @@ do
-- my_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook
-- my_ctld.TroopUnloadDistHoverHook = 5 -- When hovering, unload troops this far behind the Chinook
-- my_ctld.showstockinmenuitems = false -- When set to true, the menu lines will also show the remaining items in stock (that is, if you set any), downside is that the menu for all will be build every 30 seconds anew.
-- my_ctld.onestepmenu = false -- When set to true, the menu will create Drop and build, Get and load, Pack and remove, Pack and load, Pack. it will be a 1 step solution.
-- my_ctld.VehicleMoveFormation = AI.Task.VehicleFormation.VEE -- When a group moves to a MOVE zone, then it takes this formation. Can be a table of formations, which are then randomly chosen. Defaults to "Vee".
--
-- ## 2.1 CH-47 Chinook support
--
@@ -1295,7 +1292,6 @@ CTLD = {
LoadedGroupsTable = {},
keeploadtable = true,
allowCATransport = false,
VehicleMoveFormation = AI.Task.VehicleFormation.VEE,
}
------------------------------
@@ -1416,7 +1412,7 @@ CTLD.FixedWingTypes = {
--- CTLD class version.
-- @field #string version
CTLD.version="1.3.37"
CTLD.version="1.2.33"
--- Instantiate a new CTLD.
-- @param #CTLD self
@@ -1483,7 +1479,6 @@ function CTLD:New(Coalition, Prefixes, Alias)
self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event.
self:AddTransition("*", "CratesBuildStarted", "*") -- CTLD build event.
self:AddTransition("*", "CratesRepairStarted", "*") -- CTLD repair event.
self:AddTransition("*", "CratesPacked", "*") -- CTLD repack event.
self:AddTransition("*", "HelicopterLost", "*") -- CTLD lost event.
self:AddTransition("*", "Load", "*") -- CTLD load event.
self:AddTransition("*", "Loaded", "*") -- CTLD load event.
@@ -1556,8 +1551,6 @@ function CTLD:New(Coalition, Prefixes, Alias)
self.movetroopsdistance = 5000
self.troopdropzoneradius = 100
self.VehicleMoveFormation = AI.Task.VehicleFormation.VEE
-- added support Hercules Mod
self.enableHercules = false -- deprecated
self.enableFixedWing = false
@@ -1598,7 +1591,6 @@ function CTLD:New(Coalition, Prefixes, Alias)
self.subcats = {}
self.subcatsTroop = {}
self.showstockinmenuitems = false
self.onestepmenu = false
-- disallow building in loadzones
self.nobuildinloadzones = true
@@ -1765,17 +1757,6 @@ function CTLD:New(Coalition, Prefixes, Alias)
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired.
-- @return #CTLD self
--- FSM Function OnBeforeCratesPacked.
-- @function [parent=#CTLD] OnBeforeCratesPacked
-- @param #CTLD self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Group#GROUP Group Group Object.
-- @param Wrapper.Unit#UNIT Unit Unit Object.
-- @param #CTLD_CARGO Cargo Cargo crate that was repacked.
-- @return #CTLD self
--- FSM Function OnBeforeTroopsRTB.
-- @function [parent=#CTLD] OnBeforeTroopsRTB
-- @param #CTLD self
@@ -1862,7 +1843,6 @@ function CTLD:New(Coalition, Prefixes, Alias)
-- @param #string To State.
-- @param Wrapper.Group#GROUP Group Group Object.
-- @param Wrapper.Unit#UNIT Unit Unit Object.
-- @param CargoName The name of the cargo being built.
-- @return #CTLD self
--- FSM Function OnAfterCratesRepairStarted. Info event that a repair has been started.
@@ -1906,17 +1886,6 @@ function CTLD:New(Coalition, Prefixes, Alias)
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired.
-- @return #CTLD self
--- FSM Function OnAfterCratesPacked.
-- @function [parent=#CTLD] OnAfterCratesPacked
-- @param #CTLD self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Group#GROUP Group Group Object.
-- @param Wrapper.Unit#UNIT Unit Unit Object.
-- @param #CTLD_CARGO Cargo Cargo crate that was repacked.
-- @return #CTLD self
--- FSM Function OnAfterTroopsRTB.
-- @function [parent=#CTLD] OnAfterTroopsRTB
-- @param #CTLD self
@@ -2103,9 +2072,6 @@ function CTLD:_EventHandler(EventData)
local _group = event.IniGroup
local _unit = event.IniUnit
self:_RefreshLoadCratesMenu(_group, _unit)
if self:IsFixedWing(_unit) and self.enableFixedWing then
self:_RefreshDropCratesMenu(_group, _unit)
end
end
elseif event.id == EVENTS.PlayerLeaveUnit or event.id == EVENTS.UnitLost then
-- remove from pilot table
@@ -2855,12 +2821,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack)
if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end
-- altered heading logic
-- DONE: right standard deviation?
if self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) then
rheading = math.random(20,60)
else
rheading = UTILS.RandomGaussian(0, 30, -90, 90, 100)
end
rheading=math.fmod((heading+rheading),360)
rheading = UTILS.RandomGaussian(0,30,-90,90,100)
rheading = math.fmod((heading + rheading), 360)
cratecoord = position:Translate(cratedistance,rheading)
else
cratedistance = (row-1)*6
@@ -2951,10 +2913,10 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack)
if drop then
text = string.format("Crates for %s have been dropped!",cratename)
self:__CratesDropped(1, Group, Unit, droppedcargo)
else
self:_SendMessage(text, 10, false, Group)
end
self:_SendMessage(text, 10, false, Group)
self:_RefreshLoadCratesMenu(Group, Unit)
return self
end
@@ -3602,7 +3564,7 @@ end
function CTLD:IsFixedWing(Unit)
local typename = Unit:GetTypeName() or "none"
for _,_name in pairs(self.FixedWingTypes or {}) do
if _name and (typename==_name or string.find(typename,_name,1,true))then
if typename == _name or string.find(typename,_name,1,true) then
return true
end
end
@@ -3614,10 +3576,7 @@ end
-- @param Wrapper.Unit#UNIT Unit
-- @return #boolean Outcome
function CTLD:IsHook(Unit)
if not Unit then return false end
local typeName = Unit:GetTypeName()
if not typeName then return false end
if string.find(typeName, "CH.47") then
if Unit and string.find(Unit:GetTypeName(),"CH.47") then
return true
else
return false
@@ -3807,6 +3766,7 @@ function CTLD:_UnloadCrates(Group, Unit)
self:T(self.lid .. " _UnloadCrates")
if not self.dropcratesanywhere then -- #1570
-- check if we are in DROP zone
local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP)
if not inzone then
self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group)
@@ -3815,60 +3775,46 @@ function CTLD:_UnloadCrates(Group, Unit)
end
end
end
-- Door check
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then
self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group)
if not self.debug then return self end
end
local hoverunload = self:IsCorrectHover(Unit)
-- check for hover unload
local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters
local IsHerc = self:IsFixedWing(Unit)
local IsHook = self:IsHook(Unit)
if IsHerc and (not IsHook) then
-- no hover but airdrop here
hoverunload = self:IsCorrectFlightParameters(Unit)
end
-- check if we\'re landed
local grounded = not self:IsUnitInAir(Unit)
-- Get what we have loaded
local unitname = Unit:GetName()
if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then
local loadedcargo = self.Loaded_Cargo[unitname] or {}
local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo
-- looking for crate
local cargotable = loadedcargo.Cargo
local droppedCount = {}
local neededMap = {}
for _,_cargo in pairs (cargotable) do
local cargo = _cargo
local type = cargo:GetType()
local cargo = _cargo -- #CTLD_CARGO
local type = cargo:GetType() -- #CTLD_CARGO.Enum
if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE and (not cargo:WasDropped() or self.allowcratepickupagain) then
-- unload crates
self:_GetCrates(Group, Unit, cargo, 1, true)
cargo:SetWasDropped(true)
cargo:SetHasMoved(true)
local cname = cargo:GetName() or "Unknown"
droppedCount[cname] = (droppedCount[cname] or 0) + 1
if not neededMap[cname] then
neededMap[cname] = cargo:GetCratesNeeded() or 1
end
end
end
for cname,count in pairs(droppedCount) do
local needed = neededMap[cname] or 1
if needed > 1 then
local full = math.floor(count/needed)
local left = count % needed
if full > 0 and left == 0 then
self:_SendMessage(string.format("Dropped %d %s.",full,cname),10,false,Group)
elseif full > 0 and left > 0 then
self:_SendMessage(string.format("Dropped %d %s(s), with %d leftover crate(s).",full,cname,left),10,false,Group)
else
self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group)
end
else
self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group)
end
end
local loaded = {}
-- cleanup load list
local loaded = {} -- #CTLD.LoadedCargo
loaded.Troopsloaded = 0
loaded.Cratesloaded = 0
loaded.Cargo = {}
for _,_cargo in pairs (cargotable) do
local cargo = _cargo
local type = cargo:GetType()
local cargo = _cargo -- #CTLD_CARGO
local type = cargo:GetType() -- #CTLD_CARGO.Enum
local size = cargo:GetCratesNeeded()
if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then
table.insert(loaded.Cargo,_cargo)
@@ -3892,15 +3838,14 @@ function CTLD:_UnloadCrates(Group, Unit)
end
end
return self
end
end
--- (Internal) Function to build nearby crates.
-- @param #CTLD self
-- @param Wrapper.Group#GROUP Group
-- @param Wrapper.Unit#UNIT Unit
-- @param #boolean Engineering If true build is by an engineering team.
-- @param #boolean MultiDrop If true and not engineering or FOB, vary position a bit.
function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop)
function CTLD:_BuildCrates(Group, Unit,Engineering)
self:T(self.lid .. " _BuildCrates")
-- avoid users trying to build from flying Hercs
if self:IsFixedWing(Unit) and self.enableFixedWing and not Engineering then
@@ -3994,13 +3939,12 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop)
if build.CanBuild then
self:_CleanUpCrates(crates,build,number)
if self.buildtime and self.buildtime > 0 then
local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop)
local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate())
buildtimer:Start(self.buildtime)
self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group)
self:__CratesBuildStarted(1,Group,Unit,build.Name)
self:_RefreshDropTroopsMenu(Group,Unit)
self:__CratesBuildStarted(1,Group,Unit)
else
self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop)
self:_BuildObjectFromCrates(Group,Unit,build)
end
end
end
@@ -4039,15 +3983,13 @@ function CTLD:_PackCratesNearby(Group, Unit)
_Group:Destroy() -- if a match is found destroy the Wrapper.Group#GROUP near the player
self:_GetCrates(Group, Unit, _entry, nil, false, true) -- spawn the appropriate crates near the player
self:_RefreshLoadCratesMenu(Group,Unit) -- call the refresher to show the crates in the menu
self:__CratesPacked(1,Group,Unit,_entry)
return true
return self
end
end
end
end
end
self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group)
return false
return self
end
--- (Internal) Function to repair nearby vehicles / FOBs
@@ -4140,8 +4082,7 @@ end
-- @param #CTLD.Buildable Build
-- @param #boolean Repair If true this is a repair and not a new build
-- @param Core.Point#COORDINATE RepairLocation Location for repair (e.g. where the destroyed unit was)
-- @param #boolean MultiDrop if true and not a repair, vary location a bit if not a FOB
function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,MultiDrop)
function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation)
self:T(self.lid .. " _BuildObjectFromCrates")
-- Spawn-a-crate-content
if Group and Group:IsAlive() or (RepairLocation and not Repair) then
@@ -4158,7 +4099,7 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
if type(temptable) == "string" then
temptable = {temptable}
end
local zone = nil -- Core.Zone#ZONE_RADIUS
local zone = nil
if RepairLocation and not Repair then
-- timed build
zone = ZONE_RADIUS:New(string.format("Build zone-%d",math.random(1,10000)),RepairLocation:GetVec2(),100)
@@ -4167,10 +4108,6 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
end
--local randomcoord = zone:GetRandomCoordinate(35):GetVec2()
local randomcoord = Build.Coord or zone:GetRandomCoordinate(35):GetVec2()
if MultiDrop and (not Repair) and canmove then
-- coordinate may be the same, avoid
local randomcoord = zone:GetRandomCoordinate(35):GetVec2()
end
if Repair then
randomcoord = RepairLocation:GetVec2()
end
@@ -4201,17 +4138,6 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
return self
end
--- (Internal) Function to get a vehicle formation for a moving group
-- @param #CTLD self
-- @return #string Formation
function CTLD:_GetVehicleFormation()
local VehicleMoveFormation = self.VehicleMoveFormation or AI.Task.VehicleFormation.VEE
if type(self.VehicleMoveFormation)=="table" then
VehicleMoveFormation = self.VehicleMoveFormation[math.random(1,#self.VehicleMoveFormation)]
end
return VehicleMoveFormation
end
--- (Internal) Function to move group to WP zone.
-- @param #CTLD self
-- @param Wrapper.Group#GROUP Group The Group to move.
@@ -4226,20 +4152,18 @@ function CTLD:_MoveGroupToZone(Group)
-- yes, we can ;)
local groupname = Group:GetName()
local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE
local formation = self:_GetVehicleFormation()
--local coordinate = zonecoord:GetVec2()
local coordinate = zonecoord:GetVec2()
Group:SetAIOn()
Group:OptionAlarmStateAuto()
Group:OptionDisperseOnAttack(30)
Group:OptionROEOpenFire()
Group:RouteGroundTo(zonecoord,25,formation)
Group:OptionROEOpenFirePossible()
Group:RouteToVec2(coordinate,5)
end
return self
end
--- (Internal) Housekeeping - Cleanup crates when build
-- @param #CTLD self
--
-- @param #table Crates Table of #CTLD_CARGO objects near the unit.
-- @param #CTLD.Buildable Build Table build object.
-- @param #number Number Number of objects in Crates (found) to limit search.
@@ -4275,91 +4199,11 @@ function CTLD:_CleanUpCrates(Crates,Build,Number)
return self
end
--- (Internal) Helper - Drop **all** loaded crates nearby and build them.
-- @param Wrapper.Group#GROUP Group The calling group
-- @param Wrapper.Unit#UNIT Unit The calling unit
function CTLD:_DropAndBuild(Group,Unit)
if self.nobuildinloadzones then
if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) then
self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group)
return self
end
end
self:_UnloadCrates(Group,Unit)
timer.scheduleFunction(function() self:_BuildCrates(Group,Unit,false,true) end,{},timer.getTime()+1)
end
--- (Internal) Helper - Drop a **single** crate set and build it.
-- @param Wrapper.Group#GROUP Group The calling group
-- @param Wrapper.Unit#UNIT Unit The calling unit
-- @param number setIndex Index of the crate-set to drop
function CTLD:_DropSingleAndBuild(Group,Unit,setIndex)
if self.nobuildinloadzones then
if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) then
self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group)
return self
end
end
self:_UnloadSingleCrateSet(Group,Unit,setIndex)
timer.scheduleFunction(function() self:_BuildCrates(Group,Unit,false) end,{},timer.getTime()+1)
end
--- (Internal) Helper - Pack crates near the unit and load them.
-- @param Wrapper.Group#GROUP Group The calling group
-- @param Wrapper.Unit#UNIT Unit The calling unit
function CTLD:_PackAndLoad(Group,Unit)
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then
self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group)
return self
end
if not self:_PackCratesNearby(Group,Unit) then
return self
end
timer.scheduleFunction(function() self:_LoadCratesNearby(Group,Unit) end,{},timer.getTime()+1)
return self
end
--- (Internal) Helper - Pack crates near the unit and then remove them.
-- @param Wrapper.Group#GROUP Group The calling group
-- @param Wrapper.Unit#UNIT Unit The calling unit
function CTLD:_PackAndRemove(Group,Unit)
if not self:_PackCratesNearby(Group,Unit) then
return self
end
timer.scheduleFunction(function() self:_RemoveCratesNearby(Group,Unit) end,{},timer.getTime()+1)
return self
end
--- (Internal) Helper - get and load in one step
-- @param Wrapper.Group#GROUP Group The calling group
-- @param Wrapper.Unit#UNIT Unit The calling unit
function CTLD:_GetAndLoad(Group,Unit,cargoObj)
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then
self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group)
return self
end
self:_GetCrates(Group,Unit,cargoObj)
timer.scheduleFunction(function() self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name) end,{},timer.getTime()+1)
end
-- @param Wrapper.Group#GROUP Group The player’s group that triggered the action
-- @param Wrapper.Unit#UNIT Unit The unit performing the pack-and-load
function CTLD:_GetAllAndLoad(Group,Unit)
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then
self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group)
return self
end
timer.scheduleFunction(function() self:_LoadCratesNearby(Group,Unit) end,{},timer.getTime()+1)
end
--- (Internal) Housekeeping - Function to refresh F10 menus.
-- @param #CTLD self
-- @return #CTLD self
function CTLD:_RefreshF10Menus()
self:T(self.lid .. " _RefreshF10Menus")
self.onestepmenu = self.onestepmenu or false -- hybrid toggle (default = false)
-- 1) Gather all the pilot groups from our Set
local PlayerSet = self.PilotGroups
@@ -4470,6 +4314,7 @@ function CTLD:_RefreshF10Menus()
local menutext = cargoObj.Name
if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end
MENU_GROUP_COMMAND:New(_group, menutext, troopsmenu, self._LoadTroops, self, _group, _unit, cargoObj)
end
end
end
@@ -4495,66 +4340,11 @@ function CTLD:_RefreshF10Menus()
_group.MyTopCratesMenu = topcrates
-- Build the “Get Crates” sub-menu items
local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates)
if self.onestepmenu then
if self.usesubcats then
local subcatmenus = {}
for catName,_ in pairs(self.subcats) do
subcatmenus[catName] = MENU_GROUP:New(_group,catName,cratesmenu)
end
for _,cargoObj in pairs(self.Cargo_Crates) do
if not cargoObj.DontShowInMenu then
local txt = string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
if cargoObj.Location then txt = txt.."[R]" end
local stock = cargoObj:GetStock()
if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end
local mSet = MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory])
MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj)
MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
end
end
for _,cargoObj in pairs(self.Cargo_Statics) do
if not cargoObj.DontShowInMenu then
local txt = string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
if cargoObj.Location then txt = txt.."[R]" end
local stock = cargoObj:GetStock()
if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end
local mSet = MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory])
MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj)
MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
end
end
else
for _,cargoObj in pairs(self.Cargo_Crates) do
if not cargoObj.DontShowInMenu then
local txt = string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
if cargoObj.Location then txt = txt.."[R]" end
local stock = cargoObj:GetStock()
if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end
local mSet = MENU_GROUP:New(_group,txt,cratesmenu)
MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj)
MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
end
end
for _,cargoObj in pairs(self.Cargo_Statics) do
if not cargoObj.DontShowInMenu then
local txt = string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
if cargoObj.Location then txt = txt.."[R]" end
local stock = cargoObj:GetStock()
if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end
local mSet = MENU_GROUP:New(_group,txt,cratesmenu)
MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj)
MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
end
end
end
else
local cratesmenu = MENU_GROUP:New(_group, "Get Crates", topcrates)
if self.usesubcats then
local subcatmenus = {}
for catName, _ in pairs(self.subcats) do
subcatmenus[catName] = MENU_GROUP:New(_group, catName, cratesmenu) -- fixed variable case
subcatmenus[catName] = MENU_GROUP:New(_group, catName, cratesmenu)
end
for _, cargoObj in pairs(self.Cargo_Crates) do
if not cargoObj.DontShowInMenu then
@@ -4594,15 +4384,14 @@ function CTLD:_RefreshF10Menus()
end
end
end
end
local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates)
_group.MyLoadCratesMenu=loadCratesMenu
MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit)
MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit)
local dropCratesMenu = MENU_GROUP:New(_group,"Drop Crates",topcrates)
topcrates.DropCratesMenu = dropCratesMenu
local dropCratesMenu=MENU_GROUP:New(_group,"Drop Crates",topcrates)
topcrates.DropCratesMenu=dropCratesMenu
if not self.nobuildmenu then
MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit)
@@ -4612,16 +4401,8 @@ function CTLD:_RefreshF10Menus()
local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates)
MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit)
if self.onestepmenu then
local mPack=MENU_GROUP:New(_group,"Pack crates",topcrates)
MENU_GROUP_COMMAND:New(_group,"Pack",mPack,self._PackCratesNearby,self,_group,_unit)
MENU_GROUP_COMMAND:New(_group,"Pack and Load",mPack,self._PackAndLoad,self,_group,_unit)
MENU_GROUP_COMMAND:New(_group,"Pack and Remove",mPack,self._PackAndRemove,self,_group,_unit)
MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit)
else
MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit)
MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit)
end
local uName = _unit:GetName()
local loadedData = self.Loaded_Cargo[uName]
@@ -4642,6 +4423,8 @@ function CTLD:_RefreshF10Menus()
end
end
-----------------------------------------------------
-- Misc sub‐menus
-----------------------------------------------------
@@ -4670,7 +4453,7 @@ function CTLD:_RefreshF10Menus()
-- Mark we built the menu
self.MenusDone[_unitName] = true
self:_RefreshLoadCratesMenu(_group,_unit)
self:_RefreshLoadCratesMenu(_group, _unit)
self:_RefreshDropCratesMenu(_group,_unit)
end -- if _group
@@ -4681,53 +4464,45 @@ function CTLD:_RefreshF10Menus()
end -- for all pilot units
return self
end
end
--- (Internal) Function to refresh the menu for load crates. Triggered from land/getcrate/pack and more
-- @param #CTLD self
-- @param Wrapper.Group#GROUP Group The calling group.
-- @param Wrapper.Unit#UNIT Unit The calling unit.
-- @return #CTLD self
function CTLD:_RefreshLoadCratesMenu(Group,Unit)
function CTLD:_RefreshLoadCratesMenu(Group, Unit)
if not Group.MyLoadCratesMenu then return end
Group.MyLoadCratesMenu:RemoveSubMenus()
local d=self.CrateDistance or 35
local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true)
if n==0 then
MENU_GROUP_COMMAND:New(Group,"No crates found! Rescan?",Group.MyLoadCratesMenu,function() self:_RefreshLoadCratesMenu(Group,Unit) end)
local d = self.CrateDistance or 35
local nearby, n = self:_FindCratesNearby(Group, Unit, d, true, true)
if n == 0 then
MENU_GROUP_COMMAND:New(Group, "No crates found! Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end)
return
end
MENU_GROUP_COMMAND:New(Group,"Load ALL",Group.MyLoadCratesMenu,self._LoadCratesNearby,self,Group,Unit)
local cargoByName={}
for _,crate in pairs(nearby) do
local name=crate:GetName()
cargoByName[name]=cargoByName[name] or{}
table.insert(cargoByName[name],crate)
MENU_GROUP_COMMAND:New(Group, "Load ALL", Group.MyLoadCratesMenu, self._LoadCratesNearby, self, Group, Unit)
local cargoByName = {}
for _, crate in pairs(nearby) do
local cName = crate:GetName()
cargoByName[cName] = cargoByName[cName] or {}
table.insert(cargoByName[cName], crate)
end
local lineIndex=1
for cName,list in pairs(cargoByName) do
local needed=list[1]:GetCratesNeeded() or 1
table.sort(list,function(a,b)return a:GetID()<b:GetID() end)
local i=1
while i<=#list do
local left=#list-i+1
local label
if left>=needed then
label=string.format("%d. Load %s",lineIndex,cName)
i=i+needed
for cName, cList in pairs(cargoByName) do
local needed = cList[1]:GetCratesNeeded() or 1
local found = #cList
local line
if found >= needed then
line = string.format("Load %s", cName)
else
label=string.format("%d. Load %s (%d/%d)",lineIndex,cName,left,needed)
i=#list+1
MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end)
line = string.format("Load %s (%d/%d)", cName, found, needed)
end
MENU_GROUP_COMMAND:New(Group,label,Group.MyLoadCratesMenu,self._LoadSingleCrateSet,self,Group,Unit,cName)
lineIndex=lineIndex+1
MENU_GROUP_COMMAND:New(Group, line, Group.MyLoadCratesMenu, self._LoadSingleCrateSet, self, Group, Unit, cName)
end
end
end
end
---
-- Loads exactly `CratesNeeded` crates for one cargoName in range.
@@ -4937,17 +4712,7 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex)
cObj:SetWasDropped(true)
cObj:SetHasMoved(true)
end
local cname = crateObj:GetName() or "Unknown"
local count = #chunk
if needed > 1 then
if count == needed then
self:_SendMessage(string.format("Dropped %d %s.", 1, cname), 10, false, Group)
else
self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.", count, needed, cname), 15, false, Group)
end
else
self:_SendMessage(string.format("Dropped %d %s(s).", count, cname), 10, false, Group)
end
-- Rebuild the cargo list to remove the dropped crates
local loadedData = self.Loaded_Cargo[unitName]
if loadedData and loadedData.Cargo then
@@ -4980,7 +4745,6 @@ end
-- @param Wrapper.Unit#UNIT Unit The calling unit.
-- @return #CTLD self
function CTLD:_RefreshDropCratesMenu(Group, Unit)
if not Group.CTLDTopmenu then return end
local topCrates = Group.MyTopCratesMenu
if not topCrates then return end
@@ -5016,15 +4780,7 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit)
return
end
----------------------------------------------------------------------
-- DEFAULT (“classic”) versus ONE-STEP behaviour
----------------------------------------------------------------------
if not self.onestepmenu then
--------------------------------------------------------------------
-- classic menu
--------------------------------------------------------------------
MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit)
self.CrateGroupList=self.CrateGroupList or{}
self.CrateGroupList[Unit:GetName()]={}
@@ -5059,57 +4815,7 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit)
lineIndex=lineIndex+1
end
end
else
--------------------------------------------------------------------
-- one-step (enhanced) menu
--------------------------------------------------------------------
local mAll=MENU_GROUP:New(Group,"Drop ALL crates",dropCratesMenu)
MENU_GROUP_COMMAND:New(Group,"Drop",mAll,self._UnloadCrates,self,Group,Unit)
if not ( self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) ) then
MENU_GROUP_COMMAND:New(Group,"Drop and build",mAll,self._DropAndBuild,self,Group,Unit)
end
self.CrateGroupList=self.CrateGroupList or{}
self.CrateGroupList[Unit:GetName()]={}
local lineIndex=1
for cName,list in pairs(cargoByName) do
local needed=list[1]:GetCratesNeeded() or 1
table.sort(list,function(a,b)return a:GetID()<b:GetID()end)
local i=1
while i<=#list do
local left=(#list-i+1)
if left>=needed then
local chunk={}
for n=i,i+needed-1 do
table.insert(chunk,list[n])
end
local label=string.format("%d. %s",lineIndex,cName)
table.insert(self.CrateGroupList[Unit:GetName()],chunk)
local setIndex=#self.CrateGroupList[Unit:GetName()]
local mSet=MENU_GROUP:New(Group,label,dropCratesMenu)
MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex)
if not ( self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) ) then
MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex)
end
i=i+needed
else
local chunk={}
for n=i,#list do
table.insert(chunk,list[n])
end
local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed)
table.insert(self.CrateGroupList[Unit:GetName()],chunk)
local setIndex=#self.CrateGroupList[Unit:GetName()]
MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex)
i=#list+1
end
lineIndex=lineIndex+1
end
end
end
end
end
--- (Internal) Function to unload a single Troop group by ID.
-- @param #CTLD self
@@ -5158,7 +4864,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID)
return self
end
-- Drop the FIRST cargo in that chunk
-- Drop ONLY the FIRST cargo in that chunk
local foundCargo = chunk[1]
if not foundCargo then
self:_SendMessage(string.format("No troop cargo at chunk %d!", chunkID), 10, false, Group)
@@ -5219,8 +4925,6 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID)
foundCargo:SetWasDropped(true)
if cType == CTLD_CARGO.Enum.ENGINEERS then
self.Engineers = self.Engineers + 1
local grpname = self.DroppedTroops[self.TroopCounter]:GetName()
self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname)
self:_SendMessage(string.format("Dropped Engineers %s into action!", name), 10, false, Group)
else
self:_SendMessage(string.format("Dropped Troops %s into action!", name), 10, false, Group)
@@ -5958,7 +5662,6 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
if Zonetype == CTLD.CargoZoneType.SHIP then
self:T("Checking Type Ship: "..zonename)
local ZoneUNIT = UNIT:FindByName(zonename)
if not ZoneUNIT then return false end
zonecoord = ZoneUNIT:GetCoordinate()
zoneradius = czone.shiplength
zonewidth = czone.shipwidth
@@ -6036,23 +5739,16 @@ function CTLD:SmokeZoneNearBy(Unit, Flare)
for index,cargozone in pairs(zones[i]) do
local CZone = cargozone --#CTLD.CargoZone
local zonename = CZone.name
local zone = nil -- Core.Zone#ZONE_RADIUS
local airbasezone = false
local zone = nil
if i == 4 then
zone = UNIT:FindByName(zonename)
else
zone = ZONE:FindByName(zonename)
if not zone then
zone = AIRBASE:FindByName(zonename):GetZone()
airbasezone = true
end
end
local zonecoord = zone:GetCoordinate()
-- Avoid smoke/flares on runways
if (i==1 or 1==3) and airbasezone==true and zone:IsInstanceOf("ZONE_BASE") then
zonecoord = zone:GetRandomCoordinate(inner,outer,{land.SurfaceType.LAND})
end
if zonecoord then
local active = CZone.active
local color = CZone.color
local distance = self:_GetDistance(zonecoord,unitcoord)
@@ -6071,7 +5767,6 @@ function CTLD:SmokeZoneNearBy(Unit, Flare)
end
end
end
end
if not smoked then
local distance = UTILS.MetersToNM(self.smokedistance)
self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group)
@@ -7151,16 +6846,6 @@ end
local filepath = self.filepath
self:__Save(interval,filepath,filename)
end
if type(self.VehicleMoveFormation) == "table" then
local Formations = {}
for _,_formation in pairs(self.VehicleMoveFormation) do
table.insert(Formations,_formation)
end
self.VehicleMoveFormation = nil
self.VehicleMoveFormation = Formations
end
return self
end
@@ -7298,8 +6983,7 @@ end
-- right subtype?
if Event == subtype and not task:IsDone() then
local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case ....
self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")})
if task:GetProperty("ExtractName") then
--self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")})
local okaygroup = string.find(Groupname,task:GetProperty("ExtractName"),1,true)
if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and okaygroup then
if task.Clients:HasUniqueID(playername) then
@@ -7307,9 +6991,6 @@ end
task:__Success(-1)
end
end
else
self:T({Text="'ExtractName' Property not set",Name=Groupname,Property=task.Type})
end
end
end
)

View File

@@ -136,7 +136,6 @@ COMMANDER = {
awacsZones = {},
tankerZones = {},
limitMission = {},
maxMissionsAssignPerCycle = 1,
}
--- COMMANDER class version.
@@ -1536,8 +1535,6 @@ function COMMANDER:CheckMissionQueue()
end
end
local missionsAssigned = 0
-- Loop over missions in queue.
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
@@ -1598,13 +1595,10 @@ function COMMANDER:CheckMissionQueue()
LEGION.UnRecruitAssets(assets, mission)
end
missionsAssigned = missionsAssigned + 1
if missionsAssigned >= (self.maxMissionsAssignPerCycle or 1) then
-- Only ONE mission is assigned.
return
end
end
else
---
@@ -1617,16 +1611,6 @@ function COMMANDER:CheckMissionQueue()
end
--- Set how many missions can be assigned in a single status iteration. (eg. This is useful for persistent missions where you need to load all AUFTRAGs on mission start and then change it back to default)
--- Warning: Increasing this value will increase the number of missions started per iteration and thus may lead to performance issues if too many missions are started at once.
-- @param #COMMANDER self
-- @param #number Number of missions assigned per status iteration. Default is 1.
-- @return #COMMANDER self.
function COMMANDER:SetMaxMissionsAssignPerCycle(MaxMissionsAssignPerCycle)
self.maxMissionsAssignPerCycle = MaxMissionsAssignPerCycle or 1
return self
end
--- Get cohorts.
-- @param #COMMANDER self
-- @param #table Legions Special legions.
@@ -1687,10 +1671,7 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation)
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.
local Runway=true
if legion:IsAirwing() then
Runway=legion:IsRunwayOperational() and legion.airbase and legion.airbase:GetCoalition() == legion:GetCoalition()
end
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
-- Legion has to be running.
if legion:IsRunning() and Runway then
@@ -1723,10 +1704,7 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation)
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.
local Runway=true
if legion:IsAirwing() then
Runway=legion:IsRunwayOperational() and legion.airbase and legion.airbase:GetCoalition() == legion:GetCoalition()
end
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
-- Legion has to be running.
if legion:IsRunning() and Runway then

View File

@@ -1,18 +1,13 @@
-------------------------------------------------------------------------
-- 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: Aug 2025
-- Last Update: July 2024
-------------------------------------------------------------------------
--
--- **Ops** - Easy GCI & CAP Manager
@@ -75,10 +70,6 @@
-- @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
-- @extends Core.Fsm#FSM
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
@@ -232,11 +223,7 @@ EASYGCICAP = {
ReadyFlightGroups = {},
DespawnAfterLanding = false,
DespawnAfterHolding = true,
ListOfAuftrag = {},
defaulttakeofftype = "hot",
FuelLowThreshold = 25,
FuelCriticalThreshold = 10,
showpatrolpointmarks = false,
ListOfAuftrag = {}
}
--- Internal Squadron data type
@@ -269,11 +256,10 @@ 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.27"
EASYGCICAP.version="0.1.18"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -326,10 +312,6 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
self.DespawnAfterLanding = false
self.DespawnAfterHolding = true
self.ListOfAuftrag = {}
self.defaulttakeofftype = "hot"
self.FuelLowThreshold = 25
self.FuelCriticalThreshold = 10
self.showpatrolpointmarks = false
-- Set some string id for output to DCS.log file.
self.lid=string.format("EASYGCICAP %s | ", self.alias)
@@ -354,63 +336,6 @@ 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
--- 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
@@ -431,7 +356,7 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch)
return self
end
--- (internal) Count alive missions in our internal stack.
--- Count alive missions in our internal stack.
-- @param #EASYGCICAP self
-- @return #number count
function EASYGCICAP:_CountAliveAuftrags()
@@ -475,16 +400,6 @@ function EASYGCICAP:SetDefaultRepeatOnFailure(Retries)
return self
end
--- Add default take off type for the airwings.
-- @param #EASYGCICAP self
-- @param #string Takeoff Can be "hot", "cold", or "air" - default is "hot".
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultTakeOffType(Takeoff)
self:T(self.lid.."SetDefaultTakeOffType")
self.defaulttakeofftype = Takeoff or "hot"
return self
end
--- Set default CAP Speed in knots
-- @param #EASYGCICAP self
-- @param #number Speed Speed defaults to 300
@@ -654,13 +569,6 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
local DespawnAfterLanding = self.DespawnAfterLanding
local DespawnAfterHolding = self.DespawnAfterHolding
-- Check STATIC name
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
end
-- Create Airwing
local CAP_Wing = AIRWING:New(Airbasename,Alias)
CAP_Wing:SetVerbosityLevel(0)
@@ -671,10 +579,6 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
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)
end
@@ -692,8 +596,9 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
if #self.ManagedREC > 0 then
CAP_Wing:SetNumberRecon(1)
end
CAP_Wing:SetTakeoffType(self.defaulttakeofftype)
--local PatrolCoordinateKutaisi = ZONE:New(CapZoneName):GetCoordinate()
--CAP_Wing:AddPatrolPointCAP(PatrolCoordinateKutaisi,self.capalt,UTILS.KnotsToAltKIAS(self.capspeed,self.capalt),self.capdir,self.capleg)
CAP_Wing:SetTakeoffHot()
CAP_Wing:SetLowFuelThreshold(0.3)
CAP_Wing.RandomAssetScore = math.random(50,100)
CAP_Wing:Start()
@@ -701,11 +606,6 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
local Intel = self.Intel
local TankerInvisible = self.TankerInvisible
local engagerange = self.engagerange
local GoZoneSet = self.GoZoneSet
local NoGoZoneSet = self.NoGoZoneSet
local FuelLow = self.FuelLowThreshold or 25
local FuelCritical = self.FuelCriticalThreshold or 10
function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission)
local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP
@@ -717,15 +617,10 @@ 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(self.engagerange,{"Air"},self.GoZoneSet,self.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
@@ -764,30 +659,24 @@ 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. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param Core.Point#COORDINATE Coordinate.
-- @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 coordinate = Coordinate
self:T(self.lid.."AddPatrolPointCAP "..Coordinate:ToStringLLDDM())
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
@@ -795,7 +684,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. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param Core.Point#COORDINATE Coordinate.
-- @param #number Altitude Defaults to 25000 feet.
-- @param #number Speed Defaults to 300 knots.
-- @param #number Heading Defaults to 90 degrees (East).
@@ -820,7 +709,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. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param Core.Point#COORDINATE Coordinate.
-- @param #number Altitude Defaults to 25000 feet.
-- @param #number Speed Defaults to 300 knots.
-- @param #number Heading Defaults to 90 degrees (East).
@@ -845,7 +734,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. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param Core.Point#COORDINATE Coordinate.
-- @param #number Altitude Defaults to 25000 feet.
-- @param #number Speed Defaults to 300 knots.
-- @param #number Heading Defaults to 90 degrees (East).
@@ -874,11 +763,6 @@ function EASYGCICAP:_SetTankerPatrolPoints()
self:T(self.lid.."_SetTankerPatrolPoints")
for _,_data in pairs(self.ManagedTK) do
local data = _data --#EASYGCICAP.CapPoint
self:T("Airbasename = "..data.AirbaseName)
if not self.wings[data.AirbaseName] then
MESSAGE:New(self.lid.."You are trying to create a TANKER point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
@@ -898,11 +782,6 @@ function EASYGCICAP:_SetAwacsPatrolPoints()
self:T(self.lid.."_SetAwacsPatrolPoints")
for _,_data in pairs(self.ManagedEWR) do
local data = _data --#EASYGCICAP.CapPoint
self:T("Airbasename = "..data.AirbaseName)
if not self.wings[data.AirbaseName] then
MESSAGE:New(self.lid.."You are trying to create an AWACS point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
@@ -922,24 +801,14 @@ function EASYGCICAP:_SetCAPPatrolPoints()
self:T(self.lid.."_SetCAPPatrolPoints")
for _,_data in pairs(self.ManagedCP) do
local data = _data --#EASYGCICAP.CapPoint
self:T("Airbasename = "..data.AirbaseName)
if not self.wings[data.AirbaseName] then
MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
local Heading = data.Heading
local LegLength = data.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
end
@@ -951,11 +820,6 @@ function EASYGCICAP:_SetReconPatrolPoints()
self:T(self.lid.."_SetReconPatrolPoints")
for _,_data in pairs(self.ManagedREC) do
local data = _data --#EASYGCICAP.CapPoint
self:T("Airbasename = "..data.AirbaseName)
if not self.wings[data.AirbaseName] then
MESSAGE:New(self.lid.."You are trying to create a RECON point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
@@ -1004,7 +868,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
@@ -1293,19 +1157,19 @@ end
-- @return #boolean assigned
-- @return #number leftover
function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize)
self:T("_TryAssignIntercept for size "..WingSize or 1)
self:I("_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:T(self.lid..string.format("Assignment for %s",Group:GetName()))
self:I(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))
self:I(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
@@ -1322,7 +1186,7 @@ function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group
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))
self:I(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
if wingsize == 0 then
assigned = true
break
@@ -1397,10 +1261,6 @@ 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
@@ -1502,7 +1362,7 @@ function EASYGCICAP:_StartIntel()
end
-------------------------------------------------------------------------
-- TODO FSM Functions
-- FSM Functions
-------------------------------------------------------------------------
--- (Internal) FSM Function onafterStart
@@ -1598,7 +1458,7 @@ function EASYGCICAP:onafterStatus(From,Event,To)
local engage = FG:IsEngaging()
local hasmissiles = FG:IsOutOfMissiles() == nil and true or false
local ready = hasmissiles and FG:IsFuelGood() and FG:IsAirborne()
--self:T(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready)))
--self:I(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready)))
if ready then
self.ReadyFlightGroups[name] = FG
end
@@ -1633,8 +1493,5 @@ 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

File diff suppressed because it is too large Load Diff

View File

@@ -259,7 +259,7 @@ function FLIGHTGROUP:New(group)
local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #FLIGHTGROUP
-- Set some string id for output to DCS.log file.
self.lid=string.format("FLIGHTGROUP %s | ", self.groupname or "N/A")
self.lid=string.format("FLIGHTGROUP %s | ", self.groupname)
-- Defaults
self:SetDefaultROE()
@@ -779,61 +779,6 @@ function FLIGHTGROUP:SetJettisonWeapons(Switch)
return self
end
--- Set the aircraft to land straight in.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionLandingStraightIn()
self.OptionLandingStraightIn = true
if self:GetGroup():IsAlive() then
self:GetGroup():SetOptionLandingStraightIn()
end
return self
end
--- Set the aircraft to land in pairs.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionLandingForcePair()
self.OptionLandingForcePair = true
if self:GetGroup():IsAlive() then
self:GetGroup():SetOptionLandingForcePair()
end
return self
end
--- Set the aircraft to NOT land in pairs.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionLandingRestrictPair()
self.OptionLandingRestrictPair = true
if self:GetGroup():IsAlive() then
self:GetGroup():SetOptionLandingRestrictPair()
end
return self
end
--- Set the aircraft to land after overhead break.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionLandingOverheadBreak()
self.OptionLandingOverheadBreak = true
if self:GetGroup():IsAlive() then
self:GetGroup():SetOptionLandingOverheadBreak()
end
return self
end
--- [HELICOPTER] Set the aircraft to prefer takeoff and landing vertically.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionPreferVertical()
self.OptionPreferVertical = true
if self:GetGroup():IsAlive() then
self:GetGroup():OptionPreferVerticalLanding()
end
return self
end
--- Set if group is ready for taxi/takeoff if controlled by a `FLIGHTCONTROL`.
-- @param #FLIGHTGROUP self
-- @param #boolean ReadyTO If `true`, flight is ready for takeoff.
@@ -2058,9 +2003,6 @@ function FLIGHTGROUP:onafterElementAirborne(From, Event, To, Element)
-- Debug info.
self:T2(self.lid..string.format("Element airborne %s", Element.name))
-- Set parking spot to free. Also for FC. This is usually done after taxiing but doing it here in case the group is teleported.
self:_SetElementParkingFree(Element)
-- Set element status.
self:_UpdateStatus(Element, OPSGROUP.ElementStatus.AIRBORNE)
@@ -3134,7 +3076,7 @@ function FLIGHTGROUP:onbeforeLandAtAirbase(From, Event, To, airbase)
local Tsuspend=nil
if airbase==nil then
self:T(self.lid.."ERROR: Airbase is nil in LandAtAirbase() call!")
self:T(self.lid.."ERROR: Airbase is nil in LandAtAirase() call!")
allowed=false
end
@@ -4552,11 +4494,6 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase)
-- Airbase.
airbase=airbase or self:GetClosestAirbase()
if airbase == nil then
self:T(self.lid.."No airbase found for element "..element.name)
return nil
end
-- Parking table of airbase.
local parking=airbase.parking --:GetParkingSpotsTable()
@@ -4667,14 +4604,12 @@ function FLIGHTGROUP:GetParking(airbase)
local coords={}
for clientname, client in pairs(clients) do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
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
end

View File

@@ -2324,7 +2324,7 @@ INTEL_DLINK = {
verbose = 0,
lid = nil,
alias = nil,
cachetime = 120,
cachetime = 300,
interval = 20,
contacts = {},
clusters = {},
@@ -2333,7 +2333,7 @@ INTEL_DLINK = {
--- Version string
-- @field #string version
INTEL_DLINK.version = "0.0.2"
INTEL_DLINK.version = "0.0.1"
--- Function to instantiate a new object
-- @param #INTEL_DLINK self
@@ -2384,15 +2384,15 @@ function INTEL_DLINK:New(Intels, Alias, Interval, Cachetime)
self.alias="SPECTRE"
end
-- Cache time
self.cachetime = Cachetime or 300
-- Interval
self.interval = Interval or 20
-- Set some string id for output to DCS.log file.
self.lid=string.format("INTEL_DLINK %s | ", self.alias)
-- Cache time
self:SetDLinkCacheTime(Cachetime or 120)
-- Start State.
self:SetStartState("Stopped")
@@ -2477,16 +2477,6 @@ function INTEL_DLINK:onafterStart(From, Event, To)
return self
end
--- Function to set how long INTEL DLINK remembers contacts.
-- @param #INTEL_DLINK self
-- @param #number seconds Remember this many seconds. Defaults to 180.
-- @return #INTEL_DLINK self
function INTEL_DLINK:SetDLinkCacheTime(seconds)
self.cachetime = math.abs(seconds or 120)
self:I(self.lid.."Caching for "..self.cachetime.." seconds.")
return self
end
--- Function to collect data from the various #INTEL
-- @param #INTEL_DLINK self
-- @param #string From The From state

View File

@@ -2514,10 +2514,7 @@ function LEGION._GetCohorts(Legions, Cohorts, Operation, OpsQueue)
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.
local Runway=true
if legion:IsAirwing() then
Runway=legion:IsRunwayOperational() and legion.airbase and legion.airbase:GetCoalition() == legion:GetCoalition()
end
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
-- Legion has to be running.
if legion:IsRunning() and Runway then

View File

@@ -445,6 +445,7 @@ OPSGROUP.TaskType={
-- @field Wrapper.Marker#MARKER marker Marker on the F10 map.
-- @field #string formation Ground formation. Similar to action but on/off road.
-- @field #number missionUID Mission UID (Auftragsnr) this waypoint belongs to.
-- @field Navigation.FlightPlan#FLIGHTPLAN flightplan Flightplan this waypoint belongs to.
--- Cargo Carrier status.
-- @type OPSGROUP.CarrierStatus
@@ -573,6 +574,11 @@ function OPSGROUP:New(group)
-- Set DCS group and controller.
self.dcsgroup=self:GetDCSGroup()
if not self.dcsgroup then
return
end
self.controller=self.dcsgroup:getController()
-- Category.
@@ -5589,13 +5595,10 @@ function OPSGROUP:onafterUnpauseMission(From, Event, To)
-- Debug info.
self:T(self.lid..string.format("Unpausing mission %s [%s]", mission:GetName(), mission:GetType()))
-- Set state of mission, e.g. for not teleporting again
mission.unpaused=true
-- Start mission.
self:MissionStart(mission)
-- Remove mission from pausedmissions queue
-- Remove mission from
for i,mid in pairs(self.pausedmissions) do
--self:T(self.lid..string.format("Checking paused mission", mid))
if mid==mission.auftragsnummer then
@@ -5730,7 +5733,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission)
-- Decrease patrol data.
if Mission.patroldata then
Mission.patroldata.noccupied=Mission.patroldata.noccupied-1
AIRWING.UpdatePatrolPointMarker(self,Mission.patroldata)
AIRWING.UpdatePatrolPointMarker(Mission.patroldata)
end
-- Switch auto engage detected off. This IGNORES that engage detected had been activated for the group!
@@ -6235,7 +6238,7 @@ function OPSGROUP:RouteToMission(mission, delay)
end
-- Check if group is mobile. Note that some immobile units report a speed of 1 m/s = 3.6 km/h.
if (self.speedMax<=3.6 or mission.teleport) and not mission.unpaused then
if self.speedMax<=3.6 or mission.teleport then
-- Teleport to waypoint coordinate. Mission will not be paused.
self:Teleport(waypointcoord, nil, true)
@@ -11352,9 +11355,9 @@ function OPSGROUP:_SimpleTaskFunction(Function, uid)
return DCSTask
end
--- Enhance waypoint table.
--- Enhanced waypoint table.
-- @param #OPSGROUP self
-- @param #OPSGROUP.Waypoint Waypoint data.
-- @param #OPSGROUP.Waypoint waypoint Waypoint data.
-- @return #OPSGROUP.Waypoint Modified waypoint data.
function OPSGROUP:_CreateWaypoint(waypoint)
@@ -13610,20 +13613,24 @@ end
-- @return Core.Point#COORDINATE The coordinate of the object.
function OPSGROUP:_CoordinateFromObject(Object)
env.info("FF coordfrom object")
if Object then
if Object:IsInstanceOf("COORDINATE") then
if VECTOR._IsVector(Object) then
env.info("FF VECTOR")
return Object:GetCoordinate()
elseif Object:IsInstanceOf("COORDINATE") then
return Object
else
if Object:IsInstanceOf("POSITIONABLE") or Object:IsInstanceOf("ZONE_BASE") then
self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate")
self:E(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate")
local coord=Object:GetCoordinate()
return coord
else
self:T(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!")
self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!")
end
end
else
self:T(self.lid.."ERROR: Object passed is nil!")
self:E(self.lid.."ERROR: Object passed is nil!")
end
return nil

View File

@@ -53,7 +53,6 @@
-- @field #number threatlevelCapture Threat level necessary to capture a zone.
-- @field Core.Set#SET_UNIT ScanUnitSet Set of scanned units.
-- @field Core.Set#SET_GROUP ScanGroupSet Set of scanned groups.
-- @field #number UpdateSeconds Run status every this many seconds.
-- @extends Core.Fsm#FSM
--- *Gentlemen, when the enemy is committed to a mistake we must not interrupt him too soon.* --- Horation Nelson
@@ -78,7 +77,6 @@ OPSZONE = {
Tnut = 0,
chiefs = {},
Missions = {},
UpdateSeconds = 120,
}
--- OPSZONE.MISSION
@@ -99,7 +97,7 @@ OPSZONE.ZoneType={
--- OPSZONE class version.
-- @field #string version
OPSZONE.version="0.6.2"
OPSZONE.version="0.6.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -735,8 +733,7 @@ function OPSZONE:onafterStart(From, Event, To)
self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status, self)
-- Status update.
local EveryUpdateIn = self.UpdateSeconds or 120
self.timerStatus:Start(1, EveryUpdateIn)
self.timerStatus:Start(1, 120)
-- Handle base captured event.
if self.airbase then

View File

@@ -1544,7 +1544,7 @@ end
-- @param #PLAYERRECCE self
-- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 271 or {127,251}. There needs to be exactly the same number of modulations!
-- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies!
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone"
-- @param #string Gender (Optional) Defaults to "male"
-- @param #string Culture (Optional) Defaults to "en-US"
-- @param #number Port (Optional) Defaults to 5002
@@ -1556,7 +1556,7 @@ end
-- @return #PLAYERRECCE self
function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend)
self:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" --
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" --
self.Gender = Gender or MSRS.gender or "male" --
self.Culture = Culture or MSRS.culture or "en-US" --
self.Port = Port or MSRS.port or 5002 --

View File

@@ -21,7 +21,7 @@
-- ===
-- @module Ops.PlayerTask
-- @image OPS_PlayerTask.jpg
-- @date Last Update May 2025
-- @date Last Update Jan 2025
do
@@ -98,7 +98,7 @@ PLAYERTASK = {
--- PLAYERTASK class version.
-- @field #string version
PLAYERTASK.version="0.1.27"
PLAYERTASK.version="0.1.25"
--- Generic task condition.
-- @type PLAYERTASK.Condition
@@ -556,7 +556,6 @@ end
-- @param #PLAYERTASK self
-- @param #SET_BASE CaptureSquadGroupNamePrefix The prefix of the group name that needs to capture the zone.
-- @param #number Coalition The coalition that needs to capture the zone.
-- @param #boolean CheckClientInZone If true, a CLIENT assigned to this task also needs to be in the zone for the task to be successful.
-- @return #PLAYERTASK self
-- @usage
-- -- We can use either STATIC, SET_STATIC, SCENERY or SET_SCENERY as target objects.
@@ -571,20 +570,20 @@ end
--
-- -- We set CaptureSquadGroupNamePrefix the group name prefix as set in the ME or the spawn of the group that need to be present at the OpsZone like a capture squad,
-- -- and set the capturing Coalition in order to trigger a successful task.
-- mytask:AddOpsZoneCaptureSuccessCondition("capture-squad", coalition.side.BLUE, false)
-- mytask:AddOpsZoneCaptureSuccessCondition("capture-squad", coalition.side.BLUE)
--
-- playerTaskManager:AddPlayerTaskToQueue(mytask)
function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone)
function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix, Coalition)
local task = self
task:AddConditionSuccess(
function(target)
if target:IsInstanceOf("OPSZONE") then
return task:_CheckCaptureOpsZoneSuccess(target, CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone or true)
return task:_CheckCaptureOpsZoneSuccess(target, CaptureSquadGroupNamePrefix, Coalition, true)
elseif target:IsInstanceOf("SET_OPSZONE") then
local successes = 0
local isClientInZone = false
target:ForEachZone(function(opszone)
if task:_CheckCaptureOpsZoneSuccess(opszone, CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone or true) then
if task:_CheckCaptureOpsZoneSuccess(opszone, CaptureSquadGroupNamePrefix, Coalition) then
successes = successes + 1
end
@@ -980,12 +979,6 @@ function PLAYERTASK:onafterStatus(From, Event, To)
if status == "Stopped" then return self end
-- update marker in case target is moving
if self.TargetMarker then
local coordinate = self.Target:GetCoordinate()
self.TargetMarker:UpdateCoordinate(coordinate,0.5)
end
-- Check Target status
local targetdead = false
@@ -1440,9 +1433,9 @@ do
-- taskmanager:AddRejectZone(ZONE:FindByName("RejectZone"))
--
-- -- Set up using SRS for messaging
-- local hereSRSPath = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- local hereSRSPath = "C:\\Program Files\\DCS-SimpleRadio-Standalone"
-- local hereSRSPort = 5002
-- -- local hereSRSGoogle = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\yourkey.json"
-- -- local hereSRSGoogle = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourkey.json"
-- taskmanager:SetSRS({130,255},{radio.modulation.AM,radio.modulation.AM},hereSRSPath,"female","en-GB",hereSRSPort,"Microsoft Hazel Desktop",0.7,hereSRSGoogle)
--
-- -- Controller will announce itself under these broadcast frequencies, handy to use cold-start frequencies here of your aircraft
@@ -1909,7 +1902,7 @@ PLAYERTASKCONTROLLER.Messages = {
--- PLAYERTASK class version.
-- @field #string version
PLAYERTASKCONTROLLER.version="0.1.70"
PLAYERTASKCONTROLLER.version="0.1.69"
--- Create and run a new TASKCONTROLLER instance.
-- @param #PLAYERTASKCONTROLLER self
@@ -1951,7 +1944,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
self.taskinfomenu = false
self.activehasinfomenu = false
self.MenuName = nil
self.menuitemlimit = 6
self.menuitemlimit = 5
self.holdmenutime = 30
self.MarkerReadOnly = false
@@ -2422,7 +2415,7 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,Holdi
end
)
else
self:E(self.lid.."No OPSGROUP/SET_OPSGROUP object passed or object is not alive!")
self:E(self.lid.."No FLIGHTGROUP object passed or FLIGHTGROUP is not alive!")
end
else
self.autolase = nil
@@ -2581,7 +2574,7 @@ function PLAYERTASKCONTROLLER:SetMenuOptions(InfoMenu,ItemLimit,HoldTime)
if self.activehasinfomenu then
self:EnableTaskInfoMenu()
end
self.menuitemlimit = ItemLimit+1 or 6
self.menuitemlimit = ItemLimit or 5
self.holdmenutime = HoldTime or 30
return self
end
@@ -3486,7 +3479,7 @@ end
-- @param #PLAYERTASKCONTROLLER self
-- @param Ops.PlayerTask#PLAYERTASK PlayerTask
-- @param #boolean Silent If true, make no "has new task" announcement
-- @param #boolean TaskFilter If true, apply the white/black-list task filters here, also
-- @param #boolen TaskFilter If true, apply the white/black-list task filters here, also
-- @return #PLAYERTASKCONTROLLER self
-- @usage
-- Example to create a PLAYERTASK of type CTLD and give Players 10 minutes to complete:
@@ -3710,7 +3703,6 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
else
CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic)
end
--self:I("CoordText = "..CoordText)
-- Threat Level
local ThreatLevel = task.Target:GetThreatLevelMax()
--local ThreatLevelText = "high"
@@ -3845,8 +3837,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
Text = string.gsub(Text,"9","niner")
CoordText = "MGRS;"..Text
if self.PathToGoogleKey then
--CoordText = string.format("<say-as interpret-as=\'characters\'>%s</say-as>",CoordText)
--doesn't seem to work any longer
CoordText = string.format("<say-as interpret-as='characters'>%s</say-as>",CoordText)
end
--self:I(self.lid.." | ".. CoordText)
end
@@ -3864,12 +3855,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
CoordText = string.gsub(ttstext," BR, "," Bee, Arr, ")
end
elseif task:HasFreetext() then
-- add tts freetext
local brieftxt = self.gettext:GetEntry("BRIEFING",self.locale)
ttstext = ttstext .. string.format("; %s: ",brieftxt)..task:GetFreetextTTS()
end
--self:I("**** TTS Text ****\n"..ttstext.."\n*****")
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,2)
end
else
@@ -4368,7 +4357,7 @@ function PLAYERTASKCONTROLLER:SwitchDetectStatics(OnOff)
return self
end
--- [User] Add an accept zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
--- [User] Add accept zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set.
-- @return #PLAYERTASKCONTROLLER self
@@ -4382,7 +4371,7 @@ function PLAYERTASKCONTROLLER:AddAcceptZone(AcceptZone)
return self
end
--- [User] Add an accept SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
--- [User] Add accept 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 AcceptZoneSet Add a SET_ZONE to the accept zone set.
-- @return #PLAYERTASKCONTROLLER self
@@ -4396,7 +4385,7 @@ function PLAYERTASKCONTROLLER:AddAcceptZoneSet(AcceptZoneSet)
return self
end
--- [User] Add a reject zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
--- [User] Add reject zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Zone#ZONE RejectZone Add a zone to the reject zone set.
-- @return #PLAYERTASKCONTROLLER self
@@ -4410,7 +4399,7 @@ function PLAYERTASKCONTROLLER:AddRejectZone(RejectZone)
return self
end
--- [User] Add a reject SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
--- [User] Add reject 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 RejectZoneSet Add a zone to the reject zone set.
-- @return #PLAYERTASKCONTROLLER self
@@ -4424,37 +4413,9 @@ function PLAYERTASKCONTROLLER:AddRejectZoneSet(RejectZoneSet)
return self
end
--- [User] Add a conflict zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
--- [User] Remove accept zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Zone#ZONE ConflictZone Add a zone to the conflict zone set.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:AddConflictZone(ConflictZone)
self:T(self.lid.."AddConflictZone")
if self.Intel then
self.Intel:AddConflictZone(ConflictZone)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
--- [User] Add a conflict 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 ConflictZoneSet Add a zone to the conflict zone set.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:AddConflictZoneSet(ConflictZoneSet)
self:T(self.lid.."AddConflictZoneSet")
if self.Intel then
self.Intel.conflictzoneset:AddSet(ConflictZoneSet)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
--- [User] Remove an accept zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Zone#ZONE AcceptZone Remove this zone from the accept zone set.
-- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:RemoveAcceptZone(AcceptZone)
self:T(self.lid.."RemoveAcceptZone")
@@ -4466,11 +4427,11 @@ function PLAYERTASKCONTROLLER:RemoveAcceptZone(AcceptZone)
return self
end
--- [User] Remove a reject zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
--- [User] Remove reject zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Zone#ZONE RejectZone Remove this zone from the reject zone set.
-- @param Core.Zone#ZONE RejectZone Add a zone to the reject zone set.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:RemoveRejectZone(RejectZone)
function PLAYERTASKCONTROLLER:RemoveRejectZoneSet(RejectZone)
self:T(self.lid.."RemoveRejectZone")
if self.Intel then
self.Intel:RemoveRejectZone(RejectZone)
@@ -4480,20 +4441,6 @@ function PLAYERTASKCONTROLLER:RemoveRejectZone(RejectZone)
return self
end
--- [User] Remove a conflict zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Zone#ZONE ConflictZone Remove this zone from the conflict zone set.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone)
self:T(self.lid.."RemoveConflictZone")
if self.Intel then
self.Intel:RemoveConflictZone(ConflictZone)
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.
@@ -4606,7 +4553,7 @@ end
-- @param #PLAYERTASKCONTROLLER self
-- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 271 or {127,251}. There needs to be exactly the same number of modulations!
-- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies!
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone"
-- @param #string Gender (Optional) Defaults to "male"
-- @param #string Culture (Optional) Defaults to "en-US"
-- @param #number Port (Optional) Defaults to 5002
@@ -4620,7 +4567,7 @@ end
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend)
self:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" --
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" --
self.Gender = Gender or MSRS.gender or "male" --
self.Culture = Culture or MSRS.culture or "en-US" --
self.Port = Port or MSRS.port or 5002 --

View File

@@ -1715,26 +1715,6 @@ function TARGET:GetAverageCoordinate()
return nil
end
--- Get coordinates of all targets. (e.g. for a SET_STATIC)
-- @param #TARGET self
-- @return #table Table with coordinates of all targets.
function TARGET:GetCoordinates()
local coordinates={}
for _,_target in pairs(self.targets) do
local target=_target --#TARGET.Object
local coordinate=self:GetTargetCoordinate(target)
if coordinate then
table.insert(coordinates, coordinate)
end
end
return coordinates
end
--- Get heading of target.
-- @param #TARGET self
-- @return #number Heading of the target in degrees.
@@ -1988,21 +1968,6 @@ function TARGET:GetObject(RefCoordinate, Coalitions)
return nil
end
--- Get all target objects.
-- @param #TARGET self
-- @return #table List of target objects.
function TARGET:GetObjects()
local objects={}
for _,_target in pairs(self.targets) do
local target=_target --#TARGET.Object
table.insert(objects, target.Object)
end
return objects
end
--- Count alive objects.
-- @param #TARGET self
-- @param #TARGET.Object Target Target objective.

View File

@@ -72,7 +72,7 @@ end
--- Checks if a point is contained within the circle.
-- @param #table point The point to check
-- @return #boolean True if the point is contained, false otherwise
-- @return #bool 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,11 +226,6 @@ 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
@@ -242,11 +237,6 @@ 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

View File

@@ -352,7 +352,6 @@ 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

View File

@@ -73,11 +73,6 @@ 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)

View File

@@ -443,32 +443,28 @@ 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
-- 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
["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
-- 2025 changes
["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
["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
-- 2025 catalog changes
["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female
["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male
@@ -478,15 +474,14 @@ 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-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
["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
["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
@@ -502,36 +497,32 @@ MSRS.Voices = {
["it_IT_Standard_F"] = "it-IT-Standard-F", -- Male
},
Wavenet = {
["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
["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
-- 2025 changes
["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_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_O"] = 'en-GB-Wavenet-O', -- [12] 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
["en_US_Wavenet_A"] = 'en-US-Wavenet-N', -- [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
-- 2025 catalog changes
["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female
["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male
@@ -541,15 +532,14 @@ 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-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
["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
["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
@@ -563,134 +553,6 @@ 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
},
}
@@ -770,7 +632,7 @@ end
-- set the path to the exe file via @{#MSRS.SetPath}.
--
-- @param #MSRS self
-- @param #string Path Path to SRS directory. Default `C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio`.
-- @param #string Path Path to SRS directory. Default `C:\\Program Files\\DCS-SimpleRadio-Standalone`.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies.
-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations.
-- @param #string Backend Backend used: `MSRS.Backend.SRSEXE` (default) or `MSRS.Backend.GRPC`.
@@ -905,13 +767,13 @@ end
--- Set path to SRS install directory. More precisely, path to where the `DCS-SR-ExternalAudio.exe` is located.
-- @param #MSRS self
-- @param #string Path Path to the directory, where the sound file is located. Default is `C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio`.
-- @param #string Path Path to the directory, where the sound file is located. Default is `C:\\Program Files\\DCS-SimpleRadio-Standalone`.
-- @return #MSRS self
function MSRS:SetPath(Path)
self:F( {Path=Path} )
-- Set path.
self.path=Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.path=Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
-- Remove (back)slashes.
local n=1 ; local nmax=1000
@@ -1955,7 +1817,7 @@ end
--
-- -- Moose MSRS default Config
-- MSRS_Config = {
-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio", -- Path to SRS install directory.
-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- Path to SRS install directory.
-- Port = 5002, -- Port of SRS server. Default 5002.
-- Backend = "srsexe", -- Interface to SRS: "srsexe" or "grpc".
-- Frequency = {127, 243}, -- Default frequences. Must be a table 1..n entries!
@@ -1975,7 +1837,7 @@ end
-- -- Google Cloud
-- gcloud = {
-- voice = "en-GB-Standard-A", -- The Google Cloud voice to use (see https://cloud.google.com/text-to-speech/docs/voices).
-- credentials="C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\yourfilename.json", -- Full path to credentials JSON file (only for SRS-TTS.exe backend)
-- credentials="C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- Full path to credentials JSON file (only for SRS-TTS.exe backend)
-- key="Your access Key", -- Google API access key (only for DCS-gRPC backend)
-- },
-- -- Amazon Web Service
@@ -2043,7 +1905,7 @@ function MSRS:LoadConfigFile(Path,Filename)
local Self = self or MSRS --#MSRS
Self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
Self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
Self.port = MSRS_Config.Port or 5002
Self.backend = MSRS_Config.Backend or MSRS.Backend.SRSEXE
Self.frequencies = MSRS_Config.Frequency or {127,243}

View File

@@ -30,8 +30,6 @@
--- Governs multiple missions, the tasking and the reporting.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- Command centers govern missions, communicates the task assignments between human players of the coalition, and manages the menu flow.
-- It can assign a random task to a player when requested.
-- The commandcenter provides the facilitites to communicate between human players online, executing a task.

View File

@@ -5,8 +5,6 @@
-- The @{#DETECTION_MANAGER} class defines the core functions to report detected objects to groups.
-- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- 1.1) DETECTION_MANAGER constructor:
-- -----------------------------------
-- * @{#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance.

View File

@@ -28,8 +28,6 @@
--- Models goals to be achieved and can contain multiple tasks to be executed to achieve the goals.
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- A mission contains multiple tasks and can be of different task types.
-- These tasks need to be assigned to human players to be executed.
--

View File

@@ -12,8 +12,6 @@
--
-- ===
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- # 1) Tasking from a player perspective.
--
-- Tasking can be controlled by using the "other" menu in the radio menu of the player group.

View File

@@ -18,8 +18,6 @@
---
-- # TASKINFO class, extends @{Core.Base#BASE}
--
-- ![Banner Image](..\Images\deprecated.png)
--
-- ## The TASKINFO class implements the methods to contain information and display information of a task.
--
-- # Developer Note

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