Compare commits

..

6 Commits

Author SHA1 Message Date
Frank e8f5214e0b Merge branch 'master-ng' into FF/Ops 2026-06-21 18:21:33 +02:00
Frank e4c7e809cb TARGET v0.8.0
- Added Element functions
- Improved handling of delayed dead events
2026-06-18 22:47:11 +02:00
Frank aab16dd966 Legion/Cohort Database
- moved to individual classes
2026-06-17 09:00:17 +02:00
Frank 8b356f0913 Merge branch 'master-ng' into FF/Ops 2026-06-15 20:48:57 +02:00
Frank c3086b0265 Update Database.lua
- fixed bug
2026-06-05 22:54:42 +02:00
Frank 599a2ba6fb Database
- Added COHORTs and LEGIONs to database
2026-06-05 22:50:52 +02:00
23 changed files with 248 additions and 5070 deletions
+36
View File
@@ -92,6 +92,8 @@ DATABASE = {
ZONES_GOAL = {},
WAREHOUSES = {},
FLIGHTGROUPS = {},
COHORTS={},
LEGIONS={},
FLIGHTCONTROLS = {},
OPSZONES = {},
PATHLINES = {},
@@ -2087,6 +2089,40 @@ function DATABASE:FindOpsGroupFromUnit(unitname)
end
end
--
--- Add an OPS COHORT (SQUADRON, PLATOON, FLOTILLA) to the data base.
-- @param #DATABASE self
-- @param Ops.Cohort#COHORT cohort The cohort added to the DB.
function DATABASE:AddCohort(cohort)
self.COHORTS[cohort.name]=cohort
end
--- Find an OPS COHORT (SQUADRON, PLATOON, FLOTILLA) in the data base.
-- @param #DATABASE self
-- @param #string cohortname Name of the cohort.
-- @return Ops.Cohort#COHORT Cohort object.
function DATABASE:FindCohort(cohortname)
return self.COHORTS[cohortname]
end
--- Add an OPS LEGION (AIRWING, BRIGADE, FLEET) to the data base.
-- @param #DATABASE self
-- @param Ops.Legion#LEGION legion The legion added to the DB.
function DATABASE:AddLegion(legion)
self.LEGIONS[legion.alias]=legion
end
--- Find an OPS LEGION (AIRWING, BRIGADE, FLEET) in the data base.
-- @param #DATABASE self
-- @param #string legionname Name of the legion.
-- @return Ops.Legion#LEGION Legion object.
function DATABASE:FindLegion(legionname)
return self.LEGIONS[legionname]
end
--
--- Add a flight control to the data base.
-- @param #DATABASE self
-- @param OPS.FlightControl#FLIGHTCONTROL flightcontrol
@@ -246,11 +246,11 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
-- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
return ScheduleTime -- returns the next time the function needs to be called.
else
self:Stop( Scheduler, CallID )
self:_Reclaim( Scheduler, CallID )
end
else
self:Stop( Scheduler, CallID )
self:_Reclaim( Scheduler, CallID )
end
else
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
-1
View File
@@ -614,7 +614,6 @@ do -- Object
-- @field SCENERY
-- @field CARGO
---
-- @type Object.Desc
-- @extends #Desc
-- @field #number life initial life level
@@ -742,6 +742,7 @@ do
--
function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs, EmOnOff, Padding, Zones)
-- Inherit everything from BASE class.
local self = BASE:Inherit(self, FSM:New()) -- #MANTIS
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 1015 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 813km 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 |
-- | | (300600m 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
-------------------------------------------------------------------------------
-5
View File
@@ -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' )
+45 -28
View File
@@ -290,6 +290,9 @@ function AIRWING:New(warehousename, airwingname)
-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FLIGHTGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- Add legion to DB
_DATABASE:AddLegion(self)
return self
end
@@ -541,6 +544,47 @@ function AIRWING:AddPayloadCapability(Payload, MissionTypes, Performance)
return self
end
--- Filter available payloads for a given aircraft type and mission type.
-- @param #AIRWING self
-- @param #string UnitType The type of the unit.
-- @param #string MissionType The mission type.
-- @param #table Payloads Specific payloads only to be considered.
-- @return #AIRWING.Payload Payload table or *nil*.
function AIRWING:_FilterPlayloads(UnitType, MissionType, Payloads)
local function _checkPayloads(payload)
if Payloads then
for _,Payload in pairs(Payloads) do
if Payload.uid==payload.uid then
return true
end
end
else
-- Payload was not specified.
return nil
end
return false
end
-- Pre-selection: filter out only those payloads that are valid for the airframe and mission type and are available.
local payloads={}
for _,_payload in pairs(self.payloads) do
local payload=_payload --#AIRWING.Payload
local specialpayload=_checkPayloads(payload)
local compatible=AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities)
local goforit = specialpayload or (specialpayload==nil and compatible)
if payload.aircrafttype==UnitType and payload.navail>0 and goforit then
table.insert(payloads, payload)
end
end
return payloads
end
--- Fetch a payload from the airwing resources for a given unit and mission type.
-- The payload with the highest priority is preferred.
-- @param #AIRWING self
@@ -589,34 +633,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads)
end
end
local function _checkPayloads(payload)
if Payloads then
for _,Payload in pairs(Payloads) do
if Payload.uid==payload.uid then
return true
end
end
else
-- Payload was not specified.
return nil
end
return false
end
-- Pre-selection: filter out only those payloads that are valid for the airframe and mission type and are available.
local payloads={}
for _,_payload in pairs(self.payloads) do
local payload=_payload --#AIRWING.Payload
local specialpayload=_checkPayloads(payload)
local compatible=AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities)
local goforit = specialpayload or (specialpayload==nil and compatible)
if payload.aircrafttype==UnitType and payload.navail>0 and goforit then
table.insert(payloads, payload)
end
end
local payloads=self:_FilterPlayloads(UnitType, MissionType, Payloads)
-- Debug.
if self.verbose>=4 then
-1
View File
@@ -5120,7 +5120,6 @@ end
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "Planned" event.
-- @param #AUFTRAG self
-- @param #string From From state.
+3
View File
@@ -150,6 +150,9 @@ function BRIGADE:New(WarehouseName, BrigadeName)
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- Add legion to DB
_DATABASE:AddLegion(self)
return self
end
+3 -17
View File
@@ -135,7 +135,6 @@
-- local mywing = EASYGCICAP:New("Blue CAP Operations",AIRBASE.Caucasus.Kutaisi,"blue","Blue EWR")
--
-- -- Add a CAP patrol point belonging to our airbase, we'll be at 30k ft doing 400 kn, initial direction 90 degrees (East), leg 20NM
-- -- NOTE - Skip this function and do not create CAP Points if you want GCI behaviour only.
-- mywing:AddPatrolPointCAP(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone 1"):GetCoordinate(),30000,400,90,20)
--
-- -- Add a Squadron with template "Blue Sq1 M2000c", 20 airframes, skill good, Modex starting with 102 and skin "Vendee Jeanne"
@@ -287,7 +286,7 @@ EASYGCICAP = {
--- EASYGCICAP class version.
-- @field #string version
EASYGCICAP.version="0.1.37"
EASYGCICAP.version="0.1.36"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--
@@ -755,15 +754,6 @@ end
function EASYGCICAP:_AddAirwing(Airbasename, Alias)
self:T(self.lid.."_AddAirwing "..Airbasename)
-- Gather Some Stats
local function counttable(tbl)
local count = 0
for _,_data in pairs(tbl) do
count = count + 1
end
return count
end
local CapFormation = self.CapFormation
local DespawnAfterLanding = self.DespawnAfterLanding
local DespawnAfterHolding = self.DespawnAfterHolding
@@ -782,11 +772,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
CAP_Wing:SetMarker(false)
CAP_Wing:SetAirbase(AIRBASE:FindByName(Airbasename))
CAP_Wing:SetRespawnAfterDestroyed()
--- #DONE avoid wings with no CAP points starting CAP anyhow; AirWing uses this to start CAP and creates points when there are none.
if counttable(self.ManagedCP) >0 then
CAP_Wing:SetNumberCAP(self.capgrouping)
end
CAP_Wing:SetNumberCAP(self.capgrouping)
CAP_Wing:SetCapCloseRaceTrack(true)
if self.showpatrolpointmarks then
@@ -1053,7 +1039,7 @@ function EASYGCICAP:_SetCAPPatrolPoints()
MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.AirWing#AIRWING
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
+3
View File
@@ -167,6 +167,9 @@ function FLEET:New(WarehouseName, FleetName)
-- @param Ops.NavyGroup#NAVYGROUP NavyGroup The NAVYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- Add legion to DB
_DATABASE:AddLegion(self)
return self
end
+3
View File
@@ -73,6 +73,9 @@ function FLOTILLA:New(TemplateGroupName, Ngroups, FlotillaName)
-- Get initial ammo.
self.ammo=self:_CheckAmmo()
-- Add cohort to DB
_DATABASE:AddCohort(self)
return self
end
+1 -2
View File
@@ -306,7 +306,6 @@ function LEGION:New(WarehouseName, LegionName)
-- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to.
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned.
return self
end
@@ -2661,7 +2660,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
+3
View File
@@ -71,6 +71,9 @@ function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName)
-- Get ammo.
self.ammo=self:_CheckAmmo()
-- Add cohort to DB
_DATABASE:AddCohort(self)
return self
end
+3
View File
@@ -116,6 +116,9 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
-- See COHORT class
-- Add cohort to DB
_DATABASE:AddCohort(self)
return self
end
+94 -54
View File
@@ -153,7 +153,7 @@ _TARGETID=0
--- TARGET class version.
-- @field #string version
TARGET.version="0.7.1"
TARGET.version="0.8.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -206,6 +206,9 @@ function TARGET:New(TargetObject)
self:AddTransition("*", "Status", "*") -- Status update.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self:AddTransition("*", "ElementDestroyed", "*") -- A target element was destroyed.
self:AddTransition("*", "ElementDead", "*") -- A target element is dead (destroyed or despawned).
self:AddTransition("*", "ObjectDamaged", "*") -- A target object was damaged.
self:AddTransition("*", "ObjectDestroyed", "*") -- A target object was destroyed.
self:AddTransition("*", "ObjectDead", "*") -- A target object is dead (destroyed or despawned).
@@ -245,6 +248,19 @@ function TARGET:New(TargetObject)
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "ElementDestroyed".
-- @function [parent=#TARGET] ElementDestroyed
-- @param #TARGET self
-- @param #string ElementName Name of the element.
-- @param #TARGET.Object Target Target object.
--- Triggers the FSM event "ElementDead".
-- @function [parent=#TARGET] ElementDead
-- @param #TARGET self
-- @param #string ElementName Name of the element.
-- @param #TARGET.Object Target Target object.
--- Triggers the FSM event "ObjectDamaged".
-- @function [parent=#TARGET] ObjectDamaged
-- @param #TARGET self
@@ -642,6 +658,19 @@ function TARGET:onafterStatus(From, Event, To)
-- FSM state.
local fsmstate=self:GetState()
-- First we check any target has been destroyed and the dead/unitlost event was not fired
for i,_target in pairs(self.targets) do
local target=_target --#TARGET.Object
local life=self:GetTargetLife(target)
if life<1 and target.Status~=TARGET.ObjectStatus.DEAD then
self:E(self.lid..string.format("FF life is zero but no object dead event fired ==> waiting for target object %s events!", tostring(target.Name)))
-- We wait 60 seconds for the events to occur
self:__Status(-60)
return self
end
end
-- Update damage.
local damaged=false
@@ -736,6 +765,61 @@ end
-- FSM Events
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "ElementDestroyed" event.
-- @param #TARGET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Name Name of the element.
-- @param #TARGET.Object Target Target object.
function TARGET:onafterElementDestroyed(From, Event, To, Name, Target)
-- Debug message.
self:T(self.lid..string.format("Element %s of target object %s destroyed", Name, Target.Name))
-- Increase destroyed counter.
Target.Ndestroyed=Target.Ndestroyed+1
-- Increase dead counter.
self.Ndestroyed=self.Ndestroyed+1
self:ElementDead(Name, Target)
return self
end
--- On after "ElementDead" event.
-- @param #TARGET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Name Name of the element.
-- @param #TARGET.Object Target Target object.
function TARGET:onafterElementDead(From, Event, To, Name, Target)
-- Debug message.
self:T(self.lid..string.format("Element %s of target object %s dead", Name, Target.Name))
-- Increase dead counter.
Target.Ndead=Target.Ndead+1
-- Increase dead counter.
self.Ndead=self.Ndead+1
-- All dead ==> Trigger event.
if Target.Ndestroyed==Target.N0 then
self:ObjectDestroyed(Target)
elseif Target.Ndead==Target.N0 then
self:ObjectDead(Target)
end
return self
end
--- On after "ObjectDamaged" event.
-- @param #TARGET self
-- @param #string From From state.
@@ -761,11 +845,6 @@ function TARGET:onafterObjectDestroyed(From, Event, To, Target)
-- Debug message.
self:T(self.lid..string.format("Object %s destroyed", Target.Name))
-- Increase destroyed counter.
self.Ndestroyed=self.Ndestroyed+1
Target.Ndestroyed=Target.Ndestroyed+1
Target.Life=0
-- Call object dead event.
@@ -787,16 +866,10 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
-- Set target status.
Target.Status=TARGET.ObjectStatus.DEAD
-- Increase dead object counter
Target.Ndead=Target.Ndead+1
-- Set target object life to 0.
Target.Life=0
-- Increase dead counter.
self.Ndead=self.Ndead+1
-- Check if anyone is alive?
local dead=true
for _,_target in pairs(self.targets) do
@@ -889,54 +962,21 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
-- Add to the list of casualties.
table.insert(self.casualties, Name)
-- Try to get target Group.
local target=self:GetTargetByName(EventData.IniGroupName)
-- Try unit target.
if not target then
target=self:GetTargetByName(EventData.IniUnitName)
end
-- Get target from Group or Unit.
local target=self:GetTargetByName(EventData.IniGroupName) or self:GetTargetByName(EventData.IniUnitName)
-- Check if we could find a target object.
if target then
local Ndead=target.Ndead
local Ndestroyed=target.Ndestroyed
-- Increase dead/destroyed counter
if EventData.id==EVENTS.RemoveUnit then
Ndead=Ndead+1
self:ElementDead(Name, target)
else
Ndestroyed=Ndestroyed+1
Ndead=Ndead+1
end
-- Check if ALL objects are dead
if Ndead==target.N0 then
if Ndestroyed>=target.N0 then
-- Debug message.
self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name)))
target.Life = 0
-- Trigger object destroyed event. This sets the Life to zero and increases Ndestroyed
self:ObjectDestroyed(target)
else
-- Debug message.
self:T2(self.lid..string.format("EVENT ID=%d: target %s removed ==> dead", EventData.id, tostring(target.Name)))
target.Life = 0
-- Trigger object dead event. This sets the Life to zero and increases Ndead counter
self:ObjectDead(target)
end
self:ElementDestroyed(Name, target)
end
else
self:E(self.lid..string.format("ERROR: Could not get target from IniGroup or IniUnit name when event Dead or UnitLost occured! Stats are not correctly updated :("))
end -- Event belongs to this TARGET
end
+3 -3
View File
@@ -2108,7 +2108,7 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co
local ffs = {}
for _,_f in pairs(Frequencies) do
table.insert(ffs,string.format("%.3f",_f))
table.insert(ffs,string.format("%.1f",_f))
end
local freqs = table.concat(ffs, ",")
@@ -2214,7 +2214,7 @@ function MSRS:_HoundTestTone(Frequencies, Modulations, Coalition)
local ffs = {}
for _,_f in pairs(Frequencies or self.frequencies) do
table.insert(ffs,string.format("%.3f",_f))
table.insert(ffs,string.format("%.1f",_f))
end
local freqs = table.concat(ffs, ",")
@@ -2291,7 +2291,7 @@ function MSRS:RadioJammerOn(Frequencies, Modulations, Coalition, Noisetype, Volu
local ffs = {}
for _,_f in pairs(Frequencies or self.frequencies) do
table.insert(ffs,string.format("%.3f",_f))
table.insert(ffs,string.format("%.1f",_f))
end
local freqs = table.concat(ffs, ",")
+46 -11
View File
@@ -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
}
@@ -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'
+2 -7
View File
@@ -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 )
@@ -2378,11 +2378,6 @@ function UTILS.IsLoadingDoorOpen( unit_name )
BASE:T(unit_name .. " front door(s) are open or doors removed")
return true
end
if type_name == "Ka-50_3" and unit:getDrawArgumentValue(38) == 1 then
BASE:T(unit_name .. " cockpit door is open")
return true
end
if type_name=="C-130J-30" and (unit:getDrawArgumentValue(86)==1)then
BASE:T(unit_name.." rear doors are open")
-4
View File
@@ -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