mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-17 17:26:53 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1be2c92a5c | |||
| 4c8a17fe57 | |||
| 64dea17982 | |||
| b1f86c702d | |||
| 1a9540d0d4 | |||
| cdfaac527c | |||
| 4e3bf9969b | |||
| 74c16bd4f0 | |||
| 55e048b322 | |||
| b31fb7ece3 | |||
| 149e3421d9 | |||
| 338ec34604 | |||
| 3d79a16d3a | |||
| efa08c1280 | |||
| 589e65963f | |||
| af4f20af67 | |||
| b1b8630d21 | |||
| 98ed7d50c1 | |||
| 4ba81cb8bc | |||
| 626b12f486 |
@@ -735,6 +735,12 @@ do -- Event Handling
|
||||
-- @function [parent=#BASE] OnEventPlayerEnterAircraft
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when an option is changed for an AI group.
|
||||
-- The event identifies the initiating group, but does not provide the option ID or value.
|
||||
-- @function [parent=#BASE] OnEventGroupChangeOption
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a player creates a dynamic cargo object from the F8 ground crew menu.
|
||||
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
|
||||
@@ -880,6 +886,22 @@ end
|
||||
|
||||
world.onEvent(Event)
|
||||
end
|
||||
|
||||
--- Creation of a `S_EVENT_GROUP_CHANGE_OPTION` event.
|
||||
-- @param #BASE self
|
||||
-- @param DCS#Time EventTime The time stamp of the event.
|
||||
-- @param DCS#Group Initiator The initiating group of the event.
|
||||
function BASE:CreateEventGroupChangeOption( EventTime, Initiator )
|
||||
self:F( { EventTime, Initiator } )
|
||||
|
||||
local Event = {
|
||||
id = EVENTS.GroupChangeOption,
|
||||
time = EventTime,
|
||||
initiator = Initiator,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event.
|
||||
-- @param #BASE self
|
||||
|
||||
@@ -278,6 +278,7 @@ EVENTS = {
|
||||
SimulationUnfreeze = world.event.S_EVENT_SIMULATION_UNFREEZE or -1,
|
||||
HumanAircraftRepairStart = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_START or -1,
|
||||
HumanAircraftRepairFinish = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or -1,
|
||||
GroupChangeOption = world.event.S_EVENT_GROUP_CHANGE_OPTION or -1,
|
||||
-- dynamic cargo
|
||||
NewDynamicCargo = world.event.S_EVENT_NEW_DYNAMIC_CARGO or -1,
|
||||
DynamicCargoLoaded = world.event.S_EVENT_DYNAMIC_CARGO_LOADED or -1,
|
||||
@@ -291,23 +292,25 @@ EVENTS = {
|
||||
--
|
||||
-- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event.
|
||||
-- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.
|
||||
-- * A GROUP : A DCS Group object is involved in the Event.
|
||||
--
|
||||
-- @type EVENTDATA
|
||||
-- @field #number id The identifier of the event.
|
||||
--
|
||||
-- @field DCS#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{DCS#Unit} or @{DCS#StaticObject}.
|
||||
-- @field DCS#Unit initiator (UNIT/STATIC/SCENERY/GROUP) The initiating @{DCS#Unit} or @{DCS#StaticObject}. For `S_EVENT_GROUP_CHANGE_OPTION`, this is a DCS Group.
|
||||
-- @field DCS#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ).
|
||||
-- @field DCS#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCS#Unit} or @{DCS#StaticObject}.
|
||||
-- @field #string IniDCSUnitName (UNIT/STATIC) The initiating Unit name.
|
||||
-- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Wrapper.Unit#UNIT} of the initiator Unit object.
|
||||
-- @field #string IniUnitName (UNIT/STATIC) The initiating UNIT name (same as IniDCSUnitName).
|
||||
-- @field DCS#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}.
|
||||
-- @field #string IniDCSGroupName (UNIT) The initiating Group name.
|
||||
-- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object.
|
||||
-- @field #string IniGroupName UNIT) The initiating GROUP name (same as IniDCSGroupName).
|
||||
-- @field DCS#Group IniDCSGroup (UNIT/GROUP) The initiating @{DCS#Group}.
|
||||
-- @field #string IniDCSGroupName (UNIT/GROUP) The initiating Group name.
|
||||
-- @field Wrapper.Group#GROUP IniGroup (UNIT/GROUP) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object.
|
||||
-- @field #string IniGroupName (UNIT/GROUP) The initiating GROUP name (same as IniDCSGroupName).
|
||||
-- @field DCS#Group.Category IniGroupCategory (GROUP) The category of the initiating group.
|
||||
-- @field #string IniPlayerName (UNIT) The name of the initiating player in case the Unit is a client or player slot.
|
||||
-- @field #string IniPlayerUCID (UNIT) The UCID of the initiating player in case the Unit is a client or player slot and on a multi-player server.
|
||||
-- @field DCS#coalition.side IniCoalition (UNIT) The coalition of the initiator.
|
||||
-- @field DCS#coalition.side IniCoalition (UNIT/GROUP) The coalition of the initiator.
|
||||
-- @field DCS#Unit.Category IniCategory (UNIT) The category of the initiator.
|
||||
-- @field #string IniTypeName (UNIT) The type name of the initiator.
|
||||
--
|
||||
@@ -734,6 +737,12 @@ local _EVENTMETA = {
|
||||
Side = "I",
|
||||
Event = "OnEventHumanAircraftRepairFinish",
|
||||
Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH"
|
||||
},
|
||||
[EVENTS.GroupChangeOption] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventGroupChangeOption",
|
||||
Text = "S_EVENT_GROUP_CHANGE_OPTION"
|
||||
},
|
||||
-- dynamic cargo
|
||||
[EVENTS.NewDynamicCargo] = {
|
||||
@@ -1234,7 +1243,16 @@ function EVENT:onEvent( Event )
|
||||
|
||||
if Event.initiator then
|
||||
|
||||
Event.IniObjectCategory = Object.getCategory(Event.initiator)
|
||||
if Event.id == EVENTS.GroupChangeOption then
|
||||
Event.IniDCSGroup = Event.initiator
|
||||
Event.IniDCSGroupName = Group.getName(Event.initiator)
|
||||
Event.IniGroupName = Event.IniDCSGroupName
|
||||
Event.IniGroup = GROUP:FindByName(Event.IniDCSGroupName)
|
||||
Event.IniCoalition = Group.getCoalition(Event.initiator)
|
||||
Event.IniGroupCategory = Group.getCategory(Event.initiator)
|
||||
else
|
||||
Event.IniObjectCategory = Object.getCategory(Event.initiator)
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.STATIC then
|
||||
---
|
||||
|
||||
@@ -87,7 +87,8 @@ do -- world
|
||||
-- @field S_EVENT_SIMULATION_UNFREEZE = 58
|
||||
-- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_START = 59
|
||||
-- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH = 60
|
||||
-- @field S_EVENT_MAX = 61
|
||||
-- @field S_EVENT_GROUP_CHANGE_OPTION = 61
|
||||
-- @field S_EVENT_MAX = 62
|
||||
|
||||
--- The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events.
|
||||
-- @type world.BirthPlace
|
||||
@@ -614,7 +615,6 @@ do -- Object
|
||||
-- @field SCENERY
|
||||
-- @field CARGO
|
||||
|
||||
---
|
||||
-- @type Object.Desc
|
||||
-- @extends #Desc
|
||||
-- @field #number life initial life level
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,760 +0,0 @@
|
||||
--- **Functional** - Enhanced Warsaw Pact GCI Controller.
|
||||
--
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * Guide AI and human pilots in Warsaw Pact Style. GCI Kernel Functions.
|
||||
-- * Advanced Tactics for Groups.
|
||||
-- * Many additional events that the mission designer can hook into.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- ===
|
||||
-- @module Functional.REDGCI_KERNEL
|
||||
-- @image Func_RedGCI.png
|
||||
-- @version 1.0.0
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
--- REDGCI_KERNEL class
|
||||
-- @type REDGCI_KERNEL
|
||||
|
||||
|
||||
---
|
||||
-- # RedGCI — Soviet GCI Doctrine & Player Guide
|
||||
--
|
||||
-- ## Philosophy: Централизованное управление (Centralized Control)
|
||||
--
|
||||
-- The fundamental difference between Soviet and NATO GCI is **who makes the tactical decision**.
|
||||
--
|
||||
-- In NATO doctrine, the GCI controller provides situational awareness — bearing, range, altitude, aspect — and the pilot decides how to prosecute the intercept. The pilot is an autonomous tactician. GCI is an advisor.
|
||||
--
|
||||
-- In Soviet doctrine, the GCI controller **directs**. The pilot executes. The controller selects the intercept geometry, assigns the heading, manages the radar, calls weapons free, and coordinates multi-ship tactics. The pilot's job is to fly the numbers and shoot when told. This is not a flaw — it is the system working as designed. Soviet fighter pilots were trained to be precise executors of GCI instructions, not independent tacticians. The ground radar network (PVO) was the brain; the aircraft was the weapon.
|
||||
--
|
||||
-- RedGCI models this philosophy faithfully.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## What to Expect as a Player
|
||||
--
|
||||
-- ### You will not be asked what you want to do.
|
||||
--
|
||||
-- There are no "recommend a vector" calls, no "at your discretion" callouts. The controller tells you your heading, your altitude, and your task. Your acknowledgement is assumed.
|
||||
--
|
||||
-- ### The controller manages your radar.
|
||||
--
|
||||
-- You do not decide when to turn your radar on. The GCI will tell you when to switch on (`локатор` / `Radar on`). Before that call, you fly cold and silent. This preserves your emissions discipline and prevents the target from getting an early RWR spike.
|
||||
--
|
||||
-- ### Weapons free is a controlled event.
|
||||
--
|
||||
-- You do not engage until the controller clears you (`цель разрешена` / `WEAPONS FREE`). The controller determines when geometry, range, and aspect are favorable. Shooting early breaks the coordinated intercept and may compromise your wingman's attack.
|
||||
--
|
||||
-- ### Radio calls are short and military.
|
||||
--
|
||||
-- Soviet GCI brevity is terse by design. Expect calls like:
|
||||
--
|
||||
-- - `"Сокол, курс 170, высота 4500."` — vector, altitude
|
||||
-- - `"Сокол, цель, пара, истребитель. Локатор."` — picture call on commit: count, type, radar on
|
||||
-- - `"Сокол, захват. Дальность 20. Цель разрешена."` — lock confirmed, range, weapons free
|
||||
-- - `"Сокол, молодец. Домой."` — good kill, RTB
|
||||
--
|
||||
-- There are no "BOGEY DOPE" requests, no "BRAA" calls, no "DECLARE" queries. The controller has already done that work. You fly the vector.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## State Flow — What the GCI is Doing Behind the Scenes
|
||||
--
|
||||
-- RedGCI manages a state machine that progresses through six phases. Understanding these phases helps you anticipate what call is coming next.
|
||||
--
|
||||
-- ```
|
||||
-- VECTOR → COMMIT → RADAR_CONTACT → VISUAL → MERGE → (SPLASH / ABORT / RTB)
|
||||
-- ```
|
||||
--
|
||||
-- ### VECTOR
|
||||
-- The controller has a track. You are being vectored onto an intercept geometry. Your radar is off. The controller is solving a collision course and updating your heading every tick. Altitude calls reflect the intercept geometry — you may be sent below the target (classic Soviet shoot-up doctrine for radar-limited types) or level/above (MiG-29/Su-27 lookdown geometry). Expect heading updates every 10–15 seconds.
|
||||
--
|
||||
-- **What you should do:** Fly the heading. Don't deviate. Don't turn your radar on yet. Speed is expected at 900kph TAS (depending on airframe)
|
||||
--
|
||||
-- ### COMMIT
|
||||
-- Range has closed to approximately 30km. The controller calls the picture: count and type. Your radar comes on. You are now committed to the intercept — turning away is no longer the default option. The controller is building your radar geometry toward a lock.
|
||||
--
|
||||
-- **What you should do:** Activate your radar. Acquire the target. Do not fire yet.
|
||||
--
|
||||
-- ### RADAR_CONTACT
|
||||
-- You have radar lock (or the AI has achieved it). The controller confirms lock and calls range. If geometry and range are favorable, weapons free follows immediately. If not — for example if aspect angle is unfavorable for a stern conversion — the controller holds fire and waits for better geometry.
|
||||
--
|
||||
-- **What you should do:** Maintain lock. Track the target. Wait for the weapons free call.
|
||||
--
|
||||
-- ### VISUAL
|
||||
-- Range has closed to approximately 5km — visual conditions. Weapons free is automatic at this point. You are now in the merge envelope.
|
||||
--
|
||||
-- **What you should do:** Engage.
|
||||
--
|
||||
-- ### MERGE
|
||||
-- Inside 2km. The GCI transitions to merge control: bearing to target, overshoot calls, separation instructions, reattack vectors. At this range the controller cannot see fine-grained geometry — merge calls are based on relative bearing and closure.
|
||||
--
|
||||
-- **What you should do:** Fight. Listen for overshoot, separation, and reattack calls.
|
||||
--
|
||||
-- ### SPLASH / ABORT / RTB
|
||||
-- - `SPLASH` — kill confirmed, RTB
|
||||
-- - `ABORT (THREAT)` — your RWR is spiked or a threat geometry has developed; break off immediately on the given heading
|
||||
-- - `ABORT (BINGO)` — fuel state critical; break off and return
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## Multi-Ship (2v2) Tactics (REDGCI2v2)
|
||||
--
|
||||
-- When two fighters are dispatched against a threat, the GCI selects a tactic automatically based on the tactical situation. The tactic is applied at COMMIT — until then, both fighters are vectored together toward the intercept midpoint.
|
||||
--
|
||||
-- | Tactic | Description |
|
||||
-- |--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
-- | **PINCER** | Classic bracket. F1 and F2 split left and right, attacking from opposite angles simultaneously. Forces the target to choose which threat to react to. |
|
||||
-- | **HIGH-LOW** | Vertical split. One fighter attacks from below (radar up, clean sky background), one from above. Degrades the target's ability to acquire both simultaneously. |
|
||||
-- | **STAGGER** | BVR timing offset. F1 fires first at long range, F2 follows 8–13km behind to engage a maneuvering or defending target. |
|
||||
-- | **TRAIL** | Close trail. F1 is the shooter, F2 is support — ready to engage if F1 overshoots or is defeated. |
|
||||
-- | **GIRAFFE** | *(Historical — Iraq/Iran War, Mirage F1 vs F-14A)* F1 attacks at normal altitude, binding the AWG-9 radar. F2 flies nap-of-earth |
|
||||
-- | | (300–600m AGL) using ground clutter to degrade radar detection, then pulls up and fires from close range. |
|
||||
--
|
||||
-- During a tactic split, you may receive a heading that seems unusual — a large lateral offset or an unexpected altitude change. **Trust the vector.** The controller is positioning you for the tactic geometry. The merge point will bring you back onto the target.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## Dispatcher & CAP Flow (REDGCI_DISPATCHER)
|
||||
--
|
||||
-- When using the dispatcher layer, the full operational flow is:
|
||||
--
|
||||
-- ```
|
||||
-- Spawn at homeplate
|
||||
-- → Taxi and takeoff (template-controlled)
|
||||
-- → Transit to CAP zone
|
||||
-- → Orbit in assigned zone (radar cold, weapons safe)
|
||||
-- ↓ INTEL detects threat cluster
|
||||
-- → "Attention, radar contact. Pair, fighter, 45 kilometers." (all CAP fighters)
|
||||
-- ↓ Dispatcher assigns pair
|
||||
-- → "101 102, intercept. Pair, fighter." (dispatched pair)
|
||||
-- → VECTOR → COMMIT → RADAR_CONTACT → VISUAL → MERGE → SPLASH
|
||||
-- ↓ Engagement complete
|
||||
-- → AI: RTB waypoint → land → despawn → respawn after delay
|
||||
-- → Human: "101, mission complete. RTB, refuel and rearm."
|
||||
-- ↓ After RespawnDelay
|
||||
-- → New AI pair spawns into same CAP zone
|
||||
-- ```
|
||||
--
|
||||
-- Human players are dispatched first when available. If a human and AI are both in the CAP pool, the human is always assigned to the next intercept. AI fills gaps. The dispatcher does not send a single fighter if a pair is available — pairing is always preferred.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## Key Differences from NATO GCI at a Glance
|
||||
--
|
||||
-- | | Soviet (RedGCI) | NATO |
|
||||
-- |--------------------|--------------------------------------|------------------------------------|
|
||||
-- | Tactical decision | Controller | Pilot |
|
||||
-- | Radar management | Controller-commanded | Pilot-initiated |
|
||||
-- | Weapons free | Controller-called | Pilot-discretion (after WF) |
|
||||
-- | Heading calls | Prescriptive | Advisory |
|
||||
-- | Brevity style | Terse, military, positional | Standardized (BRAA, DECLARE, etc.) |
|
||||
-- | Multi-ship tactics | Centrally planned, applied at COMMIT | Mutually briefed, pilot-executed |
|
||||
-- | Pilot autonomy | Low (by design) | High |
|
||||
--
|
||||
-- **The Soviet system is not inferior** — it is optimized for a different kind of pilot and a different operational context. Mass interception of large NATO strike packages over defended Soviet airspace demanded centralized, efficient, high-throughput GCI control. RedGCI brings that experience to DCS.
|
||||
--
|
||||
-- @field #REDGCI_KERNEL
|
||||
REDGCI_KERNEL = {}
|
||||
REDGCI_KERNEL.version = "1.0.0"
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- Konstanten (aus gci_types.h)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
REDGCI_KERNEL.C = {
|
||||
RANGE_VECTOR_START = 60000.0,
|
||||
RANGE_COMMIT = 30000.0,
|
||||
RANGE_RADAR_FLOOR = 20000.0,
|
||||
RANGE_VISUAL = 5000.0,
|
||||
RANGE_MERGE = 2000.0,
|
||||
WF_RANGE_MAX = 25000.0,
|
||||
ALT_OFFSET_LOOKDOWN = 0.0,
|
||||
ASPECT_NOTCH_MIN = 80.0,
|
||||
ASPECT_NOTCH_MAX = 100.0,
|
||||
ASPECT_REAR_ATTACK = 120.0,
|
||||
MAX_TTI = 600.0,
|
||||
TICK_INTERVAL = 15.0,
|
||||
FUEL_BINGO = 0.25,
|
||||
DELAY_MIN = 3.0,
|
||||
DELAY_MAX = 8.0,
|
||||
DELAY_MERGE_MIN = 2.0,
|
||||
DELAY_MERGE_MAX = 4.0,
|
||||
TACTIC_PINCER = 0,
|
||||
TACTIC_HIGH_LOW = 1,
|
||||
TACTIC_STAGGER = 2,
|
||||
TACTIC_TRAIL = 3,
|
||||
TACTIC_GIRAFFE = 4, -- Irak/Iran-Doktrin: F1=Decoy normal, F2=Nap-of-Earth im Groundclutter
|
||||
PURSUIT_COLLISION = 0,
|
||||
PURSUIT_LEAD = 1,
|
||||
PURSUIT_PURE = 2,
|
||||
PURSUIT_NO_SOLUTION = 3,
|
||||
}
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- Hilfsfunktionen
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.clamp(v, lo, hi)
|
||||
if v < lo then return lo end
|
||||
if v > hi then return hi end
|
||||
return v
|
||||
end
|
||||
|
||||
function REDGCI_KERNEL.vec2len(x, z)
|
||||
return math.sqrt(x*x + z*z)
|
||||
end
|
||||
|
||||
function REDGCI_KERNEL.bearing(dx, dz)
|
||||
local b = math.deg(math.atan2(dx, dz))
|
||||
return b < 0.0 and b + 360.0 or b
|
||||
end
|
||||
|
||||
function REDGCI_KERNEL.randDelay(lo, hi)
|
||||
return lo + UTILS.LCGRandom() * (hi - lo)
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- Aspect Angle (0=Nose-on, 90=Beam, 180=Tail)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.aspectAngle(target, observer)
|
||||
local dx = observer.x - target.x
|
||||
local dz = observer.z - target.z
|
||||
local range = REDGCI_KERNEL.vec2len(dx, dz)
|
||||
if range < 1.0 then return 0.0 end
|
||||
local nx = dx / range
|
||||
local nz = dz / range
|
||||
local spd = (target.speed or 1.0) + 1e-6
|
||||
local tvx = (target.vx or 0.0) / spd
|
||||
local tvz = (target.vz or 0.0) / spd
|
||||
local dot = REDGCI_KERNEL.clamp(tvx*nx + tvz*nz, -1.0, 1.0)
|
||||
return math.deg(math.acos(dot))
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- Closure Rate (positiv = Annäherung)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.closureRate(f, t)
|
||||
local dx = t.x - f.x
|
||||
local dz = t.z - f.z
|
||||
local range = REDGCI_KERNEL.vec2len(dx, dz)
|
||||
if range < 1.0 then return 0.0 end
|
||||
local nx = dx / range
|
||||
local nz = dz / range
|
||||
local dvx = (t.vx or 0.0) - (f.vx or 0.0)
|
||||
local dvz = (t.vz or 0.0) - (f.vz or 0.0)
|
||||
return -(dvx*nx + dvz*nz)
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- _solveCollision (intern, GCI-Koordinaten)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL._solveCollision(f, t)
|
||||
local C = REDGCI_KERNEL.C
|
||||
local dx = t.x - f.x
|
||||
local dz = t.z - f.z
|
||||
local vtx = t.vx or 0.0
|
||||
local vtz = t.vz or 0.0
|
||||
local vf = f.speed or 1.0
|
||||
local a = vtx*vtx + vtz*vtz - vf*vf
|
||||
local b = 2.0 * (dx*vtx + dz*vtz)
|
||||
local c = dx*dx + dz*dz
|
||||
local sol_t = -1.0
|
||||
if math.abs(a) < 1.0 then
|
||||
if math.abs(b) > 0.01 then
|
||||
sol_t = -c / b
|
||||
else
|
||||
return false
|
||||
end
|
||||
else
|
||||
local disc = b*b - 4.0*a*c
|
||||
if disc < 0.0 then return false end
|
||||
local sq = math.sqrt(disc)
|
||||
local t1 = (-b - sq) / (2.0 * a)
|
||||
local t2 = (-b + sq) / (2.0 * a)
|
||||
if t1 > 0.0 and t2 > 0.0 then sol_t = math.min(t1, t2)
|
||||
elseif t1 > 0.0 then sol_t = t1
|
||||
elseif t2 > 0.0 then sol_t = t2
|
||||
else return false
|
||||
end
|
||||
end
|
||||
if sol_t < 0.0 or sol_t > C.MAX_TTI then return false end
|
||||
local ip = {
|
||||
x = t.x + vtx * sol_t,
|
||||
z = t.z + vtz * sol_t,
|
||||
y = math.max(t.y + C.ALT_OFFSET_LOOKDOWN, 300.0),
|
||||
}
|
||||
local hdg = REDGCI_KERNEL.bearing(ip.x - f.x, ip.z - f.z)
|
||||
return true, hdg, sol_t, ip
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- _solveLead (intern, GCI-Koordinaten)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL._solveLead(f, t)
|
||||
local C = REDGCI_KERNEL.C
|
||||
local dx = t.x - f.x
|
||||
local dz = t.z - f.z
|
||||
local range = REDGCI_KERNEL.vec2len(dx, dz)
|
||||
if range < 1.0 then return 0.0, 0.0 end
|
||||
local base_bearing = REDGCI_KERNEL.bearing(dx, dz)
|
||||
local aspect_rad = math.rad(REDGCI_KERNEL.aspectAngle(t, f))
|
||||
local speed_ratio = (t.speed or 1.0) / ((f.speed or 1.0) + 1e-6)
|
||||
local sin_lead = REDGCI_KERNEL.clamp(speed_ratio * math.sin(aspect_rad), -1.0, 1.0)
|
||||
local lead_deg = math.deg(math.asin(sin_lead))
|
||||
local hdg
|
||||
if math.abs(lead_deg) < 45.0 then
|
||||
hdg = (base_bearing + lead_deg + 360.0) % 360.0
|
||||
else
|
||||
hdg = base_bearing
|
||||
end
|
||||
local closing = REDGCI_KERNEL.closureRate(f, t)
|
||||
if closing < 50.0 then closing = 50.0 end
|
||||
local tti = math.min(range / closing, C.MAX_TTI)
|
||||
return hdg, tti
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- computeIntercept (GCI-Koordinaten intern)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.computeIntercept(fighter, target)
|
||||
local C = REDGCI_KERNEL.C
|
||||
local dx = target.x - fighter.x
|
||||
local dz = target.z - fighter.z
|
||||
local sol = {
|
||||
heading_deg = 0.0,
|
||||
time_to_intercept = C.MAX_TTI,
|
||||
intercept_point = { x=target.x, z=target.z, y=target.y },
|
||||
target_alt = target.y,
|
||||
solution_found = false,
|
||||
aspect_angle = REDGCI_KERNEL.aspectAngle(target, fighter),
|
||||
range = REDGCI_KERNEL.vec2len(dx, dz),
|
||||
mode = C.PURSUIT_NO_SOLUTION,
|
||||
weapons_free = false,
|
||||
}
|
||||
local closure = REDGCI_KERNEL.closureRate(fighter, target)
|
||||
local projected_range = sol.range - closure * C.TICK_INTERVAL
|
||||
sol.weapons_free = (sol.range < C.WF_RANGE_MAX) or
|
||||
(closure > 0.0 and projected_range < C.WF_RANGE_MAX)
|
||||
local ok, hdg, tti, ip = REDGCI_KERNEL._solveCollision(fighter, target)
|
||||
if ok then
|
||||
sol.heading_deg = hdg
|
||||
sol.time_to_intercept = tti
|
||||
sol.intercept_point = ip
|
||||
sol.solution_found = true
|
||||
sol.mode = C.PURSUIT_COLLISION
|
||||
return sol
|
||||
end
|
||||
local lhdg, ltti = REDGCI_KERNEL._solveLead(fighter, target)
|
||||
sol.heading_deg = lhdg
|
||||
sol.time_to_intercept = ltti
|
||||
sol.solution_found = true
|
||||
sol.mode = C.PURSUIT_LEAD
|
||||
sol.intercept_point = {
|
||||
x = target.x + (target.vx or 0.0) * ltti,
|
||||
z = target.z + (target.vz or 0.0) * ltti,
|
||||
y = math.max(target.y + C.ALT_OFFSET_LOOKDOWN, 300.0),
|
||||
}
|
||||
return sol
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- computeInterceptDCS (DCS-Koordinaten Ein/Aus)
|
||||
-- Flat-Rückgabe kompatibel mit alter C-API
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.computeInterceptDCS(f, t)
|
||||
local f_gci = { x=f.z, z=f.x, y=f.y, vx=f.vz, vz=f.vx, vy=f.vy, speed=f.spd }
|
||||
local t_gci = { x=t.z, z=t.x, y=t.y, vx=t.vz, vz=t.vx, vy=t.vy, speed=t.spd }
|
||||
local sol = REDGCI_KERNEL.computeIntercept(f_gci, t_gci)
|
||||
-- GCI→DCS: ip.z(Nord)→DCS.x, ip.x(Ost)→DCS.z
|
||||
return sol.heading_deg,
|
||||
sol.time_to_intercept,
|
||||
sol.mode,
|
||||
sol.weapons_free,
|
||||
sol.range,
|
||||
sol.aspect_angle,
|
||||
sol.intercept_point.z,
|
||||
sol.intercept_point.x,
|
||||
sol.intercept_point.y,
|
||||
sol.target_alt
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- computeSplit (GCI-Koordinaten intern)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.computeSplit(f1, f2, tgt, tactic, variation)
|
||||
local C = REDGCI_KERNEL.C
|
||||
variation = REDGCI_KERNEL.clamp(variation or 0.5, 0.0, 1.0)
|
||||
tactic = tactic or C.TACTIC_PINCER
|
||||
local mid_x = (f1.x + f2.x) * 0.5
|
||||
local mid_z = (f1.z + f2.z) * 0.5
|
||||
local dx = tgt.x - mid_x
|
||||
local dz = tgt.z - mid_z
|
||||
local rng = REDGCI_KERNEL.vec2len(dx, dz)
|
||||
if rng < 1.0 then rng = 1.0 end
|
||||
local ax = dx / rng
|
||||
local az = dz / rng
|
||||
local px = -az
|
||||
local pz = ax
|
||||
|
||||
local plan = {
|
||||
tactic = tactic,
|
||||
wp_f1 = { x=0, z=0, y=0 },
|
||||
wp_f2 = { x=0, z=0, y=0 },
|
||||
merge_f1 = { x=0, z=0, y=0 },
|
||||
merge_f2 = { x=0, z=0, y=0 },
|
||||
}
|
||||
|
||||
local function clamp_alt(pt)
|
||||
if pt.y < 300.0 then pt.y = 300.0 end
|
||||
end
|
||||
|
||||
local function clamp_alt_noe(pt)
|
||||
if pt.y < 150.0 then pt.y = 150.0 end
|
||||
end
|
||||
|
||||
-- GIRAFFE F2 fliegt NOE — separater clamp mit niedrigerer Mindesthöhe
|
||||
local function clamp_alt_noe(pt)
|
||||
if pt.y < 150.0 then pt.y = 150.0 end
|
||||
end
|
||||
|
||||
if tactic == C.TACTIC_PINCER then
|
||||
local spread = 12000.0 + variation * 5000.0
|
||||
local approach = rng * 0.45
|
||||
local merge_off = spread * 0.30
|
||||
plan.wp_f1 = { x=mid_x+ax*approach+px*spread, z=mid_z+az*approach+pz*spread, y=tgt.y }
|
||||
plan.wp_f2 = { x=mid_x+ax*approach-px*spread, z=mid_z+az*approach-pz*spread, y=tgt.y }
|
||||
plan.merge_f1 = { x=tgt.x-ax*3000+px*merge_off, z=tgt.z-az*3000+pz*merge_off, y=tgt.y }
|
||||
plan.merge_f2 = { x=tgt.x-ax*3000-px*merge_off, z=tgt.z-az*3000-pz*merge_off, y=tgt.y }
|
||||
|
||||
elseif tactic == C.TACTIC_HIGH_LOW then
|
||||
local vert = 3000.0 + variation * 1500.0
|
||||
local approach = rng * 0.50
|
||||
local side_off = 2000.0
|
||||
plan.wp_f1 = { x=mid_x+ax*approach+px*side_off, z=mid_z+az*approach+pz*side_off, y=tgt.y-500.0 }
|
||||
plan.wp_f2 = { x=mid_x+ax*approach-px*side_off, z=mid_z+az*approach-pz*side_off, y=tgt.y+vert }
|
||||
plan.merge_f1 = { x=tgt.x-ax*3000+px*side_off, z=tgt.z-az*3000+pz*side_off, y=plan.wp_f1.y }
|
||||
plan.merge_f2 = { x=tgt.x-ax*3000-px*side_off, z=tgt.z-az*3000-pz*side_off, y=plan.wp_f2.y }
|
||||
|
||||
elseif tactic == C.TACTIC_STAGGER then
|
||||
local lag = 8000.0 + variation * 3000.0
|
||||
local lead_dist = rng * 0.85
|
||||
plan.wp_f1 = { x=mid_x+ax*lead_dist, z=mid_z+az*lead_dist, y=tgt.y }
|
||||
plan.wp_f2 = { x=mid_x+ax*(lead_dist-lag), z=mid_z+az*(lead_dist-lag), y=tgt.y }
|
||||
plan.merge_f1 = { x=plan.wp_f1.x, z=plan.wp_f1.z, y=plan.wp_f1.y }
|
||||
plan.merge_f2 = { x=plan.wp_f2.x, z=plan.wp_f2.z, y=plan.wp_f2.y }
|
||||
|
||||
elseif tactic == C.TACTIC_TRAIL then
|
||||
local lag = 3000.0 + variation * 2000.0
|
||||
local lead_dist = rng * 0.85
|
||||
local side_off = 500.0
|
||||
plan.wp_f1 = { x=mid_x+ax*lead_dist, z=mid_z+az*lead_dist, y=tgt.y }
|
||||
plan.wp_f2 = { x=mid_x+ax*(lead_dist-lag)+px*side_off, z=mid_z+az*(lead_dist-lag)+pz*side_off, y=tgt.y }
|
||||
plan.merge_f1 = { x=plan.wp_f1.x, z=plan.wp_f1.z, y=plan.wp_f1.y }
|
||||
plan.merge_f2 = { x=plan.wp_f2.x, z=plan.wp_f2.z, y=plan.wp_f2.y }
|
||||
|
||||
elseif tactic == C.TACTIC_GIRAFFE then
|
||||
-- ── GIRAFFE: Irak/Iran-Doktrin (Mirage F1 vs F-14A) ──────────────
|
||||
--
|
||||
-- F1 = Decoy: normale Höhe, direkter Anflug, bindet AWG-9 Radar.
|
||||
-- Zieht Phoenixe auf sich — gibt F2 Zeit für Pull-up.
|
||||
--
|
||||
-- F2 = Killer: Nap-of-Earth (~300-600m AGL/MSL) weit seitlich versetzt.
|
||||
-- Nutzt Groundclutter um AWG-9 Look-Down zu degradieren.
|
||||
-- Nähert sich bis 10km vor Ziel, dann Pull-up und Schuss.
|
||||
-- Seitlicher Versatz ~8-12km damit F2 nicht im Radarsektor
|
||||
-- des AWG-9 liegt während F1 den Lock hält.
|
||||
--
|
||||
-- Historisch: Iran-Irak Krieg 1982-88. Irakische Mirage F1EQ nutzten
|
||||
-- dieses Profil gegen F-14A/AIM-54 Phoenix — der AWG-9 hatte trotz
|
||||
-- Look-Down-Fähigkeit bei sehr tiefen Zielen über unebenen Terrain
|
||||
-- (Zagros-Berge, Khuzestan-Ebene) erhöhte Clutter-Probleme.
|
||||
--
|
||||
-- Höhen: neutral (0.0) — Lua addiert AltOffset + terrain-awareness.
|
||||
-- Mindesthöhe 300m MSL wird am Ende geclampt.
|
||||
|
||||
local noe_alt = 300.0 + variation * 300.0 -- 300-600m MSL (Nap-of-Earth)
|
||||
local side_off = 8000.0 + variation * 4000.0 -- 8-12km seitlich (aus AWG-9 Sektor)
|
||||
local approach = rng * 0.60 -- F1 direkter Anflug, 60% des Weges
|
||||
local noe_dist = rng * 0.75 -- F2 nähert sich weiter ran vor Pull-up
|
||||
|
||||
plan.wp_f1 = { x=mid_x+ax*approach+px*2000, z=mid_z+az*approach+pz*2000, y=tgt.y }
|
||||
plan.merge_f1 = { x=tgt.x-ax*5000+px*2000, z=tgt.z-az*5000+pz*2000, y=tgt.y }
|
||||
plan.wp_f2 = { x=mid_x+ax*noe_dist-px*side_off, z=mid_z+az*noe_dist-pz*side_off, y=noe_alt, absolute_alt=true }
|
||||
plan.merge_f2 = { x=tgt.x-ax*10000-px*side_off*0.3, z=tgt.z-az*10000-pz*side_off*0.3, y=tgt.y }
|
||||
|
||||
else
|
||||
plan.wp_f1 = { x=tgt.x, z=tgt.z, y=tgt.y }
|
||||
plan.wp_f2 = { x=tgt.x, z=tgt.z, y=tgt.y }
|
||||
plan.merge_f1 = { x=tgt.x, z=tgt.z, y=tgt.y }
|
||||
plan.merge_f2 = { x=tgt.x, z=tgt.z, y=tgt.y }
|
||||
end
|
||||
|
||||
clamp_alt(plan.wp_f1)
|
||||
if tactic == C.TACTIC_GIRAFFE then
|
||||
clamp_alt_noe(plan.wp_f2)
|
||||
else
|
||||
clamp_alt(plan.wp_f2)
|
||||
end
|
||||
clamp_alt(plan.merge_f1)
|
||||
clamp_alt(plan.merge_f2)
|
||||
return plan
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- computeSplitDCS (DCS-Koordinaten Ein/Aus)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.computeSplitDCS(f1, f2, tgt, tactic, variation)
|
||||
local function dcs2gci(u)
|
||||
return { x=u.z, z=u.x, y=u.y, vx=u.vz or 0, vz=u.vx or 0, speed=u.spd or 0 }
|
||||
end
|
||||
local function gci2dcs(pt)
|
||||
return { x=pt.z, z=pt.x, y=pt.y, absolute_alt=pt.absolute_alt }
|
||||
end
|
||||
local tgt_gci = { x=tgt.z, z=tgt.x, y=tgt.y }
|
||||
local plan = REDGCI_KERNEL.computeSplit(
|
||||
dcs2gci(f1), dcs2gci(f2), tgt_gci, tactic, variation)
|
||||
plan.wp_f1 = gci2dcs(plan.wp_f1)
|
||||
plan.wp_f2 = gci2dcs(plan.wp_f2)
|
||||
plan.merge_f1 = gci2dcs(plan.merge_f1)
|
||||
plan.merge_f2 = gci2dcs(plan.merge_f2)
|
||||
return plan
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- buildTransmission (Port von gci_build_transmission)
|
||||
--
|
||||
-- ctx/prev Felder: state, prev_state, ticks_in_state, range,
|
||||
-- aspect_angle, closure_rate, altitude_delta, fuel_fraction
|
||||
-- sol Felder: heading_deg, time_to_intercept, target_alt,
|
||||
-- range, aspect_angle, weapons_free
|
||||
-- Rückgabe: { token_str, delay_sec, weapons_free, silence }
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.buildTransmission(ctx, prev, sol)
|
||||
local C = REDGCI_KERNEL.C
|
||||
local tx = {
|
||||
token_str = "",
|
||||
delay_sec = REDGCI_KERNEL.clamp(
|
||||
REDGCI_KERNEL.randDelay(C.DELAY_MIN, C.DELAY_MAX), 3.0, 8.0),
|
||||
weapons_free = sol.weapons_free,
|
||||
silence = false,
|
||||
priority = 50,
|
||||
}
|
||||
|
||||
local hdg_i = math.floor((sol.heading_deg or 0) + 0.5)
|
||||
local alt_i = math.floor((sol.target_alt or 0) / 100.0 + 0.5) * 100
|
||||
local rng_km = math.floor((ctx.range or 0) / 1000.0 + 0.5)
|
||||
local aspect_i = math.floor((ctx.aspect_angle or 0) + 0.5)
|
||||
local delay = tx.delay_sec
|
||||
local state = ctx.state or "VECTOR"
|
||||
local prev_state = prev.state or "VECTOR"
|
||||
local ticks = ctx.ticks_in_state or 0
|
||||
|
||||
local function emit(fmt, ...)
|
||||
tx.token_str = string.format(fmt, ...)
|
||||
end
|
||||
|
||||
if state == "VECTOR" then
|
||||
if prev_state ~= "VECTOR" then
|
||||
local tti_m = math.floor((sol.time_to_intercept or 0) / 60.0)
|
||||
local tti_s = math.floor(sol.time_to_intercept or 0) % 60
|
||||
if tti_m > 0 then
|
||||
emit("VECTOR_WITH_TTI|hdg=%d|alt=%d|rng=%d|tti_m=%d|tti_s=%d|delay=%.1f",
|
||||
hdg_i, alt_i, rng_km, tti_m, tti_s, delay)
|
||||
else
|
||||
emit("VECTOR|hdg=%d|alt=%d|rng=%d|delay=%.1f",
|
||||
hdg_i, alt_i, rng_km, delay)
|
||||
end
|
||||
else
|
||||
local hdg_delta = math.abs((sol.heading_deg or 0) - (ctx.aspect_angle or 0))
|
||||
if hdg_delta < 5.0 and ticks > 3 then
|
||||
tx.silence = true
|
||||
else
|
||||
emit("VECTOR|hdg=%d|alt=%d|rng=%d|delay=%.1f",
|
||||
hdg_i, alt_i, rng_km, delay)
|
||||
end
|
||||
end
|
||||
|
||||
elseif state == "COMMIT" then
|
||||
if prev_state ~= "COMMIT" then
|
||||
tx.priority = 100
|
||||
emit("COMMIT_FIRST|hdg=%d|alt=%d|rng=%d|aspect=%d|delay=%.1f",
|
||||
hdg_i, alt_i, rng_km, aspect_i, delay)
|
||||
elseif ticks == 6 then
|
||||
emit("COMMIT_NO_LOCK|hdg=%d|rng=%d|aspect=%d|delay=%.1f",
|
||||
hdg_i, rng_km, aspect_i, delay)
|
||||
elseif ticks > 8 then
|
||||
emit("COMMIT_NUDGE|hdg=%d|aspect=%d|delay=%.1f",
|
||||
hdg_i, aspect_i, delay)
|
||||
else
|
||||
tx.silence = true
|
||||
end
|
||||
|
||||
elseif state == "RADAR_CONTACT" then
|
||||
if prev_state ~= "RADAR_CONTACT" then
|
||||
if sol.weapons_free then
|
||||
tx.priority = 100
|
||||
emit("RADAR_LOCK_WF|rng=%d|delay=%.1f", rng_km, delay)
|
||||
tx.weapons_free = true
|
||||
else
|
||||
emit("RADAR_LOCK_HOLD|rng=%d|delay=%.1f", rng_km, delay)
|
||||
end
|
||||
elseif not sol.weapons_free
|
||||
and (ctx.range or 0) < C.WF_RANGE_MAX
|
||||
and (ctx.aspect_angle or 0) > C.ASPECT_REAR_ATTACK
|
||||
and prev_state == "RADAR_CONTACT" then
|
||||
emit("RADAR_WF_NOW|rng=%d|delay=%.1f", rng_km, delay)
|
||||
tx.priority = 100
|
||||
tx.weapons_free = true
|
||||
else
|
||||
tx.silence = true
|
||||
end
|
||||
|
||||
elseif state == "VISUAL" then
|
||||
if prev_state ~= "VISUAL" then
|
||||
emit("VISUAL_CONFIRM|rng=%d|delay=%.1f", rng_km, delay)
|
||||
tx.weapons_free = true
|
||||
else
|
||||
tx.silence = true
|
||||
end
|
||||
|
||||
elseif state == "NOTCH" then
|
||||
if prev_state ~= "NOTCH" then
|
||||
emit("NOTCH_ENTRY|delay=1.5")
|
||||
elseif ticks % 8 == 0 then
|
||||
emit("NOTCH_UPDATE|rng=%d|aspect=%d|delay=%.1f",
|
||||
rng_km, aspect_i, delay)
|
||||
else
|
||||
tx.silence = true
|
||||
end
|
||||
|
||||
elseif state == "ABORT" then
|
||||
tx.delay_sec = 1.5
|
||||
if (ctx.fuel_fraction or 1.0) < C.FUEL_BINGO then
|
||||
emit("ABORT_BINGO|hdg=%d|delay=1.5", hdg_i)
|
||||
else
|
||||
emit("ABORT_THREAT|hdg=%d|delay=1.5", hdg_i)
|
||||
end
|
||||
|
||||
else
|
||||
tx.silence = true
|
||||
end
|
||||
|
||||
return tx
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- mergeTransition (Port von gci_merge_transition)
|
||||
--
|
||||
-- ctx Felder: phase, ticks_in_phase, range, bearing_to_target,
|
||||
-- closure_rate, altitude_delta, pass_count, radar_lost
|
||||
-- Rückgabe: phase string
|
||||
-- "ENTRY"|"OVERSHOOT"|"SEPARATION"|"REATTACK"|"LOST"|"SPLASH"
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.mergeTransition(ctx, prev)
|
||||
if ctx.radar_lost then return "LOST" end
|
||||
if (ctx.closure_rate or 0) < -30.0 and (ctx.range or 0) > 3000.0 then
|
||||
return "SEPARATION"
|
||||
end
|
||||
local brg = ctx.bearing_to_target or 0
|
||||
if brg > 100.0 and brg < 260.0 and (ctx.range or 0) < 5000.0 then
|
||||
return "OVERSHOOT"
|
||||
end
|
||||
if ctx.phase == "SEPARATION" and (ctx.pass_count or 0) < 3 then
|
||||
return "REATTACK"
|
||||
end
|
||||
return ctx.phase
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- buildMergeTransmission (Port von gci_build_merge_transmission)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
|
||||
function REDGCI_KERNEL.buildMergeTransmission(ctx, prev)
|
||||
local C = REDGCI_KERNEL.C
|
||||
local tx = {
|
||||
token_str = "",
|
||||
delay_sec = REDGCI_KERNEL.clamp(
|
||||
REDGCI_KERNEL.randDelay(C.DELAY_MERGE_MIN, C.DELAY_MERGE_MAX), 2.0, 5.0),
|
||||
weapons_free = false,
|
||||
silence = false,
|
||||
priority = 50,
|
||||
}
|
||||
|
||||
local brg = math.floor((ctx.bearing_to_target or 0) + 0.5)
|
||||
local rng_km = math.floor((ctx.range or 0) / 1000.0 + 0.5)
|
||||
local delay = tx.delay_sec
|
||||
local phase = ctx.phase or "ENTRY"
|
||||
local prev_phase = prev.phase or "ENTRY"
|
||||
local ticks = ctx.ticks_in_phase or 0
|
||||
|
||||
local dir_rl = ((ctx.bearing_to_target or 0) < 180.0) and "right" or "left"
|
||||
local alt_d = ctx.altitude_delta or 0
|
||||
local alt_rel = ""
|
||||
if alt_d > 400.0 then alt_rel = "low"
|
||||
elseif alt_d < -400.0 then alt_rel = "high"
|
||||
end
|
||||
|
||||
local function emit(fmt, ...)
|
||||
tx.token_str = string.format(fmt, ...)
|
||||
end
|
||||
|
||||
if phase == "ENTRY" then
|
||||
tx.priority = 100
|
||||
emit("MERGE_ENTRY|brg=%d|dir_rl=%s|delay=%.1f", brg, dir_rl, delay)
|
||||
|
||||
elseif phase == "OVERSHOOT" then
|
||||
emit("MERGE_OVERSHOOT|brg=%d|dir_rl=%s|alt_rel=%s|delay=%.1f",
|
||||
brg, dir_rl, alt_rel, delay)
|
||||
|
||||
elseif phase == "SEPARATION" then
|
||||
if (ctx.pass_count or 0) < 3 then
|
||||
emit("MERGE_REATTACK|brg=%d|rng=%d|delay=%.1f", brg, rng_km, delay)
|
||||
else
|
||||
emit("ABORT_THREAT|hdg=%d|delay=%.1f", brg, delay)
|
||||
end
|
||||
|
||||
elseif phase == "REATTACK" then
|
||||
if prev_phase ~= "REATTACK" or ticks % 3 == 0 then
|
||||
emit("MERGE_REATTACK|brg=%d|rng=%d|delay=%.1f", brg, rng_km, delay)
|
||||
else
|
||||
tx.silence = true
|
||||
end
|
||||
|
||||
elseif phase == "LOST" then
|
||||
if prev_phase ~= "LOST" or ticks == 4 then
|
||||
emit("MERGE_LOST|brg=%d|rng=%d|delay=%.1f", brg, rng_km, delay)
|
||||
else
|
||||
tx.silence = true
|
||||
end
|
||||
|
||||
elseif phase == "SPLASH" then
|
||||
tx.delay_sec = 1.5
|
||||
tx.priority = 100
|
||||
emit("MERGE_SPLASH|delay=1.5")
|
||||
|
||||
else
|
||||
tx.silence = true
|
||||
end
|
||||
|
||||
return tx
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- END of Class
|
||||
-------------------------------------------------------------------------------
|
||||
@@ -80,11 +80,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Stratego.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ClientWatch.lua')
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Formation.lua')
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCIKernel.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCI.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCI2v2.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCIDispatcher.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RescueHelo.lua' )
|
||||
|
||||
@@ -3747,16 +3747,16 @@ function CTLD:_C130GetUnits(Group, Unit, Name)
|
||||
return self
|
||||
end
|
||||
|
||||
local coord = Unit:GetCoordinate() or Group:GetCoordinate()
|
||||
local coord = Unit:GetCoord() or Group:GetCoord()
|
||||
local capabilities = self:_GetUnitCapabilities(Unit)
|
||||
local innerDist = (capabilities.length and capabilities.length/2) or 15
|
||||
local maxUnitsNearby = self.maxUnitsNearby or 3
|
||||
local searchRadius = self.UnitDistance or 90
|
||||
local checkZone = ZONE_RADIUS:New("CTLD_C130UnitsZone",coord:GetVec2(),searchRadius,false)
|
||||
local nearGroups = SET_GROUP:New():FilterCoalitions("blue"):FilterZones({checkZone}):FilterOnce()
|
||||
local nearGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterZones({checkZone}):FilterOnce()
|
||||
local nearbyCount = 0
|
||||
for _,gr in pairs(nearGroups.Set) do
|
||||
local gc = gr:GetCoordinate()
|
||||
local gc = gr:GetCoord()
|
||||
if gc then
|
||||
local dist = coord:Get2DDistance(gc)
|
||||
if dist > innerDist then
|
||||
@@ -3925,7 +3925,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
-- Check cargo location if available
|
||||
local location = Cargo:GetLocation()
|
||||
if location then
|
||||
local unitcoord = Unit:GetCoordinate() or Group:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoord() or Group:GetCoord()
|
||||
if unitcoord then
|
||||
if not location:IsCoordinateInZone(unitcoord) then
|
||||
-- no we're not at the right spot
|
||||
@@ -4136,8 +4136,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
if type(ship) == "string" then
|
||||
self:T("Spawning on ship "..ship)
|
||||
local Ship = UNIT:FindByName(ship)
|
||||
local shipcoord = Ship:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local shipcoord = Ship:GetCoord()
|
||||
local unitcoord = Unit:GetCoord()
|
||||
local dist = shipcoord:Get2DDistance(unitcoord)
|
||||
dist = dist - (20 + math.random(1, 10))
|
||||
local halfwidth = (width or 20) / 2
|
||||
@@ -4395,11 +4395,11 @@ function CTLD:_C130RemoveUnitsNearby(_group,_unit)
|
||||
local innerDist = (capabilities.length and capabilities.length/2) or 15
|
||||
local finddist = self.PackDistance or (self.CrateDistance or 35)
|
||||
local zone = ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false)
|
||||
local nearestGroups = SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce()
|
||||
local nearestGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterZones({zone}):FilterOnce()
|
||||
local removedAny = false
|
||||
local removedTable = {}
|
||||
for _, gr in pairs(nearestGroups.Set) do
|
||||
local gc = gr:GetCoordinate()
|
||||
local gc = gr:GetCoord()
|
||||
if gc then
|
||||
local dist = location:Get2DDistance(gc)
|
||||
if dist > innerDist then
|
||||
@@ -5683,18 +5683,18 @@ end
|
||||
-- @return #number Number
|
||||
function CTLD:_FindPackableGroupsNearby(Group, Unit)
|
||||
self:T(self.lid .. " _FindPackableGroupsNearby")
|
||||
local location = Group:GetCoordinate()
|
||||
local location = Group:GetCoord()
|
||||
if not location then return {}, 0 end
|
||||
local capabilities = self:_GetUnitCapabilities(Unit)
|
||||
local innerDist = (capabilities.length and capabilities.length/2) or 15
|
||||
local finddist = self.PackDistance or (self.CrateDistance or 35)
|
||||
local zone = ZONE_RADIUS:New("CTLD_PackableZone", location:GetVec2(), finddist, false)
|
||||
local nearestGroups = SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce()
|
||||
local nearestGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterZones({zone}):FilterOnce()
|
||||
local packable = {}
|
||||
|
||||
for _, gr in pairs(nearestGroups.Set) do
|
||||
if gr and gr:GetName() ~= Group:GetName() then
|
||||
local gc = gr:GetCoordinate()
|
||||
local gc = gr:GetCoord()
|
||||
if gc then
|
||||
local dist = location:Get2DDistance(gc)
|
||||
if dist > innerDist and dist <= finddist then
|
||||
@@ -6171,11 +6171,11 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
|
||||
local ctype = Build.Type -- #CTLD_CARGO.Enum
|
||||
local canmove = false
|
||||
if ctype == CTLD_CARGO.Enum.VEHICLE then canmove = true end
|
||||
if ctype == CTLD_CARGO.Enum.STATIC then
|
||||
return self
|
||||
if ctype == CTLD_CARGO.Enum.STATIC then
|
||||
return self
|
||||
end
|
||||
local temptable = Build.Template or {}
|
||||
if type(temptable) == "string" then
|
||||
if type(temptable) == "string" then
|
||||
temptable = {temptable}
|
||||
end
|
||||
local zone = nil -- Core.Zone#ZONE_RADIUS
|
||||
@@ -6217,8 +6217,11 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
|
||||
self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
|
||||
end
|
||||
end -- template loop
|
||||
self:_RefreshLoadCratesMenu(Group, Unit)
|
||||
self:_RefreshPackMenus(Group, Unit)
|
||||
|
||||
if Group and Group:IsAlive() and Group:GetID() then
|
||||
self:_RefreshLoadCratesMenu(Group, Unit)
|
||||
self:_RefreshPackMenus(Group, Unit)
|
||||
end
|
||||
else
|
||||
self:T(self.lid.."Group KIA while building!")
|
||||
end
|
||||
@@ -9008,7 +9011,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
|
||||
local zoneret = nil
|
||||
local zonewret = nil
|
||||
local zonenameret = nil
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoord()
|
||||
if not unitcoord then
|
||||
if Zonetype == CTLD.CargoZoneType.SHIP then
|
||||
return false, nil, nil, 1000000, nil
|
||||
@@ -9029,7 +9032,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
|
||||
self:T("Checking Type Ship: "..zonename)
|
||||
local ZoneUNIT = UNIT:FindByName(zonename)
|
||||
if not ZoneUNIT then return false end
|
||||
zonecoord = ZoneUNIT:GetCoordinate()
|
||||
zonecoord = ZoneUNIT:GetCoord()
|
||||
zoneradius = czone.shiplength
|
||||
zonewidth = czone.shipwidth
|
||||
zone = ZONE_UNIT:New( ZoneUNIT:GetName(), ZoneUNIT, zoneradius/2)
|
||||
|
||||
@@ -191,9 +191,10 @@
|
||||
-- * @{#EASYGCICAP.SetDefaultResurrection}: Set how many seconds the AirWing stays inoperable after the AirWing STATIC HQ ist destroyed, default 900 secs.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPSpeed}: Set how many knots the CAP flights should do (will be altitude corrected), default 300 kn.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPAlt}: Set at which altitude (ASL) the CAP planes will fly, default 25,000 ft.
|
||||
-- * @{#EASYGCICAP.SetDefaultINTERCEPTAlt}: Set at which altitude (ASL) the Intercept planes will fly, default 25,000 ft.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPDirection}: Set the initial direction from the CAP point the planes will fly in degrees, default is 90°.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPLeg}: Set the length of the CAP leg, default is 15 NM.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CAP/GCI), defaults to 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100.
|
||||
-- * @{#EASYGCICAP.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultEngageRange}: Set max engage range for CAP flights if they detect intruders, defaults to 50.
|
||||
@@ -287,7 +288,7 @@ EASYGCICAP = {
|
||||
|
||||
--- EASYGCICAP class version.
|
||||
-- @field #string version
|
||||
EASYGCICAP.version="0.1.37"
|
||||
EASYGCICAP.version="0.1.38"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
@@ -588,6 +589,17 @@ function EASYGCICAP:SetDefaultCAPAlt(Altitude)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default INTERCEPT Altitude in feet
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Altitude (Optional) Altitude defaults to 25000
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetDefaultINTERCEPTAlt(Altitude)
|
||||
self:T(self.lid.."SetDefaultINTERCEPTAlt")
|
||||
self.interceptalt = Altitude or 25000
|
||||
return self
|
||||
end
|
||||
--
|
||||
|
||||
--- Set default CAP lieg initial direction in degrees
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Direction (Optional) Direction defaults to 90 (East)
|
||||
@@ -1519,6 +1531,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
local overhead = self.overhead
|
||||
local capspeed = self.capspeed + 100
|
||||
local capalt = self.capalt
|
||||
local interalt = self.interceptalt or self.capalt
|
||||
local maxsize = self.maxinterceptsize
|
||||
local repeatsonfailure = self.repeatsonfailure
|
||||
|
||||
@@ -1606,6 +1619,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
:SetRepeatOnFailure(repeats)
|
||||
:SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt))
|
||||
:SetMissionAltitude(capalt)
|
||||
:SetEngageAltitude(interalt)
|
||||
|
||||
if nogozoneset:Count() > 0 then
|
||||
InterceptAuftrag:AddConditionSuccess(
|
||||
|
||||
@@ -2661,7 +2661,7 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti
|
||||
local RangeMax = RangeMax or 0
|
||||
local InRange=(RangeMax and math.max(RangeMax, Rmax) or Rmax) >= TargetDistance and TargetDistance > RangeMin
|
||||
|
||||
--env.info(string.format("Range TargetDist=%.1f Rmax=%.1f RangeMax=%.1f InRange=%s RangeMin=%s", TargetDistance, Rmax, RangeMax, tostring(InRange),tostring(RangeMin)))
|
||||
--env.info(string.format("Range TargetDist=%.1f Rmax=%.1f RangeMax=%.1f InRange=%s", TargetDistance, Rmax, RangeMax, tostring(InRange)))
|
||||
|
||||
return InRange
|
||||
end
|
||||
|
||||
@@ -131,14 +131,24 @@ ENUMS.WeaponFlag={
|
||||
Cannons = 805306368, -- GUN_POD + BuiltInCannon
|
||||
--- Torpedo
|
||||
Torpedo = 4294967296,
|
||||
--- Decoy
|
||||
Decoys = 8589934592,
|
||||
--- Shell
|
||||
SmokeShell = 17179869184,
|
||||
IlluminationShell = 34359738368,
|
||||
MarkerShell = 51539607552,
|
||||
MarkerWeapon = 51539620864,
|
||||
SubmunitionDispenserShell = 68719476736,
|
||||
ConventionalShell = 206963736576,
|
||||
---
|
||||
-- Even More Genral
|
||||
Auto = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons)
|
||||
AutoDCS = 1073741822, -- Something if often see
|
||||
AnyAG = 2956984318, -- Any Air-To-Ground Weapon
|
||||
AnyAA = 264241152, -- Any Air-To-Air Weapon
|
||||
AnyUnguided = 2952822768, -- Any Unguided Weapon
|
||||
AnyGuided = 268402702, -- Any Guided Weapon
|
||||
Auto = 265214230526, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons + Torpedos)
|
||||
AutoDCS = 1073741822, -- Something if often see
|
||||
AnyAG = 2956984318, -- Any Air-To-Ground Weapon
|
||||
AnyAA = 264241152, -- Any Air-To-Air Weapon
|
||||
AnyUnguided = 2952822768, -- Any Unguided Weapon
|
||||
AnyGuided = 268402702, -- Any Guided Weapon
|
||||
AnyShell = 258503344128, -- Any Shell
|
||||
}
|
||||
|
||||
--- Weapon types by category. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on hoggit wiki.
|
||||
@@ -220,11 +230,11 @@ ENUMS.WeaponType.Torpedo={
|
||||
}
|
||||
ENUMS.WeaponType.Any={
|
||||
-- General combinations
|
||||
Weapon = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons)
|
||||
AG = 2956984318, -- Any Air-To-Ground Weapon
|
||||
AA = 264241152, -- Any Air-To-Air Weapon
|
||||
Unguided = 2952822768, -- Any Unguided Weapon
|
||||
Guided = 268402702, -- Any Guided Weapon
|
||||
Weapon = 265214230526, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons + Torpedos)
|
||||
AG = 2956984318, -- Any Air-To-Ground Weapon
|
||||
AA = 264241152, -- Any Air-To-Air Weapon
|
||||
Unguided = 2952822768, -- Any Unguided Weapon
|
||||
Guided = 268402702, -- Any Guided Weapon
|
||||
}
|
||||
|
||||
|
||||
@@ -513,7 +523,7 @@ ENUMS.ReportingName =
|
||||
Tornado = "Tornado",
|
||||
-- Transport / Bomber / Others
|
||||
Atlas = "A400",
|
||||
Lancer = "B1-B",
|
||||
Lancer = "B-1B",
|
||||
Stratofortress = "B-52H",
|
||||
Herc = "C-130",
|
||||
Hercules = "C-130J-30",
|
||||
@@ -558,7 +568,7 @@ ENUMS.ReportingName =
|
||||
Apache = "AH-64",
|
||||
Chinook = "CH-47",
|
||||
Sea_Stallion = "CH-53",
|
||||
Kiowa = "OH-58",
|
||||
Kiowa = "OH58D",
|
||||
Seahawk = "SH-60",
|
||||
Blackhawk = "UH-60",
|
||||
Sea_King = "S-61",
|
||||
@@ -1798,13 +1808,21 @@ ENUMS.Storage.weapons.missiles.Igla_S = 'weapons.missiles.Igla_S'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.{AKAN_NO_TRC}'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.{AKAN}'
|
||||
ENUMS.Storage.weapons.shells.M882_9x19 = 'weapons.shells.9x19_m882'
|
||||
ENUMS.Storage.weapons.droptanks.fuel_tank_370gal = "weapons.droptanks.fuel_tank_370gal"
|
||||
ENUMS.Storage.weapons.droptanks.fuel_tank_300gal = "weapons.droptanks.fuel_tank_300gal"
|
||||
|
||||
-- NEW
|
||||
ENUMS.Storage.weapons.adapters.HB_F_4E_ORD_LAU_77 = 'weapons.adapters.HB_F-4E_ORD_LAU_77'
|
||||
ENUMS.Storage.weapons.adapters.hb_a_6e_lau7_adu299 = 'weapons.adapters.hb_a-6e_lau7_adu299'
|
||||
ENUMS.Storage.weapons.adapters.CHAP_AIM92LN = 'weapons.adapters.CHAP_AIM92LN'
|
||||
ENUMS.Storage.weapons.adapters.CHAP_HOT3LN = 'weapons.adapters.CHAP_HOT3LN'
|
||||
ENUMS.Storage.weapons.adapters.CHAP_FZ225 = 'weapons.adapters.CHAP_FZ225'
|
||||
|
||||
ENUMS.Storage.weapons.bombs.AH6_SMOKE_BLUE = 'weapons.bombs.AH6_SMOKE_BLUE'
|
||||
ENUMS.Storage.weapons.bombs.AH6_SMOKE_GREEN = 'weapons.bombs.AH6_SMOKE_GREEN'
|
||||
ENUMS.Storage.weapons.bombs.AH6_SMOKE_RED = 'weapons.bombs.AH6_SMOKE_RED'
|
||||
ENUMS.Storage.weapons.bombs.AH6_SMOKE_YELLOW = 'weapons.bombs.AH6_SMOKE_YELLOW'
|
||||
ENUMS.Storage.weapons.bombs.AO_25SL = 'weapons.bombs.AO_25SL'
|
||||
|
||||
ENUMS.Storage.weapons.missiles.HB_AGM_78 = 'weapons.missiles.HB_AGM_78'
|
||||
ENUMS.Storage.weapons.missiles.V_1 = 'weapons.missiles.V-1'
|
||||
@@ -1851,6 +1869,10 @@ ENUMS.Storage.weapons.shells.M42A1_HE = 'weapons.shells.M42A1_HE'
|
||||
ENUMS.Storage.weapons.shells.HE_20x138B = 'weapons.shells.20x138B_HE'
|
||||
ENUMS.Storage.weapons.shells.HE_37x263 = 'weapons.shells.37x263_HE'
|
||||
ENUMS.Storage.weapons.shells.HE_2A20_115mm = 'weapons.shells.2A20_115mm_HE'
|
||||
ENUMS.Storage.weapons.shells.AP_20x99R = 'weapons.shells.20x99R_AP'
|
||||
ENUMS.Storage.weapons.shells.IranFAC_DShK_API_T = 'weapons.shells.IranFAC_DShK_API_T'
|
||||
ENUMS.Storage.weapons.shells.IranFAC_DShK_API = 'weapons.shells.IranFAC_DShK_API'
|
||||
ENUMS.Storage.weapons.shells.HE_T_20x99R = 'weapons.shells.20x99R_HE_T'
|
||||
|
||||
ENUMS.Storage.weapons.gunmounts.B17_TailTurret_M2_L = 'weapons.gunmounts.B17_TailTurret_M2_L'
|
||||
ENUMS.Storage.weapons.gunmounts.AH6_M134L = 'weapons.gunmounts.{AH6_M134L}'
|
||||
@@ -1881,6 +1903,11 @@ ENUMS.Storage.weapons.gunmounts.AH_6_FN_HMP400 = 'weapons.gunmounts.{AH-6_FN_HMP
|
||||
ENUMS.Storage.weapons.gunmounts.AH_6_M134L = 'weapons.gunmounts.AH-6_M134L'
|
||||
ENUMS.Storage.weapons.gunmounts.AH_6_M134R = 'weapons.gunmounts.AH-6_M134R'
|
||||
ENUMS.Storage.weapons.gunmounts.AH_6_HMP400 = 'weapons.gunmounts.AH-6_HMP400'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.{AKAN_NO_TRC}'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.{AKAN}'
|
||||
ENUMS.Storage.weapons.gunmounts.CHAP_HMP400LC = 'weapons.gunmounts.{CHAP_HMP400LC}'
|
||||
ENUMS.Storage.weapons.gunmounts.HMP400LC = 'weapons.gunmounts.HMP400LC'
|
||||
ENUMS.Storage.weapons.gunmounts.SHVAK_GUN = 'weapons.gunmounts.SHVAK_GUN'
|
||||
|
||||
ENUMS.Storage.weapons.droptanks.PTB_800 = 'weapons.droptanks.PTB-800'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_275 = 'weapons.droptanks.PTB-275'
|
||||
@@ -1931,6 +1958,7 @@ ENUMS.Storage.weapons.droptanks.S_3_PTB = 'weapons.droptanks.S-3-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.fuel_tank_370gal = 'weapons.droptanks.fuel_tank_370gal'
|
||||
ENUMS.Storage.weapons.droptanks.MIG_23_PTB = 'weapons.droptanks.MIG-23-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.f_18c_ptb = 'weapons.droptanks.f-18c-ptb'
|
||||
ENUMS.Storage.weapons.droptanks.CHAP_TigerUHT_fueltank = 'weapons.droptanks.CHAP_TigerUHT_fueltank'
|
||||
|
||||
ENUMS.Storage.weapons.containers.FN_HMP400_100 = 'weapons.containers.{FN_HMP400_100}'
|
||||
ENUMS.Storage.weapons.containers.AN_M3 = 'weapons.containers.{AN-M3}'
|
||||
@@ -2062,6 +2090,13 @@ ENUMS.Storage.weapons.containers.GIAT_M621_AP = 'weapons.containers.{GIAT_M621_A
|
||||
ENUMS.Storage.weapons.containers.M134_R = 'weapons.containers.M134_R'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L300 = 'weapons.containers.OH58D_M3P_L300'
|
||||
ENUMS.Storage.weapons.containers.AH6_M134R = 'weapons.containers.{AH6_M134R}'
|
||||
ENUMS.Storage.weapons.containers.CHAP_HMP400LC = 'weapons.containers.{CHAP_HMP400LC}'
|
||||
|
||||
ENUMS.Storage.weapons.missiles.C701_AShM = 'weapons.missiles.C701 AShM'
|
||||
ENUMS.Storage.weapons.missiles.IGLA_9M39 = 'weapons.missiles.9M39_IGLA'
|
||||
ENUMS.Storage.weapons.missiles.CHAP_AIM92 = 'weapons.missiles.CHAP_AIM92'
|
||||
ENUMS.Storage.weapons.missiles.AM39 = 'weapons.missiles.AM39'
|
||||
ENUMS.Storage.weapons.missiles.Shahed136_LM = 'weapons.missiles.Shahed136_LM'
|
||||
|
||||
ENUMS.Storage.weapons.torpedoes.G7A_T1 = 'weapons.torpedoes.G7A_T1'
|
||||
|
||||
@@ -2091,6 +2126,47 @@ ENUMS.Storage.weapons.containers.UH60_GAU19_RIGHT = 'weapons.containers.{UH60_GA
|
||||
ENUMS.Storage.weapons.containers.UH60_M230_RIGHT = 'weapons.containers.{UH60_M230_RIGHT}'
|
||||
ENUMS.Storage.weapons.containers.UH60L_M2_GUNNER = 'weapons.containers.{UH60L_M2_GUNNER}'
|
||||
|
||||
|
||||
ENUMS.Storage.weapons.adapters.GD_F100_SUU_13 = 'weapons.adapters.GD_F100_SUU-13'
|
||||
ENUMS.Storage.weapons.containers.GPU_5_POD = 'weapons.containers.{GPU_5_POD}'
|
||||
ENUMS.Storage.weapons.bombs.BOLT_117 = 'weapons.bombs.BOLT-117'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_TP_T = 'weapons.shells.F100_M39_20_TP_T'
|
||||
ENUMS.Storage.weapons.gunmounts.M_39_F_100 = 'weapons.gunmounts.M-39 F-100'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_275_L = 'weapons.droptanks.GD_F100_TANK_275_L'
|
||||
ENUMS.Storage.weapons.bombs.BLU_4B_R_GROUP_R = 'weapons.bombs.BLU-4B_R_GROUP_R'
|
||||
ENUMS.Storage.weapons.containers.F100_CAMERA_P2 = 'weapons.containers.F100_CAMERA_P2'
|
||||
ENUMS.Storage.weapons.containers.HB_ORD_SUU_7 = 'weapons.containers.HB_ORD_SUU_7'
|
||||
ENUMS.Storage.weapons.adapters.SidewinderPylon = 'weapons.adapters.SidewinderPylon'
|
||||
ENUMS.Storage.weapons.adapters.apu_470 = 'weapons.adapters.apu_470'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_335_R = 'weapons.droptanks.GD_F100_TANK_335_R'
|
||||
ENUMS.Storage.weapons.bombs.BLU_3B_R_GROUP_R = 'weapons.bombs.BLU-3B_R_GROUP_R'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_335_L = 'weapons.droptanks.GD_F100_TANK_335_L'
|
||||
ENUMS.Storage.weapons.adapters.mbdz_u2t = 'weapons.adapters.mbdz_u2t'
|
||||
ENUMS.Storage.weapons.containers.ZellRocket = 'weapons.containers.ZellRocket'
|
||||
ENUMS.Storage.weapons.containers.apu_73 = 'weapons.containers.apu-73'
|
||||
ENUMS.Storage.weapons.gunmounts.GAU_13 = 'weapons.gunmounts.GAU-13'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_TP = 'weapons.shells.F100_M39_20_TP'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_API = 'weapons.shells.F100_M39_20_API'
|
||||
ENUMS.Storage.weapons.adapters.bdz_umk2b = 'weapons.adapters.bdz_umk2b'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_450 = 'weapons.droptanks.GD_F100_TANK_450'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_275_R = 'weapons.droptanks.GD_F100_TANK_275_R'
|
||||
ENUMS.Storage.weapons.bombs.BLU_18_B_GROUP = 'weapons.bombs.BLU-18/B_GROUP'
|
||||
ENUMS.Storage.weapons.containers.ZELL_Booster = 'weapons.containers.ZELL Booster'
|
||||
ENUMS.Storage.weapons.adapters.apu_60 = 'weapons.adapters.apu_60'
|
||||
ENUMS.Storage.weapons.gunmounts.GPU_5_POD = 'weapons.gunmounts.{GPU_5_POD}'
|
||||
ENUMS.Storage.weapons.containers.F100_CAMERA_KA71A = 'weapons.containers.F100_CAMERA_KA71A'
|
||||
ENUMS.Storage.weapons.bombs.BLU_3_R_GROUP_R = 'weapons.bombs.BLU-3_R_GROUP_R'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_HEI = 'weapons.shells.F100_M39_20_HEI'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_200 = 'weapons.droptanks.GD_F100_TANK_200'
|
||||
ENUMS.Storage.weapons.adapters.apu_73 = 'weapons.adapters.apu-73'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.AKAN_NO_TRC'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.AKAN'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_HEI_T = 'weapons.shells.F100_M39_20_HEI_T'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_API_T = 'weapons.shells.F100_M39_20_API_T'
|
||||
ENUMS.Storage.weapons.containers.GD_F100_STRIKE_CAMERA = 'weapons.containers.GD_F100_STRIKE_CAMERA'
|
||||
ENUMS.Storage.weapons.bombs.BLU_49_B = 'weapons.bombs.BLU-49/B'
|
||||
ENUMS.Storage.weapons.containers.GD_F100_SUU_13 = 'weapons.containers.GD_F100_SUU-13'
|
||||
|
||||
---
|
||||
-- @type ENUMS.FARPType
|
||||
-- @field #string FARP
|
||||
|
||||
@@ -753,8 +753,8 @@ UTILS.KnotsToKmph = function(knots)
|
||||
return knots * 1.852
|
||||
end
|
||||
|
||||
UTILS.KmphToKnots = function(kmph)
|
||||
return kmph / 1.852
|
||||
UTILS.KmphToKnots = function(knots)
|
||||
return knots / 1.852
|
||||
end
|
||||
|
||||
UTILS.KmphToMps = function( kmph )
|
||||
|
||||
@@ -76,10 +76,6 @@ Functional/Tiresias.lua
|
||||
Functional/Stratego.lua
|
||||
Functional/ClientWatch.lua
|
||||
Functional/Formation.lua
|
||||
Functional/RedGCIKernel.lua
|
||||
Functional/RedGCI.lua
|
||||
Functional/RedGCI2v2.lua
|
||||
Functional/RedGCIDispatcher.lua
|
||||
|
||||
Ops/Airboss.lua
|
||||
Ops/RecoveryTanker.lua
|
||||
|
||||
Reference in New Issue
Block a user