diff --git a/DCS_Kola/Operation_Polar_Shield/DUAL_COALITION_CHANGES.md b/DCS_Kola/Operation_Polar_Shield/DUAL_COALITION_CHANGES.md deleted file mode 100644 index cacc48f..0000000 --- a/DCS_Kola/Operation_Polar_Shield/DUAL_COALITION_CHANGES.md +++ /dev/null @@ -1,155 +0,0 @@ -# Dual Coalition Zone Capture - Full Analysis & Changes - -## Summary -The script has been refactored to provide **complete parity between RED and BLUE coalitions**. Both sides now have equal access to all features, information, and victory conditions. - ---- - -## ✅ Changes Made for Full Dual-Coalition Support - -### 1. **Tactical Markers** (Lines ~390-440) -**BEFORE:** Only BLUE coalition received tactical markers -**AFTER:** Both RED and BLUE receive separate tactical markers -- Each coalition sees enemy unit positions (when ≤10 units) -- Markers are coalition-specific and read-only -- Uses `TacticalMarkerID_BLUE` and `TacticalMarkerID_RED` for tracking - -### 2. **Victory Conditions** (Lines ~490-600) -**BEFORE:** Only checked for BLUE victory -**AFTER:** Both coalitions can win -- BLUE Victory: All zones captured → "BLUE_VICTORY" flag set -- RED Victory: All zones captured → "RED_VICTORY" flag set -- Each side gets appropriate celebration effects (smoke colors, flares) -- Proper victory/defeat messages for both sides - -### 3. **Zone Status Reports** (Lines ~710-740) -**BEFORE:** Only BLUE received status broadcasts -**AFTER:** Both coalitions receive status reports -- Each coalition sees their specific victory progress percentage -- Same zone ownership data, customized messaging per coalition - -### 4. **Victory Progress Monitoring** (Lines ~745-780) -**BEFORE:** Only warned BLUE when approaching victory -**AFTER:** Both sides get symmetric warnings -- BLUE approaching victory (80%+) → BLUE gets encouragement, RED gets warning -- RED approaching victory (80%+) → RED gets encouragement, BLUE gets warning - -### 5. **F10 Radio Menu Commands** (Lines ~840-900) -**BEFORE:** Only BLUE had F10 menu access -**AFTER:** Both coalitions have identical F10 menus -- "Get Zone Status Report" - Shows current zone ownership -- "Check Victory Progress" - Shows their specific progress percentage -- "Refresh Zone Colors" - Forces zone border redraw - -### 6. **Zone Color Refresh Messages** (Line ~835) -**BEFORE:** Only BLUE notified when colors refreshed -**AFTER:** Both coalitions receive confirmation message - -### 7. **Mission Definitions** (Lines 52-88) -**BEFORE:** Only BLUE mission defined -**AFTER:** Both coalitions have missions -- BLUE: "Capture the Airfields" (offensive mission) -- RED: "Defend the Motherland" (defensive mission) - ---- - -## 🎯 Features Now Available to BOTH Coalitions - -| Feature | BLUE | RED | -|---------|------|-----| -| Mission Objectives | ✅ | ✅ | -| Tactical Markers (enemy positions) | ✅ | ✅ | -| Zone Status Reports | ✅ | ✅ | -| Victory Progress Tracking | ✅ | ✅ | -| Victory Conditions | ✅ | ✅ | -| F10 Menu Commands | ✅ | ✅ | -| Zone Color Indicators | ✅ | ✅ | -| Capture/Attack/Guard Messages | ✅ | ✅ | - ---- - -## 🔧 Configuration - -Mission makers can now set up asymmetric scenarios by configuring the `ZONE_CONFIG` table: - -```lua -local ZONE_CONFIG = { - RED = { - "Kilpyavr", - "Severomorsk-1", - -- ... more zones - }, - - BLUE = { - "Banak", -- Example: BLUE starting zone - "Kirkenes" - }, - - NEUTRAL = { - "Contested Valley" -- Starts empty - } -} -``` - ---- - -## 🎮 Gameplay Impact - -### Balanced Competition -- Both sides can now win by capturing all zones -- Victory celebrations are coalition-specific (blue/red smoke & flares) -- Mission end triggers coalition-specific flags - -### Equal Information Access -- Both coalitions see enemy positions in contested zones -- Both receive periodic status updates -- Both have access to F10 menu commands - -### Symmetric Design -- All event handlers work equally for both sides -- Messages are dynamically generated based on zone ownership -- No hardcoded coalition bias anywhere in the code - ---- - -## 📝 Technical Notes - -### Global Variables Used -- `US_CC` - BLUE coalition command center -- `RU_CC` - RED coalition command center -- Both must be defined before loading this script - -### User Flags Set on Victory -- `BLUE_VICTORY` = 1 when BLUE wins -- `RED_VICTORY` = 1 when RED wins - -### Storage Structure -- `zoneCaptureObjects[]` - Array of zone capture objects -- `zoneNames[]` - Array of zone names -- `zoneMetadata{}` - Dictionary with coalition info -- All zones accessible via table iteration (no global zone variables) - ---- - -## 🚀 Migration from Old Script - -If migrating from `Moose_CaptureZones.lua`: - -1. **Update zone configuration** - Move zone names to `ZONE_CONFIG` table -2. **Remove manual zone creation** - The loop handles it now -3. **No code changes needed** for existing trigger zones in mission editor -4. **F10 menus now available** to RED players automatically - ---- - -## ✨ Benefits of Refactoring - -1. **Easy to configure** - Simple table instead of repetitive code -2. **Coalition agnostic** - Works equally for RED/BLUE/NEUTRAL -3. **Maintainable** - Zone logic centralized in loops -4. **Extensible** - Easy to add new features for both sides -5. **Balanced** - True dual-coalition gameplay - ---- - -*Last Updated: Analysis completed with full dual-coalition parity* diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.4.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.4.miz new file mode 100644 index 0000000..e44634f Binary files /dev/null and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.4.miz differ diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.5.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.5.miz new file mode 100644 index 0000000..86a9809 Binary files /dev/null and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.5.miz differ diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua b/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua index fe64c8a..38049bc 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua @@ -893,20 +893,30 @@ local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function() end end -end, {}, 60, 120 ) -- Start after 60 seconds, repeat every 120 seconds (2 minutes) +end, {}, 60, 240 ) -- Start after 60 seconds, repeat every 240 seconds (4 minutes) --- Periodic tactical marker update system (every 1 minute) +-- Periodic tactical marker update system with change detection (every 3 minutes) +local __lastForceCountsByZone = {} local TacticalMarkerUpdateScheduler = SCHEDULER:New( nil, function() - log("[TACTICAL] Running periodic tactical marker update...") - - -- Update tactical markers for all zones + log("[TACTICAL] Running periodic tactical marker update (change-detected)...") + for i, zoneCapture in ipairs(zoneCaptureObjects) do if zoneCapture then - CreateTacticalInfoMarker(zoneCapture) + local zoneName = zoneNames and zoneNames[i] or (zoneCapture.GetZoneName and zoneCapture:GetZoneName()) or ("Zone " .. i) + local counts = GetZoneForceStrengths(zoneCapture) + local last = __lastForceCountsByZone[zoneName] + local changed = (not last) or (last.red ~= counts.red) or (last.blue ~= counts.blue) or (last.neutral ~= counts.neutral) + + if changed then + __lastForceCountsByZone[zoneName] = { red = counts.red, blue = counts.blue, neutral = counts.neutral } + CreateTacticalInfoMarker(zoneCapture) + else + -- unchanged: skip marker churn + end end end - -end, {}, 30, 60 ) -- Start after 30 seconds, repeat every 60 seconds (1 minute) + +end, {}, 30, 180 ) -- Start after 30 seconds, repeat every 180 seconds (3 minutes) -- Function to refresh all zone colors based on current ownership local function RefreshAllZoneColors() diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_FAC2MarkRecceZone.lua b/DCS_Kola/Operation_Polar_Shield/Moose_FAC2MarkRecceZone.lua index e86bc49..4ad1ef4 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_FAC2MarkRecceZone.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_FAC2MarkRecceZone.lua @@ -1,4 +1,4 @@ ---[[ +.--[[ Simple AFAC System v1.0 ======================== @@ -86,8 +86,8 @@ AFAC.Config = { mapMarkerDuration = 120, smokeInterval = 300, - -- Target update intervals - autoUpdateInterval = 1.0, + -- Target update intervals (throttled to reduce per-second scanning) + autoUpdateInterval = 2.5, manualScanRange = 18520, -- Debug mode @@ -962,10 +962,10 @@ timer.scheduleFunction( trigger.action.outText("Simple AFAC System v1.0 loaded successfully!", 10) AFAC.log("AFAC Script fully loaded and initialized") --- Test message every 30 seconds to verify script is running -function AFAC.heartbeat() - AFAC.log("AFAC System heartbeat - script is running") - timer.scheduleFunction(AFAC.heartbeat, nil, timer.getTime() + 30) -end - -timer.scheduleFunction(AFAC.heartbeat, nil, timer.getTime() + 30) \ No newline at end of file +-- Heartbeat disabled in production to avoid periodic UI/network churn +-- To re-enable for debugging, uncomment lines below: +-- function AFAC.heartbeat() +-- AFAC.log("AFAC System heartbeat - script is running") +-- timer.scheduleFunction(AFAC.heartbeat, nil, timer.getTime() + 30) +-- end +-- timer.scheduleFunction(AFAC.heartbeat, nil, timer.getTime() + 30) \ No newline at end of file diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_NavalGroup.lua b/DCS_Kola/Operation_Polar_Shield/Moose_NavalGroup.lua index b88171a..8a80d1a 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_NavalGroup.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_NavalGroup.lua @@ -31,6 +31,19 @@ local CVN_TACAN_Name = "CVN" -- put the 3-letter TACAN identifier you want to local CVN_RecoveryWindowTime = 20 -- time in minutes for how long recovery will be open, feel free to change the number +-- Simple per-event cooldown to limit chat/sound spam (reduces network/UI churn) +local __cvnLastMsg = {} +local function sendThrottled(key, cooldownSec, sendFn) + local now = timer.getTime() + local last = __cvnLastMsg[key] or 0 + if (now - last) >= (cooldownSec or 30) then + __cvnLastMsg[key] = now + local ok, err = pcall(sendFn) + if not ok then env.info("[CVN] sendThrottled error for key "..tostring(key)..": "..tostring(err)) end + end +end + + -- Functions to move ship to designated waypoint/zones local msgCVNPatrol = "Sending Carrier Group to Patrol Zone: " @@ -38,8 +51,10 @@ local msgCVNPatrol = "Sending Carrier Group to Patrol Zone: " function SetCVNPatrolZone(zoneNumber) if PatrolZones[zoneNumber] then SetCVNActivePatrolZone = zoneNumber - MESSAGE:New(msgCVNPatrol .. SetCVNActivePatrolZone .. " (Waypoints " .. PatrolZones[zoneNumber].startWP .. "-" .. PatrolZones[zoneNumber].endWP .. ")", msgTime):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_SetPatrolZone", 30, function() + MESSAGE:New(msgCVNPatrol .. SetCVNActivePatrolZone .. " (Waypoints " .. PatrolZones[zoneNumber].startWP .. "-" .. PatrolZones[zoneNumber].endWP .. ")", msgTime):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) BlueCVNGroup:GotoWaypoint(PatrolZones[zoneNumber].startWP) else MESSAGE:New("Invalid patrol zone: " .. zoneNumber, msgTime):ToBlue() @@ -93,8 +108,10 @@ function start_recovery() if BlueCVNGroup:IsSteamingIntoWind() == true then local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln" - MESSAGE:New(unitName .. " is currently launching/recovering aircraft, currently active recovery window closes at time " .. timerecovery_end, msgTime, "CVNNAVINFO",false):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_RecoveryActive", 60, function() + MESSAGE:New(unitName .. " is currently launching/recovering aircraft, currently active recovery window closes at time " .. timerecovery_end, msgTime, "CVNNAVINFO",false):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) else local timenow=timer.getAbsTime( ) local timeend=timenow+CVN_RecoveryWindowTime*60 -- this sets the recovery window to 45 minutes, you can change the numbers as you wish @@ -102,8 +119,10 @@ function start_recovery() timerecovery_end = UTILS.SecondsToClock(timeend,true) BlueCVNGroup:AddTurnIntoWind(timerecovery_start,timerecovery_end,25,true,-9) local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln" - MESSAGE:New(unitName.." is turning into the wind to begin " .. CVN_RecoveryWindowTime .. " mins of aircraft operations.\nLaunch/Recovery Window will be open from time " .. timerecovery_start .. " until " .. timerecovery_end, msgTime, "CVNNAVINFO",false):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_RecoveryStart", 60, function() + MESSAGE:New(unitName.." is turning into the wind to begin " .. CVN_RecoveryWindowTime .. " mins of aircraft operations.\nLaunch/Recovery Window will be open from time " .. timerecovery_start .. " until " .. timerecovery_end, msgTime, "CVNNAVINFO",false):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) end end @@ -135,8 +154,10 @@ end function ResetAwacs() if BlueAwacs then BlueAwacs:Destroy() - MESSAGE:New("Resetting AWACS...", msgTime, "CVNNAVINFO",false):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_ResetAWACS", 30, function() + MESSAGE:New("Resetting AWACS...", msgTime, "CVNNAVINFO",false):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) else MESSAGE:New("No AWACS to reset - AWACS functionality not available", msgTime):ToBlue() end @@ -193,8 +214,10 @@ function BlueCVNGroup:OnAfterPassingWaypoint(From, Event, To, Waypoint) -- Debug info. local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln" local text=string.format(unitName.." passed waypoint ID=%d (Index=%d) %d times", waypoint.uid, BlueCVNGroup:GetWaypointIndex(waypoint.uid), waypoint.npassed) - MESSAGE:New(text, msgTime):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_PassingWaypoint", 20, function() + MESSAGE:New(text, msgTime):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) env.info(text) -- Dynamic patrol logic based on current patrol zone @@ -206,13 +229,13 @@ function BlueCVNGroup:OnAfterPassingWaypoint(From, Event, To, Waypoint) if currentWaypointID == currentZone.endWP then BlueCVNGroup:GotoWaypoint(currentZone.startWP) local patrolText = string.format("Patrolling: Going to waypoint %d (Zone %d)", currentZone.startWP, SetCVNActivePatrolZone) - MESSAGE:New(patrolText, msgTime):ToBlue() + sendThrottled("CVN_PatrolGoto", 10, function() MESSAGE:New(patrolText, msgTime):ToBlue() end) env.info(patrolText) -- If we reached the start waypoint of current patrol zone, go to end waypoint elseif currentWaypointID == currentZone.startWP then BlueCVNGroup:GotoWaypoint(currentZone.endWP) local patrolText = string.format("Patrolling: Going to waypoint %d (Zone %d)", currentZone.endWP, SetCVNActivePatrolZone) - MESSAGE:New(patrolText, msgTime):ToBlue() + sendThrottled("CVN_PatrolGoto", 10, function() MESSAGE:New(patrolText, msgTime):ToBlue() end) env.info(patrolText) end end @@ -223,8 +246,10 @@ end function BlueCVNGroup:OnAfterCruise(From, Event, To) local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln" local text=unitName.." is cruising straight and steady." - MESSAGE:New(text, msgTime):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_Cruise", 30, function() + MESSAGE:New(text, msgTime):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) env.info(text) end @@ -232,8 +257,10 @@ end function BlueCVNGroup:OnAfterTurningStarted(From, Event, To) local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln" local text=unitName.." has started turning!" - MESSAGE:New(text, msgTime):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_TurningStarted", 30, function() + MESSAGE:New(text, msgTime):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) env.info(text) end @@ -241,8 +268,10 @@ end function BlueCVNGroup:OnAfterTurningStopped(From, Event, To) local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln" local text=unitName.." has stopped turning..proceeding to next waypoint." - MESSAGE:New(text, msgTime):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_TurningStopped", 30, function() + MESSAGE:New(text, msgTime):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) env.info(text) end @@ -262,8 +291,10 @@ BlueCVNGroup:SetCheckZones(ZoneSet) function BlueCVNGroup:OnAfterEnterZone(From, Event, To, Zone) local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln" local text=string.format(unitName.." has entered patrol zone %s.", Zone:GetName()) - MESSAGE:New(text, msgTime):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_EnterZone", 20, function() + MESSAGE:New(text, msgTime):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) env.info(text) end @@ -271,7 +302,9 @@ end function BlueCVNGroup:OnAfterLeaveZone(From, Event, To, Zone) local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln" local text=string.format(unitName.." left patrol zone %s.", Zone:GetName()) - MESSAGE:New(text, msgTime):ToBlue() - USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + sendThrottled("CVN_LeaveZone", 20, function() + MESSAGE:New(text, msgTime):ToBlue() + USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE) + end) env.info(text) end \ No newline at end of file diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua index 64f2d33..eae8de1 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua @@ -19,6 +19,13 @@ REQUIRES: ═══════════════════════════════════════════════════════════════════════════════ ]] +-- Single-run guard to prevent duplicate dispatcher loops if script is reloaded +if _G.__TDAC_DISPATCHER_RUNNING then + env.info("[TDAC] CargoDispatcher already running; aborting duplicate load") + return +end +_G.__TDAC_DISPATCHER_RUNNING = true + --[[ GLOBAL STATE AND CONFIGURATION -------------------------------------------------------------------------- diff --git a/DCS_Kola/Operation_Polar_Shield/OnBirthMessage.lua b/DCS_Kola/Operation_Polar_Shield/OnBirthMessage.lua index b5f0b75..b296f00 100644 --- a/DCS_Kola/Operation_Polar_Shield/OnBirthMessage.lua +++ b/DCS_Kola/Operation_Polar_Shield/OnBirthMessage.lua @@ -5,6 +5,7 @@ trigger.action.outText("OnBirthMessage script is loading...", 10) -- Player preferences storage local playerWelcomeSettings = {} local processedPlayers = {} -- Track players to prevent double processing +local DUP_TTL_SECONDS = 10 -- window to ignore duplicate events for the same player -- F10 Menu Functions local function enableWelcomeMessage(playerUnitID, playerName) @@ -77,19 +78,20 @@ function onPlayerJoin:onEvent(event) if (event.id == world.event.S_EVENT_BIRTH or event.id == world.event.S_EVENT_ENGINE_STARTUP) then env.info("OnBirthMessage: Correct event type detected") - if event.initiator then + if event.initiator then env.info("OnBirthMessage: Initiator exists") local playerName = event.initiator:getPlayerName() if playerName then env.info("OnBirthMessage: Player name found: " .. playerName) - -- Check if we've already processed this player to prevent doubles - local playerKey = playerName .. "_" .. event.id - if processedPlayers[playerKey] then - env.info("OnBirthMessage: Already processed " .. playerName .. " for event " .. event.id .. " - skipping") + -- Check if we've already processed this player to prevent doubles (within TTL) + local now = (event.time or timer.getTime()) + local last = processedPlayers[playerName] + if last and (now - last) < DUP_TTL_SECONDS then + env.info("OnBirthMessage: Duplicate event for " .. playerName .. " within TTL - skipping") return end - processedPlayers[playerKey] = true + processedPlayers[playerName] = now -- Add error handling to prevent script crashes local success, errorMsg = pcall(function() diff --git a/Moose_.lua b/Moose_.lua index 8bbc66d..6063473 100644 --- a/Moose_.lua +++ b/Moose_.lua @@ -1,4 +1,4 @@ -env.info('*** MOOSE GITHUB Commit Hash ID: 2025-10-26T07:31:50+01:00-1965e24860936512d2670eb7d41c8440707a12ff ***') +env.info('*** MOOSE GITHUB Commit Hash ID: 2025-11-03T06:30:00+01:00-4ade4c78bab6bc82e93a604b4a1838872cd5daa9 ***') if not MOOSE_DEVELOPMENT_FOLDER then MOOSE_DEVELOPMENT_FOLDER='Scripts' end @@ -3986,7 +3986,7 @@ for id,gridpoint in ipairs(Grid)do local UnitTemplate=UTILS.DeepCopy(unitData) UnitTemplate.x=gridpoint.x UnitTemplate.y=gridpoint.y -UnitTemplate.name=Name.."-"..id +if id>1 then UnitTemplate.name=Name.."-"..id end table.insert(groupData.units,UnitTemplate) if id==1 then groupData.x=gridpoint.x @@ -14784,8 +14784,7 @@ end return self end function SET_CLIENT:HandleCASlots() -self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) -self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventPlayerEnterUnit) self:FilterFunction(function(client)if client and client:IsAlive()and client:IsGround()then return true else return false end end) return self end @@ -17752,6 +17751,7 @@ local AirbaseCategory=airbase:GetAirbaseCategory() if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then RoutePoint.linkUnit=AirbaseID RoutePoint.helipadId=AirbaseID +RoutePoint.airdromeId=airbase:IsAirdrome()and AirbaseID or nil elseif AirbaseCategory==Airbase.Category.AIRDROME then RoutePoint.airdromeId=AirbaseID else @@ -24080,6 +24080,7 @@ POSITIONABLE.CargoBayCapacityValues={ ["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, ["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +["Sd_Kfz_251"]=10*POSITIONABLE.DefaultInfantryWeight, } } function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) @@ -35460,8 +35461,8 @@ self:SetScoringMenu(PlayerUnit:GetGroup()) end end) self.AutoSavePath=SavePath -self.AutoSave=AutoSave or true -if self.AutoSave==true then +self.AutoSave=(AutoSave==nil or AutoSave==true)and true or false +if self.AutoSavePath and self.AutoSave==true then self:OpenCSV(GameName) end return self @@ -62991,13 +62992,13 @@ aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) elseif goshawk then -aoa.SLOW=8.00 -aoa.Slow=7.75 -aoa.OnSpeedMax=7.25 -aoa.OnSpeed=7.00 -aoa.OnSpeedMin=6.75 -aoa.Fast=6.25 -aoa.FAST=6.00 +aoa.SLOW=9.5 +aoa.Slow=9.25 +aoa.OnSpeedMax=9.0 +aoa.OnSpeed=8.5 +aoa.OnSpeedMin=8.25 +aoa.Fast=7.75 +aoa.FAST=5.5 elseif skyhawk then aoa.SLOW=10.50 aoa.Slow=9.50 @@ -64300,7 +64301,12 @@ if playerData then local unit=playerData.unit if unit and unit:IsAlive()then if unit:IsInZone(self.zoneCCA)then -if playerData.step==AIRBOSS.PatternStep.WAKE then +local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF +or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then playerData.wrappedUpAtWakeLittle=true elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then @@ -64322,6 +64328,28 @@ playerData.AFU=true else end end +if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<12 and math.abs(playerData.unit:GetRoll())>=5 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<5 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end if playerData.attitudemonitor then self:_AttitudeMonitor(playerData) end @@ -73462,6 +73490,7 @@ self.usesubcats=false self.subcats={} self.subcatsTroop={} self.showstockinmenuitems=false +self.maxCrateMenuQuantity=5 self.onestepmenu=false self.nobuildinloadzones=true self.movecratesbeforebuild=true @@ -73614,6 +73643,7 @@ self.Loaded_Cargo[unitname]=loaded local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesPickedUp(1,Group,client,dcargo) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoUnloaded then self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) @@ -73650,6 +73680,7 @@ end local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesDropped(1,Group,client,{dcargo}) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoRemoved then self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) @@ -73833,6 +73864,7 @@ self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group,Unit,Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() +self:_RefreshTroopQuantityMenus(Group,Unit,Cargotype) end return self end @@ -74057,14 +74089,189 @@ end self:CleanDroppedTroops() return self end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +function CTLD:_LoadTroopsQuantity(Group,Unit,Cargo,quantity) +local n=math.max(1,tonumber(quantity)or 1) +local prevSuppress=self.suppressmessages +self.suppressmessages=true +for i=1,n do +timer.scheduleFunction(function()self:_LoadTroops(Group,Unit,Cargo,true)end,{},timer.getTime()+0.2*i) +end +timer.scheduleFunction(function() +self.suppressmessages=prevSuppress +local dname=Cargo:GetName() +self:_SendMessage(string.format("Loaded %d %s.",n,dname),10,false,Group) +end,{},timer.getTime()+0.2*n+0.05) +return self +end +function CTLD:_AddTroopQuantityMenus(Group,Unit,parentMenu,cargoObj) +local stock=cargoObj:GetStock() +local maxQuantity=self.maxCrateMenuQuantity or 1 +if type(stock)=="number"and stock>=0 and stock0 then +local space=trooplimit-onboard +if space=0 then +availableSets=math.floor(stock) +if availableSets<=0 then +MENU_GROUP_COMMAND:New(Group,"Out of stock",parentMenu,function()end) +return self +end +if availableSets0 then +local loadedData=nil +if self.Loaded_Cargo then +loadedData=self.Loaded_Cargo[Unit:GetName()] +end +local loadedCount=0 +if loadedData and type(loadedData.Cratesloaded)=="number"then +loadedCount=loadedData.Cratesloaded +end +local space=capacity-loadedCount +if space<0 then +space=0 +end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +end +end +local allowLoad=true +if type(capacitySets)=="number"then +if capacitySets>=1 then +if capacitySets0 and needed or 1) +if type(maxload)=="number"and maxload>0 and setMass>0 then +maxMassSets=math.floor(maxload/setMass) +if maxMassSets<1 then +maxQuantity=1 +allowLoad=false +elseif maxMassSets=1)and(not maxMassSets or maxMassSets>=1)) +if canLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1) +else +local msg +if maxMassSets and(not capacitySets or capacitySets>=1)and maxMassSets<1 then +msg="Weight limit reached" +else +msg="Crate limit reached" +end +MENU_GROUP_COMMAND:New(Group,msg,parentMenu,self._SendMessage,self,msg,10,false,Group) +end +return self +end +for quantity=1,maxQuantity do +local label=tostring(quantity) +local qMenu=MENU_GROUP:New(Group,label,parentMenu) +MENU_GROUP_COMMAND:New(Group,"Get",qMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,quantity) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=quantity)and(not maxMassSets or maxMassSets>=quantity)) +if canLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",qMenu,self._GetAndLoad,self,Group,Unit,cargoObj,quantity) +else +local msg +if maxMassSets and(not capacitySets or capacitySets>=quantity)and maxMassSets=canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!",10,false,Group) -return self +return false end local IsHerc=self:IsFixedWing(Unit) local IsHook=self:IsHook(Unit) local IsTruck=Unit:IsGround() local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() +local number=requestNumber local cratesneeded=cargotype:GetCratesNeeded() local cratename=cargotype:GetName() local cratetemplate="Container" @@ -74217,8 +74424,8 @@ if drop then realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) local map=cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +local CCat3,CType3,CShape3=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end @@ -74231,22 +74438,25 @@ if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end end -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +local CCat4,CType4,CShape4=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) table.insert(self.Spawned_Cargo,realcargo) end if not(drop or pack)then -Cargo:RemoveStock() +Cargo:RemoveStock(requestedSets) +self:_RefreshCrateQuantityMenus(Group,Unit,Cargo) end local text=string.format("Crates for %s have been positioned near you!",cratename) if drop then text=string.format("Crates for %s have been dropped!",cratename) self:__CratesDropped(1,Group,Unit,droppedcargo) else +if not quiet then self:_SendMessage(text,10,false,Group) end +end self:_RefreshLoadCratesMenu(Group,Unit) -return self +return true end function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) self:T(self.lid.." InjectStatics") @@ -74367,7 +74577,15 @@ text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(),30,true,_group) -self:_CleanupTrackedCrates(removedIDs) +local done={} +for _,e in pairs(crates)do +local n=e:GetName()or"none" +if not done[n]then +local object=self:_FindCratesCargoObject(n) +if object then self:_RefreshCrateQuantityMenus(_group,_unit,object)end +done[n]=true +end +end self:_RefreshLoadCratesMenu(_group,_unit) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) @@ -74540,6 +74758,7 @@ self:_RefreshDropCratesMenu(Group,Unit) self:_RefreshLoadCratesMenu(Group,Unit) self:_CleanupTrackedCrates(crateidsloaded) self:__CratesPickedUp(1,Group,Unit,loaded.Cargo) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) end end return self @@ -74903,7 +75122,10 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then local stock=_troop:GetStock() -if stock and tonumber(stock)>=0 then _troop:AddStock()end +if stock and tonumber(stock)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end end end end @@ -74913,6 +75135,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -75001,6 +75224,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -75347,13 +75571,40 @@ end timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) return self end -function CTLD:_GetAndLoad(Group,Unit,cargoObj) +function CTLD:_GetAndLoad(Group,Unit,cargoObj,quantity) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end -self:_GetCrates(Group,Unit,cargoObj) -timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1) +local needed=cargoObj and cargoObj:GetCratesNeeded()or 1 +local count=math.max(1,tonumber(quantity)or 1) +local capacitySets=nil +local cap=self:_GetUnitCapabilities(Unit) +local limit=cap and cap.cratelimit or 0 +if limit>0 then +local ld=self.Loaded_Cargo and self.Loaded_Cargo[Unit:GetName()]or nil +local loaded=(ld and type(ld.Cratesloaded)=="number")and ld.Cratesloaded or 0 +local space=limit-loaded +if space<0 then space=0 end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +if capacitySets<1 then +self:_SendMessage("No capacity to load more now!",10,false,Group) +return self +end +if count>capacitySets then count=capacitySets end +end +local total=needed*count +local ok=self:_GetCrates(Group,Unit,cargoObj,total,false,false,true) +if ok then +local uname=Unit:GetName() +self._batchCrateLoad=self._batchCrateLoad or{} +self._batchCrateLoad[uname]={remaining=count,group=Group,cname=cargoObj.Name,loaded=0,partials=0} +for i=1,count do +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+0.2*i) +end +end +return self end function CTLD:_GetAllAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then @@ -75362,6 +75613,236 @@ return self end timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) end +function CTLD:_GetCrateStockEntry(cargoObj,stockSummary) +if not cargoObj or not stockSummary then +return nil +end +local name=cargoObj:GetName() +if not name then +return nil +end +return stockSummary[name] +end +function CTLD:_FormatCrateStockSuffix(cargoObj,stockSummary) +if not cargoObj then +return nil +end +local stockEntry=self:_GetCrateStockEntry(cargoObj,stockSummary) +local available=nil +if stockEntry and type(stockEntry.Stock)=="number"then +available=stockEntry.Stock +end +if type(available)~="number"then +local direct=cargoObj:GetStock() +if type(direct)=="number"then +available=direct +end +end +if type(available)~="number"or available<0 then +return nil +end +local rounded=math.floor(available+0.5) +local total=nil +if stockEntry and type(stockEntry.Stock0)=="number"and stockEntry.Stock0>=0 then +total=math.floor(stockEntry.Stock0+0.5) +elseif stockEntry and type(stockEntry.Sum)=="number"and stockEntry.Sum>=0 then +total=math.floor(stockEntry.Sum+0.5) +end +if type(total)~="number"then +local baseTotal=cargoObj.GetStock0 and cargoObj:GetStock0()or nil +if type(baseTotal)=="number"and baseTotal>=0 then +total=math.floor(baseTotal+0.5) +end +end +if type(total)=="number"and total>0 and total~=rounded then +return string.format("[%d/%d]",rounded,total) +else +return string.format("[%d]",rounded) +end +end +function CTLD:_RefreshCrateQuantityMenus(Group,Unit,CargoObj) +if not Group and Unit then Group=Unit:GetGroup()end +if Group and Unit then +local uname=Unit:GetName()or"none" +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +if Group.CTLD_CrateMenus then +local present={} +for item,_ in pairs(Group.CTLD_CrateMenus)do present["C:"..tostring(item)]=true end +for key,_ in pairs(self._qtySnap[uname])do +if string.sub(key,1,2)=="C:"and not present[key]then +self._qtySnap[uname][key]=nil +end +end +local stockSummary=self.showstockinmenuitems and self:_CountStockPlusInHeloPlusAliveGroups(false)or nil +for item,menu in pairs(Group.CTLD_CrateMenus)do +menu:RemoveSubMenus() +local obj=self:_FindCratesCargoObject(item) +if obj then self:_AddCrateQuantityMenus(Group,Unit,menu,obj,stockSummary)end +end +end +end +if CargoObj and Group and Unit then +local uname=Unit:GetName()or"none" +local cap=(self:_GetUnitCapabilities(Unit).cratelimit or 0) +local loaded=(self.Loaded_Cargo[uname]and self.Loaded_Cargo[uname].Cratesloaded)or 0 +local avail=math.max(0,cap-loaded) +local per=CargoObj:GetCratesNeeded()or 1 +if per<1 then per=1 end +local unitAvail=math.max(0,math.min(self.maxCrateMenuQuantity or 1,math.floor(avail/per))) +local s=CargoObj:GetStock() +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +local k="C:"..(CargoObj:GetName()or"none") +local snap=tostring(type(s)=="number"and s or-1)..":"..tostring(unitAvail) +if self._qtySnap[uname][k]~=snap then +self._qtySnap[uname][k]=snap +if type(s)=="number"and s>=0 and s=0 and s=0 and s=0 and s=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj) +local parent=subcatmenus[cargoObj.Subcategory]or troopsmenu +local mSet=MENU_GROUP:New(_group,menutext,parent) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Troops)do if not cargoObj.DontShowInMenu then -local stock=cargoObj:GetStock() local menutext=cargoObj.Name -if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj) +local mSet=MENU_GROUP:New(_group,menutext,troopsmenu) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end end @@ -75486,59 +75970,43 @@ local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) _group.MyTopCratesMenu=topcrates local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) if self.onestepmenu then +_group.CTLD_CrateMenus={} +local crateStockSummary=nil +if self.showstockinmenuitems then +crateStockSummary=self:_CountStockPlusInHeloPlusAliveGroups(false) +end +local function addCrateMenuEntry(cargoObj,parentMenu) +if cargoObj.DontShowInMenu then +return +end +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +if cargoObj.Location then txt=txt.."[R]"end +if self.showstockinmenuitems then +local suffix=self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) +if suffix then txt=txt..suffix end +end +local mSet=MENU_GROUP:New(_group,txt,parentMenu) +_group.CTLD_CrateMenus[cargoObj.Name]=mSet +self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) +end if self.usesubcats then local subcatmenus={} for catName,_ in pairs(self.subcats)do subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local needed=cargoObj:GetCratesNeeded()or 1 -local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,subcatmenus[cargoObj.Subcategory]or cratesmenu) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local needed=cargoObj:GetCratesNeeded()or 1 -local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,subcatmenus[cargoObj.Subcategory]or cratesmenu) end else for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local needed=cargoObj:GetCratesNeeded()or 1 -local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local needed=cargoObj:GetCratesNeeded()or 1 -local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end end else @@ -75652,6 +76120,8 @@ end self.MenusDone[_unitName]=true self:_RefreshLoadCratesMenu(_group,_unit) self:_RefreshDropCratesMenu(_group,_unit) +if firstBuild then menucount=menucount+1 end +if firstBuild and not self.showstockinmenuitems then self:_RefreshQuantityMenusForGroup(_group,_unit)end end end else @@ -75727,6 +76197,9 @@ self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10 return self end local found=#matchingCrates +local batch=self._batchCrateLoad and self._batchCrateLoad[Unit:GetName()]or nil +local prevSuppress=self.suppressmessages +if batch and batch.cname==cargoName then self.suppressmessages=true end local unitName=Unit:GetName() local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} local capabilities=self:_GetUnitCapabilities(Unit) @@ -75771,6 +76244,7 @@ end end self.Spawned_Cargo=newSpawned local loadedHere=toLoad +if not batch then if loadedHere=capacity then self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) else @@ -75788,8 +76262,27 @@ else self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) end end +end self:_RefreshLoadCratesMenu(Group,Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,self:_FindCratesCargoObject(cargoName)) +if batch and batch.cname==cargoName then +local setsLoaded=math.floor((loadedHere or 0)/(needed or 1)) +batch.loaded=(batch.loaded or 0)+(setsLoaded or 0) +if loadedHere<(needed or 1)then batch.partials=(batch.partials or 0)+1 end +batch.remaining=(batch.remaining or 1)-1 +if batch.remaining<=0 then +self.suppressmessages=prevSuppress +local txt=string.format("Loaded %d %s.",batch.loaded,cargoName) +if batch.partials and batch.partials>0 then +txt=txt.." Some sets could not be fully loaded." +end +self:_SendMessage(txt,10,false,batch.group) +self._batchCrateLoad[Unit:GetName()]=nil +else +self.suppressmessages=prevSuppress +end +end return self end function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) @@ -75883,6 +76376,7 @@ end self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) return self end function CTLD:_RefreshDropCratesMenu(Group,Unit) @@ -76113,6 +76607,7 @@ if _troop.Name==cName then local st=_troop:GetStock() if st and tonumber(st)>=0 then _troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) end end end @@ -76143,6 +76638,7 @@ end self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded self:_RefreshDropTroopsMenu(Group,Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else local isHerc=self:IsFixedWing(Unit) if isHerc then @@ -77113,6 +77609,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -77125,6 +77622,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -77137,6 +77635,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -77149,6 +77648,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -77161,6 +77661,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -77173,6 +77674,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -77223,6 +77725,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) end end return self @@ -77234,6 +77737,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() end end return self @@ -77245,6 +77749,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() end end return self @@ -77694,6 +78199,7 @@ function CTLD:onafterTroopsDeployed(From,Event,To,Group,Unit,Troops,Type) self:T({From,Event,To}) if self.movetroopstowpzone and Type~=CTLD_CARGO.Enum.ENGINEERS then self:_MoveGroupToZone(Troops) +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end end return self end @@ -77732,6 +78238,7 @@ if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_n self:_MoveGroupToZone(Vehicle) end end +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end return self end function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) @@ -110282,8 +110789,10 @@ NextTaskSuccess={}, NextTaskFailure={}, FinalState="none", PreviousCount=0, +CanSmoke=true, +ShowThreatDetails=true, } -PLAYERTASK.version="0.1.28" +PLAYERTASK.version="0.1.29" function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) local self=BASE:Inherit(self,FSM:New()) self.Type=Type @@ -110448,6 +110957,16 @@ self:T(self.lid.."AddSubType") self.TaskSubType=Type return self end +function PLAYERTASK:SetCanSmoke(OnOff) +self:T(self.lid.."AddSSetCanSmokeubType") +self.CanSmoke=OnOff +return self +end +function PLAYERTASK:SetShowThreatDetails(OnOff) +self:T(self.lid.."SetShowThreatDetails") +self.ShowThreatDetails=OnOff +return self +end function PLAYERTASK:GetSubType() self:T(self.lid.."GetSubType") return self.TaskSubType @@ -110845,10 +111364,10 @@ self.timestamp=timer.getAbsTime() self:__Stop(-1) return self end -function PLAYERTASK:onafterCancel(From,Event,To) +function PLAYERTASK:onafterCancel(From,Event,To,Silent) self:T({From,Event,To}) if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) +self.TaskController:__TaskCancelled(-1,self,Silent) end self.timestamp=timer.getAbsTime() self.FinalState="Cancelled" @@ -111010,12 +111529,15 @@ THREATHIGH="high", THREATMEDIUM="medium", THREATLOW="low", THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +NOTHREATTEXT="%s\nNo target information available.", ELEVATION="\nTarget Elevation: %s %s", METER="meter", FEET="feet", THREATTEXTTTS="%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.", +NOTHREATTEXTTTS="%s, %s. No target information available.", MARKTASK="%s, %s, copy, task %03d location marked on map!", SMOKETASK="%s, %s, copy, task %03d location smoked!", +NOSMOKETASK="%s, %s, negative, task %03d location cannot be smoked!", FLARETASK="%s, %s, copy, task %03d location illuminated!", ABORTTASK="All stations, %s, %s has aborted %s task %03d!", UNKNOWN="Unknown", @@ -111094,12 +111616,15 @@ THREATHIGH="hoch", THREATMEDIUM="mittel", THREATLOW="niedrig", THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +NOTHREATTEXT="%s\nKeine Zielinformation verfügbar.", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +NOTHREATTEXTTTS="%s, %s. Keine Zielinformation verfügbar.", MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +NOSMOKETASK="%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN="Unbekannt", @@ -111157,7 +111682,7 @@ CARRIER="Flugzeugträger", RADIOS="Frequenzen", }, } -PLAYERTASKCONTROLLER.version="0.1.70" +PLAYERTASKCONTROLLER.version="0.1.71" function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) local self=BASE:Inherit(self,FSM:New()) self.Name=Name or"CentCom" @@ -111583,9 +112108,9 @@ self.ClusterRadius=Radius or 0.5 self.usecluster=true return self end -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") -Task:__Cancel(-1) +Task:__Cancel(-1,Silent) return self end function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) @@ -112282,6 +112807,7 @@ local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0) local Elevation=Coordinate:GetLandHeight()or 0 local CoordText="" local CoordTextLLDM=nil +local ShowThreatInfo=task.ShowThreatDetails local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr) if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) @@ -112299,7 +112825,12 @@ local targets=task.Target:CountTargets()or 0 local clientlist,clientcount=task:GetClients() local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel local ThreatLocaleText=self.gettext:GetEntry("THREATTEXT",self.locale) +if ShowThreatInfo==true then text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) +else +ThreatLocaleText=self.gettext:GetEntry("NOTHREATTEXT",self.locale) +text=string.format(ThreatLocaleText,taskname) +end local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric()then @@ -112401,8 +112932,14 @@ CoordText="MGRS;"..Text if self.PathToGoogleKey then end end +local ttstext local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if ShowThreatInfo==true then +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +else +ThreatLocaleTextTTS=self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) +end if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) @@ -112455,6 +112992,7 @@ local playername,ttsplayername=self:_GetPlayerName(Client) local text="" if self.TasksPerPlayer:HasUniqueID(playername)then local task=self.TasksPerPlayer:ReadByID(playername) +if task.CanSmoke==true then task:SmokeTarget() local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) @@ -112464,6 +113002,14 @@ self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self:__TaskTargetSmoked(5,task) else +local textmark=self.gettext:GetEntry("NOSMOKETASK",self.locale) +text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) +self:T(self.lid..text) +if self.UseSRS then +self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) +end +end +else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then @@ -113051,19 +113597,21 @@ self:T({From,Event,To}) self:T(self.lid.."TaskDone") return self end -function PLAYERTASKCONTROLLER:onafterTaskCancelled(From,Event,To,Task) +function PLAYERTASKCONTROLLER:onafterTaskCancelled(From,Event,To,Task,Silent) self:T({From,Event,To}) self:T(self.lid.."TaskCancelled") +if Silent~=true then local canceltxt=self.gettext:GetEntry("TASKCANCELLED",self.locale) local canceltxttts=self.gettext:GetEntry("TASKCANCELLEDTTS",self.locale) local taskname=string.format(canceltxt,Task.PlayerTaskNr,tostring(Task.Type)) -if not self.NoScreenOutput then +if self.NoScreenOutput~=true then self:_SendMessageToClients(taskname,15) end if self.UseSRS then taskname=string.format(canceltxttts,self.MenuName or self.Name,Task.PlayerTaskNr,tostring(Task.TTSType)) self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2) end +end local clients=Task:GetClientObjects() for _,client in pairs(clients)do self:_RemoveMenuEntriesForTask(Task,client) diff --git a/Moose_.lua.backup b/Moose_.lua.backup new file mode 100644 index 0000000..8bbc66d --- /dev/null +++ b/Moose_.lua.backup @@ -0,0 +1,132469 @@ +env.info('*** MOOSE GITHUB Commit Hash ID: 2025-10-26T07:31:50+01:00-1965e24860936512d2670eb7d41c8440707a12ff ***') +if not MOOSE_DEVELOPMENT_FOLDER then +MOOSE_DEVELOPMENT_FOLDER='Scripts' +end +ModuleLoader=MOOSE_DEVELOPMENT_FOLDER..'/Moose/Modules.lua' +if io then +local f=io.open(ModuleLoader,"r") +if f~=nil then +io.close(f) +env.info('*** MOOSE DYNAMIC INCLUDE START *** ') +local base=_G +__Moose={} +__Moose.Include=function(IncludeFile) +if not __Moose.Includes[IncludeFile]then +__Moose.Includes[IncludeFile]=IncludeFile +local f=assert(base.loadfile(IncludeFile)) +if f==nil then +error("Moose: Could not load Moose file "..IncludeFile) +else +env.info("Moose: "..IncludeFile.." dynamically loaded.") +return f() +end +end +end +__Moose.Includes={} +__Moose.Include(MOOSE_DEVELOPMENT_FOLDER..'/Moose/Modules.lua') +BASE:TraceOnOff(true) +env.info('*** MOOSE INCLUDE END *** ') +do return end +end +else +env.info('*** MOOSE DYNAMIC INCLUDE NOT POSSIBLE (Desanitize io to use it) *** ') +end +env.info('*** MOOSE STATIC INCLUDE START *** ') +ENUMS={} +env.setErrorMessageBoxEnabled(false) +ENUMS.ROE={ +WeaponFree=0, +OpenFireWeaponFree=1, +OpenFire=2, +ReturnFire=3, +WeaponHold=4, +} +ENUMS.ROT={ +NoReaction=0, +PassiveDefense=1, +EvadeFire=2, +BypassAndEscape=3, +AllowAbortMission=4, +} +ENUMS.AlarmState={ +Auto=0, +Green=1, +Red=2, +} +ENUMS.WeaponFlag={ +LGB=2, +TvGB=4, +SNSGB=8, +HEBomb=16, +Penetrator=32, +NapalmBomb=64, +FAEBomb=128, +ClusterBomb=256, +Dispencer=512, +CandleBomb=1024, +ParachuteBomb=2147483648, +LightRocket=2048, +MarkerRocket=4096, +CandleRocket=8192, +HeavyRocket=16384, +AntiRadarMissile=32768, +AntiShipMissile=65536, +AntiTankMissile=131072, +FireAndForgetASM=262144, +LaserASM=524288, +TeleASM=1048576, +CruiseMissile=2097152, +AntiRadarMissile2=1073741824, +SRAM=4194304, +MRAAM=8388608, +LRAAM=16777216, +IR_AAM=33554432, +SAR_AAM=67108864, +AR_AAM=134217728, +GunPod=268435456, +BuiltInCannon=536870912, +GuidedBomb=14, +AnyUnguidedBomb=2147485680, +AnyBomb=2147485694, +AnyRocket=30720, +GuidedASM=1572864, +TacticalASM=1835008, +AnyASM=4161536, +AnyASM2=1077903360, +AnyAAM=264241152, +AnyAutonomousMissile=36012032, +AnyMissile=268402688, +Cannons=805306368, +Torpedo=4294967296, +Auto=3221225470, +AutoDCS=1073741822, +AnyAG=2956984318, +AnyAA=264241152, +AnyUnguided=2952822768, +AnyGuided=268402702, +} +ENUMS.WeaponType={} +ENUMS.WeaponType.Bomb={ +LGB=2, +TvGB=4, +SNSGB=8, +HEBomb=16, +Penetrator=32, +NapalmBomb=64, +FAEBomb=128, +ClusterBomb=256, +Dispencer=512, +CandleBomb=1024, +ParachuteBomb=2147483648, +GuidedBomb=14, +AnyUnguidedBomb=2147485680, +AnyBomb=2147485694, +} +ENUMS.WeaponType.Rocket={ +LightRocket=2048, +MarkerRocket=4096, +CandleRocket=8192, +HeavyRocket=16384, +AnyRocket=30720, +} +ENUMS.WeaponType.Gun={ +GunPod=268435456, +BuiltInCannon=536870912, +Cannons=805306368, +} +ENUMS.WeaponType.Missile={ +AntiRadarMissile=32768, +AntiShipMissile=65536, +AntiTankMissile=131072, +FireAndForgetASM=262144, +LaserASM=524288, +TeleASM=1048576, +CruiseMissile=2097152, +AntiRadarMissile2=1073741824, +GuidedASM=1572864, +TacticalASM=1835008, +AnyASM=4161536, +AnyASM2=1077903360, +AnyAutonomousMissile=36012032, +AnyMissile=268402688, +} +ENUMS.WeaponType.AAM={ +SRAM=4194304, +MRAAM=8388608, +LRAAM=16777216, +IR_AAM=33554432, +SAR_AAM=67108864, +AR_AAM=134217728, +AnyAAM=264241152, +} +ENUMS.WeaponType.Torpedo={ +Torpedo=4294967296, +} +ENUMS.WeaponType.Any={ +Weapon=3221225470, +AG=2956984318, +AA=264241152, +Unguided=2952822768, +Guided=268402702, +} +ENUMS.MissionTask={ +NOTHING="Nothing", +AFAC="AFAC", +ANTISHIPSTRIKE="Antiship Strike", +AWACS="AWACS", +CAP="CAP", +CAS="CAS", +ESCORT="Escort", +GROUNDESCORT="Ground escort", +FIGHTERSWEEP="Fighter Sweep", +GROUNDATTACK="Ground Attack", +INTERCEPT="Intercept", +PINPOINTSTRIKE="Pinpoint Strike", +RECONNAISSANCE="Reconnaissance", +REFUELING="Refueling", +RUNWAYATTACK="Runway Attack", +SEAD="SEAD", +TRANSPORT="Transport", +} +ENUMS.Formation={} +ENUMS.Formation.FixedWing={} +ENUMS.Formation.FixedWing.LineAbreast={} +ENUMS.Formation.FixedWing.LineAbreast.Close=65537 +ENUMS.Formation.FixedWing.LineAbreast.Open=65538 +ENUMS.Formation.FixedWing.LineAbreast.Group=65539 +ENUMS.Formation.FixedWing.Trail={} +ENUMS.Formation.FixedWing.Trail.Close=131073 +ENUMS.Formation.FixedWing.Trail.Open=131074 +ENUMS.Formation.FixedWing.Trail.Group=131075 +ENUMS.Formation.FixedWing.Wedge={} +ENUMS.Formation.FixedWing.Wedge.Close=196609 +ENUMS.Formation.FixedWing.Wedge.Open=196610 +ENUMS.Formation.FixedWing.Wedge.Group=196611 +ENUMS.Formation.FixedWing.EchelonRight={} +ENUMS.Formation.FixedWing.EchelonRight.Close=262145 +ENUMS.Formation.FixedWing.EchelonRight.Open=262146 +ENUMS.Formation.FixedWing.EchelonRight.Group=262147 +ENUMS.Formation.FixedWing.EchelonLeft={} +ENUMS.Formation.FixedWing.EchelonLeft.Close=327681 +ENUMS.Formation.FixedWing.EchelonLeft.Open=327682 +ENUMS.Formation.FixedWing.EchelonLeft.Group=327683 +ENUMS.Formation.FixedWing.FingerFour={} +ENUMS.Formation.FixedWing.FingerFour.Close=393217 +ENUMS.Formation.FixedWing.FingerFour.Open=393218 +ENUMS.Formation.FixedWing.FingerFour.Group=393219 +ENUMS.Formation.FixedWing.Spread={} +ENUMS.Formation.FixedWing.Spread.Close=458753 +ENUMS.Formation.FixedWing.Spread.Open=458754 +ENUMS.Formation.FixedWing.Spread.Group=458755 +ENUMS.Formation.FixedWing.BomberElement={} +ENUMS.Formation.FixedWing.BomberElement.Close=786433 +ENUMS.Formation.FixedWing.BomberElement.Open=786434 +ENUMS.Formation.FixedWing.BomberElement.Group=786435 +ENUMS.Formation.FixedWing.BomberElementHeight={} +ENUMS.Formation.FixedWing.BomberElementHeight.Close=851968 +ENUMS.Formation.FixedWing.FighterVic={} +ENUMS.Formation.FixedWing.FighterVic.Close=917505 +ENUMS.Formation.FixedWing.FighterVic.Open=917506 +ENUMS.Formation.RotaryWing={} +ENUMS.Formation.RotaryWing.Column={} +ENUMS.Formation.RotaryWing.Column.D70=720896 +ENUMS.Formation.RotaryWing.Wedge={} +ENUMS.Formation.RotaryWing.Wedge.D70=8 +ENUMS.Formation.RotaryWing.FrontRight={} +ENUMS.Formation.RotaryWing.FrontRight.D300=655361 +ENUMS.Formation.RotaryWing.FrontRight.D600=655362 +ENUMS.Formation.RotaryWing.FrontLeft={} +ENUMS.Formation.RotaryWing.FrontLeft.D300=655617 +ENUMS.Formation.RotaryWing.FrontLeft.D600=655618 +ENUMS.Formation.RotaryWing.EchelonRight={} +ENUMS.Formation.RotaryWing.EchelonRight.D70=589825 +ENUMS.Formation.RotaryWing.EchelonRight.D300=589826 +ENUMS.Formation.RotaryWing.EchelonRight.D600=589827 +ENUMS.Formation.RotaryWing.EchelonLeft={} +ENUMS.Formation.RotaryWing.EchelonLeft.D70=590081 +ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082 +ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083 +ENUMS.Formation.Vehicle={} +ENUMS.Formation.Vehicle.Vee="Vee" +ENUMS.Formation.Vehicle.EchelonRight="EchelonR" +ENUMS.Formation.Vehicle.OffRoad="Off Road" +ENUMS.Formation.Vehicle.Rank="Rank" +ENUMS.Formation.Vehicle.EchelonLeft="EchelonL" +ENUMS.Formation.Vehicle.OnRoad="On Road" +ENUMS.Formation.Vehicle.Cone="Cone" +ENUMS.Formation.Vehicle.Diamond="Diamond" +ENUMS.FormationOld={} +ENUMS.FormationOld.FixedWing={} +ENUMS.FormationOld.FixedWing.LineAbreast=1 +ENUMS.FormationOld.FixedWing.Trail=2 +ENUMS.FormationOld.FixedWing.Wedge=3 +ENUMS.FormationOld.FixedWing.EchelonRight=4 +ENUMS.FormationOld.FixedWing.EchelonLeft=5 +ENUMS.FormationOld.FixedWing.FingerFour=6 +ENUMS.FormationOld.FixedWing.SpreadFour=7 +ENUMS.FormationOld.FixedWing.BomberElement=12 +ENUMS.FormationOld.FixedWing.BomberElementHeight=13 +ENUMS.FormationOld.FixedWing.FighterVic=14 +ENUMS.FormationOld.RotaryWing={} +ENUMS.FormationOld.RotaryWing.Wedge=8 +ENUMS.FormationOld.RotaryWing.Echelon=9 +ENUMS.FormationOld.RotaryWing.Front=10 +ENUMS.FormationOld.RotaryWing.Column=11 +ENUMS.Morse={} +ENUMS.Morse.A="* -" +ENUMS.Morse.B="- * * *" +ENUMS.Morse.C="- * - *" +ENUMS.Morse.D="- * *" +ENUMS.Morse.E="*" +ENUMS.Morse.F="* * - *" +ENUMS.Morse.G="- - *" +ENUMS.Morse.H="* * * *" +ENUMS.Morse.I="* *" +ENUMS.Morse.J="* - - -" +ENUMS.Morse.K="- * -" +ENUMS.Morse.L="* - * *" +ENUMS.Morse.M="- -" +ENUMS.Morse.N="- *" +ENUMS.Morse.O="- - -" +ENUMS.Morse.P="* - - *" +ENUMS.Morse.Q="- - * -" +ENUMS.Morse.R="* - *" +ENUMS.Morse.S="* * *" +ENUMS.Morse.T="-" +ENUMS.Morse.U="* * -" +ENUMS.Morse.V="* * * -" +ENUMS.Morse.W="* - -" +ENUMS.Morse.X="- * * -" +ENUMS.Morse.Y="- * - -" +ENUMS.Morse.Z="- - * *" +ENUMS.Morse.N1="* - - - -" +ENUMS.Morse.N2="* * - - -" +ENUMS.Morse.N3="* * * - -" +ENUMS.Morse.N4="* * * * -" +ENUMS.Morse.N5="* * * * *" +ENUMS.Morse.N6="- * * * *" +ENUMS.Morse.N7="- - * * *" +ENUMS.Morse.N8="- - - * *" +ENUMS.Morse.N9="- - - - *" +ENUMS.Morse.N0="- - - - -" +ENUMS.Morse[" "]=" " +ENUMS.ISOLang= +{ +Arabic='AR', +Chinese='ZH', +English='EN', +French='FR', +German='DE', +Russian='RU', +Spanish='ES', +Japanese='JA', +Italian='IT', +} +ENUMS.Phonetic= +{ +A='Alpha', +B='Bravo', +C='Charlie', +D='Delta', +E='Echo', +F='Foxtrot', +G='Golf', +H='Hotel', +I='India', +J='Juliett', +K='Kilo', +L='Lima', +M='Mike', +N='November', +O='Oscar', +P='Papa', +Q='Quebec', +R='Romeo', +S='Sierra', +T='Tango', +U='Uniform', +V='Victor', +W='Whiskey', +X='Xray', +Y='Yankee', +Z='Zulu', +} +ENUMS.ReportingName= +{ +NATO={ +Dragon="JF-17", +Fagot="MiG-15", +Farmer="MiG-19", +Felon="Su-57", +Fencer="Su-24", +Fishbed="MiG-21", +Fitter="Su-17", +Flogger="MiG-23", +Flogger_D="MiG-27", +Flagon="Su-15", +Foxbat="MiG-25", +Fulcrum="MiG-29", +Foxhound="MiG-31", +Flanker="Su-27", +Flanker_C="Su-30", +Flanker_E="Su-35", +Flanker_F="Su-37", +Flanker_L="J-11A", +Firebird="J-10", +Sea_Flanker="Su-33", +Fullback="Su-34", +Frogfoot="Su-25", +Tomcat="F-14", +Mirage="Mirage", +Codling="Yak-40", +Maya="L-39", +Warthog="A-10", +Skyhawk="A-4E", +Viggen="AJS37", +Harrier_B="AV8BNA", +Harrier="AV-8B", +Spirit="B-2", +Aviojet="C-101", +Nighthawk="F-117A", +Eagle="F-15C", +Mudhen="F-15E", +Viper="F-16", +Phantom="F-4E", +Tiger="F-5", +Sabre="F-86", +Hornet="A-18", +Hawk="Hawk", +Albatros="L-39", +Goshawk="T-45", +Starfighter="F-104", +Tornado="Tornado", +Atlas="A400", +Lancer="B1-B", +Stratofortress="B-52H", +Hercules="C-130", +Super_Hercules="Hercules", +Globemaster="C-17", +Greyhound="C-2A", +Galaxy="C-5", +Hawkeye="E-2D", +Sentry="E-3A", +Stratotanker="KC-135", +Gasstation="KC-135MPRS", +Extender="KC-10", +Orion="P-3C", +Viking="S-3B", +Osprey="V-22", +Badger="H6-J", +Bear_J="Tu-142", +Bear="Tu-95", +Blinder="Tu-22", +Blackjack="Tu-160", +Clank="An-30", +Curl="An-26", +Candid="IL-76", +Midas="IL-78", +Mainstay="A-50", +Mainring="KJ-2000", +Yak="Yak-52", +Helix="Ka-27", +Shark="Ka-50", +Hind="Mi-24", +Halo="Mi-26", +Hip="Mi-8", +Havoc="Mi-28", +Gazelle="SA342", +Huey="UH-1H", +Cobra="AH-1", +Apache="AH-64", +Chinook="CH-47", +Sea_Stallion="CH-53", +Kiowa="OH-58", +Seahawk="SH-60", +Blackhawk="UH-60", +Sea_King="S-61", +UCAV="WingLoong", +Reaper="MQ-9", +Predator="MQ-1A", +} +} +ENUMS.Link16Power={ +none=0, +low=1, +medium=2, +high=3, +} +ENUMS.Storage={ +weapons={ +missiles={}, +bombs={}, +nurs={}, +containers={}, +droptanks={}, +adapters={}, +torpedoes={}, +Gazelle={}, +CH47={}, +OH58={}, +UH1H={}, +AH64D={}, +UH60L={}, +} +} +ENUMS.Storage.weapons.nurs.SNEB_TYPE253_F1B="weapons.nurs.SNEB_TYPE253_F1B" +ENUMS.Storage.weapons.missiles.P_24T="weapons.missiles.P_24T" +ENUMS.Storage.weapons.bombs.BLU_3B_OLD="weapons.bombs.BLU-3B_OLD" +ENUMS.Storage.weapons.missiles.AGM_154="weapons.missiles.AGM_154" +ENUMS.Storage.weapons.nurs.HYDRA_70_M151_M433="weapons.nurs.HYDRA_70_M151_M433" +ENUMS.Storage.weapons.bombs.SAM_Avenger_M1097_Skid_7090lb="weapons.bombs.SAM Avenger M1097 Skid [7090lb]" +ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk5="weapons.bombs.British_GP_250LB_Bomb_Mk5" +ENUMS.Storage.weapons.containers.OV10_SMOKE="weapons.containers.{OV10_SMOKE}" +ENUMS.Storage.weapons.bombs.BLU_4B_OLD="weapons.bombs.BLU-4B_OLD" +ENUMS.Storage.weapons.bombs.FAB_500M54="weapons.bombs.FAB-500M54" +ENUMS.Storage.weapons.bombs.GBU_38="weapons.bombs.GBU_38" +ENUMS.Storage.weapons.containers.F_15E_AXQ_14_DATALINK="weapons.containers.F-15E_AXQ-14_DATALINK" +ENUMS.Storage.weapons.bombs.BEER_BOMB="weapons.bombs.BEER_BOMB" +ENUMS.Storage.weapons.bombs.P_50T="weapons.bombs.P-50T" +ENUMS.Storage.weapons.nurs.C_8CM_GN="weapons.nurs.C_8CM_GN" +ENUMS.Storage.weapons.bombs.FAB_500SL="weapons.bombs.FAB-500SL" +ENUMS.Storage.weapons.bombs.KAB_1500Kr="weapons.bombs.KAB_1500Kr" +ENUMS.Storage.weapons.bombs.two50_2="weapons.bombs.250-2" +ENUMS.Storage.weapons.droptanks.Spitfire_tank_1="weapons.droptanks.Spitfire_tank_1" +ENUMS.Storage.weapons.missiles.AGM_65G="weapons.missiles.AGM_65G" +ENUMS.Storage.weapons.missiles.AGM_65A="weapons.missiles.AGM_65A" +ENUMS.Storage.weapons.containers.Hercules_JATO="weapons.containers.Hercules_JATO" +ENUMS.Storage.weapons.nurs.HYDRA_70_M259="weapons.nurs.HYDRA_70_M259" +ENUMS.Storage.weapons.missiles.AGM_84E="weapons.missiles.AGM_84E" +ENUMS.Storage.weapons.bombs.AN_M30A1="weapons.bombs.AN_M30A1" +ENUMS.Storage.weapons.nurs.C_25="weapons.nurs.C_25" +ENUMS.Storage.weapons.containers.AV8BNA_ALQ164="weapons.containers.AV8BNA_ALQ164" +ENUMS.Storage.weapons.containers.lav_25="weapons.containers.lav-25" +ENUMS.Storage.weapons.missiles.P_60="weapons.missiles.P_60" +ENUMS.Storage.weapons.bombs.FAB_1500="weapons.bombs.FAB_1500" +ENUMS.Storage.weapons.droptanks.FuelTank_350L="weapons.droptanks.FuelTank_350L" +ENUMS.Storage.weapons.bombs.AAA_Vulcan_M163_Skid_21577lb="weapons.bombs.AAA Vulcan M163 Skid [21577lb]" +ENUMS.Storage.weapons.missiles.Kormoran="weapons.missiles.Kormoran" +ENUMS.Storage.weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY="weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY" +ENUMS.Storage.weapons.droptanks.FuelTank_150L="weapons.droptanks.FuelTank_150L" +ENUMS.Storage.weapons.missiles.Rb_15F_for_A_I="weapons.missiles.Rb 15F (for A.I.)" +ENUMS.Storage.weapons.missiles.RB75T="weapons.missiles.RB75T" +ENUMS.Storage.weapons.missiles.Vikhr_M="weapons.missiles.Vikhr_M" +ENUMS.Storage.weapons.nurs.FFAR_M156_WP="weapons.nurs.FFAR M156 WP" +ENUMS.Storage.weapons.nurs.British_HE_60LBSAPNo2_3INCHNo1="weapons.nurs.British_HE_60LBSAPNo2_3INCHNo1" +ENUMS.Storage.weapons.missiles.DWS39_MJ2="weapons.missiles.DWS39_MJ2" +ENUMS.Storage.weapons.bombs.HEBOMBD="weapons.bombs.HEBOMBD" +ENUMS.Storage.weapons.missiles.CATM_9M="weapons.missiles.CATM_9M" +ENUMS.Storage.weapons.bombs.Mk_81="weapons.bombs.Mk_81" +ENUMS.Storage.weapons.droptanks.Drop_Tank_300_Liter="weapons.droptanks.Drop_Tank_300_Liter" +ENUMS.Storage.weapons.containers.HMMWV_M1025="weapons.containers.HMMWV_M1025" +ENUMS.Storage.weapons.bombs.SAM_CHAPARRAL_Air_21624lb="weapons.bombs.SAM CHAPARRAL Air [21624lb]" +ENUMS.Storage.weapons.missiles.AGM_154A="weapons.missiles.AGM_154A" +ENUMS.Storage.weapons.bombs.Mk_84AIR_TP="weapons.bombs.Mk_84AIR_TP" +ENUMS.Storage.weapons.bombs.GBU_31_V_3B="weapons.bombs.GBU_31_V_3B" +ENUMS.Storage.weapons.nurs.C_8CM_WH="weapons.nurs.C_8CM_WH" +ENUMS.Storage.weapons.missiles.Matra_Super_530D="weapons.missiles.Matra Super 530D" +ENUMS.Storage.weapons.nurs.ARF8M3TPSM="weapons.nurs.ARF8M3TPSM" +ENUMS.Storage.weapons.missiles.TGM_65H="weapons.missiles.TGM_65H" +ENUMS.Storage.weapons.nurs.M8rocket="weapons.nurs.M8rocket" +ENUMS.Storage.weapons.bombs.GBU_27="weapons.bombs.GBU_27" +ENUMS.Storage.weapons.missiles.AGR_20A="weapons.missiles.AGR_20A" +ENUMS.Storage.weapons.missiles.LS_6_250="weapons.missiles.LS-6-250" +ENUMS.Storage.weapons.droptanks.M2KC_RPL_522_EMPTY="weapons.droptanks.M2KC_RPL_522_EMPTY" +ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541="weapons.droptanks.M2KC_02_RPL541" +ENUMS.Storage.weapons.missiles.AGM_45="weapons.missiles.AGM_45" +ENUMS.Storage.weapons.missiles.AGM_84A="weapons.missiles.AGM_84A" +ENUMS.Storage.weapons.bombs.APC_BTR_80_Air_23936lb="weapons.bombs.APC BTR-80 Air [23936lb]" +ENUMS.Storage.weapons.missiles.P_33E="weapons.missiles.P_33E" +ENUMS.Storage.weapons.missiles.Ataka_9M120="weapons.missiles.Ataka_9M120" +ENUMS.Storage.weapons.bombs.MK76="weapons.bombs.MK76" +ENUMS.Storage.weapons.bombs.AB_250_2_SD_2="weapons.bombs.AB_250_2_SD_2" +ENUMS.Storage.weapons.missiles.Rb_05A="weapons.missiles.Rb 05A" +ENUMS.Storage.weapons.bombs.ART_GVOZDIKA_34720lb="weapons.bombs.ART GVOZDIKA [34720lb]" +ENUMS.Storage.weapons.bombs.Generic_Crate_20000lb="weapons.bombs.Generic Crate [20000lb]" +ENUMS.Storage.weapons.bombs.FAB_100SV="weapons.bombs.FAB_100SV" +ENUMS.Storage.weapons.bombs.BetAB_500="weapons.bombs.BetAB_500" +ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541_EMPTY="weapons.droptanks.M2KC_02_RPL541_EMPTY" +ENUMS.Storage.weapons.droptanks.PTB600_MIG15="weapons.droptanks.PTB600_MIG15" +ENUMS.Storage.weapons.missiles.Rb_24J="weapons.missiles.Rb 24J" +ENUMS.Storage.weapons.nurs.C_8CM_BU="weapons.nurs.C_8CM_BU" +ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_F1B="weapons.nurs.SNEB_TYPE259E_F1B" +ENUMS.Storage.weapons.nurs.WGr21="weapons.nurs.WGr21" +ENUMS.Storage.weapons.bombs.SAMP250HD="weapons.bombs.SAMP250HD" +ENUMS.Storage.weapons.containers.alq_184long="weapons.containers.alq-184long" +ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_H1="weapons.nurs.SNEB_TYPE259E_H1" +ENUMS.Storage.weapons.bombs.British_SAP_250LB_Bomb_Mk5="weapons.bombs.British_SAP_250LB_Bomb_Mk5" +ENUMS.Storage.weapons.bombs.Transport_UAZ_469_Air_3747lb="weapons.bombs.Transport UAZ-469 Air [3747lb]" +ENUMS.Storage.weapons.bombs.Mk_83CT="weapons.bombs.Mk_83CT" +ENUMS.Storage.weapons.missiles.AIM_7P="weapons.missiles.AIM-7P" +ENUMS.Storage.weapons.missiles.AT_6="weapons.missiles.AT_6" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_GREEN="weapons.nurs.SNEB_TYPE254_H1_GREEN" +ENUMS.Storage.weapons.nurs.SNEB_TYPE250_F1B="weapons.nurs.SNEB_TYPE250_F1B" +ENUMS.Storage.weapons.containers.U22A="weapons.containers.U22A" +ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk1="weapons.bombs.British_GP_250LB_Bomb_Mk1" +ENUMS.Storage.weapons.bombs.CBU_105="weapons.bombs.CBU_105" +ENUMS.Storage.weapons.droptanks.FW_190_Fuel_Tank="weapons.droptanks.FW-190_Fuel-Tank" +ENUMS.Storage.weapons.missiles.X_58="weapons.missiles.X_58" +ENUMS.Storage.weapons.missiles.BK90_MJ1_MJ2="weapons.missiles.BK90_MJ1_MJ2" +ENUMS.Storage.weapons.missiles.TGM_65D="weapons.missiles.TGM_65D" +ENUMS.Storage.weapons.containers.BRD_4_250="weapons.containers.BRD-4-250" +ENUMS.Storage.weapons.missiles.P_73="weapons.missiles.P_73" +ENUMS.Storage.weapons.bombs.AN_M66="weapons.bombs.AN_M66" +ENUMS.Storage.weapons.bombs.APC_LAV_25_Air_22520lb="weapons.bombs.APC LAV-25 Air [22520lb]" +ENUMS.Storage.weapons.missiles.AIM_7MH="weapons.missiles.AIM-7MH" +ENUMS.Storage.weapons.containers.MB339_TravelPod="weapons.containers.MB339_TravelPod" +ENUMS.Storage.weapons.bombs.GBU_12="weapons.bombs.GBU_12" +ENUMS.Storage.weapons.bombs.SC_250_T3_J="weapons.bombs.SC_250_T3_J" +ENUMS.Storage.weapons.missiles.KD_20="weapons.missiles.KD-20" +ENUMS.Storage.weapons.missiles.AGM_86C="weapons.missiles.AGM_86C" +ENUMS.Storage.weapons.missiles.X_35="weapons.missiles.X_35" +ENUMS.Storage.weapons.bombs.MK106="weapons.bombs.MK106" +ENUMS.Storage.weapons.bombs.BETAB_500S="weapons.bombs.BETAB-500S" +ENUMS.Storage.weapons.nurs.C_5="weapons.nurs.C_5" +ENUMS.Storage.weapons.nurs.S_24B="weapons.nurs.S-24B" +ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk2="weapons.bombs.British_MC_500LB_Bomb_Mk2" +ENUMS.Storage.weapons.containers.ANAWW_13="weapons.containers.ANAWW_13" +ENUMS.Storage.weapons.droptanks.droptank_108_gal="weapons.droptanks.droptank_108_gal" +ENUMS.Storage.weapons.droptanks.DFT_300_GAL_A4E_LR="weapons.droptanks.DFT_300_GAL_A4E_LR" +ENUMS.Storage.weapons.bombs.CBU_87="weapons.bombs.CBU_87" +ENUMS.Storage.weapons.missiles.GAR_8="weapons.missiles.GAR-8" +ENUMS.Storage.weapons.bombs.BELOUGA="weapons.bombs.BELOUGA" +ENUMS.Storage.weapons.containers.EclairM_33="weapons.containers.{EclairM_33}" +ENUMS.Storage.weapons.bombs.ART_2S9_NONA_Air_19140lb="weapons.bombs.ART 2S9 NONA Air [19140lb]" +ENUMS.Storage.weapons.bombs.BR_250="weapons.bombs.BR_250" +ENUMS.Storage.weapons.bombs.IAB_500="weapons.bombs.IAB-500" +ENUMS.Storage.weapons.containers.AN_ASQ_228="weapons.containers.AN_ASQ_228" +ENUMS.Storage.weapons.missiles.P_27P="weapons.missiles.P_27P" +ENUMS.Storage.weapons.bombs.SD_250_Stg="weapons.bombs.SD_250_Stg" +ENUMS.Storage.weapons.missiles.R_530F_IR="weapons.missiles.R_530F_IR" +ENUMS.Storage.weapons.bombs.British_SAP_500LB_Bomb_Mk5="weapons.bombs.British_SAP_500LB_Bomb_Mk5" +ENUMS.Storage.weapons.bombs.FAB_250M54="weapons.bombs.FAB-250M54" +ENUMS.Storage.weapons.containers.M2KC_AAF="weapons.containers.{M2KC_AAF}" +ENUMS.Storage.weapons.missiles.CM_802AKG_AI="weapons.missiles.CM-802AKG_AI" +ENUMS.Storage.weapons.bombs.CBU_103="weapons.bombs.CBU_103" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_RED="weapons.containers.{US_M10_SMOKE_TANK_RED}" +ENUMS.Storage.weapons.missiles.X_29T="weapons.missiles.X_29T" +ENUMS.Storage.weapons.bombs.HEMTT_TFFT_34400lb="weapons.bombs.HEMTT TFFT [34400lb]" +ENUMS.Storage.weapons.missiles.C_701IR="weapons.missiles.C-701IR" +ENUMS.Storage.weapons.containers.fullCargoSeats="weapons.containers.fullCargoSeats" +ENUMS.Storage.weapons.bombs.GBU_15_V_31_B="weapons.bombs.GBU_15_V_31_B" +ENUMS.Storage.weapons.bombs.APC_M1043_HMMWV_Armament_Air_7023lb="weapons.bombs.APC M1043 HMMWV Armament Air [7023lb]" +ENUMS.Storage.weapons.missiles.PL_5EII="weapons.missiles.PL-5EII" +ENUMS.Storage.weapons.bombs.SC_250_T1_L2="weapons.bombs.SC_250_T1_L2" +ENUMS.Storage.weapons.torpedoes.mk46torp_name="weapons.torpedoes.mk46torp_name" +ENUMS.Storage.weapons.containers.F_15E_AAQ_33_XR_ATP_SE="weapons.containers.F-15E_AAQ-33_XR_ATP-SE" +ENUMS.Storage.weapons.missiles.AIM_7="weapons.missiles.AIM_7" +ENUMS.Storage.weapons.missiles.AGM_122="weapons.missiles.AGM_122" +ENUMS.Storage.weapons.bombs.HEBOMB="weapons.bombs.HEBOMB" +ENUMS.Storage.weapons.bombs.CBU_97="weapons.bombs.CBU_97" +ENUMS.Storage.weapons.bombs.MK_81SE="weapons.bombs.MK-81SE" +ENUMS.Storage.weapons.nurs.Zuni_127="weapons.nurs.Zuni_127" +ENUMS.Storage.weapons.containers.M2KC_AGF="weapons.containers.{M2KC_AGF}" +ENUMS.Storage.weapons.droptanks.Hercules_ExtFuelTank="weapons.droptanks.Hercules_ExtFuelTank" +ENUMS.Storage.weapons.containers.SMOKE_WHITE="weapons.containers.{SMOKE_WHITE}" +ENUMS.Storage.weapons.droptanks.droptank_150_gal="weapons.droptanks.droptank_150_gal" +ENUMS.Storage.weapons.nurs.HYDRA_70_WTU1B="weapons.nurs.HYDRA_70_WTU1B" +ENUMS.Storage.weapons.missiles.GB_6_SFW="weapons.missiles.GB-6-SFW" +ENUMS.Storage.weapons.missiles.KD_63="weapons.missiles.KD-63" +ENUMS.Storage.weapons.bombs.GBU_28="weapons.bombs.GBU_28" +ENUMS.Storage.weapons.nurs.C_8CM_YE="weapons.nurs.C_8CM_YE" +ENUMS.Storage.weapons.droptanks.HB_F14_EXT_DROPTANK="weapons.droptanks.HB_F14_EXT_DROPTANK" +ENUMS.Storage.weapons.missiles.Super_530F="weapons.missiles.Super_530F" +ENUMS.Storage.weapons.missiles.Ataka_9M220="weapons.missiles.Ataka_9M220" +ENUMS.Storage.weapons.bombs.BDU_33="weapons.bombs.BDU_33" +ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk4="weapons.bombs.British_GP_250LB_Bomb_Mk4" +ENUMS.Storage.weapons.missiles.TOW="weapons.missiles.TOW" +ENUMS.Storage.weapons.bombs.ATGM_M1045_HMMWV_TOW_Air_7183lb="weapons.bombs.ATGM M1045 HMMWV TOW Air [7183lb]" +ENUMS.Storage.weapons.missiles.X_25MR="weapons.missiles.X_25MR" +ENUMS.Storage.weapons.droptanks.fueltank230="weapons.droptanks.fueltank230" +ENUMS.Storage.weapons.droptanks.PTB_490C_MIG21="weapons.droptanks.PTB-490C-MIG21" +ENUMS.Storage.weapons.bombs.M1025_HMMWV_Air_6160lb="weapons.bombs.M1025 HMMWV Air [6160lb]" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_GREEN="weapons.nurs.SNEB_TYPE254_F1B_GREEN" +ENUMS.Storage.weapons.missiles.R_550="weapons.missiles.R_550" +ENUMS.Storage.weapons.bombs.KAB_1500LG="weapons.bombs.KAB_1500LG" +ENUMS.Storage.weapons.missiles.AGM_84D="weapons.missiles.AGM_84D" +ENUMS.Storage.weapons.missiles.YJ_83K="weapons.missiles.YJ-83K" +ENUMS.Storage.weapons.missiles.AIM_54C_Mk47="weapons.missiles.AIM_54C_Mk47" +ENUMS.Storage.weapons.missiles.BRM_1_90MM="weapons.missiles.BRM-1_90MM" +ENUMS.Storage.weapons.missiles.Ataka_9M120F="weapons.missiles.Ataka_9M120F" +ENUMS.Storage.weapons.droptanks.Eleven00L_Tank="weapons.droptanks.1100L Tank" +ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP_100" +ENUMS.Storage.weapons.adapters.lau_88="weapons.adapters.lau-88" +ENUMS.Storage.weapons.missiles.P_40T="weapons.missiles.P_40T" +ENUMS.Storage.weapons.missiles.GB_6="weapons.missiles.GB-6" +ENUMS.Storage.weapons.bombs.FAB_250M54TU="weapons.bombs.FAB-250M54TU" +ENUMS.Storage.weapons.missiles.DWS39_MJ1="weapons.missiles.DWS39_MJ1" +ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.bombs.FAB_250="weapons.bombs.FAB_250" +ENUMS.Storage.weapons.missiles.C_802AK="weapons.missiles.C_802AK" +ENUMS.Storage.weapons.bombs.SD_500_A="weapons.bombs.SD_500_A" +ENUMS.Storage.weapons.bombs.GBU_32_V_2B="weapons.bombs.GBU_32_V_2B" +ENUMS.Storage.weapons.containers.marder="weapons.containers.marder" +ENUMS.Storage.weapons.missiles.ADM_141B="weapons.missiles.ADM_141B" +ENUMS.Storage.weapons.bombs.ROCKEYE="weapons.bombs.ROCKEYE" +ENUMS.Storage.weapons.missiles.BK90_MJ1="weapons.missiles.BK90_MJ1" +ENUMS.Storage.weapons.containers.BTR_80="weapons.containers.BTR-80" +ENUMS.Storage.weapons.bombs.SAM_ROLAND_ADS_34720lb="weapons.bombs.SAM ROLAND ADS [34720lb]" +ENUMS.Storage.weapons.containers.wmd7="weapons.containers.wmd7" +ENUMS.Storage.weapons.missiles.C_701T="weapons.missiles.C-701T" +ENUMS.Storage.weapons.missiles.AIM_7E_2="weapons.missiles.AIM-7E-2" +ENUMS.Storage.weapons.nurs.HVAR="weapons.nurs.HVAR" +ENUMS.Storage.weapons.containers.HMMWV_M1043="weapons.containers.HMMWV_M1043" +ENUMS.Storage.weapons.droptanks.PTB_800_MIG21="weapons.droptanks.PTB-800-MIG21" +ENUMS.Storage.weapons.missiles.AGM_114="weapons.missiles.AGM_114" +ENUMS.Storage.weapons.bombs.APC_M1126_Stryker_ICV_29542lb="weapons.bombs.APC M1126 Stryker ICV [29542lb]" +ENUMS.Storage.weapons.bombs.APC_M113_Air_21624lb="weapons.bombs.APC M113 Air [21624lb]" +ENUMS.Storage.weapons.bombs.M_117="weapons.bombs.M_117" +ENUMS.Storage.weapons.missiles.AGM_65D="weapons.missiles.AGM_65D" +ENUMS.Storage.weapons.droptanks.MB339_TT320_L="weapons.droptanks.MB339_TT320_L" +ENUMS.Storage.weapons.missiles.AGM_86="weapons.missiles.AGM_86" +ENUMS.Storage.weapons.bombs.BDU_45LGB="weapons.bombs.BDU_45LGB" +ENUMS.Storage.weapons.missiles.AGM_65H="weapons.missiles.AGM_65H" +ENUMS.Storage.weapons.nurs.RS_82="weapons.nurs.RS-82" +ENUMS.Storage.weapons.nurs.SNEB_TYPE252_F1B="weapons.nurs.SNEB_TYPE252_F1B" +ENUMS.Storage.weapons.bombs.BL_755="weapons.bombs.BL_755" +ENUMS.Storage.weapons.containers.F_15E_AAQ_28_LITENING="weapons.containers.F-15E_AAQ-28_LITENING" +ENUMS.Storage.weapons.nurs.SNEB_TYPE256_F1B="weapons.nurs.SNEB_TYPE256_F1B" +ENUMS.Storage.weapons.missiles.AGM_84H="weapons.missiles.AGM_84H" +ENUMS.Storage.weapons.missiles.AIM_54="weapons.missiles.AIM_54" +ENUMS.Storage.weapons.missiles.X_31A="weapons.missiles.X_31A" +ENUMS.Storage.weapons.bombs.KAB_500Kr="weapons.bombs.KAB_500Kr" +ENUMS.Storage.weapons.containers.SPS_141_100="weapons.containers.SPS-141-100" +ENUMS.Storage.weapons.missiles.BK90_MJ2="weapons.missiles.BK90_MJ2" +ENUMS.Storage.weapons.missiles.Super_530D="weapons.missiles.Super_530D" +ENUMS.Storage.weapons.bombs.CBU_52B="weapons.bombs.CBU_52B" +ENUMS.Storage.weapons.droptanks.PTB_450="weapons.droptanks.PTB-450" +ENUMS.Storage.weapons.bombs.IFV_MCV_80_34720lb="weapons.bombs.IFV MCV-80 [34720lb]" +ENUMS.Storage.weapons.containers.Two_c9="weapons.containers.2-c9" +ENUMS.Storage.weapons.missiles.AIM_9JULI="weapons.missiles.AIM-9JULI" +ENUMS.Storage.weapons.droptanks.MB339_TT500_R="weapons.droptanks.MB339_TT500_R" +ENUMS.Storage.weapons.nurs.C_8CM="weapons.nurs.C_8CM" +ENUMS.Storage.weapons.containers.BARAX="weapons.containers.BARAX" +ENUMS.Storage.weapons.missiles.P_40R="weapons.missiles.P_40R" +ENUMS.Storage.weapons.missiles.YJ_12="weapons.missiles.YJ-12" +ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_YELLOW="weapons.nurs.SNEB_TYPE254_H1_YELLOW" +ENUMS.Storage.weapons.bombs.Durandal="weapons.bombs.Durandal" +ENUMS.Storage.weapons.droptanks.i16_eft="weapons.droptanks.i16_eft" +ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D_EMPTY="weapons.droptanks.AV8BNA_AERO1D_EMPTY" +ENUMS.Storage.weapons.containers.Hercules_Battle_Station_TGP="weapons.containers.Hercules_Battle_Station_TGP" +ENUMS.Storage.weapons.nurs.C_8CM_VT="weapons.nurs.C_8CM_VT" +ENUMS.Storage.weapons.missiles.PL_12="weapons.missiles.PL-12" +ENUMS.Storage.weapons.missiles.R_3R="weapons.missiles.R-3R" +ENUMS.Storage.weapons.bombs.GBU_54_V_1B="weapons.bombs.GBU_54_V_1B" +ENUMS.Storage.weapons.droptanks.MB339_TT320_R="weapons.droptanks.MB339_TT320_R" +ENUMS.Storage.weapons.bombs.RN_24="weapons.bombs.RN-24" +ENUMS.Storage.weapons.containers.Twoc6m="weapons.containers.2c6m" +ENUMS.Storage.weapons.bombs.ARV_BRDM_2_Air_12320lb="weapons.bombs.ARV BRDM-2 Air [12320lb]" +ENUMS.Storage.weapons.bombs.ARV_BRDM_2_Skid_12210lb="weapons.bombs.ARV BRDM-2 Skid [12210lb]" +ENUMS.Storage.weapons.nurs.SNEB_TYPE251_F1B="weapons.nurs.SNEB_TYPE251_F1B" +ENUMS.Storage.weapons.missiles.X_41="weapons.missiles.X_41" +ENUMS.Storage.weapons.containers.MIG21_SMOKE_WHITE="weapons.containers.{MIG21_SMOKE_WHITE}" +ENUMS.Storage.weapons.bombs.MK_82AIR="weapons.bombs.MK_82AIR" +ENUMS.Storage.weapons.missiles.R_530F_EM="weapons.missiles.R_530F_EM" +ENUMS.Storage.weapons.bombs.SAMP400LD="weapons.bombs.SAMP400LD" +ENUMS.Storage.weapons.bombs.FAB_50="weapons.bombs.FAB_50" +ENUMS.Storage.weapons.bombs.AB_250_2_SD_10A="weapons.bombs.AB_250_2_SD_10A" +ENUMS.Storage.weapons.missiles.ADM_141A="weapons.missiles.ADM_141A" +ENUMS.Storage.weapons.containers.KBpod="weapons.containers.KBpod" +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4="weapons.bombs.British_GP_500LB_Bomb_Mk4" +ENUMS.Storage.weapons.missiles.AGM_65E="weapons.missiles.AGM_65E" +ENUMS.Storage.weapons.containers.sa342_dipole_antenna="weapons.containers.sa342_dipole_antenna" +ENUMS.Storage.weapons.bombs.OFAB_100_Jupiter="weapons.bombs.OFAB-100 Jupiter" +ENUMS.Storage.weapons.nurs.SNEB_TYPE257_F1B="weapons.nurs.SNEB_TYPE257_F1B" +ENUMS.Storage.weapons.missiles.Rb_04E_for_A_I="weapons.missiles.Rb 04E (for A.I.)" +ENUMS.Storage.weapons.bombs.AN_M66A2="weapons.bombs.AN-M66A2" +ENUMS.Storage.weapons.missiles.P_27T="weapons.missiles.P_27T" +ENUMS.Storage.weapons.droptanks.LNS_VIG_XTANK="weapons.droptanks.LNS_VIG_XTANK" +ENUMS.Storage.weapons.missiles.R_55="weapons.missiles.R-55" +ENUMS.Storage.weapons.torpedoes.YU_6="weapons.torpedoes.YU-6" +ENUMS.Storage.weapons.bombs.British_MC_250LB_Bomb_Mk2="weapons.bombs.British_MC_250LB_Bomb_Mk2" +ENUMS.Storage.weapons.droptanks.PTB_120_F86F35="weapons.droptanks.PTB_120_F86F35" +ENUMS.Storage.weapons.missiles.PL_8B="weapons.missiles.PL-8B" +ENUMS.Storage.weapons.droptanks.F_15E_Drop_Tank_Empty="weapons.droptanks.F-15E_Drop_Tank_Empty" +ENUMS.Storage.weapons.nurs.British_HE_60LBFNo1_3INCHNo1="weapons.nurs.British_HE_60LBFNo1_3INCHNo1" +ENUMS.Storage.weapons.missiles.P_77="weapons.missiles.P_77" +ENUMS.Storage.weapons.torpedoes.LTF_5B="weapons.torpedoes.LTF_5B" +ENUMS.Storage.weapons.missiles.R_3S="weapons.missiles.R-3S" +ENUMS.Storage.weapons.nurs.SNEB_TYPE253_H1="weapons.nurs.SNEB_TYPE253_H1" +ENUMS.Storage.weapons.missiles.PL_8A="weapons.missiles.PL-8A" +ENUMS.Storage.weapons.bombs.APC_BTR_82A_Skid_24888lb="weapons.bombs.APC BTR-82A Skid [24888lb]" +ENUMS.Storage.weapons.containers.Sborka="weapons.containers.Sborka" +ENUMS.Storage.weapons.missiles.AGM_65L="weapons.missiles.AGM_65L" +ENUMS.Storage.weapons.missiles.X_28="weapons.missiles.X_28" +ENUMS.Storage.weapons.missiles.TGM_65G="weapons.missiles.TGM_65G" +ENUMS.Storage.weapons.nurs.SNEB_TYPE257_H1="weapons.nurs.SNEB_TYPE257_H1" +ENUMS.Storage.weapons.missiles.RB75B="weapons.missiles.RB75B" +ENUMS.Storage.weapons.missiles.X_25ML="weapons.missiles.X_25ML" +ENUMS.Storage.weapons.droptanks.FPU_8A="weapons.droptanks.FPU_8A" +ENUMS.Storage.weapons.bombs.BLG66="weapons.bombs.BLG66" +ENUMS.Storage.weapons.nurs.C_8CM_RD="weapons.nurs.C_8CM_RD" +ENUMS.Storage.weapons.containers.EclairM_06="weapons.containers.{EclairM_06}" +ENUMS.Storage.weapons.bombs.RBK_500AO="weapons.bombs.RBK_500AO" +ENUMS.Storage.weapons.missiles.AIM_9P="weapons.missiles.AIM-9P" +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4_Short="weapons.bombs.British_GP_500LB_Bomb_Mk4_Short" +ENUMS.Storage.weapons.containers.MB339_Vinten="weapons.containers.MB339_Vinten" +ENUMS.Storage.weapons.missiles.Rb_15F="weapons.missiles.Rb 15F" +ENUMS.Storage.weapons.nurs.ARAKM70BHE="weapons.nurs.ARAKM70BHE" +ENUMS.Storage.weapons.bombs.AAA_Vulcan_M163_Air_21666lb="weapons.bombs.AAA Vulcan M163 Air [21666lb]" +ENUMS.Storage.weapons.missiles.X_29L="weapons.missiles.X_29L" +ENUMS.Storage.weapons.containers.F14_LANTIRN_TP="weapons.containers.{F14-LANTIRN-TP}" +ENUMS.Storage.weapons.bombs.FAB_250_M62="weapons.bombs.FAB-250-M62" +ENUMS.Storage.weapons.missiles.AIM_120C="weapons.missiles.AIM_120C" +ENUMS.Storage.weapons.bombs.EWR_SBORKA_Air_21624lb="weapons.bombs.EWR SBORKA Air [21624lb]" +ENUMS.Storage.weapons.bombs.SAMP250LD="weapons.bombs.SAMP250LD" +ENUMS.Storage.weapons.droptanks.Spitfire_slipper_tank="weapons.droptanks.Spitfire_slipper_tank" +ENUMS.Storage.weapons.missiles.LS_6_500="weapons.missiles.LS-6-500" +ENUMS.Storage.weapons.bombs.GBU_31_V_4B="weapons.bombs.GBU_31_V_4B" +ENUMS.Storage.weapons.droptanks.PTB400_MIG15="weapons.droptanks.PTB400_MIG15" +ENUMS.Storage.weapons.containers.m_113="weapons.containers.m-113" +ENUMS.Storage.weapons.bombs.SPG_M1128_Stryker_MGS_33036lb="weapons.bombs.SPG M1128 Stryker MGS [33036lb]" +ENUMS.Storage.weapons.missiles.AIM_9L="weapons.missiles.AIM-9L" +ENUMS.Storage.weapons.missiles.AIM_9X="weapons.missiles.AIM_9X" +ENUMS.Storage.weapons.nurs.C_8="weapons.nurs.C_8" +ENUMS.Storage.weapons.bombs.SAM_CHAPARRAL_Skid_21516lb="weapons.bombs.SAM CHAPARRAL Skid [21516lb]" +ENUMS.Storage.weapons.missiles.P_27TE="weapons.missiles.P_27TE" +ENUMS.Storage.weapons.bombs.ODAB_500PM="weapons.bombs.ODAB-500PM" +ENUMS.Storage.weapons.bombs.MK77mod1_WPN="weapons.bombs.MK77mod1-WPN" +ENUMS.Storage.weapons.droptanks.PTB400_MIG19="weapons.droptanks.PTB400_MIG19" +ENUMS.Storage.weapons.torpedoes.Mark_46="weapons.torpedoes.Mark_46" +ENUMS.Storage.weapons.containers.rightSeat="weapons.containers.rightSeat" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_ORANGE="weapons.containers.{US_M10_SMOKE_TANK_ORANGE}" +ENUMS.Storage.weapons.bombs.SAB_100MN="weapons.bombs.SAB_100MN" +ENUMS.Storage.weapons.nurs.FFAR_Mk5_HEAT="weapons.nurs.FFAR Mk5 HEAT" +ENUMS.Storage.weapons.bombs.IFV_TPZ_FUCH_33440lb="weapons.bombs.IFV TPZ FUCH [33440lb]" +ENUMS.Storage.weapons.bombs.IFV_M2A2_Bradley_34720lb="weapons.bombs.IFV M2A2 Bradley [34720lb]" +ENUMS.Storage.weapons.bombs.MK77mod0_WPN="weapons.bombs.MK77mod0-WPN" +ENUMS.Storage.weapons.containers.ASO_2="weapons.containers.ASO-2" +ENUMS.Storage.weapons.bombs.Mk_84AIR_GP="weapons.bombs.Mk_84AIR_GP" +ENUMS.Storage.weapons.nurs.S_24A="weapons.nurs.S-24A" +ENUMS.Storage.weapons.bombs.RBK_250_275_AO_1SCH="weapons.bombs.RBK_250_275_AO_1SCH" +ENUMS.Storage.weapons.bombs.Transport_Tigr_Skid_15730lb="weapons.bombs.Transport Tigr Skid [15730lb]" +ENUMS.Storage.weapons.missiles.AIM_7F="weapons.missiles.AIM-7F" +ENUMS.Storage.weapons.bombs.CBU_99="weapons.bombs.CBU_99" +ENUMS.Storage.weapons.bombs.LUU_2B="weapons.bombs.LUU_2B" +ENUMS.Storage.weapons.bombs.FAB_500TA="weapons.bombs.FAB-500TA" +ENUMS.Storage.weapons.missiles.AGR_20_M282="weapons.missiles.AGR_20_M282" +ENUMS.Storage.weapons.droptanks.MB339_FT330="weapons.droptanks.MB339_FT330" +ENUMS.Storage.weapons.bombs.SAMP125LD="weapons.bombs.SAMP125LD" +ENUMS.Storage.weapons.missiles.X_25MP="weapons.missiles.X_25MP" +ENUMS.Storage.weapons.nurs.SNEB_TYPE252_H1="weapons.nurs.SNEB_TYPE252_H1" +ENUMS.Storage.weapons.missiles.AGM_65F="weapons.missiles.AGM_65F" +ENUMS.Storage.weapons.missiles.AIM_9P5="weapons.missiles.AIM-9P5" +ENUMS.Storage.weapons.bombs.Transport_Tigr_Air_15900lb="weapons.bombs.Transport Tigr Air [15900lb]" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_RED="weapons.nurs.SNEB_TYPE254_H1_RED" +ENUMS.Storage.weapons.nurs.FFAR_Mk1_HE="weapons.nurs.FFAR Mk1 HE" +ENUMS.Storage.weapons.nurs.SPRD_99="weapons.nurs.SPRD-99" +ENUMS.Storage.weapons.bombs.BIN_200="weapons.bombs.BIN_200" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU_4B_GROUP" +ENUMS.Storage.weapons.bombs.GBU_24="weapons.bombs.GBU_24" +ENUMS.Storage.weapons.missiles.Rb_04E="weapons.missiles.Rb 04E" +ENUMS.Storage.weapons.missiles.Rb_74="weapons.missiles.Rb 74" +ENUMS.Storage.weapons.containers.leftSeat="weapons.containers.leftSeat" +ENUMS.Storage.weapons.bombs.LS_6_100="weapons.bombs.LS-6-100" +ENUMS.Storage.weapons.bombs.Transport_URAL_375_14815lb="weapons.bombs.Transport URAL-375 [14815lb]" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_GREEN="weapons.containers.{US_M10_SMOKE_TANK_GREEN}" +ENUMS.Storage.weapons.missiles.X_22="weapons.missiles.X_22" +ENUMS.Storage.weapons.containers.FAS="weapons.containers.FAS" +ENUMS.Storage.weapons.nurs.S_25_O="weapons.nurs.S-25-O" +ENUMS.Storage.weapons.droptanks.para="weapons.droptanks.para" +ENUMS.Storage.weapons.droptanks.F_15E_Drop_Tank="weapons.droptanks.F-15E_Drop_Tank" +ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541_EMPTY="weapons.droptanks.M2KC_08_RPL541_EMPTY" +ENUMS.Storage.weapons.missiles.X_31P="weapons.missiles.X_31P" +ENUMS.Storage.weapons.bombs.RBK_500U="weapons.bombs.RBK_500U" +ENUMS.Storage.weapons.missiles.AIM_54A_Mk47="weapons.missiles.AIM_54A_Mk47" +ENUMS.Storage.weapons.droptanks.oiltank="weapons.droptanks.oiltank" +ENUMS.Storage.weapons.missiles.AGM_154B="weapons.missiles.AGM_154B" +ENUMS.Storage.weapons.containers.MB339_SMOKE_POD="weapons.containers.MB339_SMOKE-POD" +ENUMS.Storage.weapons.containers.ECM_POD_L_175V="weapons.containers.{ECM_POD_L_175V}" +ENUMS.Storage.weapons.droptanks.PTB_580G_F1="weapons.droptanks.PTB_580G_F1" +ENUMS.Storage.weapons.containers.EclairM_15="weapons.containers.{EclairM_15}" +ENUMS.Storage.weapons.containers.F_15E_AAQ_13_LANTIRN="weapons.containers.F-15E_AAQ-13_LANTIRN" +ENUMS.Storage.weapons.droptanks.Eight00L_Tank_Empty="weapons.droptanks.800L Tank Empty" +ENUMS.Storage.weapons.containers.One6c_hts_pod="weapons.containers.16c_hts_pod" +ENUMS.Storage.weapons.bombs.AN_M81="weapons.bombs.AN-M81" +ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_100gal="weapons.droptanks.Mosquito_Drop_Tank_100gal" +ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_50gal="weapons.droptanks.Mosquito_Drop_Tank_50gal" +ENUMS.Storage.weapons.droptanks.DFT_150_GAL_A4E="weapons.droptanks.DFT_150_GAL_A4E" +ENUMS.Storage.weapons.missiles.AIM_9="weapons.missiles.AIM_9" +ENUMS.Storage.weapons.bombs.IFV_BTR_D_Air_18040lb="weapons.bombs.IFV BTR-D Air [18040lb]" +ENUMS.Storage.weapons.containers.EclairM_42="weapons.containers.{EclairM_42}" +ENUMS.Storage.weapons.bombs.KAB_1500T="weapons.bombs.KAB_1500T" +ENUMS.Storage.weapons.droptanks.PTB_490_MIG21="weapons.droptanks.PTB-490-MIG21" +ENUMS.Storage.weapons.droptanks.PTB_200_F86F35="weapons.droptanks.PTB_200_F86F35" +ENUMS.Storage.weapons.droptanks.PTB760_MIG19="weapons.droptanks.PTB760_MIG19" +ENUMS.Storage.weapons.bombs.GBU_43_B_MOAB="weapons.bombs.GBU-43/B(MOAB)" +ENUMS.Storage.weapons.torpedoes.G7A_T1="weapons.torpedoes.G7A_T1" +ENUMS.Storage.weapons.bombs.IFV_BMD_1_Air_18040lb="weapons.bombs.IFV BMD-1 Air [18040lb]" +ENUMS.Storage.weapons.bombs.SAM_LINEBACKER_34720lb="weapons.bombs.SAM LINEBACKER [34720lb]" +ENUMS.Storage.weapons.containers.ais_pod_t50_r="weapons.containers.ais-pod-t50_r" +ENUMS.Storage.weapons.containers.CE2_SMOKE_WHITE="weapons.containers.{CE2_SMOKE_WHITE}" +ENUMS.Storage.weapons.droptanks.fuel_tank_230="weapons.droptanks.fuel_tank_230" +ENUMS.Storage.weapons.droptanks.M2KC_RPL_522="weapons.droptanks.M2KC_RPL_522" +ENUMS.Storage.weapons.missiles.AGM_130="weapons.missiles.AGM_130" +ENUMS.Storage.weapons.droptanks.Eight00L_Tank="weapons.droptanks.800L Tank" +ENUMS.Storage.weapons.bombs.IFV_BTR_D_Skid_17930lb="weapons.bombs.IFV BTR-D Skid [17930lb]" +ENUMS.Storage.weapons.containers.bmp_1="weapons.containers.bmp-1" +ENUMS.Storage.weapons.bombs.GBU_31="weapons.bombs.GBU_31" +ENUMS.Storage.weapons.containers.aaq_28LEFT_litening="weapons.containers.aaq-28LEFT litening" +ENUMS.Storage.weapons.missiles.Kh_66_Grom="weapons.missiles.Kh-66_Grom" +ENUMS.Storage.weapons.containers.MIG21_SMOKE_RED="weapons.containers.{MIG21_SMOKE_RED}" +ENUMS.Storage.weapons.containers.U22="weapons.containers.U22" +ENUMS.Storage.weapons.bombs.IFV_BMD_1_Skid_17930lb="weapons.bombs.IFV BMD-1 Skid [17930lb]" +ENUMS.Storage.weapons.droptanks.Bidon="weapons.droptanks.Bidon" +ENUMS.Storage.weapons.bombs.GBU_31_V_2B="weapons.bombs.GBU_31_V_2B" +ENUMS.Storage.weapons.bombs.Mk_82Y="weapons.bombs.Mk_82Y" +ENUMS.Storage.weapons.containers.pl5eii="weapons.containers.pl5eii" +ENUMS.Storage.weapons.bombs.RBK_500U_OAB_2_5RT="weapons.bombs.RBK_500U_OAB_2_5RT" +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk5="weapons.bombs.British_GP_500LB_Bomb_Mk5" +ENUMS.Storage.weapons.containers.Eclair="weapons.containers.{Eclair}" +ENUMS.Storage.weapons.nurs.S5MO_HEFRAG_FFAR="weapons.nurs.S5MO_HEFRAG_FFAR" +ENUMS.Storage.weapons.bombs.BETAB_500M="weapons.bombs.BETAB-500M" +ENUMS.Storage.weapons.bombs.Transport_M818_16000lb="weapons.bombs.Transport M818 [16000lb]" +ENUMS.Storage.weapons.bombs.British_MC_250LB_Bomb_Mk1="weapons.bombs.British_MC_250LB_Bomb_Mk1" +ENUMS.Storage.weapons.nurs.SNEB_TYPE251_H1="weapons.nurs.SNEB_TYPE251_H1" +ENUMS.Storage.weapons.bombs.TYPE_200A="weapons.bombs.TYPE-200A" +ENUMS.Storage.weapons.nurs.HYDRA_70_M151="weapons.nurs.HYDRA_70_M151" +ENUMS.Storage.weapons.bombs.IFV_BMP_3_32912lb="weapons.bombs.IFV BMP-3 [32912lb]" +ENUMS.Storage.weapons.bombs.APC_MTLB_Air_26400lb="weapons.bombs.APC MTLB Air [26400lb]" +ENUMS.Storage.weapons.nurs.HYDRA_70_M229="weapons.nurs.HYDRA_70_M229" +ENUMS.Storage.weapons.bombs.BDU_45="weapons.bombs.BDU_45" +ENUMS.Storage.weapons.bombs.OFAB_100_120TU="weapons.bombs.OFAB-100-120TU" +ENUMS.Storage.weapons.missiles.AIM_9J="weapons.missiles.AIM-9J" +ENUMS.Storage.weapons.nurs.ARF8M3API="weapons.nurs.ARF8M3API" +ENUMS.Storage.weapons.bombs.BetAB_500ShP="weapons.bombs.BetAB_500ShP" +ENUMS.Storage.weapons.nurs.C_8OFP2="weapons.nurs.C_8OFP2" +ENUMS.Storage.weapons.bombs.GBU_10="weapons.bombs.GBU_10" +ENUMS.Storage.weapons.bombs.APC_MTLB_Skid_26290lb="weapons.bombs.APC MTLB Skid [26290lb]" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_RED="weapons.nurs.SNEB_TYPE254_F1B_RED" +ENUMS.Storage.weapons.missiles.X_65="weapons.missiles.X_65" +ENUMS.Storage.weapons.missiles.R_550_M1="weapons.missiles.R_550_M1" +ENUMS.Storage.weapons.missiles.AGM_65K="weapons.missiles.AGM_65K" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_YELLOW="weapons.nurs.SNEB_TYPE254_F1B_YELLOW" +ENUMS.Storage.weapons.missiles.AGM_88="weapons.missiles.AGM_88" +ENUMS.Storage.weapons.nurs.C_8OM="weapons.nurs.C_8OM" +ENUMS.Storage.weapons.bombs.SAM_ROLAND_LN_34720b="weapons.bombs.SAM ROLAND LN [34720b]" +ENUMS.Storage.weapons.missiles.AIM_120="weapons.missiles.AIM_120" +ENUMS.Storage.weapons.missiles.HOT3_MBDA="weapons.missiles.HOT3_MBDA" +ENUMS.Storage.weapons.missiles.R_13M="weapons.missiles.R-13M" +ENUMS.Storage.weapons.missiles.AIM_54C_Mk60="weapons.missiles.AIM_54C_Mk60" +ENUMS.Storage.weapons.bombs.AAA_GEPARD_34720lb="weapons.bombs.AAA GEPARD [34720lb]" +ENUMS.Storage.weapons.missiles.R_13M1="weapons.missiles.R-13M1" +ENUMS.Storage.weapons.bombs.APC_Cobra_Air_10912lb="weapons.bombs.APC Cobra Air [10912lb]" +ENUMS.Storage.weapons.bombs.RBK_250="weapons.bombs.RBK_250" +ENUMS.Storage.weapons.bombs.SC_500_J="weapons.bombs.SC_500_J" +ENUMS.Storage.weapons.missiles.AGM_114K="weapons.missiles.AGM_114K" +ENUMS.Storage.weapons.missiles.ALARM="weapons.missiles.ALARM" +ENUMS.Storage.weapons.bombs.Mk_83="weapons.bombs.Mk_83" +ENUMS.Storage.weapons.missiles.AGM_65B="weapons.missiles.AGM_65B" +ENUMS.Storage.weapons.bombs.MK_82SNAKEYE="weapons.bombs.MK_82SNAKEYE" +ENUMS.Storage.weapons.nurs.HYDRA_70_MK1="weapons.nurs.HYDRA_70_MK1" +ENUMS.Storage.weapons.bombs.BLG66_BELOUGA="weapons.bombs.BLG66_BELOUGA" +ENUMS.Storage.weapons.containers.EclairM_51="weapons.containers.{EclairM_51}" +ENUMS.Storage.weapons.missiles.AIM_54A_Mk60="weapons.missiles.AIM_54A_Mk60" +ENUMS.Storage.weapons.droptanks.DFT_300_GAL_A4E="weapons.droptanks.DFT_300_GAL_A4E" +ENUMS.Storage.weapons.bombs.ATGM_M1134_Stryker_30337lb="weapons.bombs.ATGM M1134 Stryker [30337lb]" +ENUMS.Storage.weapons.bombs.BAT_120="weapons.bombs.BAT-120" +ENUMS.Storage.weapons.missiles.DWS39_MJ1_MJ2="weapons.missiles.DWS39_MJ1_MJ2" +ENUMS.Storage.weapons.containers.SPRD="weapons.containers.SPRD" +ENUMS.Storage.weapons.bombs.BR_500="weapons.bombs.BR_500" +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk1="weapons.bombs.British_GP_500LB_Bomb_Mk1" +ENUMS.Storage.weapons.bombs.BDU_50HD="weapons.bombs.BDU_50HD" +ENUMS.Storage.weapons.missiles.RS2US="weapons.missiles.RS2US" +ENUMS.Storage.weapons.bombs.IFV_BMP_2_25168lb="weapons.bombs.IFV BMP-2 [25168lb]" +ENUMS.Storage.weapons.bombs.SAMP400HD="weapons.bombs.SAMP400HD" +ENUMS.Storage.weapons.containers.Hercules_Battle_Station="weapons.containers.Hercules_Battle_Station" +ENUMS.Storage.weapons.bombs.AN_M64="weapons.bombs.AN_M64" +ENUMS.Storage.weapons.containers.rearCargoSeats="weapons.containers.rearCargoSeats" +ENUMS.Storage.weapons.bombs.Mk_82="weapons.bombs.Mk_82" +ENUMS.Storage.weapons.missiles.AKD_10="weapons.missiles.AKD-10" +ENUMS.Storage.weapons.bombs.BDU_50LGB="weapons.bombs.BDU_50LGB" +ENUMS.Storage.weapons.missiles.SD_10="weapons.missiles.SD-10" +ENUMS.Storage.weapons.containers.IRDeflector="weapons.containers.IRDeflector" +ENUMS.Storage.weapons.bombs.FAB_500="weapons.bombs.FAB_500" +ENUMS.Storage.weapons.bombs.KAB_500="weapons.bombs.KAB_500" +ENUMS.Storage.weapons.nurs.S_5M="weapons.nurs.S-5M" +ENUMS.Storage.weapons.missiles.MICA_R="weapons.missiles.MICA_R" +ENUMS.Storage.weapons.missiles.X_59M="weapons.missiles.X_59M" +ENUMS.Storage.weapons.nurs.UG_90MM="weapons.nurs.UG_90MM" +ENUMS.Storage.weapons.bombs.LYSBOMB="weapons.bombs.LYSBOMB" +ENUMS.Storage.weapons.nurs.R4M="weapons.nurs.R4M" +ENUMS.Storage.weapons.containers.dlpod_akg="weapons.containers.dlpod_akg" +ENUMS.Storage.weapons.missiles.LD_10="weapons.missiles.LD-10" +ENUMS.Storage.weapons.bombs.SC_50="weapons.bombs.SC_50" +ENUMS.Storage.weapons.nurs.HYDRA_70_MK5="weapons.nurs.HYDRA_70_MK5" +ENUMS.Storage.weapons.bombs.FAB_100M="weapons.bombs.FAB_100M" +ENUMS.Storage.weapons.missiles.Rb_24="weapons.missiles.Rb 24" +ENUMS.Storage.weapons.bombs.BDU_45B="weapons.bombs.BDU_45B" +ENUMS.Storage.weapons.missiles.GB_6_HE="weapons.missiles.GB-6-HE" +ENUMS.Storage.weapons.missiles.KD_63B="weapons.missiles.KD-63B" +ENUMS.Storage.weapons.missiles.P_27PE="weapons.missiles.P_27PE" +ENUMS.Storage.weapons.droptanks.PTB300_MIG15="weapons.droptanks.PTB300_MIG15" +ENUMS.Storage.weapons.bombs.Two50_3="weapons.bombs.250-3" +ENUMS.Storage.weapons.bombs.SC_500_L2="weapons.bombs.SC_500_L2" +ENUMS.Storage.weapons.containers.HMMWV_M1045="weapons.containers.HMMWV_M1045" +ENUMS.Storage.weapons.bombs.FAB_500M54TU="weapons.bombs.FAB-500M54TU" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_YELLOW="weapons.containers.{US_M10_SMOKE_TANK_YELLOW}" +ENUMS.Storage.weapons.containers.EclairM_60="weapons.containers.{EclairM_60}" +ENUMS.Storage.weapons.bombs.SAB_250_200="weapons.bombs.SAB_250_200" +ENUMS.Storage.weapons.bombs.FAB_100="weapons.bombs.FAB_100" +ENUMS.Storage.weapons.bombs.KAB_500S="weapons.bombs.KAB_500S" +ENUMS.Storage.weapons.missiles.AGM_45A="weapons.missiles.AGM_45A" +ENUMS.Storage.weapons.missiles.Kh25MP_PRGS1VP="weapons.missiles.Kh25MP_PRGS1VP" +ENUMS.Storage.weapons.nurs.S5M1_HEFRAG_FFAR="weapons.nurs.S5M1_HEFRAG_FFAR" +ENUMS.Storage.weapons.containers.kg600="weapons.containers.kg600" +ENUMS.Storage.weapons.bombs.AN_M65="weapons.bombs.AN_M65" +ENUMS.Storage.weapons.bombs.AN_M57="weapons.bombs.AN_M57" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP-100" +ENUMS.Storage.weapons.containers.HEMTT="weapons.containers.HEMTT" +ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk1_Short="weapons.bombs.British_MC_500LB_Bomb_Mk1_Short" +ENUMS.Storage.weapons.nurs.ARAKM70BAP="weapons.nurs.ARAKM70BAP" +ENUMS.Storage.weapons.missiles.AGM_119="weapons.missiles.AGM_119" +ENUMS.Storage.weapons.missiles.MMagicII="weapons.missiles.MMagicII" +ENUMS.Storage.weapons.bombs.AB_500_1_SD_10A="weapons.bombs.AB_500_1_SD_10A" +ENUMS.Storage.weapons.nurs.HYDRA_70_M282="weapons.nurs.HYDRA_70_M282" +ENUMS.Storage.weapons.droptanks.DFT_400_GAL_A4E="weapons.droptanks.DFT_400_GAL_A4E" +ENUMS.Storage.weapons.nurs.HYDRA_70_M257="weapons.nurs.HYDRA_70_M257" +ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D="weapons.droptanks.AV8BNA_AERO1D" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_BLUE="weapons.containers.{US_M10_SMOKE_TANK_BLUE}" +ENUMS.Storage.weapons.nurs.ARF8M3HEI="weapons.nurs.ARF8M3HEI" +ENUMS.Storage.weapons.bombs.RN_28="weapons.bombs.RN-28" +ENUMS.Storage.weapons.bombs.Squad_30_x_Soldier_7950lb="weapons.bombs.Squad 30 x Soldier [7950lb]" +ENUMS.Storage.weapons.containers.uaz_469="weapons.containers.uaz-469" +ENUMS.Storage.weapons.containers.Otokar_Cobra="weapons.containers.Otokar_Cobra" +ENUMS.Storage.weapons.bombs.APC_BTR_82A_Air_24998lb="weapons.bombs.APC BTR-82A Air [24998lb]" +ENUMS.Storage.weapons.nurs.HYDRA_70_M274="weapons.nurs.HYDRA_70_M274" +ENUMS.Storage.weapons.missiles.P_24R="weapons.missiles.P_24R" +ENUMS.Storage.weapons.nurs.HYDRA_70_MK61="weapons.nurs.HYDRA_70_MK61" +ENUMS.Storage.weapons.missiles.Igla_1E="weapons.missiles.Igla_1E" +ENUMS.Storage.weapons.missiles.C_802AK="weapons.missiles.C-802AK" +ENUMS.Storage.weapons.nurs.C_24="weapons.nurs.C_24" +ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541="weapons.droptanks.M2KC_08_RPL541" +ENUMS.Storage.weapons.nurs.C_13="weapons.nurs.C_13" +ENUMS.Storage.weapons.droptanks.droptank_110_gal="weapons.droptanks.droptank_110_gal" +ENUMS.Storage.weapons.bombs.Mk_84="weapons.bombs.Mk_84" +ENUMS.Storage.weapons.missiles.Sea_Eagle="weapons.missiles.Sea_Eagle" +ENUMS.Storage.weapons.droptanks.PTB_1200_F1="weapons.droptanks.PTB_1200_F1" +ENUMS.Storage.weapons.nurs.SNEB_TYPE256_H1="weapons.nurs.SNEB_TYPE256_H1" +ENUMS.Storage.weapons.containers.MATRA_PHIMAT="weapons.containers.MATRA-PHIMAT" +ENUMS.Storage.weapons.containers.smoke_pod="weapons.containers.smoke_pod" +ENUMS.Storage.weapons.containers.F_15E_AAQ_14_LANTIRN="weapons.containers.F-15E_AAQ-14_LANTIRN" +ENUMS.Storage.weapons.containers.EclairM_24="weapons.containers.{EclairM_24}" +ENUMS.Storage.weapons.bombs.GBU_16="weapons.bombs.GBU_16" +ENUMS.Storage.weapons.nurs.HYDRA_70_M156="weapons.nurs.HYDRA_70_M156" +ENUMS.Storage.weapons.missiles.R_60="weapons.missiles.R-60" +ENUMS.Storage.weapons.containers.zsu_23_4="weapons.containers.zsu-23-4" +ENUMS.Storage.weapons.missiles.RB75="weapons.missiles.RB75" +ENUMS.Storage.weapons.missiles.Mistral="weapons.missiles.Mistral" +ENUMS.Storage.weapons.droptanks.MB339_TT500_L="weapons.droptanks.MB339_TT500_L" +ENUMS.Storage.weapons.bombs.SAM_SA_13_STRELA_21624lb="weapons.bombs.SAM SA-13 STRELA [21624lb]" +ENUMS.Storage.weapons.bombs.SAM_Avenger_M1097_Air_7200lb="weapons.bombs.SAM Avenger M1097 Air [7200lb]" +ENUMS.Storage.weapons.droptanks.Eleven00L_Tank_Empty="weapons.droptanks.1100L Tank Empty" +ENUMS.Storage.weapons.bombs.AN_M88="weapons.bombs.AN-M88" +ENUMS.Storage.weapons.missiles.S_25L="weapons.missiles.S_25L" +ENUMS.Storage.weapons.nurs.British_AP_25LBNo1_3INCHNo1="weapons.nurs.British_AP_25LBNo1_3INCHNo1" +ENUMS.Storage.weapons.bombs.BDU_50LD="weapons.bombs.BDU_50LD" +ENUMS.Storage.weapons.bombs.AGM_62="weapons.bombs.AGM_62" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE="weapons.containers.{US_M10_SMOKE_TANK_WHITE}" +ENUMS.Storage.weapons.missiles.MICA_T="weapons.missiles.MICA_T" +ENUMS.Storage.weapons.containers.HVAR_rocket="weapons.containers.HVAR_rocket" +ENUMS.Storage.weapons.containers.LANTIRN="weapons.containers.LANTIRN" +ENUMS.Storage.weapons.missiles.AGM_78B="weapons.missiles.AGM_78B" +ENUMS.Storage.weapons.containers.uh_60l_pilot="weapons.containers.uh-60l_pilot" +ENUMS.Storage.weapons.missiles.AIM_92E="weapons.missiles.AIM-92E" +ENUMS.Storage.weapons.missiles.KD_63B="weapons.missiles.KD_63B" +ENUMS.Storage.weapons.bombs.Type_200A="weapons.bombs.Type_200A" +ENUMS.Storage.weapons.missiles.HB_AIM_7E_2="weapons.missiles.HB-AIM-7E-2" +ENUMS.Storage.weapons.containers.Spear="weapons.containers.Spear" +ENUMS.Storage.weapons.missiles.LS_6="weapons.missiles.LS_6" +ENUMS.Storage.weapons.containers.HB_ALE_40_0_120="weapons.containers.HB_ALE_40_0_120" +ENUMS.Storage.weapons.containers.Fantasm="weapons.containers.Fantasm" +ENUMS.Storage.weapons.nurs.FFAR_Mk61="weapons.nurs.FFAR_Mk61" +ENUMS.Storage.weapons.bombs.HB_F4E_GBU15V1="weapons.bombs.HB_F4E_GBU15V1" +ENUMS.Storage.weapons.containers.HB_F14_EXT_AN_APQ_167="weapons.containers.HB_F14_EXT_AN_APQ-167" +ENUMS.Storage.weapons.nurs.LWL_RP="weapons.nurs.LWL_RP" +ENUMS.Storage.weapons.bombs.AGM_62_I="weapons.bombs.AGM_62_I" +ENUMS.Storage.weapons.containers.ETHER="weapons.containers.ETHER" +ENUMS.Storage.weapons.containers.TANGAZH="weapons.containers.TANGAZH" +ENUMS.Storage.weapons.bombs.LYSBOMB_11086="weapons.bombs.LYSBOMB 11086" +ENUMS.Storage.weapons.containers.Stub_Wing="weapons.containers.Stub_Wing" +ENUMS.Storage.weapons.missiles.AIM_9E="weapons.missiles.AIM-9E" +ENUMS.Storage.weapons.missiles.C_701T="weapons.missiles.C_701T" +ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP_100" +ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.missiles.CM_400AKG="weapons.missiles.CM-400AKG" +ENUMS.Storage.weapons.missiles.C_802AK="weapons.missiles.C_802AK" +ENUMS.Storage.weapons.missiles.KD_63="weapons.missiles.KD_63" +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike_Fast="weapons.containers.HB_ORD_Pave_Spike_Fast" +ENUMS.Storage.weapons.missiles.SPIKE_ER2="weapons.missiles.SPIKE_ER2" +ENUMS.Storage.weapons.containers.KINGAL="weapons.containers.KINGAL" +ENUMS.Storage.weapons.containers.LANTIRN_F14_TARGET="weapons.containers.LANTIRN-F14-TARGET" +ENUMS.Storage.weapons.containers.SPS_141="weapons.containers.SPS-141" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU-3B_GROUP" +ENUMS.Storage.weapons.containers.HB_ALE_40_30_0="weapons.containers.HB_ALE_40_30_0" +ENUMS.Storage.weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL="weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL" +ENUMS.Storage.weapons.containers.ALQ_184="weapons.containers.ALQ-184" +ENUMS.Storage.weapons.missiles.AGM_45B="weapons.missiles.AGM_45B" +ENUMS.Storage.weapons.bombs.BLU_3_GROUP="weapons.bombs.BLU-3_GROUP" +ENUMS.Storage.weapons.missiles.SPIKE_ER="weapons.missiles.SPIKE_ER" +ENUMS.Storage.weapons.nurs.ARAKM70BAPPX="weapons.nurs.ARAKM70BAPPX" +ENUMS.Storage.weapons.bombs.LYSBOMB_11088="weapons.bombs.LYSBOMB 11088" +ENUMS.Storage.weapons.bombs.LYSBOMB_11087="weapons.bombs.LYSBOMB 11087" +ENUMS.Storage.weapons.missiles.KD_20="weapons.missiles.KD_20" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank="weapons.droptanks.HB_F-4E_EXT_WingTank" +ENUMS.Storage.weapons.missiles.Rb_04="weapons.missiles.Rb_04" +ENUMS.Storage.weapons.containers.AAQ_33="weapons.containers.AAQ-33" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank_EMPTY="weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank_EMPTY" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R_EMPTY="weapons.droptanks.HB_F-4E_EXT_WingTank_R_EMPTY" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_EMPTY="weapons.droptanks.HB_F-4E_EXT_WingTank_EMPTY" +ENUMS.Storage.weapons.containers.uh_60l_copilot="weapons.containers.uh-60l_copilot" +ENUMS.Storage.weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2="weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2" +ENUMS.Storage.weapons.containers.supply_m134="weapons.containers.supply_m134" +ENUMS.Storage.weapons.containers.Seahawk_Pylon="weapons.containers.Seahawk_Pylon" +ENUMS.Storage.weapons.nurs.LWL_MPP="weapons.nurs.LWL_MPP" +ENUMS.Storage.weapons.nurs.S_5KP="weapons.nurs.S_5KP" +ENUMS.Storage.weapons.missiles.AIM_92J="weapons.missiles.AIM-92J" +ENUMS.Storage.weapons.missiles.HB_AIM_7E="weapons.missiles.HB-AIM-7E" +ENUMS.Storage.weapons.containers.ALQ_131="weapons.containers.ALQ-131" +ENUMS.Storage.weapons.containers.HB_F14_EXT_TARPS="weapons.containers.HB_F14_EXT_TARPS" +ENUMS.Storage.weapons.containers.MH60_SOAR="weapons.containers.MH60_SOAR" +ENUMS.Storage.weapons.missiles.YJ_83="weapons.missiles.YJ-83" +ENUMS.Storage.weapons.bombs.GBU_8_B="weapons.bombs.GBU_8_B" +ENUMS.Storage.weapons.containers.HB_F14_EXT_ECA="weapons.containers.HB_F14_EXT_ECA" +ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP-100" +ENUMS.Storage.weapons.nurs.M261_MPSM_Rocket="weapons.nurs.M261_MPSM_Rocket" +ENUMS.Storage.weapons.droptanks.SEAHAWK_120_Fuel_Tank="weapons.droptanks.SEAHAWK_120_Fuel_Tank" +ENUMS.Storage.weapons.containers.SHPIL="weapons.containers.SHPIL" +ENUMS.Storage.weapons.bombs.GBU_39="weapons.bombs.GBU_39" +ENUMS.Storage.weapons.nurs.S_5M="weapons.nurs.S_5M" +ENUMS.Storage.weapons.containers.HB_ALE_40_15_90="weapons.containers.HB_ALE_40_15_90" +ENUMS.Storage.weapons.missiles.AIM_7E="weapons.missiles.AIM-7E" +ENUMS.Storage.weapons.missiles.AIM_9P3="weapons.missiles.AIM-9P3" +ENUMS.Storage.weapons.missiles.AGM_12B="weapons.missiles.AGM_12B" +ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank="weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank="weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank" +ENUMS.Storage.weapons.containers.PAVETACK="weapons.containers.PAVETACK" +ENUMS.Storage.weapons.missiles.LS_6_500="weapons.missiles.LS_6_500" +ENUMS.Storage.weapons.bombs.LYSBOMB_11089="weapons.bombs.LYSBOMB 11089" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU-4B_GROUP" +ENUMS.Storage.weapons.containers.ah_64d_radar="weapons.containers.ah-64d_radar" +ENUMS.Storage.weapons.containers.F_18_LDT_POD="weapons.containers.F-18-LDT-POD" +ENUMS.Storage.weapons.containers.HB_ALE_40_30_60="weapons.containers.HB_ALE_40_30_60" +ENUMS.Storage.weapons.bombs.LS_6_100="weapons.bombs.LS_6_100" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R="weapons.droptanks.HB_F-4E_EXT_WingTank_R" +ENUMS.Storage.weapons.containers.SORBCIJA_R="weapons.containers.SORBCIJA_R" +ENUMS.Storage.weapons.missiles.CATM_65K="weapons.missiles.CATM_65K" +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike="weapons.containers.HB_ORD_Pave_Spike" +ENUMS.Storage.weapons.containers.RobbieTank1="weapons.containers.RobbieTank1" +ENUMS.Storage.weapons.containers.SKY_SHADOW="weapons.containers.SKY_SHADOW" +ENUMS.Storage.weapons.containers.SORBCIJA_L="weapons.containers.SORBCIJA_L" +ENUMS.Storage.weapons.containers.Pavehawk="weapons.containers.Pavehawk" +ENUMS.Storage.weapons.bombs.BLG66_EG="weapons.bombs.BLG66_EG" +ENUMS.Storage.weapons.missiles.AGM_12C_ED="weapons.missiles.AGM_12C_ED" +ENUMS.Storage.weapons.missiles.AIM_92C="weapons.missiles.AIM-92C" +ENUMS.Storage.weapons.containers.MPS_410="weapons.containers.MPS-410" +ENUMS.Storage.weapons.missiles.HJ_12="weapons.missiles.HJ-12" +ENUMS.Storage.weapons.containers.AAQ_28_LITENING="weapons.containers.AAQ-28_LITENING" +ENUMS.Storage.weapons.containers.F_18_FLIR_POD="weapons.containers.F-18-FLIR-POD" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.containers.UH60L_Jayhawk="weapons.containers.UH60L_Jayhawk" +ENUMS.Storage.weapons.containers.BOZ_100="weapons.containers.BOZ-100" +ENUMS.Storage.weapons.missiles.AGM_78A="weapons.missiles.AGM_78A" +ENUMS.Storage.weapons.missiles.LAU_61_APKWS_M282="weapons.missiles.LAU_61_APKWS_M282" +ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP-100" +ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU-4B_GROUP" +ENUMS.Storage.weapons.nurs.S_5M="weapons.nurs.S_5M" +ENUMS.Storage.weapons.missiles.AGM_12A="weapons.missiles.AGM_12A" +ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Tank="weapons.droptanks.JAYHAWK_120_Fuel_Tank" +ENUMS.Storage.weapons.bombs.GBU_15_V_1_B="weapons.bombs.GBU_15_V_1_B" +ENUMS.Storage.weapons.missiles.HYDRA_70_M151_APKWS={4,4,8,292} +ENUMS.Storage.weapons.missiles.HYDRA_70_M282_APKWS={4,4,8,293} +ENUMS.Storage.weapons.bombs.BAP100="weapons.bombs.BAP_100" +ENUMS.Storage.weapons.bombs.BLU3B_GROUP="weapons.bombs.BLU-3B_GROUP" +ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU_4B_GROUP" +ENUMS.Storage.weapons.nurs.S5M="weapons.nurs.S-5M" +ENUMS.Storage.weapons.Gazelle.HMP400_100RDS={4,15,46,1771} +ENUMS.Storage.weapons.Gazelle.HMP400_200RDS={4,15,46,1770} +ENUMS.Storage.weapons.Gazelle.HMP400_400RDS={4,15,46,1769} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_AP={4,15,46,1768} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_SAPHEI={4,15,46,1767} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_HE={4,15,46,1766} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_HEAP={4,15,46,1765} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_APHE={4,15,46,1764} +ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR={4,15,47,680} +ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER={4,15,47,679} +ENUMS.Storage.weapons.CH47.CH47_PORT_M60D={4,15,46,2489} +ENUMS.Storage.weapons.CH47.CH47_STBD_M60D={4,15,46,2488} +ENUMS.Storage.weapons.CH47.CH47_AFT_M60D={4,15,46,2490} +ENUMS.Storage.weapons.CH47.CH47_PORT_M134D={4,15,46,2494} +ENUMS.Storage.weapons.CH47.CH47_STBD_M134D={4,15,46,2495} +ENUMS.Storage.weapons.CH47.CH47_AFT_M3M={4,15,46,2496} +ENUMS.Storage.weapons.CH47.CH47_PORT_M240H={4,15,46,2492} +ENUMS.Storage.weapons.CH47.CH47_STBD_M240H={4,15,46,2491} +ENUMS.Storage.weapons.CH47.CH47_AFT_M240H={4,15,46,2493} +ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right={4,15,46,161} +ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left={4,15,46,160} +ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right_Door={4,15,46,175} +ENUMS.Storage.weapons.UH1H.M60_MG_Right_Door={4,15,46,177} +ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left_Door={4,15,46,174} +ENUMS.Storage.weapons.UH1H.M60_MG_Left_Door={4,15,46,176} +ENUMS.Storage.weapons.UH60L.M151_HYDRA={4,7,33,147} +ENUMS.Storage.weapons.UH60L.M156_HYDRA={4,7,33,148} +ENUMS.Storage.weapons.UH60L.M229_HYDRA={4,7,33,148} +ENUMS.Storage.weapons.UH60L.M257_HYDRA={4,7,33,151} +ENUMS.Storage.weapons.UH60L.M259_HYDRA={4,7,33,151} +ENUMS.Storage.weapons.UH60L.M274_HYDRA={4,7,33,150} +ENUMS.Storage.weapons.UH60L.M134_DOOR_GUN={4,15,46,3031} +ENUMS.Storage.weapons.UH60L.M3M={4,15,46,2496} +ENUMS.Storage.weapons.UH60L.M3M_DOOR_GUN={4,15,46,3032} +ENUMS.Storage.weapons.UH60L.M60_DOOR_GUN={4,15,46,3033} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_200={1,3,43,3023} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_230={1,3,43,3024} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_450={1,3,43,3025} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_DUAL_AUX={1,3,43,3026} +ENUMS.Storage.weapons.UH60L.CARGO_SEAT_REAR_ROW={1,3,43,3030} +ENUMS.Storage.weapons.UH60L.CARGO_SEAT_THREE_ROWS={1,3,43,3029} +ENUMS.Storage.weapons.UH60L.EMPTY_GUNNER_SEAT_1={1,3,43,3027} +ENUMS.Storage.weapons.UH60L.EMPTY_GUNNER_SEAT_2={1,3,43,3028} +ENUMS.Storage.weapons.OH58.FIM92={4,4,7,449} +ENUMS.Storage.weapons.OH58.MG_M3P100={4,15,46,2611} +ENUMS.Storage.weapons.OH58.MG_M3P200={4,15,46,2610} +ENUMS.Storage.weapons.OH58.MG_M3P300={4,15,46,2609} +ENUMS.Storage.weapons.OH58.MG_M3P400={4,15,46,2608} +ENUMS.Storage.weapons.OH58.MG_M3P500={4,15,46,2607} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Blue={4,5,9,488} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Green={4,5,9,489} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Red={4,5,9,487} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Violet={4,5,9,490} +ENUMS.Storage.weapons.OH58.Smk_Grenade_White={4,5,9,492} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Yellow={4,5,9,491} +ENUMS.Storage.weapons.AH64D.AN_APG78={4,15,44,2114} +ENUMS.Storage.weapons.AH64D.Internal_Aux_FuelTank={1,3,43,1700} +ENUMS.Storage.weapons.droptanks.FuelTank_610gal={1,3,43,10} +ENUMS.Storage.weapons.droptanks.FuelTank_370gal={1,3,43,11} +ENUMS.Storage.weapons.containers.AV8BNA_GAU_12_AP_M79={4,15,46,824} +ENUMS.Storage.weapons.containers.AV8BNA_GAU_12_HE_M792={4,15,46,825} +ENUMS.Storage.weapons.containers.AV8BNA_GAU_12_SAPHEI_T={4,15,46,300} +ENUMS.FARPType={ +FARP="FARP", +INVISIBLE="INVISIBLE", +HELIPADSINGLE="HELIPADSINGLE", +PADSINGLE="PADSINGLE", +} +ENUMS.FARPObjectTypeNamesAndShape={ +[ENUMS.FARPType.FARP]={TypeName="FARP",ShapeName="FARPS"}, +[ENUMS.FARPType.INVISIBLE]={TypeName="Invisible FARP",ShapeName="invisiblefarp"}, +[ENUMS.FARPType.HELIPADSINGLE]={TypeName="SINGLE_HELIPAD",ShapeName="FARP"}, +[ENUMS.FARPType.PADSINGLE]={TypeName="FARP_SINGLE_01",ShapeName="FARP_SINGLE_01"}, +} +SMOKECOLOR=trigger.smokeColor +FLARECOLOR=trigger.flareColor +BIGSMOKEPRESET={ +SmallSmokeAndFire=1, +MediumSmokeAndFire=2, +LargeSmokeAndFire=3, +HugeSmokeAndFire=4, +SmallSmoke=5, +MediumSmoke=6, +LargeSmoke=7, +HugeSmoke=8, +} +DCSMAP={ +Caucasus="Caucasus", +NTTR="Nevada", +Normandy="Normandy", +PersianGulf="PersianGulf", +TheChannel="TheChannel", +Syria="Syria", +MarianaIslands="MarianaIslands", +Falklands="Falklands", +Sinai="SinaiMap", +Kola="Kola", +Afghanistan="Afghanistan", +Iraq="Iraq", +GermanyCW="GermanyCW", +} +CALLSIGN={ +Aircraft={ +Enfield=1, +Springfield=2, +Uzi=3, +Colt=4, +Dodge=5, +Ford=6, +Chevy=7, +Pontiac=8, +Hawg=9, +Boar=10, +Pig=11, +Tusk=12, +}, +AWACS={ +Overlord=1, +Magic=2, +Wizard=3, +Focus=4, +Darkstar=5, +}, +Tanker={ +Texaco=1, +Arco=2, +Shell=3, +Navy_One=4, +Mauler=5, +Bloodhound=6, +}, +JTAC={ +Axeman=1, +Darknight=2, +Warrior=3, +Pointer=4, +Eyeball=5, +Moonbeam=6, +Whiplash=7, +Finger=8, +Pinpoint=9, +Ferret=10, +Shaba=11, +Playboy=12, +Hammer=13, +Jaguar=14, +Deathstar=15, +Anvil=16, +Firefly=17, +Mantis=18, +Badger=19, +}, +FARP={ +London=1, +Dallas=2, +Paris=3, +Moscow=4, +Berlin=5, +Rome=6, +Madrid=7, +Warsaw=8, +Dublin=9, +Perth=10, +}, +F16={ +Viper=9, +Venom=10, +Lobo=11, +Cowboy=12, +Python=13, +Rattler=14, +Panther=15, +Wolf=16, +Weasel=17, +Wild=18, +Ninja=19, +Jedi=20, +}, +F18={ +Hornet=9, +Squid=10, +Ragin=11, +Roman=12, +Sting=13, +Jury=14, +Jokey=15, +Ram=16, +Hawk=17, +Devil=18, +Check=19, +Snake=20, +}, +F15E={ +Dude=9, +Thud=10, +Gunny=11, +Trek=12, +Sniper=13, +Sled=14, +Best=15, +Jazz=16, +Rage=17, +Tahoe=18, +}, +B1B={ +Bone=9, +Dark=10, +Vader=11 +}, +B52={ +Buff=9, +Dump=10, +Kenworth=11, +}, +TransportAircraft={ +Heavy=9, +Trash=10, +Cargo=11, +Ascot=12, +}, +AH64={ +Army_Air=9, +Apache=10, +Crow=11, +Sioux=12, +Gatling=13, +Gunslinger=14, +Hammerhead=15, +Bootleg=16, +Palehorse=17, +Carnivor=18, +Saber=19, +}, +Kiowa={ +Anvil=1, +Azrael=2, +BamBam=3, +Blackjack=4, +Bootleg=5, +BurninStogie=6, +Chaos=7, +CrazyHorse=8, +Crusader=9, +Darkhorse=10, +Eagle=11, +Lighthorse=12, +Mustang=13, +Outcast=14, +Palehorse=15, +Pegasus=16, +Pistol=17, +Roughneck=18, +Saber=19, +Shamus=20, +Spur=21, +Stetson=22, +Wrath=23, +}, +} +UTILS={ +_MarkID=1 +} +UTILS.IsInstanceOf=function(object,className) +if type(className)~='string'then +if type(className)=='table'and className.IsInstanceOf~=nil then +className=className.ClassName +else +local err_str='className parameter should be a string; parameter received: '..type(className) +return false +end +end +if type(object)=='table'and object.IsInstanceOf~=nil then +return object:IsInstanceOf(className) +else +local basicDataTypes={'string','number','function','boolean','nil','table'} +for _,basicDataType in ipairs(basicDataTypes)do +if className==basicDataType then +return type(object)==basicDataType +end +end +end +return false +end +UTILS.DeepCopy=function(object) +local lookup_table={} +local function _copy(object) +if type(object)~="table"then +return object +elseif lookup_table[object]then +return lookup_table[object] +end +local new_table={} +lookup_table[object]=new_table +for index,value in pairs(object)do +new_table[_copy(index)]=_copy(value) +end +return setmetatable(new_table,getmetatable(object)) +end +local objectreturn=_copy(object) +return objectreturn +end +UTILS.OneLineSerialize=function(tbl) +local lookup_table={} +local function _Serialize(tbl) +if type(tbl)=='table'then +if lookup_table[tbl]then +return lookup_table[object] +end +local tbl_str={} +lookup_table[tbl]=tbl_str +tbl_str[#tbl_str+1]='{' +for ind,val in pairs(tbl)do +local ind_str={} +if type(ind)=="number"then +ind_str[#ind_str+1]='[' +ind_str[#ind_str+1]=tostring(ind) +ind_str[#ind_str+1]=']=' +else +ind_str[#ind_str+1]='[' +ind_str[#ind_str+1]=UTILS.BasicSerialize(ind) +ind_str[#ind_str+1]=']=' +end +local val_str={} +if((type(val)=='number')or(type(val)=='boolean'))then +val_str[#val_str+1]=tostring(val) +val_str[#val_str+1]=',' +tbl_str[#tbl_str+1]=table.concat(ind_str) +tbl_str[#tbl_str+1]=table.concat(val_str) +elseif type(val)=='string'then +val_str[#val_str+1]=UTILS.BasicSerialize(val) +val_str[#val_str+1]=',' +tbl_str[#tbl_str+1]=table.concat(ind_str) +tbl_str[#tbl_str+1]=table.concat(val_str) +elseif type(val)=='nil'then +val_str[#val_str+1]='nil,' +tbl_str[#tbl_str+1]=table.concat(ind_str) +tbl_str[#tbl_str+1]=table.concat(val_str) +elseif type(val)=='table'then +if ind=="__index"then +else +val_str[#val_str+1]=_Serialize(val) +val_str[#val_str+1]=',' +tbl_str[#tbl_str+1]=table.concat(ind_str) +tbl_str[#tbl_str+1]=table.concat(val_str) +end +elseif type(val)=='function'then +tbl_str[#tbl_str+1]="f() "..tostring(ind) +tbl_str[#tbl_str+1]=',' +else +env.info('unable to serialize value type '..UTILS.BasicSerialize(type(val))..' at index '..tostring(ind)) +env.info(debug.traceback()) +end +end +tbl_str[#tbl_str+1]='}' +return table.concat(tbl_str) +else +return tostring(tbl) +end +end +local objectreturn=_Serialize(tbl) +return objectreturn +end +function UTILS._OneLineSerialize(tbl) +if type(tbl)=='table'then +local tbl_str={} +tbl_str[#tbl_str+1]='{ ' +for ind,val in pairs(tbl)do +if type(ind)=="number"then +tbl_str[#tbl_str+1]='[' +tbl_str[#tbl_str+1]=tostring(ind) +tbl_str[#tbl_str+1]='] = ' +else +tbl_str[#tbl_str+1]='[' +tbl_str[#tbl_str+1]=UTILS.BasicSerialize(ind) +tbl_str[#tbl_str+1]='] = ' +end +if((type(val)=='number')or(type(val)=='boolean'))then +tbl_str[#tbl_str+1]=tostring(val) +tbl_str[#tbl_str+1]=', ' +elseif type(val)=='string'then +tbl_str[#tbl_str+1]=UTILS.BasicSerialize(val) +tbl_str[#tbl_str+1]=', ' +elseif type(val)=='nil'then +tbl_str[#tbl_str+1]='nil, ' +elseif type(val)=='table'then +else +end +end +tbl_str[#tbl_str+1]='}' +return table.concat(tbl_str) +else +return UTILS.BasicSerialize(tbl) +end +end +UTILS.BasicSerialize=function(s) +if s==nil then +return"\"\"" +else +if((type(s)=='number')or(type(s)=='boolean')or(type(s)=='function')or(type(s)=='userdata'))then +return tostring(s) +elseif type(s)=="table"then +return UTILS._OneLineSerialize(s) +elseif type(s)=='string'then +s=string.format('(%s)',s) +return s +end +end +end +function UTILS.TableLength(T) +local count=0 +for _ in pairs(T or{})do count=count+1 end +return count +end +function UTILS.PrintTableToLog(table,indent,noprint) +local text="\n" +if not table or type(table)~="table"then +env.warning("No table passed!") +return nil +end +if not indent then indent=0 end +for k,v in pairs(table)do +if string.find(k," ")then k='"'..k..'"'end +if type(v)=="table"and UTILS.TableLength(v)>0 then +if not noprint then +env.info(string.rep(" ",indent)..tostring(k).." = {") +end +text=text..string.rep(" ",indent)..tostring(k).." = {\n" +text=text..tostring(UTILS.PrintTableToLog(v,indent+1),noprint).."\n" +if not noprint then +env.info(string.rep(" ",indent).."},") +end +text=text..string.rep(" ",indent).."},\n" +elseif type(v)=="function"then +else +local value +if tostring(v)=="true"or tostring(v)=="false"or tonumber(v)~=nil then +value=v +else +value='"'..tostring(v)..'"' +end +if not noprint then +env.info(string.rep(" ",indent)..tostring(k).." = "..tostring(value)..",\n") +end +text=text..string.rep(" ",indent)..tostring(k).." = "..tostring(value)..",\n" +end +end +return text +end +function UTILS.TableShow(tbl,loc,indent,tableshow_tbls) +tableshow_tbls=tableshow_tbls or{} +loc=loc or"" +indent=indent or"" +if type(tbl)=='table'then +tableshow_tbls[tbl]=loc +local tbl_str={} +tbl_str[#tbl_str+1]=indent..'{\n' +for ind,val in pairs(tbl)do +if type(ind)=="number"then +tbl_str[#tbl_str+1]=indent +tbl_str[#tbl_str+1]=loc..'[' +tbl_str[#tbl_str+1]=tostring(ind) +tbl_str[#tbl_str+1]='] = ' +else +tbl_str[#tbl_str+1]=indent +tbl_str[#tbl_str+1]=loc..'[' +tbl_str[#tbl_str+1]=UTILS.BasicSerialize(ind) +tbl_str[#tbl_str+1]='] = ' +end +if((type(val)=='number')or(type(val)=='boolean'))then +tbl_str[#tbl_str+1]=tostring(val) +tbl_str[#tbl_str+1]=',\n' +elseif type(val)=='string'then +tbl_str[#tbl_str+1]=UTILS.BasicSerialize(val) +tbl_str[#tbl_str+1]=',\n' +elseif type(val)=='nil'then +tbl_str[#tbl_str+1]='nil,\n' +elseif type(val)=='table'then +if tableshow_tbls[val]then +tbl_str[#tbl_str+1]=tostring(val)..' already defined: '..tableshow_tbls[val]..',\n' +else +tableshow_tbls[val]=loc..'['..UTILS.BasicSerialize(ind)..']' +tbl_str[#tbl_str+1]=tostring(val)..' ' +tbl_str[#tbl_str+1]=UTILS.TableShow(val,loc..'['..UTILS.BasicSerialize(ind)..']',indent..' ',tableshow_tbls) +tbl_str[#tbl_str+1]=',\n' +end +elseif type(val)=='function'then +if debug and debug.getinfo then +local fcnname=tostring(val) +local info=debug.getinfo(val,"S") +if info.what=="C"then +tbl_str[#tbl_str+1]=string.format('%q',fcnname..', C function')..',\n' +else +if(string.sub(info.source,1,2)==[[./]])then +tbl_str[#tbl_str+1]=string.format('%q',fcnname..', defined in ('..info.linedefined..'-'..info.lastlinedefined..')'..info.source)..',\n' +else +tbl_str[#tbl_str+1]=string.format('%q',fcnname..', defined in ('..info.linedefined..'-'..info.lastlinedefined..')')..',\n' +end +end +else +tbl_str[#tbl_str+1]='a function,\n' +end +else +tbl_str[#tbl_str+1]='unable to serialize value type '..UTILS.BasicSerialize(type(val))..' at index '..tostring(ind) +end +end +tbl_str[#tbl_str+1]=indent..'}' +return table.concat(tbl_str) +end +end +function UTILS.Gdump(fname) +if lfs and io then +local fdir=lfs.writedir()..[[Logs\]]..fname +local f=io.open(fdir,'w') +f:write(UTILS.TableShow(_G)) +f:close() +env.info(string.format('Wrote debug data to $1',fdir)) +else +env.error("WARNING: lfs and/or io not de-sanitized - cannot dump _G!") +end +end +function UTILS.DoString(s) +local f,err=loadstring(s) +if f then +return true,f() +else +return false,err +end +end +UTILS.ToDegree=function(angle) +return angle*180/math.pi +end +UTILS.ToRadian=function(angle) +return angle*math.pi/180 +end +UTILS.MetersToNM=function(meters) +return meters/1852 +end +UTILS.KiloMetersToNM=function(kilometers) +return kilometers/1852*1000 +end +UTILS.MetersToSM=function(meters) +return meters/1609.34 +end +UTILS.KiloMetersToSM=function(kilometers) +return kilometers/1609.34*1000 +end +UTILS.MetersToFeet=function(meters) +return meters/0.3048 +end +UTILS.KiloMetersToFeet=function(kilometers) +return kilometers/0.3048*1000 +end +UTILS.NMToMeters=function(NM) +return NM*1852 +end +UTILS.NMToKiloMeters=function(NM) +return NM*1852/1000 +end +UTILS.FeetToMeters=function(feet) +return feet*0.3048 +end +UTILS.KnotsToKmph=function(knots) +return knots*1.852 +end +UTILS.KmphToKnots=function(knots) +return knots/1.852 +end +UTILS.KmphToMps=function(kmph) +return kmph/3.6 +end +UTILS.MpsToKmph=function(mps) +return mps*3.6 +end +UTILS.MiphToMps=function(miph) +return miph*0.44704 +end +UTILS.MpsToMiph=function(mps) +return mps/0.44704 +end +UTILS.MpsToKnots=function(mps) +return mps*1.94384 +end +UTILS.KnotsToMps=function(knots) +if type(knots)=="number"then +return knots/1.94384 +else +return 0 +end +end +UTILS.CelsiusToFahrenheit=function(Celcius) +return Celcius*9/5+32 +end +UTILS.hPa2inHg=function(hPa) +return hPa*0.0295299830714 +end +UTILS.IasToTas=function(ias,altitude,oatcorr) +oatcorr=oatcorr or 0.017 +local tas=ias+(ias*oatcorr*UTILS.MetersToFeet(altitude)/1000) +return tas +end +UTILS.TasToIas=function(tas,altitude,oatcorr) +oatcorr=oatcorr or 0.017 +local ias=tas/(1+oatcorr*UTILS.MetersToFeet(altitude)/1000) +return ias +end +UTILS.KnotsToAltKIAS=function(knots,altitude) +return(knots*0.018*(altitude/1000))+knots +end +UTILS.hPa2mmHg=function(hPa) +return hPa*0.7500615613030 +end +UTILS.kg2lbs=function(kg) +return kg*2.20462 +end +UTILS.tostringLL=function(lat,lon,acc,DMS) +local latHemi,lonHemi +if lat>0 then +latHemi='N' +else +latHemi='S' +end +if lon>0 then +lonHemi='E' +else +lonHemi='W' +end +lat=math.abs(lat) +lon=math.abs(lon) +local latDeg=math.floor(lat) +local latMin=(lat-latDeg)*60 +local lonDeg=math.floor(lon) +local lonMin=(lon-lonDeg)*60 +if DMS then +local oldLatMin=latMin +latMin=math.floor(latMin) +local latSec=UTILS.Round((oldLatMin-latMin)*60,acc) +local oldLonMin=lonMin +lonMin=math.floor(lonMin) +local lonSec=UTILS.Round((oldLonMin-lonMin)*60,acc) +if latSec==60 then +latSec=0 +latMin=latMin+1 +end +if lonSec==60 then +lonSec=0 +lonMin=lonMin+1 +end +local secFrmtStr +secFrmtStr='%02d' +if acc<=0 then +secFrmtStr='%02d' +else +local width=3+acc +secFrmtStr='%0'..width..'.'..acc..'f' +end +return string.format('%03d°',latDeg)..string.format('%02d',latMin)..'\''..string.format(secFrmtStr,latSec)..'"'..latHemi..' ' +..string.format('%03d°',lonDeg)..string.format('%02d',lonMin)..'\''..string.format(secFrmtStr,lonSec)..'"'..lonHemi +else +latMin=UTILS.Round(latMin,acc) +lonMin=UTILS.Round(lonMin,acc) +if latMin==60 then +latMin=0 +latDeg=latDeg+1 +end +if lonMin==60 then +lonMin=0 +lonDeg=lonDeg+1 +end +local minFrmtStr +if acc<=0 then +minFrmtStr='%02d' +else +local width=3+acc +minFrmtStr='%0'..width..'.'..acc..'f' +end +return string.format('%03d°',latDeg)..' '..string.format(minFrmtStr,latMin)..'\''..latHemi..' ' +..string.format('%03d°',lonDeg)..' '..string.format(minFrmtStr,lonMin)..'\''..lonHemi +end +end +UTILS.tostringLLM2KData=function(lat,lon,acc) +local latHemi,lonHemi +if lat>0 then +latHemi='N' +else +latHemi='S' +end +if lon>0 then +lonHemi='E' +else +lonHemi='W' +end +lat=math.abs(lat) +lon=math.abs(lon) +local latDeg=math.floor(lat) +local latMin=(lat-latDeg)*60 +local lonDeg=math.floor(lon) +local lonMin=(lon-lonDeg)*60 +latMin=UTILS.Round(latMin,acc) +lonMin=UTILS.Round(lonMin,acc) +if latMin==60 then +latMin=0 +latDeg=latDeg+1 +end +if lonMin==60 then +lonMin=0 +lonDeg=lonDeg+1 +end +local minFrmtStr +if acc<=0 then +minFrmtStr='%02d' +else +local width=3+acc +minFrmtStr='%0'..width..'.'..acc..'f' +end +return latHemi..string.format('%02d:',latDeg)..string.format(minFrmtStr,latMin),lonHemi..string.format('%02d:',lonDeg)..string.format(minFrmtStr,lonMin) +end +UTILS.tostringMGRS=function(MGRS,acc) +if acc<=0 then +return MGRS.UTMZone..' '..MGRS.MGRSDigraph +else +if acc>5 then acc=5 end +local Easting=tostring(MGRS.Easting) +local Northing=tostring(MGRS.Northing) +local nE=5-string.len(Easting) +local nN=5-string.len(Northing) +for i=1,nE do Easting="0"..Easting end +for i=1,nN do Northing="0"..Northing end +return string.format("%s %s %s %s",MGRS.UTMZone,MGRS.MGRSDigraph,string.sub(Easting,1,acc),string.sub(Northing,1,acc)) +end +end +function UTILS.Round(num,idp) +local mult=10^(idp or 0) +return math.floor(num*mult+0.5)/mult +end +function UTILS.DoString(s) +local f,err=loadstring(s) +if f then +return true,f() +else +return false,err +end +end +function UTILS.spairs(t,order) +local keys={} +for k in pairs(t)do keys[#keys+1]=k end +if order then +table.sort(keys,function(a,b)return order(t,a,b)end) +else +table.sort(keys) +end +local i=0 +return function() +i=i+1 +if keys[i]then +return keys[i],t[keys[i]] +end +end +end +function UTILS.kpairs(t,getkey,order) +local keys={} +local keyso={} +for k,o in pairs(t)do keys[#keys+1]=k keyso[#keyso+1]=getkey(o)end +if order then +table.sort(keys,function(a,b)return order(t,a,b)end) +else +table.sort(keys) +end +local i=0 +return function() +i=i+1 +if keys[i]then +return keyso[i],t[keys[i]] +end +end +end +function UTILS.rpairs(t) +local keys={} +for k in pairs(t)do keys[#keys+1]=k end +local random={} +local j=#keys +for i=1,j do +local k=math.random(1,#keys) +random[i]=keys[k] +table.remove(keys,k) +end +local i=0 +return function() +i=i+1 +if random[i]then +return random[i],t[random[i]] +end +end +end +function UTILS.GetMarkID() +UTILS._MarkID=UTILS._MarkID+1 +return UTILS._MarkID +end +function UTILS.RemoveMark(MarkID,Delay) +if Delay and Delay>0 then +TIMER:New(UTILS.RemoveMark,MarkID):Start(Delay) +else +if MarkID then +trigger.action.removeMark(MarkID) +end +end +end +function UTILS.IsInRadius(InVec2,Vec2,Radius) +local InRadius=((InVec2.x-Vec2.x)^2+(InVec2.y-Vec2.y)^2)^0.5<=Radius +return InRadius +end +function UTILS.IsInSphere(InVec3,Vec3,Radius) +local InSphere=((InVec3.x-Vec3.x)^2+(InVec3.y-Vec3.y)^2+(InVec3.z-Vec3.z)^2)^0.5<=Radius +return InSphere +end +function UTILS.BeaufortScale(speed) +local bn=nil +local bd=nil +if speed<0.51 then +bn=0 +bd="Calm" +elseif speed<2.06 then +bn=1 +bd="Light Air" +elseif speed<3.60 then +bn=2 +bd="Light Breeze" +elseif speed<5.66 then +bn=3 +bd="Gentle Breeze" +elseif speed<8.23 then +bn=4 +bd="Moderate Breeze" +elseif speed<11.32 then +bn=5 +bd="Fresh Breeze" +elseif speed<14.40 then +bn=6 +bd="Strong Breeze" +elseif speed<17.49 then +bn=7 +bd="Moderate Gale" +elseif speed<21.09 then +bn=8 +bd="Fresh Gale" +elseif speed<24.69 then +bn=9 +bd="Strong Gale" +elseif speed<28.81 then +bn=10 +bd="Storm" +elseif speed<32.92 then +bn=11 +bd="Violent Storm" +else +bn=12 +bd="Hurricane" +end +return bn,bd +end +function UTILS.Split(str,sep) +local result={} +local regex=("([^%s]+)"):format(sep) +for each in str:gmatch(regex)do +table.insert(result,each) +end +return result +end +function UTILS.GetCharacters(str) +local chars={} +for i=1,#str do +local c=str:sub(i,i) +table.insert(chars,c) +end +return chars +end +function UTILS.SecondsToClock(seconds,short) +if seconds==nil then +return nil +end +local seconds=tonumber(seconds)or 0 +local _seconds=seconds%(60*60*24) +if seconds<0 then +return nil +else +local hours=string.format("%02.f",math.floor(_seconds/3600)) +local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60))) +local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60)) +local days=string.format("%d",seconds/(60*60*24)) +local clock=hours..":"..mins..":"..secs.."+"..days +if short then +if hours=="00"then +clock=hours..":"..mins..":"..secs +else +clock=hours..":"..mins..":"..secs +end +end +return clock +end +end +function UTILS.SecondsOfToday() +local time=timer.getAbsTime() +local clock=UTILS.SecondsToClock(time,true) +return UTILS.ClockToSeconds(clock) +end +function UTILS.SecondsToMidnight() +return 24*60*60-UTILS.SecondsOfToday() +end +function UTILS.ClockToSeconds(clock) +if clock==nil then +return nil +end +local seconds=0 +local dsplit=UTILS.Split(clock,"+") +if#dsplit>1 then +seconds=seconds+tonumber(dsplit[2])*60*60*24 +end +local tsplit=UTILS.Split(dsplit[1],":") +local i=1 +for _,time in ipairs(tsplit)do +if i==1 then +seconds=seconds+tonumber(time)*60*60 +elseif i==2 then +seconds=seconds+tonumber(time)*60 +elseif i==3 then +seconds=seconds+tonumber(time) +end +i=i+1 +end +return seconds +end +function UTILS.DisplayMissionTime(duration) +duration=duration or 5 +local Tnow=timer.getAbsTime() +local mission_time=Tnow-timer.getTime0() +local mission_time_minutes=mission_time/60 +local mission_time_seconds=mission_time%60 +local local_time=UTILS.SecondsToClock(Tnow) +local text=string.format("Time: %s - %02d:%02d",local_time,mission_time_minutes,mission_time_seconds) +MESSAGE:New(text,duration):ToAll() +end +function UTILS.ReplaceIllegalCharacters(Text,ReplaceBy) +ReplaceBy=ReplaceBy or"_" +local text=Text:gsub("[<>|/?*:\\]",ReplaceBy) +return text +end +function UTILS.RandomGaussian(x0,sigma,xmin,xmax,imax) +sigma=sigma or 10 +imax=imax or 100 +local r +local gotit=false +local i=0 +while not gotit do +local x1=math.random() +local x2=math.random() +r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0 +i=i+1 +if(r>=xmin and r<=xmax)or i>imax then +gotit=true +end +end +return r +end +function UTILS.Randomize(value,fac,lower,upper) +local min +if lower then +min=math.max(value-value*fac,lower) +else +min=value-value*fac +end +local max +if upper then +max=math.min(value+value*fac,upper) +else +max=value+value*fac +end +local r=math.random(min,max) +return r +end +function UTILS.VecDot(a,b) +return a.x*b.x+a.y*b.y+a.z*b.z +end +function UTILS.Vec2Dot(a,b) +return a.x*b.x+a.y*b.y +end +function UTILS.VecNorm(a) +return math.sqrt(UTILS.VecDot(a,a)) +end +function UTILS.Vec2Norm(a) +return math.sqrt(UTILS.Vec2Dot(a,a)) +end +function UTILS.VecDist2D(a,b) +local d=math.huge +if(not a)or(not b)then return d end +local c={x=b.x-a.x,y=b.y-a.y} +d=math.sqrt(c.x*c.x+c.y*c.y) +return d +end +function UTILS.VecDist3D(a,b) +local d=math.huge +if(not a)or(not b)then return d end +local c={x=b.x-a.x,y=b.y-a.y,z=b.z-a.z} +d=math.sqrt(UTILS.VecDot(c,c)) +return d +end +function UTILS.VecCross(a,b) +return{x=a.y*b.z-a.z*b.y,y=a.z*b.x-a.x*b.z,z=a.x*b.y-a.y*b.x} +end +function UTILS.VecSubstract(a,b) +return{x=a.x-b.x,y=a.y-b.y,z=a.z-b.z} +end +function UTILS.VecSubtract(a,b) +return UTILS.VecSubstract(a,b) +end +function UTILS.Vec2Substract(a,b) +return{x=a.x-b.x,y=a.y-b.y} +end +function UTILS.Vec2Subtract(a,b) +return UTILS.Vec2Substract(a,b) +end +function UTILS.VecAdd(a,b) +return{x=a.x+b.x,y=a.y+b.y,z=a.z+b.z} +end +function UTILS.Vec2Add(a,b) +return{x=a.x+b.x,y=a.y+b.y} +end +function UTILS.VecAngle(a,b) +local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b)) +local alpha=0 +if cosalpha>=0.9999999999 then +alpha=0 +elseif cosalpha<=-0.999999999 then +alpha=math.pi +else +alpha=math.acos(cosalpha) +end +return math.deg(alpha) +end +function UTILS.VecHdg(a) +local h=math.deg(math.atan2(a.z,a.x)) +if h<0 then +h=h+360 +end +return h +end +function UTILS.Vec2Hdg(a) +local h=math.deg(math.atan2(a.y,a.x)) +if h<0 then +h=h+360 +end +return h +end +function UTILS.HdgDiff(h1,h2) +local alpha=math.rad(tonumber(h1)) +local beta=math.rad(tonumber(h2)) +local v1={x=math.cos(alpha),y=0,z=math.sin(alpha)} +local v2={x=math.cos(beta),y=0,z=math.sin(beta)} +local delta=UTILS.VecAngle(v1,v2) +return math.abs(delta) +end +function UTILS.HdgTo(a,b) +local dz=(b.z or b.y)-(a.z or a.y) +local dx=b.x-a.x +local heading=math.deg(math.atan2(dz,dx)) +if heading<0 then +heading=360+heading +end +return heading +end +function UTILS.VecTranslate(a,distance,angle) +local SX=a.x +local SY=a.z +local Radians=math.rad(angle or 0) +local TX=distance*math.cos(Radians)+SX +local TY=distance*math.sin(Radians)+SY +return{x=TX,y=a.y,z=TY} +end +function UTILS.Vec2Translate(a,distance,angle) +local SX=a.x +local SY=a.y +local Radians=math.rad(angle or 0) +local TX=distance*math.cos(Radians)+SX +local TY=distance*math.sin(Radians)+SY +return{x=TX,y=TY} +end +function UTILS.Rotate2D(a,angle) +local phi=math.rad(angle) +local x=a.z +local y=a.x +local Z=x*math.cos(phi)-y*math.sin(phi) +local X=x*math.sin(phi)+y*math.cos(phi) +local Y=a.y +local A={x=X,y=Y,z=Z} +return A +end +function UTILS.Vec2Rotate2D(a,angle) +local phi=math.rad(angle) +local x=a.x +local y=a.y +local X=x*math.cos(phi)-y*math.sin(phi) +local Y=x*math.sin(phi)+y*math.cos(phi) +local A={x=X,y=Y} +return A +end +function UTILS.TACANToFrequency(TACANChannel,TACANMode) +if type(TACANChannel)~="number"then +return nil +end +if TACANMode~="X"and TACANMode~="Y"then +return nil +end +local A=1151 +local B=64 +if TACANChannel<64 then +B=1 +end +if TACANMode=='Y'then +A=1025 +if TACANChannel<64 then +A=1088 +end +else +if TACANChannel<64 then +A=962 +end +end +return(A+TACANChannel-B)*1000000 +end +function UTILS.GetDCSMap() +return env.mission.theatre +end +function UTILS.GetDCSMissionDate() +local year=tostring(env.mission.date.Year) +local month=tostring(env.mission.date.Month) +local day=tostring(env.mission.date.Day) +return string.format("%s/%s/%s",year,month,day),tonumber(year),tonumber(month),tonumber(day) +end +function UTILS.GetMissionDay(Time) +Time=Time or timer.getAbsTime() +local clock=UTILS.SecondsToClock(Time,false) +local x=tonumber(UTILS.Split(clock,"+")[2]) +return x +end +function UTILS.GetMissionDayOfYear(Time) +local Date,Year,Month,Day=UTILS.GetDCSMissionDate() +local d=UTILS.GetMissionDay(Time) +return UTILS.GetDayOfYear(Year,Month,Day)+d +end +function UTILS.GetMagneticDeclination(map) +map=map or UTILS.GetDCSMap() +local declination=0 +if map==DCSMAP.Caucasus then +declination=6 +elseif map==DCSMAP.NTTR then +declination=12 +elseif map==DCSMAP.Normandy then +declination=-10 +elseif map==DCSMAP.PersianGulf then +declination=2 +elseif map==DCSMAP.TheChannel then +declination=-10 +elseif map==DCSMAP.Syria then +declination=5 +elseif map==DCSMAP.MarianaIslands then +declination=2 +elseif map==DCSMAP.Falklands then +declination=12 +elseif map==DCSMAP.Sinai then +declination=4.8 +elseif map==DCSMAP.Kola then +declination=15 +elseif map==DCSMAP.Afghanistan then +declination=3 +elseif map==DCSMAP.Iraq then +declination=4.4 +elseif map==DCSMAP.GermanyCW then +declination=0.1 +else +declination=0 +end +return declination +end +function UTILS.FileExists(file) +if io then +local f=io.open(file,"r") +if f~=nil then +io.close(f) +return true +else +return false +end +else +return nil +end +end +function UTILS.CheckMemory(output) +local time=timer.getTime() +local clock=UTILS.SecondsToClock(time) +local mem=collectgarbage("count") +if output then +env.info(string.format("T=%s Memory usage %d kByte = %.2f MByte",clock,mem,mem/1024)) +end +return mem +end +function UTILS.GetCoalitionName(Coalition) +if Coalition then +if Coalition==coalition.side.BLUE then +return"Blue" +elseif Coalition==coalition.side.RED then +return"Red" +elseif Coalition==coalition.side.NEUTRAL then +return"Neutral" +else +return"Unknown" +end +else +return"Unknown" +end +end +function UTILS.GetCoalitionEnemy(Coalition,Neutral) +local Coalitions={} +if Coalition then +if Coalition==coalition.side.RED then +Coalitions={coalition.side.BLUE} +elseif Coalition==coalition.side.BLUE then +Coalitions={coalition.side.RED} +elseif Coalition==coalition.side.NEUTRAL then +Coalitions={coalition.side.RED,coalition.side.BLUE} +end +end +if Neutral then +table.insert(Coalitions,coalition.side.NEUTRAL) +end +return Coalitions +end +function UTILS.GetModulationName(Modulation) +if Modulation then +if Modulation==0 then +return"AM" +elseif Modulation==1 then +return"FM" +else +return"Unknown" +end +else +return"Unknown" +end +end +function UTILS.GetReportingName(Typename) +local typename=string.lower(Typename) +if string.find(typename,"ka-50",1,true)then +return"Shark" +elseif string.find(typename,"a-50",1,true)then +return"Mainstay" +end +for name,value in pairs(ENUMS.ReportingName.NATO)do +local svalue=string.lower(value) +if string.find(typename,svalue,1,true)then +return name +end +end +return"Bogey" +end +function UTILS.GetCallsignName(Callsign) +for name,value in pairs(CALLSIGN.Aircraft)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.AWACS)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.JTAC)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.Tanker)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.B1B)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.B52)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.F15E)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.F16)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.F18)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.FARP)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.TransportAircraft)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.AH64)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.Kiowa)do +if value==Callsign then +return name +end +end +return"Ghostrider" +end +function UTILS.GMTToLocalTimeDifference() +local theatre=UTILS.GetDCSMap() +if theatre==DCSMAP.Caucasus then +return 4 +elseif theatre==DCSMAP.PersianGulf then +return 4 +elseif theatre==DCSMAP.NTTR then +return-8 +elseif theatre==DCSMAP.Normandy then +return 0 +elseif theatre==DCSMAP.TheChannel then +return 2 +elseif theatre==DCSMAP.Syria then +return 3 +elseif theatre==DCSMAP.MarianaIslands then +return 10 +elseif theatre==DCSMAP.Falklands then +return-3 +elseif theatre==DCSMAP.Sinai then +return 2 +elseif theatre==DCSMAP.Kola then +return 3 +elseif theatre==DCSMAP.Afghanistan then +return 4.5 +elseif theatre==DCSMAP.Iraq then +return 3.0 +elseif theatre==DCSMAP.GermanyCW then +return 1.0 +else +BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0",tostring(theatre))) +return 0 +end +end +function UTILS.GetDayOfYear(Year,Month,Day) +local floor=math.floor +local n1=floor(275*Month/9) +local n2=floor((Month+9)/12) +local n3=(1+floor((Year-4*floor(Year/4)+2)/3)) +return n1-(n2*n3)+Day-30 +end +function UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,Rising,Tlocal) +local zenith=90.83 +local latitude=Latitude +local longitude=Longitude +local rising=Rising +local n=DayOfYear +Tlocal=Tlocal or 0 +local rad=math.rad +local deg=math.deg +local floor=math.floor +local frac=function(n)return n-floor(n)end +local cos=function(d)return math.cos(rad(d))end +local acos=function(d)return deg(math.acos(d))end +local sin=function(d)return math.sin(rad(d))end +local asin=function(d)return deg(math.asin(d))end +local tan=function(d)return math.tan(rad(d))end +local atan=function(d)return deg(math.atan(d))end +local function fit_into_range(val,min,max) +local range=max-min +local count +if val=max then +count=floor((val-max)/range)+1 +return val-count*range +else +return val +end +end +local lng_hour=longitude/15 +local t +if rising then +t=n+((6-lng_hour)/24) +else +t=n+((18-lng_hour)/24) +end +local M=(0.9856*t)-3.289 +local L=fit_into_range(M+(1.916*sin(M))+(0.020*sin(2*M))+282.634,0,360) +local RA=fit_into_range(atan(0.91764*tan(L)),0,360) +local Lquadrant=floor(L/90)*90 +local RAquadrant=floor(RA/90)*90 +RA=RA+Lquadrant-RAquadrant +RA=RA/15 +local sinDec=0.39782*sin(L) +local cosDec=cos(asin(sinDec)) +local cosH=(cos(zenith)-(sinDec*sin(latitude)))/(cosDec*cos(latitude)) +if rising and cosH>1 then +return"N/R" +elseif cosH<-1 then +return"N/S" +end +local H +if rising then +H=360-acos(cosH) +else +H=acos(cosH) +end +H=H/15 +local T=H+RA-(0.06571*t)-6.622 +local UT=fit_into_range(T-lng_hour+Tlocal,0,24) +return floor(UT)*60*60+frac(UT)*60*60 +end +function UTILS.GetSunrise(Day,Month,Year,Latitude,Longitude,Tlocal) +local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) +return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tlocal) +end +function UTILS.GetSunset(Day,Month,Year,Latitude,Longitude,Tlocal) +local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) +return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tlocal) +end +function UTILS.GetOSTime() +if os then +local ts=0 +local t=os.date("*t") +local s=t.sec +local m=t.min*60 +local h=t.hour*3600 +ts=s+m+h +return ts +else +return nil +end +end +function UTILS.ShuffleTable(t) +if t==nil or type(t)~="table"then +BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") +return +end +math.random() +math.random() +math.random() +local TempTable={} +for i=1,#t do +local r=math.random(1,#t) +TempTable[i]=t[r] +table.remove(t,r) +end +return TempTable +end +function UTILS.GetRandomTableElement(t,replace) +if t==nil or type(t)~="table"then +BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") +return +end +math.random() +math.random() +math.random() +local r=math.random(#t) +local element=t[r] +if not replace then +table.remove(t,r) +end +return element +end +function UTILS.IsLoadingDoorOpen(unit_name) +local unit=Unit.getByName(unit_name) +if unit~=nil then +local type_name=unit:getTypeName() +BASE:T("TypeName = "..type_name) +if type_name=="Mi-8MT"and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(86)==1 or unit:getDrawArgumentValue(250)<0)then +BASE:T(unit_name.." Cargo doors are open or cargo door not present") +return true +end +if type_name=="Mi-24P"and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(86)==1)then +BASE:T(unit_name.." a side door is open") +return true +end +if type_name=="UH-1H"and(unit:getDrawArgumentValue(43)==1 or unit:getDrawArgumentValue(44)==1)then +BASE:T(unit_name.." a side door is open ") +return true +end +if string.find(type_name,"SA342")and(unit:getDrawArgumentValue(34)==1)then +BASE:T(unit_name.." front door(s) are open or doors removed") +return true +end +if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1215)==1 and unit:getDrawArgumentValue(1216)==1)then +BASE:T(unit_name.." rear doors are open") +return true +end +if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1220)==1 or unit:getDrawArgumentValue(1221)==1)then +BASE:T(unit_name.." para doors are open") +return true +end +if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1217)==1)then +BASE:T(unit_name.." side door is open") +return true +end +if type_name=="Bell-47"then +BASE:T(unit_name.." door is open") +return true +end +if type_name=="UH-60L"and(unit:getDrawArgumentValue(401)==1 or unit:getDrawArgumentValue(402)==1)then +BASE:T(unit_name.." cargo door is open") +return true +end +if type_name=="UH-60L"and(unit:getDrawArgumentValue(38)>0 or unit:getDrawArgumentValue(400)==1)then +BASE:T(unit_name.." front door(s) are open") +return true +end +if type_name=="UH-60L_DAP"and(unit:getDrawArgumentValue(401)==1 or unit:getDrawArgumentValue(402)==1)then +BASE:T(unit_name.." cargo door is open") +return true +end +if type_name=="UH-60L_DAP"and(unit:getDrawArgumentValue(38)>0 or unit:getDrawArgumentValue(400)==1)then +BASE:T(unit_name.." front door(s) are open") +return true +end +if type_name=="AH-64D_BLK_II"then +BASE:T(unit_name.." front door(s) are open") +return true +end +if type_name=="Bronco-OV-10A"then +BASE:T(unit_name.." front door(s) are open") +return true +end +if type_name=="MH-60R"and(unit:getDrawArgumentValue(403)>0 or unit:getDrawArgumentValue(403)==-1)then +BASE:T(unit_name.." cargo door is open") +return true +end +if type_name=="OH58D"then +BASE:T(unit_name.." front door(s) are open") +return true +end +if type_name=="CH-47Fbl1"and(unit:getDrawArgumentValue(86)>0.5)then +BASE:T(unit_name.." rear cargo door is open") +return true +end +local UnitDescriptor=unit:getDesc() +local IsGroundResult=(UnitDescriptor.category==Unit.Category.GROUND_UNIT) +return IsGroundResult +end +return nil +end +function UTILS.GenerateFMFrequencies() +local FreeFMFrequencies={} +for _first=3,7 do +for _second=0,5 do +for _third=0,9 do +local _frequency=((100*_first)+(10*_second)+_third)*100000 +table.insert(FreeFMFrequencies,_frequency) +end +end +end +return FreeFMFrequencies +end +function UTILS.GenerateVHFrequencies() +local _skipFrequencies={ +214,243,264,273,274,288,291.5,295,297.5, +300.5,304,305,307,309.5,310,311,312,312.5,316,317, +320,323,324,325,326,328,329,330,332,335,336,337, +340,342,343,346,348,351,352,353,358, +360,363,364,365,368,372.5,373,374, +380,381,384,385,387,389,391,395,396,399, +403,404,410,412,414,418,420,423, +430,432,435,440,445, +450,455,462,470,485,490, +507,515,520,525,528,540,550,560,563,570,577,580,595, +602,625,641,662,670,680,682,690, +705,720,722,730,735,740,745,750,770,795, +822,830,862,866, +905,907,920,935,942,950,995, +1000,1025,1030,1050,1065,1116,1175,1182,1210,1215 +} +local FreeVHFFrequencies={} +local _start=200000 +while _start<400000 do +local _found=false +for _,value in pairs(_skipFrequencies)do +if value*1000==_start then +_found=true +break +end +end +if _found==false then +table.insert(FreeVHFFrequencies,_start) +end +_start=_start+10000 +end +_start=400000 +while _start<850000 do +local _found=false +for _,value in pairs(_skipFrequencies)do +if value*1000==_start then +_found=true +break +end +end +if _found==false then +table.insert(FreeVHFFrequencies,_start) +end +_start=_start+10000 +end +_start=850000 +while _start<=999000 do +local _found=false +for _,value in pairs(_skipFrequencies)do +if value*1000==_start then +_found=true +break +end +end +if _found==false then +table.insert(FreeVHFFrequencies,_start) +end +_start=_start+50000 +end +return FreeVHFFrequencies +end +function UTILS.GenerateUHFrequencies(Start,End) +local FreeUHFFrequencies={} +local _start=220000000 +if not Start then +while _start<399000000 do +if _start~=243000000 then +table.insert(FreeUHFFrequencies,_start) +end +_start=_start+500000 +end +else +local myend=End*1000000 or 399000000 +local mystart=Start*1000000 or 220000000 +while _start<399000000 do +if _start~=243000000 and(_startmyend)then +print(_start) +table.insert(FreeUHFFrequencies,_start) +end +_start=_start+500000 +end +end +return FreeUHFFrequencies +end +function UTILS.GenerateLaserCodes() +local jtacGeneratedLaserCodes={} +local function ContainsDigit(_number,_numberToFind) +local _thisNumber=_number +local _thisDigit=0 +while _thisNumber~=0 do +_thisDigit=_thisNumber%10 +_thisNumber=math.floor(_thisNumber/10) +if _thisDigit==_numberToFind then +return true +end +end +return false +end +local _code=1111 +local _count=1 +while _code<1777 and _count<30 do +while true do +_code=_code+1 +if not ContainsDigit(_code,8) +and not ContainsDigit(_code,9) +and not ContainsDigit(_code,0)then +table.insert(jtacGeneratedLaserCodes,_code) +break +end +end +_count=_count+1 +end +return jtacGeneratedLaserCodes +end +function UTILS.EnsureTable(Object,ReturnNil) +if Object then +if type(Object)~="table"then +Object={Object} +end +else +if ReturnNil then +return nil +else +Object={} +end +end +return Object +end +function UTILS.SaveToFile(Path,Filename,Data) +if not io then +BASE:E("ERROR: io not desanitized. Can't save current file.") +return false +end +if Path==nil and not lfs then +BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +local path=nil +if lfs then +path=Path or lfs.writedir() +end +local filename=Filename +if path~=nil then +filename=path.."\\"..filename +end +local f=assert(io.open(filename,"wb")) +f:write(Data) +f:close() +return true +end +function UTILS.LoadFromFile(Path,Filename) +if not io then +BASE:E("ERROR: io not desanitized. Can't save current state.") +return false +end +if Path==nil and not lfs then +BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +local path=nil +if lfs then +path=Path or lfs.writedir() +end +local filename=Filename +if path~=nil then +filename=path.."\\"..filename +end +local exists=UTILS.CheckFileExists(Path,Filename) +if not exists then +BASE:I(string.format("ERROR: File %s does not exist!",filename)) +return false +end +local file=assert(io.open(filename,"rb")) +local loadeddata={} +for line in file:lines()do +loadeddata[#loadeddata+1]=line +end +file:close() +return true,loadeddata +end +function UTILS.CheckFileExists(Path,Filename) +local function _fileexists(name) +local f=io.open(name,"r") +if f~=nil then +io.close(f) +return true +else +return false +end +end +if not io then +BASE:E("ERROR: io not desanitized.") +return false +end +if Path==nil and not lfs then +BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +local path=nil +if lfs then +path=Path or lfs.writedir() +end +local filename=Filename +if path~=nil then +filename=path.."\\"..filename +end +local exists=_fileexists(filename) +if not exists then +BASE:E(string.format("ERROR: File %s does not exist!",filename)) +return false +else +return true +end +end +function UTILS.GetCountPerTypeName(Group) +local units=Group:GetUnits() +local TypeNameTable={} +for _,_unt in pairs(units)do +local unit=_unt +local typen=unit:GetTypeName() +if not TypeNameTable[typen]then +TypeNameTable[typen]=1 +else +TypeNameTable[typen]=TypeNameTable[typen]+1 +end +end +return TypeNameTable +end +function UTILS.SaveStationaryListOfGroups(List,Path,Filename,Structured) +local filename=Filename or"StateListofGroups" +local data="--Save Stationary List of Groups: "..Filename.."\n" +for _,_group in pairs(List)do +local group=GROUP:FindByName(_group) +if group and group:IsAlive()then +local units=group:CountAliveUnits() +local position=group:GetVec3() +if Structured then +local structure=UTILS.GetCountPerTypeName(group) +local strucdata="" +for typen,anzahl in pairs(structure)do +strucdata=strucdata..typen.."=="..anzahl..";" +end +data=string.format("%s%s,%d,%d,%d,%d,%s\n",data,_group,units,position.x,position.y,position.z,strucdata) +else +data=string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z) +end +else +data=string.format("%s%s,0,0,0,0\n",data,_group) +end +end +local outcome=UTILS.SaveToFile(Path,Filename,data) +return outcome +end +function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured) +local filename=Filename or"SetOfGroups" +local data="--Save SET of groups: "..Filename.."\n" +local List=Set:GetSetObjects() +for _,_group in pairs(List)do +local group=_group +if group and group:IsAlive()then +local name=group:GetName() +local template=string.gsub(name,"-(.+)$","") +if string.find(name,"AID")then +template=string.gsub(name,"(.AID.%d+$","") +end +if string.find(template,"#")then +template=string.gsub(name,"#(%d+)$","") +end +local units=group:CountAliveUnits() +local position=group:GetVec3() +if Structured then +local structure=UTILS.GetCountPerTypeName(group) +local strucdata="" +for typen,anzahl in pairs(structure)do +strucdata=strucdata..typen.."=="..anzahl..";" +end +data=string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata) +else +data=string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z) +end +end +end +local outcome=UTILS.SaveToFile(Path,Filename,data) +return outcome +end +function UTILS.SaveSetOfStatics(Set,Path,Filename) +local filename=Filename or"SetOfStatics" +local data="--Save SET of statics: "..Filename.."\n" +local List=Set:GetSetObjects() +for _,_group in pairs(List)do +local group=_group +if group and group:IsAlive()then +local name=group:GetName() +local position=group:GetVec3() +data=string.format("%s%s,%d,%d,%d\n",data,name,position.x,position.y,position.z) +end +end +local outcome=UTILS.SaveToFile(Path,Filename,data) +return outcome +end +function UTILS.SaveStationaryListOfStatics(List,Path,Filename) +local filename=Filename or"StateListofStatics" +local data="--Save Stationary List of Statics: "..Filename.."\n" +for _,_group in pairs(List)do +local group=STATIC:FindByName(_group,false) +if group and group:IsAlive()then +local position=group:GetVec3() +data=string.format("%s%s,1,%d,%d,%d\n",data,_group,position.x,position.y,position.z) +else +data=string.format("%s%s,0,0,0,0\n",data,_group) +end +end +local outcome=UTILS.SaveToFile(Path,Filename,data) +return outcome +end +function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density) +local fires={} +local function Smokers(name,coord,effect,density) +local eff=math.random(8) +if type(effect)=="number"then eff=effect end +coord:BigSmokeAndFire(eff,density,name) +table.insert(fires,name) +end +local function Cruncher(group,typename,anzahl) +local units=group:GetUnits() +local reduced=0 +for _,_unit in pairs(units)do +local typo=_unit:GetTypeName() +if typename==typo then +if Cinematic then +local coordinate=_unit:GetCoordinate() +local name=_unit:GetName() +Smokers(name,coordinate,Effect,Density) +end +_unit:Destroy(false) +reduced=reduced+1 +if reduced==anzahl then break end +end +end +end +local reduce=true +if Reduce==false then reduce=false end +local filename=Filename or"StateListofGroups" +local datatable={} +if UTILS.CheckFileExists(Path,filename)then +local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename) +table.remove(loadeddata,1) +for _id,_entry in pairs(loadeddata)do +local dataset=UTILS.Split(_entry,",") +local groupname=dataset[1] +local size=tonumber(dataset[2]) +local posx=tonumber(dataset[3]) +local posy=tonumber(dataset[4]) +local posz=tonumber(dataset[5]) +local structure=dataset[6] +local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz}) +local data={groupname=groupname,size=size,coordinate=coordinate,group=GROUP:FindByName(groupname)} +if reduce then +local actualgroup=GROUP:FindByName(groupname) +if actualgroup and actualgroup:IsAlive()and actualgroup:CountAliveUnits()>size then +if Structured and structure then +local loadedstructure={} +local strcset=UTILS.Split(structure,";") +for _,_data in pairs(strcset)do +local datasplit=UTILS.Split(_data,"==") +loadedstructure[datasplit[1]]=tonumber(datasplit[2]) +end +local originalstructure=UTILS.GetCountPerTypeName(actualgroup) +for _name,_number in pairs(originalstructure)do +local loadednumber=0 +if loadedstructure[_name]then +loadednumber=loadedstructure[_name] +end +local reduce=false +if loadednumber<_number then reduce=true end +if reduce then +Cruncher(actualgroup,_name,_number-loadednumber) +end +end +else +local reduction=actualgroup:CountAliveUnits()-size +local units=actualgroup:GetUnits() +local units2=UTILS.ShuffleTable(units) +for i=1,reduction do +units2[i]:Destroy(false) +end +end +end +end +table.insert(datatable,data) +end +else +return nil +end +return datatable,fires +end +function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density) +local fires={} +local usedtemplates={} +local spawn=true +if Spawn==false then spawn=false end +local filename=Filename or"SetOfGroups" +local setdata=SET_GROUP:New() +local datatable={} +local function Smokers(name,coord,effect,density) +local eff=math.random(8) +if type(effect)=="number"then eff=effect end +coord:BigSmokeAndFire(eff,density,name) +table.insert(fires,name) +end +local function Cruncher(group,typename,anzahl) +local units=group:GetUnits() +local reduced=0 +for _,_unit in pairs(units)do +local typo=_unit:GetTypeName() +if typename==typo then +if Cinematic then +local coordinate=_unit:GetCoordinate() +local name=_unit:GetName() +Smokers(name,coordinate,Effect,Density) +end +_unit:Destroy(false) +reduced=reduced+1 +if reduced==anzahl then break end +end +end +end +local function PostSpawn(args) +local spwndgrp=args[1] +local size=args[2] +local structure=args[3] +setdata:AddObject(spwndgrp) +local actualsize=spwndgrp:CountAliveUnits() +if actualsize>size then +if Structured and structure then +local loadedstructure={} +local strcset=UTILS.Split(structure,";") +for _,_data in pairs(strcset)do +local datasplit=UTILS.Split(_data,"==") +loadedstructure[datasplit[1]]=tonumber(datasplit[2]) +end +local originalstructure=UTILS.GetCountPerTypeName(spwndgrp) +for _name,_number in pairs(originalstructure)do +local loadednumber=0 +if loadedstructure[_name]then +loadednumber=loadedstructure[_name] +end +local reduce=false +if loadednumber<_number then reduce=true end +if reduce then +Cruncher(spwndgrp,_name,_number-loadednumber) +end +end +else +local reduction=actualsize-size +local units=spwndgrp:GetUnits() +local units2=UTILS.ShuffleTable(units) +for i=1,reduction do +units2[i]:Destroy(false) +end +end +end +end +local function MultiUse(Data) +local template=Data.template +if template and usedtemplates[template]and usedtemplates[template].used and usedtemplates[template].used>1 then +if not usedtemplates[template].done then +local spwnd=0 +local spawngrp=SPAWN:New(template) +spawngrp:InitLimit(0,usedtemplates[template].used) +for _,_entry in pairs(usedtemplates[template].data)do +spwnd=spwnd+1 +local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd) +BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) +end +usedtemplates[template].done=true +end +return true +else +return false +end +end +if UTILS.CheckFileExists(Path,filename)then +local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename) +table.remove(loadeddata,1) +for _id,_entry in pairs(loadeddata)do +local dataset=UTILS.Split(_entry,",") +local groupname=dataset[1] +local template=dataset[2] +local size=tonumber(dataset[3]) +local posx=tonumber(dataset[4]) +local posy=tonumber(dataset[5]) +local posz=tonumber(dataset[6]) +local structure=dataset[7] +local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz}) +local group=nil +if size>0 then +local data={groupname=groupname,size=size,coordinate=coordinate,template=template,structure=structure} +table.insert(datatable,data) +if usedtemplates[template]then +usedtemplates[template].used=usedtemplates[template].used+1 +table.insert(usedtemplates[template].data,data) +else +usedtemplates[template]={ +data={}, +used=1, +done=false, +} +table.insert(usedtemplates[template].data,data) +end +end +end +for _id,_entry in pairs(datatable)do +if spawn and not MultiUse(_entry)and _entry.size>0 then +local group=SPAWN:New(_entry.template) +local sgrp=group:SpawnFromCoordinate(_entry.coordinate) +BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) +end +end +else +return nil +end +if spawn then +return setdata,fires +else +return datatable +end +end +function UTILS.LoadSetOfStatics(Path,Filename) +local filename=Filename or"SetOfStatics" +local datatable=SET_STATIC:New() +if UTILS.CheckFileExists(Path,filename)then +local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename) +table.remove(loadeddata,1) +for _id,_entry in pairs(loadeddata)do +local dataset=UTILS.Split(_entry,",") +local staticname=dataset[1] +local StaticObject=STATIC:FindByName(staticname,false) +if StaticObject then +datatable:AddObject(StaticObject) +end +end +else +return nil +end +return datatable +end +function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density) +local fires={} +local reduce=true +if Reduce==false then reduce=false end +local filename=Filename or"StateListofStatics" +local datatable={} +if UTILS.CheckFileExists(Path,filename)then +local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename) +table.remove(loadeddata,1) +for _id,_entry in pairs(loadeddata)do +local dataset=UTILS.Split(_entry,",") +local staticname=dataset[1] +local size=tonumber(dataset[2]) +local posx=tonumber(dataset[3]) +local posy=tonumber(dataset[4]) +local posz=tonumber(dataset[5]) +local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz}) +local data={staticname=staticname,size=size,coordinate=coordinate,static=STATIC:FindByName(staticname,false)} +table.insert(datatable,data) +if size==0 and reduce then +local static=STATIC:FindByName(staticname,false) +if static then +if Dead then +local deadobject=SPAWNSTATIC:NewFromStatic(staticname,static:GetCountry()) +deadobject:InitDead(true) +local heading=static:GetHeading() +local coord=static:GetCoordinate() +static:Destroy(false) +deadobject:SpawnFromCoordinate(coord,heading,staticname) +if Cinematic then +local effect=math.random(8) +if type(Effect)=="number"then +effect=Effect +end +coord:BigSmokeAndFire(effect,Density,staticname) +table.insert(fires,staticname) +end +else +static:Destroy(false) +end +end +end +end +else +return nil +end +return datatable,fires +end +function UTILS.BearingToCardinal(Heading) +if Heading>=0 and Heading<=22 then return"North" +elseif Heading>=23 and Heading<=66 then return"North-East" +elseif Heading>=67 and Heading<=101 then return"East" +elseif Heading>=102 and Heading<=146 then return"South-East" +elseif Heading>=147 and Heading<=201 then return"South" +elseif Heading>=202 and Heading<=246 then return"South-West" +elseif Heading>=247 and Heading<=291 then return"West" +elseif Heading>=292 and Heading<=338 then return"North-West" +elseif Heading>=339 then return"North" +end +end +function UTILS.ToStringBRAANATO(FromGrp,ToGrp) +local BRAANATO="Merged." +local GroupNumber=ToGrp:GetSize() +local GroupWords="Singleton" +if GroupNumber==2 then GroupWords="Two-Ship" +elseif GroupNumber>=3 then GroupWords="Heavy" +end +local grpLeadUnit=ToGrp:GetUnit(1) +local tgtCoord=grpLeadUnit:GetCoordinate() +local currentCoord=FromGrp:GetCoordinate() +local hdg=UTILS.Round(ToGrp:GetHeading()/100,1)*100 +local bearing=UTILS.Round(currentCoord:HeadingTo(tgtCoord),0) +local rangeMetres=tgtCoord:Get2DDistance(currentCoord) +local rangeNM=UTILS.Round(UTILS.MetersToNM(rangeMetres),0) +local aspect=tgtCoord:ToStringAspect(currentCoord) +local alt=UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0) +local track=UTILS.BearingToCardinal(hdg) +if rangeNM>3 then +if aspect==""then +BRAANATO=string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing,rangeNM,alt,track) +else +BRAANATO=string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords,bearing,rangeNM,alt,aspect,track) +end +end +return BRAANATO +end +function UTILS.IsInTable(Table,Object,Key) +for key,object in pairs(Table)do +if Key then +if Object[Key]==object[Key]then +return true +end +else +if object==Object then +return true +end +end +end +return false +end +function UTILS.IsAnyInTable(Table,Objects,Key) +for _,Object in pairs(UTILS.EnsureTable(Objects))do +for key,object in pairs(Table)do +if Key then +if Object[Key]==object[Key]then +return true +end +else +if object==Object then +return true +end +end +end +end +return false +end +function UTILS.PlotRacetrack(Coordinate,Altitude,Speed,Heading,Leg,Coalition,Color,Alpha,LineType,ReadOnly) +local fix_coordinate=Coordinate +local altitude=Altitude +local speed=Speed or 350 +local heading=Heading or 270 +local leg_distance=Leg or 10 +local coalition=Coalition or-1 +local color=Color or{1,0,0} +local alpha=Alpha or 1 +local lineType=LineType or 1 +speed=UTILS.IasToTas(speed,UTILS.FeetToMeters(altitude),oatcorr) +local turn_radius=0.0211*speed-3.01 +local point_two=fix_coordinate:Translate(UTILS.NMToMeters(leg_distance),heading,true,false) +local point_three=point_two:Translate(UTILS.NMToMeters(turn_radius)*2,heading-90,true,false) +local point_four=fix_coordinate:Translate(UTILS.NMToMeters(turn_radius)*2,heading-90,true,false) +local circle_center_fix_four=point_two:Translate(UTILS.NMToMeters(turn_radius),heading-90,true,false) +local circle_center_two_three=fix_coordinate:Translate(UTILS.NMToMeters(turn_radius),heading-90,true,false) +fix_coordinate:LineToAll(point_two,coalition,color,alpha,lineType) +point_four:LineToAll(point_three,coalition,color,alpha,lineType) +circle_center_fix_four:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType) +circle_center_two_three:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType) +end +function UTILS.TimeNow() +return UTILS.SecondsToClock(timer.getAbsTime(),false,false) +end +function UTILS.TimeDifferenceInSeconds(start_time,end_time) +return UTILS.ClockToSeconds(end_time)-UTILS.ClockToSeconds(start_time) +end +function UTILS.TimeLaterThan(time_string) +if timer.getAbsTime()>UTILS.ClockToSeconds(time_string)then +return true +end +return false +end +function UTILS.TimeBefore(time_string) +if timer.getAbsTime()max then value=max end +return value +end +function UTILS.ClampAngle(value) +if value>360 then return value-360 end +if value<0 then return value+360 end +return value +end +function UTILS.RemapValue(value,old_min,old_max,new_min,new_max) +new_min=new_min or 0 +new_max=new_max or 100 +local old_range=old_max-old_min +local new_range=new_max-new_min +local percentage=(value-old_min)/old_range +return(new_range*percentage)+new_min +end +function UTILS.RandomPointInTriangle(pt1,pt2,pt3) +local pt={math.random(),math.random()} +table.sort(pt) +local s=pt[1] +local t=pt[2]-pt[1] +local u=1-pt[2] +return{x=s*pt1.x+t*pt2.x+u*pt3.x, +y=s*pt1.y+t*pt2.y+u*pt3.y} +end +function UTILS.AngleBetween(angle,min,max) +angle=(360+(angle%360))%360 +min=(360+min%360)%360 +max=(360+max%360)%360 +if min0 then +for _,property in pairs(zone["properties"])do +return_table[property["key"]]=property["value"] +end +return return_table +else +BASE:I(string.format("%s doesn't have any properties",zone_name)) +return{} +end +end +end +end +function UTILS.RotatePointAroundPivot(point,pivot,angle) +local radians=math.rad(angle) +local x=point.x-pivot.x +local y=point.y-pivot.y +local rotated_x=x*math.cos(radians)-y*math.sin(radians) +local rotatex_y=x*math.sin(radians)+y*math.cos(radians) +local original_x=rotated_x+pivot.x +local original_y=rotatex_y+pivot.y +return{x=original_x,y=original_y} +end +function UTILS.UniqueName(base) +base=base or"" +local ran=tostring(math.random(0,1000000)) +if base==""then +return ran +end +return base.."_"..ran +end +function string.startswith(str,value) +return string.sub(str,1,string.len(value))==value +end +function string.endswith(str,value) +return value==""or str:sub(-#value)==value +end +function string.split(input,separator) +local parts={} +for part in input:gmatch("[^"..separator.."]+")do +table.insert(parts,part) +end +return parts +end +function string.contains(str,value) +return string.match(str,value) +end +function table.move_object(obj,from_table,to_table) +local index +for i,v in pairs(from_table)do +if v==obj then +index=i +end +end +if index then +local moved=table.remove(from_table,index) +table.insert_unique(to_table,moved) +end +end +function table.contains(tbl,element) +if element==nil or tbl==nil then return false end +local index=1 +while tbl[index]do +if tbl[index]==element then +return true +end +index=index+1 +end +return false +end +function table.contains_key(tbl,key) +if tbl[key]~=nil then return true else return false end +end +function table.insert_unique(tbl,element) +if element==nil or tbl==nil then return end +if not table.contains(tbl,element)then +table.insert(tbl,element) +end +end +function table.remove_by_value(tbl,element) +local indices_to_remove={} +local index=1 +for _,value in pairs(tbl)do +if value==element then +table.insert(indices_to_remove,index) +end +index=index+1 +end +for _,idx in pairs(indices_to_remove)do +table.remove(tbl,idx) +end +end +function table.remove_key(table,key) +local element=table[key] +table[key]=nil +return element +end +function table.index_of(table,element) +for i,v in ipairs(table)do +if v==element then +return i +end +end +return nil +end +function table.length(T) +local count=0 +for _ in pairs(T)do count=count+1 end +return count +end +function table.slice(tbl,first,last) +local sliced={} +local start=first or 1 +local stop=last or table.length(tbl) +local count=1 +for key,value in pairs(tbl)do +if count>=start and count<=stop then +sliced[key]=value +end +count=count+1 +end +return sliced +end +function table.count_value(tbl,value) +local count=0 +for _,item in pairs(tbl)do +if item==value then count=count+1 end +end +return count +end +function table.combine(t1,t2) +if t1==nil and t2==nil then +BASE:E("Both tables were empty!") +end +if t1==nil then return t2 end +if t2==nil then return t1 end +for i=1,#t2 do +t1[#t1+1]=t2[i] +end +return t1 +end +function table.merge(t1,t2) +for k,v in pairs(t2)do +if(type(v)=="table")and(type(t1[k]or false)=="table")then +table.merge(t1[k],t2[k]) +else +t1[k]=v +end +end +return t1 +end +function table.add(tbl,item) +tbl[#tbl+1]=item +end +function table.shuffle(tbl) +local new_table={} +for _,value in ipairs(tbl)do +local pos=math.random(1,#new_table+1) +table.insert(new_table,pos,value) +end +return new_table +end +function table.find_key_value_pair(tbl,key,value) +for k,v in pairs(tbl)do +if type(v)=="table"then +local result=table.find_key_value_pair(v,key,value) +if result~=nil then +return result +end +elseif k==key and v==value then +return tbl +end +end +return nil +end +function UTILS.DecimalToOctal(Number) +if Number<8 then return Number end +local number=tonumber(Number) +local octal="" +local n=1 +while number>7 do +local number1=number%8 +octal=string.format("%d",number1)..octal +local number2=math.abs(number/8) +if number2<8 then +octal=string.format("%d",number2)..octal +end +number=number2 +n=n+1 +end +return tonumber(octal) +end +function UTILS.OctalToDecimal(Number) +return tonumber(Number,8) +end +function UTILS.HexToRGBA(hex_string) +local hexNumber=tonumber(string.sub(hex_string,3),16) +local alpha=hexNumber%256 +hexNumber=(hexNumber-alpha)/256 +local blue=hexNumber%256 +hexNumber=(hexNumber-blue)/256 +local green=hexNumber%256 +hexNumber=(hexNumber-green)/256 +local red=hexNumber%256 +return{R=red,G=green,B=blue,A=alpha} +end +function UTILS.SaveSetOfOpsGroups(Set,Path,Filename,Structured) +local filename=Filename or"SetOfGroups" +local data="--Save SET of groups: (name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata) "..Filename.."\n" +local List=Set:GetSetObjects() +for _,_group in pairs(List)do +local group=_group:GetGroup() +if group and group:IsAlive()then +local name=group:GetName() +local template=string.gsub(name,"(.AID.%d+$","") +if string.find(template,"#")then +template=string.gsub(name,"#(%d+)$","") +end +local alttemplate=_group.templatename or"none" +local legiono=_group.legion +local legion="none" +if legiono and type(legiono)=="table"and legiono.ClassName then +legion=legiono:GetName() +local asset=legiono:GetAssetByName(name) +alttemplate=asset.templatename +end +local units=group:CountAliveUnits() +local position=group:GetVec3() +if Structured then +local structure=UTILS.GetCountPerTypeName(group) +local strucdata="" +for typen,anzahl in pairs(structure)do +strucdata=strucdata..typen.."=="..anzahl..";" +end +data=string.format("%s%s,%s,%s,%s,%d,%d,%d,%d,%s\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata) +else +data=string.format("%s%s,%s,%s,%s,%d,%d,%d,%d\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z) +end +end +end +local outcome=UTILS.SaveToFile(Path,Filename,data) +return outcome +end +function UTILS.LoadSetOfOpsGroups(Path,Filename) +local filename=Filename or"SetOfGroups" +local datatable={} +if UTILS.CheckFileExists(Path,filename)then +local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename) +table.remove(loadeddata,1) +for _id,_entry in pairs(loadeddata)do +local dataset=UTILS.Split(_entry,",") +local groupname=dataset[1] +local legion=dataset[2] +local template=dataset[3] +local alttemplate=dataset[4] +local size=tonumber(dataset[5]) +local posx=tonumber(dataset[6]) +local posy=tonumber(dataset[7]) +local posz=tonumber(dataset[8]) +local structure=dataset[9] +local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz}) +if size>0 then +local data={groupname=groupname,size=size,coordinate=coordinate,template=template,structure=structure,legion=legion,alttemplate=alttemplate} +table.insert(datatable,data) +end +end +else +return nil +end +return datatable +end +function UTILS.ClockHeadingString(refHdg,tgtHdg) +local relativeAngle=tgtHdg-refHdg +if relativeAngle<0 then +relativeAngle=relativeAngle+360 +end +local clockPos=math.ceil((relativeAngle%360)/30) +return clockPos.." o'clock" +end +function UTILS.MGRSStringToSRSFriendly(Text,Slow) +local Text=string.gsub(Text,"MGRS ","") +Text=string.gsub(Text,"%s+","") +Text=string.gsub(Text,"([%a%d])","%1;") +Text=string.gsub(Text,"A","Alpha") +Text=string.gsub(Text,"B","Bravo") +Text=string.gsub(Text,"C","Charlie") +Text=string.gsub(Text,"D","Delta") +Text=string.gsub(Text,"E","Echo") +Text=string.gsub(Text,"F","Foxtrot") +Text=string.gsub(Text,"G","Golf") +Text=string.gsub(Text,"H","Hotel") +Text=string.gsub(Text,"I","India") +Text=string.gsub(Text,"J","Juliett") +Text=string.gsub(Text,"K","Kilo") +Text=string.gsub(Text,"L","Lima") +Text=string.gsub(Text,"M","Mike") +Text=string.gsub(Text,"N","November") +Text=string.gsub(Text,"O","Oscar") +Text=string.gsub(Text,"P","Papa") +Text=string.gsub(Text,"Q","Quebec") +Text=string.gsub(Text,"R","Romeo") +Text=string.gsub(Text,"S","Sierra") +Text=string.gsub(Text,"T","Tango") +Text=string.gsub(Text,"U","Uniform") +Text=string.gsub(Text,"V","Victor") +Text=string.gsub(Text,"W","Whiskey") +Text=string.gsub(Text,"X","Xray") +Text=string.gsub(Text,"Y","Yankee") +Text=string.gsub(Text,"Z","Zulu") +Text=string.gsub(Text,"0","zero") +Text=string.gsub(Text,"9","niner") +if Slow then +Text=''..Text..'' +end +Text="MGRS;"..Text +return Text +end +function UTILS.ReadCSV(filename) +if not UTILS.FileExists(filename)then +env.error("File does not exist") +return nil +end +local function _loadfile(filename) +local f=io.open(filename,"rb") +if f then +local data=f:read("*all") +f:close() +return data +else +BASE:E(string.format("WARNING: Could read data from file %s!",tostring(filename))) +return nil +end +end +local data=_loadfile(filename) +local lines=UTILS.Split(data,"\n") +for _,line in pairs(lines)do +line=string.gsub(line,"[\n\r]","") +end +local sep=";" +local columns=UTILS.Split(lines[1],sep) +table.remove(lines,1) +local csvdata={} +for i,line in pairs(lines)do +line=string.gsub(line,"[\n\r]","") +local row={} +for j,value in pairs(UTILS.Split(line,sep))do +local key=string.gsub(columns[j],"[\n\r]","") +row[key]=value +end +table.insert(csvdata,row) +end +return csvdata +end +function UTILS.LCGRandomSeed(seed) +UTILS.lcg={ +seed=seed or math.random(1,2^32-1), +a=1664525, +c=1013904223, +m=2^32 +} +end +function UTILS.LCGRandom() +if UTILS.lcg==nil then +UTILS.LCGRandomSeed() +end +UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m +return UTILS.lcg.seed/UTILS.lcg.m +end +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end +local farplocation=Coordinate +local farptype=FARPType or ENUMS.FARPType.FARP +local Coalition=Coalition or coalition.side.BLUE +local callsign=CallSign or CALLSIGN.FARP.Berlin +local freq=Frequency or 127.5 +local mod=Modulation or radio.modulation.AM +local radius=SpawnRadius or 100 +if radius<0 or radius>150 then radius=100 end +local liquids=Liquids or 10 +liquids=liquids*1000 +local equip=Equipment or 10 +local airframes=Airframes or 10 +local statictypes=ENUMS.FARPObjectTypeNamesAndShape[farptype]or{TypeName="FARP",ShapeName="FARPS"} +local STypeName=statictypes.TypeName +local SShapeName=statictypes.ShapeName +local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) +local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +UnitTemplate.name=Name.."-"..id +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +PopulateStorage(Name.."-1",liquids,equip,airframes) +else +local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) +newfarp:InitShape(SShapeName) +newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) +local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) +table.insert(ReturnObjects,spawnedfarp) +PopulateStorage(Name,liquids,equip,airframes) +end +local FARPStaticObjectsNato={ +["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, +["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, +["TENT"]={TypeName="FARP Tent",ShapeName="PalatkaB",Category="Fortifications"}, +["WINDSOCK"]={TypeName="Windsock",ShapeName="H-Windsock_RW",Category="Fortifications"}, +} +local farpobcount=0 +for _name,_object in pairs(FARPStaticObjectsNato)do +local objloc=farplocation:Translate(radius,farpobcount*30) +local heading=objloc:HeadingTo(farplocation) +local newobject=SPAWNSTATIC:NewFromType(_object.TypeName,_object.Category,Country) +newobject:InitShape(_object.ShapeName) +newobject:InitHeading(heading) +newobject:SpawnFromCoordinate(objloc,farpobcount*30,_name.." - "..Name) +table.insert(ReturnObjects,newobject) +farpobcount=farpobcount+1 +end +if VehicleTemplate and type(VehicleTemplate)=="string"then +local vcoordinate=farplocation:Translate(radius,farpobcount*30) +local heading=vcoordinate:HeadingTo(farplocation) +local vehicles=SPAWN:NewWithAlias(VehicleTemplate,"FARP Vehicles - "..Name) +vehicles:InitGroupHeading(heading) +vehicles:InitCountry(Country) +vehicles:InitCoalition(Coalition) +vehicles:InitDelayOff() +local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) +table.insert(ReturnObjects,spawnedvehicle) +end +local ADFName +if ADF and type(ADF)=="number"then +local ADFFreq=ADF*1000 +local Sound="l10n/DEFAULT/beacon.ogg" +local vec3=farplocation:GetVec3() +ADFName=Name.." ADF "..tostring(ADF).."KHz" +trigger.action.radioTransmission(Sound,vec3,0,true,ADFFreq,250,ADFName) +end +local MarkerID=nil +if F10Text then +local Color={0,0,1} +if Coalition==coalition.side.RED then +Color={1,0,0} +elseif Coalition==coalition.side.NEUTRAL then +Color={0,1,0} +end +local Alpha=0.75 +local coordinate=Coordinate:Translate(600,0) +MarkerID=coordinate:TextToAll(F10Text,Coalition,Color,1,{1,1,1},Alpha,14,true) +end +return ReturnObjects,ADFName,MarkerID +end +function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,DeployHelo,MASHRadio,MASHRadioModulation,MASHCallsign,Templates) +local MASHTemplates={ +[1]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.000000,y=0.000000,}, +[2]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.313533,y=8.778935,}, +[3]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=16.303737,y=20.379671,}, +[4]={category='Helicopters',type='CH-47Fbl1',shape_name='none',heading=0,x=-20.047735,y=-63.166179,livery_id="us army dark green",}, +[5]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=26.650339,y=20.066138,}, +[6]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.432292,y=9.077099,}, +[7]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=-3.216114,}, +[8]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.439281,y=-3.216114,}, +[9]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=9.155603,}, +[10]={category='Fortifications',type='TACAN_beacon',shape_name='none',heading=0,x=-2.329847,y=-16.579903,}, +[11]={category='Fortifications',type='FARP Fuel Depot',shape_name='GSM Rus',heading=0,x=2.222011,y=4.487030,}, +[12]={category='Fortifications',type='APFC fuel',shape_name='M92_APFCfuel',heading=0,x=3.614927,y=0.367838,}, +[13]={category='Fortifications',type='Camouflage03',shape_name='M92_Camouflage03',heading=0,x=21.544148,y=21.998879,}, +[14]={category='Fortifications',type='Container_generator',shape_name='M92_Container_generator',heading=0,x=20.989192,y=37.314334,}, +[15]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=3.988003,y=8.362333,}, +[16]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=-3.953195,y=12.945844,}, +[17]={category='Fortifications',type='Windsock',shape_name='H-Windsock_RW',heading=0,x=-18.944173,y=-33.042196,}, +[18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,}, +} +if Templates then MASHTemplates=Templates end +local name=Name or"Florence Nightingale" +local positionVec2 +local positionVec3 +local ReturnStatics={} +local CountryID=Country or country.id.USA +local livery="us army dark green" +local MASHRadio=MASHRadio or 127.5 +local MASHRadioModulation=MASHRadioModulation or radio.modulation.AM +local MASHCallsign=MASHCallsign or CALLSIGN.FARP.Berlin +if type(Coordinate)=="table"then +if Coordinate:IsInstanceOf("COORDINATE")or Coordinate:IsInstanceOf("ZONE_BASE")then +positionVec2=Coordinate:GetVec2() +positionVec3=Coordinate:GetVec3() +end +else +BASE:E("Spawn MASH - no ZONE or COORDINATE handed!") +return +end +local BaseX=positionVec2.x +local BaseY=positionVec2.y +for id,object in pairs(MASHTemplates)do +local NewName=string.format("%s#%3d",name,id) +local vec2={x=BaseX+object.x,y=BaseY+object.y} +local Coordinate=COORDINATE:NewFromVec2(vec2) +local static=SPAWNSTATIC:NewFromType(object.type,object.category,CountryID) +if object.shape_name and object.shape_name~="none"then +static:InitShape(object.shape_name) +end +if object.category=="Helicopters"and DeployHelo==true then +if object.livery_id~=nil then +livery=object.livery_id +end +static:InitLivery(livery) +local newstatic=static:SpawnFromCoordinate(Coordinate,object.heading,NewName) +table.insert(ReturnStatics,newstatic) +elseif object.category=="Heliports"then +static:InitFARP(MASHCallsign,MASHRadio,MASHRadioModulation,false,false) +local newstatic=static:SpawnFromCoordinate(Coordinate,object.heading,NewName) +table.insert(ReturnStatics,newstatic) +elseif object.category~="Helicopters"and object.category~="Heliports"then +local newstatic=static:SpawnFromCoordinate(Coordinate,object.heading,NewName) +table.insert(ReturnStatics,newstatic) +end +end +local ADFName +if ADF and type(ADF)=="number"then +local ADFFreq=ADF*1000 +local Sound="l10n/DEFAULT/beacon.ogg" +ADFName=Name.." ADF "..tostring(ADF).."KHz" +trigger.action.radioTransmission(Sound,positionVec3,0,true,ADFFreq,250,ADFName) +end +return ReturnStatics,ADFName +end +function UTILS.Vec2toVec3(vec,y) +if not vec.z then +if vec.alt and not y then +y=vec.alt +elseif not y then +y=0 +end +return{x=vec.x,y=y,z=vec.y} +else +return{x=vec.x,y=vec.y,z=vec.z} +end +end +function UTILS.GetNorthCorrection(gPoint) +local point=UTILS.DeepCopy(gPoint) +if not point.z then +point.z=point.y +point.y=0 +end +local lat,lon=coord.LOtoLL(point) +local north_posit=coord.LLtoLO(lat+1,lon) +return math.atan2(north_posit.z-point.z,north_posit.x-point.x) +end +function UTILS.GetDHMS(timeInSec) +if timeInSec and type(timeInSec)=='number'then +local tbl={d=0,h=0,m=0,s=0} +if timeInSec>86400 then +while timeInSec>86400 do +tbl.d=tbl.d+1 +timeInSec=timeInSec-86400 +end +end +if timeInSec>3600 then +while timeInSec>3600 do +tbl.h=tbl.h+1 +timeInSec=timeInSec-3600 +end +end +if timeInSec>60 then +while timeInSec>60 do +tbl.m=tbl.m+1 +timeInSec=timeInSec-60 +end +end +tbl.s=timeInSec +return tbl +else +BASE:E("No number handed!") +return +end +end +function UTILS.GetDirectionRadians(vec,point) +local dir=math.atan2(vec.z,vec.x) +if point then +dir=dir+UTILS.GetNorthCorrection(point) +end +if dir<0 then +dir=dir+2*math.pi +end +return dir +end +function UTILS.IsPointInPolygon(point,poly,maxalt) +point=UTILS.Vec2toVec3(point) +local px=point.x +local pz=point.z +local cn=0 +local newpoly=UTILS.DeepCopy(poly) +if not maxalt or(point.y<=maxalt)then +local polysize=#newpoly +newpoly[#newpoly+1]=newpoly[1] +newpoly[1]=UTILS.Vec2toVec3(newpoly[1]) +for k=1,polysize do +newpoly[k+1]=UTILS.Vec2toVec3(newpoly[k+1]) +if((newpoly[k].z<=pz)and(newpoly[k+1].z>pz))or((newpoly[k].z>pz)and(newpoly[k+1].z<=pz))then +local vt=(pz-newpoly[k].z)/(newpoly[k+1].z-newpoly[k].z) +if(px5000 then value=5000 end +return world.weather.setFogThickness(value) +end +function UTILS.Weather.RemoveFog() +return world.weather.setFogThickness(0) +end +function UTILS.Weather.GetFogVisibilityDistanceMax() +return world.weather.getFogVisibilityDistance() +end +function UTILS.Weather.SetFogVisibilityDistance(Thickness) +local value=Thickness +if value<100 then value=100 +elseif value>100000 then value=100000 end +return world.weather.setFogVisibilityDistance(value) +end +function UTILS.Weather.SetFogAnimation(AnimationKeys) +return world.weather.setFogAnimation(AnimationKeys) +end +function UTILS.Weather.StopFogAnimation() +return world.weather.setFogAnimation({}) +end +function UTILS.GetEnvZone(name) +for _,v in ipairs(env.mission.triggers.zones)do +if v.name==name then +return v +end +end +end +function UTILS.DoStringIn(State,DoString) +return net.dostring_in(State,DoString) +end +function UTILS.ShowPictureToAll(FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture(\"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToCoalition(Coalition,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +local coalName=string.lower(UTILS.GetCoalitionName(Coalition)) +net.dostring_in("mission",string.format("a_out_picture_s(\"%s\", \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",coalName,FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToCountry(Country,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture_c(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Country,FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToGroup(Group,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture_g(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Group:GetID(),FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToUnit(Unit,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture_u(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Unit:GetID(),FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.LoadMission(FileName) +net.dostring_in("mission",string.format("a_load_mission(\"%s\")",FileName)) +end +function UTILS.SetMissionBriefing(Coalition,Text,Picture) +Text=Text or"" +Text=Text:gsub("\n","\\n") +Picture=Picture or"" +local coalName=string.lower(UTILS.GetCoalitionName(Coalition)) +net.dostring_in("mission",string.format("a_set_briefing(\"%s\", \"%s\", \"%s\")",coalName,Picture,Text)) +end +function UTILS.ShowHelperGate(pos,heading) +net.dostring_in("mission",string.format("a_show_helper_gate(%s, %s, %s, %f)",pos.x,pos.y,pos.z,math.rad(heading))) +end +function UTILS.ShowHelperGateForUnit(Unit,Flag) +net.dostring_in("mission",string.format("a_show_route_gates_for_unit(%d, \"%d\")",Unit:GetID(),Flag)) +end +function UTILS.SetCarrierIlluminationMode(UnitID,Mode) +net.dostring_in("mission",string.format("a_set_carrier_illumination_mode(%d, %d)",UnitID,Mode)) +end +function UTILS.ShellZone(name,power,count) +local z=UTILS.GetEnvZone(name) +if z then +net.dostring_in("mission",string.format("a_shelling_zone(%d, %d, %d)",z.zoneId,power,count)) +end +end +function UTILS.RemoveObjects(name,type) +local z=UTILS.GetEnvZone(name) +if z then +net.dostring_in("mission",string.format("a_remove_scene_objects(%d, %d)",z.zoneId,type)) +end +end +function UTILS.DestroyScenery(name,level) +local z=UTILS.GetEnvZone(name) +if z then +net.dostring_in("mission",string.format("a_scenery_destruction_zone(%d, %d)",z.zoneId,level)) +end +end +function UTILS.GetSimpleZones(Vec3,SearchRadius,PosRadius,NumPositions) +return Disposition.getSimpleZones(Vec3,SearchRadius,PosRadius,NumPositions) +end +function UTILS.GetClearZonePositions(Zone,PosRadius,NumPositions) +local radius=PosRadius or math.min(Zone:GetRadius()/10,200) +local clearPositions=UTILS.GetSimpleZones(Zone:GetVec3(),Zone:GetRadius(),radius,NumPositions or 50) +if clearPositions and#clearPositions>0 then +local validZones={} +for _,vec2 in pairs(clearPositions)do +if Zone:IsVec2InZone(vec2)then +table.insert(validZones,vec2) +end +end +if#validZones>0 then +return validZones,radius +end +end +return nil +end +function UTILS.GetRandomClearZoneCoordinate(Zone,PosRadius,NumPositions) +local clearPositions=UTILS.GetClearZonePositions(Zone,PosRadius,NumPositions) +if clearPositions and#clearPositions>0 then +local randomPosition,radius=clearPositions[math.random(1,#clearPositions)] +return COORDINATE:NewFromVec2(randomPosition),radius +end +return nil +end +function UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) +local r=Radius +local cx=Vec1.x or 1 +local cy=Vec1.y or 1 +local px=Vec2.x or 1 +local py=Vec2.y or 1 +local dx=px-cx +local dy=py-cy +local dist=math.sqrt(dx*dx+dy*dy) +if dist==0 then +return{x=cx+r,y=cy} +end +local norm_dx=dx/dist +local norm_dy=dy/dist +local qx=cx+r*norm_dx +local qy=cy+r*norm_dy +local shift_factor=1 +qx=qx+shift_factor*norm_dx +qy=qy+shift_factor*norm_dy +return{x=qx,y=qy} +end +function UTILS.ValidateAndRepositionGroundUnits(Positions,Anchor,MaxRadius,Spacing) +local units=Positions +Anchor=Anchor or UTILS.GetCenterPoint(units) +local gPos={x=Anchor.x,y=Anchor.z or Anchor.y} +local maxRadius=0 +local unitCount=0 +for _,unit in pairs(units)do +local pos={x=unit.x,y=unit.z or unit.y} +local dist=UTILS.VecDist2D(pos,gPos) +if dist>maxRadius then +maxRadius=dist +end +unitCount=unitCount+1 +end +maxRadius=MaxRadius or math.max(maxRadius*2,10) +local spacing=Spacing or math.max(maxRadius*0.05,5) +if unitCount>0 and maxRadius>5 then +local spots=UTILS.GetSimpleZones(UTILS.Vec2toVec3(gPos),maxRadius,spacing,1000) +if spots and#spots>0 then +local validSpots={} +for _,spot in pairs(spots)do +if land.getSurfaceType(spot)==land.SurfaceType.LAND then +table.insert(validSpots,spot) +end +end +spots=validSpots +end +local step=spacing +for _,unit in pairs(units)do +local pos={x=unit.x,y=unit.z or unit.y} +local isOnLand=land.getSurfaceType(pos)==land.SurfaceType.LAND +local isValid=false +if spots and#spots>0 then +local si=1 +local sid=0 +local closestDist=100000000 +local closestSpot +for _,spot in pairs(spots)do +local dist=UTILS.VecDist2D(pos,spot) +if dist=spacing then +pos=closestSpot +end +isValid=true +table.remove(spots,sid) +end +end +if not isValid and not isOnLand then +local h=UTILS.HdgTo(pos,gPos) +local retries=0 +while not isValid and retries<500 do +local dist=UTILS.VecDist2D(pos,gPos) +pos=UTILS.Vec2Translate(pos,step,h) +local skip=false +for _,unit2 in pairs(units)do +if unit~=unit2 then +local pos2={x=unit2.x,y=unit2.z or unit2.y} +local dist2=UTILS.VecDist2D(pos,pos2) +if dist2<12 then +isValid=false +skip=true +break +end +end +end +if not skip and dist>step and land.getSurfaceType(pos)==land.SurfaceType.LAND then +isValid=true +break +elseif dist<=step then +break +end +retries=retries+1 +end +end +if isValid then +unit.x=pos.x +if unit.z then +unit.z=pos.y +else +unit.y=pos.y +end +end +end +end +end +function UTILS.ValidateAndRepositionStatic(Country,Category,Type,Position,ShapeName,MaxRadius) +local coord=COORDINATE:NewFromVec2(Position) +local st=SPAWNSTATIC:NewFromType(Type,Category,Country) +if ShapeName then +st:InitShape(ShapeName) +end +local sName="s-"..timer.getTime().."-"..math.random(1,10000) +local tempStatic=st:SpawnFromCoordinate(coord,0,sName) +if tempStatic then +local sRadius=tempStatic:GetBoundingRadius(2)or 3 +tempStatic:Destroy() +sRadius=sRadius*0.5 +MaxRadius=MaxRadius or math.max(sRadius*10,100) +local positions=UTILS.GetSimpleZones(coord:GetVec3(),MaxRadius,sRadius,20) +if positions and#positions>0 then +local closestSpot +local closestDist=math.huge +for _,spot in pairs(positions)do +if land.getSurfaceType(spot)==land.SurfaceType.LAND then +local dist=UTILS.VecDist2D(Position,spot) +if dist=sRadius then +return closestSpot +else +return Position +end +end +end +end +return nil +end +PROFILER={ +ClassName="PROFILER", +Counters={}, +dInfo={}, +fTime={}, +fTimeTotal={}, +eventHandler={}, +logUnknown=false, +ThreshCPS=0.0, +ThreshTtot=0.005, +fileNamePrefix="MooseProfiler", +fileNameSuffix="txt" +} +function PROFILER.Start(Delay,Duration) +local go=true +if not os then +env.error("ERROR: Profiler needs os to be de-sanitized!") +go=false +end +if not io then +env.error("ERROR: Profiler needs io to be desanitized!") +go=false +end +if not lfs then +env.error("ERROR: Profiler needs lfs to be desanitized!") +go=false +end +if not go then +return +end +if Delay and Delay>0 then +BASE:ScheduleOnce(Delay,PROFILER.Start,0,Duration) +else +PROFILER.TstartGame=timer.getTime() +PROFILER.TstartOS=os.clock() +world.addEventHandler(PROFILER.eventHandler) +env.info('############################ Profiler Started ############################') +if Duration then +env.info(string.format("- Will be running for %d seconds",Duration)) +else +env.info(string.format("- Will be stopped when mission ends")) +end +env.info(string.format("- Calls per second threshold %.3f/sec",PROFILER.ThreshCPS)) +env.info(string.format("- Total function time threshold %.3f sec",PROFILER.ThreshTtot)) +env.info(string.format("- Output file \"%s\" in your DCS log file folder",PROFILER.getfilename(PROFILER.fileNameSuffix))) +env.info(string.format("- Output file \"%s\" in CSV format",PROFILER.getfilename("csv"))) +env.info('###############################################################################') +local duration=Duration or 600 +trigger.action.outText("### Profiler running ###",duration) +debug.sethook(PROFILER.hook,"cr") +if Duration then +PROFILER.Stop(Duration) +end +end +end +function PROFILER.Stop(Delay) +if Delay and Delay>0 then +BASE:ScheduleOnce(Delay,PROFILER.Stop) +end +end +function PROFILER.Stop(Delay) +if Delay and Delay>0 then +BASE:ScheduleOnce(Delay,PROFILER.Stop) +else +debug.sethook() +local runTimeGame=timer.getTime()-PROFILER.TstartGame +local runTimeOS=os.clock()-PROFILER.TstartOS +PROFILER.showInfo(runTimeGame,runTimeOS) +end +end +function PROFILER.eventHandler:onEvent(event) +if event.id==world.event.S_EVENT_MISSION_END then +PROFILER.Stop() +end +end +function PROFILER.hook(event) +local f=debug.getinfo(2,"f").func +if event=='call'then +if PROFILER.Counters[f]==nil then +PROFILER.Counters[f]=1 +PROFILER.dInfo[f]=debug.getinfo(2,"Sn") +if PROFILER.fTimeTotal[f]==nil then +PROFILER.fTimeTotal[f]=0 +end +else +PROFILER.Counters[f]=PROFILER.Counters[f]+1 +end +if PROFILER.fTime[f]==nil then +PROFILER.fTime[f]=os.clock() +end +elseif(event=='return')then +if PROFILER.fTime[f]~=nil then +PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f]) +PROFILER.fTime[f]=nil +end +end +end +function PROFILER.getData(func) +local n=PROFILER.dInfo[func] +if n.what=="C"then +return n.name,"?","?",PROFILER.fTimeTotal[func] +end +return n.name,n.short_src,n.linedefined,PROFILER.fTimeTotal[func] +end +function PROFILER._flog(f,txt) +f:write(txt.."\r\n") +end +function PROFILER.showTable(data,f,runTimeGame) +for i=1,#data do +local t=data[i] +local cps=t.count/runTimeGame +local threshCPS=cps>=PROFILER.ThreshCPS +local threshTot=t.tm>=PROFILER.ThreshTtot +if threshCPS and threshTot then +local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line)) +PROFILER._flog(f,text) +end +end +end +function PROFILER.printCSV(data,runTimeGame) +local file=PROFILER.getfilename("csv") +local g=io.open(file,'w') +local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number," +g:write(text.."\r\n") +for i=1,#data do +local t=data[i] +local cps=t.count/runTimeGame +local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line)) +g:write(txt.."\r\n") +end +g:close() +end +function PROFILER.getfilename(ext) +local dir=lfs.writedir()..[[Logs\]] +ext=ext or PROFILER.fileNameSuffix +local file=dir..PROFILER.fileNamePrefix.."."..ext +if not UTILS.FileExists(file)then +return file +end +for i=1,999 do +local file=string.format("%s%s-%03d.%s",dir,PROFILER.fileNamePrefix,i,ext) +if not UTILS.FileExists(file)then +return file +end +end +end +function PROFILER.showInfo(runTimeGame,runTimeOS) +local file=PROFILER.getfilename(PROFILER.fileNameSuffix) +local f=io.open(file,'w') +local Ttot=0 +local Calls=0 +local t={} +local tcopy=nil +local tserialize=nil +local tforgen=nil +local tpairs=nil +for func,count in pairs(PROFILER.Counters)do +local s,src,line,tm=PROFILER.getData(func) +if PROFILER.logUnknown==true then +if s==nil then s=""end +end +if s~=nil then +local T= +{func=s, +src=src, +line=line, +count=count, +tm=tm, +} +if s=="_copy"then +if tcopy==nil then +tcopy=T +else +tcopy.count=tcopy.count+T.count +tcopy.tm=tcopy.tm+T.tm +end +elseif s=="_Serialize"then +if tserialize==nil then +tserialize=T +else +tserialize.count=tserialize.count+T.count +tserialize.tm=tserialize.tm+T.tm +end +elseif s=="(for generator)"then +if tforgen==nil then +tforgen=T +else +tforgen.count=tforgen.count+T.count +tforgen.tm=tforgen.tm+T.tm +end +elseif s=="pairs"then +if tpairs==nil then +tpairs=T +else +tpairs.count=tpairs.count+T.count +tpairs.tm=tpairs.tm+T.tm +end +else +table.insert(t,T) +end +Ttot=Ttot+tm +Calls=Calls+count +end +end +if tcopy then +table.insert(t,tcopy) +end +if tserialize then +table.insert(t,tserialize) +end +if tforgen then +table.insert(t,tforgen) +end +if tpairs then +table.insert(t,tpairs) +end +env.info('############################ Profiler Stopped ############################') +env.info(string.format("* Runtime Game : %s = %d sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame)) +env.info(string.format("* Runtime Real : %s = %d sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS)) +env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100)) +env.info(string.format("* Total functions : %d",#t)) +env.info(string.format("* Total func calls : %d",Calls)) +env.info(string.format("* Writing to file : \"%s\"",file)) +env.info(string.format("* Writing to file : \"%s\"",PROFILER.getfilename("csv"))) +env.info("##############################################################################") +table.sort(t,function(a,b)return a.tm>b.tm end) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"") +PROFILER._flog(f,"-------------------------") +PROFILER._flog(f,"---- Profiler Report ----") +PROFILER._flog(f,"-------------------------") +PROFILER._flog(f,"") +PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame)) +PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS)) +PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100)) +PROFILER._flog(f,"") +PROFILER._flog(f,string.format("* Total functions = %d",#t)) +PROFILER._flog(f,string.format("* Total func calls = %d",Calls)) +PROFILER._flog(f,"") +PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec",PROFILER.ThreshCPS)) +PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec",PROFILER.ThreshTtot)) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"") +PROFILER.showTable(t,f,runTimeGame) +table.sort(t,function(a,b)return a.tm/a.count>b.tm/b.count end) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"") +PROFILER._flog(f,"--------------------------------------") +PROFILER._flog(f,"---- Data Sorted by Time per Call ----") +PROFILER._flog(f,"--------------------------------------") +PROFILER._flog(f,"") +PROFILER.showTable(t,f,runTimeGame) +table.sort(t,function(a,b)return a.count>b.count end) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"") +PROFILER._flog(f,"------------------------------------") +PROFILER._flog(f,"---- Data Sorted by Total Calls ----") +PROFILER._flog(f,"------------------------------------") +PROFILER._flog(f,"") +PROFILER.showTable(t,f,runTimeGame) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"************************************************************************************************************************") +f:close() +PROFILER.printCSV(t,runTimeGame) +end +do +FIFO={ +ClassName="FIFO", +lid="", +version="0.0.5", +counter=0, +pointer=0, +stackbypointer={}, +stackbyid={} +} +function FIFO:New() +local self=BASE:Inherit(self,BASE:New()) +self.pointer=0 +self.counter=0 +self.stackbypointer={} +self.stackbyid={} +self.uniquecounter=0 +self.lid=string.format("%s (%s) | ","FiFo",self.version) +self:T(self.lid.."Created.") +return self +end +function FIFO:Clear() +self:T(self.lid.."Clear") +self.pointer=0 +self.counter=0 +self.stackbypointer=nil +self.stackbyid=nil +self.stackbypointer={} +self.stackbyid={} +self.uniquecounter=0 +return self +end +function FIFO:Push(Object,UniqueID) +self:T(self.lid.."Push") +self:T({Object,UniqueID}) +self.pointer=self.pointer+1 +self.counter=self.counter+1 +local uniID=UniqueID +if not UniqueID then +self.uniquecounter=self.uniquecounter+1 +uniID=self.uniquecounter +end +self.stackbyid[uniID]={pointer=self.pointer,data=Object,uniqueID=uniID} +self.stackbypointer[self.pointer]={pointer=self.pointer,data=Object,uniqueID=uniID} +return self +end +function FIFO:Pull() +self:T(self.lid.."Pull") +if self.counter==0 then return nil end +local object=self.stackbypointer[1].data +self.stackbypointer[1]=nil +self.counter=self.counter-1 +self:Flatten() +return object +end +function FIFO:PullByPointer(Pointer) +self:T(self.lid.."PullByPointer "..tostring(Pointer)) +if self.counter==0 then return nil end +local object=self.stackbypointer[Pointer] +self.stackbypointer[Pointer]=nil +if object then self.stackbyid[object.uniqueID]=nil end +self.counter=self.counter-1 +self:Flatten() +if object then +return object.data +else +return nil +end +end +function FIFO:ReadByPointer(Pointer) +self:T(self.lid.."ReadByPointer "..tostring(Pointer)) +if self.counter==0 or not Pointer or not self.stackbypointer[Pointer]then return nil end +local object=self.stackbypointer[Pointer] +if object then +return object.data +else +return nil +end +end +function FIFO:ReadByID(UniqueID) +self:T(self.lid.."ReadByID "..tostring(UniqueID)) +if self.counter==0 or not UniqueID or not self.stackbyid[UniqueID]then return nil end +local object=self.stackbyid[UniqueID] +if object then +return object.data +else +return nil +end +end +function FIFO:PullByID(UniqueID) +self:T(self.lid.."PullByID "..tostring(UniqueID)) +if self.counter==0 then return nil end +local object=self.stackbyid[UniqueID] +if object then +return self:PullByPointer(object.pointer) +else +return nil +end +end +function FIFO:Flatten() +self:T(self.lid.."Flatten") +local pointerstack={} +local idstack={} +local counter=0 +for _ID,_entry in pairs(self.stackbypointer)do +counter=counter+1 +pointerstack[counter]={pointer=counter,data=_entry.data,uniqueID=_entry.uniqueID} +end +for _ID,_entry in pairs(pointerstack)do +idstack[_entry.uniqueID]={pointer=_entry.pointer,data=_entry.data,uniqueID=_entry.uniqueID} +end +self.stackbypointer=nil +self.stackbypointer=pointerstack +self.stackbyid=nil +self.stackbyid=idstack +self.counter=counter +self.pointer=counter +return self +end +function FIFO:IsEmpty() +self:T(self.lid.."IsEmpty") +return self.counter==0 and true or false +end +function FIFO:GetSize() +self:T(self.lid.."GetSize") +return self.counter +end +function FIFO:Count() +self:T(self.lid.."Count") +return self.counter +end +function FIFO:IsNotEmpty() +self:T(self.lid.."IsNotEmpty") +return not self:IsEmpty() +end +function FIFO:GetPointerStack() +self:T(self.lid.."GetPointerStack") +return self.stackbypointer +end +function FIFO:HasUniqueID(UniqueID) +self:T(self.lid.."HasUniqueID") +if self.stackbyid[UniqueID]~=nil then +return true +else +return false +end +end +function FIFO:GetIDStack() +self:T(self.lid.."GetIDStack") +return self.stackbyid +end +function FIFO:GetIDStackSorted() +self:T(self.lid.."GetIDStackSorted") +local stack=self:GetIDStack() +local idstack={} +for _id,_entry in pairs(stack)do +idstack[#idstack+1]=_id +self:T({"pre",_id}) +end +local function sortID(a,b) +return a=1 then +self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:F2(Arguments) +if BASE.Debug and _TraceOnOff==true and _TraceLevel>=2 then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=2 then +self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:F3(Arguments) +if BASE.Debug and _TraceOnOff==true and _TraceLevel>=3 then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=3 then +self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:_T(Arguments,DebugInfoCurrentParam,DebugInfoFromParam) +if BASE.Debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then +local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo(3,"l") +local Function="function" +if DebugInfoCurrent.name then +Function=DebugInfoCurrent.name +end +if _TraceAll==true or _TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName].Method[Function]then +local LineCurrent=0 +if DebugInfoCurrent.currentline then +LineCurrent=DebugInfoCurrent.currentline +end +local LineFrom=0 +if DebugInfoFrom then +LineFrom=DebugInfoFrom.currentline +end +env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s",LineCurrent,LineFrom,"T",self.ClassName,self.ClassID,BASE:_Serialize(Arguments))) +end +end +end +function BASE:T(Arguments) +if BASE.Debug and _TraceOnOff==true then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=1 then +self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:T2(Arguments) +if BASE.Debug and _TraceOnOff==true and _TraceLevel>=2 then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=2 then +self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:T3(Arguments) +if BASE.Debug and _TraceOnOff==true and _TraceLevel>=3 then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=3 then +self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:E(Arguments) +if BASE.Debug then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +local Function="function" +if DebugInfoCurrent.name then +Function=DebugInfoCurrent.name +end +local LineCurrent=DebugInfoCurrent.currentline +local LineFrom=-1 +if DebugInfoFrom then +LineFrom=DebugInfoFrom.currentline +end +env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"E",self.ClassName,self.ClassID,Function,UTILS.BasicSerialize(Arguments))) +else +env.info(string.format("%1s:%30s%05d(%s)","E",self.ClassName,self.ClassID,UTILS.BasicSerialize(Arguments))) +end +end +function BASE:I(Arguments) +if BASE.Debug then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +local Function="function" +if DebugInfoCurrent.name then +Function=DebugInfoCurrent.name +end +local LineCurrent=DebugInfoCurrent.currentline +local LineFrom=-1 +if DebugInfoFrom then +LineFrom=DebugInfoFrom.currentline +end +env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"I",self.ClassName,self.ClassID,Function,UTILS.BasicSerialize(Arguments))) +else +env.info(string.format("%1s:%30s%05d(%s)","I",self.ClassName,self.ClassID,UTILS.BasicSerialize(Arguments))) +end +end +ASTAR={ +ClassName="ASTAR", +Debug=nil, +lid=nil, +nodes={}, +counter=1, +Nnodes=0, +ncost=0, +ncostcache=0, +nvalid=0, +nvalidcache=0, +} +ASTAR.INF=1/0 +ASTAR.version="0.4.0" +function ASTAR:New() +local self=BASE:Inherit(self,BASE:New()) +self.lid="ASTAR | " +return self +end +function ASTAR:SetStartCoordinate(Coordinate) +self.startCoord=Coordinate +return self +end +function ASTAR:SetEndCoordinate(Coordinate) +self.endCoord=Coordinate +return self +end +function ASTAR:GetNodeFromCoordinate(Coordinate) +local node={} +node.coordinate=Coordinate +node.surfacetype=Coordinate:GetSurfaceType() +node.id=self.counter +node.valid={} +node.cost={} +self.counter=self.counter+1 +return node +end +function ASTAR:AddNode(Node) +self.nodes[Node.id]=Node +self.Nnodes=self.Nnodes+1 +return self +end +function ASTAR:AddNodeFromCoordinate(Coordinate) +local node=self:GetNodeFromCoordinate(Coordinate) +self:AddNode(node) +return node +end +function ASTAR:CheckValidSurfaceType(Node,SurfaceTypes) +if SurfaceTypes then +if type(SurfaceTypes)~="table"then +SurfaceTypes={SurfaceTypes} +end +for _,surface in pairs(SurfaceTypes)do +if surface==Node.surfacetype then +return true +end +end +return false +else +return true +end +end +function ASTAR:SetValidNeighbourFunction(NeighbourFunction,...) +self.ValidNeighbourFunc=NeighbourFunction +self.ValidNeighbourArg={} +if arg then +self.ValidNeighbourArg=arg +end +return self +end +function ASTAR:SetValidNeighbourLoS(CorridorWidth) +self:SetValidNeighbourFunction(ASTAR.LoS,CorridorWidth) +return self +end +function ASTAR:SetValidNeighbourDistance(MaxDistance) +self:SetValidNeighbourFunction(ASTAR.DistMax,MaxDistance) +return self +end +function ASTAR:SetValidNeighbourRoad(MaxDistance) +self:SetValidNeighbourFunction(ASTAR.Road,MaxDistance) +return self +end +function ASTAR:SetCostFunction(CostFunction,...) +self.CostFunc=CostFunction +self.CostArg={} +if arg then +self.CostArg=arg +end +return self +end +function ASTAR:SetCostDist2D() +self:SetCostFunction(ASTAR.Dist2D) +return self +end +function ASTAR:SetCostDist3D() +self:SetCostFunction(ASTAR.Dist3D) +return self +end +function ASTAR:SetCostRoad() +self:SetCostFunction(ASTAR) +return self +end +function ASTAR:CreateGrid(ValidSurfaceTypes,BoxHY,SpaceX,deltaX,deltaY,MarkGrid) +local Dz=SpaceX or 10000 +local Dx=BoxHY and BoxHY/2 or 20000 +local dz=deltaX or 2000 +local dx=deltaY or dz +local angle=self.startCoord:HeadingTo(self.endCoord) +local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz +local co=COORDINATE:New(0,0,0) +local do1=co:Get2DDistance(self.startCoord) +local ho1=co:HeadingTo(self.startCoord) +local xmin=-Dx +local zmin=-Dz +local nz=dist/dz+1 +local nx=2*Dx/dx+1 +local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes",nx,nz,nx*nz) +self:T(self.lid..text) +for i=1,nx do +local x=xmin+dx*(i-1) +for j=1,nz do +local z=zmin+dz*(j-1) +local vec3=UTILS.Rotate2D({x=x,y=0,z=z},angle) +local c=COORDINATE:New(vec3.z,vec3.y,vec3.x):Translate(do1,ho1,true) +local node=self:GetNodeFromCoordinate(c) +if self:CheckValidSurfaceType(node,ValidSurfaceTypes)then +if MarkGrid then +c:MarkToAll(string.format("i=%d, j=%d surface=%d",i,j,node.surfacetype)) +end +self:AddNode(node) +end +end +end +local text=string.format("Done building grid!") +self:T2(self.lid..text) +return self +end +function ASTAR.LoS(nodeA,nodeB,corridor) +local offset=1 +local dx=corridor and corridor/2 or nil +local dy=dx +local cA=nodeA.coordinate:GetVec3() +local cB=nodeB.coordinate:GetVec3() +cA.y=offset +cB.y=offset +local los=land.isVisible(cA,cB) +if los and corridor then +local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate) +local Ap=UTILS.VecTranslate(cA,dx,heading+90) +local Bp=UTILS.VecTranslate(cB,dx,heading+90) +los=land.isVisible(Ap,Bp) +if los then +local Am=UTILS.VecTranslate(cA,dx,heading-90) +local Bm=UTILS.VecTranslate(cB,dx,heading-90) +los=land.isVisible(Am,Bm) +end +end +return los +end +function ASTAR.Road(nodeA,nodeB) +local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z) +if path then +return true +else +return false +end +end +function ASTAR.DistMax(nodeA,nodeB,distmax) +distmax=distmax or 2000 +local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate) +return dist<=distmax +end +function ASTAR.Dist2D(nodeA,nodeB) +local dist=nodeA.coordinate:Get2DDistance(nodeB) +return dist +end +function ASTAR.Dist3D(nodeA,nodeB) +local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate) +return dist +end +function ASTAR.DistRoad(nodeA,nodeB) +local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z) +if path then +local dist=0 +for i=2,#path do +local b=path[i] +local a=path[i-1] +dist=dist+UTILS.VecDist2D(a,b) +end +return dist +end +return math.huge +end +function ASTAR:FindClosestNode(Coordinate) +local distMin=math.huge +local closeNode=nil +for _,_node in pairs(self.nodes)do +local node=_node +local dist=node.coordinate:Get2DDistance(Coordinate) +if dist1000 then +self:T(self.lid.."Adding start node to node grid!") +self:AddNode(node) +end +return self +end +function ASTAR:FindEndNode() +local node,dist=self:FindClosestNode(self.endCoord) +self.endNode=node +if dist>1000 then +self:T(self.lid.."Adding end node to node grid!") +self:AddNode(node) +end +return self +end +function ASTAR:GetPath(ExcludeStartNode,ExcludeEndNode) +self:FindStartNode() +self:FindEndNode() +local nodes=self.nodes +local start=self.startNode +local goal=self.endNode +local openset={} +local closedset={} +local came_from={} +local g_score={} +local f_score={} +openset[start.id]=true +local Nopen=1 +g_score[start.id]=0 +f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start,goal) +local T0=timer.getAbsTime() +local text=string.format("Starting A* pathfinding with %d Nodes",self.Nnodes) +self:T(self.lid..text) +local Tstart=UTILS.GetOSTime() +while Nopen>0 do +local current=self:_LowestFscore(openset,f_score) +if current.id==goal.id then +local path=self:_UnwindPath({},came_from,goal) +if not ExcludeEndNode then +table.insert(path,goal) +end +if ExcludeStartNode then +table.remove(path,1) +end +local Tstop=UTILS.GetOSTime() +local dT=nil +if Tstart and Tstop then +dT=Tstop-Tstart +end +local text=string.format("Found path with %d nodes (%d total)",#path,self.Nnodes) +if dT then +text=text..string.format(", OS Time %.6f sec",dT) +end +text=text..string.format(", Nvalid=%d [%d cached]",self.nvalid,self.nvalidcache) +text=text..string.format(", Ncost=%d [%d cached]",self.ncost,self.ncostcache) +self:T(self.lid..text) +return path +end +openset[current.id]=nil +Nopen=Nopen-1 +closedset[current.id]=true +local neighbors=self:_NeighbourNodes(current,nodes) +for _,neighbor in pairs(neighbors)do +if self:_NotIn(closedset,neighbor.id)then +local tentative_g_score=g_score[current.id]+self:_DistNodes(current,neighbor) +if self:_NotIn(openset,neighbor.id)or tentative_g_score result=%s",tostring(isGen),tostring(isAny),tostring(isAll),tostring(self.negateResult),tostring(result))) +return result +end +function CONDITION:_EvalConditionsAll(functions) +local gotone=false +for _,_condition in pairs(functions or{})do +local condition=_condition +gotone=true +local istrue=condition.func(unpack(condition.arg)) +if not istrue then +return false +end +end +return true +end +function CONDITION:_EvalConditionsAny(functions) +local gotone=false +for _,_condition in pairs(functions or{})do +local condition=_condition +gotone=true +local istrue=condition.func(unpack(condition.arg)) +if istrue then +return true +end +end +if gotone then +return false +else +return true +end +end +function CONDITION:_CreateCondition(Ftype,Function,...) +self.functionCounter=self.functionCounter+1 +local condition={} +condition.uid=self.functionCounter +condition.type=Ftype or 0 +condition.persistence=self.defaultPersist +condition.func=Function +condition.arg={} +if arg then +condition.arg=arg +end +return condition +end +function CONDITION.IsTimeGreater(Time,Absolute) +local Tnow=nil +if Absolute then +Tnow=timer.getAbsTime() +else +Tnow=timer.getTime() +end +if Tnow>Time then +return true +else +return false +end +return nil +end +function CONDITION.IsRandomSuccess(Probability) +Probability=Probability or 50 +math.random() +math.random() +math.random() +local N=math.random()*100 +if N0 then +self:ScheduleOnce(Delay,USERFLAG.Set,self,Number) +else +trigger.action.setUserFlag(self.UserFlagName,Number) +end +return self +end +function USERFLAG:Get() +return trigger.misc.getUserFlag(self.UserFlagName) +end +function USERFLAG:Is(Number) +return trigger.misc.getUserFlag(self.UserFlagName)==Number +end +end +REPORT={ +ClassName="REPORT", +Title="", +} +function REPORT:New(Title) +local self=BASE:Inherit(self,BASE:New()) +self.Report={} +self:SetTitle(Title or"") +self:SetIndent(3) +return self +end +function REPORT:HasText() +return#self.Report>0 +end +function REPORT:SetIndent(Indent) +self.Indent=Indent +return self +end +function REPORT:Add(Text) +self.Report[#self.Report+1]=Text +return self +end +function REPORT:AddIndent(Text,Separator) +self.Report[#self.Report+1]=((Separator and Separator..string.rep(" ",self.Indent-1))or string.rep(" ",self.Indent))..Text:gsub("\n","\n"..string.rep(" ",self.Indent)) +return self +end +function REPORT:Text(Delimiter) +Delimiter=Delimiter or"\n" +local ReportText=(self.Title~=""and self.Title..Delimiter or self.Title)..table.concat(self.Report,Delimiter)or"" +return ReportText +end +function REPORT:SetTitle(Title) +self.Title=Title +return self +end +function REPORT:GetCount() +return#self.Report +end +SCHEDULER={ +ClassName="SCHEDULER", +Schedules={}, +MasterObject=nil, +ShowTrace=nil, +} +function SCHEDULER:New(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop) +local self=BASE:Inherit(self,BASE:New()) +self:F2({Start,Repeat,RandomizeFactor,Stop}) +local ScheduleID=nil +self.MasterObject=MasterObject +self.ShowTrace=false +if SchedulerFunction then +ScheduleID=self:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,3) +end +return self,ScheduleID +end +function SCHEDULER:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,TraceLevel,Fsm) +self:F2({Start,Repeat,RandomizeFactor,Stop}) +self:T3({SchedulerArguments}) +local ObjectName="-" +if MasterObject and MasterObject.ClassName and MasterObject.ClassID then +ObjectName=MasterObject.ClassName..MasterObject.ClassID +end +self:F3({"Schedule :",ObjectName,tostring(MasterObject),Start,Repeat,RandomizeFactor,Stop}) +self.MasterObject=MasterObject +local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(self, +SchedulerFunction, +SchedulerArguments, +Start, +Repeat, +RandomizeFactor, +Stop, +TraceLevel or 3, +Fsm +) +self.Schedules[#self.Schedules+1]=ScheduleID +return ScheduleID +end +function SCHEDULER:Start(ScheduleID) +self:F3({ScheduleID}) +self:T(string.format("Starting scheduler ID=%s",tostring(ScheduleID))) +_SCHEDULEDISPATCHER:Start(self,ScheduleID) +end +function SCHEDULER:Stop(ScheduleID) +self:F3({ScheduleID}) +self:T(string.format("Stopping scheduler ID=%s",tostring(ScheduleID))) +_SCHEDULEDISPATCHER:Stop(self,ScheduleID) +end +function SCHEDULER:Remove(ScheduleID) +self:F3({ScheduleID}) +self:T(string.format("Removing scheduler ID=%s",tostring(ScheduleID))) +_SCHEDULEDISPATCHER:RemoveSchedule(self,ScheduleID) +end +function SCHEDULER:Clear() +self:F3() +self:T(string.format("Clearing scheduler")) +_SCHEDULEDISPATCHER:Clear(self) +end +function SCHEDULER:ShowTrace() +_SCHEDULEDISPATCHER:ShowTrace(self) +end +function SCHEDULER:NoTrace() +_SCHEDULEDISPATCHER:NoTrace(self) +end +SCHEDULEDISPATCHER={ +ClassName="SCHEDULEDISPATCHER", +CallID=0, +PersistentSchedulers={}, +ObjectSchedulers={}, +Schedule=nil, +} +function SCHEDULEDISPATCHER:New() +local self=BASE:Inherit(self,BASE:New()) +self:F3() +return self +end +function SCHEDULEDISPATCHER:AddSchedule(Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm) +self:F2({Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm}) +self.CallID=self.CallID+1 +local CallID=self.CallID.."#"..(Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID()or"")or"" +self:T2(string.format("Adding schedule #%d CallID=%s",self.CallID,CallID)) +self.PersistentSchedulers=self.PersistentSchedulers or{} +self.ObjectSchedulers=self.ObjectSchedulers or setmetatable({},{__mode="v"}) +if Scheduler.MasterObject then +self.ObjectSchedulers[CallID]=Scheduler +self:F3({CallID=CallID,ObjectScheduler=tostring(self.ObjectSchedulers[CallID]),MasterObject=tostring(Scheduler.MasterObject)}) +else +self.PersistentSchedulers[CallID]=Scheduler +self:F3({CallID=CallID,PersistentScheduler=self.PersistentSchedulers[CallID]}) +end +self.Schedule=self.Schedule or setmetatable({},{__mode="k"}) +self.Schedule[Scheduler]=self.Schedule[Scheduler]or{} +self.Schedule[Scheduler][CallID]={} +self.Schedule[Scheduler][CallID].Function=ScheduleFunction +self.Schedule[Scheduler][CallID].Arguments=ScheduleArguments +self.Schedule[Scheduler][CallID].StartTime=timer.getTime()+(Start or 0) +self.Schedule[Scheduler][CallID].Start=Start+0.001 +self.Schedule[Scheduler][CallID].Repeat=Repeat or 0 +self.Schedule[Scheduler][CallID].Randomize=Randomize or 0 +self.Schedule[Scheduler][CallID].Stop=Stop +local Info={} +if debug then +TraceLevel=TraceLevel or 2 +Info=debug.getinfo(TraceLevel,"nlS") +local name_fsm=debug.getinfo(TraceLevel-1,"n").name +if name_fsm then +Info.name=name_fsm +end +end +self:T3(self.Schedule[Scheduler][CallID]) +self.Schedule[Scheduler][CallID].CallHandler=function(Params) +local CallID=Params.CallID +local Info=Params.Info or{} +local Source=Info.source or"?" +local Line=Info.currentline or"?" +local Name=Info.name or"?" +local ErrorHandler=function(errmsg) +env.info("Error in timer function: "..errmsg or"") +if BASE.Debug~=nil then +env.info(BASE.Debug.traceback()) +end +return errmsg +end +local Scheduler=self.ObjectSchedulers[CallID] +if not Scheduler then +Scheduler=self.PersistentSchedulers[CallID] +end +if Scheduler then +local MasterObject=tostring(Scheduler.MasterObject) +local Schedule=self.Schedule[Scheduler][CallID] +local SchedulerObject=Scheduler.MasterObject +local ShowTrace=Scheduler.ShowTrace +local ScheduleFunction=Schedule.Function +local ScheduleArguments=Schedule.Arguments or{} +local Start=Schedule.Start +local Repeat=Schedule.Repeat or 0 +local Randomize=Schedule.Randomize or 0 +local Stop=Schedule.Stop or 0 +local ScheduleID=Schedule.ScheduleID +local Prefix=(Repeat==0)and"--->"or"+++>" +local Status,Result +if SchedulerObject then +local function Timer() +if ShowTrace then +SchedulerObject:T(Prefix..Name..":"..Line.." ("..Source..")") +end +return ScheduleFunction(SchedulerObject,unpack(ScheduleArguments)) +end +Status,Result=xpcall(Timer,ErrorHandler) +else +local function Timer() +if ShowTrace then +self:T(Prefix..Name..":"..Line.." ("..Source..")") +end +return ScheduleFunction(unpack(ScheduleArguments)) +end +Status,Result=xpcall(Timer,ErrorHandler) +end +local CurrentTime=timer.getTime() +local StartTime=Schedule.StartTime +self:F3({CallID=CallID,ScheduleID=ScheduleID,Master=MasterObject,CurrentTime=CurrentTime,StartTime=StartTime,Start=Start,Repeat=Repeat,Randomize=Randomize,Stop=Stop}) +if Status and((Result==nil)or(Result and Result~=false))then +if Repeat~=0 and((Stop==0)or(Stop~=0 and CurrentTime<=StartTime+Stop))then +local ScheduleTime=CurrentTime+Repeat+math.random(-(Randomize*Repeat/2),(Randomize*Repeat/2))+0.0001 +return ScheduleTime +else +self:Stop(Scheduler,CallID) +end +else +self:Stop(Scheduler,CallID) +end +else +self:I("<<<>"..Name..":"..Line.." ("..Source..")") +end +return nil +end +self:Start(Scheduler,CallID,Info) +return CallID +end +function SCHEDULEDISPATCHER:RemoveSchedule(Scheduler,CallID) +self:F2({Remove=CallID,Scheduler=Scheduler}) +if CallID then +self:Stop(Scheduler,CallID) +self.Schedule[Scheduler][CallID]=nil +end +end +function SCHEDULEDISPATCHER:Start(Scheduler,CallID,Info) +self:F2({Start=CallID,Scheduler=Scheduler}) +if CallID then +local Schedule=self.Schedule[Scheduler][CallID] +if not Schedule.ScheduleID then +local Tnow=timer.getTime() +Schedule.StartTime=Tnow +Schedule.ScheduleID=timer.scheduleFunction(Schedule.CallHandler,{CallID=CallID,Info=Info},Tnow+Schedule.Start) +self:T(string.format("Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s",tostring(CallID),tostring(Schedule.ScheduleID))) +end +else +for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do +self:Start(Scheduler,CallID,Info) +end +end +end +function SCHEDULEDISPATCHER:Stop(Scheduler,CallID) +self:F2({Stop=CallID,Scheduler=Scheduler}) +if CallID then +local Schedule=self.Schedule[Scheduler][CallID] +if Schedule and Schedule.ScheduleID then +self:T(string.format("SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s",tostring(CallID),tostring(Schedule.ScheduleID))) +timer.removeFunction(Schedule.ScheduleID) +Schedule.ScheduleID=nil +else +self:T(string.format("Error no ScheduleID for CallID=%s",tostring(CallID))) +end +else +for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do +self:Stop(Scheduler,CallID) +end +end +end +function SCHEDULEDISPATCHER:Clear(Scheduler) +self:F2({Scheduler=Scheduler}) +for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do +self:Stop(Scheduler,CallID) +end +end +function SCHEDULEDISPATCHER:ShowTrace(Scheduler) +self:F2({Scheduler=Scheduler}) +Scheduler.ShowTrace=true +end +function SCHEDULEDISPATCHER:NoTrace(Scheduler) +self:F2({Scheduler=Scheduler}) +Scheduler.ShowTrace=false +end +EVENT={ +ClassName="EVENT", +ClassID=0, +MissionEnd=false, +} +world.event.S_EVENT_NEW_CARGO=world.event.S_EVENT_MAX+1000 +world.event.S_EVENT_DELETE_CARGO=world.event.S_EVENT_MAX+1001 +world.event.S_EVENT_NEW_ZONE=world.event.S_EVENT_MAX+1002 +world.event.S_EVENT_DELETE_ZONE=world.event.S_EVENT_MAX+1003 +world.event.S_EVENT_NEW_ZONE_GOAL=world.event.S_EVENT_MAX+1004 +world.event.S_EVENT_DELETE_ZONE_GOAL=world.event.S_EVENT_MAX+1005 +world.event.S_EVENT_REMOVE_UNIT=world.event.S_EVENT_MAX+1006 +world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT=world.event.S_EVENT_MAX+1007 +world.event.S_EVENT_NEW_DYNAMIC_CARGO=world.event.S_EVENT_MAX+1008 +world.event.S_EVENT_DYNAMIC_CARGO_LOADED=world.event.S_EVENT_MAX+1009 +world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED=world.event.S_EVENT_MAX+1010 +world.event.S_EVENT_DYNAMIC_CARGO_REMOVED=world.event.S_EVENT_MAX+1011 +EVENTS={ +Shot=world.event.S_EVENT_SHOT, +Hit=world.event.S_EVENT_HIT, +Takeoff=world.event.S_EVENT_TAKEOFF, +Land=world.event.S_EVENT_LAND, +Crash=world.event.S_EVENT_CRASH, +Ejection=world.event.S_EVENT_EJECTION, +Refueling=world.event.S_EVENT_REFUELING, +Dead=world.event.S_EVENT_DEAD, +PilotDead=world.event.S_EVENT_PILOT_DEAD, +BaseCaptured=world.event.S_EVENT_BASE_CAPTURED, +MissionStart=world.event.S_EVENT_MISSION_START, +MissionEnd=world.event.S_EVENT_MISSION_END, +TookControl=world.event.S_EVENT_TOOK_CONTROL, +RefuelingStop=world.event.S_EVENT_REFUELING_STOP, +Birth=world.event.S_EVENT_BIRTH, +HumanFailure=world.event.S_EVENT_HUMAN_FAILURE, +EngineStartup=world.event.S_EVENT_ENGINE_STARTUP, +EngineShutdown=world.event.S_EVENT_ENGINE_SHUTDOWN, +PlayerEnterUnit=world.event.S_EVENT_PLAYER_ENTER_UNIT, +PlayerLeaveUnit=world.event.S_EVENT_PLAYER_LEAVE_UNIT, +PlayerComment=world.event.S_EVENT_PLAYER_COMMENT, +ShootingStart=world.event.S_EVENT_SHOOTING_START, +ShootingEnd=world.event.S_EVENT_SHOOTING_END, +MarkAdded=world.event.S_EVENT_MARK_ADDED, +MarkChange=world.event.S_EVENT_MARK_CHANGE, +MarkRemoved=world.event.S_EVENT_MARK_REMOVED, +NewCargo=world.event.S_EVENT_NEW_CARGO, +DeleteCargo=world.event.S_EVENT_DELETE_CARGO, +NewZone=world.event.S_EVENT_NEW_ZONE, +DeleteZone=world.event.S_EVENT_DELETE_ZONE, +NewZoneGoal=world.event.S_EVENT_NEW_ZONE_GOAL, +DeleteZoneGoal=world.event.S_EVENT_DELETE_ZONE_GOAL, +RemoveUnit=world.event.S_EVENT_REMOVE_UNIT, +PlayerEnterAircraft=world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT, +DetailedFailure=world.event.S_EVENT_DETAILED_FAILURE or-1, +Kill=world.event.S_EVENT_KILL or-1, +Score=world.event.S_EVENT_SCORE or-1, +UnitLost=world.event.S_EVENT_UNIT_LOST or-1, +LandingAfterEjection=world.event.S_EVENT_LANDING_AFTER_EJECTION or-1, +ParatrooperLanding=world.event.S_EVENT_PARATROOPER_LENDING or-1, +DiscardChairAfterEjection=world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or-1, +WeaponAdd=world.event.S_EVENT_WEAPON_ADD or-1, +TriggerZone=world.event.S_EVENT_TRIGGER_ZONE or-1, +LandingQualityMark=world.event.S_EVENT_LANDING_QUALITY_MARK or-1, +BDA=world.event.S_EVENT_BDA or-1, +AIAbortMission=world.event.S_EVENT_AI_ABORT_MISSION or-1, +DayNight=world.event.S_EVENT_DAYNIGHT or-1, +FlightTime=world.event.S_EVENT_FLIGHT_TIME or-1, +SelfKillPilot=world.event.S_EVENT_PLAYER_SELF_KILL_PILOT or-1, +PlayerCaptureAirfield=world.event.S_EVENT_PLAYER_CAPTURE_AIRFIELD or-1, +EmergencyLanding=world.event.S_EVENT_EMERGENCY_LANDING or-1, +UnitCreateTask=world.event.S_EVENT_UNIT_CREATE_TASK or-1, +UnitDeleteTask=world.event.S_EVENT_UNIT_DELETE_TASK or-1, +SimulationStart=world.event.S_EVENT_SIMULATION_START or-1, +WeaponRearm=world.event.S_EVENT_WEAPON_REARM or-1, +WeaponDrop=world.event.S_EVENT_WEAPON_DROP or-1, +UnitTaskComplete=world.event.S_EVENT_UNIT_TASK_COMPLETE or-1, +UnitTaskStage=world.event.S_EVENT_UNIT_TASK_STAGE or-1, +MacExtraScore=world.event.S_EVENT_MAC_EXTRA_SCORE or-1, +MissionRestart=world.event.S_EVENT_MISSION_RESTART or-1, +MissionWinner=world.event.S_EVENT_MISSION_WINNER or-1, +RunwayTakeoff=world.event.S_EVENT_RUNWAY_TAKEOFF or-1, +RunwayTouch=world.event.S_EVENT_RUNWAY_TOUCH or-1, +MacLMSRestart=world.event.S_EVENT_MAC_LMS_RESTART or-1, +SimulationFreeze=world.event.S_EVENT_SIMULATION_FREEZE or-1, +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, +NewDynamicCargo=world.event.S_EVENT_NEW_DYNAMIC_CARGO or-1, +DynamicCargoLoaded=world.event.S_EVENT_DYNAMIC_CARGO_LOADED or-1, +DynamicCargoUnloaded=world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED or-1, +DynamicCargoRemoved=world.event.S_EVENT_DYNAMIC_CARGO_REMOVED or-1, +} +local _EVENTMETA={ +[world.event.S_EVENT_SHOT]={ +Order=1, +Side="I", +Event="OnEventShot", +Text="S_EVENT_SHOT" +}, +[world.event.S_EVENT_HIT]={ +Order=1, +Side="T", +Event="OnEventHit", +Text="S_EVENT_HIT" +}, +[world.event.S_EVENT_TAKEOFF]={ +Order=1, +Side="I", +Event="OnEventTakeoff", +Text="S_EVENT_TAKEOFF" +}, +[world.event.S_EVENT_LAND]={ +Order=1, +Side="I", +Event="OnEventLand", +Text="S_EVENT_LAND" +}, +[world.event.S_EVENT_CRASH]={ +Order=-1, +Side="I", +Event="OnEventCrash", +Text="S_EVENT_CRASH" +}, +[world.event.S_EVENT_EJECTION]={ +Order=1, +Side="I", +Event="OnEventEjection", +Text="S_EVENT_EJECTION" +}, +[world.event.S_EVENT_REFUELING]={ +Order=1, +Side="I", +Event="OnEventRefueling", +Text="S_EVENT_REFUELING" +}, +[world.event.S_EVENT_DEAD]={ +Order=-1, +Side="I", +Event="OnEventDead", +Text="S_EVENT_DEAD" +}, +[world.event.S_EVENT_PILOT_DEAD]={ +Order=1, +Side="I", +Event="OnEventPilotDead", +Text="S_EVENT_PILOT_DEAD" +}, +[world.event.S_EVENT_BASE_CAPTURED]={ +Order=1, +Side="I", +Event="OnEventBaseCaptured", +Text="S_EVENT_BASE_CAPTURED" +}, +[world.event.S_EVENT_MISSION_START]={ +Order=1, +Side="N", +Event="OnEventMissionStart", +Text="S_EVENT_MISSION_START" +}, +[world.event.S_EVENT_MISSION_END]={ +Order=1, +Side="N", +Event="OnEventMissionEnd", +Text="S_EVENT_MISSION_END" +}, +[world.event.S_EVENT_TOOK_CONTROL]={ +Order=1, +Side="N", +Event="OnEventTookControl", +Text="S_EVENT_TOOK_CONTROL" +}, +[world.event.S_EVENT_REFUELING_STOP]={ +Order=1, +Side="I", +Event="OnEventRefuelingStop", +Text="S_EVENT_REFUELING_STOP" +}, +[world.event.S_EVENT_BIRTH]={ +Order=1, +Side="I", +Event="OnEventBirth", +Text="S_EVENT_BIRTH" +}, +[world.event.S_EVENT_HUMAN_FAILURE]={ +Order=1, +Side="I", +Event="OnEventHumanFailure", +Text="S_EVENT_HUMAN_FAILURE" +}, +[world.event.S_EVENT_ENGINE_STARTUP]={ +Order=1, +Side="I", +Event="OnEventEngineStartup", +Text="S_EVENT_ENGINE_STARTUP" +}, +[world.event.S_EVENT_ENGINE_SHUTDOWN]={ +Order=1, +Side="I", +Event="OnEventEngineShutdown", +Text="S_EVENT_ENGINE_SHUTDOWN" +}, +[world.event.S_EVENT_PLAYER_ENTER_UNIT]={ +Order=1, +Side="I", +Event="OnEventPlayerEnterUnit", +Text="S_EVENT_PLAYER_ENTER_UNIT" +}, +[world.event.S_EVENT_PLAYER_LEAVE_UNIT]={ +Order=-1, +Side="I", +Event="OnEventPlayerLeaveUnit", +Text="S_EVENT_PLAYER_LEAVE_UNIT" +}, +[world.event.S_EVENT_PLAYER_COMMENT]={ +Order=1, +Side="I", +Event="OnEventPlayerComment", +Text="S_EVENT_PLAYER_COMMENT" +}, +[world.event.S_EVENT_SHOOTING_START]={ +Order=1, +Side="I", +Event="OnEventShootingStart", +Text="S_EVENT_SHOOTING_START" +}, +[world.event.S_EVENT_SHOOTING_END]={ +Order=1, +Side="I", +Event="OnEventShootingEnd", +Text="S_EVENT_SHOOTING_END" +}, +[world.event.S_EVENT_MARK_ADDED]={ +Order=1, +Side="I", +Event="OnEventMarkAdded", +Text="S_EVENT_MARK_ADDED" +}, +[world.event.S_EVENT_MARK_CHANGE]={ +Order=1, +Side="I", +Event="OnEventMarkChange", +Text="S_EVENT_MARK_CHANGE" +}, +[world.event.S_EVENT_MARK_REMOVED]={ +Order=1, +Side="I", +Event="OnEventMarkRemoved", +Text="S_EVENT_MARK_REMOVED" +}, +[EVENTS.NewCargo]={ +Order=1, +Event="OnEventNewCargo", +Text="S_EVENT_NEW_CARGO" +}, +[EVENTS.DeleteCargo]={ +Order=1, +Event="OnEventDeleteCargo", +Text="S_EVENT_DELETE_CARGO" +}, +[EVENTS.NewZone]={ +Order=1, +Event="OnEventNewZone", +Text="S_EVENT_NEW_ZONE" +}, +[EVENTS.DeleteZone]={ +Order=1, +Event="OnEventDeleteZone", +Text="S_EVENT_DELETE_ZONE" +}, +[EVENTS.NewZoneGoal]={ +Order=1, +Event="OnEventNewZoneGoal", +Text="S_EVENT_NEW_ZONE_GOAL" +}, +[EVENTS.DeleteZoneGoal]={ +Order=1, +Event="OnEventDeleteZoneGoal", +Text="S_EVENT_DELETE_ZONE_GOAL" +}, +[EVENTS.RemoveUnit]={ +Order=-1, +Event="OnEventRemoveUnit", +Text="S_EVENT_REMOVE_UNIT" +}, +[EVENTS.PlayerEnterAircraft]={ +Order=1, +Event="OnEventPlayerEnterAircraft", +Text="S_EVENT_PLAYER_ENTER_AIRCRAFT" +}, +[EVENTS.DetailedFailure]={ +Order=1, +Event="OnEventDetailedFailure", +Text="S_EVENT_DETAILED_FAILURE" +}, +[EVENTS.Kill]={ +Order=1, +Event="OnEventKill", +Text="S_EVENT_KILL" +}, +[EVENTS.Score]={ +Order=1, +Event="OnEventScore", +Text="S_EVENT_SCORE" +}, +[EVENTS.UnitLost]={ +Order=1, +Event="OnEventUnitLost", +Text="S_EVENT_UNIT_LOST" +}, +[EVENTS.LandingAfterEjection]={ +Order=1, +Event="OnEventLandingAfterEjection", +Text="S_EVENT_LANDING_AFTER_EJECTION" +}, +[EVENTS.ParatrooperLanding]={ +Order=1, +Event="OnEventParatrooperLanding", +Text="S_EVENT_PARATROOPER_LENDING" +}, +[EVENTS.DiscardChairAfterEjection]={ +Order=1, +Event="OnEventDiscardChairAfterEjection", +Text="S_EVENT_DISCARD_CHAIR_AFTER_EJECTION" +}, +[EVENTS.WeaponAdd]={ +Order=1, +Event="OnEventWeaponAdd", +Text="S_EVENT_WEAPON_ADD" +}, +[EVENTS.TriggerZone]={ +Order=1, +Event="OnEventTriggerZone", +Text="S_EVENT_TRIGGER_ZONE" +}, +[EVENTS.LandingQualityMark]={ +Order=1, +Event="OnEventLandingQualityMark", +Text="S_EVENT_LANDING_QUALITYMARK" +}, +[EVENTS.BDA]={ +Order=1, +Event="OnEventBDA", +Text="S_EVENT_BDA" +}, +[EVENTS.AIAbortMission]={ +Order=1, +Side="I", +Event="OnEventAIAbortMission", +Text="S_EVENT_AI_ABORT_MISSION" +}, +[EVENTS.DayNight]={ +Order=1, +Event="OnEventDayNight", +Text="S_EVENT_DAYNIGHT" +}, +[EVENTS.FlightTime]={ +Order=1, +Event="OnEventFlightTime", +Text="S_EVENT_FLIGHT_TIME" +}, +[EVENTS.SelfKillPilot]={ +Order=1, +Side="I", +Event="OnEventSelfKillPilot", +Text="S_EVENT_PLAYER_SELF_KILL_PILOT" +}, +[EVENTS.PlayerCaptureAirfield]={ +Order=1, +Event="OnEventPlayerCaptureAirfield", +Text="S_EVENT_PLAYER_CAPTURE_AIRFIELD" +}, +[EVENTS.EmergencyLanding]={ +Order=1, +Side="I", +Event="OnEventEmergencyLanding", +Text="S_EVENT_EMERGENCY_LANDING" +}, +[EVENTS.UnitCreateTask]={ +Order=1, +Event="OnEventUnitCreateTask", +Text="S_EVENT_UNIT_CREATE_TASK" +}, +[EVENTS.UnitDeleteTask]={ +Order=1, +Event="OnEventUnitDeleteTask", +Text="S_EVENT_UNIT_DELETE_TASK" +}, +[EVENTS.SimulationStart]={ +Order=1, +Event="OnEventSimulationStart", +Text="S_EVENT_SIMULATION_START" +}, +[EVENTS.WeaponRearm]={ +Order=1, +Side="I", +Event="OnEventWeaponRearm", +Text="S_EVENT_WEAPON_REARM" +}, +[EVENTS.WeaponDrop]={ +Order=1, +Side="I", +Event="OnEventWeaponDrop", +Text="S_EVENT_WEAPON_DROP" +}, +[EVENTS.UnitTaskStage]={ +Order=1, +Side="I", +Event="OnEventUnitTaskStage", +Text="S_EVENT_UNIT_TASK_STAGE " +}, +[EVENTS.MacExtraScore]={ +Order=1, +Side="I", +Event="OnEventMacExtraScore", +Text="S_EVENT_MAC_EXTRA_SCOREP" +}, +[EVENTS.MissionRestart]={ +Order=1, +Side="I", +Event="OnEventMissionRestart", +Text="S_EVENT_MISSION_RESTART" +}, +[EVENTS.MissionWinner]={ +Order=1, +Side="I", +Event="OnEventMissionWinner", +Text="S_EVENT_MISSION_WINNER" +}, +[EVENTS.RunwayTakeoff]={ +Order=1, +Side="I", +Event="OnEventRunwayTakeoff", +Text="S_EVENT_RUNWAY_TAKEOFF" +}, +[EVENTS.RunwayTouch]={ +Order=1, +Side="I", +Event="OnEventRunwayTouch", +Text="S_EVENT_RUNWAY_TOUCH" +}, +[EVENTS.MacLMSRestart]={ +Order=1, +Side="I", +Event="OnEventMacLMSRestart", +Text="S_EVENT_MAC_LMS_RESTART" +}, +[EVENTS.SimulationFreeze]={ +Order=1, +Side="I", +Event="OnEventSimulationFreeze", +Text="S_EVENT_SIMULATION_FREEZE" +}, +[EVENTS.SimulationUnfreeze]={ +Order=1, +Side="I", +Event="OnEventSimulationUnfreeze", +Text="S_EVENT_SIMULATION_UNFREEZE" +}, +[EVENTS.HumanAircraftRepairStart]={ +Order=1, +Side="I", +Event="OnEventHumanAircraftRepairStart", +Text="S_EVENT_HUMAN_AIRCRAFT_REPAIR_START" +}, +[EVENTS.HumanAircraftRepairFinish]={ +Order=1, +Side="I", +Event="OnEventHumanAircraftRepairFinish", +Text="S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH" +}, +[EVENTS.NewDynamicCargo]={ +Order=1, +Side="I", +Event="OnEventNewDynamicCargo", +Text="S_EVENT_NEW_DYNAMIC_CARGO" +}, +[EVENTS.DynamicCargoLoaded]={ +Order=1, +Side="I", +Event="OnEventDynamicCargoLoaded", +Text="S_EVENT_DYNAMIC_CARGO_LOADED" +}, +[EVENTS.DynamicCargoUnloaded]={ +Order=1, +Side="I", +Event="OnEventDynamicCargoUnloaded", +Text="S_EVENT_DYNAMIC_CARGO_UNLOADED" +}, +[EVENTS.DynamicCargoRemoved]={ +Order=1, +Side="I", +Event="OnEventDynamicCargoRemoved", +Text="S_EVENT_DYNAMIC_CARGO_REMOVED" +}, +} +function EVENT:New() +local self=BASE:Inherit(self,BASE:New()) +self.EventHandler=world.addEventHandler(self) +return self +end +function EVENT:Init(EventID,EventClass) +self:F3({_EVENTMETA[EventID].Text,EventClass}) +if not self.Events[EventID]then +self.Events[EventID]={} +end +local EventPriority=EventClass:GetEventPriority() +if not self.Events[EventID][EventPriority]then +self.Events[EventID][EventPriority]=setmetatable({},{__mode="k"}) +end +if not self.Events[EventID][EventPriority][EventClass]then +self.Events[EventID][EventPriority][EventClass]={} +end +return self.Events[EventID][EventPriority][EventClass] +end +function EVENT:RemoveEvent(EventClass,EventID) +self:F2({"Removing subscription for class: ",EventClass:GetClassNameAndID()}) +local EventPriority=EventClass:GetEventPriority() +self.Events=self.Events or{} +self.Events[EventID]=self.Events[EventID]or{} +self.Events[EventID][EventPriority]=self.Events[EventID][EventPriority]or{} +self.Events[EventID][EventPriority][EventClass]=nil +return self +end +function EVENT:Reset(EventObject) +self:F({"Resetting subscriptions for class: ",EventObject:GetClassNameAndID()}) +local EventPriority=EventObject:GetEventPriority() +for EventID,EventData in pairs(self.Events)do +if self.EventsDead then +if self.EventsDead[EventID]then +if self.EventsDead[EventID][EventPriority]then +if self.EventsDead[EventID][EventPriority][EventObject]then +self.Events[EventID][EventPriority][EventObject]=self.EventsDead[EventID][EventPriority][EventObject] +end +end +end +end +end +end +function EVENT:RemoveAll(EventClass) +local EventClassName=EventClass:GetClassNameAndID() +local EventPriority=EventClass:GetEventPriority() +for EventID,EventData in pairs(self.Events)do +self.Events[EventID][EventPriority][EventClass]=nil +end +return self +end +function EVENT:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EventID) +self:F2(EventTemplate.name) +for EventUnitID,EventUnit in pairs(EventTemplate.units)do +self:OnEventForUnit(EventUnit.name,EventFunction,EventClass,EventID) +end +return self +end +function EVENT:OnEventGeneric(EventFunction,EventClass,EventID) +self:F2({EventID,EventClass,EventFunction}) +local EventData=self:Init(EventID,EventClass) +EventData.EventFunction=EventFunction +return self +end +function EVENT:OnEventForUnit(UnitName,EventFunction,EventClass,EventID) +self:F2(UnitName) +local EventData=self:Init(EventID,EventClass) +EventData.EventUnit=true +EventData.EventFunction=EventFunction +return self +end +function EVENT:OnEventForGroup(GroupName,EventFunction,EventClass,EventID,...) +local Event=self:Init(EventID,EventClass) +Event.EventGroup=true +Event.EventFunction=EventFunction +Event.Params=arg +return self +end +do +function EVENT:OnBirthForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Birth) +return self +end +end +do +function EVENT:OnCrashForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Crash) +return self +end +end +do +function EVENT:OnDeadForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Dead) +return self +end +end +do +function EVENT:OnLandForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Land) +return self +end +end +do +function EVENT:OnTakeOffForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Takeoff) +return self +end +end +do +function EVENT:OnEngineShutDownForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.EngineShutdown) +return self +end +end +do +function EVENT:CreateEventNewCargo(Cargo) +self:F({Cargo}) +local Event={ +id=EVENTS.NewCargo, +time=timer.getTime(), +cargo=Cargo, +} +world.onEvent(Event) +end +function EVENT:CreateEventDeleteCargo(Cargo) +self:F({Cargo}) +local Event={ +id=EVENTS.DeleteCargo, +time=timer.getTime(), +cargo=Cargo, +} +world.onEvent(Event) +end +function EVENT:CreateEventNewZone(Zone) +self:F({Zone}) +local Event={ +id=EVENTS.NewZone, +time=timer.getTime(), +zone=Zone, +} +world.onEvent(Event) +end +function EVENT:CreateEventDeleteZone(Zone) +self:F({Zone}) +local Event={ +id=EVENTS.DeleteZone, +time=timer.getTime(), +zone=Zone, +} +world.onEvent(Event) +end +function EVENT:CreateEventNewZoneGoal(ZoneGoal) +self:F({ZoneGoal}) +local Event={ +id=EVENTS.NewZoneGoal, +time=timer.getTime(), +ZoneGoal=ZoneGoal, +} +world.onEvent(Event) +end +function EVENT:CreateEventDeleteZoneGoal(ZoneGoal) +self:F({ZoneGoal}) +local Event={ +id=EVENTS.DeleteZoneGoal, +time=timer.getTime(), +ZoneGoal=ZoneGoal, +} +world.onEvent(Event) +end +function EVENT:CreateEventPlayerEnterUnit(PlayerUnit) +self:F({PlayerUnit}) +local Event={ +id=EVENTS.PlayerEnterUnit, +time=timer.getTime(), +initiator=PlayerUnit:GetDCSObject() +} +world.onEvent(Event) +end +function EVENT:CreateEventPlayerEnterAircraft(PlayerUnit) +self:F({PlayerUnit}) +local Event={ +id=EVENTS.PlayerEnterAircraft, +time=timer.getTime(), +initiator=PlayerUnit:GetDCSObject() +} +world.onEvent(Event) +end +function EVENT:CreateEventNewDynamicCargo(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.NewDynamicCargo, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +function EVENT:CreateEventDynamicCargoLoaded(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.DynamicCargoLoaded, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +function EVENT:CreateEventDynamicCargoUnloaded(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.DynamicCargoUnloaded, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +function EVENT:CreateEventDynamicCargoRemoved(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.DynamicCargoRemoved, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +end +function EVENT:onEvent(Event) +local ErrorHandler=function(errmsg) +env.info("Error in SCHEDULER function:"..errmsg) +if BASE.Debug~=nil then +env.info(debug.traceback()) +end +return errmsg +end +local EventMeta=_EVENTMETA[Event.id] +if EventMeta then +if self and self.Events and self.Events[Event.id]and self.MissionEnd==false and(Event.initiator~=nil or(Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit))then +if Event.id and Event.id==EVENTS.MissionEnd then +self.MissionEnd=true +end +if Event.initiator then +Event.IniObjectCategory=Object.getCategory(Event.initiator) +if Event.IniObjectCategory==Object.Category.STATIC then +if Event.id==31 then +Event.IniDCSUnit=Event.initiator +local ID=Event.initiator.id_ +Event.IniDCSUnitName=string.format("Ejected Pilot ID %s",tostring(ID)) +Event.IniUnitName=Event.IniDCSUnitName +Event.IniCoalition=0 +Event.IniCategory=0 +Event.IniTypeName="Ejected Pilot" +elseif Event.id==33 then +Event.IniDCSUnit=Event.initiator +local ID=Event.initiator.id_ +Event.IniDCSUnitName=string.format("Ejection Seat ID %s",tostring(ID)) +Event.IniUnitName=Event.IniDCSUnitName +Event.IniCoalition=0 +Event.IniCategory=0 +Event.IniTypeName="Ejection Seat" +else +Event.IniDCSUnit=Event.initiator +Event.IniDCSUnitName=Event.IniDCSUnit:getName() +Event.IniUnitName=Event.IniDCSUnitName +Event.IniUnit=STATIC:FindByName(Event.IniDCSUnitName,false) +Event.IniCoalition=Event.IniDCSUnit:getCoalition() +Event.IniCategory=Event.IniDCSUnit:getDesc().category +Event.IniTypeName=Event.IniDCSUnit:getTypeName() +end +local Unit=UNIT:FindByName(Event.IniDCSUnitName) +if Unit then +Event.IniObjectCategory=Object.Category.UNIT +end +elseif Event.IniObjectCategory==Object.Category.UNIT then +Event.IniDCSUnit=Event.initiator +Event.IniDCSUnitName=Event.IniDCSUnit:getName() +Event.IniUnitName=Event.IniDCSUnitName +Event.IniDCSGroup=Event.IniDCSUnit:getGroup() +Event.IniUnit=UNIT:FindByName(Event.IniDCSUnitName) +if not Event.IniUnit then +Event.IniUnit=CLIENT:FindByName(Event.IniDCSUnitName,'',true) +end +Event.IniDCSGroupName=Event.IniUnit and Event.IniUnit.GroupName or"" +Event.IniGroupName=Event.IniDCSGroupName +if Event.IniDCSGroup and Event.IniDCSGroup:isExist()then +Event.IniDCSGroupName=Event.IniDCSGroup:getName() +Event.IniGroup=GROUP:FindByName(Event.IniDCSGroupName) +Event.IniGroupName=Event.IniDCSGroupName +end +Event.IniPlayerName=Event.IniDCSUnit:getPlayerName() +if Event.IniPlayerName then +local PID=NET.GetPlayerIDByName(nil,Event.IniPlayerName) +if PID then +Event.IniPlayerUCID=net.get_player_info(tonumber(PID),'ucid') +end +end +Event.IniCoalition=Event.IniDCSUnit:getCoalition() +Event.IniTypeName=Event.IniDCSUnit:getTypeName() +Event.IniCategory=Event.IniDCSUnit:getDesc().category +elseif Event.IniObjectCategory==Object.Category.CARGO then +Event.IniDCSUnit=Event.initiator +Event.IniDCSUnitName=Event.IniDCSUnit:getName() +Event.IniUnitName=Event.IniDCSUnitName +if string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+")then +Event.IniDynamicCargo=DYNAMICCARGO:FindByName(Event.IniUnitName) +Event.IniDynamicCargoName=Event.IniUnitName +Event.IniPlayerName=string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+") +else +Event.IniUnit=CARGO:FindByName(Event.IniDCSUnitName) +end +Event.IniCoalition=Event.IniDCSUnit:getCoalition() +Event.IniCategory=Event.IniDCSUnit:getDesc().category +Event.IniTypeName=Event.IniDCSUnit:getTypeName() +elseif Event.IniObjectCategory==Object.Category.SCENERY then +Event.IniDCSUnit=Event.initiator +Event.IniDCSUnitName=(Event.IniDCSUnit and Event.IniDCSUnit.getName)and Event.IniDCSUnit:getName()or"Scenery no name "..math.random(1,20000) +Event.IniUnitName=Event.IniDCSUnitName +Event.IniUnit=SCENERY:Register(Event.IniDCSUnitName,Event.initiator) +Event.IniCategory=(Event.IniDCSUnit and Event.IniDCSUnit.getDesc)and Event.IniDCSUnit:getDesc().category +Event.IniTypeName=(Event.initiator and Event.initiator.isExist +and Event.initiator:isExist()and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName)and Event.IniDCSUnit:getTypeName()or"SCENERY" +elseif Event.IniObjectCategory==Object.Category.BASE then +Event.IniDCSUnit=Event.initiator +Event.IniDCSUnitName=Event.IniDCSUnit:getName() +Event.IniUnitName=Event.IniDCSUnitName +Event.IniUnit=AIRBASE:FindByName(Event.IniDCSUnitName) +Event.IniCoalition=Event.IniDCSUnit:getCoalition() +Event.IniCategory=Event.IniDCSUnit:getDesc().category +Event.IniTypeName=Event.IniDCSUnit:getTypeName() +if not Event.IniUnit then +_DATABASE:_RegisterAirbase(Event.initiator) +Event.IniUnit=AIRBASE:FindByName(Event.IniDCSUnitName) +end +end +end +if Event.target then +Event.TgtObjectCategory=Object.getCategory(Event.target) +if Event.TgtObjectCategory==Object.Category.UNIT then +Event.TgtDCSUnit=Event.target +Event.TgtDCSGroup=Event.TgtDCSUnit:getGroup() +Event.TgtDCSUnitName=Event.TgtDCSUnit:getName() +Event.TgtUnitName=Event.TgtDCSUnitName +Event.TgtUnit=UNIT:FindByName(Event.TgtDCSUnitName) +Event.TgtDCSGroupName="" +if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist()then +Event.TgtDCSGroupName=Event.TgtDCSGroup:getName() +Event.TgtGroup=GROUP:FindByName(Event.TgtDCSGroupName) +Event.TgtGroupName=Event.TgtDCSGroupName +end +Event.TgtPlayerName=Event.TgtDCSUnit:getPlayerName() +if Event.TgtPlayerName then +local PID=NET.GetPlayerIDByName(nil,Event.TgtPlayerName) +if PID then +Event.TgtPlayerUCID=net.get_player_info(tonumber(PID),'ucid') +end +end +Event.TgtCoalition=Event.TgtDCSUnit:getCoalition() +Event.TgtCategory=Event.TgtDCSUnit:getDesc().category +Event.TgtTypeName=Event.TgtDCSUnit:getTypeName() +elseif Event.TgtObjectCategory==Object.Category.STATIC then +Event.TgtDCSUnit=Event.target +if Event.target.isExist and Event.target:isExist()and Event.id~=33 then +Event.TgtDCSUnitName=Event.TgtDCSUnit:getName() +if Event.TgtDCSUnitName and Event.TgtDCSUnitName~=""then +Event.TgtUnitName=Event.TgtDCSUnitName +Event.TgtUnit=STATIC:FindByName(Event.TgtDCSUnitName,false) +Event.TgtCoalition=Event.TgtDCSUnit:getCoalition() +Event.TgtCategory=Event.TgtDCSUnit:getDesc().category +Event.TgtTypeName=Event.TgtDCSUnit:getTypeName() +end +else +Event.TgtDCSUnitName=string.format("No target object for Event ID %s",tostring(Event.id)) +Event.TgtUnitName=Event.TgtDCSUnitName +Event.TgtUnit=nil +Event.TgtCoalition=0 +Event.TgtCategory=0 +if Event.id==6 then +Event.TgtTypeName="Ejected Pilot" +Event.TgtDCSUnitName=string.format("Ejected Pilot ID %s",tostring(Event.IniDCSUnitName)) +Event.TgtUnitName=Event.TgtDCSUnitName +elseif Event.id==33 then +Event.TgtTypeName="Ejection Seat" +Event.TgtDCSUnitName=string.format("Ejection Seat ID %s",tostring(Event.IniDCSUnitName)) +Event.TgtUnitName=Event.TgtDCSUnitName +else +Event.TgtTypeName="Static" +end +end +elseif Event.TgtObjectCategory==Object.Category.SCENERY then +Event.TgtDCSUnit=Event.target +Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName()or nil +if Event.TgtDCSUnitName~=nil then +Event.TgtUnitName=Event.TgtDCSUnitName +Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) +Event.TgtCategory=Event.TgtDCSUnit:getDesc().category +Event.TgtTypeName=Event.TgtDCSUnit:getTypeName() +end +end +end +if Event.weapon and type(Event.weapon)=="table"and Event.weapon.isExist and Event.weapon:isExist()then +Event.Weapon=Event.weapon +Event.WeaponName=Event.weapon:isExist()and Event.weapon:getTypeName()or"Unknown Weapon" +Event.WeaponUNIT=CLIENT:Find(Event.Weapon,'',true) +Event.WeaponPlayerName=Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName() +Event.WeaponCoalition=Event.WeaponUNIT and Event.Weapon.getCoalition and Event.Weapon:getCoalition() +Event.WeaponCategory=Event.WeaponUNIT and Event.Weapon.getDesc and Event.Weapon:getDesc().category +Event.WeaponTypeName=Event.WeaponUNIT and Event.Weapon.getTypeName and Event.Weapon:getTypeName() +end +if Event.place then +if Event.id==EVENTS.LandingAfterEjection then +else +if Event.place:isExist()and Object.getCategory(Event.place)~=Object.Category.SCENERY then +Event.Place=AIRBASE:Find(Event.place) +if Event.Place then +Event.PlaceName=Event.Place:GetName() +end +end +end +end +if Event.idx then +Event.MarkID=Event.idx +Event.MarkVec3=Event.pos +Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) +Event.MarkText=Event.text +Event.MarkCoalition=Event.coalition +Event.IniCoalition=Event.coalition +Event.MarkGroupID=Event.groupID +end +if Event.cargo then +Event.Cargo=Event.cargo +Event.CargoName=Event.cargo.Name +end +if Event.dynamiccargo then +Event.IniDynamicCargo=Event.dynamiccargo +Event.IniDynamicCargoName=Event.IniDynamicCargo.StaticName +if Event.IniDynamicCargo.Owner or Event.IniUnitName then +Event.IniPlayerName=Event.IniDynamicCargo.Owner or string.match(Event.IniUnitName or"None|00:00|PKG00","^(.+)|%d%d:%d%d|PKG%d+") +end +end +if Event.zone then +Event.Zone=Event.zone +Event.ZoneName=Event.zone.ZoneName +end +local PriorityOrder=EventMeta.Order +local PriorityBegin=PriorityOrder==-1 and 5 or 1 +local PriorityEnd=PriorityOrder==-1 and 1 or 5 +for EventPriority=PriorityBegin,PriorityEnd,PriorityOrder do +if self.Events[Event.id][EventPriority]then +for EventClass,EventData in pairs(self.Events[Event.id][EventPriority])do +Event.IniGroup=Event.IniGroup or GROUP:FindByName(Event.IniDCSGroupName) +Event.TgtGroup=Event.TgtGroup or GROUP:FindByName(Event.TgtDCSGroupName) +if EventData.EventUnit then +if EventClass:IsAlive()or +Event.id==EVENTS.PlayerEnterUnit or +Event.id==EVENTS.Crash or +Event.id==EVENTS.Dead or +Event.id==EVENTS.RemoveUnit or +Event.id==EVENTS.UnitLost then +local UnitName=EventClass:GetName() +if(EventMeta.Side=="I"and UnitName==Event.IniDCSUnitName)or +(EventMeta.Side=="T"and UnitName==Event.TgtDCSUnitName)then +if EventData.EventFunction then +local Result,Value=xpcall( +function() +return EventData.EventFunction(EventClass,Event) +end,ErrorHandler) +else +local EventFunction=EventClass[EventMeta.Event] +if EventFunction and type(EventFunction)=="function"then +local Result,Value=xpcall( +function() +return EventFunction(EventClass,Event) +end,ErrorHandler) +end +end +end +else +self:RemoveEvent(EventClass,Event.id) +end +else +if EventData.EventGroup then +if EventClass:IsAlive()or +Event.id==EVENTS.PlayerEnterUnit or +Event.id==EVENTS.Crash or +Event.id==EVENTS.Dead or +Event.id==EVENTS.RemoveUnit or +Event.id==EVENTS.UnitLost then +local GroupName=EventClass:GetName() +if(EventMeta.Side=="I"and GroupName==Event.IniDCSGroupName)or +(EventMeta.Side=="T"and GroupName==Event.TgtDCSGroupName)then +if EventData.EventFunction then +local Result,Value=xpcall( +function() +return EventData.EventFunction(EventClass,Event,unpack(EventData.Params)) +end,ErrorHandler) +else +local EventFunction=EventClass[EventMeta.Event] +if EventFunction and type(EventFunction)=="function"then +local Result,Value=xpcall( +function() +return EventFunction(EventClass,Event,unpack(EventData.Params)) +end,ErrorHandler) +end +end +end +else +end +else +if not EventData.EventUnit then +if EventData.EventFunction then +local Result,Value=xpcall( +function() +return EventData.EventFunction(EventClass,Event) +end,ErrorHandler) +else +local EventFunction=EventClass[EventMeta.Event] +if EventFunction and type(EventFunction)=="function"then +local Result,Value=xpcall( +function() +local Result,Value=EventFunction(EventClass,Event) +return Result,Value +end,ErrorHandler) +end +end +end +end +end +end +end +end +if Event.id==EVENTS.DeleteCargo then +Event.Cargo.NoDestroy=nil +end +else +self:T({EventMeta.Text,Event}) +end +else +self:E(string.format("WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?",tostring(Event.id))) +end +Event=nil +end +EVENTHANDLER={ +ClassName="EVENTHANDLER", +ClassID=0, +} +function EVENTHANDLER:New() +self=BASE:Inherit(self,BASE:New()) +return self +end +SETTINGS={ +ClassName="SETTINGS", +ShowPlayerMenu=true, +MenuShort=false, +MenuStatic=false, +} +SETTINGS.__Enum={} +SETTINGS.__Enum.Era={ +WWII=1, +Korea=2, +Cold=3, +Modern=4, +} +do +function SETTINGS:Set(PlayerName) +if PlayerName==nil then +local self=BASE:Inherit(self,BASE:New()) +self:SetMetric() +self:SetA2G_BR() +self:SetA2A_BRAA() +self:SetLL_Accuracy(3) +self:SetMGRS_Accuracy(5) +self:SetMessageTime(MESSAGE.Type.Briefing,180) +self:SetMessageTime(MESSAGE.Type.Detailed,60) +self:SetMessageTime(MESSAGE.Type.Information,30) +self:SetMessageTime(MESSAGE.Type.Overview,60) +self:SetMessageTime(MESSAGE.Type.Update,15) +self:SetEraModern() +self:SetLocale("en") +return self +else +local Settings=_DATABASE:GetPlayerSettings(PlayerName) +if not Settings then +Settings=BASE:Inherit(self,BASE:New()) +_DATABASE:SetPlayerSettings(PlayerName,Settings) +end +return Settings +end +end +function SETTINGS:SetMenutextShort(onoff) +_SETTINGS.MenuShort=onoff +end +function SETTINGS:SetMenuStatic(onoff) +_SETTINGS.MenuStatic=onoff +end +function SETTINGS:SetMetric() +self.Metric=true +end +function SETTINGS:SetLocale(Locale) +self.Locale=Locale or"en" +end +function SETTINGS:GetLocale() +return self.Locale or _SETTINGS:GetLocale() +end +function SETTINGS:IsMetric() +return(self.Metric~=nil and self.Metric==true)or(self.Metric==nil and _SETTINGS:IsMetric()) +end +function SETTINGS:SetImperial() +self.Metric=false +end +function SETTINGS:IsImperial() +return(self.Metric~=nil and self.Metric==false)or(self.Metric==nil and _SETTINGS:IsImperial()) +end +function SETTINGS:SetLL_Accuracy(LL_Accuracy) +self.LL_Accuracy=LL_Accuracy +end +function SETTINGS:GetLL_DDM_Accuracy() +return self.LL_DDM_Accuracy or _SETTINGS:GetLL_DDM_Accuracy() +end +function SETTINGS:SetMGRS_Accuracy(MGRS_Accuracy) +self.MGRS_Accuracy=MGRS_Accuracy +end +function SETTINGS:GetMGRS_Accuracy() +return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy() +end +function SETTINGS:SetMessageTime(MessageType,MessageTime) +self.MessageTypeTimings=self.MessageTypeTimings or{} +self.MessageTypeTimings[MessageType]=MessageTime +end +function SETTINGS:GetMessageTime(MessageType) +return(self.MessageTypeTimings and self.MessageTypeTimings[MessageType])or _SETTINGS:GetMessageTime(MessageType) +end +function SETTINGS:SetA2G_LL_DMS() +self.A2GSystem="LL DMS" +end +function SETTINGS:SetA2G_LL_DDM() +self.A2GSystem="LL DDM" +end +function SETTINGS:IsA2G_LL_DMS() +return(self.A2GSystem and self.A2GSystem=="LL DMS")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS()) +end +function SETTINGS:IsA2G_LL_DDM() +return(self.A2GSystem and self.A2GSystem=="LL DDM")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM()) +end +function SETTINGS:SetA2G_MGRS() +self.A2GSystem="MGRS" +end +function SETTINGS:IsA2G_MGRS() +return(self.A2GSystem and self.A2GSystem=="MGRS")or(not self.A2GSystem and _SETTINGS:IsA2G_MGRS()) +end +function SETTINGS:SetA2G_BR() +self.A2GSystem="BR" +end +function SETTINGS:IsA2G_BR() +return(self.A2GSystem and self.A2GSystem=="BR")or(not self.A2GSystem and _SETTINGS:IsA2G_BR()) +end +function SETTINGS:SetA2A_BRAA() +self.A2ASystem="BRAA" +end +function SETTINGS:IsA2A_BRAA() +return(self.A2ASystem and self.A2ASystem=="BRAA")or(not self.A2ASystem and _SETTINGS:IsA2A_BRAA()) +end +function SETTINGS:SetA2A_BULLS() +self.A2ASystem="BULLS" +end +function SETTINGS:IsA2A_BULLS() +return(self.A2ASystem and self.A2ASystem=="BULLS")or(not self.A2ASystem and _SETTINGS:IsA2A_BULLS()) +end +function SETTINGS:SetA2A_LL_DMS() +self.A2ASystem="LL DMS" +end +function SETTINGS:SetA2A_LL_DDM() +self.A2ASystem="LL DDM" +end +function SETTINGS:IsA2A_LL_DMS() +return(self.A2ASystem and self.A2ASystem=="LL DMS")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS()) +end +function SETTINGS:IsA2A_LL_DDM() +return(self.A2ASystem and self.A2ASystem=="LL DDM")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM()) +end +function SETTINGS:SetA2A_MGRS() +self.A2ASystem="MGRS" +end +function SETTINGS:IsA2A_MGRS() +return(self.A2ASystem and self.A2ASystem=="MGRS")or(not self.A2ASystem and _SETTINGS:IsA2A_MGRS()) +end +function SETTINGS:SetSystemMenu(MenuGroup,RootMenu) +local MenuText="System Settings" +local MenuTime=timer.getTime() +local SettingsMenu=MENU_GROUP:New(MenuGroup,MenuText,RootMenu):SetTime(MenuTime) +local text="A2G Coordinate System" +if _SETTINGS.MenuShort then +text="A2G Coordinates" +end +local A2GCoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) +if not self:IsA2G_LL_DMS()then +local text="Lat/Lon Degree Min Sec (LL DMS)" +if _SETTINGS.MenuShort then +text="LL DMS" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime) +end +if not self:IsA2G_LL_DDM()then +local text="Lat/Lon Degree Dec Min (LL DDM)" +if _SETTINGS.MenuShort then +text="LL DDM" +end +MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime) +end +if self:IsA2G_LL_DDM()then +local text1="LL DDM Accuracy 1" +local text2="LL DDM Accuracy 2" +local text3="LL DDM Accuracy 3" +if _SETTINGS.MenuShort then +text1="LL DDM" +end +MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) +end +if not self:IsA2G_BR()then +local text="Bearing, Range (BR)" +if _SETTINGS.MenuShort then +text="BR" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"BR"):SetTime(MenuTime) +end +if not self:IsA2G_MGRS()then +local text="Military Grid (MGRS)" +if _SETTINGS.MenuShort then +text="MGRS" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime) +end +if self:IsA2G_MGRS()then +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime) +end +local text="A2A Coordinate System" +if _SETTINGS.MenuShort then +text="A2A Coordinates" +end +local A2ACoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) +if not self:IsA2A_LL_DMS()then +local text="Lat/Lon Degree Min Sec (LL DMS)" +if _SETTINGS.MenuShort then +text="LL DMS" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime) +end +if not self:IsA2A_LL_DDM()then +local text="Lat/Lon Degree Dec Min (LL DDM)" +if _SETTINGS.MenuShort then +text="LL DDM" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime) +end +if self:IsA2A_LL_DDM()or self:IsA2A_LL_DMS()then +MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 0",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,0):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 1",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 2",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 3",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) +end +if not self:IsA2A_BULLS()then +local text="Bullseye (BULLS)" +if _SETTINGS.MenuShort then +text="Bulls" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BULLS"):SetTime(MenuTime) +end +if not self:IsA2A_BRAA()then +local text="Bearing Range Altitude Aspect (BRAA)" +if _SETTINGS.MenuShort then +text="BRAA" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BRAA"):SetTime(MenuTime) +end +if not self:IsA2A_MGRS()then +local text="Military Grid (MGRS)" +if _SETTINGS.MenuShort then +text="MGRS" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime) +end +if self:IsA2A_MGRS()then +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime) +end +local text="Measures and Weights System" +if _SETTINGS.MenuShort then +text="Unit System" +end +local MetricsMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) +if self:IsMetric()then +local text="Imperial (Miles,Feet)" +if _SETTINGS.MenuShort then +text="Imperial" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,false):SetTime(MenuTime) +end +if self:IsImperial()then +local text="Metric (Kilometers,Meters)" +if _SETTINGS.MenuShort then +text="Metric" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,true):SetTime(MenuTime) +end +local text="Messages and Reports" +if _SETTINGS.MenuShort then +text="Messages & Reports" +end +local MessagesMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) +local UpdateMessagesMenu=MENU_GROUP:New(MenuGroup,"Update Messages",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"Off",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,0):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,5):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,10):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,60):SetTime(MenuTime) +local InformationMessagesMenu=MENU_GROUP:New(MenuGroup,"Information Messages",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,5):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,10):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,60):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,120):SetTime(MenuTime) +local BriefingReportsMenu=MENU_GROUP:New(MenuGroup,"Briefing Reports",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,60):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,120):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,180):SetTime(MenuTime) +local OverviewReportsMenu=MENU_GROUP:New(MenuGroup,"Overview Reports",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,60):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,120):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,180):SetTime(MenuTime) +local DetailedReportsMenu=MENU_GROUP:New(MenuGroup,"Detailed Reports",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,60):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,120):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,180):SetTime(MenuTime) +SettingsMenu:Remove(MenuTime) +return self +end +function SETTINGS:SetPlayerMenuOn() +self.ShowPlayerMenu=true +end +function SETTINGS:SetPlayerMenuOff() +self.ShowPlayerMenu=false +end +function SETTINGS:SetPlayerMenu(PlayerUnit) +if _SETTINGS.ShowPlayerMenu==true then +local PlayerGroup=PlayerUnit:GetGroup() +local PlayerName=PlayerUnit:GetPlayerName()or"None" +local PlayerMenu=MENU_GROUP:New(PlayerGroup,'Settings "'..PlayerName..'"') +self.PlayerMenu=PlayerMenu +self:T(string.format("Setting menu for player %s",tostring(PlayerName))) +local submenu=MENU_GROUP:New(PlayerGroup,"LL Accuracy",PlayerMenu) +MENU_GROUP_COMMAND:New(PlayerGroup,"LL 0 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,0) +MENU_GROUP_COMMAND:New(PlayerGroup,"LL 1 Decimal",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1) +MENU_GROUP_COMMAND:New(PlayerGroup,"LL 2 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2) +MENU_GROUP_COMMAND:New(PlayerGroup,"LL 3 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3) +MENU_GROUP_COMMAND:New(PlayerGroup,"LL 4 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4) +local submenu=MENU_GROUP:New(PlayerGroup,"MGRS Accuracy",PlayerMenu) +MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 0",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,0) +MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 1",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1) +MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 2",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2) +MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 3",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3) +MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 4",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4) +MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 5",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,5) +local text="A2G Coordinate System" +if _SETTINGS.MenuShort then +text="A2G Coordinates" +end +local A2GCoordinateMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu) +if not self:IsA2G_LL_DMS()or _SETTINGS.MenuStatic then +local text="Lat/Lon Degree Min Sec (LL DMS)" +if _SETTINGS.MenuShort then +text="A2G LL DMS" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS") +end +if not self:IsA2G_LL_DDM()or _SETTINGS.MenuStatic then +local text="Lat/Lon Degree Dec Min (LL DDM)" +if _SETTINGS.MenuShort then +text="A2G LL DDM" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM") +end +if not self:IsA2G_BR()or _SETTINGS.MenuStatic then +local text="Bearing, Range (BR)" +if _SETTINGS.MenuShort then +text="A2G BR" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"BR") +end +if not self:IsA2G_MGRS()or _SETTINGS.MenuStatic then +local text="Military Grid (MGRS)" +if _SETTINGS.MenuShort then +text="A2G MGRS" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS") +end +local text="A2A Coordinate System" +if _SETTINGS.MenuShort then +text="A2A Coordinates" +end +local A2ACoordinateMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu) +if not self:IsA2A_LL_DMS()or _SETTINGS.MenuStatic then +local text="Lat/Lon Degree Min Sec (LL DMS)" +if _SETTINGS.MenuShort then +text="A2A LL DMS" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS") +end +if not self:IsA2A_LL_DDM()or _SETTINGS.MenuStatic then +local text="Lat/Lon Degree Dec Min (LL DDM)" +if _SETTINGS.MenuShort then +text="A2A LL DDM" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM") +end +if not self:IsA2A_BULLS()or _SETTINGS.MenuStatic then +local text="Bullseye (BULLS)" +if _SETTINGS.MenuShort then +text="A2A BULLS" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BULLS") +end +if not self:IsA2A_BRAA()or _SETTINGS.MenuStatic then +local text="Bearing Range Altitude Aspect (BRAA)" +if _SETTINGS.MenuShort then +text="A2A BRAA" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BRAA") +end +if not self:IsA2A_MGRS()or _SETTINGS.MenuStatic then +local text="Military Grid (MGRS)" +if _SETTINGS.MenuShort then +text="A2A MGRS" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS") +end +local text="Measures and Weights System" +if _SETTINGS.MenuShort then +text="Unit System" +end +local MetricsMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu) +if self:IsMetric()or _SETTINGS.MenuStatic then +local text="Imperial (Miles,Feet)" +if _SETTINGS.MenuShort then +text="Imperial" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,false) +end +if self:IsImperial()or _SETTINGS.MenuStatic then +local text="Metric (Kilometers,Meters)" +if _SETTINGS.MenuShort then +text="Metric" +end +MENU_GROUP_COMMAND:New(PlayerGroup,text,MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,true) +end +local text="Messages and Reports" +if _SETTINGS.MenuShort then +text="Messages & Reports" +end +local MessagesMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu) +local UpdateMessagesMenu=MENU_GROUP:New(PlayerGroup,"Update Messages",MessagesMenu) +MENU_GROUP_COMMAND:New(PlayerGroup,"Updates Off",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,0) +MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 5 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,5) +MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 10 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,10) +MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 15 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,15) +MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 30 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,30) +MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 1 min",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,60) +local InformationMessagesMenu=MENU_GROUP:New(PlayerGroup,"Info Messages",MessagesMenu) +MENU_GROUP_COMMAND:New(PlayerGroup,"Info 5 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,5) +MENU_GROUP_COMMAND:New(PlayerGroup,"Info 10 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,10) +MENU_GROUP_COMMAND:New(PlayerGroup,"Info 15 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,15) +MENU_GROUP_COMMAND:New(PlayerGroup,"Info 30 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,30) +MENU_GROUP_COMMAND:New(PlayerGroup,"Info 1 min",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,60) +MENU_GROUP_COMMAND:New(PlayerGroup,"Info 2 min",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,120) +local BriefingReportsMenu=MENU_GROUP:New(PlayerGroup,"Briefing Reports",MessagesMenu) +MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 15 sec",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,15) +MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 30 sec",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,30) +MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 1 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,60) +MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 2 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,120) +MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 3 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,180) +local OverviewReportsMenu=MENU_GROUP:New(PlayerGroup,"Overview Reports",MessagesMenu) +MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 15 sec",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,15) +MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 30 sec",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,30) +MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 1 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,60) +MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 2 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,120) +MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 3 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,180) +local DetailedReportsMenu=MENU_GROUP:New(PlayerGroup,"Detailed Reports",MessagesMenu) +MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 15 sec",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,15) +MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 30 sec",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,30) +MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 1 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,60) +MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 2 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,120) +MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 3 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,180) +end +return self +end +function SETTINGS:RemovePlayerMenu(PlayerUnit) +if self.PlayerMenu then +self.PlayerMenu:Remove() +self.PlayerMenu=nil +end +return self +end +function SETTINGS:A2GMenuSystem(MenuGroup,RootMenu,A2GSystem) +self.A2GSystem=A2GSystem +MESSAGE:New(string.format("Settings: Default A2G coordinate system set to %s for all players!",A2GSystem),5):ToAll() +self:SetSystemMenu(MenuGroup,RootMenu) +end +function SETTINGS:A2AMenuSystem(MenuGroup,RootMenu,A2ASystem) +self.A2ASystem=A2ASystem +MESSAGE:New(string.format("Settings: Default A2A coordinate system set to %s for all players!",A2ASystem),5):ToAll() +self:SetSystemMenu(MenuGroup,RootMenu) +end +function SETTINGS:MenuLL_DDM_Accuracy(MenuGroup,RootMenu,LL_Accuracy) +self.LL_Accuracy=LL_Accuracy +MESSAGE:New(string.format("Settings: Default LL accuracy set to %s for all players!",LL_Accuracy),5):ToAll() +self:SetSystemMenu(MenuGroup,RootMenu) +end +function SETTINGS:MenuMGRS_Accuracy(MenuGroup,RootMenu,MGRS_Accuracy) +self.MGRS_Accuracy=MGRS_Accuracy +MESSAGE:New(string.format("Settings: Default MGRS accuracy set to %s for all players!",MGRS_Accuracy),5):ToAll() +self:SetSystemMenu(MenuGroup,RootMenu) +end +function SETTINGS:MenuMWSystem(MenuGroup,RootMenu,MW) +self.Metric=MW +MESSAGE:New(string.format("Settings: Default measurement format set to %s for all players!",MW and"Metric"or"Imperial"),5):ToAll() +self:SetSystemMenu(MenuGroup,RootMenu) +end +function SETTINGS:MenuMessageTimingsSystem(MenuGroup,RootMenu,MessageType,MessageTime) +self:SetMessageTime(MessageType,MessageTime) +MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToAll() +end +do +function SETTINGS:MenuGroupA2GSystem(PlayerUnit,PlayerGroup,PlayerName,A2GSystem) +self.A2GSystem=A2GSystem +MESSAGE:New(string.format("Settings: A2G format set to %s for player %s.",A2GSystem,PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupA2ASystem(PlayerUnit,PlayerGroup,PlayerName,A2ASystem) +self.A2ASystem=A2ASystem +MESSAGE:New(string.format("Settings: A2A format set to %s for player %s.",A2ASystem,PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupLL_DDM_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,LL_Accuracy) +self.LL_Accuracy=LL_Accuracy +MESSAGE:New(string.format("Settings: LL format accuracy set to %d decimal places for player %s.",LL_Accuracy,PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupMGRS_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,MGRS_Accuracy) +self.MGRS_Accuracy=MGRS_Accuracy +MESSAGE:New(string.format("Settings: MGRS format accuracy set to %d for player %s.",MGRS_Accuracy,PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupMWSystem(PlayerUnit,PlayerGroup,PlayerName,MW) +self.Metric=MW +MESSAGE:New(string.format("Settings: Measurement format set to %s for player %s.",MW and"Metric"or"Imperial",PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupMessageTimingsSystem(PlayerUnit,PlayerGroup,PlayerName,MessageType,MessageTime) +self:SetMessageTime(MessageType,MessageTime) +MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToGroup(PlayerGroup) +end +end +function SETTINGS:SetEraWWII() +self.Era=SETTINGS.__Enum.Era.WWII +end +function SETTINGS:SetEraKorea() +self.Era=SETTINGS.__Enum.Era.Korea +end +function SETTINGS:SetEraCold() +self.Era=SETTINGS.__Enum.Era.Cold +end +function SETTINGS:SetEraModern() +self.Era=SETTINGS.__Enum.Era.Modern +end +end +MENU_INDEX={} +MENU_INDEX.MenuMission={} +MENU_INDEX.MenuMission.Menus={} +MENU_INDEX.Coalition={} +MENU_INDEX.Coalition[coalition.side.BLUE]={} +MENU_INDEX.Coalition[coalition.side.BLUE].Menus={} +MENU_INDEX.Coalition[coalition.side.RED]={} +MENU_INDEX.Coalition[coalition.side.RED].Menus={} +MENU_INDEX.Group={} +function MENU_INDEX:ParentPath(ParentMenu,MenuText) +local Path=ParentMenu and"@"..table.concat(ParentMenu.MenuPath or{},"@")or"" +if ParentMenu then +if ParentMenu:IsInstanceOf("MENU_GROUP")or ParentMenu:IsInstanceOf("MENU_GROUP_COMMAND")then +local GroupName=ParentMenu.Group:GetName() +if not self.Group[GroupName].Menus[Path]then +BASE:E({Path=Path,GroupName=GroupName}) +error("Parent path not found in menu index for group menu") +return nil +end +elseif ParentMenu:IsInstanceOf("MENU_COALITION")or ParentMenu:IsInstanceOf("MENU_COALITION_COMMAND")then +local Coalition=ParentMenu.Coalition +if not self.Coalition[Coalition].Menus[Path]then +BASE:E({Path=Path,Coalition=Coalition}) +error("Parent path not found in menu index for coalition menu") +return nil +end +elseif ParentMenu:IsInstanceOf("MENU_MISSION")or ParentMenu:IsInstanceOf("MENU_MISSION_COMMAND")then +if not self.MenuMission.Menus[Path]then +BASE:E({Path=Path}) +error("Parent path not found in menu index for mission menu") +return nil +end +end +end +Path=Path.."@"..MenuText +return Path +end +function MENU_INDEX:PrepareMission() +self.MenuMission.Menus=self.MenuMission.Menus or{} +end +function MENU_INDEX:PrepareCoalition(CoalitionSide) +self.Coalition[CoalitionSide]=self.Coalition[CoalitionSide]or{} +self.Coalition[CoalitionSide].Menus=self.Coalition[CoalitionSide].Menus or{} +end +function MENU_INDEX:PrepareGroup(Group) +if Group and Group:IsAlive()~=nil then +local GroupName=Group:GetName() +self.Group[GroupName]=self.Group[GroupName]or{} +self.Group[GroupName].Menus=self.Group[GroupName].Menus or{} +end +end +function MENU_INDEX:HasMissionMenu(Path) +return self.MenuMission.Menus[Path] +end +function MENU_INDEX:SetMissionMenu(Path,Menu) +self.MenuMission.Menus[Path]=Menu +end +function MENU_INDEX:ClearMissionMenu(Path) +self.MenuMission.Menus[Path]=nil +end +function MENU_INDEX:HasCoalitionMenu(Coalition,Path) +return self.Coalition[Coalition].Menus[Path] +end +function MENU_INDEX:SetCoalitionMenu(Coalition,Path,Menu) +self.Coalition[Coalition].Menus[Path]=Menu +end +function MENU_INDEX:ClearCoalitionMenu(Coalition,Path) +self.Coalition[Coalition].Menus[Path]=nil +end +function MENU_INDEX:HasGroupMenu(Group,Path) +if Group and Group:IsAlive()then +local MenuGroupName=Group:GetName() +if self.Group[MenuGroupName]and self.Group[MenuGroupName].Menus and self.Group[MenuGroupName].Menus[Path]then +return self.Group[MenuGroupName].Menus[Path] +end +end +return nil +end +function MENU_INDEX:SetGroupMenu(Group,Path,Menu) +local MenuGroupName=Group:GetName() +self.Group[MenuGroupName].Menus[Path]=Menu +end +function MENU_INDEX:ClearGroupMenu(Group,Path) +local MenuGroupName=Group:GetName() +self.Group[MenuGroupName].Menus[Path]=nil +end +function MENU_INDEX:Refresh(Group) +for MenuID,Menu in pairs(self.MenuMission.Menus)do +Menu:Refresh() +end +for MenuID,Menu in pairs(self.Coalition[coalition.side.BLUE].Menus)do +Menu:Refresh() +end +for MenuID,Menu in pairs(self.Coalition[coalition.side.RED].Menus)do +Menu:Refresh() +end +local GroupName=Group:GetName() +for MenuID,Menu in pairs(self.Group[GroupName].Menus)do +Menu:Refresh() +end +return self +end +do +MENU_BASE={ +ClassName="MENU_BASE", +MenuPath=nil, +MenuText="", +MenuParentPath=nil, +} +function MENU_BASE:New(MenuText,ParentMenu) +local MenuParentPath={} +if ParentMenu~=nil then +MenuParentPath=ParentMenu.MenuPath +end +local self=BASE:Inherit(self,BASE:New()) +self.MenuPath=nil +self.MenuText=MenuText +self.ParentMenu=ParentMenu +self.MenuParentPath=MenuParentPath +self.Path=(self.ParentMenu and"@"..table.concat(self.MenuParentPath or{},"@")or"").."@"..self.MenuText +self.Menus={} +self.MenuCount=0 +self.MenuStamp=timer.getTime() +self.MenuRemoveParent=false +if self.ParentMenu then +self.ParentMenu.Menus=self.ParentMenu.Menus or{} +self.ParentMenu.Menus[MenuText]=self +end +return self +end +function MENU_BASE:SetParentMenu(MenuText,Menu) +if self.ParentMenu then +self.ParentMenu.Menus=self.ParentMenu.Menus or{} +self.ParentMenu.Menus[MenuText]=Menu +self.ParentMenu.MenuCount=self.ParentMenu.MenuCount+1 +end +end +function MENU_BASE:ClearParentMenu(MenuText) +if self.ParentMenu and self.ParentMenu.Menus[MenuText]then +self.ParentMenu.Menus[MenuText]=nil +self.ParentMenu.MenuCount=self.ParentMenu.MenuCount-1 +if self.ParentMenu.MenuCount==0 then +end +end +end +function MENU_BASE:SetRemoveParent(RemoveParent) +self.MenuRemoveParent=RemoveParent +return self +end +function MENU_BASE:GetMenu(MenuText) +return self.Menus[MenuText] +end +function MENU_BASE:SetStamp(MenuStamp) +self.MenuStamp=MenuStamp +return self +end +function MENU_BASE:GetStamp() +return timer.getTime() +end +function MENU_BASE:SetTime(MenuStamp) +self.MenuStamp=MenuStamp +return self +end +function MENU_BASE:SetTag(MenuTag) +self.MenuTag=MenuTag +return self +end +end +do +MENU_COMMAND_BASE={ +ClassName="MENU_COMMAND_BASE", +CommandMenuFunction=nil, +CommandMenuArgument=nil, +MenuCallHandler=nil, +} +function MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,CommandMenuArguments) +local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu)) +local ErrorHandler=function(errmsg) +env.info("MOOSE error in MENU COMMAND function: "..errmsg) +if BASE.Debug~=nil then +env.info(BASE.Debug.traceback()) +end +return errmsg +end +self:SetCommandMenuFunction(CommandMenuFunction) +self:SetCommandMenuArguments(CommandMenuArguments) +self.MenuCallHandler=function() +local function MenuFunction() +return self.CommandMenuFunction(unpack(self.CommandMenuArguments)) +end +local Status,Result=xpcall(MenuFunction,ErrorHandler) +end +return self +end +function MENU_COMMAND_BASE:SetCommandMenuFunction(CommandMenuFunction) +self.CommandMenuFunction=CommandMenuFunction +return self +end +function MENU_COMMAND_BASE:SetCommandMenuArguments(CommandMenuArguments) +self.CommandMenuArguments=CommandMenuArguments +return self +end +end +do +MENU_MISSION={ +ClassName="MENU_MISSION", +} +function MENU_MISSION:New(MenuText,ParentMenu) +MENU_INDEX:PrepareMission() +local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) +local MissionMenu=MENU_INDEX:HasMissionMenu(Path) +if MissionMenu then +return MissionMenu +else +local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu)) +MENU_INDEX:SetMissionMenu(Path,self) +self.MenuPath=missionCommands.addSubMenu(self.MenuText,self.MenuParentPath) +self:SetParentMenu(self.MenuText,self) +return self +end +end +function MENU_MISSION:Refresh() +do +missionCommands.removeItem(self.MenuPath) +self.MenuPath=missionCommands.addSubMenu(self.MenuText,self.MenuParentPath) +end +return self +end +function MENU_MISSION:RemoveSubMenus() +for MenuID,Menu in pairs(self.Menus or{})do +Menu:Remove() +end +self.Menus=nil +end +function MENU_MISSION:Remove(MenuStamp,MenuTag) +MENU_INDEX:PrepareMission() +local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText) +local MissionMenu=MENU_INDEX:HasMissionMenu(Path) +if MissionMenu==self then +self:RemoveSubMenus() +if not MenuStamp or self.MenuStamp~=MenuStamp then +if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then +self:F({Text=self.MenuText,Path=self.MenuPath}) +if self.MenuPath~=nil then +missionCommands.removeItem(self.MenuPath) +end +MENU_INDEX:ClearMissionMenu(self.Path) +self:ClearParentMenu(self.MenuText) +return nil +end +end +else +BASE:E({"Cannot Remove MENU_MISSION",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText}) +end +return self +end +end +do +MENU_MISSION_COMMAND={ +ClassName="MENU_MISSION_COMMAND", +} +function MENU_MISSION_COMMAND:New(MenuText,ParentMenu,CommandMenuFunction,...) +MENU_INDEX:PrepareMission() +local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) +local MissionMenu=MENU_INDEX:HasMissionMenu(Path) +if MissionMenu then +MissionMenu:SetCommandMenuFunction(CommandMenuFunction) +MissionMenu:SetCommandMenuArguments(arg) +return MissionMenu +else +local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg)) +MENU_INDEX:SetMissionMenu(Path,self) +self.MenuPath=missionCommands.addCommand(MenuText,self.MenuParentPath,self.MenuCallHandler) +self:SetParentMenu(self.MenuText,self) +return self +end +end +function MENU_MISSION_COMMAND:Refresh() +do +missionCommands.removeItem(self.MenuPath) +missionCommands.addCommand(self.MenuText,self.MenuParentPath,self.MenuCallHandler) +end +return self +end +function MENU_MISSION_COMMAND:Remove() +MENU_INDEX:PrepareMission() +local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText) +local MissionMenu=MENU_INDEX:HasMissionMenu(Path) +if MissionMenu==self then +if not MenuStamp or self.MenuStamp~=MenuStamp then +if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then +self:F({Text=self.MenuText,Path=self.MenuPath}) +if self.MenuPath~=nil then +missionCommands.removeItem(self.MenuPath) +end +MENU_INDEX:ClearMissionMenu(self.Path) +self:ClearParentMenu(self.MenuText) +return nil +end +end +else +BASE:E({"Cannot Remove MENU_MISSION_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText}) +end +return self +end +end +do +MENU_COALITION={ +ClassName="MENU_COALITION" +} +function MENU_COALITION:New(Coalition,MenuText,ParentMenu) +MENU_INDEX:PrepareCoalition(Coalition) +local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) +local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(Coalition,Path) +if CoalitionMenu then +return CoalitionMenu +else +local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu)) +MENU_INDEX:SetCoalitionMenu(Coalition,Path,self) +self.Coalition=Coalition +self.MenuPath=missionCommands.addSubMenuForCoalition(Coalition,MenuText,self.MenuParentPath) +self:SetParentMenu(self.MenuText,self) +return self +end +end +function MENU_COALITION:Refresh() +do +missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath) +missionCommands.addSubMenuForCoalition(self.Coalition,self.MenuText,self.MenuParentPath) +end +return self +end +function MENU_COALITION:RemoveSubMenus() +for MenuID,Menu in pairs(self.Menus or{})do +Menu:Remove() +end +self.Menus=nil +end +function MENU_COALITION:Remove(MenuStamp,MenuTag) +MENU_INDEX:PrepareCoalition(self.Coalition) +local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText) +local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(self.Coalition,Path) +if CoalitionMenu==self then +self:RemoveSubMenus() +if not MenuStamp or self.MenuStamp~=MenuStamp then +if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then +self:F({Coalition=self.Coalition,Text=self.MenuText,Path=self.MenuPath}) +if self.MenuPath~=nil then +missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath) +end +MENU_INDEX:ClearCoalitionMenu(self.Coalition,Path) +self:ClearParentMenu(self.MenuText) +return nil +end +end +else +BASE:E({"Cannot Remove MENU_COALITION",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Coalition=self.Coalition}) +end +return self +end +end +do +MENU_COALITION_COMMAND={ +ClassName="MENU_COALITION_COMMAND" +} +function MENU_COALITION_COMMAND:New(Coalition,MenuText,ParentMenu,CommandMenuFunction,...) +MENU_INDEX:PrepareCoalition(Coalition) +local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) +local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(Coalition,Path) +if CoalitionMenu then +CoalitionMenu:SetCommandMenuFunction(CommandMenuFunction) +CoalitionMenu:SetCommandMenuArguments(arg) +return CoalitionMenu +else +local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg)) +MENU_INDEX:SetCoalitionMenu(Coalition,Path,self) +self.Coalition=Coalition +self.MenuPath=missionCommands.addCommandForCoalition(self.Coalition,MenuText,self.MenuParentPath,self.MenuCallHandler) +self:SetParentMenu(self.MenuText,self) +return self +end +end +function MENU_COALITION_COMMAND:Refresh() +do +missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath) +missionCommands.addCommandForCoalition(self.Coalition,self.MenuText,self.MenuParentPath,self.MenuCallHandler) +end +return self +end +function MENU_COALITION_COMMAND:Remove(MenuStamp,MenuTag) +MENU_INDEX:PrepareCoalition(self.Coalition) +local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText) +local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(self.Coalition,Path) +if CoalitionMenu==self then +if not MenuStamp or self.MenuStamp~=MenuStamp then +if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then +self:F({Coalition=self.Coalition,Text=self.MenuText,Path=self.MenuPath}) +if self.MenuPath~=nil then +missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath) +end +MENU_INDEX:ClearCoalitionMenu(self.Coalition,Path) +self:ClearParentMenu(self.MenuText) +return nil +end +end +else +BASE:E({"Cannot Remove MENU_COALITION_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Coalition=self.Coalition}) +end +return self +end +end +do +local _MENUGROUPS={} +MENU_GROUP={ +ClassName="MENU_GROUP" +} +function MENU_GROUP:New(Group,MenuText,ParentMenu) +MENU_INDEX:PrepareGroup(Group) +local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText) +local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path) +if GroupMenu then +return GroupMenu +else +self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu)) +MENU_INDEX:SetGroupMenu(Group,Path,self) +self.Group=Group +self.GroupID=Group:GetID() +self.MenuPath=missionCommands.addSubMenuForGroup(self.GroupID,MenuText,self.MenuParentPath) +self:SetParentMenu(self.MenuText,self) +return self +end +end +function MENU_GROUP:Refresh() +do +missionCommands.removeItemForGroup(self.GroupID,self.MenuPath) +missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath) +for MenuText,Menu in pairs(self.Menus or{})do +Menu:Refresh() +end +end +return self +end +function MENU_GROUP:RefreshAndOrderByTag() +do +missionCommands.removeItemForGroup(self.GroupID,self.MenuPath) +missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath) +local MenuTable={} +for MenuText,Menu in pairs(self.Menus or{})do +local tag=Menu.MenuTag or math.random(1,10000) +MenuTable[#MenuTable+1]={Tag=tag,Enty=Menu} +end +table.sort(MenuTable,function(k1,k2)return k1.tag0 then +self:ScheduleOnce(Delay,ZONE_BASE.UndrawZone,self) +else +if self.DrawID then +if type(self.DrawID)~="table"then +UTILS.RemoveMark(self.DrawID) +else +for _,mark_id in pairs(self.DrawID)do +UTILS.RemoveMark(mark_id) +end +end +end +end +return self +end +function ZONE_BASE:GetDrawID() +return self.DrawID +end +function ZONE_BASE:SmokeZone(SmokeColor) +end +function ZONE_BASE:SetZoneProbability(ZoneProbability) +self.ZoneProbability=ZoneProbability or 1 +return self +end +function ZONE_BASE:GetZoneProbability() +return self.ZoneProbability +end +function ZONE_BASE:FindNearestCoordinateOnRadius(Outsidecoordinate) +local Vec1=self:GetVec2() +local Radius=self:GetRadius() +local Vec2=Outsidecoordinate:GetVec2() +local Point=UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) +local rc=COORDINATE:NewFromVec2(Point) +return rc +end +function ZONE_BASE:GetZoneMaybe() +local Randomization=math.random() +if Randomization<=self.ZoneProbability then +return self +else +return nil +end +end +function ZONE_BASE:SetCheckTime(seconds) +self.Checktime=seconds or 5 +return self +end +function ZONE_BASE:Trigger(Objects) +self:SetStartState("TriggerStopped") +self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning") +self:AddTransition("*","EnteredZone","*") +self:AddTransition("*","LeftZone","*") +self:AddTransition("*","ZoneEmpty","*") +self:AddTransition("*","ObjectDead","*") +self:AddTransition("*","TriggerRunCheck","*") +self:AddTransition("*","TriggerStop","TriggerStopped") +self:TriggerStart() +self.checkobjects=Objects +self.ObjectsInZone=false +if UTILS.IsInstanceOf(Objects,"SET_BASE")then +self.objectset=Objects.Set +else +self.objectset={Objects} +end +self:_TriggerCheck(true) +self:__TriggerRunCheck(self.Checktime) +return self +end +function ZONE_BASE:SetPartlyInside(state) +self.PartlyInside=state or not(state==false) +return self +end +function ZONE_BASE:_TriggerCheck(fromstart) +local objectset=self.objectset or{} +if fromstart then +for _,_object in pairs(objectset)do +local obj=_object +if not obj.TriggerInZone then +obj.TriggerInZone={} +obj.TriggerZoneDeadNotification=false +end +if obj and obj:IsAlive()and self:IsCoordinateInZone(obj:GetCoordinate())then +obj.TriggerInZone[self.ZoneName]=true +self.ObjectsInZone=true +else +obj.TriggerInZone[self.ZoneName]=false +end +end +else +local objcount=0 +for _,_object in pairs(objectset)do +local obj=_object +if obj and obj:IsAlive()then +if not obj.TriggerInZone then +obj.TriggerInZone={} +end +if not obj.TriggerInZone[self.ZoneName]then +obj.TriggerInZone[self.ZoneName]=false +end +local inzone +if self.PartlyInside and obj.ClassName=="GROUP"then +inzone=obj:IsAnyInZone(self) +else +inzone=self:IsCoordinateInZone(obj:GetCoordinate()) +end +if inzone and obj.TriggerInZone[self.ZoneName]then +objcount=objcount+1 +self.ObjectsInZone=true +obj.TriggerZoneDeadNotification=false +end +if inzone and not obj.TriggerInZone[self.ZoneName]then +self:__EnteredZone(0.5,obj) +obj.TriggerInZone[self.ZoneName]=true +objcount=objcount+1 +self.ObjectsInZone=true +obj.TriggerZoneDeadNotification=false +elseif(not inzone)and obj.TriggerInZone[self.ZoneName]then +self:__LeftZone(0.5,obj) +obj.TriggerInZone[self.ZoneName]=false +else +end +else +if not obj.TriggerZoneDeadNotification==true then +obj.TriggerInZone=nil +self:__ObjectDead(0.5,obj) +obj.TriggerZoneDeadNotification=true +end +end +end +if objcount==0 and self.ObjectsInZone==true then +self.ObjectsInZone=false +self:__ZoneEmpty(0.5) +end +end +return self +end +function ZONE_BASE:onafterTriggerRunCheck(From,Event,To) +if self:GetState()~="TriggerStopped"then +self:_TriggerCheck() +self:__TriggerRunCheck(self.Checktime) +end +return self +end +function ZONE_BASE:GetProperty(PropertyName) +return self.Properties[PropertyName] +end +function ZONE_BASE:GetAllProperties() +return self.Properties +end +ZONE_RADIUS={ +ClassName="ZONE_RADIUS", +} +function ZONE_RADIUS:New(ZoneName,Vec2,Radius,DoNotRegisterZone) +local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName)) +self.Radius=Radius +self.Vec2=Vec2 +if not DoNotRegisterZone then +_EVENTDISPATCHER:CreateEventNewZone(self) +end +return self +end +function ZONE_RADIUS:UpdateFromVec2(Vec2,Radius) +self.Vec2=Vec2 +if Radius then +self.Radius=Radius +end +return self +end +function ZONE_RADIUS:UpdateFromVec3(Vec3,Radius) +self.Vec2.x=Vec3.x +self.Vec2.y=Vec3.z +if Radius then +self.Radius=Radius +end +return self +end +function ZONE_RADIUS:MarkZone(Points) +local Point={} +local Vec2=self:GetVec2() +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,(360/Points)do +local Radial=Angle*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName()) +end +end +function ZONE_RADIUS:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly) +local coordinate=self:GetCoordinate() +local Radius=self:GetRadius() +Color=Color or self:GetColorRGB() +Alpha=Alpha or 1 +FillColor=FillColor or UTILS.DeepCopy(Color) +FillAlpha=FillAlpha or self:GetColorAlpha() +self.DrawID=coordinate:CircleToAll(Radius,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly) +return self +end +function ZONE_RADIUS:BoundZone(Points,CountryID,UnBound) +local Point={} +local Vec2=self:GetVec2() +local countryID=CountryID or country.id.USA +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,(360/Points)do +local Radial=Angle*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +local CountryName=_DATABASE.COUNTRY_NAME[countryID] +local Tire={ +["country"]=CountryName, +["category"]="Fortifications", +["canCargo"]=false, +["shape_name"]="H-tyre_B_WF", +["type"]="Black_Tyre_WF", +["y"]=Point.y, +["x"]=Point.x, +["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle), +["heading"]=0, +} +local Group=coalition.addStaticObject(countryID,Tire) +if UnBound and UnBound==true then +Group:destroy() +end +end +return self +end +function ZONE_RADIUS:SmokeZone(SmokeColor,Points,AddHeight,AngleOffset) +local Point={} +local Vec2=self:GetVec2() +AddHeight=AddHeight or 0 +AngleOffset=AngleOffset or 0 +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,360/Points do +local Radial=(Angle+AngleOffset)*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +COORDINATE:New(Point.x,AddHeight,Point.y):Smoke(SmokeColor) +end +return self +end +function ZONE_RADIUS:FlareZone(FlareColor,Points,Azimuth,AddHeight) +local Point={} +local Vec2=self:GetVec2() +AddHeight=AddHeight or 0 +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,360/Points do +local Radial=Angle*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +COORDINATE:New(Point.x,AddHeight,Point.y):Flare(FlareColor,Azimuth) +end +return self +end +function ZONE_RADIUS:GetRadius() +return self.Radius +end +function ZONE_RADIUS:SetRadius(Radius) +self.Radius=Radius +return self.Radius +end +function ZONE_RADIUS:GetVec2() +return self.Vec2 +end +function ZONE_RADIUS:SetVec2(Vec2) +self.Vec2=Vec2 +return self.Vec2 +end +function ZONE_RADIUS:GetVec3(Height) +Height=Height or 0 +local Vec2=self:GetVec2() +local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y} +return Vec3 +end +function ZONE_RADIUS:Scan(ObjectCategories,UnitCategories) +self.ScanData={} +self.ScanData.Coalitions={} +self.ScanData.Scenery={} +self.ScanData.SceneryTable={} +self.ScanData.Units={} +local ZoneCoord=self:GetCoordinate() +local ZoneRadius=self:GetRadius() +local SphereSearch={ +id=world.VolumeType.SPHERE, +params={ +point=ZoneCoord:GetVec3(), +radius=ZoneRadius, +} +} +local function EvaluateZone(ZoneObject) +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())then +local ObjectCategory=Object.getCategory(ZoneObject) +if(ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive())or(ObjectCategory==Object.Category.STATIC and ZoneObject:isExist())then +local Include=false +if not UnitCategories then +Include=true +else +local CategoryDCSUnit=ZoneObject:getDesc().category +for UnitCategoryID,UnitCategory in pairs(UnitCategories)do +if UnitCategory==CategoryDCSUnit then +Include=true +break +end +end +end +if Include then +local CoalitionDCSUnit=ZoneObject:getCoalition() +self.ScanData.Coalitions[CoalitionDCSUnit]=true +self.ScanData.Units[ZoneObject]=ZoneObject +end +end +if ObjectCategory==Object.Category.SCENERY then +local SceneryType=ZoneObject:getTypeName() +local SceneryName=ZoneObject:getName() +self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} +self.ScanData.Scenery[SceneryType][SceneryName]=SCENERY:Register(tostring(SceneryName),ZoneObject) +table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName]) +end +end +return true +end +world.searchObjects(ObjectCategories,SphereSearch,EvaluateZone) +end +function ZONE_RADIUS:RemoveJunk() +local radius=self.Radius +local vec3=self:GetVec3() +local volS={ +id=world.VolumeType.SPHERE, +params={point=vec3,radius=radius} +} +local n=world.removeJunk(volS) +return n +end +function ZONE_RADIUS:GetScannedUnits() +return self.ScanData.Units +end +function ZONE_RADIUS:GetScannedSetUnit() +local SetUnit=SET_UNIT:New() +if self.ScanData then +for ObjectID,UnitObject in pairs(self.ScanData.Units)do +local UnitObject=UnitObject +if UnitObject:isExist()then +local FoundUnit=UNIT:FindByName(UnitObject:getName()) +if FoundUnit then +SetUnit:AddUnit(FoundUnit) +else +local FoundStatic=STATIC:FindByName(UnitObject:getName(),false) +if FoundStatic then +SetUnit:AddUnit(FoundStatic) +end +end +end +end +end +return SetUnit +end +function ZONE_RADIUS:GetScannedSetGroup() +self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup.Set={} +if self.ScanData then +for ObjectID,UnitObject in pairs(self.ScanData.Units)do +local UnitObject=UnitObject +if UnitObject:isExist()then +local FoundUnit=UNIT:FindByName(UnitObject:getName()) +if FoundUnit then +local group=FoundUnit:GetGroup() +self.ScanSetGroup:AddGroup(group) +end +end +end +end +return self.ScanSetGroup +end +function ZONE_RADIUS:CountScannedCoalitions() +local Count=0 +for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do +Count=Count+1 +end +return Count +end +function ZONE_RADIUS:CheckScannedCoalition(Coalition) +if Coalition then +return self.ScanData.Coalitions[Coalition] +end +return nil +end +function ZONE_RADIUS:GetScannedCoalition(Coalition) +if Coalition then +return self.ScanData.Coalitions[Coalition] +else +local Count=0 +local ReturnCoalition=nil +for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do +Count=Count+1 +ReturnCoalition=CoalitionID +end +if Count~=1 then +ReturnCoalition=nil +end +return ReturnCoalition +end +end +function ZONE_RADIUS:GetScannedSceneryType(SceneryType) +return self.ScanData.Scenery[SceneryType] +end +function ZONE_RADIUS:GetScannedScenery() +return self.ScanData.Scenery +end +function ZONE_RADIUS:GetScannedSceneryObjects() +return self.ScanData.SceneryTable +end +function ZONE_RADIUS:GetScannedSetScenery() +local scenery=SET_SCENERY:New() +local objects=self:GetScannedSceneryObjects() +for _,_obj in pairs(objects)do +scenery:AddScenery(_obj) +end +return scenery +end +function ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition) +return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==true +end +function ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition) +return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==nil +end +function ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition) +return self:CountScannedCoalitions()>1 and self:GetScannedCoalition(Coalition)==true +end +function ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition) +return self:GetScannedCoalition(Coalition)==nil +end +function ZONE_RADIUS:IsNoneInZone() +return self:CountScannedCoalitions()==0 +end +function ZONE_RADIUS:SearchZone(EvaluateFunction,ObjectCategories) +local SearchZoneResult=true +local ZoneCoord=self:GetCoordinate() +local ZoneRadius=self:GetRadius() +local SphereSearch={ +id=world.VolumeType.SPHERE, +params={ +point=ZoneCoord:GetVec3(), +radius=ZoneRadius, +} +} +local function EvaluateZone(ZoneDCSUnit) +local ZoneUnit=UNIT:Find(ZoneDCSUnit) +return EvaluateFunction(ZoneUnit) +end +world.searchObjects(Object.Category.UNIT,SphereSearch,EvaluateZone) +end +function ZONE_RADIUS:IsVec2InZone(Vec2) +if not Vec2 then return false end +local ZoneVec2=self:GetVec2() +if ZoneVec2 then +if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then +return true +end +end +return false +end +function ZONE_RADIUS:IsVec3InZone(Vec3) +if not Vec3 then return false end +local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z}) +return InZone +end +function ZONE_RADIUS:GetClearZonePositions(PosRadius,NumPositions) +return UTILS.GetClearZonePositions(self,PosRadius,NumPositions) +end +function ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius,NumPositions) +return UTILS.GetRandomClearZoneCoordinate(self,PosRadius,NumPositions) +end +function ZONE_RADIUS:GetRandomVec2(inner,outer,surfacetypes) +local Vec2=self:GetVec2() +local _inner=inner or 0 +local _outer=outer or self:GetRadius() +math.random() +math.random() +math.random() +if surfacetypes and type(surfacetypes)~="table"then +surfacetypes={surfacetypes} +end +local function _getpoint() +local point={} +local angle=math.random()*math.pi*2 +point.x=Vec2.x+math.cos(angle)*math.random(_inner,_outer) +point.y=Vec2.y+math.sin(angle)*math.random(_inner,_outer) +return point +end +local function _checkSurface(point) +local stype=land.getSurfaceType(point) +for _,sf in pairs(surfacetypes)do +if sf==stype then +return true +end +end +return false +end +local point=_getpoint() +if surfacetypes then +local N=1;local Nmax=100;local gotit=false +while gotit==false and N<=Nmax do +gotit=_checkSurface(point) +if gotit then +else +point=_getpoint() +N=N+1 +end +end +end +return point +end +function ZONE_RADIUS:GetRandomPointVec2(inner,outer) +local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2(inner,outer)) +return PointVec2 +end +function ZONE_RADIUS:GetRandomVec3(inner,outer) +local Vec2=self:GetRandomVec2(inner,outer) +return{x=Vec2.x,y=self.y,z=Vec2.y} +end +function ZONE_RADIUS:GetRandomPointVec3(inner,outer) +local PointVec3=COORDINATE:NewFromVec2(self:GetRandomVec2(inner,outer)) +return PointVec3 +end +function ZONE_RADIUS:GetRandomCoordinate(inner,outer,surfacetypes) +local vec2=self:GetRandomVec2(inner,outer,surfacetypes) +local Coordinate=COORDINATE:NewFromVec2(vec2) +return Coordinate +end +function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,markbuildings,markfinal) +local dist=distance or 100 +local objects={} +if self.ScanData and self.ScanData.Scenery then +objects=self:GetScannedScenery() +else +self:Scan({Object.Category.SCENERY}) +objects=self:GetScannedScenery() +end +local T0=timer.getTime() +local T1=timer.getTime() +local buildings={} +local buildingzones={} +if self.ScanData and self.ScanData.BuildingCoordinates then +buildings=self.ScanData.BuildingCoordinates +buildingzones=self.ScanData.BuildingZones +else +for _,_object in pairs(objects)do +for _,_scen in pairs(_object)do +local scenery=_scen +local description=scenery:GetDesc() +if description and description.attributes and description.attributes.Buildings then +if markbuildings then +MARKER:New(scenery:GetCoordinate(),"Building"):ToAll() +end +buildings[#buildings+1]=scenery:GetCoordinate() +local bradius=scenery:GetBoundingRadius()or dist +local bzone=ZONE_RADIUS:New("Building-"..math.random(1,100000),scenery:GetVec2(),bradius,false) +buildingzones[#buildingzones+1]=bzone +end +end +end +self.ScanData.BuildingCoordinates=buildings +self.ScanData.BuildingZones=buildingzones +end +local rcoord=nil +local found=true +local iterations=0 +for i=1,1000 do +iterations=iterations+1 +rcoord=self:GetRandomCoordinate(inner,outer) +found=true +for _,_coord in pairs(buildingzones)do +local zone=_coord +if zone:IsPointVec2InZone(rcoord)then +found=false +break +end +end +if found then +if markfinal then +MARKER:New(rcoord,"FREE"):ToAll() +end +break +end +end +if not found then +local rcoord=nil +local found=true +local iterations=0 +for i=1,1000 do +iterations=iterations+1 +rcoord=self:GetRandomCoordinate(inner,outer) +found=true +for _,_coord in pairs(buildings)do +local coord=_coord +if coord:Get3DDistance(rcoord)0)or(d2>0)or(d3>0) +return not(has_neg and has_pos) +end +function _ZONE_TRIANGLE:GetRandomVec2(points) +points=points or self.Points +local pt={math.random(),math.random()} +table.sort(pt) +local s=pt[1] +local t=pt[2]-pt[1] +local u=1-pt[2] +return{x=s*points[1].x+t*points[2].x+u*points[3].x, +y=s*points[1].y+t*points[2].y+u*points[3].y} +end +function _ZONE_TRIANGLE:Draw(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly) +Coalition=Coalition or-1 +Color=Color or{1,0,0} +Alpha=Alpha or 1 +FillColor=FillColor or Color +if not FillColor then UTILS.DeepCopy(Color)end +FillAlpha=FillAlpha or Alpha +if not FillAlpha then FillAlpha=1 end +for i=1,#self.Coords do +local c1=self.Coords[i] +local c2=self.Coords[i%#self.Coords+1] +local id=c1:LineToAll(c2,Coalition,Color,Alpha,LineType,ReadOnly) +self.DrawID[#self.DrawID+1]=id +end +local newID=self.Coords[1]:MarkupToAllFreeForm({self.Coords[2],self.Coords[3]},Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly) +self.DrawID[#self.DrawID+1]=newID +return self.DrawID +end +function _ZONE_TRIANGLE:Fill(Coalition,FillColor,FillAlpha,ReadOnly) +Coalition=Coalition or-1 +FillColor=FillColor +FillAlpha=FillAlpha +local newID=self.Coords[1]:MarkupToAllFreeForm({self.Coords[2],self.Coords[3]},Coalition,nil,nil,FillColor,FillAlpha,0,nil) +self.DrawID[#self.DrawID+1]=newID +return self.DrawID +end +ZONE_POLYGON_BASE={ +ClassName="ZONE_POLYGON_BASE", +_Triangles={}, +SurfaceArea=0, +DrawID={}, +FillTriangles={}, +Borderlines={}, +} +function ZONE_POLYGON_BASE:New(ZoneName,PointsArray) +local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName)) +if PointsArray then +self._.Polygon={} +for i=1,#PointsArray do +self._.Polygon[i]={} +self._.Polygon[i].x=PointsArray[i].x +self._.Polygon[i].y=PointsArray[i].y +end +self._Triangles=self:_Triangulate() +self.SurfaceArea=self:_CalculateSurfaceArea() +end +return self +end +function ZONE_POLYGON_BASE:_Triangulate() +local points=self._.Polygon +local triangles={} +local function get_orientation(shape_points) +local sum=0 +for i=1,#shape_points do +local j=i%#shape_points+1 +sum=sum+(shape_points[j].x-shape_points[i].x)*(shape_points[j].y+shape_points[i].y) +end +return sum>=0 and"clockwise"or"counter-clockwise" +end +local function ensure_clockwise(shape_points) +local orientation=get_orientation(shape_points) +if orientation=="counter-clockwise"then +local reversed={} +for i=#shape_points,1,-1 do +table.insert(reversed,shape_points[i]) +end +return reversed +end +return shape_points +end +local function is_clockwise(p1,p2,p3) +local cross_product=(p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x) +return cross_product<0 +end +local function divide_recursively(shape_points) +if#shape_points==3 then +table.insert(triangles,_ZONE_TRIANGLE:New(shape_points[1],shape_points[2],shape_points[3])) +elseif#shape_points>3 then +for i,p1 in ipairs(shape_points)do +local p2=shape_points[(i%#shape_points)+1] +local p3=shape_points[(i+1)%#shape_points+1] +local triangle=_ZONE_TRIANGLE:New(p1,p2,p3) +local is_ear=true +if not is_clockwise(p1,p2,p3)then +is_ear=false +else +for _,point in ipairs(shape_points)do +if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then +is_ear=false +break +end +end +end +if is_ear then +local is_valid_triangle=true +for _,point in ipairs(points)do +if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then +is_valid_triangle=false +break +end +end +if is_valid_triangle then +table.insert(triangles,triangle) +local remaining_points={} +for j,point in ipairs(shape_points)do +if point~=p2 then +table.insert(remaining_points,point) +end +end +divide_recursively(remaining_points) +break +end +else +end +end +end +end +points=ensure_clockwise(points) +divide_recursively(points) +return triangles +end +function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) +self._.Polygon={} +for i=1,#Vec2Array do +self._.Polygon[i]={} +self._.Polygon[i].x=Vec2Array[i].x +self._.Polygon[i].y=Vec2Array[i].y +end +self._Triangles=self:_Triangulate() +self.SurfaceArea=self:_CalculateSurfaceArea() +return self +end +function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) +self._.Polygon={} +for i=1,#Vec3Array do +self._.Polygon[i]={} +self._.Polygon[i].x=Vec3Array[i].x +self._.Polygon[i].y=Vec3Array[i].z +end +self._Triangles=self:_Triangulate() +self.SurfaceArea=self:_CalculateSurfaceArea() +return self +end +function ZONE_POLYGON_BASE:_CalculateSurfaceArea() +local area=0 +for _,triangle in pairs(self._Triangles)do +area=area+triangle.SurfaceArea +end +return area +end +function ZONE_POLYGON_BASE:GetVec2() +local Bounds=self:GetBoundingSquare() +return{x=(Bounds.x2+Bounds.x1)/2,y=(Bounds.y2+Bounds.y1)/2} +end +function ZONE_POLYGON_BASE:GetVertexVec2(Index) +return self._.Polygon[Index or 1] +end +function ZONE_POLYGON_BASE:GetVertexVec3(Index) +local vec2=self:GetVertexVec2(Index) +if vec2 then +local vec3={x=vec2.x,y=land.getHeight(vec2),z=vec2.y} +return vec3 +end +return nil +end +function ZONE_POLYGON_BASE:GetVertexCoordinate(Index) +local vec2=self:GetVertexVec2(Index) +if vec2 then +local coord=COORDINATE:NewFromVec2(vec2) +return coord +end +return nil +end +function ZONE_POLYGON_BASE:GetVerticiesVec2() +return self._.Polygon +end +function ZONE_POLYGON_BASE:GetVerticiesVec3() +local coords={} +for i,vec2 in ipairs(self._.Polygon)do +local vec3={x=vec2.x,y=land.getHeight(vec2),z=vec2.y} +table.insert(coords,vec3) +end +return coords +end +function ZONE_POLYGON_BASE:GetVerticiesCoordinates() +local coords={} +for i,vec2 in ipairs(self._.Polygon)do +local coord=COORDINATE:NewFromVec2(vec2) +table.insert(coords,coord) +end +return coords +end +function ZONE_POLYGON_BASE:Flush() +return self +end +function ZONE_POLYGON_BASE:GetClearZonePositions(PosRadius,NumPositions) +return UTILS.GetClearZonePositions(self,PosRadius,NumPositions) +end +function ZONE_POLYGON_BASE:GetRandomClearZoneCoordinate(PosRadius,NumPositions) +return UTILS.GetRandomClearZoneCoordinate(self,PosRadius,NumPositions) +end +function ZONE_POLYGON_BASE:BoundZone(UnBound) +local i +local j +local Segments=10 +i=1 +j=#self._.Polygon +while i<=#self._.Polygon do +local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x +local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y +for Segment=0,Segments do +local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments) +local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments) +local Tire={ +["country"]="USA", +["category"]="Fortifications", +["canCargo"]=false, +["shape_name"]="H-tyre_B_WF", +["type"]="Black_Tyre_WF", +["y"]=PointY, +["x"]=PointX, +["name"]=string.format("%s-Tire #%0d",self:GetName(),((i-1)*Segments)+Segment), +["heading"]=0, +} +local Group=coalition.addStaticObject(country.id.USA,Tire) +if UnBound and UnBound==true then +Group:destroy() +end +end +j=i +i=i+1 +end +return self +end +function ZONE_POLYGON_BASE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,IncludeTriangles) +if self._.Polygon and#self._.Polygon>=3 then +Coalition=Coalition or self:GetDrawCoalition() +self:SetDrawCoalition(Coalition) +Color=Color or self:GetColorRGB() +Alpha=Alpha or self:GetColorAlpha() +FillColor=FillColor or self:GetFillColorRGB() +FillAlpha=FillAlpha or self:GetFillColorAlpha() +if FillColor then +self:ReFill(FillColor,FillAlpha) +end +if Color then +self:ReDrawBorderline(Color,Alpha,LineType) +end +end +if false then +local coords=self:GetVerticiesCoordinates() +local coord=coords[1] +table.remove(coords,1) +coord:MarkupToAllFreeForm(coords,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,"Drew Polygon") +if true then +return +end +end +return self +end +function ZONE_POLYGON_BASE:ReFill(Color,Alpha) +local color=Color or self:GetFillColorRGB()or{1,0,0} +local alpha=Alpha or self:GetFillColorAlpha()or 1 +local coalition=self:GetDrawCoalition()or-1 +if#self.FillTriangles>0 then +for _,triangle in pairs(self._Triangles)do +triangle:UndrawZone() +end +for _,_value in pairs(self.FillTriangles)do +table.remove_by_value(self.DrawID,_value) +end +self.FillTriangles=nil +self.FillTriangles={} +end +for _,triangle in pairs(self._Triangles)do +local draw_ids=triangle:Fill(coalition,color,alpha,nil) +self.FillTriangles=draw_ids +table.combine(self.DrawID,draw_ids) +end +return self +end +function ZONE_POLYGON_BASE:ReDrawBorderline(Color,Alpha,LineType) +local color=Color or self:GetFillColorRGB()or{1,0,0} +local alpha=Alpha or self:GetFillColorAlpha()or 1 +local coalition=self:GetDrawCoalition()or-1 +local linetype=LineType or 1 +if#self.Borderlines>0 then +for _,MarkID in pairs(self.Borderlines)do +trigger.action.removeMark(MarkID) +end +for _,_value in pairs(self.Borderlines)do +table.remove_by_value(self.DrawID,_value) +end +self.Borderlines=nil +self.Borderlines={} +end +local coords=self:GetVerticiesCoordinates() +for i=1,#coords do +local c1=coords[i] +local c2=coords[i%#coords+1] +local newID=c1:LineToAll(c2,coalition,color,alpha,linetype,nil) +self.DrawID[#self.DrawID+1]=newID +self.Borderlines[#self.Borderlines+1]=newID +end +return self +end +function ZONE_POLYGON_BASE:GetSurfaceArea() +return self.SurfaceArea +end +function ZONE_POLYGON_BASE:GetRadius() +local center=self:GetVec2() +local radius=0 +for _,_vec2 in pairs(self._.Polygon)do +local vec2=_vec2 +local r=UTILS.VecDist2D(center,vec2) +if r>radius then +radius=r +end +end +return radius +end +function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName,DoNotRegisterZone) +local center=self:GetVec2() +local radius=self:GetRadius() +local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName,center,radius,DoNotRegisterZone) +return zone +end +function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName,DoNotRegisterZone) +local vec1,vec3=self:GetBoundingVec2() +local vec2={x=vec1.x,y=vec3.y} +local vec4={x=vec3.x,y=vec1.y} +local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName,{vec1,vec2,vec3,vec4}) +return zone +end +function ZONE_POLYGON_BASE:RemoveJunk(Height) +Height=Height or 1000 +local vec2SW,vec2NE=self:GetBoundingVec2() +local vec3SW={x=vec2SW.x,y=-Height,z=vec2SW.y} +local vec3NE={x=vec2NE.x,y=Height,z=vec2NE.y} +local volume={ +id=world.VolumeType.BOX, +params={ +min=vec3SW, +max=vec3SW +} +} +local n=world.removeJunk(volume) +return n +end +function ZONE_POLYGON_BASE:SmokeZone(SmokeColor,Segments) +Segments=Segments or 10 +local i=1 +local j=#self._.Polygon +while i<=#self._.Polygon do +local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x +local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y +for Segment=0,Segments do +local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments) +local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments) +COORDINATE:New(PointX,0,PointY):Smoke(SmokeColor) +end +j=i +i=i+1 +end +return self +end +function ZONE_POLYGON_BASE:FlareZone(FlareColor,Segments,Azimuth,AddHeight) +Segments=Segments or 10 +AddHeight=AddHeight or 0 +local i=1 +local j=#self._.Polygon +while i<=#self._.Polygon do +local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x +local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y +for Segment=0,Segments do +local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments) +local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments) +COORDINATE:New(PointX,AddHeight,PointY):Flare(FlareColor,Azimuth) +end +j=i +i=i+1 +end +return self +end +function ZONE_POLYGON_BASE:IsVec2InZone(Vec2) +if not Vec2 then return false end +local Next +local Prev +local InPolygon=false +Next=1 +Prev=#self._.Polygon +while Next<=#self._.Polygon do +if(((self._.Polygon[Next].y>Vec2.y)~=(self._.Polygon[Prev].y>Vec2.y))and +(Vec2.x<(self._.Polygon[Prev].x-self._.Polygon[Next].x)*(Vec2.y-self._.Polygon[Next].y)/(self._.Polygon[Prev].y-self._.Polygon[Next].y)+self._.Polygon[Next].x) +)then +InPolygon=not InPolygon +end +Prev=Next +Next=Next+1 +end +return InPolygon +end +function ZONE_POLYGON_BASE:IsVec3InZone(Vec3) +if not Vec3 then return false end +local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z}) +return InZone +end +function ZONE_POLYGON_BASE:GetRandomVec2() +math.random() +math.random() +math.random() +local weights={} +for _,triangle in pairs(self._Triangles)do +weights[triangle]=triangle.SurfaceArea/self.SurfaceArea +end +local random_weight=math.random() +local accumulated_weight=0 +for triangle,weight in pairs(weights)do +accumulated_weight=accumulated_weight+weight +if accumulated_weight>=random_weight then +return triangle:GetRandomVec2() +end +end +end +function ZONE_POLYGON_BASE:GetRandomPointVec2() +local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2()) +return PointVec2 +end +function ZONE_POLYGON_BASE:GetRandomPointVec3() +local PointVec3=COORDINATE:NewFromVec2(self:GetRandomVec2()) +return PointVec3 +end +function ZONE_POLYGON_BASE:GetRandomCoordinate() +local Coordinate=COORDINATE:NewFromVec2(self:GetRandomVec2()) +return Coordinate +end +function ZONE_POLYGON_BASE:GetBoundingSquare() +local x1=self._.Polygon[1].x +local y1=self._.Polygon[1].y +local x2=self._.Polygon[1].x +local y2=self._.Polygon[1].y +for i=2,#self._.Polygon do +x1=(x1>self._.Polygon[i].x)and self._.Polygon[i].x or x1 +x2=(x2self._.Polygon[i].y)and self._.Polygon[i].y or y1 +y2=(y2self._.Polygon[i].x)and self._.Polygon[i].x or x1 +x2=(x2self._.Polygon[i].y)and self._.Polygon[i].y or y1 +y2=(y21 and self:GetScannedCoalition(Coalition)==true +end +function ZONE_POLYGON:IsNoneInZoneOfCoalition(Coalition) +return self:GetScannedCoalition(Coalition)==nil +end +function ZONE_POLYGON:IsNoneInZone() +return self:CountScannedCoalitions()==0 +end +end +do +ZONE_ELASTIC={ +ClassName="ZONE_ELASTIC", +points={}, +setGroups={} +} +function ZONE_ELASTIC:New(ZoneName,Points) +local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,Points)) +_EVENTDISPATCHER:CreateEventNewZone(self) +if Points then +self.points=Points +end +return self +end +function ZONE_ELASTIC:AddVertex2D(Vec2) +table.insert(self.points,Vec2) +return self +end +function ZONE_ELASTIC:RemoveVertex2D(Vec2) +local found=false +local findex=0 +for _id,_vec2 in pairs(self.points)do +if _vec2.x==Vec2.x and _vec2.y==Vec2.y then +found=true +findex=_id +break +end +end +if found==true and findex>0 then +table.remove(self.points,findex) +end +return self +end +function ZONE_ELASTIC:RemoveVertex3D(Vec3) +return self:RemoveVertex2D({x=Vec3.x,y=Vec3.z}) +end +function ZONE_ELASTIC:AddVertex3D(Vec3) +table.insert(self.points,{x=Vec3.x,y=Vec3.z}) +return self +end +function ZONE_ELASTIC:AddSetGroup(GroupSet) +table.insert(self.setGroups,GroupSet) +return self +end +function ZONE_ELASTIC:Update(Delay,Draw) +local points=UTILS.DeepCopy(self.points or{}) +if self.setGroups then +for _,_setGroup in pairs(self.setGroups)do +local setGroup=_setGroup +for _,_group in pairs(setGroup.Set)do +local group=_group +if group and group:IsAlive()then +table.insert(points,group:GetVec2()) +end +end +end +end +self._.Polygon=self:_ConvexHull(points) +self._Triangles=self:_Triangulate() +self.SurfaceArea=self:_CalculateSurfaceArea() +if Draw~=false then +if self.DrawID or Draw==true then +self:UndrawZone() +self:DrawZone() +end +end +return self +end +function ZONE_ELASTIC:StartUpdate(Tstart,dT,Tstop,Draw) +self.updateID=self:ScheduleRepeat(Tstart,dT,0,Tstop,ZONE_ELASTIC.Update,self,0,Draw) +return self +end +function ZONE_ELASTIC:StopUpdate(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,ZONE_ELASTIC.StopUpdate,self) +else +if self.updateID then +self:ScheduleStop(self.updateID) +self.updateID=nil +end +end +return self +end +function ZONE_ELASTIC:_ConvexHull(pl) +if#pl==0 then +return{} +end +table.sort(pl,function(left,right) +return left.x(b.y-a.y)*(c.x-a.x) +end +for i,pt in pairs(pl)do +while#h>=2 and not ccw(h[#h-1],h[#h],pt)do +table.remove(h,#h) +end +table.insert(h,pt) +end +local t=#h+1 +for i=#pl,1,-1 do +local pt=pl[i] +while#h>=t and not ccw(h[#h-1],h[#h],pt)do +table.remove(h,#h) +end +table.insert(h,pt) +end +table.remove(h,#h) +return h +end +end +ZONE_OVAL={ +ClassName="OVAL", +ZoneName="", +MajorAxis=nil, +MinorAxis=nil, +Angle=0, +DrawPoly=nil +} +function ZONE_OVAL:New(name,vec2,major_axis,minor_axis,angle) +self=BASE:Inherit(self,ZONE_BASE:New()) +self.ZoneName=name +self.CenterVec2=vec2 +self.MajorAxis=major_axis +self.MinorAxis=minor_axis +self.Angle=angle or 0 +_DATABASE:AddZone(name,self) +return self +end +function ZONE_OVAL:NewFromDrawing(DrawingName) +self=BASE:Inherit(self,ZONE_BASE:New(DrawingName)) +for _,layer in pairs(env.mission.drawings.layers)do +for _,object in pairs(layer["objects"])do +if string.find(object["name"],DrawingName,1,true)then +if object["polygonMode"]=="oval"then +self.CenterVec2={x=object["mapX"],y=object["mapY"]} +self.MajorAxis=object["r1"] +self.MinorAxis=object["r2"] +self.Angle=object["angle"] +end +end +end +end +_DATABASE:AddZone(DrawingName,self) +return self +end +function ZONE_OVAL:GetMajorAxis() +return self.MajorAxis +end +function ZONE_OVAL:GetMinorAxis() +return self.MinorAxis +end +function ZONE_OVAL:GetAngle() +return self.Angle +end +function ZONE_OVAL:GetVec2() +return self.CenterVec2 +end +function ZONE_OVAL:IsVec2InZone(vec2) +local cos,sin=math.cos,math.sin +local dx=vec2.x-self.CenterVec2.x +local dy=vec2.y-self.CenterVec2.y +local rx=dx*cos(self.Angle)+dy*sin(self.Angle) +local ry=-dx*sin(self.Angle)+dy*cos(self.Angle) +return rx*rx/(self.MajorAxis*self.MajorAxis)+ry*ry/(self.MinorAxis*self.MinorAxis)<=1 +end +function ZONE_OVAL:GetBoundingSquare() +local min_x=self.CenterVec2.x-self.MajorAxis +local min_y=self.CenterVec2.y-self.MinorAxis +local max_x=self.CenterVec2.x+self.MajorAxis +local max_y=self.CenterVec2.y+self.MinorAxis +return{ +{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y} +} +end +function ZONE_OVAL:PointsOnEdge(num_points) +num_points=num_points or 40 +local points={} +local dtheta=2*math.pi/num_points +for i=0,num_points-1 do +local theta=i*dtheta +local x=self.CenterVec2.x+self.MajorAxis*math.cos(theta)*math.cos(self.Angle)-self.MinorAxis*math.sin(theta)*math.sin(self.Angle) +local y=self.CenterVec2.y+self.MajorAxis*math.cos(theta)*math.sin(self.Angle)+self.MinorAxis*math.sin(theta)*math.cos(self.Angle) +table.insert(points,{x=x,y=y}) +end +return points +end +function ZONE_OVAL:GetRandomVec2() +local theta=math.rad(self.Angle) +local random_point=math.sqrt(math.random()) +local phi=math.random()*2*math.pi +local x_c=random_point*math.cos(phi) +local y_c=random_point*math.sin(phi) +local x_e=x_c*self.MajorAxis +local y_e=y_c*self.MinorAxis +local rx=(x_e*math.cos(theta)-y_e*math.sin(theta))+self.CenterVec2.x +local ry=(x_e*math.sin(theta)+y_e*math.cos(theta))+self.CenterVec2.y +return{x=rx,y=ry} +end +function ZONE_OVAL:GetRandomPointVec2() +return COORDINATE:NewFromVec2(self:GetRandomVec2()) +end +function ZONE_OVAL:GetRandomPointVec3() +return COORDINATE:NewFromVec3(self:GetRandomVec2()) +end +function ZONE_OVAL:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType) +Coalition=Coalition or self:GetDrawCoalition() +self:SetDrawCoalition(Coalition) +Color=Color or self:GetColorRGB() +Alpha=Alpha or 1 +self:SetColor(Color,Alpha) +FillColor=FillColor or self:GetFillColorRGB() +if not FillColor then +UTILS.DeepCopy(Color) +end +FillAlpha=FillAlpha or self:GetFillColorAlpha() +if not FillAlpha then +FillAlpha=0.15 +end +LineType=LineType or 1 +self:SetFillColor(FillColor,FillAlpha) +self.DrawPoly=ZONE_POLYGON:NewFromPointsArray(self.ZoneName,self:PointsOnEdge(80)) +self.DrawPoly:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType) +end +function ZONE_OVAL:UndrawZone() +if self.DrawPoly then +self.DrawPoly:UndrawZone() +end +end +do +ZONE_AIRBASE={ +ClassName="ZONE_AIRBASE", +} +function ZONE_AIRBASE:New(AirbaseName,Radius) +Radius=Radius or 4000 +local Airbase=AIRBASE:FindByName(AirbaseName) +local self=BASE:Inherit(self,ZONE_RADIUS:New(AirbaseName,Airbase:GetVec2(),Radius,true)) +self._.ZoneAirbase=Airbase +self._.ZoneVec2Cache=self._.ZoneAirbase:GetVec2() +if Airbase:IsShip()then +self.isShip=true +self.isHelipad=false +self.isAirdrome=false +elseif Airbase:IsHelipad()then +self.isShip=false +self.isHelipad=true +self.isAirdrome=false +elseif Airbase:IsAirdrome()then +self.isShip=false +self.isHelipad=false +self.isAirdrome=true +end +_EVENTDISPATCHER:CreateEventNewZone(self) +return self +end +function ZONE_AIRBASE:GetAirbase() +return self._.ZoneAirbase +end +function ZONE_AIRBASE:GetVec2() +local ZoneVec2=nil +if self._.ZoneAirbase:IsAlive()then +ZoneVec2=self._.ZoneAirbase:GetVec2() +self._.ZoneVec2Cache=ZoneVec2 +else +ZoneVec2=self._.ZoneVec2Cache +end +return ZoneVec2 +end +function ZONE_AIRBASE:GetRandomPointVec2(inner,outer) +local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2()) +return PointVec2 +end +end +ZONE_DETECTION={ +ClassName="ZONE_DETECTION", +} +function ZONE_DETECTION:New(ZoneName,Detection,Radius) +local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName)) +self:F({ZoneName,Detection,Radius}) +self.Detection=Detection +self.Radius=Radius +return self +end +function ZONE_DETECTION:BoundZone(Points,CountryID,UnBound) +local Point={} +local Vec2=self:GetVec2() +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,(360/Points)do +local Radial=Angle*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +local CountryName=_DATABASE.COUNTRY_NAME[CountryID] +local Tire={ +["country"]=CountryName, +["category"]="Fortifications", +["canCargo"]=false, +["shape_name"]="H-tyre_B_WF", +["type"]="Black_Tyre_WF", +["y"]=Point.y, +["x"]=Point.x, +["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle), +["heading"]=0, +} +local Group=coalition.addStaticObject(CountryID,Tire) +if UnBound and UnBound==true then +Group:destroy() +end +end +return self +end +function ZONE_DETECTION:SmokeZone(SmokeColor,Points,AddHeight,AngleOffset) +self:F2(SmokeColor) +local Point={} +local Vec2=self:GetVec2() +AddHeight=AddHeight or 0 +AngleOffset=AngleOffset or 0 +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,360/Points do +local Radial=(Angle+AngleOffset)*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +COORDINATE:New(Point.x,AddHeight,Point.y):Smoke(SmokeColor) +end +return self +end +function ZONE_DETECTION:FlareZone(FlareColor,Points,Azimuth,AddHeight) +self:F2({FlareColor,Azimuth}) +local Point={} +local Vec2=self:GetVec2() +AddHeight=AddHeight or 0 +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,360/Points do +local Radial=Angle*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +COORDINATE:New(Point.x,AddHeight,Point.y):Flare(FlareColor,Azimuth) +end +return self +end +function ZONE_DETECTION:GetRadius() +self:F2(self.ZoneName) +self:T2({self.Radius}) +return self.Radius +end +function ZONE_DETECTION:SetRadius(Radius) +self:F2(self.ZoneName) +self.Radius=Radius +self:T2({self.Radius}) +return self.Radius +end +function ZONE_DETECTION:IsVec2InZone(Vec2) +self:F2(Vec2) +local Coordinates=self.Detection:GetDetectedItemCoordinates() +for CoordinateID,Coordinate in pairs(Coordinates)do +local ZoneVec2=Coordinate:GetVec2() +if ZoneVec2 then +if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then +return true +end +end +end +return false +end +function ZONE_DETECTION:IsVec3InZone(Vec3) +self:F2(Vec3) +local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z}) +return InZone +end +DATABASE={ +ClassName="DATABASE", +Templates={ +Units={}, +Groups={}, +Statics={}, +ClientsByName={}, +ClientsByID={}, +}, +UNITS={}, +UNITS_Index={}, +STATICS={}, +GROUPS={}, +PLAYERS={}, +PLAYERSJOINED={}, +PLAYERUNITS={}, +CLIENTS={}, +CARGOS={}, +AIRBASES={}, +COUNTRY_ID={}, +COUNTRY_NAME={}, +NavPoints={}, +PLAYERSETTINGS={}, +ZONENAMES={}, +HITS={}, +DESTROYS={}, +ZONES={}, +ZONES_GOAL={}, +WAREHOUSES={}, +FLIGHTGROUPS={}, +FLIGHTCONTROLS={}, +OPSZONES={}, +PATHLINES={}, +STORAGES={}, +STNS={}, +SADL={}, +DYNAMICCARGO={}, +} +local _DATABASECoalition= +{ +[1]="Red", +[2]="Blue", +[3]="Neutral", +} +local _DATABASECategory= +{ +["plane"]=Unit.Category.AIRPLANE, +["helicopter"]=Unit.Category.HELICOPTER, +["vehicle"]=Unit.Category.GROUND_UNIT, +["ship"]=Unit.Category.SHIP, +["static"]=Unit.Category.STRUCTURE, +} +function DATABASE:New() +local self=BASE:Inherit(self,BASE:New()) +self:SetEventPriority(1) +self:HandleEvent(EVENTS.Birth,self._EventOnBirth) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventOnPlayerEnterUnit) +self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) +self:HandleEvent(EVENTS.RemoveUnit,self._EventOnDeadOrCrash) +self:HandleEvent(EVENTS.UnitLost,self._EventOnDeadOrCrash) +self:HandleEvent(EVENTS.Hit,self.AccountHits) +self:HandleEvent(EVENTS.NewCargo) +self:HandleEvent(EVENTS.DeleteCargo) +self:HandleEvent(EVENTS.NewZone) +self:HandleEvent(EVENTS.DeleteZone) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventOnPlayerLeaveUnit) +self:HandleEvent(EVENTS.DynamicCargoRemoved,self._EventOnDynamicCargoRemoved) +self:_RegisterTemplates() +self:_RegisterGroupsAndUnits() +self:_RegisterClients() +self:_RegisterStatics() +self.UNITS_Position=0 +return self +end +function DATABASE:FindUnit(UnitName) +local UnitFound=self.UNITS[UnitName] +return UnitFound +end +function DATABASE:AddUnit(DCSUnitName,force) +local DCSunitName=DCSUnitName +if type(DCSunitName)=="number"then DCSunitName=string.format("%d",DCSUnitName)end +if not self.UNITS[DCSunitName]or force==true then +self:T({"Add UNIT:",DCSunitName}) +self.UNITS[DCSunitName]=UNIT:Register(DCSunitName) +end +return self.UNITS[DCSunitName] +end +function DATABASE:DeleteUnit(DCSUnitName) +self:T("DeleteUnit "..tostring(DCSUnitName)) +self.UNITS[DCSUnitName]=nil +end +function DATABASE:AddStatic(DCSStaticName) +if not self.STATICS[DCSStaticName]then +self.STATICS[DCSStaticName]=STATIC:Register(DCSStaticName) +end +return self.STATICS[DCSStaticName] +end +function DATABASE:DeleteStatic(DCSStaticName) +self.STATICS[DCSStaticName]=nil +end +function DATABASE:FindStatic(StaticName) +local StaticFound=self.STATICS[StaticName] +return StaticFound +end +function DATABASE:AddDynamicCargo(Name) +if not self.DYNAMICCARGO[Name]then +self.DYNAMICCARGO[Name]=DYNAMICCARGO:Register(Name) +end +return self.DYNAMICCARGO[Name] +end +function DATABASE:FindDynamicCargo(DynamicCargoName) +local StaticFound=self.DYNAMICCARGO[DynamicCargoName] +return StaticFound +end +function DATABASE:DeleteDynamicCargo(DynamicCargoName) +self.DYNAMICCARGO[DynamicCargoName]=nil +return self +end +function DATABASE:AddAirbase(AirbaseName) +if not self.AIRBASES[AirbaseName]then +self.AIRBASES[AirbaseName]=AIRBASE:Register(AirbaseName) +end +return self.AIRBASES[AirbaseName] +end +function DATABASE:DeleteAirbase(AirbaseName) +self.AIRBASES[AirbaseName]=nil +end +function DATABASE:FindAirbase(AirbaseName) +local AirbaseFound=self.AIRBASES[AirbaseName] +return AirbaseFound +end +function DATABASE:AddStorage(AirbaseName) +if not self.STORAGES[AirbaseName]then +self.STORAGES[AirbaseName]=STORAGE:New(AirbaseName) +end +return self.STORAGES[AirbaseName] +end +function DATABASE:DeleteStorage(AirbaseName) +self.STORAGES[AirbaseName]=nil +end +function DATABASE:FindStorage(AirbaseName) +local storage=self.STORAGES[AirbaseName] +return storage +end +do +function DATABASE:FindZone(ZoneName) +local ZoneFound=self.ZONES[ZoneName] +return ZoneFound +end +function DATABASE:AddZone(ZoneName,Zone) +if not self.ZONES[ZoneName]then +self.ZONES[ZoneName]=Zone +end +end +function DATABASE:DeleteZone(ZoneName) +self.ZONES[ZoneName]=nil +end +function DATABASE:AddPathline(PathlineName,Pathline) +if not self.PATHLINES[PathlineName]then +self.PATHLINES[PathlineName]=Pathline +end +end +function DATABASE:FindPathline(PathlineName) +local pathline=self.PATHLINES[PathlineName] +return pathline +end +function DATABASE:DeletePathline(PathlineName) +self.PATHLINES[PathlineName]=nil +return self +end +function DATABASE:_RegisterZones() +for ZoneID,ZoneData in pairs(env.mission.triggers.zones)do +local ZoneName=ZoneData.name +local color=ZoneData.color or{1,0,0,0.15} +local Zone=nil +if ZoneData.type==0 then +self:I(string.format("Register ZONE: %s (Circular)",ZoneName)) +Zone=ZONE:New(ZoneName) +else +self:I(string.format("Register ZONE: %s (Polygon, Quad)",ZoneName)) +Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,ZoneData.verticies) +end +if Zone then +Zone.Color=color +Zone.ZoneID=ZoneData.zoneId +local ZoneProperties=ZoneData.properties or nil +Zone.Properties={} +if ZoneName and ZoneProperties then +for _,ZoneProp in ipairs(ZoneProperties)do +if ZoneProp.key then +Zone.Properties[ZoneProp.key]=ZoneProp.value +end +end +end +self.ZONENAMES[ZoneName]=ZoneName +self:AddZone(ZoneName,Zone) +end +end +for ZoneGroupName,ZoneGroup in pairs(self.GROUPS)do +if ZoneGroupName:match("#ZONE_POLYGON")then +local ZoneName1=ZoneGroupName:match("(.*)#ZONE_POLYGON") +local ZoneName2=ZoneGroupName:match(".*#ZONE_POLYGON(.*)") +local ZoneName=ZoneName1..(ZoneName2 or"") +self:I(string.format("Register ZONE: %s (Polygon)",ZoneName)) +local Zone_Polygon=ZONE_POLYGON:New(ZoneName,ZoneGroup) +Zone_Polygon:SetColor({1,0,0},0.15) +self.ZONENAMES[ZoneName]=ZoneName +self:AddZone(ZoneName,Zone_Polygon) +end +end +if env.mission.drawings and env.mission.drawings.layers then +for layerID,layerData in pairs(env.mission.drawings.layers or{})do +for objectID,objectData in pairs(layerData.objects or{})do +if objectData.polygonMode and(objectData.polygonMode=="free")and objectData.points and#objectData.points>=4 then +local ZoneName=objectData.name or"Unknown free Polygon Drawing" +local vec2={x=objectData.mapX,y=objectData.mapY} +local points=UTILS.DeepCopy(objectData.points) +for i,_point in pairs(points)do +local point=_point +points[i]=UTILS.Vec2Add(point,vec2) +end +table.remove(points,#points) +self:I(string.format("Register ZONE: %s (Polygon (free) drawing with %d vertices)",ZoneName,#points)) +local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points) +Zone:SetColor({1,0,0},0.15) +Zone:SetFillColor({1,0,0},0.15) +if objectData.colorString then +local color=string.gsub(objectData.colorString,"^0x","") +local r=tonumber(string.sub(color,1,2),16)/255 +local g=tonumber(string.sub(color,3,4),16)/255 +local b=tonumber(string.sub(color,5,6),16)/255 +local a=tonumber(string.sub(color,7,8),16)/255 +Zone:SetColor({r,g,b},a) +end +if objectData.fillColorString then +local color=string.gsub(objectData.colorString,"^0x","") +local r=tonumber(string.sub(color,1,2),16)/255 +local g=tonumber(string.sub(color,3,4),16)/255 +local b=tonumber(string.sub(color,5,6),16)/255 +local a=tonumber(string.sub(color,7,8),16)/255 +Zone:SetFillColor({r,g,b},a) +end +self.ZONENAMES[ZoneName]=ZoneName +self:AddZone(ZoneName,Zone) +elseif objectData.polygonMode and objectData.polygonMode=="rect"then +local ZoneName=objectData.name or"Unknown rect Polygon Drawing" +local vec2={x=objectData.mapX,y=objectData.mapY} +local w=objectData.width +local h=objectData.height +local rotation=UTILS.ToRadian(objectData.angle or 0) +local sinRot=math.sin(rotation) +local cosRot=math.cos(rotation) +local dx=h/2 +local dy=w/2 +local points={ +{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y}, +{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y}, +} +self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)",ZoneName,#points)) +local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points) +Zone:SetColor({1,0,0},0.15) +if objectData.colorString then +local color=string.gsub(objectData.colorString,"^0x","") +local r=tonumber(string.sub(color,1,2),16)/255 +local g=tonumber(string.sub(color,3,4),16)/255 +local b=tonumber(string.sub(color,5,6),16)/255 +local a=tonumber(string.sub(color,7,8),16)/255 +Zone:SetColor({r,g,b},a) +end +if objectData.fillColorString then +local color=string.gsub(objectData.colorString,"^0x","") +local r=tonumber(string.sub(color,1,2),16)/255 +local g=tonumber(string.sub(color,3,4),16)/255 +local b=tonumber(string.sub(color,5,6),16)/255 +local a=tonumber(string.sub(color,7,8),16)/255 +Zone:SetFillColor({r,g,b},a) +end +self.ZONENAMES[ZoneName]=ZoneName +self:AddZone(ZoneName,Zone) +elseif objectData.lineMode and(objectData.lineMode=="segments"or objectData.lineMode=="segment"or objectData.lineMode=="free")and objectData.points and#objectData.points>=2 then +local Name=objectData.name or"Unknown Line Drawing" +local vec2={x=objectData.mapX,y=objectData.mapY} +local points=UTILS.DeepCopy(objectData.points) +for i,_point in pairs(points)do +local point=_point +points[i]=UTILS.Vec2Add(point,vec2) +end +self:I(string.format("Register PATHLINE: %s (Line drawing with %d points)",Name,#points)) +local Pathline=PATHLINE:NewFromVec2Array(Name,points) +self:AddPathline(Name,Pathline) +end +end +end +end +end +end +do +function DATABASE:FindZoneGoal(ZoneName) +local ZoneFound=self.ZONES_GOAL[ZoneName] +return ZoneFound +end +function DATABASE:AddZoneGoal(ZoneName,Zone) +if not self.ZONES_GOAL[ZoneName]then +self.ZONES_GOAL[ZoneName]=Zone +end +end +function DATABASE:DeleteZoneGoal(ZoneName) +self.ZONES_GOAL[ZoneName]=nil +end +end +do +function DATABASE:FindOpsZone(ZoneName) +local ZoneFound=self.OPSZONES[ZoneName] +return ZoneFound +end +function DATABASE:AddOpsZone(OpsZone) +if OpsZone then +local ZoneName=OpsZone:GetName() +if not self.OPSZONES[ZoneName]then +self.OPSZONES[ZoneName]=OpsZone +end +end +end +function DATABASE:DeleteOpsZone(ZoneName) +self.OPSZONES[ZoneName]=nil +end +end +do +function DATABASE:AddCargo(Cargo) +if not self.CARGOS[Cargo.Name]then +self.CARGOS[Cargo.Name]=Cargo +end +end +function DATABASE:DeleteCargo(CargoName) +self.CARGOS[CargoName]=nil +end +function DATABASE:FindCargo(CargoName) +local CargoFound=self.CARGOS[CargoName] +return CargoFound +end +function DATABASE:IsCargo(TemplateName) +TemplateName=env.getValueDictByKey(TemplateName) +local Cargo=TemplateName:match("#(CARGO)") +return Cargo and Cargo=="CARGO" +end +function DATABASE:_RegisterCargos() +local Groups=UTILS.DeepCopy(self.GROUPS) +for CargoGroupName,CargoGroup in pairs(Groups)do +if self:IsCargo(CargoGroupName)then +local CargoInfo=CargoGroupName:match("#CARGO(.*)") +local CargoParam=CargoInfo and CargoInfo:match("%((.*)%)") +local CargoName1=CargoGroupName:match("(.*)#CARGO%(.*%)") +local CargoName2=CargoGroupName:match(".*#CARGO%(.*%)(.*)") +local CargoName=CargoName1..(CargoName2 or"") +local Type=CargoParam and CargoParam:match("T=([%a%d ]+),?") +local Name=CargoParam and CargoParam:match("N=([%a%d]+),?")or CargoName +local LoadRadius=CargoParam and tonumber(CargoParam:match("RR=([%a%d]+),?")) +local NearRadius=CargoParam and tonumber(CargoParam:match("NR=([%a%d]+),?")) +self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) +CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius) +end +end +for CargoStaticName,CargoStatic in pairs(self.STATICS)do +if self:IsCargo(CargoStaticName)then +local CargoInfo=CargoStaticName:match("#CARGO(.*)") +local CargoParam=CargoInfo and CargoInfo:match("%((.*)%)") +local CargoName=CargoStaticName:match("(.*)#CARGO") +local Type=CargoParam and CargoParam:match("T=([%a%d ]+),?") +local Category=CargoParam and CargoParam:match("C=([%a%d ]+),?") +local Name=CargoParam and CargoParam:match("N=([%a%d]+),?")or CargoName +local LoadRadius=CargoParam and tonumber(CargoParam:match("RR=([%a%d]+),?")) +local NearRadius=CargoParam and tonumber(CargoParam:match("NR=([%a%d]+),?")) +if Category=="SLING"then +self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) +CARGO_SLINGLOAD:New(CargoStatic,Type,Name,LoadRadius,NearRadius) +else +if Category=="CRATE"then +self:I({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) +CARGO_CRATE:New(CargoStatic,Type,Name,LoadRadius,NearRadius) +end +end +end +end +end +end +function DATABASE:FindClient(ClientName) +local ClientFound=self.CLIENTS[ClientName] +return ClientFound +end +function DATABASE:AddClient(ClientName,Force) +local DCSUnitName=ClientName +if type(DCSUnitName)=="number"then DCSUnitName=string.format("%d",ClientName)end +if not self.CLIENTS[DCSUnitName]or Force==true then +self.CLIENTS[DCSUnitName]=CLIENT:Register(DCSUnitName) +end +return self.CLIENTS[DCSUnitName] +end +function DATABASE:FindGroup(GroupName) +if type(GroupName)~="string"or GroupName==""then return end +local GroupFound=self.GROUPS[GroupName] +if GroupFound==nil and GroupName~=nil and self.Templates.Groups[GroupName]==nil then +self:_RegisterDynamicGroup(GroupName) +return self.GROUPS[GroupName] +end +return GroupFound +end +function DATABASE:AddGroup(GroupName,force) +if not self.GROUPS[GroupName]or force==true then +self:T({"Add GROUP:",GroupName}) +self.GROUPS[GroupName]=GROUP:Register(GroupName) +end +return self.GROUPS[GroupName] +end +function DATABASE:AddPlayer(UnitName,PlayerName) +if type(UnitName)=="number"then UnitName=string.format("%d",UnitName)end +if PlayerName then +self:I({"Add player for unit:",UnitName,PlayerName}) +self.PLAYERS[PlayerName]=UnitName +self.PLAYERUNITS[PlayerName]=self:FindUnit(UnitName) +self.PLAYERSJOINED[PlayerName]=PlayerName +end +end +function DATABASE:_FindPlayerNameByUnitName(UnitName) +if UnitName then +for playername,unitname in pairs(self.PLAYERS)do +if unitname==UnitName and self.PLAYERUNITS[playername]and self.PLAYERUNITS[playername]:IsAlive()then +return playername,self.PLAYERUNITS[playername] +end +end +end +return nil +end +function DATABASE:DeletePlayer(UnitName,PlayerName) +if PlayerName then +self:T({"Clean player:",PlayerName}) +self.PLAYERS[PlayerName]=nil +self.PLAYERUNITS[PlayerName]=nil +end +end +function DATABASE:GetPlayers() +return self.PLAYERS +end +function DATABASE:GetPlayerUnits() +return self.PLAYERUNITS +end +function DATABASE:GetPlayersJoined() +return self.PLAYERSJOINED +end +function DATABASE:Spawn(SpawnTemplate) +self:F(SpawnTemplate.name) +self:T({SpawnTemplate.SpawnCountryID,SpawnTemplate.SpawnCategoryID}) +local SpawnCoalitionID=SpawnTemplate.CoalitionID +local SpawnCountryID=SpawnTemplate.CountryID +local SpawnCategoryID=SpawnTemplate.CategoryID +SpawnTemplate.CoalitionID=nil +SpawnTemplate.CountryID=nil +SpawnTemplate.CategoryID=nil +self:_RegisterGroupTemplate(SpawnTemplate,SpawnCoalitionID,SpawnCategoryID,SpawnCountryID,SpawnTemplate.name) +self:T3(SpawnTemplate) +coalition.addGroup(SpawnCountryID,SpawnCategoryID,SpawnTemplate) +SpawnTemplate.CoalitionID=SpawnCoalitionID +SpawnTemplate.CountryID=SpawnCountryID +SpawnTemplate.CategoryID=SpawnCategoryID +local SpawnGroup=self:AddGroup(SpawnTemplate.name) +for UnitID,UnitData in pairs(SpawnTemplate.units)do +self:AddUnit(UnitData.name) +end +return SpawnGroup +end +function DATABASE:SetStatusGroup(GroupName,Status) +self:F2(Status) +self.Templates.Groups[GroupName].Status=Status +end +function DATABASE:GetStatusGroup(GroupName) +self:F2(GroupName) +if self.Templates.Groups[GroupName]then +return self.Templates.Groups[GroupName].Status +else +return"" +end +end +function DATABASE:_RegisterGroupTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID,GroupName) +local GroupTemplateName=GroupName or env.getValueDictByKey(GroupTemplate.name) +if not self.Templates.Groups[GroupTemplateName]then +self.Templates.Groups[GroupTemplateName]={} +self.Templates.Groups[GroupTemplateName].Status=nil +end +if GroupTemplate.route and GroupTemplate.route.spans then +GroupTemplate.route.spans=nil +end +GroupTemplate.CategoryID=CategoryID +GroupTemplate.CoalitionID=CoalitionSide +GroupTemplate.CountryID=CountryID +self.Templates.Groups[GroupTemplateName].GroupName=GroupTemplateName +self.Templates.Groups[GroupTemplateName].Template=GroupTemplate +self.Templates.Groups[GroupTemplateName].groupId=GroupTemplate.groupId +self.Templates.Groups[GroupTemplateName].UnitCount=#GroupTemplate.units +self.Templates.Groups[GroupTemplateName].Units=GroupTemplate.units +self.Templates.Groups[GroupTemplateName].CategoryID=CategoryID +self.Templates.Groups[GroupTemplateName].CoalitionID=CoalitionSide +self.Templates.Groups[GroupTemplateName].CountryID=CountryID +local UnitNames={} +for unit_num,UnitTemplate in pairs(GroupTemplate.units)do +UnitTemplate.name=env.getValueDictByKey(UnitTemplate.name) +self.Templates.Units[UnitTemplate.name]={} +self.Templates.Units[UnitTemplate.name].UnitName=UnitTemplate.name +self.Templates.Units[UnitTemplate.name].Template=UnitTemplate +self.Templates.Units[UnitTemplate.name].GroupName=GroupTemplateName +self.Templates.Units[UnitTemplate.name].GroupTemplate=GroupTemplate +self.Templates.Units[UnitTemplate.name].GroupId=GroupTemplate.groupId +self.Templates.Units[UnitTemplate.name].CategoryID=CategoryID +self.Templates.Units[UnitTemplate.name].CoalitionID=CoalitionSide +self.Templates.Units[UnitTemplate.name].CountryID=CountryID +if UnitTemplate.skill and(UnitTemplate.skill=="Client"or UnitTemplate.skill=="Player")then +self.Templates.ClientsByName[UnitTemplate.name]=UnitTemplate +self.Templates.ClientsByName[UnitTemplate.name].CategoryID=CategoryID +self.Templates.ClientsByName[UnitTemplate.name].CoalitionID=CoalitionSide +self.Templates.ClientsByName[UnitTemplate.name].CountryID=CountryID +self.Templates.ClientsByID[UnitTemplate.unitId]=UnitTemplate +end +if UnitTemplate.AddPropAircraft then +if UnitTemplate.AddPropAircraft.STN_L16 then +local stn=UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.STN_L16) +if stn==nil or stn<1 then +self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name) +else +self.STNS[stn]=UnitTemplate.name +self:T("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name) +end +end +if UnitTemplate.AddPropAircraft.SADL_TN then +local sadl=UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.SADL_TN) +if sadl==nil or sadl<1 then +self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name) +else +self.SADL[sadl]=UnitTemplate.name +self:T("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name) +end +end +end +UnitNames[#UnitNames+1]=self.Templates.Units[UnitTemplate.name].UnitName +end +self:T({Group=self.Templates.Groups[GroupTemplateName].GroupName, +Coalition=self.Templates.Groups[GroupTemplateName].CoalitionID, +Category=self.Templates.Groups[GroupTemplateName].CategoryID, +Country=self.Templates.Groups[GroupTemplateName].CountryID, +Units=UnitNames +} +) +end +function DATABASE:GetNextSTN(octal,unitname) +local first=UTILS.OctalToDecimal(octal)or 0 +if self.STNS[first]==unitname then return octal end +local nextoctal=77777 +local found=false +if 32767-first<10 then +first=0 +end +for i=first+1,32767 do +if self.STNS[i]==nil then +found=true +nextoctal=UTILS.DecimalToOctal(i) +self.STNS[i]=unitname +self:T("Register STN "..tostring(nextoctal).." for "..unitname) +break +end +end +if not found then +self:E(string.format("WARNING: No next free STN past %05d found!",octal)) +local NewSTNS={} +for _id,_name in pairs(self.STNS)do +if self.UNITS[_name]~=nil then +NewSTNS[_id]=_name +end +end +self.STNS=nil +self.STNS=NewSTNS +end +return nextoctal +end +function DATABASE:GetNextSADL(octal,unitname) +local first=UTILS.OctalToDecimal(octal)or 0 +if self.SADL[first]==unitname then return octal end +local nextoctal=7777 +local found=false +if 4095-first<10 then +first=0 +end +for i=first+1,4095 do +if self.STNS[i]==nil then +found=true +nextoctal=UTILS.DecimalToOctal(i) +self.SADL[i]=unitname +self:T("Register SADL "..tostring(nextoctal).." for "..unitname) +break +end +end +if not found then +self:E(string.format("WARNING: No next free SADL past %04d found!",octal)) +local NewSTNS={} +for _id,_name in pairs(self.SADL)do +if self.UNITS[_name]~=nil then +NewSTNS[_id]=_name +end +end +self.SADL=nil +self.SADL=NewSTNS +end +return nextoctal +end +function DATABASE:GetGroupTemplate(GroupName) +local GroupTemplate=nil +if self.Templates.Groups[GroupName]then +GroupTemplate=self.Templates.Groups[GroupName].Template +GroupTemplate.SpawnCoalitionID=self.Templates.Groups[GroupName].CoalitionID +GroupTemplate.SpawnCategoryID=self.Templates.Groups[GroupName].CategoryID +GroupTemplate.SpawnCountryID=self.Templates.Groups[GroupName].CountryID +end +return GroupTemplate +end +function DATABASE:_RegisterStaticTemplate(StaticTemplate,CoalitionID,CategoryID,CountryID) +local StaticTemplate=UTILS.DeepCopy(StaticTemplate) +local StaticTemplateGroupName=env.getValueDictByKey(StaticTemplate.name) +local StaticTemplateName=StaticTemplate.units[1].name +self.Templates.Statics[StaticTemplateName]=self.Templates.Statics[StaticTemplateName]or{} +StaticTemplate.CategoryID=CategoryID +StaticTemplate.CoalitionID=CoalitionID +StaticTemplate.CountryID=CountryID +self.Templates.Statics[StaticTemplateName].StaticName=StaticTemplateGroupName +self.Templates.Statics[StaticTemplateName].GroupTemplate=StaticTemplate +self.Templates.Statics[StaticTemplateName].UnitTemplate=StaticTemplate.units[1] +self.Templates.Statics[StaticTemplateName].CategoryID=CategoryID +self.Templates.Statics[StaticTemplateName].CoalitionID=CoalitionID +self.Templates.Statics[StaticTemplateName].CountryID=CountryID +self:T({Static=self.Templates.Statics[StaticTemplateName].StaticName, +Coalition=self.Templates.Statics[StaticTemplateName].CoalitionID, +Category=self.Templates.Statics[StaticTemplateName].CategoryID, +Country=self.Templates.Statics[StaticTemplateName].CountryID +} +) +self:AddStatic(StaticTemplateName) +return self +end +function DATABASE:_GetGenericStaticCargoGroupTemplate(Name,Typename,Mass,Coalition,Country) +local StaticTemplate={} +StaticTemplate.name=Name or"None" +StaticTemplate.units={[1]={ +name=Name, +resourcePayload={ +["weapons"]={}, +["aircrafts"]={}, +["gasoline"]=0, +["diesel"]=0, +["methanol_mixture"]=0, +["jet_fuel"]=0, +}, +["mass"]=Mass or 0, +["category"]="Cargos", +["canCargo"]=true, +["type"]=Typename or"container_cargo", +["rate"]=100, +["y"]=0, +["x"]=0, +["heading"]=0, +}} +StaticTemplate.CategoryID="static" +StaticTemplate.CoalitionID=Coalition or coalition.side.BLUE +StaticTemplate.CountryID=Country or country.id.GERMANY +return StaticTemplate +end +function DATABASE:GetStaticGroupTemplate(StaticName) +if self.Templates.Statics[StaticName]then +local StaticTemplate=self.Templates.Statics[StaticName].GroupTemplate +return StaticTemplate,self.Templates.Statics[StaticName].CoalitionID,self.Templates.Statics[StaticName].CategoryID,self.Templates.Statics[StaticName].CountryID +else +self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName)) +return nil +end +end +function DATABASE:GetStaticUnitTemplate(StaticName) +if self.Templates.Statics[StaticName]then +local UnitTemplate=self.Templates.Statics[StaticName].UnitTemplate +return UnitTemplate,self.Templates.Statics[StaticName].CoalitionID,self.Templates.Statics[StaticName].CategoryID,self.Templates.Statics[StaticName].CountryID +else +self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName)) +return nil +end +end +function DATABASE:GetGroupNameFromUnitName(UnitName) +if self.Templates.Units[UnitName]then +return self.Templates.Units[UnitName].GroupName +else +self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) +return nil +end +end +function DATABASE:GetGroupTemplateFromUnitName(UnitName) +if self.Templates.Units[UnitName]then +return self.Templates.Units[UnitName].GroupTemplate +else +self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) +return nil +end +end +function DATABASE:GetUnitTemplateFromUnitName(UnitName) +if self.Templates.Units[UnitName]then +return self.Templates.Units[UnitName] +else +self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) +return nil +end +end +function DATABASE:GetCoalitionFromClientTemplate(ClientName) +if self.Templates.ClientsByName[ClientName]then +return self.Templates.ClientsByName[ClientName].CoalitionID +end +self:T("WARNING: Template does not exist for client "..tostring(ClientName)) +return nil +end +function DATABASE:GetCategoryFromClientTemplate(ClientName) +if self.Templates.ClientsByName[ClientName]then +return self.Templates.ClientsByName[ClientName].CategoryID +end +self:T("WARNING: Template does not exist for client "..tostring(ClientName)) +return nil +end +function DATABASE:GetCountryFromClientTemplate(ClientName) +if self.Templates.ClientsByName[ClientName]then +return self.Templates.ClientsByName[ClientName].CountryID +end +self:T("WARNING: Template does not exist for client "..tostring(ClientName)) +return nil +end +function DATABASE:GetCoalitionFromAirbase(AirbaseName) +return self.AIRBASES[AirbaseName]:GetCoalition() +end +function DATABASE:GetCategoryFromAirbase(AirbaseName) +return self.AIRBASES[AirbaseName]:GetAirbaseCategory() +end +function DATABASE:_RegisterPlayers() +local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE),AlivePlayersNeutral=coalition.getPlayers(coalition.side.NEUTRAL)} +for CoalitionId,CoalitionData in pairs(CoalitionsData)do +for UnitId,UnitData in pairs(CoalitionData)do +self:T3({"UnitData:",UnitData}) +if UnitData and UnitData:isExist()then +local UnitName=UnitData:getName() +local PlayerName=UnitData:getPlayerName() +if not self.PLAYERS[PlayerName]then +self:I({"Add player for unit:",UnitName,PlayerName}) +self:AddPlayer(UnitName,PlayerName) +end +end +end +end +return self +end +function DATABASE:_RegisterDynamicGroup(Groupname) +local DCSGroup=Group.getByName(Groupname) +if DCSGroup and DCSGroup:isExist()then +local DCSGroupName=DCSGroup:getName() +self:I(string.format("Register Group: %s",tostring(DCSGroupName))) +self:AddGroup(DCSGroupName,true) +for DCSUnitId,DCSUnit in pairs(DCSGroup:getUnits())do +local DCSUnitName=DCSUnit:getName() +self:I(string.format("Register Unit: %s",tostring(DCSUnitName))) +self:AddUnit(tostring(DCSUnitName),true) +end +else +self:E({"Group does not exist: ",DCSGroup}) +end +return self +end +function DATABASE:_RegisterGroupsAndUnits() +local CoalitionsData={GroupsRed=coalition.getGroups(coalition.side.RED),GroupsBlue=coalition.getGroups(coalition.side.BLUE),GroupsNeutral=coalition.getGroups(coalition.side.NEUTRAL)} +for CoalitionId,CoalitionData in pairs(CoalitionsData)do +for DCSGroupId,DCSGroup in pairs(CoalitionData)do +if DCSGroup:isExist()then +local DCSGroupName=DCSGroup:getName() +self:I(string.format("Register Group: %s",tostring(DCSGroupName))) +self:AddGroup(DCSGroupName) +for DCSUnitId,DCSUnit in pairs(DCSGroup:getUnits())do +local DCSUnitName=DCSUnit:getName() +self:I(string.format("Register Unit: %s",tostring(DCSUnitName))) +self:AddUnit(DCSUnitName) +end +else +self:E({"Group does not exist: ",DCSGroup}) +end +end +end +return self +end +function DATABASE:_RegisterClients() +for ClientName,ClientTemplate in pairs(self.Templates.ClientsByName)do +self:I(string.format("Register Client: %s",tostring(ClientName))) +local client=self:AddClient(ClientName) +client.SpawnCoord=COORDINATE:New(ClientTemplate.x,ClientTemplate.alt,ClientTemplate.y) +end +return self +end +function DATABASE:_RegisterStatics() +local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED),GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE),GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)} +for CoalitionId,CoalitionData in pairs(CoalitionsData)do +for DCSStaticId,DCSStatic in pairs(CoalitionData)do +if DCSStatic:isExist()then +local DCSStaticName=DCSStatic:getName() +self:I(string.format("Register Static: %s",tostring(DCSStaticName))) +self:AddStatic(DCSStaticName) +else +self:E({"Static does not exist: ",DCSStatic}) +end +end +end +return self +end +function DATABASE:_RegisterAirbases() +for DCSAirbaseId,DCSAirbase in pairs(world.getAirbases())do +self:_RegisterAirbase(DCSAirbase) +end +return self +end +function DATABASE:_RegisterAirbase(airbase) +local IsSyria=UTILS.GetDCSMap()=="Syria"and true or false +local countHSyria=0 +if airbase then +local DCSAirbaseName=airbase:getName() +if IsSyria and DCSAirbaseName=="H"and countHSyria>0 then +return self +elseif IsSyria and DCSAirbaseName=="H"and countHSyria==0 then +countHSyria=countHSyria+1 +end +local airbaseID=airbase:getID() +local airbase=self:AddAirbase(DCSAirbaseName) +local airbaseUID=airbase:GetID(true) +local typename=airbase:GetTypeName() +local category=airbase.category +if category==Airbase.Category.SHIP and typename=="FARP_SINGLE_01"then +category=Airbase.Category.HELIPAD +end +local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [",AIRBASE.CategoryName[category],tostring(DCSAirbaseName),airbaseUID,#airbase.runways,airbase.NparkingTotal) +for _,terminalType in pairs(AIRBASE.TerminalType)do +if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType]then +text=text..string.format("%d=%d ",terminalType,airbase.NparkingTerminal[terminalType]) +end +end +text=text.."]" +self:I(text) +end +return self +end +function DATABASE:_EventOnBirth(Event) +self:T({Event}) +if Event.IniDCSUnit then +if Event.IniObjectCategory==Object.Category.STATIC then +self:AddStatic(Event.IniDCSUnitName) +elseif Event.IniObjectCategory==Object.Category.CARGO and string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+")then +local cargo=self:AddDynamicCargo(Event.IniDCSUnitName) +self:I(string.format("Adding dynamic cargo %s",tostring(Event.IniDCSUnitName))) +self:CreateEventNewDynamicCargo(cargo) +else +if Event.IniObjectCategory==Object.Category.UNIT then +self:AddUnit(Event.IniDCSUnitName) +self:AddGroup(Event.IniDCSGroupName) +local DCSAirbase=Airbase.getByName(Event.IniDCSUnitName) +if DCSAirbase then +self:I(string.format("Adding airbase %s",tostring(Event.IniDCSUnitName))) +self:AddAirbase(Event.IniDCSUnitName) +end +end +end +if Event.IniObjectCategory==Object.Category.UNIT then +Event.IniGroup=self:FindGroup(Event.IniDCSGroupName) +Event.IniUnit=self:FindUnit(Event.IniDCSUnitName) +local client=self.CLIENTS[Event.IniDCSUnitName] +if client then +end +local PlayerName=Event.IniUnit:GetPlayerName() +if PlayerName then +self:I(string.format("Player '%s' joined unit '%s' (%s) of group '%s'",tostring(PlayerName),tostring(Event.IniDCSUnitName),tostring(Event.IniTypeName),tostring(Event.IniDCSGroupName))) +if client==nil or(client and client:CountPlayers()==0)then +client=self:AddClient(Event.IniDCSUnitName,true) +end +client:AddPlayer(PlayerName) +if not self.PLAYERS[PlayerName]then +self:AddPlayer(Event.IniUnitName,PlayerName) +end +local function SetPlayerSettings(self,PlayerName,IniUnit) +local Settings=SETTINGS:Set(PlayerName) +Settings:SetPlayerMenu(IniUnit) +self:CreateEventPlayerEnterAircraft(IniUnit) +end +self:ScheduleOnce(1,SetPlayerSettings,self,PlayerName,Event.IniUnit) +end +end +end +end +function DATABASE:_EventOnDeadOrCrash(Event) +if Event.IniDCSUnit then +local name=Event.IniDCSUnitName +if Event.IniObjectCategory==3 then +if self.STATICS[Event.IniDCSUnitName]then +self:DeleteStatic(Event.IniDCSUnitName) +end +if self.UNITS[Event.IniDCSUnitName]then +self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName)) +local DCSUnit=_DATABASE:FindUnit(Event.IniDCSUnitName) +self:T({DCSUnit}) +if DCSUnit then +return +end +end +else +if Event.IniObjectCategory==1 then +if self.UNITS[Event.IniDCSUnitName]then +self:ScheduleOnce(1,self.DeleteUnit,self,Event.IniDCSUnitName) +end +local client=self.CLIENTS[name] +if client then +client:RemovePlayers() +end +end +end +local airbase=self.AIRBASES[Event.IniDCSUnitName] +if airbase and(airbase:IsHelipad()or airbase:IsShip())then +self:DeleteAirbase(Event.IniDCSUnitName) +end +end +self:AccountDestroys(Event) +end +function DATABASE:_EventOnPlayerEnterUnit(Event) +self:F2({Event}) +if Event.IniDCSUnit then +if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then +local IsPlayer=Event.IniDCSUnit:getPlayerName() +if IsPlayer then +self:I(string.format("Player '%s' joined GROUND unit '%s' of group '%s'",tostring(Event.IniPlayerName),tostring(Event.IniDCSUnitName),tostring(Event.IniDCSGroupName))) +local client=self.CLIENTS[Event.IniDCSUnitName] +if not client then +client=self:AddClient(Event.IniDCSUnitName) +end +client:AddPlayer(Event.IniPlayerName) +if not self.PLAYERS[Event.IniPlayerName]then +self:AddPlayer(Event.IniUnitName,Event.IniPlayerName) +end +local Settings=SETTINGS:Set(Event.IniPlayerName) +Settings:SetPlayerMenu(Event.IniUnit) +end +end +end +end +function DATABASE:_EventOnDynamicCargoRemoved(Event) +self:T({Event}) +if Event.IniDynamicCargoName then +self:DeleteDynamicCargo(Event.IniDynamicCargoName) +end +end +function DATABASE:_EventOnPlayerLeaveUnit(Event) +self:F2({Event}) +local function FindPlayerName(UnitName) +local playername=nil +for _name,_unitname in pairs(self.PLAYERS)do +if _unitname==UnitName then +playername=_name +break +end +end +return playername +end +if Event.IniUnit then +if Event.IniObjectCategory==1 then +local PlayerName=Event.IniPlayerName or Event.IniUnit:GetPlayerName()or FindPlayerName(Event.IniUnitName) +if PlayerName then +self:I(string.format("Player '%s' left unit %s",tostring(PlayerName),tostring(Event.IniUnitName))) +local Settings=SETTINGS:Set(PlayerName) +Settings:RemovePlayerMenu(Event.IniUnit) +self:DeletePlayer(Event.IniUnit,PlayerName) +local client=self.CLIENTS[Event.IniDCSUnitName] +if client then +client:RemovePlayer(PlayerName) +end +end +end +end +end +function DATABASE:ForEach(IteratorFunction,FinalizeFunction,arg,Set) +self:F2(arg) +local function CoRoutine() +local Count=0 +for ObjectID,Object in pairs(Set)do +self:T2(Object) +IteratorFunction(Object,unpack(arg)) +Count=Count+1 +end +return true +end +local co=CoRoutine +local function Schedule() +local status,res=co() +self:T3({status,res}) +if status==false then +error(res) +end +if res==false then +return true +end +if FinalizeFunction then +FinalizeFunction(unpack(arg)) +end +return false +end +Schedule() +return self +end +function DATABASE:ForEachStatic(IteratorFunction,FinalizeFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,FinalizeFunction,arg,self.STATICS) +return self +end +function DATABASE:ForEachUnit(IteratorFunction,FinalizeFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,FinalizeFunction,arg,self.UNITS) +return self +end +function DATABASE:ForEachGroup(IteratorFunction,FinalizeFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,FinalizeFunction,arg,self.GROUPS) +return self +end +function DATABASE:ForEachPlayer(IteratorFunction,FinalizeFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERS) +return self +end +function DATABASE:ForEachPlayerJoined(IteratorFunction,FinalizeFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERSJOINED) +return self +end +function DATABASE:ForEachPlayerUnit(IteratorFunction,FinalizeFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERUNITS) +return self +end +function DATABASE:ForEachClient(IteratorFunction,FinalizeFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,FinalizeFunction,arg,self.CLIENTS) +return self +end +function DATABASE:ForEachCargo(IteratorFunction,FinalizeFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,FinalizeFunction,arg,self.CARGOS) +return self +end +function DATABASE:OnEventNewCargo(EventData) +self:F2({EventData}) +if EventData.Cargo then +self:AddCargo(EventData.Cargo) +end +end +function DATABASE:OnEventDeleteCargo(EventData) +self:F2({EventData}) +if EventData.Cargo then +self:DeleteCargo(EventData.Cargo.Name) +end +end +function DATABASE:OnEventNewZone(EventData) +self:F2({EventData}) +if EventData.Zone then +self:AddZone(EventData.Zone.ZoneName,EventData.Zone) +end +end +function DATABASE:OnEventDeleteZone(EventData) +self:F2({EventData}) +if EventData.Zone then +self:DeleteZone(EventData.Zone.ZoneName) +end +end +function DATABASE:GetPlayerSettings(PlayerName) +self:F2({PlayerName}) +return self.PLAYERSETTINGS[PlayerName] +end +function DATABASE:SetPlayerSettings(PlayerName,Settings) +self:F2({PlayerName,Settings}) +self.PLAYERSETTINGS[PlayerName]=Settings +end +function DATABASE:AddOpsGroup(opsgroup) +self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup +end +function DATABASE:GetOpsGroup(groupname) +if type(groupname)=="string"then +else +groupname=groupname:GetName() +end +return self.FLIGHTGROUPS[groupname] +end +function DATABASE:FindOpsGroup(groupname) +if type(groupname)=="string"then +else +groupname=groupname:GetName() +end +return self.FLIGHTGROUPS[groupname] +end +function DATABASE:FindOpsGroupFromUnit(unitname) +local unit=nil +local groupname +if type(unitname)=="string"then +unit=UNIT:FindByName(unitname) +else +unit=unitname +end +if unit then +groupname=unit:GetGroup():GetName() +end +if groupname then +return self.FLIGHTGROUPS[groupname] +else +return nil +end +end +function DATABASE:AddFlightControl(flightcontrol) +self:F2({flightcontrol}) +self.FLIGHTCONTROLS[flightcontrol.airbasename]=flightcontrol +end +function DATABASE:GetFlightControl(airbasename) +return self.FLIGHTCONTROLS[airbasename] +end +function DATABASE:_RegisterTemplates() +self:F2() +self.Navpoints={} +self.UNITS={} +for CoalitionName,coa_data in pairs(env.mission.coalition)do +self:T({CoalitionName=CoalitionName}) +if(CoalitionName=='red'or CoalitionName=='blue'or CoalitionName=='neutrals')and type(coa_data)=='table'then +local CoalitionSide=coalition.side[string.upper(CoalitionName)] +if CoalitionName=="red"then +CoalitionSide=coalition.side.RED +elseif CoalitionName=="blue"then +CoalitionSide=coalition.side.BLUE +else +CoalitionSide=coalition.side.NEUTRAL +end +self.Navpoints[CoalitionName]={} +if coa_data.nav_points then +for nav_ind,nav_data in pairs(coa_data.nav_points)do +if type(nav_data)=='table'then +self.Navpoints[CoalitionName][nav_ind]=UTILS.DeepCopy(nav_data) +self.Navpoints[CoalitionName][nav_ind]['name']=nav_data.callsignStr +self.Navpoints[CoalitionName][nav_ind]['point']={} +self.Navpoints[CoalitionName][nav_ind]['point']['x']=nav_data.x +self.Navpoints[CoalitionName][nav_ind]['point']['y']=0 +self.Navpoints[CoalitionName][nav_ind]['point']['z']=nav_data.y +end +end +end +if coa_data.country then +for cntry_id,cntry_data in pairs(coa_data.country)do +local CountryName=string.upper(cntry_data.name) +local CountryID=cntry_data.id +self.COUNTRY_ID[CountryName]=CountryID +self.COUNTRY_NAME[CountryID]=CountryName +if type(cntry_data)=='table'then +for obj_type_name,obj_type_data in pairs(cntry_data)do +if obj_type_name=="helicopter"or obj_type_name=="ship"or obj_type_name=="plane"or obj_type_name=="vehicle"or obj_type_name=="static"then +local CategoryName=obj_type_name +if((type(obj_type_data)=='table')and obj_type_data.group and(type(obj_type_data.group)=='table')and(#obj_type_data.group>0))then +for group_num,Template in pairs(obj_type_data.group)do +if obj_type_name~="static"and Template and Template.units and type(Template.units)=='table'then +self:_RegisterGroupTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID) +else +self:_RegisterStaticTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID) +end +end +end +end +end +end +end +end +end +end +return self +end +function DATABASE:AccountHits(Event) +self:F({Event}) +if Event.IniPlayerName~=nil then +self:T("Hitting Something") +if Event.TgtCategory then +self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{} +local Hit=self.HITS[Event.TgtUnitName] +Hit.Players=Hit.Players or{} +Hit.Players[Event.IniPlayerName]=true +end +end +if Event.WeaponPlayerName~=nil then +self:T("Hitting Scenery") +if Event.TgtCategory then +if Event.WeaponCoalition then +self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{} +local Hit=self.HITS[Event.TgtUnitName] +Hit.Players=Hit.Players or{} +Hit.Players[Event.WeaponPlayerName]=true +else +end +end +end +end +function DATABASE:AccountDestroys(Event) +self:F({Event}) +local TargetUnit=nil +local TargetGroup=nil +local TargetUnitName="" +local TargetGroupName="" +local TargetPlayerName="" +local TargetCoalition=nil +local TargetCategory=nil +local TargetType=nil +local TargetUnitCoalition=nil +local TargetUnitCategory=nil +local TargetUnitType=nil +if Event.IniDCSUnit then +TargetUnit=Event.IniUnit +TargetUnitName=Event.IniDCSUnitName +TargetGroup=Event.IniDCSGroup +TargetGroupName=Event.IniDCSGroupName +TargetPlayerName=Event.IniPlayerName +TargetCoalition=Event.IniCoalition +TargetCategory=Event.IniCategory +TargetType=Event.IniTypeName +TargetUnitType=TargetType +self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType}) +end +local Destroyed=false +if self.HITS[Event.IniUnitName]then +self.DESTROYS[Event.IniUnitName]=self.DESTROYS[Event.IniUnitName]or{} +self.DESTROYS[Event.IniUnitName]=true +end +end +do +SET_BASE={ +ClassName="SET_BASE", +Filter={}, +Set={}, +List={}, +Index={}, +Database=nil, +CallScheduler=nil, +} +function SET_BASE:New(Database) +local self=BASE:Inherit(self,FSM:New()) +self.Database=Database +self:SetStartState("Started") +self:AddTransition("*","Added","*") +self:AddTransition("*","Removed","*") +self.YieldInterval=10 +self.TimeInterval=0.001 +self.Set={} +self.Index={} +self.CallScheduler=SCHEDULER:New(self) +self:SetEventPriority(2) +return self +end +function SET_BASE:FilterFunction(ConditionFunction,...) +local condition={} +condition.func=ConditionFunction +condition.arg={} +if arg then +condition.arg=arg +end +if not self.Filter.Functions then self.Filter.Functions={}end +table.insert(self.Filter.Functions,condition) +return self +end +function SET_BASE:_EvalFilterFunctions(Object) +for _,_condition in pairs(self.Filter.Functions or{})do +local condition=_condition +if condition.func(Object,unpack(condition.arg))==false then +return false +end +end +return true +end +function SET_BASE:Clear(TriggerEvent) +for Name,Object in pairs(self.Set)do +self:Remove(Name,not TriggerEvent) +end +return self +end +function SET_BASE:_Find(ObjectName) +local ObjectFound=self.Set[ObjectName] +return ObjectFound +end +function SET_BASE:GetSet() +return self.Set or{} +end +function SET_BASE:GetSetNames() +local Names={} +for Name,Object in pairs(self.Set)do +table.insert(Names,Name) +end +return Names +end +function SET_BASE:GetSetObjects() +local Objects={} +for Name,Object in pairs(self.Set)do +table.insert(Objects,Object) +end +return Objects +end +function SET_BASE:Remove(ObjectName,NoTriggerEvent) +local TriggerEvent=true +if NoTriggerEvent then +TriggerEvent=false +else +TriggerEvent=true +end +local Object=self.Set[ObjectName] +if Object then +for Index,Key in ipairs(self.Index)do +if Key==ObjectName then +table.remove(self.Index,Index) +self.Set[ObjectName]=nil +break +end +end +if TriggerEvent then +self:Removed(ObjectName,Object) +end +end +end +function SET_BASE:Add(ObjectName,Object) +if not ObjectName or ObjectName==""then +self:E("SET_BASE:Add - Invalid ObjectName handed") +self:E({ObjectName=ObjectName,Object=Object}) +return self +end +if self.Set[ObjectName]then +self:Remove(ObjectName,true) +end +self.Set[ObjectName]=Object +table.insert(self.Index,ObjectName) +self:Added(ObjectName,Object) +return self +end +function SET_BASE:AddObject(Object) +self:Add(Object.ObjectName,Object) +end +function SET_BASE:SortByName() +local function sort(a,b) +return aThreatMax then +ThreatMax=threat +end +end +return ThreatMax +end +function SET_BASE:FilterOnce() +for ObjectName,Object in pairs(self.Database)do +if self:IsIncludeObject(Object)then +self:Add(ObjectName,Object) +else +self:Remove(ObjectName,true) +end +end +return self +end +function SET_BASE:FilterClear() +for key,value in pairs(self.Filter)do +self.Filter[key]={} +end +return self +end +function SET_BASE:_FilterStart() +for ObjectName,Object in pairs(self.Database)do +if self:IsIncludeObject(Object)then +self:Add(ObjectName,Object) +end +end +return self +end +function SET_BASE:FilterDeads() +self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) +return self +end +function SET_BASE:FilterCrashes() +self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) +return self +end +function SET_BASE:FilterStop() +self:UnHandleEvent(EVENTS.Birth) +self:UnHandleEvent(EVENTS.Dead) +self:UnHandleEvent(EVENTS.Crash) +return self +end +function SET_BASE:FindNearestObjectFromPointVec2(Coordinate) +local NearestObject=nil +local ClosestDistance=nil +for ObjectID,ObjectData in pairs(self.Set)do +if NearestObject==nil then +NearestObject=ObjectData +ClosestDistance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate()) +else +local Distance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate()) +if Distance=Limit then +break +end +end +return true +end +local co=CoRoutine +local function Schedule() +local status,res=co() +if status==false then +error(res) +end +if res==false then +return true +end +return false +end +Schedule() +return self +end +function SET_BASE:IsIncludeObject(Object) +return true +end +function SET_BASE:IsInSet(Object) +local outcome=false +local name=Object:GetName() +self:ForEach( +function(object) +if object:GetName()==name then +outcome=true +end +end +) +return outcome +end +function SET_BASE:IsNotInSet(Object) +return not self:IsInSet(Object) +end +function SET_BASE:GetObjectNames() +local ObjectNames="" +for ObjectName,Object in pairs(self.Set)do +ObjectNames=ObjectNames..ObjectName..", " +end +return ObjectNames +end +function SET_BASE:Flush(MasterObject) +local ObjectNames="" +for ObjectName,Object in pairs(self.Set)do +ObjectNames=ObjectNames..ObjectName..", " +end +return ObjectNames +end +function SET_BASE:GetAliveSet() +local AliveSet={} +for ObjectName,Object in pairs(self.Set)do +if Object then +if Object:IsAlive()then +AliveSet[#AliveSet+1]=Object +end +end +end +return AliveSet or{} +end +end +do +SET_GROUP={ +ClassName="SET_GROUP", +Filter={ +Coalitions=nil, +Categories=nil, +Countries=nil, +GroupPrefixes=nil, +Zones=nil, +Functions=nil, +Alive=nil, +}, +FilterMeta={ +Coalitions={ +red=coalition.side.RED, +blue=coalition.side.BLUE, +neutral=coalition.side.NEUTRAL, +}, +Categories={ +plane=Group.Category.AIRPLANE, +helicopter=Group.Category.HELICOPTER, +ground=Group.Category.GROUND, +ship=Group.Category.SHIP, +structure=Group.Category.STRUCTURE, +}, +}, +} +function SET_GROUP:New() +local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.GROUPS)) +self:FilterActive(false) +return self +end +function SET_GROUP:GetAliveSet() +local AliveSet={} +for GroupName,GroupObject in pairs(self.Set)do +local GroupObject=GroupObject +if GroupObject then +if GroupObject:IsAlive()then +AliveSet[GroupName]=GroupObject +end +end +end +return AliveSet or{} +end +function SET_GROUP:GetUnitTypeNames() +local MT={} +local UnitTypes={} +local ReportUnitTypes=REPORT:New() +for GroupID,GroupData in pairs(self:GetSet())do +local Units=GroupData:GetUnits() +for UnitID,UnitData in pairs(Units)do +if UnitData:IsAlive()then +local UnitType=UnitData:GetTypeName() +if not UnitTypes[UnitType]then +UnitTypes[UnitType]=1 +else +UnitTypes[UnitType]=UnitTypes[UnitType]+1 +end +end +end +end +for UnitTypeID,UnitType in pairs(UnitTypes)do +ReportUnitTypes:Add(UnitType.." of "..UnitTypeID) +end +return ReportUnitTypes +end +function SET_GROUP:AddGroup(group,DontSetCargoBayLimit) +self:Add(group:GetName(),group) +if not DontSetCargoBayLimit then +for UnitID,UnitData in pairs(group:GetUnits()or{})do +if UnitData and UnitData:IsAlive()then +UnitData:SetCargoBayWeightLimit() +end +end +end +return self +end +function SET_GROUP:AddGroupsByName(AddGroupNames) +local AddGroupNamesArray=(type(AddGroupNames)=="table")and AddGroupNames or{AddGroupNames} +for AddGroupID,AddGroupName in pairs(AddGroupNamesArray)do +self:Add(AddGroupName,GROUP:FindByName(AddGroupName)) +end +return self +end +function SET_GROUP:RemoveGroupsByName(RemoveGroupNames) +local RemoveGroupNamesArray=(type(RemoveGroupNames)=="table")and RemoveGroupNames or{RemoveGroupNames} +for RemoveGroupID,RemoveGroupName in pairs(RemoveGroupNamesArray)do +self:Remove(RemoveGroupName) +end +return self +end +function SET_GROUP:FindGroup(GroupName) +local GroupFound=self.Set[GroupName] +return GroupFound +end +function SET_GROUP:FindNearestGroupFromPointVec2(Coordinate) +local NearestGroup=nil +local ClosestDistance=nil +local Set=self:GetAliveSet() +for ObjectID,ObjectData in pairs(Set)do +if NearestGroup==nil then +NearestGroup=ObjectData +ClosestDistance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate()) +else +local Distance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate()) +if DistanceMaxThreatLevelA2G then +MaxThreatLevelA2G=ThreatLevelA2G +MaxThreatText=ThreatText +end +end +return MaxThreatLevelA2G,MaxThreatText +end +function SET_UNIT:GetCoordinate() +local function GetSetVec3(units) +local x=0 +local y=0 +local z=0 +local n=0 +for _,unit in pairs(units)do +local vec3=nil +if unit and unit:IsAlive()then +vec3=unit:GetVec3() +end +if vec3 then +x=x+vec3.x +y=y+vec3.y +z=z+vec3.z +n=n+1 +end +end +if n>0 then +local Vec3={x=x/n,y=y/n,z=z/n} +return Vec3 +end +return nil +end +local Coordinate=nil +local Vec3=GetSetVec3(self.Set) +if Vec3 then +Coordinate=COORDINATE:NewFromVec3(Vec3) +end +if Coordinate then +local heading=self:GetHeading()or 0 +local velocity=self:GetVelocity()or 0 +Coordinate:SetHeading(heading) +Coordinate:SetVelocity(velocity) +end +return Coordinate +end +function SET_UNIT:GetVelocity() +local Coordinate=self:GetFirst():GetCoordinate() +local MaxVelocity=0 +for UnitName,UnitData in pairs(self:GetSet())do +local Unit=UnitData +local Coordinate=Unit:GetCoordinate() +local Velocity=Coordinate:GetVelocity() +if Velocity~=0 then +MaxVelocity=(MaxVelocity5 then +HeadingSet=nil +break +end +end +end +end +return HeadingSet +end +function SET_UNIT:HasRadar(RadarType) +local RadarCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitSensorTest=UnitData +local HasSensors +if RadarType then +HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR,RadarType) +else +HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR) +end +if HasSensors then +RadarCount=RadarCount+1 +end +end +return RadarCount +end +function SET_UNIT:HasSEAD() +local SEADCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitSEAD=UnitData +if UnitSEAD:IsAlive()then +local UnitSEADAttributes=UnitSEAD:GetDesc().attributes +local HasSEAD=UnitSEAD:HasSEAD() +if HasSEAD then +SEADCount=SEADCount+1 +end +end +end +return SEADCount +end +function SET_UNIT:HasGroundUnits() +local GroundUnitCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitTest=UnitData +if UnitTest:IsGround()then +GroundUnitCount=GroundUnitCount+1 +end +end +return GroundUnitCount +end +function SET_UNIT:HasAirUnits() +local AirUnitCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitTest=UnitData +if UnitTest:IsAir()then +AirUnitCount=AirUnitCount+1 +end +end +return AirUnitCount +end +function SET_UNIT:HasFriendlyUnits(FriendlyCoalition) +local FriendlyUnitCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitTest=UnitData +if UnitTest:IsFriendly(FriendlyCoalition)then +FriendlyUnitCount=FriendlyUnitCount+1 +end +end +return FriendlyUnitCount +end +function SET_UNIT:IsIncludeObject(MUnit) +local MUnitInclude=false +if MUnit:IsAlive()~=nil then +MUnitInclude=true +if self.Filter.Active~=nil then +local MUnitActive=false +if self.Filter.Active==false or(self.Filter.Active==true and MUnit:IsActive()==true)then +MUnitActive=true +end +MUnitInclude=MUnitInclude and MUnitActive +end +if self.Filter.Coalitions and MUnitInclude then +local MUnitCoalition=false +for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do +if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MUnit:GetCoalition()then +MUnitCoalition=true +end +end +MUnitInclude=MUnitInclude and MUnitCoalition +end +if self.Filter.Categories and MUnitInclude then +local MUnitCategory=false +for CategoryID,CategoryName in pairs(self.Filter.Categories)do +if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MUnit:GetDesc().category then +MUnitCategory=true +end +end +MUnitInclude=MUnitInclude and MUnitCategory +end +if self.Filter.Types and MUnitInclude then +local MUnitType=false +for TypeID,TypeName in pairs(self.Filter.Types)do +if TypeName==MUnit:GetTypeName()then +MUnitType=true +end +end +MUnitInclude=MUnitInclude and MUnitType +end +if self.Filter.Countries and MUnitInclude then +local MUnitCountry=false +for CountryID,CountryName in pairs(self.Filter.Countries)do +if country.id[CountryName]==MUnit:GetCountry()then +MUnitCountry=true +end +end +MUnitInclude=MUnitInclude and MUnitCountry +end +if self.Filter.UnitPrefixes and MUnitInclude then +local MUnitPrefix=false +for UnitPrefixId,UnitPrefix in pairs(self.Filter.UnitPrefixes)do +if string.find(MUnit:GetName(),UnitPrefix,1)then +MUnitPrefix=true +end +end +MUnitInclude=MUnitInclude and MUnitPrefix +end +if self.Filter.RadarTypes and MUnitInclude then +local MUnitRadar=false +for RadarTypeID,RadarType in pairs(self.Filter.RadarTypes)do +if MUnit:HasSensors(Unit.SensorType.RADAR,RadarType)==true then +if MUnit:GetRadar()==true then +end +MUnitRadar=true +end +end +MUnitInclude=MUnitInclude and MUnitRadar +end +if self.Filter.SEAD and MUnitInclude then +local MUnitSEAD=false +if MUnit:HasSEAD()==true then +MUnitSEAD=true +end +MUnitInclude=MUnitInclude and MUnitSEAD +end +end +if self.Filter.Zones and MUnitInclude then +local MGroupZone=false +for ZoneName,Zone in pairs(self.Filter.Zones)do +if MUnit:IsInZone(Zone)then +MGroupZone=true +end +end +MUnitInclude=MUnitInclude and MGroupZone +end +if self.Filter.Functions and MUnitInclude then +local MUnitFunc=self:_EvalFilterFunctions(MUnit) +MUnitInclude=MUnitInclude and MUnitFunc +end +return MUnitInclude +end +function SET_UNIT:GetTypeNames(Delimiter) +Delimiter=Delimiter or", " +local TypeReport=REPORT:New() +local Types={} +for UnitName,UnitData in pairs(self:GetSet())do +local Unit=UnitData +local UnitTypeName=Unit:GetTypeName() +if not Types[UnitTypeName]then +Types[UnitTypeName]=UnitTypeName +TypeReport:Add(UnitTypeName) +end +end +return TypeReport:Text(Delimiter) +end +function SET_UNIT:SetCargoBayWeightLimit() +local Set=self:GetSet() +for UnitID,UnitData in pairs(Set)do +UnitData:SetCargoBayWeightLimit() +end +end +end +do +SET_STATIC={ +ClassName="SET_STATIC", +Statics={}, +Filter={ +Coalitions=nil, +Categories=nil, +Types=nil, +Countries=nil, +StaticPrefixes=nil, +Zones=nil, +}, +FilterMeta={ +Coalitions={ +red=coalition.side.RED, +blue=coalition.side.BLUE, +neutral=coalition.side.NEUTRAL, +}, +Categories={ +plane=Unit.Category.AIRPLANE, +helicopter=Unit.Category.HELICOPTER, +ground=Unit.Category.GROUND_STATIC, +ship=Unit.Category.SHIP, +structure=Unit.Category.STRUCTURE, +}, +}, +} +function SET_STATIC:New() +local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.STATICS)) +return self +end +function SET_STATIC:AddStatic(AddStatic) +self:Add(AddStatic:GetName(),AddStatic) +return self +end +function SET_STATIC:AddStaticsByName(AddStaticNames) +local AddStaticNamesArray=(type(AddStaticNames)=="table")and AddStaticNames or{AddStaticNames} +for AddStaticID,AddStaticName in pairs(AddStaticNamesArray)do +self:Add(AddStaticName,STATIC:FindByName(AddStaticName)) +end +return self +end +function SET_STATIC:RemoveStaticsByName(RemoveStaticNames) +local RemoveStaticNamesArray=(type(RemoveStaticNames)=="table")and RemoveStaticNames or{RemoveStaticNames} +for RemoveStaticID,RemoveStaticName in pairs(RemoveStaticNamesArray)do +self:Remove(RemoveStaticName) +end +return self +end +function SET_STATIC:FindStatic(StaticName) +local StaticFound=self.Set[StaticName] +return StaticFound +end +function SET_STATIC:FilterCoalitions(Coalitions) +if not self.Filter.Coalitions then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then +Coalitions={Coalitions} +end +for CoalitionID,Coalition in pairs(Coalitions)do +self.Filter.Coalitions[Coalition]=Coalition +end +return self +end +function SET_STATIC:FilterZones(Zones) +if not self.Filter.Zones then +self.Filter.Zones={} +end +local zones={} +if Zones.ClassName and Zones.ClassName=="SET_ZONE"then +zones=Zones.Set +elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then +self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") +return self +else +zones=Zones +end +for _,Zone in pairs(zones)do +local zonename=Zone:GetName() +self.Filter.Zones[zonename]=Zone +end +return self +end +function SET_STATIC:FilterCategories(Categories) +if not self.Filter.Categories then +self.Filter.Categories={} +end +if type(Categories)~="table"then +Categories={Categories} +end +for CategoryID,Category in pairs(Categories)do +self.Filter.Categories[Category]=Category +end +return self +end +function SET_STATIC:FilterTypes(Types) +if not self.Filter.Types then +self.Filter.Types={} +end +if type(Types)~="table"then +Types={Types} +end +for TypeID,Type in pairs(Types)do +self.Filter.Types[Type]=Type +end +return self +end +function SET_STATIC:FilterCountries(Countries) +if not self.Filter.Countries then +self.Filter.Countries={} +end +if type(Countries)~="table"then +Countries={Countries} +end +for CountryID,Country in pairs(Countries)do +self.Filter.Countries[Country]=Country +end +return self +end +function SET_STATIC:FilterPrefixes(Prefixes) +if not self.Filter.StaticPrefixes then +self.Filter.StaticPrefixes={} +end +if type(Prefixes)~="table"then +Prefixes={Prefixes} +end +for PrefixID,Prefix in pairs(Prefixes)do +self.Filter.StaticPrefixes[Prefix]=Prefix +end +return self +end +function SET_STATIC:FilterStart() +if _DATABASE then +self:_FilterStart() +self:HandleEvent(EVENTS.Birth,self._EventOnBirth) +self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) +self:HandleEvent(EVENTS.UnitLost,self._EventOnDeadOrCrash) +end +return self +end +function SET_STATIC:CountAlive() +local Set=self:GetSet() +local CountU=0 +for UnitID,UnitData in pairs(Set)do +if UnitData and UnitData:IsAlive()then +CountU=CountU+1 +end +end +return CountU +end +function SET_STATIC:AddInDatabase(Event) +if Event.IniObjectCategory==Object.Category.STATIC then +if not self.Database[Event.IniDCSUnitName]then +self.Database[Event.IniDCSUnitName]=STATIC:Register(Event.IniDCSUnitName) +end +end +return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] +end +function SET_STATIC:FindInDatabase(Event) +return Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName] +end +do +function SET_STATIC:IsPartiallyInZone(Zone) +local IsPartiallyInZone=false +local function EvaluateZone(ZoneStatic) +local ZoneStaticName=ZoneStatic:GetName() +if self:FindStatic(ZoneStaticName)then +IsPartiallyInZone=true +return false +end +return true +end +return IsPartiallyInZone +end +function SET_STATIC:IsNotInZone(Zone) +local IsNotInZone=true +local function EvaluateZone(ZoneStatic) +local ZoneStaticName=ZoneStatic:GetName() +if self:FindStatic(ZoneStaticName)then +IsNotInZone=false +return false +end +return true +end +Zone:Search(EvaluateZone) +return IsNotInZone +end +function SET_STATIC:ForEachStaticInZone(IteratorFunction,...) +self:ForEach(IteratorFunction,arg,self:GetSet()) +return self +end +end +function SET_STATIC:ForEachStatic(IteratorFunction,...) +self:ForEach(IteratorFunction,arg,self:GetSet()) +return self +end +function SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject,IteratorFunction,...) +self:ForEach(IteratorFunction,arg,self:GetSet(), +function(ZoneObject,StaticObject) +if StaticObject:IsInZone(ZoneObject)then +return true +else +return false +end +end,{ZoneObject}) +return self +end +function SET_STATIC:ForEachStaticNotInZone(ZoneObject,IteratorFunction,...) +self:ForEach(IteratorFunction,arg,self:GetSet(), +function(ZoneObject,StaticObject) +if StaticObject:IsNotInZone(ZoneObject)then +return true +else +return false +end +end,{ZoneObject}) +return self +end +function SET_STATIC:GetStaticTypes() +local MT={} +local StaticTypes={} +for StaticID,StaticData in pairs(self:GetSet())do +local TextStatic=StaticData +if TextStatic:IsAlive()then +local StaticType=TextStatic:GetTypeName() +if not StaticTypes[StaticType]then +StaticTypes[StaticType]=1 +else +StaticTypes[StaticType]=StaticTypes[StaticType]+1 +end +end +end +for StaticTypeID,StaticType in pairs(StaticTypes)do +MT[#MT+1]=StaticType.." of "..StaticTypeID +end +return StaticTypes +end +function SET_STATIC:GetStaticTypesText() +local MT={} +local StaticTypes=self:GetStaticTypes() +for StaticTypeID,StaticType in pairs(StaticTypes)do +MT[#MT+1]=StaticType.." of "..StaticTypeID +end +return table.concat(MT,", ") +end +function SET_STATIC:GetCoordinate() +local Coordinate=self:GetFirst():GetCoordinate() +local x1=Coordinate.x +local x2=Coordinate.x +local y1=Coordinate.y +local y2=Coordinate.y +local z1=Coordinate.z +local z2=Coordinate.z +local MaxVelocity=0 +local AvgHeading=nil +local MovingCount=0 +for StaticName,StaticData in pairs(self:GetSet())do +local Static=StaticData +local Coordinate=Static:GetCoordinate() +x1=(Coordinate.xx2)and Coordinate.x or x2 +y1=(Coordinate.yy2)and Coordinate.y or y2 +z1=(Coordinate.yz2)and Coordinate.z or z2 +local Velocity=Coordinate:GetVelocity() +if Velocity~=0 then +MaxVelocity=(MaxVelocity5 then +HeadingSet=nil +break +end +end +end +end +return HeadingSet +end +function SET_STATIC:CalculateThreatLevelA2G() +local MaxThreatLevelA2G=0 +local MaxThreatText="" +for StaticName,StaticData in pairs(self:GetSet())do +local ThreatStatic=StaticData +local ThreatLevelA2G,ThreatText=ThreatStatic:GetThreatLevel() +if ThreatLevelA2G>MaxThreatLevelA2G then +MaxThreatLevelA2G=ThreatLevelA2G +MaxThreatText=ThreatText +end +end +return MaxThreatLevelA2G,MaxThreatText +end +function SET_STATIC:IsIncludeObject(MStatic) +local MStaticInclude=true +if self.Filter.Coalitions then +local MStaticCoalition=false +for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do +if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MStatic:GetCoalition()then +MStaticCoalition=true +end +end +MStaticInclude=MStaticInclude and MStaticCoalition +end +if self.Filter.Categories then +local MStaticCategory=false +for CategoryID,CategoryName in pairs(self.Filter.Categories)do +if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MStatic:GetDesc().category then +MStaticCategory=true +end +end +MStaticInclude=MStaticInclude and MStaticCategory +end +if self.Filter.Types then +local MStaticType=false +for TypeID,TypeName in pairs(self.Filter.Types)do +if TypeName==MStatic:GetTypeName()then +MStaticType=true +end +end +MStaticInclude=MStaticInclude and MStaticType +end +if self.Filter.Countries then +local MStaticCountry=false +for CountryID,CountryName in pairs(self.Filter.Countries)do +if country.id[CountryName]==MStatic:GetCountry()then +MStaticCountry=true +end +end +MStaticInclude=MStaticInclude and MStaticCountry +end +if self.Filter.StaticPrefixes then +local MStaticPrefix=false +for StaticPrefixId,StaticPrefix in pairs(self.Filter.StaticPrefixes)do +if string.find(MStatic:GetName(),StaticPrefix,1)then +MStaticPrefix=true +end +end +MStaticInclude=MStaticInclude and MStaticPrefix +end +if self.Filter.Zones then +local MStaticZone=false +for ZoneName,Zone in pairs(self.Filter.Zones)do +if MStatic and MStatic:IsInZone(Zone)then +MStaticZone=true +end +end +MStaticInclude=MStaticInclude and MStaticZone +end +if self.Filter.Functions and MStaticInclude then +local MClientFunc=self:_EvalFilterFunctions(MStatic) +MStaticInclude=MStaticInclude and MClientFunc +end +return MStaticInclude +end +function SET_STATIC:GetTypeNames(Delimiter) +Delimiter=Delimiter or", " +local TypeReport=REPORT:New() +local Types={} +for StaticName,StaticData in pairs(self:GetSet())do +local Static=StaticData +local StaticTypeName=Static:GetTypeName() +if not Types[StaticTypeName]then +Types[StaticTypeName]=StaticTypeName +TypeReport:Add(StaticTypeName) +end +end +return TypeReport:Text(Delimiter) +end +function SET_STATIC:GetClosestStatic(Coordinate,Coalitions) +local Set=self:GetSet() +local dmin=math.huge +local gmin=nil +for GroupID,GroupData in pairs(Set)do +local group=GroupData +if group and group:IsAlive()and(Coalitions==nil or UTILS.IsAnyInTable(Coalitions,group:GetCoalition()))then +local coord=group:GetCoord() +local d=UTILS.VecDist3D(Coordinate,coord) +if d1 then +x=x/count +y=y/count +z=z/count +end +local coord=COORDINATE:New(x,y,z) +return coord +end +function SET_ZONE:IsIncludeObject(MZone) +local MZoneInclude=true +if MZone then +local MZoneName=MZone:GetName() +if self.Filter.Prefixes then +local MZonePrefix=false +for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do +if string.find(MZoneName,ZonePrefix,1)then +MZonePrefix=true +end +end +MZoneInclude=MZoneInclude and MZonePrefix +end +end +if self.Filter.Functions and MZoneInclude then +local MClientFunc=self:_EvalFilterFunctions(MZone) +MZoneInclude=MZoneInclude and MClientFunc +end +return MZoneInclude +end +function SET_ZONE:OnEventNewZone(EventData) +if EventData.Zone then +if EventData.Zone and self:IsIncludeObject(EventData.Zone)then +self:Add(EventData.Zone.ZoneName,EventData.Zone) +end +end +end +function SET_ZONE:OnEventDeleteZone(EventData) +if EventData.Zone then +local Zone=_DATABASE:FindZone(EventData.Zone.ZoneName) +if Zone and Zone.ZoneName then +if Zone.NoDestroy then +else +self:Remove(Zone.ZoneName) +end +end +end +end +function SET_ZONE:IsCoordinateInZone(Coordinate) +for _,Zone in pairs(self:GetSet())do +local Zone=Zone +if Zone:IsCoordinateInZone(Coordinate)then +return Zone +end +end +return nil +end +function SET_ZONE:GetClosestZone(Coordinate) +local dmin=math.huge +local zmin=nil +for _,Zone in pairs(self:GetSet())do +local Zone=Zone +local d=Zone:Get2DDistance(Coordinate) +if dx2)and Coordinate.x or x2 +y1=(Coordinate.yy2)and Coordinate.y or y2 +z1=(Coordinate.yz2)and Coordinate.z or z2 +end +Coordinate.x=(x2-x1)/2+x1 +Coordinate.y=(y2-y1)/2+y1 +Coordinate.z=(z2-z1)/2+z1 +return Coordinate +end +function SET_SCENERY:IsIncludeObject(MScenery) +local MSceneryInclude=true +if MScenery then +local MSceneryName=MScenery:GetName() +if self.Filter.Prefixes then +local MSceneryPrefix=false +for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do +if string.find(MSceneryName,ZonePrefix,1)then +MSceneryPrefix=true +end +end +MSceneryInclude=MSceneryInclude and MSceneryPrefix +end +if self.Filter.Zones then +local MSceneryZone=false +for ZoneName,Zone in pairs(self.Filter.Zones)do +local coord=MScenery:GetCoordinate() +if coord and Zone:IsCoordinateInZone(coord)then +MSceneryZone=true +end +end +MSceneryInclude=MSceneryInclude and MSceneryZone +end +if self.Filter.SceneryRoles then +local MSceneryRole=false +local Role=MScenery:GetProperty("ROLE")or"none" +for ZoneRoleId,ZoneRole in pairs(self.Filter.SceneryRoles)do +if ZoneRole==Role then +MSceneryRole=true +end +end +MSceneryInclude=MSceneryInclude and MSceneryRole +end +end +if self.Filter.Functions and MSceneryInclude then +local MClientFunc=self:_EvalFilterFunctions(MScenery) +MSceneryInclude=MSceneryInclude and MClientFunc +end +return MSceneryInclude +end +function SET_SCENERY:FilterOnce() +for ObjectName,Object in pairs(self:GetSet())do +if self:IsIncludeObject(Object)then +self:Add(ObjectName,Object) +else +self:Remove(ObjectName,true) +end +end +return self +end +function SET_SCENERY:GetLife0() +local life0=0 +self:ForEachScenery( +function(obj) +local Obj=obj +life0=life0+Obj:GetLife0() +end +) +return life0 +end +function SET_SCENERY:GetLife() +local life=0 +self:ForEachScenery( +function(obj) +local Obj=obj +life=life+Obj:GetLife() +end +) +return life +end +function SET_SCENERY:GetRelativeLife() +local life=self:GetLife() +local life0=self:GetLife0() +local rlife=math.floor((life/life0)*100) +return rlife +end +end +do +SET_DYNAMICCARGO={ +ClassName="SET_DYNAMICCARGO", +Set={}, +List={}, +Index={}, +Database=nil, +CallScheduler=nil, +Filter={ +Coalitions=nil, +Types=nil, +Countries=nil, +StaticPrefixes=nil, +Zones=nil, +}, +FilterMeta={ +Coalitions={ +red=coalition.side.RED, +blue=coalition.side.BLUE, +neutral=coalition.side.NEUTRAL, +} +}, +ZoneTimerInterval=20, +ZoneTimer=nil, +} +function SET_DYNAMICCARGO:New() +local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.DYNAMICCARGO)) +return self +end +function SET_DYNAMICCARGO:IsIncludeObject(DCargo) +local DCargoInclude=true +if self.Filter.Coalitions then +local DCargoCoalition=false +for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do +if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==DCargo:GetCoalition()then +DCargoCoalition=true +end +end +DCargoInclude=DCargoInclude and DCargoCoalition +end +if self.Filter.Types then +local DCargoType=false +for TypeID,TypeName in pairs(self.Filter.Types)do +if TypeName==DCargo:GetTypeName()then +DCargoType=true +end +end +DCargoInclude=DCargoInclude and DCargoType +end +if self.Filter.Countries then +local DCargoCountry=false +for CountryID,CountryName in pairs(self.Filter.Countries)do +if country.id[CountryName]==DCargo:GetCountry()then +DCargoCountry=true +end +end +DCargoInclude=DCargoInclude and DCargoCountry +end +if self.Filter.StaticPrefixes then +local DCargoPrefix=false +for StaticPrefixId,StaticPrefix in pairs(self.Filter.StaticPrefixes)do +if string.find(DCargo:GetName(),StaticPrefix,1)then +DCargoPrefix=true +end +end +DCargoInclude=DCargoInclude and DCargoPrefix +end +if self.Filter.Zones then +local DCargoZone=false +for ZoneName,Zone in pairs(self.Filter.Zones)do +if DCargo and DCargo:IsInZone(Zone)then +DCargoZone=true +end +end +DCargoInclude=DCargoInclude and DCargoZone +end +if self.Filter.Functions and DCargoInclude then +local MClientFunc=self:_EvalFilterFunctions(DCargo) +DCargoInclude=DCargoInclude and MClientFunc +end +return DCargoInclude +end +function SET_DYNAMICCARGO:FilterCoalitions(Coalitions) +if not self.Filter.Coalitions then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then +Coalitions={Coalitions} +end +for CoalitionID,Coalition in pairs(Coalitions)do +self.Filter.Coalitions[Coalition]=Coalition +end +return self +end +function SET_DYNAMICCARGO:FilterTypes(Types) +if not self.Filter.Types then +self.Filter.Types={} +end +if type(Types)~="table"then +Types={Types} +end +for TypeID,Type in pairs(Types)do +self.Filter.Types[Type]=Type +end +return self +end +function SET_DYNAMICCARGO:FilterCountries(Countries) +if not self.Filter.Countries then +self.Filter.Countries={} +end +if type(Countries)~="table"then +Countries={Countries} +end +for CountryID,Country in pairs(Countries)do +self.Filter.Countries[Country]=Country +end +return self +end +function SET_DYNAMICCARGO:FilterPrefixes(Prefixes) +if not self.Filter.StaticPrefixes then +self.Filter.StaticPrefixes={} +end +if type(Prefixes)~="table"then +Prefixes={Prefixes} +end +for PrefixID,Prefix in pairs(Prefixes)do +self.Filter.StaticPrefixes[Prefix]=Prefix +end +return self +end +function SET_DYNAMICCARGO:FilterNamePattern(Patterns) +return self:FilterPrefixes(Patterns) +end +function SET_DYNAMICCARGO:FilterIsLoaded() +self:FilterFunction( +function(cargo) +if cargo and cargo.CargoState and cargo.CargoState==DYNAMICCARGO.State.LOADED then +return true +else +return false +end +end +) +return self +end +function SET_DYNAMICCARGO:FilterIsUnloaded() +self:FilterFunction( +function(cargo) +if cargo and cargo.CargoState and cargo.CargoState==DYNAMICCARGO.State.UNLOADED then +return true +else +return false +end +end +) +return self +end +function SET_DYNAMICCARGO:FilterIsNew() +self:FilterFunction( +function(cargo) +if cargo and cargo.CargoState and cargo.CargoState==DYNAMICCARGO.State.NEW then +return true +else +return false +end +end +) +return self +end +function SET_DYNAMICCARGO:FilterCurrentOwner(PlayerName) +self:FilterFunction( +function(cargo) +if cargo and cargo.Owner and string.find(cargo.Owner,PlayerName,1,true)then +return true +else +return false +end +end +) +return self +end +function SET_DYNAMICCARGO:FilterZones(Zones) +if not self.Filter.Zones then +self.Filter.Zones={} +end +local zones={} +if Zones.ClassName and Zones.ClassName=="SET_ZONE"then +zones=Zones.Set +elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then +self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") +return self +else +zones=Zones +end +for _,Zone in pairs(zones)do +local zonename=Zone:GetName() +self.Filter.Zones[zonename]=Zone +end +return self +end +function SET_DYNAMICCARGO:FilterStart() +if _DATABASE then +self:HandleEvent(EVENTS.NewDynamicCargo,self._EventHandlerDCAdd) +self:HandleEvent(EVENTS.DynamicCargoRemoved,self._EventHandlerDCRemove) +if self.Filter.Zones then +self.ZoneTimer=TIMER:New(self._ContinousZoneFilter,self) +local timing=self.ZoneTimerInterval or 30 +self.ZoneTimer:Start(timing,timing) +end +self:_FilterStart() +end +return self +end +function SET_DYNAMICCARGO:FilterStop() +if _DATABASE then +self:UnHandleEvent(EVENTS.NewDynamicCargo) +self:UnHandleEvent(EVENTS.DynamicCargoRemoved) +if self.ZoneTimer and self.ZoneTimer:IsRunning()then +self.ZoneTimer:Stop() +end +end +return self +end +function SET_DYNAMICCARGO:_ContinousZoneFilter() +local Database=_DATABASE.DYNAMICCARGO +for ObjectName,Object in pairs(Database)do +if self:IsIncludeObject(Object)and self:IsNotInSet(Object)then +self:Add(ObjectName,Object) +elseif(not self:IsIncludeObject(Object))and self:IsInSet(Object)then +self:Remove(ObjectName) +end +end +return self +end +function SET_DYNAMICCARGO:_EventHandlerDCAdd(Event) +if Event.IniDynamicCargo and Event.IniDynamicCargoName then +if not _DATABASE.DYNAMICCARGO[Event.IniDynamicCargoName]then +_DATABASE:AddDynamicCargo(Event.IniDynamicCargoName) +end +local ObjectName,Object=self:FindInDatabase(Event) +if Object and self:IsIncludeObject(Object)then +self:Add(ObjectName,Object) +end +end +return self +end +function SET_DYNAMICCARGO:_EventHandlerDCRemove(Event) +if Event.IniDCSUnitName then +local ObjectName,Object=self:FindInDatabase(Event) +if ObjectName then +self:Remove(ObjectName) +end +end +return self +end +function SET_DYNAMICCARGO:FindInDatabase(Event) +return Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName] +end +function SET_DYNAMICCARGO:FilterZoneTimer(Seconds) +self.ZoneTimerInterval=Seconds or 30 +return self +end +function SET_DYNAMICCARGO:FilterDeads() +return self +end +function SET_DYNAMICCARGO:FilterCrashes() +return self +end +function SET_DYNAMICCARGO:GetOwnerNames() +local owners={} +self:ForEach( +function(cargo) +if cargo and cargo.Owner then +table.insert(owners,cargo.Owner,cargo.Owner) +end +end +) +return owners +end +function SET_DYNAMICCARGO:GetStorageObjects() +local owners={} +self:ForEach( +function(cargo) +if cargo and cargo.warehouse then +table.insert(owners,cargo.StaticName,cargo.warehouse) +end +end +) +return owners +end +function SET_DYNAMICCARGO:GetOwnerClientObjects() +local owners={} +self:ForEach( +function(cargo) +if cargo and cargo.Owner then +local client=CLIENT:FindByPlayerName(cargo.Owner) +if client then +table.insert(owners,cargo.Owner,client) +end +end +end +) +return owners +end +end +do +COORDINATE={ +ClassName="COORDINATE", +} +COORDINATE.WaypointAltType={ +BARO="BARO", +RADIO="RADIO", +} +COORDINATE.WaypointAction={ +TurningPoint="Turning Point", +FlyoverPoint="Fly Over Point", +FromParkingArea="From Parking Area", +FromParkingAreaHot="From Parking Area Hot", +FromGroundAreaHot="From Ground Area Hot", +FromGroundArea="From Ground Area", +FromRunway="From Runway", +Landing="Landing", +LandingReFuAr="LandingReFuAr", +} +COORDINATE.WaypointType={ +TakeOffParking="TakeOffParking", +TakeOffParkingHot="TakeOffParkingHot", +TakeOff="TakeOffParkingHot", +TakeOffGroundHot="TakeOffGroundHot", +TakeOffGround="TakeOffGround", +TurningPoint="Turning Point", +Land="Land", +LandingReFuAr="LandingReFuAr", +} +function COORDINATE:New(x,y,z) +local self=BASE:Inherit(self,BASE:New()) +self.x=x +self.y=y +self.z=z +return self +end +function COORDINATE:NewFromCoordinate(Coordinate) +local self=BASE:Inherit(self,BASE:New()) +self.x=Coordinate.x +self.y=Coordinate.y +self.z=Coordinate.z +return self +end +function COORDINATE:NewFromVec2(Vec2,LandHeightAdd) +local LandHeight=land.getHeight(Vec2) +LandHeightAdd=LandHeightAdd or 0 +LandHeight=LandHeight+LandHeightAdd +local self=self:New(Vec2.x,LandHeight,Vec2.y) +return self +end +function COORDINATE:NewFromVec3(Vec3) +local self=self:New(Vec3.x,Vec3.y,Vec3.z) +self:F2(self) +return self +end +function COORDINATE:NewFromWaypoint(Waypoint) +local self=self:New(Waypoint.x,Waypoint.alt,Waypoint.y) +return self +end +function COORDINATE:GetCoordinate() +return self +end +function COORDINATE:GetVec3() +return{x=self.x,y=self.y,z=self.z} +end +function COORDINATE:GetVec2() +return{x=self.x,y=self.z} +end +function COORDINATE:UpdateFromVec3(Vec3) +self.x=Vec3.x +self.y=Vec3.y +self.z=Vec3.z +return self +end +function COORDINATE:UpdateFromCoordinate(Coordinate) +self.x=Coordinate.x +self.y=Coordinate.y +self.z=Coordinate.z +return self +end +function COORDINATE:UpdateFromVec2(Vec2) +self.x=Vec2.x +self.z=Vec2.y +return self +end +function COORDINATE:GetMagneticDeclination(Month,Year) +local decl=UTILS.GetMagneticDeclination() +if require then +local magvar=require('magvar') +if magvar then +local date,year,month,day=UTILS.GetDCSMissionDate() +magvar.init(Month or month,Year or year) +local lat,lon=self:GetLLDDM() +decl=magvar.get_mag_decl(lat,lon) +if decl then +decl=math.deg(decl) +end +end +else +self:T("The require package is not available. Using constant value for magnetic declination") +end +return decl +end +function COORDINATE:NewFromLLDD(latitude,longitude,altitude) +local vec3=coord.LLtoLO(latitude,longitude) +local _coord=self:NewFromVec3(vec3) +if altitude==nil then +_coord.y=self:GetLandHeight() +else +_coord.y=altitude +end +return _coord +end +function COORDINATE:IsAtCoordinate2D(Coordinate,Precision) +self:F({Coordinate=Coordinate:GetVec2()}) +self:F({self=self:GetVec2()}) +local x=Coordinate.x +local z=Coordinate.z +return x-Precision<=self.x and x+Precision>=self.x and z-Precision<=self.z and z+Precision>=self.z +end +function COORDINATE:ScanObjects(radius,scanunits,scanstatics,scanscenery) +self:F(string.format("Scanning in radius %.1f m.",radius or 100)) +local SphereSearch={ +id=world.VolumeType.SPHERE, +params={ +point=self:GetVec3(), +radius=radius, +} +} +radius=radius or 100 +if scanunits==nil then +scanunits=true +end +if scanstatics==nil then +scanstatics=true +end +if scanscenery==nil then +scanscenery=false +end +local scanobjects={} +if scanunits then +table.insert(scanobjects,Object.Category.UNIT) +end +if scanstatics then +table.insert(scanobjects,Object.Category.STATIC) +end +if scanscenery then +table.insert(scanobjects,Object.Category.SCENERY) +end +local Units={} +local Statics={} +local Scenery={} +local gotstatics=false +local gotunits=false +local gotscenery=false +local function EvaluateZone(ZoneObject) +if ZoneObject then +local ObjectCategory=Object.getCategory(ZoneObject) +if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()then +table.insert(Units,UNIT:Find(ZoneObject)) +gotunits=true +elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist()then +table.insert(Statics,ZoneObject) +gotstatics=true +elseif ObjectCategory==Object.Category.SCENERY then +table.insert(Scenery,ZoneObject) +gotscenery=true +end +end +return true +end +world.searchObjects(scanobjects,SphereSearch,EvaluateZone) +for _,unit in pairs(Units)do +self:T(string.format("Scan found unit %s",unit:GetName())) +end +for _,static in pairs(Statics)do +self:T(string.format("Scan found static %s",static:getName())) +_DATABASE:AddStatic(static:getName()) +end +for _,scenery in pairs(Scenery)do +self:T(string.format("Scan found scenery %s typename=%s",scenery:getName(),scenery:getTypeName())) +end +return gotunits,gotstatics,gotscenery,Units,Statics,Scenery +end +function COORDINATE:ScanUnits(radius) +local _,_,_,units=self:ScanObjects(radius,true,false,false) +local set=SET_UNIT:New() +for _,unit in pairs(units)do +set:AddUnit(unit) +end +return set +end +function COORDINATE:ScanStatics(radius) +local _,_,_,_,statics=self:ScanObjects(radius,false,true,false) +local set=SET_STATIC:New() +for _,stat in pairs(statics)do +set:AddStatic(STATIC:Find(stat)) +end +return set +end +function COORDINATE:FindClosestStatic(radius) +local units=self:ScanStatics(radius) +local umin=nil +local dmin=math.huge +for _,_unit in pairs(units.Set)do +local unit=_unit +local coordinate=unit:GetCoordinate() +local d=self:Get2DDistance(coordinate) +if d1 then +Radials=2-Radials +end +local RadialMultiplier +if InnerRadius and InnerRadius<=OuterRadius then +RadialMultiplier=(OuterRadius-InnerRadius)*Radials+InnerRadius +else +RadialMultiplier=OuterRadius*Radials +end +local RandomVec2 +if OuterRadius>0 then +RandomVec2={x=math.cos(Theta)*RadialMultiplier+self.x,y=math.sin(Theta)*RadialMultiplier+self.z} +else +RandomVec2={x=self.x,y=self.z} +end +return RandomVec2 +end +function COORDINATE:GetRandomCoordinateInRadius(OuterRadius,InnerRadius) +self:F2({OuterRadius,InnerRadius}) +local coord=COORDINATE:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius)) +return coord +end +function COORDINATE:GetRandomVec3InRadius(OuterRadius,InnerRadius) +local RandomVec2=self:GetRandomVec2InRadius(OuterRadius,InnerRadius) +local y=self.y+math.random(InnerRadius,OuterRadius) +local RandomVec3={x=RandomVec2.x,y=y,z=RandomVec2.y} +return RandomVec3 +end +function COORDINATE:GetLandHeight() +local Vec2={x=self.x,y=self.z} +return land.getHeight(Vec2) +end +function COORDINATE:GetLandProfileVec3(Destination) +return land.profile(self:GetVec3(),Destination) +end +function COORDINATE:GetLandProfileCoordinates(Destination) +local points=self:GetLandProfileVec3(Destination:GetVec3()) +local coords={} +for _,point in ipairs(points)do +table.insert(coords,COORDINATE:NewFromVec3(point)) +end +return coords +end +function COORDINATE:SetHeading(Heading) +self.Heading=Heading +end +function COORDINATE:GetHeading() +return self.Heading +end +function COORDINATE:SetVelocity(Velocity) +self.Velocity=Velocity +end +function COORDINATE:GetVelocity() +local Velocity=self.Velocity +return Velocity or 0 +end +function COORDINATE:GetName() +local name=self:ToStringMGRS() +return name +end +function COORDINATE:GetMovingText(Settings) +return self:GetVelocityText(Settings)..", "..self:GetHeadingText(Settings) +end +function COORDINATE:GetDirectionVec3(TargetCoordinate) +if TargetCoordinate then +return{x=TargetCoordinate.x-self.x,y=TargetCoordinate.y-self.y,z=TargetCoordinate.z-self.z} +else +return{x=0,y=0,z=0} +end +end +function COORDINATE:GetNorthCorrectionRadians() +local TargetVec3=self:GetVec3() +local lat,lon=coord.LOtoLL(TargetVec3) +local north_posit=coord.LLtoLO(lat+1,lon) +return math.atan2(north_posit.z-TargetVec3.z,north_posit.x-TargetVec3.x) +end +function COORDINATE:GetAngleRadians(DirectionVec3) +local DirectionRadians=math.atan2(DirectionVec3.z,DirectionVec3.x) +if DirectionRadians<0 then +DirectionRadians=DirectionRadians+2*math.pi +end +return DirectionRadians +end +function COORDINATE:GetAngleDegrees(DirectionVec3) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local Angle=UTILS.ToDegree(AngleRadians) +return Angle +end +function COORDINATE:GetIntermediateCoordinate(ToCoordinate,Fraction) +local f=Fraction or 0.5 +local vec=UTILS.VecSubstract(ToCoordinate,self) +if f>1 then +local norm=UTILS.VecNorm(vec) +f=Fraction/norm +end +vec.x=f*vec.x +vec.y=f*vec.y +vec.z=f*vec.z +vec=UTILS.VecAdd(self,vec) +local coord=COORDINATE:New(vec.x,vec.y,vec.z) +return coord +end +function COORDINATE:Get2DDistance(TargetCoordinate) +if not TargetCoordinate then return 1000000 end +local a=self:GetVec2() +if not TargetCoordinate.ClassName then +TargetCoordinate=COORDINATE:NewFromVec3(TargetCoordinate) +end +local b=TargetCoordinate:GetVec2() +local norm=UTILS.VecDist2D(a,b) +return norm +end +function COORDINATE:GetTemperature(height) +self:F2(height) +local y=height or self.y +local point={x=self.x,y=height or self.y,z=self.z} +local T,P=atmosphere.getTemperatureAndPressure(point) +return T-273.15 +end +function COORDINATE:GetTemperatureText(height,Settings) +local DegreesCelcius=self:GetTemperature(height) +local Settings=Settings or _SETTINGS +if DegreesCelcius then +if Settings:IsMetric()then +return string.format(" %-2.2f °C",DegreesCelcius) +else +return string.format(" %-2.2f °F",UTILS.CelsiusToFahrenheit(DegreesCelcius)) +end +else +return" no temperature" +end +return nil +end +function COORDINATE:GetPressure(height) +local point={x=self.x,y=height or self.y,z=self.z} +local T,P=atmosphere.getTemperatureAndPressure(point) +return P/100 +end +function COORDINATE:GetPressureText(height,Settings) +local Pressure_hPa=self:GetPressure(height) +local Pressure_mmHg=Pressure_hPa*0.7500615613030 +local Pressure_inHg=Pressure_hPa*0.0295299830714 +local Settings=Settings or _SETTINGS +if Pressure_hPa then +if Settings:IsMetric()then +return string.format(" %4.1f hPa (%3.1f mmHg)",Pressure_hPa,Pressure_mmHg) +else +return string.format(" %4.1f hPa (%3.2f inHg)",Pressure_hPa,Pressure_inHg) +end +else +return" no pressure" +end +return nil +end +function COORDINATE:HeadingTo(ToCoordinate) +local dz=ToCoordinate.z-self.z +local dx=ToCoordinate.x-self.x +local heading=math.deg(math.atan2(dz,dx)) +if heading<0 then +heading=360+heading +end +return heading +end +function COORDINATE:GetWindVec3(height,turbulence) +local landheight=self:GetLandHeight()+0.1 +local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z} +local wind=nil +if turbulence then +wind=atmosphere.getWindWithTurbulence(point) +else +wind=atmosphere.getWind(point) +end +return wind +end +function COORDINATE:GetWind(height,turbulence) +local wind=self:GetWindVec3(height,turbulence) +local direction=UTILS.VecHdg(wind) +if direction>180 then +direction=direction-180 +else +direction=direction+180 +end +local strength=UTILS.VecNorm(wind) +return direction,strength +end +function COORDINATE:GetWindWithTurbulenceVec3(height) +local landheight=self:GetLandHeight()+0.1 +local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z} +local vec3=atmosphere.getWindWithTurbulence(point) +return vec3 +end +function COORDINATE:GetX() +return self.x +end +function COORDINATE:GetY() +if self:IsInstanceOf("POINT_VEC2")then +return self.z +end +return self.y +end +function COORDINATE:GetZ() +return self.z +end +function COORDINATE:SetX(x) +self.x=x +return self +end +function COORDINATE:SetY(y) +if self:IsInstanceOf("POINT_VEC2")then +self.z=y +else +self.y=y +end +return self +end +function COORDINATE:SetZ(z) +self.z=z +return self +end +function COORDINATE:AddX(x) +self.x=self.x+x +return self +end +function COORDINATE:GetLat() +return self.x +end +function COORDINATE:SetLat(x) +self.x=x +return self +end +function COORDINATE:GetLon() +return self.z +end +function COORDINATE:SetLon(z) +self.z=z +return self +end +function COORDINATE:GetAlt() +return self.y~=0 or land.getHeight({x=self.x,y=self.z}) +end +function COORDINATE:SetAlt(Altitude) +self.y=Altitude or land.getHeight({x=self.x,y=self.z}) +return self +end +function COORDINATE:AddAlt(Altitude) +self.y=land.getHeight({x=self.x,y=self.z})+Altitude or 0 +return self +end +function COORDINATE:GetRandomPointVec2InRadius(OuterRadius,InnerRadius) +self:F2({OuterRadius,InnerRadius}) +return COORDINATE:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius)) +end +function COORDINATE:AddY(y) +if self:IsInstanceOf("POINT_VEC2")then +return self:AddZ(y) +else +self.y=self.y+y +end +return self +end +function COORDINATE:AddZ(z) +self.z=self.z+z +return self +end +function COORDINATE:GetWindText(height,Settings) +local Direction,Strength=self:GetWind(height) +local Settings=Settings or _SETTINGS +if Direction and Strength then +if Settings:IsMetric()then +return string.format(" %d ° at %3.2f mps",Direction,UTILS.MpsToKmph(Strength)) +else +return string.format(" %d ° at %3.2f kps",Direction,UTILS.MpsToKnots(Strength)) +end +else +return" no wind" +end +return nil +end +function COORDINATE:Get3DDistance(TargetCoordinate) +local TargetVec3={x=TargetCoordinate.x,y=TargetCoordinate.y,z=TargetCoordinate.z} +local SourceVec3=self:GetVec3() +local dist=UTILS.VecDist3D(TargetVec3,SourceVec3) +return dist +end +function COORDINATE:GetBearingText(AngleRadians,Precision,Settings,MagVar) +local Settings=Settings or _SETTINGS +local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),Precision) +local s=string.format('%03d°',AngleDegrees) +if MagVar then +local variation=self:GetMagneticDeclination()or 0 +local AngleMagnetic=AngleDegrees-variation +if AngleMagnetic<0 then AngleMagnetic=360-AngleMagnetic end +s=string.format('%03d°M|%03d°',AngleMagnetic,AngleDegrees) +end +return s +end +function COORDINATE:GetDistanceText(Distance,Settings,Language,Precision) +local Settings=Settings or _SETTINGS +local Language=Language or Settings.Locale or _SETTINGS.Locale or"EN" +Language=string.lower(Language) +local Precision=Precision or 0 +local DistanceText +if Settings:IsMetric()then +if Language=="en"then +DistanceText=" for "..UTILS.Round(Distance/1000,Precision).." km" +elseif Language=="ru"then +DistanceText=" за "..UTILS.Round(Distance/1000,Precision).." километров" +end +else +if Language=="en"then +DistanceText=" for "..UTILS.Round(UTILS.MetersToNM(Distance),Precision).." miles" +elseif Language=="ru"then +DistanceText=" за "..UTILS.Round(UTILS.MetersToNM(Distance),Precision).." миль" +end +end +return DistanceText +end +function COORDINATE:GetAltitudeText(Settings,Language) +local Altitude=self.y +local Settings=Settings or _SETTINGS +local Language=Language or Settings.Locale or _SETTINGS.Locale or"EN" +Language=string.lower(Language) +if Altitude~=0 then +if Settings:IsMetric()then +if Language=="en"then +return" at "..UTILS.Round(self.y,-3).." meters" +elseif Language=="ru"then +return" в "..UTILS.Round(self.y,-3).." метры" +end +else +if Language=="en"then +return" at "..UTILS.Round(UTILS.MetersToFeet(self.y),-3).." feet" +elseif Language=="ru"then +return" в "..UTILS.Round(self.y,-3).." ноги" +end +end +else +return"" +end +end +function COORDINATE:GetVelocityText(Settings) +local Velocity=self:GetVelocity() +local Settings=Settings or _SETTINGS +if Velocity then +if Settings:IsMetric()then +return string.format(" moving at %d km/h",UTILS.MpsToKmph(Velocity)) +else +return string.format(" moving at %d mi/h",UTILS.MpsToKmph(Velocity)/1.852) +end +else +return" stationary" +end +end +function COORDINATE:GetHeadingText(Settings) +local Heading=self:GetHeading() +if Heading then +return string.format(" bearing %3d°",Heading) +else +return" bearing unknown" +end +end +function COORDINATE:GetBRText(AngleRadians,Distance,Settings,Language,MagVar,Precision) +local Settings=Settings or _SETTINGS +Precision=Precision or 0 +local BearingText=self:GetBearingText(AngleRadians,0,Settings,MagVar) +local DistanceText=self:GetDistanceText(Distance,Settings,Language,Precision) +local BRText=BearingText..DistanceText +return BRText +end +function COORDINATE:GetBRAText(AngleRadians,Distance,Settings,Language,MagVar) +local Settings=Settings or _SETTINGS +local BearingText=self:GetBearingText(AngleRadians,0,Settings,MagVar) +local DistanceText=self:GetDistanceText(Distance,Settings,Language,0) +local AltitudeText=self:GetAltitudeText(Settings,Language) +local BRAText=BearingText..DistanceText..AltitudeText +return BRAText +end +function COORDINATE:SetAltitude(altitude,asl) +local alt=altitude +if asl then +alt=altitude +else +alt=self:GetLandHeight()+altitude +end +self.y=alt +return self +end +function COORDINATE:SetAtLandheight() +local alt=self:GetLandHeight() +self.y=alt +return self +end +function COORDINATE:WaypointAir(AltType,Type,Action,Speed,SpeedLocked,airbase,DCSTasks,description,timeReFuAr) +self:F2({AltType,Type,Action,Speed,SpeedLocked}) +AltType=AltType or"RADIO" +if SpeedLocked==nil then +SpeedLocked=true +end +Speed=Speed or 500 +local RoutePoint={} +RoutePoint.x=self.x +RoutePoint.y=self.z +RoutePoint.alt=self.y +RoutePoint.alt_type=AltType +RoutePoint.type=Type or nil +RoutePoint.action=Action or nil +RoutePoint.speed=Speed/3.6 +RoutePoint.speed_locked=SpeedLocked +RoutePoint.ETA=0 +RoutePoint.ETA_locked=false +RoutePoint.name=description +if airbase then +local AirbaseID=airbase:GetID() +local AirbaseCategory=airbase:GetAirbaseCategory() +if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then +RoutePoint.linkUnit=AirbaseID +RoutePoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.AIRDROME then +RoutePoint.airdromeId=AirbaseID +else +self:E("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!") +end +end +if Type==COORDINATE.WaypointType.LandingReFuAr then +RoutePoint.timeReFuAr=timeReFuAr or 10 +end +RoutePoint.task={} +RoutePoint.task.id="ComboTask" +RoutePoint.task.params={} +RoutePoint.task.params.tasks=DCSTasks or{} +self:T({RoutePoint=RoutePoint}) +return RoutePoint +end +function COORDINATE:WaypointAirTurningPoint(AltType,Speed,DCSTasks,description) +return self:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description) +end +function COORDINATE:WaypointAirFlyOverPoint(AltType,Speed) +return self:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.FlyoverPoint,Speed) +end +function COORDINATE:WaypointAirTakeOffParkingHot(AltType,Speed) +return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOffParkingHot,COORDINATE.WaypointAction.FromParkingAreaHot,Speed) +end +function COORDINATE:WaypointAirTakeOffParking(AltType,Speed) +return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOffParking,COORDINATE.WaypointAction.FromParkingArea,Speed) +end +function COORDINATE:WaypointAirTakeOffRunway(AltType,Speed) +return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOff,COORDINATE.WaypointAction.FromRunway,Speed) +end +function COORDINATE:WaypointAirLanding(Speed,airbase,DCSTasks,description) +return self:WaypointAir(nil,COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,Speed,false,airbase,DCSTasks,description) +end +function COORDINATE:WaypointAirLandingReFu(Speed,airbase,timeReFuAr,DCSTasks,description) +return self:WaypointAir(nil,COORDINATE.WaypointType.LandingReFuAr,COORDINATE.WaypointAction.LandingReFuAr,Speed,false,airbase,DCSTasks,description,timeReFuAr or 10) +end +function COORDINATE:WaypointGround(Speed,Formation,DCSTasks) +self:F2({Speed,Formation,DCSTasks}) +local RoutePoint={} +RoutePoint.x=self.x +RoutePoint.y=self.z +RoutePoint.alt=self:GetLandHeight()+1 +RoutePoint.alt_type=COORDINATE.WaypointAltType.BARO +RoutePoint.type="Turning Point" +RoutePoint.action=Formation or"Off Road" +RoutePoint.formation_template="" +RoutePoint.ETA=0 +RoutePoint.ETA_locked=false +RoutePoint.speed=(Speed or 20)/3.6 +RoutePoint.speed_locked=true +RoutePoint.task={} +RoutePoint.task.id="ComboTask" +RoutePoint.task.params={} +RoutePoint.task.params.tasks=DCSTasks or{} +return RoutePoint +end +function COORDINATE:WaypointNaval(Speed,Depth,DCSTasks) +self:F2({Speed,Depth,DCSTasks}) +local RoutePoint={} +RoutePoint.x=self.x +RoutePoint.y=self.z +RoutePoint.alt=Depth or self.y +RoutePoint.alt_type="BARO" +RoutePoint.type="Turning Point" +RoutePoint.action="Turning Point" +RoutePoint.formation_template="" +RoutePoint.ETA=0 +RoutePoint.ETA_locked=false +RoutePoint.speed=(Speed or 20)/3.6 +RoutePoint.speed_locked=true +RoutePoint.task={} +RoutePoint.task.id="ComboTask" +RoutePoint.task.params={} +RoutePoint.task.params.tasks=DCSTasks or{} +return RoutePoint +end +function COORDINATE:GetClosestAirbase(Category,Coalition) +local airbases=AIRBASE.GetAllAirbases(Coalition) +local closest=nil +local distmin=nil +for _,_airbase in pairs(airbases)do +local airbase=_airbase +if airbase then +local category=airbase:GetAirbaseCategory() +if Category and Category==category or Category==nil then +local dist=self:Get2DDistance(airbase:GetCoordinate()) +if closest==nil then +distmin=dist +closest=airbase +else +if dist=2 then +for i=1,#Path-1 do +Way=Way+Path[i+1]:Get2DDistance(Path[i]) +end +else +return nil,nil,false +end +return Path,Way,GotPath +end +function COORDINATE:GetSurfaceType() +local vec2=self:GetVec2() +local surface=land.getSurfaceType(vec2) +return surface +end +function COORDINATE:IsSurfaceTypeLand() +return self:GetSurfaceType()==land.SurfaceType.LAND +end +function COORDINATE:IsSurfaceTypeLand() +return self:GetSurfaceType()==land.SurfaceType.LAND +end +function COORDINATE:IsSurfaceTypeRoad() +return self:GetSurfaceType()==land.SurfaceType.ROAD +end +function COORDINATE:IsSurfaceTypeRunway() +return self:GetSurfaceType()==land.SurfaceType.RUNWAY +end +function COORDINATE:IsSurfaceTypeShallowWater() +return self:GetSurfaceType()==land.SurfaceType.SHALLOW_WATER +end +function COORDINATE:IsSurfaceTypeWater() +return self:GetSurfaceType()==land.SurfaceType.WATER +end +function COORDINATE:Explosion(ExplosionIntensity,Delay) +ExplosionIntensity=ExplosionIntensity or 100 +if Delay and Delay>0 then +self:ScheduleOnce(Delay,self.Explosion,self,ExplosionIntensity) +else +trigger.action.explosion(self:GetVec3(),ExplosionIntensity) +end +return self +end +function COORDINATE:IlluminationBomb(Power,Delay) +Power=Power or 1000 +if Delay and Delay>0 then +self:ScheduleOnce(Delay,self.IlluminationBomb,self,Power) +else +trigger.action.illuminationBomb(self:GetVec3(),Power) +end +return self +end +function COORDINATE:Smoke(SmokeColor,Duration,Delay,Name,Offset,Direction,Distance) +self:F2({SmokeColor,Name,Duration,Delay,Offset}) +SmokeColor=SmokeColor or SMOKECOLOR.Green +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COORDINATE.Smoke,self,SmokeColor,Duration,0,Name,Direction,Distance) +else +self.firename=Name or"Smoke-"..math.random(1,100000) +if Offset or self.SmokeOffset then +local Angle=Direction or self:GetSmokeOffsetDirection() +local Distance=Distance or self:GetSmokeOffsetDistance() +local newpos=self:Translate(Distance,Angle,true,false) +local newvec3=newpos:GetVec3() +trigger.action.smoke(newvec3,SmokeColor,self.firename) +else +trigger.action.smoke(self:GetVec3(),SmokeColor,self.firename) +end +if Duration and Duration>0 then +self:ScheduleOnce(Duration,COORDINATE.StopSmoke,self,self.firename) +end +end +return self +end +function COORDINATE:GetSmokeOffsetDirection() +local direction=self.SmokeOffsetDirection or math.random(1,359) +return direction +end +function COORDINATE:SetSmokeOffsetDirection(Direction) +if self then +self.SmokeOffsetDirection=Direction or math.random(1,359) +return self +else +COORDINATE.SmokeOffsetDirection=Direction or math.random(1,359) +end +end +function COORDINATE:GetSmokeOffsetDistance() +local distance=self.SmokeOffsetDistance or math.random(10,20) +return distance +end +function COORDINATE:SetSmokeOffsetDistance(Distance) +if self then +self.SmokeOffsetDistance=Distance or math.random(10,20) +return self +else +COORDINATE.SmokeOffsetDistance=Distance or math.random(10,20) +end +end +function COORDINATE:SwitchSmokeOffsetOn() +if self then +self.SmokeOffset=true +return self +else +COORDINATE.SmokeOffset=true +end +end +function COORDINATE:SwitchSmokeOffsetOff() +if self then +self.SmokeOffset=false +return self +else +COORDINATE.SmokeOffset=false +end +end +function COORDINATE:StopSmoke(name) +self:StopBigSmokeAndFire(name) +end +function COORDINATE:SmokeGreen(Duration,Delay) +self:Smoke(SMOKECOLOR.Green,Duration,Delay) +return self +end +function COORDINATE:SmokeRed(Duration,Delay) +self:Smoke(SMOKECOLOR.Red,Duration,Delay) +return self +end +function COORDINATE:SmokeWhite(Duration,Delay) +self:Smoke(SMOKECOLOR.White,Duration,Delay) +return self +end +function COORDINATE:SmokeOrange(Duration,Delay) +self:Smoke(SMOKECOLOR.Orange,Duration,Delay) +return self +end +function COORDINATE:SmokeBlue(Duration,Delay) +self:Smoke(SMOKECOLOR.Blue,Duration,Delay) +return self +end +function COORDINATE:BigSmokeAndFire(Preset,Density,Duration,Delay,Name) +self:F2({preset=Preset,density=Density}) +Preset=Preset or BIGSMOKEPRESET.SmallSmokeAndFire +Density=Density or 0.5 +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COORDINATE.BigSmokeAndFire,self,Preset,Density,Duration,0,Name) +else +self.firename=Name or"Fire-"..math.random(1,10000) +trigger.action.effectSmokeBig(self:GetVec3(),Preset,Density,self.firename) +if Duration and Duration>0 then +self:ScheduleOnce(Duration,COORDINATE.StopBigSmokeAndFire,self,self.firename) +end +end +return self +end +function COORDINATE:StopBigSmokeAndFire(name) +name=name or self.firename +trigger.action.effectSmokeStop(name) +end +function COORDINATE:BigSmokeAndFireSmall(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire,Density,Duration,Delay,Name) +return self +end +function COORDINATE:BigSmokeAndFireMedium(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire,Density,Duration,Delay,Name) +return self +end +function COORDINATE:BigSmokeAndFireLarge(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire,Density,Duration,Delay,Name) +return self +end +function COORDINATE:BigSmokeAndFireHuge(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire,Density,Duration,Delay,Name) +return self +end +function COORDINATE:BigSmokeSmall(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke,Density,Duration,Delay,Name) +return self +end +function COORDINATE:BigSmokeMedium(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke,Density,Duration,Delay,Name) +return self +end +function COORDINATE:BigSmokeLarge(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke,Density,Duration,Delay,Name) +return self +end +function COORDINATE:BigSmokeHuge(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke,Density,Duration,Delay,Name) +return self +end +function COORDINATE:Flare(FlareColor,Azimuth) +self:F2({FlareColor}) +trigger.action.signalFlare(self:GetVec3(),FlareColor,Azimuth and Azimuth or 0) +end +function COORDINATE:FlareWhite(Azimuth) +self:F2(Azimuth) +self:Flare(FLARECOLOR.White,Azimuth) +end +function COORDINATE:FlareYellow(Azimuth) +self:F2(Azimuth) +self:Flare(FLARECOLOR.Yellow,Azimuth) +end +function COORDINATE:FlareGreen(Azimuth) +self:F2(Azimuth) +self:Flare(FLARECOLOR.Green,Azimuth) +end +function COORDINATE:FlareRed(Azimuth) +self:F2(Azimuth) +self:Flare(FLARECOLOR.Red,Azimuth) +end +do +function COORDINATE:MarkToAll(MarkText,ReadOnly,Text) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +local text=Text or"" +trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,text) +return MarkID +end +function COORDINATE:MarkToCoalition(MarkText,Coalition,ReadOnly,Text) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +local text=Text or"" +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),Coalition,ReadOnly,text) +return MarkID +end +function COORDINATE:MarkToCoalitionRed(MarkText,ReadOnly,Text) +return self:MarkToCoalition(MarkText,coalition.side.RED,ReadOnly,Text) +end +function COORDINATE:MarkToCoalitionBlue(MarkText,ReadOnly,Text) +return self:MarkToCoalition(MarkText,coalition.side.BLUE,ReadOnly,Text) +end +function COORDINATE:MarkToGroup(MarkText,MarkGroup,ReadOnly,Text) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +local text=Text or"" +trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),MarkGroup:GetID(),ReadOnly,text) +return MarkID +end +function COORDINATE:RemoveMark(MarkID) +trigger.action.removeMark(MarkID) +end +function COORDINATE:LineToAll(Endpoint,Coalition,Color,Alpha,LineType,ReadOnly,Text) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +local vec3=Endpoint:GetVec3() +Coalition=Coalition or-1 +Color=Color or{1,0,0} +Color[4]=Alpha or 1.0 +LineType=LineType or 1 +trigger.action.lineToAll(Coalition,MarkID,self:GetVec3(),vec3,Color,LineType,ReadOnly,Text or"") +return MarkID +end +function COORDINATE:CircleToAll(Radius,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +local vec3=self:GetVec3() +Radius=Radius or 1000 +Coalition=Coalition or-1 +Color=Color or{1,0,0} +Color[4]=Alpha or 1.0 +LineType=LineType or 1 +FillColor=FillColor or UTILS.DeepCopy(Color) +FillColor[4]=FillAlpha or 0.15 +trigger.action.circleToAll(Coalition,MarkID,vec3,Radius,Color,FillColor,LineType,ReadOnly,Text or"") +return MarkID +end +end +function COORDINATE:RectToAll(Endpoint,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +local vec3=Endpoint:GetVec3() +Coalition=Coalition or-1 +Color=Color or{1,0,0} +Color[4]=Alpha or 1.0 +LineType=LineType or 1 +FillColor=FillColor or UTILS.DeepCopy(Color) +FillColor[4]=FillAlpha or 0.15 +trigger.action.rectToAll(Coalition,MarkID,self:GetVec3(),vec3,Color,FillColor,LineType,ReadOnly,Text or"") +return MarkID +end +function COORDINATE:QuadToAll(Coord2,Coord3,Coord4,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +local point1=self:GetVec3() +local point2=Coord2:GetVec3() +local point3=Coord3:GetVec3() +local point4=Coord4:GetVec3() +Coalition=Coalition or-1 +Color=Color or{1,0,0} +Color[4]=Alpha or 1.0 +LineType=LineType or 1 +FillColor=FillColor or UTILS.DeepCopy(Color) +FillColor[4]=FillAlpha or 0.15 +trigger.action.quadToAll(Coalition,MarkID,point1,point2,point3,point4,Color,FillColor,LineType,ReadOnly,Text or"") +return MarkID +end +function COORDINATE:MarkupToAllFreeForm(Coordinates,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +Coalition=Coalition or-1 +Color=Color or{1,0,0} +Color[4]=Alpha or 1.0 +LineType=LineType or 1 +FillColor=FillColor or UTILS.DeepCopy(Color) +FillColor[4]=FillAlpha or 0.15 +local vecs={} +vecs[1]=self:GetVec3() +for i,coord in ipairs(Coordinates)do +vecs[i+1]=coord:GetVec3() +end +if#vecs<3 then +self:E("ERROR: A free form polygon needs at least three points!") +elseif#vecs==3 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==4 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==5 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==6 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==7 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==8 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==9 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==10 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==11 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], +vecs[11], +Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==12 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], +vecs[11],vecs[12], +Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==13 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], +vecs[11],vecs[12],vecs[13], +Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==14 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], +vecs[11],vecs[12],vecs[13],vecs[14], +Color,FillColor,LineType,ReadOnly,Text or"") +elseif#vecs==15 then +trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10], +vecs[11],vecs[12],vecs[13],vecs[14],vecs[15], +Color,FillColor,LineType,ReadOnly,Text or"") +else +local s=string.format("trigger.action.markupToAll(7, %d, %d,",Coalition,MarkID) +for _,vec in pairs(vecs)do +s=s..string.format("{x=%.1f, y=%.1f, z=%.1f},",vec.x,vec.y,vec.z) +end +s=s..string.format("{%.3f, %.3f, %.3f, %.3f},",Color[1],Color[2],Color[3],Color[4]) +s=s..string.format("{%.3f, %.3f, %.3f, %.3f},",FillColor[1],FillColor[2],FillColor[3],FillColor[4]) +s=s..string.format("%d,",LineType or 1) +s=s..string.format("%s",tostring(ReadOnly)) +if Text and type(Text)=="string"and string.len(Text)>0 then +s=s..string.format(", \"%s\"",tostring(Text)) +end +s=s..")" +local success=UTILS.DoString(s) +if not success then +self:E("ERROR: Could not draw polygon") +env.info(s) +end +end +return MarkID +end +function COORDINATE:TextToAll(Text,Coalition,Color,Alpha,FillColor,FillAlpha,FontSize,ReadOnly) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +Coalition=Coalition or-1 +Color=Color or{1,0,0} +Color[4]=Alpha or 1.0 +FillColor=FillColor or UTILS.DeepCopy(Color) +FillColor[4]=FillAlpha or 0.3 +FontSize=FontSize or 14 +trigger.action.textToAll(Coalition,MarkID,self:GetVec3(),Color,FillColor,FontSize,ReadOnly,Text or"Hello World") +return MarkID +end +function COORDINATE:ArrowToAll(Endpoint,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text) +local MarkID=UTILS.GetMarkID() +if ReadOnly==nil then +ReadOnly=false +end +local vec3=Endpoint:GetVec3() +Coalition=Coalition or-1 +Color=Color or{1,0,0} +Color[4]=Alpha or 1.0 +LineType=LineType or 1 +FillColor=FillColor or UTILS.DeepCopy(Color) +FillColor[4]=FillAlpha or 0.15 +trigger.action.arrowToAll(Coalition,MarkID,vec3,self:GetVec3(),Color,FillColor,LineType,ReadOnly,Text or"") +return MarkID +end +function COORDINATE:IsLOS(ToCoordinate,Offset) +Offset=Offset or 2 +local FromVec3=self:GetVec3() +FromVec3.y=FromVec3.y+Offset +local ToVec3=ToCoordinate:GetVec3() +ToVec3.y=ToVec3.y+Offset +local IsLOS=land.isVisible(FromVec3,ToVec3) +return IsLOS +end +function COORDINATE:IsInRadius(Coordinate,Radius) +local InVec2=self:GetVec2() +local Vec2=Coordinate:GetVec2() +local InRadius=UTILS.IsInRadius(InVec2,Vec2,Radius) +return InRadius +end +function COORDINATE:IsInSphere(Coordinate,Radius) +local InVec3=self:GetVec3() +local Vec3=Coordinate:GetVec3() +local InSphere=UTILS.IsInSphere(InVec3,Vec3,Radius) +return InSphere +end +function COORDINATE:GetSunriseAtDate(Day,Month,Year,InSeconds) +local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +if InSeconds then +return sunrise +else +return UTILS.SecondsToClock(sunrise,true) +end +end +function COORDINATE:GetSunriseAtDayOfYear(DayOfYear,InSeconds) +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +if InSeconds then +return sunrise +else +return UTILS.SecondsToClock(sunrise,true) +end +end +function COORDINATE:GetSunrise(InSeconds) +local DayOfYear=UTILS.GetMissionDayOfYear() +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +local date=UTILS.GetDCSMissionDate() +if InSeconds or type(sunrise)=="string"then +return sunrise +else +return UTILS.SecondsToClock(sunrise,true) +end +end +function COORDINATE:GetMinutesToSunrise(OnlyToday) +local time=UTILS.SecondsOfToday() +local sunrise=nil +local delta=nil +if OnlyToday then +sunrise=self:GetSunrise(true) +delta=sunrise-time +else +local DayOfYear=UTILS.GetMissionDayOfYear()+1 +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +delta=sunrise+UTILS.SecondsToMidnight() +end +return delta/60 +end +function COORDINATE:IsDay(Clock) +if Clock then +local Time=UTILS.ClockToSeconds(Clock) +local clock=UTILS.Split(Clock,"+")[1] +local DayOfYear=UTILS.GetMissionDayOfYear(Time) +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) +if type(sunrise)=="string"or type(sunset)=="string"then +if sunrise=="N/R"then return false end +if sunset=="N/S"then return true end +end +local time=UTILS.ClockToSeconds(clock) +if time>sunrise and time<=sunset then +return true +else +return false +end +else +local sunrise=self:GetSunrise(true) +local sunset=self:GetSunset(true) +if type(sunrise)=="string"or type(sunset)=="string"then +if sunrise=="N/R"then return false end +if sunset=="N/S"then return true end +end +local time=UTILS.SecondsOfToday() +if time>sunrise and time<=sunset then +return true +else +return false +end +end +end +function COORDINATE:IsNight(Clock) +return not self:IsDay(Clock) +end +function COORDINATE:GetSunsetAtDate(Day,Month,Year,InSeconds) +local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) +if InSeconds then +return sunset +else +return UTILS.SecondsToClock(sunset,true) +end +end +function COORDINATE:GetSunset(InSeconds) +local DayOfYear=UTILS.GetMissionDayOfYear() +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) +local date=UTILS.GetDCSMissionDate() +if InSeconds or type(sunrise)=="string"then +return sunrise +else +return UTILS.SecondsToClock(sunrise,true) +end +end +function COORDINATE:GetMinutesToSunset(OnlyToday) +local time=UTILS.SecondsOfToday() +local sunset=nil +local delta=nil +if OnlyToday then +sunset=self:GetSunset(true) +delta=sunset-time +else +local DayOfYear=UTILS.GetMissionDayOfYear()+1 +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) +delta=sunset+UTILS.SecondsToMidnight() +end +return delta/60 +end +function COORDINATE:ToStringBR(FromCoordinate,Settings,MagVar,Precision) +local DirectionVec3=FromCoordinate:GetDirectionVec3(self) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local Distance=self:Get2DDistance(FromCoordinate) +return"BR, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar,Precision) +end +function COORDINATE:ToStringBRA(FromCoordinate,Settings,MagVar) +local DirectionVec3=FromCoordinate:GetDirectionVec3(self) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local Distance=FromCoordinate:Get2DDistance(self) +local Altitude=self:GetAltitudeText() +return"BRA, "..self:GetBRAText(AngleRadians,Distance,Settings,nil,MagVar) +end +function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades,SSML,Angels,Zeros) +local BRAANATO="Merged." +local currentCoord=FromCoordinate +local DirectionVec3=FromCoordinate:GetDirectionVec3(self) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local bearing=UTILS.Round(UTILS.ToDegree(AngleRadians),0) +local magnetic=self:GetMagneticDeclination()or 0 +bearing=bearing-magnetic +local rangeMetres=self:Get2DDistance(currentCoord) +local rangeNM=UTILS.Round(UTILS.MetersToNM(rangeMetres),0) +local aspect=self:ToStringAspect(currentCoord) +local alt=UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0) +local alttext=string.format("%d thousand",alt) +if Angels then +alttext=string.format("Angels %d",alt) +end +if alt<1 then +alttext="very low" +end +local track="Maneuver" +if self.Heading then +track=UTILS.BearingToCardinal(self.Heading)or"North" +end +if rangeNM>3 then +if SSML then +if Zeros then +bearing=string.format("%03d",bearing) +local AngleDegText=string.gsub(bearing,"%d","%1 ") +AngleDegText=string.gsub(AngleDegText," $","") +AngleDegText=string.gsub(AngleDegText,"0","zero") +if aspect==""then +BRAANATO=string.format("brah %s, %d miles, %s, Track %s",AngleDegText,rangeNM,alttext,track) +else +BRAANATO=string.format("brah %s, %d miles, %s, %s, Track %s",AngleDegText,rangeNM,alttext,aspect,track) +end +else +if aspect==""then +BRAANATO=string.format("brah %03d, %d miles, %s, Track %s",bearing,rangeNM,alttext,track) +else +BRAANATO=string.format("brah %03d, %d miles, %s, %s, Track %s",bearing,rangeNM,alttext,aspect,track) +end +end +if Bogey and Spades then +BRAANATO=BRAANATO..", Bogey, Spades." +elseif Bogey then +BRAANATO=BRAANATO..", Bogey." +elseif Spades then +BRAANATO=BRAANATO..", Spades." +else +BRAANATO=BRAANATO.."." +end +else +if aspect==""then +BRAANATO=string.format("BRA %03d, %d miles, %s, Track %s",bearing,rangeNM,alttext,track) +else +BRAANATO=string.format("BRAA %03d, %d miles, %s, %s, Track %s",bearing,rangeNM,alttext,aspect,track) +end +if Bogey and Spades then +BRAANATO=BRAANATO..", Bogey, Spades." +elseif Bogey then +BRAANATO=BRAANATO..", Bogey." +elseif Spades then +BRAANATO=BRAANATO..", Spades." +else +BRAANATO=BRAANATO.."." +end +end +end +return BRAANATO +end +function COORDINATE.GetBullseyeCoordinate(Coalition) +return COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition)) +end +function COORDINATE:ToStringBULLS(Coalition,Settings,MagVar) +local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition)) +local DirectionVec3=BullsCoordinate:GetDirectionVec3(self) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local Distance=self:Get2DDistance(BullsCoordinate) +local Altitude=self:GetAltitudeText() +return"BULLS, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar) +end +function COORDINATE:ToStringAspect(TargetCoordinate) +local Heading=self.Heading +local DirectionVec3=self:GetDirectionVec3(TargetCoordinate) +local Angle=self:GetAngleDegrees(DirectionVec3) +if Heading then +local Aspect=Angle-Heading +if Aspect>-135 and Aspect<=-45 then +return"Flanking" +end +if Aspect>-45 and Aspect<=45 then +return"Hot" +end +if Aspect>45 and Aspect<=135 then +return"Flanking" +end +if Aspect>135 or Aspect<=-135 then +return"Cold" +end +end +return"" +end +function COORDINATE:GetLLDDM() +return coord.LOtoLL(self:GetVec3()) +end +function COORDINATE:ToStringLL(Settings) +local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy +local lat,lon=coord.LOtoLL(self:GetVec3()) +return string.format('%f',lat)..' '..string.format('%f',lon) +end +function COORDINATE:ToStringLLDMS(Settings) +local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy +local lat,lon=coord.LOtoLL(self:GetVec3()) +return"LL DMS "..UTILS.tostringLL(lat,lon,LL_Accuracy,true) +end +function COORDINATE:ToStringLLDDM(Settings) +local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy +local lat,lon=coord.LOtoLL(self:GetVec3()) +return"LL DDM "..UTILS.tostringLL(lat,lon,LL_Accuracy,false) +end +function COORDINATE:ToStringMGRS(Settings) +local MGRS_Accuracy=Settings and Settings.MGRS_Accuracy or _SETTINGS.MGRS_Accuracy +local lat,lon=coord.LOtoLL(self:GetVec3()) +local MGRS=coord.LLtoMGRS(lat,lon) +return"MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy) +end +function COORDINATE:NewFromMGRSString(MGRSString) +local myparts=UTILS.Split(MGRSString," ") +local northing=tostring(myparts[5])or"" +local easting=tostring(myparts[4])or"" +if string.len(easting)<5 then easting=easting..string.rep("0",5-string.len(easting))end +if string.len(northing)<5 then northing=northing..string.rep("0",5-string.len(northing))end +local MGRS={ +UTMZone=myparts[2], +MGRSDigraph=myparts[3], +Easting=easting, +Northing=northing, +} +local lat,lon=coord.MGRStoLL(MGRS) +local point=coord.LLtoLO(lat,lon,0) +local coord=COORDINATE:NewFromVec2({x=point.x,y=point.z}) +return coord +end +function COORDINATE:NewFromMGRS(UTMZone,MGRSDigraph,Easting,Northing) +if string.len(Easting)<5 then Easting=tostring(Easting..string.rep("0",5-string.len(Easting)))end +if string.len(Northing)<5 then Northing=tostring(Northing..string.rep("0",5-string.len(Northing)))end +local MGRS={ +UTMZone=UTMZone, +MGRSDigraph=MGRSDigraph, +Easting=tostring(Easting), +Northing=tostring(Northing), +} +local lat,lon=coord.MGRStoLL(MGRS) +local point=coord.LLtoLO(lat,lon,0) +local coord=COORDINATE:NewFromVec2({x=point.x,y=point.z}) +return coord +end +function COORDINATE:ToStringFromRP(ReferenceCoord,ReferenceName,Controllable,Settings,MagVar) +self:F2({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +local IsAir=Controllable and Controllable:IsAirPlane()or false +if IsAir then +local DirectionVec3=ReferenceCoord:GetDirectionVec3(self) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local Distance=self:Get2DDistance(ReferenceCoord) +return"Targets are the last seen "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName +else +local DirectionVec3=ReferenceCoord:GetDirectionVec3(self) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local Distance=self:Get2DDistance(ReferenceCoord) +return"Target are located "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName +end +return nil +end +function COORDINATE:ToStringFromRPShort(ReferenceCoord,ReferenceName,Controllable,Settings,MagVar) +self:F2({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +local IsAir=Controllable and Controllable:IsAirPlane()or false +if IsAir then +local DirectionVec3=ReferenceCoord:GetDirectionVec3(self) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local Distance=self:Get2DDistance(ReferenceCoord) +return self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName +else +local DirectionVec3=ReferenceCoord:GetDirectionVec3(self) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local Distance=self:Get2DDistance(ReferenceCoord) +return self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName +end +return nil +end +function COORDINATE:ToStringA2G(Controllable,Settings,MagVar) +self:F2({Controllable=Controllable and Controllable:GetName()}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +if Settings:IsA2G_BR()then +if Controllable then +local Coordinate=Controllable:GetCoordinate() +return Controllable and self:ToStringBR(Coordinate,Settings,MagVar)or self:ToStringMGRS(Settings) +else +return self:ToStringMGRS(Settings) +end +end +if Settings:IsA2G_LL_DMS()then +return self:ToStringLLDMS(Settings) +end +if Settings:IsA2G_LL_DDM()then +return self:ToStringLLDDM(Settings) +end +if Settings:IsA2G_MGRS()then +return self:ToStringMGRS(Settings) +end +return nil +end +function COORDINATE:ToStringA2A(Controllable,Settings,MagVar) +self:F2({Controllable=Controllable and Controllable:GetName()}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +if Settings:IsA2A_BRAA()then +if Controllable then +local Coordinate=Controllable:GetCoordinate() +return self:ToStringBRA(Coordinate,Settings,MagVar) +else +return self:ToStringMGRS(Settings) +end +end +if Settings:IsA2A_BULLS()then +local Coalition=Controllable:GetCoalition() +return self:ToStringBULLS(Coalition,Settings,MagVar) +end +if Settings:IsA2A_LL_DMS()then +return self:ToStringLLDMS(Settings) +end +if Settings:IsA2A_LL_DDM()then +return self:ToStringLLDDM(Settings) +end +if Settings:IsA2A_MGRS()then +return self:ToStringMGRS(Settings) +end +return nil +end +function COORDINATE:ToString(Controllable,Settings) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +local ModeA2A=nil +if ModeA2A==nil then +local IsAir=Controllable and(Controllable:IsAirPlane()or Controllable:IsHelicopter())or false +if IsAir then +ModeA2A=true +else +ModeA2A=false +end +end +if ModeA2A==true then +return self:ToStringA2A(Controllable,Settings) +else +return self:ToStringA2G(Controllable,Settings) +end +return nil +end +function COORDINATE:ToStringPressure(Controllable,Settings) +self:F2({Controllable=Controllable and Controllable:GetName()}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +return self:GetPressureText(nil,Settings) +end +function COORDINATE:ToStringWind(Controllable,Settings) +self:F2({Controllable=Controllable and Controllable:GetName()}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +return self:GetWindText(nil,Settings) +end +function COORDINATE:ToStringTemperature(Controllable,Settings) +self:F2({Controllable=Controllable and Controllable:GetName()}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +return self:GetTemperatureText(nil,Settings) +end +function COORDINATE:IsInSteepArea(Radius,Minelevation) +local steep=false +local elev=Minelevation or 8 +local bdelta=0 +local h0=self:GetLandHeight() +local radius=Radius or 50 +local diam=radius*2 +for i=0,150,30 do +local polar=math.fmod(i+180,360) +local c1=self:Translate(radius,i,false,false) +local c2=self:Translate(radius,polar,false,false) +local h1=c1:GetLandHeight() +local h2=c2:GetLandHeight() +local d1=math.abs(h1-h2) +local d2=math.abs(h0-h1) +local d3=math.abs(h0-h2) +local dm=d1>d2 and d1 or d2 +local dm1=dm>d3 and dm or d3 +bdelta=dm1>bdelta and dm1 or bdelta +self:T(string.format("d1=%d, d2=%d, d3=%d, max delta=%d",d1,d2,d3,bdelta)) +end +local steepness=bdelta/(radius/100) +if steepness>=elev then steep=true end +return steep,math.floor(steepness) +end +function COORDINATE:IsInFlatArea(Radius,Minelevation) +local steep,elev=self:IsInSteepArea(Radius,Minelevation) +local flat=not steep +return flat,elev +end +function COORDINATE:GetRandomPointVec3InRadius(OuterRadius,InnerRadius) +return COORDINATE:NewFromVec3(self:GetRandomVec3InRadius(OuterRadius,InnerRadius)) +end +function COORDINATE:GetSimpleZones(SearchRadius,PosRadius,NumPositions) +local clearPositions=UTILS.GetSimpleZones(self:GetVec3(),SearchRadius,PosRadius,NumPositions) +if clearPositions and#clearPositions>0 then +local coords={} +for _,pos in pairs(clearPositions)do +local coord=COORDINATE:NewFromVec2(pos) +table.insert(coords,coord) +end +return coords +end +return nil +end +end +do +POINT_VEC3={ +ClassName="POINT_VEC3", +Metric=true, +RoutePointAltType={ +BARO="BARO", +}, +RoutePointType={ +TakeOffParking="TakeOffParking", +TurningPoint="Turning Point", +}, +RoutePointAction={ +FromParkingArea="From Parking Area", +TurningPoint="Turning Point", +}, +} +function POINT_VEC3:New(x,y,z) +local self=BASE:Inherit(self,COORDINATE:New(x,y,z)) +self:F2(self) +return self +end +end +do +POINT_VEC2={ +ClassName="POINT_VEC2", +} +function POINT_VEC2:New(x,y,LandHeightAdd) +local LandHeight=land.getHeight({["x"]=x,["y"]=y}) +LandHeightAdd=LandHeightAdd or 0 +LandHeight=LandHeight+LandHeightAdd +local self=BASE:Inherit(self,COORDINATE:New(x,LandHeight,y)) +self:F2(self) +return self +end +end +do +VELOCITY={ +ClassName="VELOCITY", +} +function VELOCITY:New(VelocityMps) +local self=BASE:Inherit(self,BASE:New()) +self:F({}) +self.Velocity=VelocityMps +return self +end +function VELOCITY:Set(VelocityMps) +self.Velocity=VelocityMps +return self +end +function VELOCITY:Get() +return self.Velocity +end +function VELOCITY:SetKmph(VelocityKmph) +self.Velocity=UTILS.KmphToMps(VelocityKmph) +return self +end +function VELOCITY:GetKmph() +return UTILS.MpsToKmph(self.Velocity) +end +function VELOCITY:SetMiph(VelocityMiph) +self.Velocity=UTILS.MiphToMps(VelocityMiph) +return self +end +function VELOCITY:GetMiph() +return UTILS.MpsToMiph(self.Velocity) +end +function VELOCITY:GetText(Settings) +local Settings=Settings or _SETTINGS +if self.Velocity~=0 then +if Settings:IsMetric()then +return string.format("%d km/h",UTILS.MpsToKmph(self.Velocity)) +else +return string.format("%d mi/h",UTILS.MpsToMiph(self.Velocity)) +end +else +return"stationary" +end +end +function VELOCITY:ToString(VelocityGroup,Settings) +self:F({Group=VelocityGroup and VelocityGroup:GetName()}) +local Settings=Settings or(VelocityGroup and _DATABASE:GetPlayerSettings(VelocityGroup:GetPlayerName()))or _SETTINGS +return self:GetText(Settings) +end +end +do +VELOCITY_POSITIONABLE={ +ClassName="VELOCITY_POSITIONABLE", +} +function VELOCITY_POSITIONABLE:New(Positionable) +local self=BASE:Inherit(self,VELOCITY:New()) +self:F({}) +self.Positionable=Positionable +return self +end +function VELOCITY_POSITIONABLE:Get() +return self.Positionable:GetVelocityMPS()or 0 +end +function VELOCITY_POSITIONABLE:GetKmph() +return UTILS.MpsToKmph(self.Positionable:GetVelocityMPS()or 0) +end +function VELOCITY_POSITIONABLE:GetMiph() +return UTILS.MpsToMiph(self.Positionable:GetVelocityMPS()or 0) +end +function VELOCITY_POSITIONABLE:ToString() +self:F({Group=self.Positionable and self.Positionable:GetName()}) +local Settings=Settings or(self.Positionable and _DATABASE:GetPlayerSettings(self.Positionable:GetPlayerName()))or _SETTINGS +self.Velocity=self.Positionable:GetVelocityMPS() +return self:GetText(Settings) +end +end +MESSAGE={ +ClassName="MESSAGE", +MessageCategory=0, +MessageID=0, +} +MESSAGE.Type={ +Update="Update", +Information="Information", +Briefing="Briefing Report", +Overview="Overview Report", +Detailed="Detailed Report", +} +function MESSAGE:New(Text,Duration,Category,ClearScreen) +local self=BASE:Inherit(self,BASE:New()) +self:F({Text,Duration,Category}) +self.MessageType=nil +if Category and Category~=""then +if Category:sub(-1)~="\n"then +self.MessageCategory=Category..": " +else +self.MessageCategory=Category:sub(1,-2)..":\n" +end +else +self.MessageCategory="" +end +self.ClearScreen=false +if ClearScreen~=nil then +self.ClearScreen=ClearScreen +end +self.MessageDuration=Duration or 5 +self.MessageTime=timer.getTime() +self.MessageText=Text:gsub("^\n","",1):gsub("\n$","",1) +self.MessageSent=false +self.MessageGroup=false +self.MessageCoalition=false +return self +end +function MESSAGE:NewType(MessageText,MessageType,ClearScreen) +local self=BASE:Inherit(self,BASE:New()) +self:F({MessageText}) +self.MessageType=MessageType +self.ClearScreen=false +if ClearScreen~=nil then +self.ClearScreen=ClearScreen +end +self.MessageTime=timer.getTime() +self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1) +return self +end +function MESSAGE:Clear() +self:F() +self.ClearScreen=true +return self +end +function MESSAGE:ToClient(Client,Settings) +self:F(Client) +self:ToUnit(Client,Settings) +return self +end +function MESSAGE:ToGroup(Group,Settings) +self:F(Group.GroupName) +if Group and Group:IsAlive()then +if self.MessageType then +local Settings=Settings or(Group and _DATABASE:GetPlayerSettings(Group:GetPlayerName()))or _SETTINGS +self.MessageDuration=Settings:GetMessageTime(self.MessageType) +self.MessageCategory="" +end +if self.MessageDuration~=0 then +self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) +trigger.action.outTextForGroup(Group:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) +end +end +return self +end +function MESSAGE:ToUnit(Unit,Settings) +self:F(Unit.IdentifiableName) +if Unit and Unit:IsAlive()then +if self.MessageType then +local Settings=Settings or(Unit and _DATABASE:GetPlayerSettings(Unit:GetPlayerName()))or _SETTINGS +self.MessageDuration=Settings:GetMessageTime(self.MessageType) +self.MessageCategory="" +end +if self.MessageDuration~=0 then +self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) +local ID=Unit:GetID() +trigger.action.outTextForUnit(Unit:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) +end +end +return self +end +function MESSAGE:ToCountry(Country,Settings) +self:F(Country) +if Country then +if self.MessageType then +local Settings=Settings or _SETTINGS +self.MessageDuration=Settings:GetMessageTime(self.MessageType) +self.MessageCategory="" +end +if self.MessageDuration~=0 then +self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) +trigger.action.outTextForCountry(Country,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) +end +end +return self +end +function MESSAGE:ToCountryIf(Country,Condition,Settings) +self:F(Country) +if Country and Condition==true then +self:ToCountry(Country,Settings) +end +return self +end +function MESSAGE:ToBlue() +self:F() +self:ToCoalition(coalition.side.BLUE) +return self +end +function MESSAGE:ToRed() +self:F() +self:ToCoalition(coalition.side.RED) +return self +end +function MESSAGE:ToCoalition(CoalitionSide,Settings) +self:F(CoalitionSide) +if self.MessageType then +local Settings=Settings or _SETTINGS +self.MessageDuration=Settings:GetMessageTime(self.MessageType) +self.MessageCategory="" +end +if CoalitionSide then +if self.MessageDuration~=0 then +self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) +trigger.action.outTextForCoalition(CoalitionSide,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) +end +end +self.CoalitionSide=CoalitionSide +return self +end +function MESSAGE:ToCoalitionIf(CoalitionSide,Condition) +self:F(CoalitionSide) +if Condition and Condition==true then +self:ToCoalition(CoalitionSide) +end +return self +end +function MESSAGE:ToAll(Settings,Delay) +self:F() +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MESSAGE.ToAll,self,Settings,0) +else +if self.MessageType then +local Settings=Settings or _SETTINGS +self.MessageDuration=Settings:GetMessageTime(self.MessageType) +self.MessageCategory="" +end +if self.MessageDuration~=0 then +self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) +trigger.action.outText(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) +end +end +return self +end +function MESSAGE:ToAllIf(Condition) +if Condition and Condition==true then +self:ToAll() +end +return self +end +function MESSAGE:ToLog() +env.info(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","")) +return self +end +function MESSAGE:ToLogIf(Condition) +if Condition and Condition==true then +env.info(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","")) +end +return self +end +_MESSAGESRS={} +function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend) +_MESSAGESRS.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +_MESSAGESRS.frequency=Frequency or MSRS.frequencies or 243 +_MESSAGESRS.modulation=Modulation or MSRS.modulations or radio.modulation.AM +_MESSAGESRS.MSRS=MSRS:New(_MESSAGESRS.PathToSRS,_MESSAGESRS.frequency,_MESSAGESRS.modulation) +_MESSAGESRS.coalition=Coalition or MSRS.coalition or coalition.side.NEUTRAL +_MESSAGESRS.MSRS:SetCoalition(_MESSAGESRS.coalition) +_MESSAGESRS.coordinate=Coordinate +if Coordinate then +_MESSAGESRS.MSRS:SetCoordinate(Coordinate) +end +if Backend then +_MESSAGESRS.MSRS:SetBackend(Backend) +end +_MESSAGESRS.Culture=Culture or MSRS.culture or"en-GB" +_MESSAGESRS.MSRS:SetCulture(Culture) +_MESSAGESRS.Gender=Gender or MSRS.gender or"female" +_MESSAGESRS.MSRS:SetGender(Gender) +if PathToCredentials then +_MESSAGESRS.MSRS:SetProviderOptionsGoogle(PathToCredentials) +_MESSAGESRS.MSRS:SetProvider(MSRS.Provider.GOOGLE) +end +_MESSAGESRS.label=Label or MSRS.Label or"MESSAGE" +_MESSAGESRS.MSRS:SetLabel(_MESSAGESRS.label) +_MESSAGESRS.port=Port or MSRS.port or 5002 +_MESSAGESRS.MSRS:SetPort(_MESSAGESRS.port) +_MESSAGESRS.volume=Volume or MSRS.volume or 1 +_MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume) +if Voice then _MESSAGESRS.MSRS:SetVoice(Voice)end +_MESSAGESRS.voice=Voice or MSRS.voice +_MESSAGESRS.SRSQ=MSRSQUEUE:New(_MESSAGESRS.label) +end +function MESSAGE:ToSRS(frequency,modulation,gender,culture,voice,coalition,volume,coordinate) +local tgender=gender or _MESSAGESRS.Gender +if _MESSAGESRS.SRSQ then +if voice then +_MESSAGESRS.MSRS:SetVoice(voice or _MESSAGESRS.voice) +end +if coordinate then +_MESSAGESRS.MSRS:SetCoordinate(coordinate) +end +local category=string.gsub(self.MessageCategory,":","") +_MESSAGESRS.SRSQ:NewTransmission(self.MessageText,nil,_MESSAGESRS.MSRS,0.5,1,nil,nil,nil,frequency or _MESSAGESRS.frequency,modulation or _MESSAGESRS.modulation,gender or _MESSAGESRS.Gender,culture or _MESSAGESRS.Culture,nil,volume or _MESSAGESRS.volume,category,coordinate or _MESSAGESRS.coordinate) +end +return self +end +function MESSAGE:ToSRSBlue(frequency,modulation,gender,culture,voice,volume,coordinate) +self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.BLUE,volume,coordinate) +return self +end +function MESSAGE:ToSRSRed(frequency,modulation,gender,culture,voice,volume,coordinate) +self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.RED,volume,coordinate) +return self +end +function MESSAGE:ToSRSAll(frequency,modulation,gender,culture,voice,volume,coordinate) +self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.NEUTRAL,volume,coordinate) +return self +end +do +FSM={ +ClassName="FSM", +} +function FSM:New() +self=BASE:Inherit(self,BASE:New()) +self.options=options or{} +self.options.subs=self.options.subs or{} +self.current=self.options.initial or'none' +self.Events={} +self.subs={} +self.endstates={} +self.Scores={} +self._StartState="none" +self._Transitions={} +self._Processes={} +self._EndStates={} +self._Scores={} +self._EventSchedules={} +self.CallScheduler=SCHEDULER:New(self) +return self +end +function FSM:SetStartState(State) +self._StartState=State +self.current=State +end +function FSM:GetStartState() +return self._StartState or{} +end +function FSM:AddTransition(From,Event,To) +local Transition={} +Transition.From=From +Transition.Event=Event +Transition.To=To +self._Transitions[Transition]=Transition +self:_eventmap(self.Events,Transition) +end +function FSM:GetTransitions() +return self._Transitions or{} +end +function FSM:AddProcess(From,Event,Process,ReturnEvents) +local Sub={} +Sub.From=From +Sub.Event=Event +Sub.fsm=Process +Sub.StartEvent="Start" +Sub.ReturnEvents=ReturnEvents +self._Processes[Sub]=Sub +self:_submap(self.subs,Sub,nil) +self:AddTransition(From,Event,From) +return Process +end +function FSM:GetProcesses() +self:F({Processes=self._Processes}) +return self._Processes or{} +end +function FSM:GetProcess(From,Event) +for ProcessID,Process in pairs(self:GetProcesses())do +if Process.From==From and Process.Event==Event then +return Process.fsm +end +end +error("Sub-Process from state "..From.." with event "..Event.." not found!") +end +function FSM:SetProcess(From,Event,Fsm) +for ProcessID,Process in pairs(self:GetProcesses())do +if Process.From==From and Process.Event==Event then +Process.fsm=Fsm +return true +end +end +error("Sub-Process from state "..From.." with event "..Event.." not found!") +end +function FSM:AddEndState(State) +self._EndStates[State]=State +self.endstates[State]=State +end +function FSM:GetEndStates() +return self._EndStates or{} +end +function FSM:AddScore(State,ScoreText,Score) +self:F({State,ScoreText,Score}) +self._Scores[State]=self._Scores[State]or{} +self._Scores[State].ScoreText=ScoreText +self._Scores[State].Score=Score +return self +end +function FSM:AddScoreProcess(From,Event,State,ScoreText,Score) +self:F({From,Event,State,ScoreText,Score}) +local Process=self:GetProcess(From,Event) +Process._Scores[State]=Process._Scores[State]or{} +Process._Scores[State].ScoreText=ScoreText +Process._Scores[State].Score=Score +return Process +end +function FSM:GetScores() +return self._Scores or{} +end +function FSM:GetSubs() +return self.options.subs +end +function FSM:LoadCallBacks(CallBackTable) +for name,callback in pairs(CallBackTable or{})do +self[name]=callback +end +end +function FSM:_eventmap(Events,EventStructure) +local Event=EventStructure.Event +local __Event="__"..EventStructure.Event +self[Event]=self[Event]or self:_create_transition(Event) +self[__Event]=self[__Event]or self:_delayed_transition(Event) +Events[Event]=self.Events[Event]or{map={}} +self:_add_to_map(Events[Event].map,EventStructure) +end +function FSM:_submap(subs,sub,name) +subs[sub.From]=subs[sub.From]or{} +subs[sub.From][sub.Event]=subs[sub.From][sub.Event]or{} +subs[sub.From][sub.Event][sub]={} +subs[sub.From][sub.Event][sub].fsm=sub.fsm +subs[sub.From][sub.Event][sub].StartEvent=sub.StartEvent +subs[sub.From][sub.Event][sub].ReturnEvents=sub.ReturnEvents or{} +subs[sub.From][sub.Event][sub].name=name +subs[sub.From][sub.Event][sub].fsmparent=self +end +function FSM:_call_handler(step,trigger,params,EventName) +local handler=step..trigger +if self[handler]then +self._EventSchedules[EventName]=nil +local ErrorHandler=function(errmsg) +env.info("Error in SCHEDULER function:"..errmsg) +if BASE.Debug~=nil then +env.info(BASE.Debug.traceback()) +end +return errmsg +end +local Result,Value=xpcall(function() +return self[handler](self,unpack(params)) +end,ErrorHandler) +return Value +end +end +function FSM._handler(self,EventName,...) +local Can,To=self:can(EventName) +if To=="*"then +To=self.current +end +if Can then +local From=self.current +local Params={From,EventName,To,...} +if self["onleave"..From]or +self["OnLeave"..From]or +self["onbefore"..EventName]or +self["OnBefore"..EventName]or +self["onafter"..EventName]or +self["OnAfter"..EventName]or +self["onenter"..To]or +self["OnEnter"..To]then +if self:_call_handler("onbefore",EventName,Params,EventName)==false then +self:T("*** FSM *** Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** onbefore"..EventName) +return false +else +if self:_call_handler("OnBefore",EventName,Params,EventName)==false then +self:T("*** FSM *** Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** OnBefore"..EventName) +return false +else +if self:_call_handler("onleave",From,Params,EventName)==false then +self:T("*** FSM *** Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** onleave"..From) +return false +else +if self:_call_handler("OnLeave",From,Params,EventName)==false then +self:T("*** FSM *** Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** OnLeave"..From) +return false +end +end +end +end +else +local ClassName=self:GetClassName() +if ClassName=="FSM"then +self:T("*** FSM *** Transit *** "..self.current.." --> "..EventName.." --> "..To) +end +if ClassName=="FSM_TASK"then +self:T("*** FSM *** Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** Task: "..self.TaskName) +end +if ClassName=="FSM_CONTROLLABLE"then +self:T("*** FSM *** Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** TaskUnit: "..self.Controllable.ControllableName.." *** ") +end +if ClassName=="FSM_PROCESS"then +self:T("*** FSM *** Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** Task: "..self.Task:GetName()..", TaskUnit: "..self.Controllable.ControllableName.." *** ") +end +end +self.current=To +local execute=true +local subtable=self:_gosub(From,EventName) +for _,sub in pairs(subtable)do +self:T("*** FSM *** Sub *** "..sub.StartEvent) +sub.fsm.fsmparent=self +sub.fsm.ReturnEvents=sub.ReturnEvents +sub.fsm[sub.StartEvent](sub.fsm) +execute=false +end +local fsmparent,Event=self:_isendstate(To) +if fsmparent and Event then +self:T("*** FSM *** End *** "..Event) +self:_call_handler("onenter",To,Params,EventName) +self:_call_handler("OnEnter",To,Params,EventName) +self:_call_handler("onafter",EventName,Params,EventName) +self:_call_handler("OnAfter",EventName,Params,EventName) +self:_call_handler("onstate","change",Params,EventName) +fsmparent[Event](fsmparent) +execute=false +end +if execute then +self:_call_handler("onafter",EventName,Params,EventName) +self:_call_handler("OnAfter",EventName,Params,EventName) +self:_call_handler("onenter",To,Params,EventName) +self:_call_handler("OnEnter",To,Params,EventName) +self:_call_handler("onstate","change",Params,EventName) +end +else +self:T("*** FSM *** NO Transition *** "..self.current.." --> "..EventName.." --> ? ") +end +return nil +end +function FSM:_delayed_transition(EventName) +return function(self,DelaySeconds,...) +self:T3("Delayed Event: "..EventName) +local CallID=0 +if DelaySeconds~=nil then +if DelaySeconds<0 then +DelaySeconds=math.abs(DelaySeconds) +if not self._EventSchedules[EventName]then +CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1,nil,nil,nil,4,true) +self._EventSchedules[EventName]=CallID +self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec SCHEDULED with CallID=%s",EventName,DelaySeconds,tostring(CallID))) +else +self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec CANCELLED as we already have such an event in the queue.",EventName,DelaySeconds)) +end +else +CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1,nil,nil,nil,4,true) +self:T2(string.format("Event %s delayed by %.3f sec SCHEDULED with CallID=%s",EventName,DelaySeconds,tostring(CallID))) +end +else +error("FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this.") +end +end +end +function FSM:_create_transition(EventName) +return function(self,...) +return self._handler(self,EventName,...) +end +end +function FSM:_gosub(ParentFrom,ParentEvent) +local fsmtable={} +if self.subs[ParentFrom]and self.subs[ParentFrom][ParentEvent]then +return self.subs[ParentFrom][ParentEvent] +else +return{} +end +end +function FSM:_isendstate(Current) +local FSMParent=self.fsmparent +if FSMParent and self.endstates[Current]then +FSMParent.current=Current +local ParentFrom=FSMParent.current +local Event=self.ReturnEvents[Current] +if Event then +return FSMParent,Event +else +end +end +return nil +end +function FSM:_add_to_map(Map,Event) +self:F3({Map,Event}) +if type(Event.From)=='string'then +Map[Event.From]=Event.To +else +for _,From in ipairs(Event.From)do +Map[From]=Event.To +end +end +end +function FSM:GetState() +return self.current +end +function FSM:GetCurrentState() +return self.current +end +function FSM:Is(State) +return self.current==State +end +function FSM:is(state) +return self.current==state +end +function FSM:can(e) +local Event=self.Events[e] +local To=Event and Event.map[self.current]or Event.map['*'] +return To~=nil,To +end +function FSM:cannot(e) +return not self:can(e) +end +end +do +FSM_CONTROLLABLE={ +ClassName="FSM_CONTROLLABLE", +} +function FSM_CONTROLLABLE:New(Controllable) +local self=BASE:Inherit(self,FSM:New()) +if Controllable then +self:SetControllable(Controllable) +end +self:AddTransition("*","Stop","Stopped") +return self +end +function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To) +self.CallScheduler:Clear() +end +function FSM_CONTROLLABLE:SetControllable(FSMControllable) +self.Controllable=FSMControllable +end +function FSM_CONTROLLABLE:GetControllable() +return self.Controllable +end +function FSM_CONTROLLABLE:_call_handler(step,trigger,params,EventName) +local handler=step..trigger +local ErrorHandler=function(errmsg) +env.info("Error in SCHEDULER function:"..errmsg) +if BASE.Debug~=nil then +env.info(BASE.Debug.traceback()) +end +return errmsg +end +if self[handler]then +self:T("*** FSM *** "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** TaskUnit: "..self.Controllable:GetName()) +self._EventSchedules[EventName]=nil +local Result,Value=xpcall(function() +return self[handler](self,self.Controllable,unpack(params)) +end,ErrorHandler) +return Value +end +end +end +do +FSM_PROCESS={ClassName="FSM_PROCESS"} +function FSM_PROCESS:New(Controllable,Task) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) +self:Assign(Controllable,Task) +return self +end +function FSM_PROCESS:Init(FsmProcess) +self:T("No Initialisation") +end +function FSM_PROCESS:_call_handler(step,trigger,params,EventName) +local handler=step..trigger +local ErrorHandler=function(errmsg) +env.info("Error in FSM_PROCESS call handler:"..errmsg) +if BASE.Debug~=nil then +env.info(BASE.Debug.traceback()) +end +return errmsg +end +if self[handler]then +if handler~="onstatechange"then +self:T("*** FSM *** "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** Task: "..self.Task:GetName()..", TaskUnit: "..self.Controllable:GetName()) +end +self._EventSchedules[EventName]=nil +local Result,Value +if self.Controllable and self.Controllable:IsAlive()==true then +Result,Value=xpcall(function() +return self[handler](self,self.Controllable,self.Task,unpack(params)) +end,ErrorHandler) +end +return Value +end +end +function FSM_PROCESS:Copy(Controllable,Task) +local NewFsm=self:New(Controllable,Task) +NewFsm:Assign(Controllable,Task) +NewFsm:Init(self) +NewFsm:SetStartState(self:GetStartState()) +for TransitionID,Transition in pairs(self:GetTransitions())do +NewFsm:AddTransition(Transition.From,Transition.Event,Transition.To) +end +for ProcessID,Process in pairs(self:GetProcesses())do +local FsmProcess=NewFsm:AddProcess(Process.From,Process.Event,Process.fsm:Copy(Controllable,Task),Process.ReturnEvents) +end +for EndStateID,EndState in pairs(self:GetEndStates())do +NewFsm:AddEndState(EndState) +end +for ScoreID,Score in pairs(self:GetScores())do +NewFsm:AddScore(ScoreID,Score.ScoreText,Score.Score) +end +return NewFsm +end +function FSM_PROCESS:Remove() +self:F({self:GetClassNameAndID()}) +self:F("Clearing Schedules") +self.CallScheduler:Clear() +for ProcessID,Process in pairs(self:GetProcesses())do +if Process.fsm then +Process.fsm:Remove() +Process.fsm=nil +end +end +return self +end +function FSM_PROCESS:SetTask(Task) +self.Task=Task +return self +end +function FSM_PROCESS:GetTask() +return self.Task +end +function FSM_PROCESS:GetMission() +return self.Task.Mission +end +function FSM_PROCESS:GetCommandCenter() +return self:GetTask():GetMission():GetCommandCenter() +end +function FSM_PROCESS:Message(Message) +self:F({Message=Message}) +local CC=self:GetCommandCenter() +local TaskGroup=self.Controllable:GetGroup() +local PlayerName=self.Controllable:GetPlayerName() +PlayerName=PlayerName and" ("..PlayerName..")"or"" +local Callsign=self.Controllable:GetCallsign() +local Prefix=Callsign and" @ "..Callsign..PlayerName or"" +Message=Prefix..": "..Message +CC:MessageToGroup(Message,TaskGroup) +end +function FSM_PROCESS:Assign(ProcessUnit,Task) +self:SetControllable(ProcessUnit) +self:SetTask(Task) +return self +end +function FSM_PROCESS:onenterFailed(ProcessUnit,Task,From,Event,To) +self:T("*** FSM *** Failed *** "..Task:GetName().."/"..ProcessUnit:GetName().." *** "..From.." --> "..Event.." --> "..To) +self.Task:Fail() +end +function FSM_PROCESS:onstatechange(ProcessUnit,Task,From,Event,To) +if From~=To then +self:T("*** FSM *** Change *** "..Task:GetName().."/"..ProcessUnit:GetName().." *** "..From.." --> "..Event.." --> "..To) +end +if self._Scores[To]then +local Task=self.Task +local Scoring=Task:GetScoring() +if Scoring then +Scoring:_AddMissionTaskScore(Task.Mission,ProcessUnit,self._Scores[To].ScoreText,self._Scores[To].Score) +end +end +end +end +do +FSM_TASK={ +ClassName="FSM_TASK", +} +function FSM_TASK:New(TaskName) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) +self["onstatechange"]=self.OnStateChange +self.TaskName=TaskName +return self +end +function FSM_TASK:_call_handler(step,trigger,params,EventName) +local handler=step..trigger +local ErrorHandler=function(errmsg) +env.info("Error in SCHEDULER function:"..errmsg) +if BASE.Debug~=nil then +env.info(BASE.Debug.traceback()) +end +return errmsg +end +if self[handler]then +self:T("*** FSM *** "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** Task: "..self.TaskName) +self._EventSchedules[EventName]=nil +local Result,Value=xpcall(function() +return self[handler](self,unpack(params)) +end,ErrorHandler) +return Value +end +end +end +do +FSM_SET={ +ClassName="FSM_SET", +} +function FSM_SET:New(FSMSet) +self=BASE:Inherit(self,FSM:New()) +if FSMSet then +self:Set(FSMSet) +end +return self +end +function FSM_SET:Set(FSMSet) +self:F(FSMSet) +self.Set=FSMSet +end +function FSM_SET:Get() +return self.Set +end +function FSM_SET:_call_handler(step,trigger,params,EventName) +local handler=step..trigger +if self[handler]then +self:T("*** FSM *** "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3]) +self._EventSchedules[EventName]=nil +return self[handler](self,self.Set,unpack(params)) +end +end +end +SPAWN={ +ClassName="SPAWN", +SpawnTemplatePrefix=nil, +SpawnAliasPrefix=nil, +} +SPAWN.Takeoff={ +Air=1, +Runway=2, +Hot=3, +Cold=4, +} +function SPAWN:New(SpawnTemplatePrefix) +local self=BASE:Inherit(self,BASE:New()) +local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix) +if TemplateGroup then +self.SpawnTemplatePrefix=SpawnTemplatePrefix +self.SpawnIndex=0 +self.SpawnCount=0 +self.AliveUnits=0 +self.SpawnIsScheduled=false +self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix) +self.Repeat=false +self.UnControlled=false +self.SpawnInitLimit=false +self.SpawnMaxUnitsAlive=0 +self.SpawnMaxGroups=0 +self.SpawnRandomize=false +self.SpawnVisible=false +self.AIOnOff=true +self.SpawnUnControlled=false +self.SpawnInitKeepUnitNames=false +self.DelayOnOff=false +self.SpawnGrouping=nil +self.SpawnInitLivery=nil +self.SpawnInitSkill=nil +self.SpawnInitFreq=nil +self.SpawnInitModu=nil +self.SpawnInitRadio=nil +self.SpawnInitModex=nil +self.SpawnInitModexPrefix=nil +self.SpawnInitModexPostfix=nil +self.SpawnInitAirbase=nil +self.TweakedTemplate=false +self.SpawnRandomCallsign=false +self.SpawnGroups={} +else +error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'") +end +self:SetEventPriority(5) +self.SpawnHookScheduler=SCHEDULER:New(nil) +return self +end +function SPAWN:NewWithAlias(SpawnTemplatePrefix,SpawnAliasPrefix) +local self=BASE:Inherit(self,BASE:New()) +local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix) +if TemplateGroup then +self.SpawnTemplatePrefix=SpawnTemplatePrefix +self.SpawnAliasPrefix=SpawnAliasPrefix +self.SpawnIndex=0 +self.SpawnCount=0 +self.AliveUnits=0 +self.SpawnIsScheduled=false +self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix) +self.Repeat=false +self.UnControlled=false +self.SpawnInitLimit=false +self.SpawnMaxUnitsAlive=0 +self.SpawnMaxGroups=0 +self.SpawnRandomize=false +self.SpawnVisible=false +self.AIOnOff=true +self.SpawnUnControlled=false +self.SpawnInitKeepUnitNames=false +self.DelayOnOff=false +self.SpawnGrouping=nil +self.SpawnInitLivery=nil +self.SpawnInitSkill=nil +self.SpawnInitFreq=nil +self.SpawnInitModu=nil +self.SpawnInitRadio=nil +self.SpawnInitModex=nil +self.SpawnInitModexPrefix=nil +self.SpawnInitModexPostfix=nil +self.SpawnInitAirbase=nil +self.TweakedTemplate=false +self.SpawnGroups={} +else +error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'") +end +self:SetEventPriority(5) +self.SpawnHookScheduler=SCHEDULER:New(nil) +return self +end +function SPAWN:NewFromTemplate(SpawnTemplate,SpawnTemplatePrefix,SpawnAliasPrefix,NoMooseNamingPostfix) +local self=BASE:Inherit(self,BASE:New()) +if SpawnTemplatePrefix==nil or SpawnTemplatePrefix==""then +BASE:I("ERROR: in function NewFromTemplate, required parameter SpawnTemplatePrefix is not set") +return nil +end +if SpawnTemplate then +self.SpawnTemplate=UTILS.DeepCopy(SpawnTemplate) +self.SpawnTemplatePrefix=SpawnTemplatePrefix +self.SpawnAliasPrefix=SpawnAliasPrefix or SpawnTemplatePrefix +self.SpawnTemplate.name=SpawnTemplatePrefix +self.SpawnIndex=0 +self.SpawnCount=0 +self.AliveUnits=0 +self.SpawnIsScheduled=false +self.Repeat=false +self.UnControlled=false +self.SpawnInitLimit=false +self.SpawnMaxUnitsAlive=0 +self.SpawnMaxGroups=0 +self.SpawnRandomize=false +self.SpawnVisible=false +self.AIOnOff=true +self.SpawnUnControlled=false +self.SpawnInitKeepUnitNames=false +self.DelayOnOff=false +self.Grouping=nil +self.SpawnInitLivery=nil +self.SpawnInitSkill=nil +self.SpawnInitFreq=nil +self.SpawnInitModu=nil +self.SpawnInitRadio=nil +self.SpawnInitModex=nil +self.SpawnInitModexPrefix=nil +self.SpawnInitModexPostfix=nil +self.SpawnInitAirbase=nil +self.TweakedTemplate=true +self.MooseNameing=true +if NoMooseNamingPostfix==true then +self.MooseNameing=false +end +self.SpawnGroups={} +else +error("There is no template provided for SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'") +end +self:SetEventPriority(5) +self.SpawnHookScheduler=SCHEDULER:New(nil) +return self +end +function SPAWN:InitLimit(SpawnMaxUnitsAlive,SpawnMaxGroups) +self.SpawnInitLimit=true +self.SpawnMaxUnitsAlive=SpawnMaxUnitsAlive +self.SpawnMaxGroups=SpawnMaxGroups +for SpawnGroupID=1,self.SpawnMaxGroups do +self:_InitializeSpawnGroups(SpawnGroupID) +end +return self +end +function SPAWN:InitKeepUnitNames(KeepUnitNames) +self.SpawnInitKeepUnitNames=false +if KeepUnitNames==true then self.SpawnInitKeepUnitNames=true end +return self +end +function SPAWN:InitLateActivated(LateActivated) +self.LateActivated=LateActivated or true +return self +end +function SPAWN:InitAirbase(AirbaseName,Takeoff,TerminalType) +self.SpawnInitAirbase=AIRBASE:FindByName(AirbaseName) +self.SpawnInitTakeoff=Takeoff or SPAWN.Takeoff.Hot +self.SpawnInitTerminalType=TerminalType +return self +end +function SPAWN:InitHeading(HeadingMin,HeadingMax) +self.SpawnInitHeadingMin=HeadingMin +self.SpawnInitHeadingMax=HeadingMax +return self +end +function SPAWN:InitGroupHeading(HeadingMin,HeadingMax,unitVar) +self:F({HeadingMin=HeadingMin,HeadingMax=HeadingMax,unitVar=unitVar}) +self.SpawnInitGroupHeadingMin=HeadingMin +self.SpawnInitGroupHeadingMax=HeadingMax +self.SpawnInitGroupUnitVar=unitVar +return self +end +function SPAWN:InitCoalition(Coalition) +self:F({coalition=Coalition}) +self.SpawnInitCoalition=Coalition +return self +end +function SPAWN:InitCountry(Country) +self.SpawnInitCountry=Country +return self +end +function SPAWN:InitCategory(Category) +self.SpawnInitCategory=Category +return self +end +function SPAWN:InitLivery(Livery) +self.SpawnInitLivery=Livery +return self +end +function SPAWN:InitSkill(Skill) +if Skill:lower()=="average"then +self.SpawnInitSkill="Average" +elseif Skill:lower()=="good"then +self.SpawnInitSkill="Good" +elseif Skill:lower()=="excellent"then +self.SpawnInitSkill="Excellent" +elseif Skill:lower()=="random"then +self.SpawnInitSkill="Random" +else +self.SpawnInitSkill="High" +end +return self +end +function SPAWN:InitSTN(Octal) +self.SpawnInitSTN=Octal or 77777 +local num=UTILS.OctalToDecimal(Octal) +if num==nil or num<1 then +self:E("WARNING - STN "..tostring(Octal).." is not valid!") +return self +end +if _DATABASE.STNS[num]~=nil then +self:E("WARNING - STN already assigned: "..tostring(Octal).." is used for ".._DATABASE.STNS[Octal]) +end +return self +end +function SPAWN:InitSADL(Octal) +self.SpawnInitSADL=Octal or 7777 +local num=UTILS.OctalToDecimal(Octal) +if num==nil or num<1 then +self:E("WARNING - SADL "..tostring(Octal).." is not valid!") +return self +end +if _DATABASE.SADL[num]~=nil then +self:E("WARNING - SADL already assigned: "..tostring(Octal).." is used for ".._DATABASE.SADL[Octal]) +end +return self +end +function SPAWN:InitSpeedMps(MPS) +if MPS==nil or tonumber(MPS)<0 then +MPS=125 +end +self.InitSpeed=MPS +return self +end +function SPAWN:InitSpeedKnots(Knots) +if Knots==nil or tonumber(Knots)<0 then +Knots=300 +end +self.InitSpeed=UTILS.KnotsToMps(Knots) +return self +end +function SPAWN:InitSpeedKph(KPH) +if KPH==nil or tonumber(KPH)<0 then +KPH=UTILS.KnotsToKmph(300) +end +self.InitSpeed=UTILS.KmphToMps(KPH) +return self +end +function SPAWN:InitRadioCommsOnOff(switch) +self.SpawnInitRadio=switch or true +return self +end +function SPAWN:InitRadioFrequency(frequency) +self.SpawnInitFreq=frequency +return self +end +function SPAWN:InitRadioModulation(modulation) +if modulation and modulation:lower()=="fm"then +self.SpawnInitModu=radio.modulation.FM +else +self.SpawnInitModu=radio.modulation.AM +end +return self +end +function SPAWN:InitModex(modex,prefix,postfix) +if modex then +self.SpawnInitModex=tonumber(modex) +end +self.SpawnInitModexPrefix=prefix +self.SpawnInitModexPostfix=postfix +return self +end +function SPAWN:InitRandomizeRoute(SpawnStartPoint,SpawnEndPoint,SpawnRadius,SpawnHeight) +self.SpawnRandomizeRoute=true +self.SpawnRandomizeRouteStartPoint=SpawnStartPoint +self.SpawnRandomizeRouteEndPoint=SpawnEndPoint +self.SpawnRandomizeRouteRadius=SpawnRadius +self.SpawnRandomizeRouteHeight=SpawnHeight +for GroupID=1,self.SpawnMaxGroups do +self:_RandomizeRoute(GroupID) +end +return self +end +function SPAWN:InitRandomizePosition(RandomizePosition,OuterRadius,InnerRadius) +self.SpawnRandomizePosition=RandomizePosition or false +self.SpawnRandomizePositionOuterRadius=OuterRadius or 0 +self.SpawnRandomizePositionInnerRadius=InnerRadius or 0 +for GroupID=1,self.SpawnMaxGroups do +self:_RandomizeRoute(GroupID) +end +return self +end +function SPAWN:InitRandomizeUnits(RandomizeUnits,OuterRadius,InnerRadius) +self.SpawnRandomizeUnits=RandomizeUnits or false +self.SpawnOuterRadius=OuterRadius or 0 +self.SpawnInnerRadius=InnerRadius or 0 +for GroupID=1,self.SpawnMaxGroups do +self:_RandomizeRoute(GroupID) +end +return self +end +function SPAWN:InitSetUnitRelativePositions(Positions) +self.SpawnUnitsWithRelativePositions=true +self.UnitsRelativePositions=Positions +return self +end +function SPAWN:InitSetUnitAbsolutePositions(Positions) +self.SpawnUnitsWithAbsolutePositions=true +self.UnitsAbsolutePositions=Positions +return self +end +function SPAWN:InitValidateAndRepositionGroundUnits(OnOff,MaxRadius,Spacing) +self.SpawnValidateAndRepositionGroundUnits=OnOff +self.SpawnValidateAndRepositionGroundUnitsRadius=MaxRadius +self.SpawnValidateAndRepositionGroundUnitsSpacing=Spacing +return self +end +function SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable) +local temptable={} +for _,_temp in pairs(SpawnTemplatePrefixTable)do +temptable[#temptable+1]=_temp +end +self.SpawnTemplatePrefixTable=UTILS.ShuffleTable(temptable) +self.SpawnRandomizeTemplate=true +for SpawnGroupID=1,self.SpawnMaxGroups do +self:_RandomizeTemplate(SpawnGroupID,RandomizePositionInZone) +end +return self +end +function SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet,RandomizePositionInZone) +local setnames=SpawnTemplateSet:GetSetNames() +self:InitRandomizeTemplate(setnames,RandomizePositionInZone) +return self +end +function SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes,RandomizePositionInZone) +local SpawnTemplateSet=SET_GROUP:New():FilterPrefixes(SpawnTemplatePrefixes):FilterOnce() +self:InitRandomizeTemplateSet(SpawnTemplateSet,RandomizePositionInZone) +return self +end +function SPAWN:InitGrouping(Grouping) +self.SpawnGrouping=Grouping +return self +end +function SPAWN:InitRandomizeZones(SpawnZoneTable,RandomizePositionInZone) +local temptable={} +for _,_temp in pairs(SpawnZoneTable)do +temptable[#temptable+1]=_temp +end +self.SpawnZoneTable=UTILS.ShuffleTable(temptable) +self.SpawnRandomizeZones=true +for SpawnGroupID=1,self.SpawnMaxGroups do +self:_RandomizeZones(SpawnGroupID,RandomizePositionInZone) +end +return self +end +function SPAWN:InitRandomizeCallsign() +self.SpawnRandomCallsign=true +return self +end +function SPAWN:InitCallSign(ID,Name,Minor,Major) +local Name=Name or"Enfield" +self.SpawnInitCallSign=true +self.SpawnInitCallSignID=ID or 1 +self.SpawnInitCallSignMinor=Minor or 1 +self.SpawnInitCallSignMajor=Major or 1 +self.SpawnInitCallSignName=string.lower(Name):gsub("^%l",string.upper) +return self +end +function SPAWN:InitPositionCoordinate(Coordinate) +self:InitPositionVec2(Coordinate:GetVec2()) +return self +end +function SPAWN:InitPositionVec2(Vec2) +self.SpawnInitPosition=Vec2 +self.SpawnFromNewPosition=true +for SpawnGroupID=1,self.SpawnMaxGroups do +self:_SetInitialPosition(SpawnGroupID) +end +return self +end +function SPAWN:InitRepeat() +self.Repeat=true +self.RepeatOnEngineShutDown=false +self.RepeatOnLanding=true +return self +end +function SPAWN:InitRepeatOnLanding(WaitingTime) +self:InitRepeat() +self.RepeatOnEngineShutDown=false +self.RepeatOnLanding=true +self.RepeatOnLandingTime=(WaitingTime and WaitingTime>3)and WaitingTime or 3 +return self +end +function SPAWN:InitRepeatOnEngineShutDown() +self:InitRepeat() +self.RepeatOnEngineShutDown=true +self.RepeatOnLanding=false +return self +end +function SPAWN:InitCleanUp(SpawnCleanUpInterval) +self.SpawnCleanUpInterval=SpawnCleanUpInterval +self.SpawnCleanUpTimeStamps={} +local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup() +self.CleanUpScheduler=SCHEDULER:New(self,self._SpawnCleanUpScheduler,{},1,SpawnCleanUpInterval,0.2) +return self +end +function SPAWN:InitArray(SpawnAngle,SpawnWidth,SpawnDeltaX,SpawnDeltaY) +self.SpawnVisible=true +local SpawnX=0 +local SpawnY=0 +local SpawnXIndex=0 +local SpawnYIndex=0 +for SpawnGroupID=1,self.SpawnMaxGroups do +self.SpawnGroups[SpawnGroupID].Visible=true +self.SpawnGroups[SpawnGroupID].Spawned=false +SpawnXIndex=SpawnXIndex+1 +if SpawnWidth and SpawnWidth~=0 then +if SpawnXIndex>=SpawnWidth then +SpawnXIndex=0 +SpawnYIndex=SpawnYIndex+1 +end +end +local SpawnRootX=self.SpawnGroups[SpawnGroupID].SpawnTemplate.x +local SpawnRootY=self.SpawnGroups[SpawnGroupID].SpawnTemplate.y +self:_TranslateRotate(SpawnGroupID,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle) +self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation=true +self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible=true +self.SpawnGroups[SpawnGroupID].Visible=true +self:HandleEvent(EVENTS.Birth,self._OnBirth) +self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.UnitLost,self._OnDeadOrCrash) +if self.Repeat then +self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff) +self:HandleEvent(EVENTS.Land,self._OnLand) +end +if self.RepeatOnEngineShutDown then +self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown) +end +self.SpawnGroups[SpawnGroupID].Group=_DATABASE:Spawn(self.SpawnGroups[SpawnGroupID].SpawnTemplate) +SpawnX=SpawnXIndex*SpawnDeltaX +SpawnY=SpawnYIndex*SpawnDeltaY +end +return self +end +function SPAWN:StopRepeat() +if self.Repeat then +self:UnHandleEvent(EVENTS.Takeoff) +self:UnHandleEvent(EVENTS.Land) +end +if self.RepeatOnEngineShutDown then +self:UnHandleEvent(EVENTS.EngineShutdown) +end +self.Repeat=false +self.RepeatOnEngineShutDown=false +self.RepeatOnLanding=false +return self +end +do +function SPAWN:InitAIOnOff(AIOnOff) +self.AIOnOff=AIOnOff +return self +end +function SPAWN:InitAIOn() +return self:InitAIOnOff(true) +end +function SPAWN:InitAIOff() +return self:InitAIOnOff(false) +end +end +do +function SPAWN:InitDelayOnOff(DelayOnOff) +self.DelayOnOff=DelayOnOff +return self +end +function SPAWN:InitDelayOn() +return self:InitDelayOnOff(true) +end +function SPAWN:InitDelayOff() +return self:InitDelayOnOff(false) +end +end +function SPAWN:InitHiddenOnMap(OnOff) +self.SpawnHiddenOnMap=OnOff==false and false or true +return self +end +function SPAWN:InitHiddenOnMFD() +self.SpawnHiddenOnMFD=true +return self +end +function SPAWN:InitHiddenOnPlanner() +self.SpawnHiddenOnPlanner=true +return self +end +function SPAWN:Spawn() +if self.SpawnInitAirbase then +return self:SpawnAtAirbase(self.SpawnInitAirbase,self.SpawnInitTakeoff,nil,self.SpawnInitTerminalType) +else +return self:SpawnWithIndex(self.SpawnIndex+1) +end +end +function SPAWN:ReSpawn(SpawnIndex) +if not SpawnIndex then +SpawnIndex=1 +end +local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) +local WayPoints=SpawnGroup and SpawnGroup.WayPoints or nil +if SpawnGroup then +local SpawnDCSGroup=SpawnGroup:GetDCSObject() +if SpawnDCSGroup then +SpawnGroup:Destroy() +end +end +local SpawnGroup=self:SpawnWithIndex(SpawnIndex) +if SpawnGroup and WayPoints then +SpawnGroup:WayPointInitialize(WayPoints) +SpawnGroup:WayPointExecute(1,5) +end +if SpawnGroup and SpawnGroup.ReSpawnFunction then +SpawnGroup:ReSpawnFunction() +end +if SpawnGroup then SpawnGroup:ResetEvents()end +return SpawnGroup +end +function SPAWN:SetSpawnIndex(SpawnIndex) +self.SpawnIndex=SpawnIndex or 0 +end +function SPAWN:SpawnWithIndex(SpawnIndex,NoBirth) +if self:_GetSpawnIndex(SpawnIndex)then +if self.SpawnFromNewPosition then +self:_SetInitialPosition(SpawnIndex) +end +if self.SpawnGroups[self.SpawnIndex].Visible then +self.SpawnGroups[self.SpawnIndex].Group:Activate() +else +local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate +local SpawnZone=self.SpawnGroups[self.SpawnIndex].SpawnZone +if SpawnTemplate then +local PointVec3=COORDINATE:New(SpawnTemplate.route.points[1].x,SpawnTemplate.route.points[1].alt,SpawnTemplate.route.points[1].y) +if self.SpawnRandomizePosition then +local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnRandomizePositionOuterRadius,self.SpawnRandomizePositionInnerRadius) +local CurrentX=SpawnTemplate.units[1].x +local CurrentY=SpawnTemplate.units[1].y +SpawnTemplate.x=RandomVec2.x +SpawnTemplate.y=RandomVec2.y +for UnitID=1,#SpawnTemplate.units do +SpawnTemplate.units[UnitID].x=SpawnTemplate.units[UnitID].x+(RandomVec2.x-CurrentX) +SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-CurrentY) +end +end +if self.SpawnRandomizeUnits then +for UnitID=1,#SpawnTemplate.units do +local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) +if(SpawnZone)then +local inZone=SpawnZone:IsVec2InZone(RandomVec2) +local numTries=1 +while(not inZone)and(numTries<20)do +if not inZone then +RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) +numTries=numTries+1 +inZone=SpawnZone:IsVec2InZone(RandomVec2) +end +end +if(not inZone)then +RandomVec2=SpawnZone:GetRandomVec2() +end +end +SpawnTemplate.units[UnitID].x=RandomVec2.x +SpawnTemplate.units[UnitID].y=RandomVec2.y +end +end +local function _Heading(courseDeg) +local h +if courseDeg<=180 then +h=math.rad(courseDeg) +else +h=-math.rad(360-courseDeg) +end +return h +end +local Rad180=math.rad(180) +local function _HeadingRad(courseRad) +if courseRad<=Rad180 then +return courseRad +else +return-((2*Rad180)-courseRad) +end +end +local function _RandomInRange(min,max) +if min and max then +return min+(math.random()*(max-min)) +else +return min +end +end +if self.SpawnInitGroupHeadingMin and#SpawnTemplate.units>0 then +local pivotX=SpawnTemplate.units[1].x +local pivotY=SpawnTemplate.units[1].y +local headingRad=math.rad(_RandomInRange(self.SpawnInitGroupHeadingMin or 0,self.SpawnInitGroupHeadingMax)) +local cosHeading=math.cos(headingRad) +local sinHeading=math.sin(headingRad) +local unitVarRad=math.rad(self.SpawnInitGroupUnitVar or 0) +for UnitID=1,#SpawnTemplate.units do +if not self.SpawnRandomizeUnits then +if UnitID>1 then +local unitXOff=SpawnTemplate.units[UnitID].x-pivotX +local unitYOff=SpawnTemplate.units[UnitID].y-pivotY +SpawnTemplate.units[UnitID].x=pivotX+(unitXOff*cosHeading)-(unitYOff*sinHeading) +SpawnTemplate.units[UnitID].y=pivotY+(unitYOff*cosHeading)+(unitXOff*sinHeading) +end +end +local unitHeading=SpawnTemplate.units[UnitID].heading+headingRad +SpawnTemplate.units[UnitID].heading=_HeadingRad(_RandomInRange(unitHeading-unitVarRad,unitHeading+unitVarRad)) +SpawnTemplate.units[UnitID].psi=-SpawnTemplate.units[UnitID].heading +end +end +if self.SpawnInitHeadingMin then +for UnitID=1,#SpawnTemplate.units do +SpawnTemplate.units[UnitID].heading=_Heading(_RandomInRange(self.SpawnInitHeadingMin,self.SpawnInitHeadingMax)) +SpawnTemplate.units[UnitID].psi=-SpawnTemplate.units[UnitID].heading +end +end +if self.SpawnUnitsWithRelativePositions and self.UnitsRelativePositions then +local BaseX=SpawnTemplate.units[1].x or 0 +local BaseY=SpawnTemplate.units[1].y or 0 +local BaseZ=SpawnTemplate.units[1].z or 0 +for UnitID=1,#SpawnTemplate.units do +if self.UnitsRelativePositions[UnitID].heading then +SpawnTemplate.units[UnitID].heading=math.rad(self.UnitsRelativePositions[UnitID].heading or 0) +end +SpawnTemplate.units[UnitID].x=BaseX+(self.UnitsRelativePositions[UnitID].x or 0) +SpawnTemplate.units[UnitID].y=BaseY+(self.UnitsRelativePositions[UnitID].y or 0) +if self.UnitsRelativePositions[UnitID].z then +SpawnTemplate.units[UnitID].z=BaseZ+(self.UnitsRelativePositions[UnitID].z or 0) +end +end +end +if self.SpawnUnitsWithAbsolutePositions and self.UnitsAbsolutePositions then +for UnitID=1,#SpawnTemplate.units do +if self.UnitsAbsolutePositions[UnitID].heading then +SpawnTemplate.units[UnitID].heading=math.rad(self.UnitsAbsolutePositions[UnitID].heading or 0) +end +SpawnTemplate.units[UnitID].x=self.UnitsAbsolutePositions[UnitID].x or 0 +SpawnTemplate.units[UnitID].y=self.UnitsAbsolutePositions[UnitID].y or 0 +if self.UnitsAbsolutePositions[UnitID].z then +SpawnTemplate.units[UnitID].z=self.UnitsAbsolutePositions[UnitID].z or 0 +end +end +end +if self.SpawnInitLivery then +for UnitID=1,#SpawnTemplate.units do +SpawnTemplate.units[UnitID].livery_id=self.SpawnInitLivery +end +end +if self.SpawnInitSkill then +for UnitID=1,#SpawnTemplate.units do +SpawnTemplate.units[UnitID].skill=self.SpawnInitSkill +end +end +if self.SpawnInitModex then +for UnitID=1,#SpawnTemplate.units do +local modexnumber=string.format("%03d",self.SpawnInitModex+(UnitID-1)) +if self.SpawnInitModexPrefix then modexnumber=self.SpawnInitModexPrefix..modexnumber end +if self.SpawnInitModexPostfix then modexnumber=modexnumber..self.SpawnInitModexPostfix end +SpawnTemplate.units[UnitID].onboard_num=modexnumber +end +end +if self.SpawnInitRadio then +SpawnTemplate.communication=self.SpawnInitRadio +end +if self.SpawnInitFreq then +SpawnTemplate.frequency=self.SpawnInitFreq +end +if self.SpawnInitModu then +SpawnTemplate.modulation=self.SpawnInitModu +end +if self.SpawnHiddenOnPlanner then +SpawnTemplate.hiddenOnPlanner=true +end +if self.SpawnHiddenOnMFD then +SpawnTemplate.hiddenOnMFD=true +end +if self.SpawnHiddenOnMap then +SpawnTemplate.hidden=self.SpawnHiddenOnMap +end +if self.SpawnValidateAndRepositionGroundUnits then +local units=SpawnTemplate.units +local gPos={x=SpawnTemplate.x,y=SpawnTemplate.y} +UTILS.ValidateAndRepositionGroundUnits(units,gPos,self.SpawnValidateAndRepositionGroundUnitsRadius,self.SpawnValidateAndRepositionGroundUnitsSpacing) +end +SpawnTemplate.CategoryID=self.SpawnInitCategory or SpawnTemplate.CategoryID +SpawnTemplate.CountryID=self.SpawnInitCountry or SpawnTemplate.CountryID +SpawnTemplate.CoalitionID=self.SpawnInitCoalition or SpawnTemplate.CoalitionID +end +if not NoBirth then +self:HandleEvent(EVENTS.Birth,self._OnBirth) +end +self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.UnitLost,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash) +if self.Repeat then +self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff) +self:HandleEvent(EVENTS.Land,self._OnLand) +end +if self.RepeatOnEngineShutDown then +self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown) +end +self.SpawnGroups[self.SpawnIndex].Group=_DATABASE:Spawn(SpawnTemplate) +local SpawnGroup=self.SpawnGroups[self.SpawnIndex].Group +if SpawnGroup then +SpawnGroup:SetAIOnOff(self.AIOnOff) +end +self:T3(SpawnTemplate.name) +if self.SpawnFunctionHook then +self.SpawnHookScheduler:Schedule(nil,self.SpawnFunctionHook,{self.SpawnGroups[self.SpawnIndex].Group,unpack(self.SpawnFunctionArguments)},0.3) +end +end +self.SpawnGroups[self.SpawnIndex].Spawned=true +self.SpawnGroups[self.SpawnIndex].Group.TemplateDonor=self.SpawnTemplatePrefix +return self.SpawnGroups[self.SpawnIndex].Group +else +end +return nil +end +function SPAWN:SpawnScheduled(SpawnTime,SpawnTimeVariation,WithDelay) +local SpawnTime=SpawnTime or 60 +local SpawnTimeVariation=SpawnTimeVariation or 0.5 +if SpawnTime<15 then +self:E("****SPAWN SCHEDULED****\nWARNING - Setting a very low SpawnTime heavily impacts your mission performance and CPU time, it is NOT useful to check the alive state of an object every "..tostring(SpawnTime).." seconds.\nSetting to 15 second intervals.\n*****") +SpawnTime=15 +end +if SpawnTimeVariation>1 or SpawnTimeVariation<0 then SpawnTimeVariation=0.5 end +if SpawnTime~=nil and SpawnTimeVariation~=nil then +local InitialDelay=0 +if WithDelay or self.DelayOnOff==true then +InitialDelay=math.random(SpawnTime-SpawnTime*SpawnTimeVariation,SpawnTime+SpawnTime*SpawnTimeVariation) +end +self.SpawnScheduler=SCHEDULER:New(self,self._Scheduler,{},InitialDelay,SpawnTime,SpawnTimeVariation) +end +return self +end +function SPAWN:SpawnScheduleStart() +self.SpawnScheduler:Start() +return self +end +function SPAWN:SpawnScheduleStop() +self.SpawnScheduler:Stop() +return self +end +function SPAWN:OnSpawnGroup(SpawnCallBackFunction,...) +self.SpawnFunctionHook=SpawnCallBackFunction +self.SpawnFunctionArguments={} +if arg then +self.SpawnFunctionArguments=arg +end +return self +end +function SPAWN:SpawnAtAirbase(SpawnAirbase,Takeoff,TakeoffAltitude,TerminalType,EmergencyAirSpawn,Parkingdata) +local PointVec3=SpawnAirbase:GetCoordinate() +Takeoff=Takeoff or SPAWN.Takeoff.Hot +if EmergencyAirSpawn==nil then +EmergencyAirSpawn=true +end +if self:_GetSpawnIndex(self.SpawnIndex+1)then +local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate +if SpawnTemplate then +local group=GROUP:FindByName(self.SpawnTemplatePrefix) +local unit=group:GetUnit(1) +local istransport=group:HasAttribute("Transports")and group:HasAttribute("Planes") +local isawacs=group:HasAttribute("AWACS") +local isfighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers")) +local isbomber=group:HasAttribute("Strategic bombers") +local istanker=group:HasAttribute("Tankers") +local ishelo=unit:HasAttribute("Helicopters") +local nunits=#SpawnTemplate.units +local SpawnPoint=SpawnTemplate.route.points[1] +SpawnPoint.linkUnit=nil +SpawnPoint.helipadId=nil +SpawnPoint.airdromeId=nil +local AirbaseID=SpawnAirbase:GetID() +local AirbaseCategory=SpawnAirbase:GetAirbaseCategory() +if AirbaseCategory==Airbase.Category.SHIP then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.HELIPAD then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +else +SpawnPoint.airdromeId=AirbaseID +end +SpawnPoint.alt=0 +SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1] +SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2] +local spawnonground=not(Takeoff==SPAWN.Takeoff.Air) +local autoparking=false +if SpawnAirbase.isAirdrome then +autoparking=false +else +autoparking=true +end +local parkingspots={} +local parkingindex={} +local spots +if spawnonground and not SpawnTemplate.parked then +local nfree=0 +local termtype=TerminalType +if Takeoff==SPAWN.Takeoff.Runway then +if SpawnAirbase.isShip then +if ishelo then +termtype=AIRBASE.TerminalType.HelicopterUsable +else +termtype=AIRBASE.TerminalType.OpenMedOrBig +end +else +termtype=AIRBASE.TerminalType.Runway +end +end +local scanradius=50 +local scanunits=true +local scanstatics=true +local scanscenery=false +local verysafe=false +if autoparking then +nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true) +spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true) +elseif Parkingdata~=nil then +nfree=#Parkingdata +spots=Parkingdata +else +if ishelo then +if termtype==nil then +spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata) +nfree=#spots +if nfree=1 then +for i=1,nunits do +table.insert(parkingspots,spots[1].Coordinate) +table.insert(parkingindex,spots[1].TerminalID) +end +PointVec3=spots[1].Coordinate +else +_notenough=true +end +else +if nfree>=nunits then +for i=1,nunits do +table.insert(parkingspots,spots[i].Coordinate) +table.insert(parkingindex,spots[i].TerminalID) +end +else +_notenough=true +end +end +if _notenough then +if EmergencyAirSpawn and not self.SpawnUnControlled then +self:E(string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +autoparking=false +SpawnPoint.type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] +SpawnPoint.action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] +PointVec3.x=PointVec3.x+math.random(-500,500) +PointVec3.z=PointVec3.z+math.random(-500,500) +if ishelo then +PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) +else +PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) +end +Takeoff=GROUP.Takeoff.Air +else +self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +return nil +end +end +else +if TakeoffAltitude then +PointVec3.y=TakeoffAltitude +else +if ishelo then +PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) +else +PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) +end +end +end +if not SpawnTemplate.parked then +SpawnTemplate.parked=true +for UnitID=1,nunits do +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x +local SY=UnitTemplate.y +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=PointVec3.x+(SX-BX) +local TY=PointVec3.z+(SY-BY) +if spawnonground then +if autoparking then +SpawnTemplate.units[UnitID].x=PointVec3.x +SpawnTemplate.units[UnitID].y=PointVec3.z +SpawnTemplate.units[UnitID].alt=PointVec3.y +else +SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x +SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z +SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y +end +else +SpawnTemplate.units[UnitID].x=TX +SpawnTemplate.units[UnitID].y=TY +SpawnTemplate.units[UnitID].alt=PointVec3.y +end +UnitTemplate.parking=nil +UnitTemplate.parking_id=nil +if parkingindex[UnitID]then +UnitTemplate.parking=parkingindex[UnitID] +end +end +end +SpawnPoint.x=PointVec3.x +SpawnPoint.y=PointVec3.z +SpawnPoint.alt=PointVec3.y +SpawnTemplate.x=PointVec3.x +SpawnTemplate.y=PointVec3.z +SpawnTemplate.uncontrolled=self.SpawnUnControlled +local GroupSpawned=self:SpawnWithIndex(self.SpawnIndex) +if Takeoff==GROUP.Takeoff.Air then +for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do +self:ScheduleOnce(5,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()}) +end +end +return GroupSpawned +end +end +return nil +end +function SPAWN:SpawnAtParkingSpot(Airbase,Spots,Takeoff) +if type(Spots)~="table"then +Spots={Spots} +end +if type(Airbase)=="string"then +Airbase=AIRBASE:FindByName(Airbase) +end +local group=GROUP:FindByName(self.SpawnTemplatePrefix) +local nunits=self.SpawnGrouping or#group:GetUnits() +if nunits then +if#Spots=nunits then +return self:SpawnAtAirbase(Airbase,Takeoff,nil,nil,nil,Parkingdata) +else +self:E("ERROR: Could not find enough free parking spots!") +end +else +self:E("ERROR: Could not get number of units in group!") +end +return nil +end +function SPAWN:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex) +local PointVec3=SpawnAirbase:GetCoordinate() +local Takeoff=SPAWN.Takeoff.Cold +local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate +if SpawnTemplate then +local GroupAlive=self:GetGroupFromIndex(SpawnIndex) +local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix) +local TemplateUnit=TemplateGroup:GetUnit(1) +local ishelo=TemplateUnit:HasAttribute("Helicopters") +local isbomber=TemplateUnit:HasAttribute("Bombers") +local istransport=TemplateUnit:HasAttribute("Transports") +local isfighter=TemplateUnit:HasAttribute("Battleplanes") +local nunits=#SpawnTemplate.units +local SpawnPoint=SpawnTemplate.route.points[1] +SpawnPoint.linkUnit=nil +SpawnPoint.helipadId=nil +SpawnPoint.airdromeId=nil +local AirbaseID=SpawnAirbase:GetID() +local AirbaseCategory=SpawnAirbase:GetAirbaseCategory() +if AirbaseCategory==Airbase.Category.SHIP then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.HELIPAD then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.AIRDROME then +SpawnPoint.airdromeId=AirbaseID +end +SpawnPoint.alt=0 +SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1] +SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2] +local spawnonground=not(Takeoff==SPAWN.Takeoff.Air) +local spawnonship=false +local spawnonfarp=false +local spawnonrunway=false +local spawnonairport=false +if spawnonground then +if AirbaseCategory==Airbase.Category.SHIP then +spawnonship=true +elseif AirbaseCategory==Airbase.Category.HELIPAD then +spawnonfarp=true +elseif AirbaseCategory==Airbase.Category.AIRDROME then +spawnonairport=true +end +spawnonrunway=Takeoff==SPAWN.Takeoff.Runway +end +local parkingspots={} +local parkingindex={} +local spots +if spawnonground and not SpawnTemplate.parked then +local nfree=0 +local termtype=TerminalType +local scanradius=50 +local scanunits=true +local scanstatics=true +local scanscenery=false +local verysafe=false +if spawnonship or spawnonfarp or spawnonrunway then +nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true) +spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true) +else +if ishelo then +if termtype==nil then +spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata) +nfree=#spots +if nfree=1 then +for i=1,nunits do +table.insert(parkingspots,spots[1].Coordinate) +table.insert(parkingindex,spots[1].TerminalID) +end +PointVec3=spots[1].Coordinate +else +_notenough=true +end +elseif spawnonairport then +if nfree>=nunits then +for i=1,nunits do +table.insert(parkingspots,spots[i].Coordinate) +table.insert(parkingindex,spots[i].TerminalID) +end +else +_notenough=true +end +end +if _notenough then +if not self.SpawnUnControlled then +else +self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +return nil +end +end +else +end +if not SpawnTemplate.parked then +SpawnTemplate.parked=true +for UnitID=1,nunits do +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x +local SY=UnitTemplate.y +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=PointVec3.x+(SX-BX) +local TY=PointVec3.z+(SY-BY) +if spawnonground then +if spawnonship or spawnonfarp or spawnonrunway then +SpawnTemplate.units[UnitID].x=PointVec3.x +SpawnTemplate.units[UnitID].y=PointVec3.z +SpawnTemplate.units[UnitID].alt=PointVec3.y +else +SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x +SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z +SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y +end +else +SpawnTemplate.units[UnitID].x=TX +SpawnTemplate.units[UnitID].y=TY +SpawnTemplate.units[UnitID].alt=PointVec3.y +end +UnitTemplate.parking=nil +UnitTemplate.parking_id=nil +if parkingindex[UnitID]then +UnitTemplate.parking=parkingindex[UnitID] +end +end +end +SpawnPoint.x=PointVec3.x +SpawnPoint.y=PointVec3.z +SpawnPoint.alt=PointVec3.y +SpawnTemplate.x=PointVec3.x +SpawnTemplate.y=PointVec3.z +SpawnTemplate.uncontrolled=true +local GroupSpawned=self:SpawnWithIndex(SpawnIndex,true) +if Takeoff==GROUP.Takeoff.Air then +for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do +SCHEDULER:New(nil,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()},5) +end +end +if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then +SCHEDULER:New(nil,AIRBASE.CheckOnRunWay,{SpawnAirbase,GroupSpawned,75,true},1.0) +end +end +end +function SPAWN:ParkAtAirbase(SpawnAirbase,TerminalType,Parkingdata) +self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,1) +for SpawnIndex=2,self.SpawnMaxGroups do +self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex) +end +self:SetSpawnIndex(0) +return nil +end +function SPAWN:SpawnFromVec3(Vec3,SpawnIndex) +local PointVec3=COORDINATE:NewFromVec3(Vec3) +if SpawnIndex then +else +SpawnIndex=self.SpawnIndex+1 +end +if self:_GetSpawnIndex(SpawnIndex)then +local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate +if SpawnTemplate then +local TemplateHeight=SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil +SpawnTemplate.route=SpawnTemplate.route or{} +SpawnTemplate.route.points=SpawnTemplate.route.points or{} +SpawnTemplate.route.points[1]=SpawnTemplate.route.points[1]or{} +SpawnTemplate.route.points[1].x=SpawnTemplate.route.points[1].x or 0 +SpawnTemplate.route.points[1].y=SpawnTemplate.route.points[1].y or 0 +for UnitID=1,#SpawnTemplate.units do +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x or 0 +local SY=UnitTemplate.y or 0 +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=Vec3.x+(SX-BX) +local TY=Vec3.z+(SY-BY) +SpawnTemplate.units[UnitID].x=TX +SpawnTemplate.units[UnitID].y=TY +if SpawnTemplate.CategoryID~=Group.Category.SHIP then +SpawnTemplate.units[UnitID].alt=Vec3.y or TemplateHeight +end +end +SpawnTemplate.route.points[1].x=Vec3.x +SpawnTemplate.route.points[1].y=Vec3.z +if SpawnTemplate.CategoryID~=Group.Category.SHIP then +SpawnTemplate.route.points[1].alt=Vec3.y or TemplateHeight +end +SpawnTemplate.x=Vec3.x +SpawnTemplate.y=Vec3.z +SpawnTemplate.alt=Vec3.y or TemplateHeight +return self:SpawnWithIndex(self.SpawnIndex) +end +end +return nil +end +function SPAWN:SpawnFromCoordinate(Coordinate,SpawnIndex) +return self:SpawnFromVec3(Coordinate:GetVec3(),SpawnIndex) +end +function SPAWN:SpawnFromPointVec3(PointVec3,SpawnIndex) +return self:SpawnFromVec3(PointVec3:GetVec3(),SpawnIndex) +end +function SPAWN:SpawnFromVec2(Vec2,MinHeight,MaxHeight,SpawnIndex) +local Height=nil +if MinHeight and MaxHeight then +Height=math.random(MinHeight,MaxHeight) +end +return self:SpawnFromVec3({x=Vec2.x,y=Height,z=Vec2.y},SpawnIndex) +end +function SPAWN:SpawnFromPointVec2(PointVec2,MinHeight,MaxHeight,SpawnIndex) +return self:SpawnFromVec2(PointVec2:GetVec2(),MinHeight,MaxHeight,SpawnIndex) +end +function SPAWN:SpawnFromUnit(HostUnit,MinHeight,MaxHeight,SpawnIndex) +if HostUnit and HostUnit:IsAlive()~=nil then +return self:SpawnFromVec2(HostUnit:GetVec2(),MinHeight,MaxHeight,SpawnIndex) +end +return nil +end +function SPAWN:SpawnFromStatic(HostStatic,MinHeight,MaxHeight,SpawnIndex) +if HostStatic and HostStatic:IsAlive()then +return self:SpawnFromVec2(HostStatic:GetVec2(),MinHeight,MaxHeight,SpawnIndex) +end +return nil +end +function SPAWN:SpawnInZone(Zone,RandomizeGroup,MinHeight,MaxHeight,SpawnIndex) +if Zone then +if RandomizeGroup then +return self:SpawnFromVec2(Zone:GetRandomVec2(),MinHeight,MaxHeight,SpawnIndex) +else +return self:SpawnFromVec2(Zone:GetVec2(),MinHeight,MaxHeight,SpawnIndex) +end +end +return nil +end +function SPAWN:InitUnControlled(UnControlled) +self:F2({self.SpawnTemplatePrefix,UnControlled}) +self.SpawnUnControlled=(UnControlled==true)and true or nil +for SpawnGroupID=1,self.SpawnMaxGroups do +self.SpawnGroups[SpawnGroupID].UnControlled=self.SpawnUnControlled +end +return self +end +function SPAWN:GetCoordinate() +local LateGroup=GROUP:FindByName(self.SpawnTemplatePrefix) +if LateGroup then +return LateGroup:GetCoordinate() +end +return nil +end +function SPAWN:SpawnGroupName(SpawnIndex) +local SpawnPrefix=self.SpawnTemplatePrefix +if self.SpawnAliasPrefix then +SpawnPrefix=self.SpawnAliasPrefix +end +if SpawnIndex then +local SpawnName=string.format('%s#%03d',SpawnPrefix,SpawnIndex) +return SpawnName +else +return SpawnPrefix +end +end +function SPAWN:GetFirstAliveGroup() +for SpawnIndex=1,self.SpawnCount do +local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) +if SpawnGroup and SpawnGroup:IsAlive()then +return SpawnGroup,SpawnIndex +end +end +return nil,nil +end +function SPAWN:GetNextAliveGroup(SpawnIndexStart) +SpawnIndexStart=SpawnIndexStart+1 +for SpawnIndex=SpawnIndexStart,self.SpawnCount do +local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) +if SpawnGroup and SpawnGroup:IsAlive()then +return SpawnGroup,SpawnIndex +end +end +return nil,nil +end +function SPAWN:GetLastAliveGroup() +for SpawnIndex=self.SpawnCount,1,-1 do +local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) +if SpawnGroup and SpawnGroup:IsAlive()then +self.SpawnIndex=SpawnIndex +return SpawnGroup +end +end +self.SpawnIndex=nil +return nil +end +function SPAWN:GetGroupFromIndex(SpawnIndex) +if not SpawnIndex then +SpawnIndex=1 +end +if self.SpawnGroups and self.SpawnGroups[SpawnIndex]then +local SpawnGroup=self.SpawnGroups[SpawnIndex].Group +return SpawnGroup +else +return nil +end +end +function SPAWN:_GetPrefixFromGroup(SpawnGroup) +local GroupName=SpawnGroup:GetName() +if GroupName then +local SpawnPrefix=self:_GetPrefixFromGroupName(GroupName) +return SpawnPrefix +end +return nil +end +function SPAWN:_GetPrefixFromGroupName(SpawnGroupName) +if SpawnGroupName then +local SpawnPrefix=string.match(SpawnGroupName,".*#") +if SpawnPrefix then +SpawnPrefix=SpawnPrefix:sub(1,-2) +end +return SpawnPrefix +end +return nil +end +function SPAWN:GetSpawnIndexFromGroup(SpawnGroup) +local IndexString=string.match(SpawnGroup:GetName(),"#(%d*)$"):sub(2) +local Index=tonumber(IndexString) +self:T3(IndexString,Index) +return Index +end +function SPAWN:_GetLastIndex() +return self.SpawnMaxGroups +end +function SPAWN:_InitializeSpawnGroups(SpawnIndex) +if not self.SpawnGroups[SpawnIndex]then +self.SpawnGroups[SpawnIndex]={} +self.SpawnGroups[SpawnIndex].Visible=false +self.SpawnGroups[SpawnIndex].Spawned=false +self.SpawnGroups[SpawnIndex].UnControlled=false +self.SpawnGroups[SpawnIndex].SpawnTime=0 +self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefix +self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex) +end +self:_RandomizeTemplate(SpawnIndex) +self:_RandomizeRoute(SpawnIndex) +return self.SpawnGroups[SpawnIndex] +end +function SPAWN:_GetGroupCategoryID(SpawnPrefix) +local TemplateGroup=Group.getByName(SpawnPrefix) +if TemplateGroup then +return TemplateGroup:getCategory() +else +return nil +end +end +function SPAWN:_GetGroupCoalitionID(SpawnPrefix) +local TemplateGroup=Group.getByName(SpawnPrefix) +if TemplateGroup then +return TemplateGroup:getCoalition() +else +return nil +end +end +function SPAWN:_GetGroupCountryID(SpawnPrefix) +local TemplateGroup=Group.getByName(SpawnPrefix) +if TemplateGroup then +local TemplateUnits=TemplateGroup:getUnits() +return TemplateUnits[1]:getCountry() +else +return nil +end +end +function SPAWN:_GetTemplate(SpawnTemplatePrefix) +local SpawnTemplate=nil +if _DATABASE.Templates.Groups[SpawnTemplatePrefix]==nil then +error('No Template exists for SpawnTemplatePrefix = '..SpawnTemplatePrefix) +end +local Template=_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template +SpawnTemplate=UTILS.DeepCopy(_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template) +if SpawnTemplate==nil then +error('No Template returned for SpawnTemplatePrefix = '..SpawnTemplatePrefix) +end +self:T3({SpawnTemplate}) +return SpawnTemplate +end +function SPAWN:_Prepare(SpawnTemplatePrefix,SpawnIndex) +local SpawnTemplate +if self.TweakedTemplate~=nil and self.TweakedTemplate==true then +BASE:I("WARNING: You are using a tweaked template.") +SpawnTemplate=self.SpawnTemplate +if self.MooseNameing==true then +SpawnTemplate.name=self:SpawnGroupName(SpawnIndex) +else +SpawnTemplate.name=self:SpawnGroupName() +end +else +SpawnTemplate=self:_GetTemplate(SpawnTemplatePrefix) +SpawnTemplate.name=self:SpawnGroupName(SpawnIndex) +end +SpawnTemplate.groupId=nil +SpawnTemplate.lateActivation=self.LateActivated or false +if SpawnTemplate.CategoryID==Group.Category.GROUND then +self:T3("For ground units, visible needs to be false...") +SpawnTemplate.visible=false +end +if self.SpawnGrouping then +local UnitAmount=#SpawnTemplate.units +if UnitAmount>self.SpawnGrouping then +for UnitID=self.SpawnGrouping+1,UnitAmount do +SpawnTemplate.units[UnitID]=nil +end +else +if UnitAmount1 then +octal=_DATABASE:GetNextSTN(self.SpawnInitSTN,SpawnTemplate.units[UnitID].name) +end +SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",octal) +else +if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16)~=nil then +local octal=SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 +local num=UTILS.OctalToDecimal(octal) +if _DATABASE.STNS[num]~=nil or UnitID>1 then +octal=_DATABASE:GetNextSTN(octal,SpawnTemplate.units[UnitID].name) +end +SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",octal) +else +local OSTN=_DATABASE:GetNextSTN(1,SpawnTemplate.units[UnitID].name) +SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",OSTN) +end +end +end +if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then +if self.SpawnInitSADL then +local octal=self.SpawnInitSADL +if UnitID>1 then +octal=_DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name) +end +SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",octal) +else +if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN)~=nil then +local octal=SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN +local num=UTILS.OctalToDecimal(octal) +self.SpawnInitSADL=num +if _DATABASE.SADL[num]~=nil or UnitID>1 then +octal=_DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name) +end +SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",octal) +else +local OSTN=_DATABASE:GetNextSADL(1,SpawnTemplate.units[UnitID].name) +SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",OSTN) +end +end +end +if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber and type(Callsign)~="number"then +SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber=SpawnTemplate.units[UnitID].callsign[2]..SpawnTemplate.units[UnitID].callsign[3] +end +if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel and type(Callsign)~="number"then +local CallsignName=SpawnTemplate.units[UnitID].callsign["name"] +CallsignName=string.match(CallsignName,"^(%a+)") +local label="NY" +if not string.find(CallsignName," ")then +label=string.upper(string.match(CallsignName,"^%a")..string.match(CallsignName,"%a$")) +end +SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel=label +end +if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.settings then +SpawnTemplate.units[UnitID].datalinks.Link16.settings.flightLead=UnitID==1 and true or false +end +if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.SADL and SpawnTemplate.units[UnitID].datalinks.SADL.settings then +SpawnTemplate.units[UnitID].datalinks.SADL.settings.flightLead=UnitID==1 and true or false +end +end +end +for UnitID=1,#SpawnTemplate.units do +if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.network then +local team={} +local isF16=string.find(SpawnTemplate.units[UnitID].type,"F-16",1,true)and true or false +for ID=1,#SpawnTemplate.units do +local member={} +member.missionUnitId=ID +if isF16 then +member.TDOA=true +end +table.insert(team,member) +end +SpawnTemplate.units[UnitID].datalinks.Link16.network.teamMembers=team +end +end +self:T3({"Template:",SpawnTemplate}) +return SpawnTemplate +end +function SPAWN:_RandomizeRoute(SpawnIndex) +if self.SpawnRandomizeRoute then +local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate +local RouteCount=#SpawnTemplate.route.points +for t=self.SpawnRandomizeRouteStartPoint+1,(RouteCount-self.SpawnRandomizeRouteEndPoint)do +SpawnTemplate.route.points[t].x=SpawnTemplate.route.points[t].x+math.random(self.SpawnRandomizeRouteRadius*-1,self.SpawnRandomizeRouteRadius) +SpawnTemplate.route.points[t].y=SpawnTemplate.route.points[t].y+math.random(self.SpawnRandomizeRouteRadius*-1,self.SpawnRandomizeRouteRadius) +if SpawnTemplate.CategoryID==Group.Category.AIRPLANE or SpawnTemplate.CategoryID==Group.Category.HELICOPTER then +if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then +SpawnTemplate.route.points[t].alt=SpawnTemplate.route.points[t].alt+math.random(1,self.SpawnRandomizeRouteHeight) +end +else +SpawnTemplate.route.points[t].alt=nil +end +end +end +self:_RandomizeZones(SpawnIndex) +return self +end +function SPAWN:_RandomizeTemplate(SpawnIndex) +if self.SpawnRandomizeTemplate then +self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefixTable[math.random(1,#self.SpawnTemplatePrefixTable)] +self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex) +self.SpawnGroups[SpawnIndex].SpawnTemplate.route=UTILS.DeepCopy(self.SpawnTemplate.route) +self.SpawnGroups[SpawnIndex].SpawnTemplate.x=self.SpawnTemplate.x +self.SpawnGroups[SpawnIndex].SpawnTemplate.y=self.SpawnTemplate.y +self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time=self.SpawnTemplate.start_time +local OldX=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x +local OldY=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y +for UnitID=1,#self.SpawnGroups[SpawnIndex].SpawnTemplate.units do +self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading=self.SpawnTemplate.units[1].heading +self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x=self.SpawnTemplate.units[1].x+(self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x-OldX) +self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y=self.SpawnTemplate.units[1].y+(self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y-OldY) +self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt=self.SpawnTemplate.units[1].alt +end +end +self:_RandomizeRoute(SpawnIndex) +return self +end +function SPAWN:_SetInitialPosition(SpawnIndex) +if self.SpawnFromNewPosition then +local SpawnVec2=self.SpawnInitPosition +local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate +SpawnTemplate.route=SpawnTemplate.route or{} +SpawnTemplate.route.points=SpawnTemplate.route.points or{} +SpawnTemplate.route.points[1]=SpawnTemplate.route.points[1]or{} +SpawnTemplate.route.points[1].x=SpawnTemplate.route.points[1].x or 0 +SpawnTemplate.route.points[1].y=SpawnTemplate.route.points[1].y or 0 +for UnitID=1,#SpawnTemplate.units do +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x +local SY=UnitTemplate.y +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=SpawnVec2.x+(SX-BX) +local TY=SpawnVec2.y+(SY-BY) +UnitTemplate.x=TX +UnitTemplate.y=TY +end +SpawnTemplate.route.points[1].x=SpawnVec2.x +SpawnTemplate.route.points[1].y=SpawnVec2.y +SpawnTemplate.x=SpawnVec2.x +SpawnTemplate.y=SpawnVec2.y +end +return self +end +function SPAWN:_RandomizeZones(SpawnIndex,RandomizePositionInZone) +if self.SpawnRandomizeZones then +local SpawnZone=nil +while not SpawnZone do +local ZoneID=math.random(#self.SpawnZoneTable) +SpawnZone=self.SpawnZoneTable[ZoneID]:GetZoneMaybe() +end +local SpawnVec2=SpawnZone:GetVec2() +if RandomizePositionInZone~=false then +SpawnVec2=SpawnZone:GetRandomVec2() +end +local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate +self.SpawnGroups[SpawnIndex].SpawnZone=SpawnZone +for UnitID=1,#SpawnTemplate.units do +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x +local SY=UnitTemplate.y +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=SpawnVec2.x+(SX-BX) +local TY=SpawnVec2.y+(SY-BY) +UnitTemplate.x=TX +UnitTemplate.y=TY +end +SpawnTemplate.x=SpawnVec2.x +SpawnTemplate.y=SpawnVec2.y +SpawnTemplate.route.points[1].x=SpawnVec2.x +SpawnTemplate.route.points[1].y=SpawnVec2.y +end +return self +end +function SPAWN:_TranslateRotate(SpawnIndex,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle) +local TranslatedX=SpawnX +local TranslatedY=SpawnY +local RotatedX=-TranslatedX*math.cos(math.rad(SpawnAngle))+TranslatedY*math.sin(math.rad(SpawnAngle)) +local RotatedY=TranslatedX*math.sin(math.rad(SpawnAngle))+TranslatedY*math.cos(math.rad(SpawnAngle)) +self.SpawnGroups[SpawnIndex].SpawnTemplate.x=SpawnRootX-RotatedX +self.SpawnGroups[SpawnIndex].SpawnTemplate.y=SpawnRootY+RotatedY +local SpawnUnitCount=table.getn(self.SpawnGroups[SpawnIndex].SpawnTemplate.units) +for u=1,SpawnUnitCount do +local TranslatedX=SpawnX +local TranslatedY=SpawnY-10*(u-1) +local RotatedX=-TranslatedX*math.cos(math.rad(SpawnAngle))+TranslatedY*math.sin(math.rad(SpawnAngle)) +local RotatedY=TranslatedX*math.sin(math.rad(SpawnAngle))+TranslatedY*math.cos(math.rad(SpawnAngle)) +self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x=SpawnRootX-RotatedX +self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y=SpawnRootY+RotatedY +self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading+math.rad(SpawnAngle) +end +return self +end +function SPAWN:_GetSpawnIndex(SpawnIndex) +if(self.SpawnMaxGroups==0)or(SpawnIndex<=self.SpawnMaxGroups)then +if(self.SpawnMaxUnitsAlive==0)or(self.AliveUnits+#self.SpawnTemplate.units<=self.SpawnMaxUnitsAlive)or self.UnControlled==true then +if SpawnIndex and SpawnIndex>=self.SpawnCount+1 then +self.SpawnCount=self.SpawnCount+1 +SpawnIndex=self.SpawnCount +end +self.SpawnIndex=SpawnIndex +if not self.SpawnGroups[self.SpawnIndex]then +self:_InitializeSpawnGroups(self.SpawnIndex) +end +else +return nil +end +else +return nil +end +return self.SpawnIndex +end +function SPAWN:_OnBirth(EventData) +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix then +if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then +self.AliveUnits=self.AliveUnits+1 +end +end +end +end +function SPAWN:_CountAliveUnits() +local count=0 +if self.SpawnAliasPrefix then +if not self.SpawnAliasPrefixEscaped then self.SpawnAliasPrefixEscaped=string.gsub(self.SpawnAliasPrefix,"[%p%s]",".")end +local SpawnAliasPrefix=self.SpawnAliasPrefixEscaped +local agroups=GROUP:FindAllByMatching(SpawnAliasPrefix) +for _,_grp in pairs(agroups)do +local game=self:_GetPrefixFromGroupName(_grp.GroupName) +if game and game==self.SpawnAliasPrefix then +count=count+_grp:CountAliveUnits() +end +end +else +if not self.SpawnTemplatePrefixEscaped then self.SpawnTemplatePrefixEscaped=string.gsub(self.SpawnTemplatePrefix,"[%p%s]",".")end +local SpawnTemplatePrefix=self.SpawnTemplatePrefixEscaped +local groups=GROUP:FindAllByMatching(SpawnTemplatePrefix) +for _,_grp in pairs(groups)do +local game=self:_GetPrefixFromGroupName(_grp.GroupName) +if game and game==self.SpawnTemplatePrefix then +count=count+_grp:CountAliveUnits() +end +end +end +self.AliveUnits=count +return self +end +function SPAWN:_OnDeadOrCrash(EventData) +local unit=UNIT:FindByName(EventData.IniUnitName) +if unit then +local EventPrefix=self:_GetPrefixFromGroupName(unit.GroupName) +if EventPrefix then +if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)and self.AliveUnits>0 then +self:ScheduleOnce(1,self._CountAliveUnits,self) +end +end +end +end +function SPAWN:_OnTakeOff(EventData) +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix then +if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then +SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",false) +end +end +end +end +function SPAWN:_OnLand(EventData) +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix then +if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then +SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",true) +if self.RepeatOnLanding then +local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup) +SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},self.RepeatOnLandingTime or 3) +end +end +end +end +end +function SPAWN:_OnEngineShutDown(EventData) +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix then +if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then +local Landed=SpawnGroup:GetState(SpawnGroup,"Spawn_Landed") +if Landed and self.RepeatOnEngineShutDown then +local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup) +SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},3) +end +end +end +end +end +function SPAWN:_Scheduler() +self:F2({"_Scheduler",self.SpawnTemplatePrefix,self.SpawnAliasPrefix,self.SpawnIndex,self.SpawnMaxGroups,self.SpawnMaxUnitsAlive}) +self:Spawn() +return true +end +function SPAWN:_SpawnCleanUpScheduler() +local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup() +local IsHelo=false +while SpawnGroup do +IsHelo=SpawnGroup:IsHelicopter() +local SpawnUnits=SpawnGroup:GetUnits() +for UnitID,UnitData in pairs(SpawnUnits)do +local SpawnUnit=UnitData +local SpawnUnitName=SpawnUnit:GetName() +self.SpawnCleanUpTimeStamps[SpawnUnitName]=self.SpawnCleanUpTimeStamps[SpawnUnitName]or{} +local Stamp=self.SpawnCleanUpTimeStamps[SpawnUnitName] +if Stamp.Vec2 then +if(SpawnUnit:InAir()==false and SpawnUnit:GetVelocityKMH()<1)or IsHelo then +local NewVec2=SpawnUnit:GetVec2()or{x=0,y=0} +if(Stamp.Vec2.x==NewVec2.x and Stamp.Vec2.y==NewVec2.y)or(SpawnUnit:GetLife()<=1)then +if Stamp.Time+self.SpawnCleanUpInterval0 then +self.Tstop=timer.getTime()+Delay +else +if self.tid then +self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!",self.ncalls)) +local status=pcall( +function() +timer.removeFunction(self.tid) +end +) +if status then +self:T2(self.lid..string.format("Stopped timer!")) +else +self:E(self.lid..string.format("WARNING: Could not remove timer function! isrunning=%s",tostring(self.isrunning))) +end +self.isrunning=false +end +end +return self +end +function TIMER:SetMaxFunctionCalls(Nmax) +self.ncallsMax=Nmax +return self +end +function TIMER:SetTimeInterval(dT) +self.dT=dT +return self +end +function TIMER:IsRunning() +return self.isrunning +end +function TIMER:_Function(time) +self.func(unpack(self.para)) +self.ncalls=self.ncalls+1 +local Tnext=self.dT and time+self.dT or nil +local stopme=false +if Tnext==nil then +self:T(self.lid..string.format("No next time as dT=nil ==> Stopping timer after %d function calls",self.ncalls)) +stopme=true +elseif self.Tstop and Tnext>self.Tstop then +self:T(self.lid..string.format("Stop time passed %.3f > %.3f ==> Stopping timer after %d function calls",Tnext,self.Tstop,self.ncalls)) +stopme=true +elseif self.ncallsMax and self.ncalls>=self.ncallsMax then +self:T(self.lid..string.format("Max function calls Nmax=%d reached ==> Stopping timer after %d function calls",self.ncallsMax,self.ncalls)) +stopme=true +end +if stopme then +self:Stop() +return nil +else +return Tnext +end +end +do +GOAL={ +ClassName="GOAL", +} +GOAL.Players={} +GOAL.TotalContributions=0 +function GOAL:New() +local self=BASE:Inherit(self,FSM:New()) +self:F({}) +self:SetStartState("Pending") +self:AddTransition("*","Achieved","Achieved") +self:SetEventPriority(5) +return self +end +function GOAL:AddPlayerContribution(PlayerName) +self:F({PlayerName}) +self.Players[PlayerName]=self.Players[PlayerName]or 0 +self.Players[PlayerName]=self.Players[PlayerName]+1 +self.TotalContributions=self.TotalContributions+1 +end +function GOAL:GetPlayerContribution(PlayerName) +return self.Players[PlayerName]or 0 +end +function GOAL:GetPlayerContributions() +return self.Players or{} +end +function GOAL:GetTotalContributions() +return self.TotalContributions or 0 +end +function GOAL:IsAchieved() +return self:Is("Achieved") +end +end +do +SPOT={ +ClassName="SPOT", +} +function SPOT:New(Recce) +local self=BASE:Inherit(self,FSM:New()) +self:F({}) +self:SetStartState("Off") +self:AddTransition("Off","LaseOn","On") +self:AddTransition("Off","LaseOnCoordinate","On") +self:AddTransition("On","Lasing","On") +self:AddTransition({"On","Destroyed"},"LaseOff","Off") +self:AddTransition("*","Destroyed","Destroyed") +self.Recce=Recce +self.RecceName=self.Recce:GetName() +self.LaseScheduler=SCHEDULER:New(self) +self:SetEventPriority(5) +self.Lasing=false +return self +end +function SPOT:onafterLaseOn(From,Event,To,Target,LaserCode,Duration) +self:T({From,Event,To}) +self:T2({"LaseOn",Target,LaserCode,Duration}) +local function StopLase(self) +self:LaseOff() +end +self.Target=Target +self.TargetName=Target:GetName() +self.LaserCode=LaserCode +self.Lasing=true +local RecceDcsUnit=self.Recce:GetDCSObject() +local relativespot=self.relstartpos or{x=0,y=2,z=0} +self.SpotIR=Spot.createInfraRed(RecceDcsUnit,relativespot,Target:GetPointVec3():AddY(1):GetVec3()) +self.SpotLaser=Spot.createLaser(RecceDcsUnit,relativespot,Target:GetPointVec3():AddY(1):GetVec3(),LaserCode) +if Duration then +self.ScheduleID=self.LaseScheduler:Schedule(self,StopLase,{self},Duration) +end +self:HandleEvent(EVENTS.Dead) +self:__Lasing(-1) +return self +end +function SPOT:onafterLaseOnCoordinate(From,Event,To,Coordinate,LaserCode,Duration) +self:T2({"LaseOnCoordinate",Coordinate,LaserCode,Duration}) +local function StopLase(self) +self:LaseOff() +end +self.Target=nil +self.TargetCoord=Coordinate +self.LaserCode=LaserCode +self.Lasing=true +local RecceDcsUnit=self.Recce:GetDCSObject() +self.SpotIR=Spot.createInfraRed(RecceDcsUnit,{x=0,y=1,z=0},Coordinate:GetVec3()) +self.SpotLaser=Spot.createLaser(RecceDcsUnit,{x=0,y=1,z=0},Coordinate:GetVec3(),LaserCode) +if Duration then +self.ScheduleID=self.LaseScheduler:Schedule(self,StopLase,{self},Duration) +end +self:__Lasing(-1) +return self +end +function SPOT:OnEventDead(EventData) +self:T2({Dead=EventData.IniDCSUnitName,Target=self.Target}) +if self.Target then +if EventData.IniDCSUnitName==self.TargetName then +self:F({"Target dead ",self.TargetName}) +self:Destroyed() +self:LaseOff() +end +end +if self.Recce then +if EventData.IniDCSUnitName==self.RecceName then +self:F({"Recce dead ",self.RecceName}) +self:LaseOff() +end +end +return self +end +function SPOT:onafterLasing(From,Event,To) +self:T({From,Event,To}) +if self.Lasing then +if self.Target and self.Target:IsAlive()then +self.SpotIR:setPoint(self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/200):AddX(math.random(-100,100)/200):GetVec3()) +self.SpotLaser:setPoint(self.Target:GetPointVec3():AddY(1):GetVec3()) +self:__Lasing(0.2) +elseif self.TargetCoord then +local irvec3={x=self.TargetCoord.x+math.random(-100,100)/200,y=self.TargetCoord.y+math.random(-100,100)/200,z=self.TargetCoord.z} +local lsvec3={x=self.TargetCoord.x,y=self.TargetCoord.y,z=self.TargetCoord.z} +self.SpotIR:setPoint(irvec3) +self.SpotLaser:setPoint(lsvec3) +self:__Lasing(0.2) +else +self:F({"Target is not alive",self.Target:IsAlive()}) +end +end +return self +end +function SPOT:onafterLaseOff(From,Event,To) +self:T({From,Event,To}) +self:T2({"Stopped lasing for ",self.Target and self.Target:GetName()or"coord",SpotIR=self.SportIR,SpotLaser=self.SpotLaser}) +self.Lasing=false +self.SpotIR:destroy() +self.SpotLaser:destroy() +self.SpotIR=nil +self.SpotLaser=nil +if self.ScheduleID then +self.LaseScheduler:Stop(self.ScheduleID) +end +self.ScheduleID=nil +self.Target=nil +return self +end +function SPOT:IsLasing() +return self.Lasing +end +function SPOT:SetRelativeStartPosition(position) +self.relstartpos=position or{x=0,y=2,z=0} +return self +end +end +MARKEROPS_BASE={ +ClassName="MARKEROPS", +Tag="mytag", +Keywords={}, +version="0.1.4", +debug=false, +Casesensitive=true, +} +function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) +local self=BASE:Inherit(self,FSM:New()) +self.lid=string.format("MARKEROPS_BASE %s | ",tostring(self.version)) +self.Tag=Tagname or"mytag" +self.Keywords=Keywords or{} +self.debug=false +self.Casesensitive=true +if Casesensitive and Casesensitive==false then +self.Casesensitive=false +end +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","MarkAdded","*") +self:AddTransition("*","MarkChanged","*") +self:AddTransition("*","MarkDeleted","*") +self:AddTransition("Running","Stop","Stopped") +self:HandleEvent(EVENTS.MarkAdded,self.OnEventMark) +self:HandleEvent(EVENTS.MarkChange,self.OnEventMark) +self:HandleEvent(EVENTS.MarkRemoved,self.OnEventMark) +self:I(self.lid..string.format("started for %s",self.Tag)) +self:__Start(1) +return self +end +function MARKEROPS_BASE:OnEventMark(Event) +self:T({Event}) +if Event==nil or Event.idx==nil then +self:E("Skipping onEvent. Event or Event.idx unknown.") +return true +end +local coalition=Event.MarkCoalition +if Event.id==world.event.S_EVENT_MARK_ADDED then +self:T({event="S_EVENT_MARK_ADDED",carrier=Event.IniGroupName,vec3=Event.pos}) +local Eventtext=tostring(Event.text) +if Eventtext~=nil then +if self:_MatchTag(Eventtext)then +local coord=COORDINATE:NewFromVec3({y=Event.pos.y,x=Event.pos.x,z=Event.pos.z}) +if self.debug then +local coordtext=coord:ToStringLLDDM() +local text=tostring(Event.text) +local m=MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll() +end +local matchtable=self:_MatchKeywords(Eventtext) +self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) +end +end +elseif Event.id==world.event.S_EVENT_MARK_CHANGE then +self:T({event="S_EVENT_MARK_CHANGE",carrier=Event.IniGroupName,vec3=Event.pos}) +local Eventtext=tostring(Event.text) +if Eventtext~=nil then +if self:_MatchTag(Eventtext)then +local coord=COORDINATE:NewFromVec3({y=Event.pos.y,x=Event.pos.x,z=Event.pos.z}) +if self.debug then +local coordtext=coord:ToStringLLDDM() +local text=tostring(Event.text) +local m=MESSAGE:New(string.format("Mark changed at %s with text: %s",coordtext,text),10,"Info",false):ToAll() +end +local matchtable=self:_MatchKeywords(Eventtext) +self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) +end +end +elseif Event.id==world.event.S_EVENT_MARK_REMOVED then +self:T({event="S_EVENT_MARK_REMOVED",carrier=Event.IniGroupName,vec3=Event.pos}) +local Eventtext=tostring(Event.text) +if Eventtext~=nil then +if self:_MatchTag(Eventtext)then +self:MarkDeleted() +end +end +end +end +function MARKEROPS_BASE:_MatchTag(Eventtext) +local matches=false +if not self.Casesensitive then +local type=string.lower(self.Tag) +if string.find(string.lower(Eventtext),type)then +matches=true +end +else +local type=self.Tag +if string.find(Eventtext,type)then +matches=true +end +end +return matches +end +function MARKEROPS_BASE:_MatchKeywords(Eventtext) +local matchtable={} +local keytable=self.Keywords +for _index,_word in pairs(keytable)do +if string.find(string.lower(Eventtext),string.lower(_word))then +table.insert(matchtable,_word) +end +end +return matchtable +end +function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber) +self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) +end +function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber) +self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) +end +function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To) +self:T({self.lid,From,Event,To}) +end +function MARKEROPS_BASE:onenterStopped(From,Event,To) +self:T({self.lid,From,Event,To}) +self:UnHandleEvent(EVENTS.MarkAdded) +self:UnHandleEvent(EVENTS.MarkChange) +self:UnHandleEvent(EVENTS.MarkRemoved) +end +TEXTANDSOUND={ +ClassName="TEXTANDSOUND", +version="0.0.1", +lid="", +locale="en", +entries={}, +textclass="", +} +function TEXTANDSOUND:New(ClassName,Defaultlocale) +local self=BASE:Inherit(self,BASE:New()) +self.lid=string.format("%s (%s) | ",self.ClassName,self.version) +self.locale=Defaultlocale or(_SETTINGS:GetLocale()or"en") +self.textclass=ClassName or"none" +self.entries={} +local initentry={} +initentry.Classname=ClassName +initentry.Data={} +initentry.Locale=self.locale +self.entries[self.locale]=initentry +self:I(self.lid.."Instantiated.") +self:T({self.entries[self.locale]}) +return self +end +function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle) +self:T(self.lid.."AddEntry") +local locale=Locale or self.locale +local dataentry={} +dataentry.ID=ID or"1" +dataentry.Text=Text or"none" +dataentry.Soundfile=Soundfile +dataentry.Soundlength=Soundlength or 0 +dataentry.Subtitle=Subtitle +if not self.entries[locale]then +local initentry={} +initentry.Classname=self.textclass +initentry.Data={} +initentry.Locale=locale +self.entries[locale]=initentry +end +self.entries[locale].Data[ID]=dataentry +self:T({self.entries[locale].Data}) +return self +end +function TEXTANDSOUND:GetEntry(ID,Locale) +self:T(self.lid.."GetEntry") +local locale=Locale or self.locale +if not self.entries[locale]then +locale=self.locale +end +local Text,Soundfile,Soundlength,Subtitle=nil,nil,0,nil +if self.entries[locale]then +if self.entries[locale].Data then +local data=self.entries[locale].Data[ID] +if data then +Text=data.Text +Soundfile=data.Soundfile +Soundlength=data.Soundlength +Subtitle=data.Subtitle +elseif self.entries[self.locale].Data[ID]then +local data=self.entries[self.locale].Data[ID] +Text=data.Text +Soundfile=data.Soundfile +Soundlength=data.Soundlength +Subtitle=data.Subtitle +end +end +else +return nil,nil,0,nil +end +return Text,Soundfile,Soundlength,Subtitle +end +function TEXTANDSOUND:GetDefaultLocale() +self:T(self.lid.."GetDefaultLocale") +return self.locale +end +function TEXTANDSOUND:SetDefaultLocale(locale) +self:T(self.lid.."SetDefaultLocale") +self.locale=locale or"en" +return self +end +function TEXTANDSOUND:HasLocale(Locale) +self:T(self.lid.."HasLocale") +return self.entries[Locale]and true or false +end +function TEXTANDSOUND:FlushToLog() +self:I(self.lid.."Flushing entries:") +local text=string.format("Textclass: %s | Default Locale: %s",self.textclass,self.locale) +for _,_entry in pairs(self.entries)do +local entry=_entry +local text=string.format("Textclassname: %s | Locale: %s",entry.Classname,entry.Locale) +self:I(text) +for _ID,_data in pairs(entry.Data)do +local data=_data +local text=string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID),data.Text or"none",data.Soundfile or"none",data.Soundlength or 0,data.Subtitle or"none") +self:I(text) +end +end +return self +end +PATHLINE={ +ClassName="PATHLINE", +lid=nil, +points={}, +} +PATHLINE.version="0.1.1" +function PATHLINE:New(Name) +local self=BASE:Inherit(self,BASE:New()) +self.name=Name or"Unknown Path" +self.lid=string.format("PATHLINE %s | ",Name) +return self +end +function PATHLINE:NewFromVec2Array(Name,Vec2Array) +local self=PATHLINE:New(Name) +for i=1,#Vec2Array do +self:AddPointFromVec2(Vec2Array[i]) +end +return self +end +function PATHLINE:NewFromVec3Array(Name,Vec3Array) +local self=PATHLINE:New(Name) +for i=1,#Vec3Array do +self:AddPointFromVec3(Vec3Array[i]) +end +return self +end +function PATHLINE:FindByName(Name) +local pathline=_DATABASE:FindPathline(Name) +return pathline +end +function PATHLINE:AddPointFromVec2(Vec2) +if Vec2 then +local point=self:_CreatePoint(Vec2) +table.insert(self.points,point) +end +return self +end +function PATHLINE:AddPointFromVec3(Vec3) +if Vec3 then +local point=self:_CreatePoint(Vec3) +table.insert(self.points,point) +end +return self +end +function PATHLINE:GetName() +return self.name +end +function PATHLINE:GetNumberOfPoints() +local N=#self.points +return N +end +function PATHLINE:GetPoints() +return self.points +end +function PATHLINE:GetPoints3D() +local vecs={} +for _,_point in pairs(self.points)do +local point=_point +table.insert(vecs,point.vec3) +end +return vecs +end +function PATHLINE:GetPoints2D() +local vecs={} +for _,_point in pairs(self.points)do +local point=_point +table.insert(vecs,point.vec2) +end +return vecs +end +function PATHLINE:GetCoordinates() +local vecs={} +for _,_point in pairs(self.points)do +local point=_point +local coord=COORDINATE:NewFromVec3(point.vec3) +table.insert(vecs,coord) +end +return vecs +end +function PATHLINE:GetPointFromIndex(n) +local N=self:GetNumberOfPoints() +n=n or 1 +local point=nil +if n>=1 and n<=N then +point=self.points[n] +else +self:E(self.lid..string.format("ERROR: No point in pathline for N=%s",tostring(n))) +end +return point +end +function PATHLINE:GetPoint3DFromIndex(n) +local point=self:GetPointFromIndex(n) +if point then +return point.vec3 +end +return nil +end +function PATHLINE:GetPoint2DFromIndex(n) +local point=self:GetPointFromIndex(n) +if point then +return point.vec2 +end +return nil +end +function PATHLINE:MarkPoints(Switch) +for i,_point in pairs(self.points)do +local point=_point +if Switch==false then +if point.markerID then +UTILS.RemoveMark(point.markerID,Delay) +end +else +if point.markerID then +UTILS.RemoveMark(point.markerID) +end +point.markerID=UTILS.GetMarkID() +local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m",self.name,i,point.surfaceType,point.landHeight,point.depth) +trigger.action.markToAll(point.markerID,text,point.vec3,"") +end +end +end +function PATHLINE:_CreatePoint(Vec) +local point={} +if Vec.z then +point.vec3=UTILS.DeepCopy(Vec) +point.vec2={x=Vec.x,y=Vec.z} +else +point.vec2=UTILS.DeepCopy(Vec) +point.vec3={x=Vec.x,y=land.getHeight(Vec),z=Vec.y} +end +point.surfaceType=land.getSurfaceType(point.vec2) +point.landHeight,point.depth=land.getSurfaceHeightWithSeabed(point.vec2) +point.markerID=nil +return point +end +CLIENTMENU={ +ClassName="CLIENTMENU", +lid="", +version="0.1.3", +name=nil, +path=nil, +group=nil, +client=nil, +GroupID=nil, +Children={}, +Once=false, +Generic=false, +debug=false, +Controller=nil, +groupname=nil, +active=false, +} +CLIENTMENU_ID=0 +function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...) +local self=BASE:Inherit(self,BASE:New()) +CLIENTMENU_ID=CLIENTMENU_ID+1 +self.ID=CLIENTMENU_ID +if Client then +self.group=Client:GetGroup() +self.client=Client +self.GroupID=self.group:GetID() +self.groupname=self.group:GetName()or"Unknown Groupname" +else +self.Generic=true +end +self.name=Text or"unknown entry" +if Parent then +if Parent:IsInstanceOf("MENU_BASE")then +self.parentpath=Parent.MenuPath +else +self.parentpath=Parent:GetPath() +Parent:AddChild(self) +end +end +self.Parent=Parent +self.Function=Function +self.Functionargs=arg or{} +table.insert(self.Functionargs,self.group) +table.insert(self.Functionargs,self.client) +if self.Functionargs and self.debug then +self:T({"Functionargs",self.Functionargs}) +end +if not self.Generic and self.active==false then +if Function~=nil then +local ErrorHandler=function(errmsg) +env.info("MOOSE Error in CLIENTMENU COMMAND function: "..errmsg) +if BASE.Debug~=nil then +env.info(BASE.Debug.traceback()) +end +return errmsg +end +self.CallHandler=function() +local function MenuFunction() +return self.Function(unpack(self.Functionargs)) +end +local Status,Result=xpcall(MenuFunction,ErrorHandler) +if self.Once==true then +self:Clear() +end +end +self.path=missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath,self.CallHandler) +self.active=true +else +self.path=missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath) +self.active=true +end +else +if self.parentpath then +self.path=UTILS.DeepCopy(self.parentpath) +else +self.path={} +end +self.path[#self.path+1]=Text +end +self.UUID=table.concat(self.path,";") +self:T({self.UUID}) +self.Once=false +self.lid=string.format("CLIENTMENU %s | %s | ",self.ID,self.name) +self:T(self.lid.."Created") +return self +end +function CLIENTMENU:CreateUUID(Parent,Text) +local path={} +if Parent and Parent.path then +path=Parent.path +end +path[#path+1]=Text +local UUID=table.concat(path,";") +return UUID +end +function CLIENTMENU:SetController(Controller) +self.Controller=Controller +return self +end +function CLIENTMENU:SetOnce() +self:T(self.lid.."SetOnce") +self.Once=true +return self +end +function CLIENTMENU:RemoveF10() +self:T(self.lid.."RemoveF10") +if self.GroupID then +local function RemoveFunction() +return missionCommands.removeItemForGroup(self.GroupID,self.path) +end +local status,err=pcall(RemoveFunction) +if not status then +self:I(string.format("**** Error Removing Menu Entry %s for %s!",tostring(self.name),self.groupname)) +end +self.active=false +end +return self +end +function CLIENTMENU:GetPath() +self:T(self.lid.."GetPath") +return self.path +end +function CLIENTMENU:GetUUID() +self:T(self.lid.."GetUUID") +return self.UUID +end +function CLIENTMENU:AddChild(Child) +self:T(self.lid.."AddChild "..Child.ID) +table.insert(self.Children,Child.ID,Child) +return self +end +function CLIENTMENU:RemoveChild(Child) +self:T(self.lid.."RemoveChild "..Child.ID) +table.remove(self.Children,Child.ID) +return self +end +function CLIENTMENU:RemoveSubEntries() +self:T(self.lid.."RemoveSubEntries") +self:T({self.Children}) +for _id,_entry in pairs(self.Children)do +self:T("Removing ".._id) +if _entry then +_entry:RemoveSubEntries() +_entry:RemoveF10() +if _entry.Parent then +_entry.Parent:RemoveChild(self) +end +end +end +return self +end +function CLIENTMENU:Clear() +self:T(self.lid.."Clear") +for _id,_entry in pairs(self.Children)do +if _entry then +_entry:RemoveSubEntries() +_entry=nil +end +end +self:RemoveF10() +if self.Parent then +self.Parent:RemoveChild(self) +end +return self +end +CLIENTMENUMANAGER={ +ClassName="CLIENTMENUMANAGER", +lid="", +version="0.1.7", +name=nil, +clientset=nil, +menutree={}, +flattree={}, +playertree={}, +entrycount=0, +rootentries={}, +debug=true, +PlayerMenu={}, +Coalition=nil, +} +function CLIENTMENUMANAGER:New(ClientSet,Alias,Coalition) +local self=BASE:Inherit(self,BASE:New()) +self.clientset=ClientSet +self.PlayerMenu={} +self.name=Alias or"Nightshift" +self.Coalition=Coalition or coalition.side.BLUE +self.lid=string.format("CLIENTMENUMANAGER %s | %s | ",self.version,self.name) +if self.debug then +self:I(self.lid.."Created") +end +return self +end +function CLIENTMENUMANAGER:_EventHandler(EventData,Retry) +self:T(self.lid.."_EventHandler: "..EventData.id) +if EventData.id==EVENTS.PlayerLeaveUnit or EventData.id==EVENTS.Ejection or EventData.id==EVENTS.Crash or EventData.id==EVENTS.PilotDead then +self:T(self.lid.."Leave event for player: "..tostring(EventData.IniPlayerName)) +local Client=_DATABASE:FindClient(EventData.IniUnitName) +if Client then +self:ResetMenu(Client) +end +elseif(EventData.id==EVENTS.PlayerEnterAircraft)and EventData.IniCoalition==self.Coalition then +if EventData.IniPlayerName and EventData.IniGroup then +if(not self.clientset:IsIncludeObject(_DATABASE:FindClient(EventData.IniUnitName)))then +self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName) +if not Retry then +self:ScheduleOnce(2,CLIENTMENUMANAGER._EventHandler,self,EventData,true) +end +return self +end +local player=_DATABASE:FindClient(EventData.IniUnitName) +self:Propagate(player) +end +elseif EventData.id==EVENTS.PlayerEnterUnit then +local grp=GROUP:FindByName(EventData.IniGroupName) +if grp:IsGround()then +self:T(string.format("Player %s entered GROUND unit %s!",EventData.IniPlayerName,EventData.IniUnitName)) +local IsPlayer=EventData.IniDCSUnit:getPlayerName() +if IsPlayer then +local client=_DATABASE.CLIENTS[EventData.IniDCSUnitName] +if not client then +self:I(string.format("Player '%s' joined ground unit '%s' of group '%s'",tostring(EventData.IniPlayerName),tostring(EventData.IniDCSUnitName),tostring(EventData.IniDCSGroupName))) +client=_DATABASE:AddClient(EventData.IniDCSUnitName) +client:AddPlayer(EventData.IniPlayerName) +if not _DATABASE.PLAYERS[EventData.IniPlayerName]then +_DATABASE:AddPlayer(EventData.IniUnitName,EventData.IniPlayerName) +end +local Settings=SETTINGS:Set(EventData.IniPlayerName) +Settings:SetPlayerMenu(EventData.IniUnit) +end +self:Propagate(client) +end +end +end +return self +end +function CLIENTMENUMANAGER:InitAutoPropagation() +self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler) +self:HandleEvent(EVENTS.Ejection,self._EventHandler) +self:HandleEvent(EVENTS.Crash,self._EventHandler) +self:HandleEvent(EVENTS.PilotDead,self._EventHandler) +self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler) +self:SetEventPriority(6) +return self +end +function CLIENTMENUMANAGER:NewEntry(Text,Parent,Function,...) +self:T(self.lid.."NewEntry "..Text or"None") +self.entrycount=self.entrycount+1 +local entry=CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg)) +if not Parent then +self.rootentries[self.entrycount]=entry +end +local depth=#entry.path +if not self.menutree[depth]then self.menutree[depth]={}end +table.insert(self.menutree[depth],entry.UUID) +self.flattree[entry.UUID]=entry +return entry +end +function CLIENTMENUMANAGER:EntryUUIDExists(UUID) +local exists=self.flattree[UUID]and true or false +return exists +end +function CLIENTMENUMANAGER:FindEntryByUUID(UUID) +self:T(self.lid.."FindEntryByUUID "..UUID or"None") +local entry=nil +for _gid,_entry in pairs(self.flattree)do +local Entry=_entry +if Entry and Entry.UUID==UUID then +entry=Entry +end +end +return entry +end +function CLIENTMENUMANAGER:FindUUIDsByText(Text,Parent) +self:T(self.lid.."FindUUIDsByText "..Text or"None") +local matches={} +local entries={} +local n=0 +for _uuid,_entry in pairs(self.flattree)do +local Entry=_entry +if Parent then +if Entry and string.find(Entry.name,Text,1,true)and string.find(Entry.UUID,Parent.UUID,1,true)then +table.insert(matches,_uuid) +table.insert(entries,Entry) +n=n+1 +end +else +if Entry and string.find(Entry.name,Text,1,true)then +table.insert(matches,_uuid) +table.insert(entries,Entry) +n=n+1 +end +end +end +return matches,entries,n +end +function CLIENTMENUMANAGER:FindEntriesByText(Text,Parent) +self:T(self.lid.."FindEntriesByText "..Text or"None") +local matches,objects,number=self:FindUUIDsByText(Text,Parent) +return objects,number +end +function CLIENTMENUMANAGER:FindUUIDsByParent(Parent) +self:T(self.lid.."FindUUIDsByParent") +local matches={} +local entries={} +local n=0 +for _uuid,_entry in pairs(self.flattree)do +local Entry=_entry +if Parent then +if Entry and string.find(Entry.UUID,Parent.UUID,1,true)then +table.insert(matches,_uuid) +table.insert(entries,Entry) +n=n+1 +end +end +end +return matches,entries,n +end +function CLIENTMENUMANAGER:FindEntriesByParent(Parent) +self:T(self.lid.."FindEntriesByParent") +local matches,objects,number=self:FindUUIDsByParent(Parent) +return objects,number +end +function CLIENTMENUMANAGER:ChangeEntryText(Entry,Text,Client) +self:T(self.lid.."ChangeEntryText "..Text or"None") +local newentry=CLIENTMENU:NewEntry(nil,Text,Entry.Parent,Entry.Function,unpack(Entry.Functionargs)) +self:DeleteF10Entry(Entry,Client) +self:DeleteGenericEntry(Entry) +if not Entry.Parent then +self.rootentries[self.entrycount]=newentry +end +local depth=#newentry.path +if not self.menutree[depth]then self.menutree[depth]={}end +table.insert(self.menutree[depth],newentry.UUID) +self.flattree[newentry.UUID]=newentry +self:AddEntry(newentry,Client) +return self +end +function CLIENTMENUMANAGER:Propagate(Client) +self:T(self.lid.."Propagate") +local knownunits={} +local Set=self.clientset.Set +if Client then +Set={Client} +end +self:ResetMenu(Client) +for _,_client in pairs(Set)do +local client=_client +if client and client:IsAlive()then +local playerunit=client:GetName() +local playername=client:GetPlayerName()or"none" +if not knownunits[playerunit]then +knownunits[playerunit]=true +else +self:I("Player in multi seat unit: "..playername) +break +end +if not self.playertree[playername]then +self.playertree[playername]={} +end +for level,branch in pairs(self.menutree)do +self:T("Building branch:"..level) +for _,leaf in pairs(branch)do +self:T("Building leaf:"..leaf) +local entry=self:FindEntryByUUID(leaf) +if entry then +self:T("Found generic entry:"..entry.UUID) +local parent=nil +if entry.Parent and entry.Parent.UUID then +parent=self.playertree[playername][entry.Parent.UUID]or self:FindEntryByUUID(entry.Parent.UUID) +end +self.playertree[playername][entry.UUID]=CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs)) +self.playertree[playername][entry.UUID].Once=entry.Once +else +self:T("NO generic entry for:"..leaf) +end +end +end +end +end +return self +end +function CLIENTMENUMANAGER:AddEntry(Entry,Client) +self:T(self.lid.."AddEntry") +local Set=self.clientset.Set +local knownunits={} +if Client then +Set={Client} +end +for _,_client in pairs(Set)do +local client=_client +if client and client:IsAlive()then +local playername=client:GetPlayerName()or"None" +local unitname=client:GetName() +if not knownunits[unitname]then +knownunits[unitname]=true +else +self:I("Player in multi seat unit: "..playername) +break +end +if Entry then +self:T("Adding generic entry:"..Entry.UUID) +local parent=nil +if not self.playertree[playername]then +self.playertree[playername]={} +end +if Entry.Parent and Entry.Parent.UUID then +parent=self.playertree[playername][Entry.Parent.UUID]or self:FindEntryByUUID(Entry.Parent.UUID) +end +self.playertree[playername][Entry.UUID]=CLIENTMENU:NewEntry(client,Entry.name,parent,Entry.Function,unpack(Entry.Functionargs)) +self.playertree[playername][Entry.UUID].Once=Entry.Once +else +self:T("NO generic entry given") +end +end +end +return self +end +function CLIENTMENUMANAGER:ResetMenu(Client) +self:T(self.lid.."ResetMenu") +for _,_entry in pairs(self.rootentries)do +if _entry then +self:DeleteF10Entry(_entry,Client) +end +end +return self +end +function CLIENTMENUMANAGER:ResetMenuComplete() +self:T(self.lid.."ResetMenuComplete") +for _,_entry in pairs(self.rootentries)do +if _entry then +self:DeleteF10Entry(_entry) +end +end +self.playertree=nil +self.playertree={} +self.rootentries=nil +self.rootentries={} +self.menutree=nil +self.menutree={} +return self +end +function CLIENTMENUMANAGER:DeleteEntry(Entry,Client) +self:T(self.lid.."DeleteEntry") +return self:DeleteF10Entry(Entry,Client) +end +function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client) +self:T(self.lid.."DeleteF10Entry") +local Set=self.clientset.Set +if Client then +Set={Client} +end +for _,_client in pairs(Set)do +if _client and _client:IsAlive()then +local playername=_client:GetPlayerName() +if self.playertree[playername]then +local centry=self.playertree[playername][Entry.UUID] +if centry then +centry:Clear() +end +end +end +end +return self +end +function CLIENTMENUMANAGER:DeleteGenericEntry(Entry) +self:T(self.lid.."DeleteGenericEntry") +if Entry.Children and#Entry.Children>0 then +self:RemoveGenericSubEntries(Entry) +end +local depth=#Entry.path +local uuid=Entry.UUID +local tbl=UTILS.DeepCopy(self.menutree) +if tbl[depth]then +for i=depth,#tbl do +for _id,_uuid in pairs(tbl[i])do +self:T(_uuid) +if string.find(_uuid,uuid,1,true)or _uuid==uuid then +self.menutree[i][_id]=nil +self.flattree[_uuid]=nil +end +end +end +end +return self +end +function CLIENTMENUMANAGER:RemoveGenericSubEntries(Entry) +self:T(self.lid.."RemoveGenericSubEntries") +local depth=#Entry.path+1 +local uuid=Entry.UUID +local tbl=UTILS.DeepCopy(self.menutree) +if tbl[depth]then +for i=depth,#tbl do +self:T("Level = "..i) +for _id,_uuid in pairs(tbl[i])do +self:T(_uuid) +if string.find(_uuid,uuid,1,true)then +self:T("Match for ".._uuid) +self.menutree[i][_id]=nil +self.flattree[_uuid]=nil +end +end +end +end +return self +end +function CLIENTMENUMANAGER:RemoveF10SubEntries(Entry,Client) +self:T(self.lid.."RemoveSubEntries") +local Set=self.clientset.Set +if Client then +Set={Client} +end +for _,_client in pairs(Set)do +if _client and _client:IsAlive()then +local playername=_client:GetPlayerName() +if self.playertree[playername]then +local centry=self.playertree[playername][Entry.UUID] +centry:RemoveSubEntries() +end +end +end +return self +end +OBJECT={ +ClassName="OBJECT", +ObjectName="", +} +function OBJECT:New(ObjectName) +local self=BASE:Inherit(self,BASE:New()) +self:F2(ObjectName) +self.ObjectName=ObjectName +return self +end +function OBJECT:GetID() +local DCSObject=self:GetDCSObject() +if DCSObject then +local ObjectID=DCSObject:getID() +return ObjectID +end +self:E({"Cannot GetID",Name=self.ObjectName,Class=self:GetClassName()}) +return nil +end +function OBJECT:Destroy() +local DCSObject=self:GetDCSObject() +if DCSObject then +DCSObject:destroy(false) +return true +end +self:E({"Cannot Destroy",Name=self.ObjectName,Class=self:GetClassName()}) +return nil +end +IDENTIFIABLE={ +ClassName="IDENTIFIABLE", +IdentifiableName="", +} +local _CategoryName={ +[Unit.Category.AIRPLANE]="Airplane", +[Unit.Category.HELICOPTER]="Helicopter", +[Unit.Category.GROUND_UNIT]="Ground Identifiable", +[Unit.Category.SHIP]="Ship", +[Unit.Category.STRUCTURE]="Structure", +} +function IDENTIFIABLE:New(IdentifiableName) +local self=BASE:Inherit(self,OBJECT:New(IdentifiableName)) +self:F2(IdentifiableName) +self.IdentifiableName=IdentifiableName +return self +end +function IDENTIFIABLE:IsAlive() +self:F3(self.IdentifiableName) +local DCSIdentifiable=self:GetDCSObject() +if DCSIdentifiable then +local IdentifiableIsAlive=DCSIdentifiable:isExist() +return IdentifiableIsAlive +end +return false +end +function IDENTIFIABLE:GetName() +local IdentifiableName=self.IdentifiableName +return IdentifiableName +end +function IDENTIFIABLE:GetTypeName() +self:F2(self.IdentifiableName) +local DCSIdentifiable=self:GetDCSObject() +if DCSIdentifiable then +local IdentifiableTypeName=DCSIdentifiable:getTypeName() +self:T3(IdentifiableTypeName) +return IdentifiableTypeName +end +self:F(self.ClassName.." "..self.IdentifiableName.." not found!") +return nil +end +function IDENTIFIABLE:GetCategory() +self:F2(self.ObjectName) +local DCSObject=self:GetDCSObject() +if DCSObject then +local ObjectCategory,UnitCategory=DCSObject:getCategory() +self:T3(ObjectCategory) +return ObjectCategory,UnitCategory +end +return nil,nil +end +function IDENTIFIABLE:GetCategoryName() +local DCSIdentifiable=self:GetDCSObject() +if DCSIdentifiable then +local IdentifiableCategoryName=_CategoryName[self:GetDesc().category] +return IdentifiableCategoryName +end +self:F(self.ClassName.." "..self.IdentifiableName.." not found!") +return nil +end +function IDENTIFIABLE:GetCoalition() +self:F2(self.IdentifiableName) +local DCSIdentifiable=self:GetDCSObject() +if DCSIdentifiable then +local IdentifiableCoalition=DCSIdentifiable:getCoalition() +self:T3(IdentifiableCoalition) +return IdentifiableCoalition +end +self:F(self.ClassName.." "..self.IdentifiableName.." not found!") +return nil +end +function IDENTIFIABLE:GetCoalitionName() +self:F2(self.IdentifiableName) +local DCSIdentifiable=self:GetDCSObject() +if DCSIdentifiable then +local IdentifiableCoalition=DCSIdentifiable:getCoalition() +self:T3(IdentifiableCoalition) +return UTILS.GetCoalitionName(IdentifiableCoalition) +end +self:F(self.ClassName.." "..self.IdentifiableName.." not found!") +return nil +end +function IDENTIFIABLE:GetCountry() +self:F2(self.IdentifiableName) +local DCSIdentifiable=self:GetDCSObject() +if DCSIdentifiable then +local IdentifiableCountry=DCSIdentifiable:getCountry() +self:T3(IdentifiableCountry) +return IdentifiableCountry +end +self:F(self.ClassName.." "..self.IdentifiableName.." not found!") +return nil +end +function IDENTIFIABLE:GetCountryName() +self:F2(self.IdentifiableName) +local countryid=self:GetCountry() +for name,id in pairs(country.id)do +if countryid==id then +return name +end +end +end +function IDENTIFIABLE:GetDesc() +self:F2(self.IdentifiableName) +local DCSIdentifiable=self:GetDCSObject() +if DCSIdentifiable then +local IdentifiableDesc=DCSIdentifiable:getDesc() +self:T2(IdentifiableDesc) +return IdentifiableDesc +end +self:F(self.ClassName.." "..self.IdentifiableName.." not found!") +return nil +end +function IDENTIFIABLE:HasAttribute(AttributeName) +self:F2(self.IdentifiableName) +local DCSIdentifiable=self:GetDCSObject() +if DCSIdentifiable then +local IdentifiableHasAttribute=DCSIdentifiable:hasAttribute(AttributeName) +self:T2(IdentifiableHasAttribute) +return IdentifiableHasAttribute +end +self:F(self.ClassName.." "..self.IdentifiableName.." not found!") +return nil +end +function IDENTIFIABLE:GetCallsign() +return'' +end +function IDENTIFIABLE:GetThreatLevel() +return 0,"Scenery" +end +POSITIONABLE={ +ClassName="POSITIONABLE", +PositionableName="", +coordinate=nil, +pointvec3=nil, +} +POSITIONABLE.__={} +POSITIONABLE.__.Cargo={} +function POSITIONABLE:New(PositionableName) +local self=BASE:Inherit(self,IDENTIFIABLE:New(PositionableName)) +self.PositionableName=PositionableName +return self +end +function POSITIONABLE:Destroy(GenerateEvent) +self:F2(self.ObjectName) +local DCSObject=self:GetDCSObject() +if DCSObject then +local UnitGroup=self:GetGroup() +local UnitGroupName=UnitGroup:GetName() +self:F({UnitGroupName=UnitGroupName}) +if GenerateEvent and GenerateEvent==true then +if self:IsAir()then +self:CreateEventCrash(timer.getTime(),DCSObject) +else +self:CreateEventDead(timer.getTime(),DCSObject) +end +elseif GenerateEvent==false then +else +self:CreateEventRemoveUnit(timer.getTime(),DCSObject) +end +USERFLAG:New(UnitGroupName):Set(100) +DCSObject:destroy() +end +return nil +end +function POSITIONABLE:GetDCSObject() +return nil +end +function POSITIONABLE:GetPosition() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if self:IsInstanceOf("GROUP")then +DCSPositionable=self:GetFirstUnitAlive():GetDCSObject() +end +if DCSPositionable then +local PositionablePosition=DCSPositionable:getPosition() +self:T3(PositionablePosition) +return PositionablePosition +end +BASE:E({"Cannot GetPosition",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetOrientation() +local position=self:GetPosition() +if position then +return position.x,position.y,position.z +else +BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) +return nil,nil,nil +end +end +function POSITIONABLE:GetOrientationX() +local position=self:GetPosition() +if position then +return position.x +else +BASE:E({"Cannot GetOrientationX",Positionable=self,Alive=self:IsAlive()}) +return nil +end +end +function POSITIONABLE:GetOrientationY() +local position=self:GetPosition() +if position then +return position.y +else +BASE:E({"Cannot GetOrientationY",Positionable=self,Alive=self:IsAlive()}) +return nil +end +end +function POSITIONABLE:GetOrientationZ() +local position=self:GetPosition() +if position then +return position.z +else +BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) +return nil +end +end +function POSITIONABLE:GetPositionVec3() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local PositionablePosition=DCSPositionable:getPosition().p +self:T3(PositionablePosition) +return PositionablePosition +end +BASE:E({"Cannot GetPositionVec3",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetVec3() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local vec3=DCSPositionable:getPoint() +if not vec3 then +local pos=DCSPositionable:getPosition() +if pos and pos.p then +vec3=pos.p +else +self:E({"Cannot get the position from DCS Object for GetVec3",Positionable=self,Alive=self:IsAlive()}) +end +end +return vec3 +end +self:E({"Cannot get the Positionable DCS Object for GetVec3",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetVec2() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec3=DCSPositionable:getPoint() +return{x=Vec3.x,y=Vec3.z} +end +self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetPointVec2() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local PositionableVec3=DCSPositionable:getPosition().p +local PositionablePointVec2=COORDINATE:NewFromVec3(PositionableVec3) +return PositionablePointVec2 +end +self:E({"Cannot Coordinate",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetPointVec3() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local PositionableVec3=self:GetPositionVec3() +if false and self.pointvec3 then +self.pointvec3.x=PositionableVec3.x +self.pointvec3.y=PositionableVec3.y +self.pointvec3.z=PositionableVec3.z +else +self.pointvec3=COORDINATE:NewFromVec3(PositionableVec3) +end +return self.pointvec3 +end +BASE:E({"Cannot GetPointVec3",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetCoord() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local PositionableVec3=self:GetVec3() +if PositionableVec3 then +if self.coordinate then +self.coordinate:UpdateFromVec3(PositionableVec3) +else +self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) +end +return self.coordinate +end +end +BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetCoordinate() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local PositionableVec3=self:GetVec3() +if PositionableVec3 then +local coord=COORDINATE:NewFromVec3(PositionableVec3) +local heading=self:GetHeading() +coord.Heading=heading +return coord +end +end +self:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:Explode(power,delay) +power=power or 100 +if delay and delay>0 then +self:ScheduleOnce(delay,POSITIONABLE.Explode,self,power,0) +else +local coord=self:GetCoord() +if coord then +coord:Explosion(power) +end +end +return self +end +function POSITIONABLE:GetOffsetCoordinate(x,y,z) +x=x or 0 +y=y or 0 +z=z or 0 +local X=self:GetOrientationX() +local Y=self:GetOrientationY() +local Z=self:GetOrientationZ() +local A={x=x,y=y,z=z} +local x={x=X.x*A.x,y=X.y*A.x,z=X.z*A.x} +local y={x=Y.x*A.y,y=Y.y*A.y,z=Y.z*A.y} +local z={x=Z.x*A.z,y=Z.y*A.z,z=Z.z*A.z} +local a={x=x.x+y.x+z.x,y=x.y+y.y+z.y,z=x.z+y.z+z.z} +local u=self:GetVec3() +local v={x=a.x+u.x,y=a.y+u.y,z=a.z+u.z} +local coord=COORDINATE:NewFromVec3(v) +return coord +end +function POSITIONABLE:GetRelativeCoordinate(x,y,z) +x=x or 0 +y=y or 0 +z=z or 0 +local selfPos=self:GetVec3() +local X=self:GetOrientationX() +local Y=self:GetOrientationY() +local Z=self:GetOrientationZ() +local off={ +x=x-selfPos.x, +y=y-selfPos.y, +z=z-selfPos.z +} +local res={x=0,y=0,z=0} +local mat={ +{X.x,Y.x,Z.x,off.x}, +{X.y,Y.y,Z.y,off.y}, +{X.z,Y.z,Z.z,off.z} +} +local m=3 +local n=4 +local h=1 +local k=1 +while h<=m and k<=n do +local v_max=math.abs(mat[h][k]) +local i_max=h +for i=h,m,1 do +local value=math.abs(mat[i][k]) +if value>v_max then +i_max=i +v_max=value +end +end +if mat[i_max][k]==0 then +k=k+1 +else +local tmp=mat[h] +mat[h]=mat[i_max] +mat[i_max]=tmp +for i=h+1,m,1 do +local f=mat[i][k]/mat[h][k] +mat[i][k]=0 +for j=k+1,n,1 do +mat[i][j]=mat[i][j]-f*mat[h][j] +end +end +h=h+1 +k=k+1 +end +end +res.z=mat[3][4]/mat[3][3] +res.y=(mat[2][4]-res.z*mat[2][3])/mat[2][2] +res.x=(mat[1][4]-res.y*mat[1][2]-res.z*mat[1][3])/mat[1][1] +local coord=COORDINATE:NewFromVec3(res) +return coord +end +function POSITIONABLE:GetRandomVec3(Radius) +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local PositionablePointVec3=DCSPositionable:getPosition().p +if Radius then +local PositionableRandomVec3={} +local angle=math.random()*math.pi*2 +PositionableRandomVec3.x=PositionablePointVec3.x+math.cos(angle)*math.random()*Radius +PositionableRandomVec3.y=PositionablePointVec3.y +PositionableRandomVec3.z=PositionablePointVec3.z+math.sin(angle)*math.random()*Radius +self:T3(PositionableRandomVec3) +return PositionableRandomVec3 +else +self:F("Radius is nil, returning the PointVec3 of the POSITIONABLE",PositionablePointVec3) +return PositionablePointVec3 +end +end +BASE:E({"Cannot GetRandomVec3",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetBoundingBox() +self:F2() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local PositionableDesc=DCSPositionable:getDesc() +if PositionableDesc then +local PositionableBox=PositionableDesc.box +return PositionableBox +end +end +BASE:E({"Cannot GetBoundingBox",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetObjectSize() +local box=self:GetBoundingBox() +if box then +local x=box.max.x+math.abs(box.min.x) +local y=box.max.y+math.abs(box.min.y) +local z=box.max.z+math.abs(box.min.z) +return math.max(x,z),x,y,z +end +return 0,0,0,0 +end +function POSITIONABLE:GetBoundingRadius(MinDist) +self:F2() +local Box=self:GetBoundingBox() +local boxmin=MinDist or 0 +if Box then +local X=Box.max.x-Box.min.x +local Z=Box.max.z-Box.min.z +local CX=X/2 +local CZ=Z/2 +return math.max(math.max(CX,CZ),boxmin) +end +BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetAltitude() +self:F2() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local PositionablePointVec3=DCSPositionable:getPoint() +return PositionablePointVec3.y +end +BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:IsAboveRunway() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec2=self:GetVec2() +local SurfaceType=land.getSurfaceType(Vec2) +local IsAboveRunway=SurfaceType==land.SurfaceType.RUNWAY +self:T2(IsAboveRunway) +return IsAboveRunway +end +BASE:E({"Cannot IsAboveRunway",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetSize() +local DCSObject=self:GetDCSObject() +if DCSObject then +return 1 +else +return 0 +end +end +function POSITIONABLE:GetHeading() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local PositionablePosition=DCSPositionable:getPosition() +if PositionablePosition then +local PositionableHeading=math.atan2(PositionablePosition.x.z,PositionablePosition.x.x) +if PositionableHeading<0 then +PositionableHeading=PositionableHeading+2*math.pi +end +PositionableHeading=PositionableHeading*180/math.pi +return PositionableHeading +end +end +self:E({"Cannot GetHeading",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:IsAir() +self:F2() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitDescriptor=DCSUnit:getDesc() +self:T3({UnitDescriptor.category,Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) +local IsAirResult=(UnitDescriptor.category==Unit.Category.AIRPLANE)or(UnitDescriptor.category==Unit.Category.HELICOPTER) +self:T3(IsAirResult) +return IsAirResult +end +self:E({"Cannot check IsAir",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:IsGround() +self:F2() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitDescriptor=DCSUnit:getDesc() +self:T3({UnitDescriptor.category,Unit.Category.GROUND_UNIT}) +local IsGroundResult=(UnitDescriptor.category==Unit.Category.GROUND_UNIT) +self:T3(IsGroundResult) +return IsGroundResult +end +self:E({"Cannot check IsGround",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:IsShip() +self:F2() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitDescriptor=DCSUnit:getDesc() +self:T3({UnitDescriptor.category,Unit.Category.SHIP}) +local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP) +self:T3(IsShipResult) +return IsShipResult +end +self:E({"Cannot check IsShip",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:IsSubmarine() +self:F2() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitDescriptor=DCSUnit:getDesc() +if UnitDescriptor.attributes["Submarines"]==true then +return true +else +return false +end +end +self:E({"Cannot check IsSubmarine",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:InAir() +self:F2(self.PositionableName) +return nil +end +function POSITIONABLE:GetVelocity() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Velocity=VELOCITY:New(self) +return Velocity +end +BASE:E({"Cannot GetVelocity",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetVelocityVec3() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local PositionableVelocityVec3=DCSPositionable:getVelocity() +self:T3(PositionableVelocityVec3) +return PositionableVelocityVec3 +end +BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function POSITIONABLE:GetRelativeVelocity(Positionable) +self:F2(self.PositionableName) +local v1=self:GetVelocityVec3() +local v2=Positionable:GetVelocityVec3() +local vtot=UTILS.VecAdd(v1,v2) +return UTILS.VecNorm(vtot) +end +function POSITIONABLE:GetHeight() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local PositionablePosition=DCSPositionable:getPosition() +if PositionablePosition then +local PositionableHeight=PositionablePosition.p.y +self:T2(PositionableHeight) +return PositionableHeight +end +end +return nil +end +function POSITIONABLE:GetVelocityKMH() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local VelocityVec3=self:GetVelocityVec3() +local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5 +local Velocity=Velocity*3.6 +self:T3(Velocity) +return Velocity +end +return 0 +end +function POSITIONABLE:GetVelocityMPS() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local VelocityVec3=self:GetVelocityVec3() +local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5 +self:T3(Velocity) +return Velocity +end +return 0 +end +function POSITIONABLE:GetVelocityKNOTS() +self:F2(self.PositionableName) +return UTILS.MpsToKnots(self:GetVelocityMPS()) +end +function POSITIONABLE:GetAirspeedTrue() +local tas=0 +local coord=self:GetCoord() +if coord then +local alt=coord.y +local wvec3=coord:GetWindVec3(alt,false) +local vvec3=self:GetVelocityVec3() +local tasvec3=UTILS.VecSubstract(vvec3,wvec3) +tas=UTILS.VecNorm(tasvec3) +end +return tas +end +function POSITIONABLE:GetAirspeedIndicated(oatcorr) +local tas=self:GetAirspeedTrue() +local altitude=self:GetAltitude() +local ias=UTILS.TasToIas(tas,altitude,oatcorr) +return ias +end +function POSITIONABLE:GetGroundSpeed() +local gs=0 +local vel=self:GetVelocityVec3() +if vel then +local vec2={x=vel.x,y=vel.z} +gs=UTILS.Vec2Norm(vel) +end +return gs +end +function POSITIONABLE:GetAoA() +local unitpos=self:GetPosition() +if unitpos then +local unitvel=self:GetVelocityVec3() +if unitvel and UTILS.VecNorm(unitvel)~=0 then +local wind=self:GetCoordinate():GetWindWithTurbulenceVec3() +unitvel.x=unitvel.x-wind.x +unitvel.y=unitvel.y-wind.y +unitvel.z=unitvel.z-wind.z +local AxialVel={} +AxialVel.x=UTILS.VecDot(unitpos.x,unitvel) +AxialVel.y=UTILS.VecDot(unitpos.y,unitvel) +AxialVel.z=UTILS.VecDot(unitpos.z,unitvel) +local AoA=math.acos(UTILS.VecDot({x=1,y=0,z=0},{x=AxialVel.x,y=AxialVel.y,z=0})/UTILS.VecNorm({x=AxialVel.x,y=AxialVel.y,z=0})) +if AxialVel.y>0 then +AoA=-AoA +end +return math.deg(AoA) +end +end +return nil +end +function POSITIONABLE:GetClimbAngle() +local unitpos=self:GetPosition() +if unitpos then +local unitvel=self:GetVelocityVec3() +if unitvel and UTILS.VecNorm(unitvel)~=0 then +local angle=math.asin(unitvel.y/UTILS.VecNorm(unitvel)) +return math.deg(angle) +else +return 0 +end +end +return nil +end +function POSITIONABLE:GetPitch() +local unitpos=self:GetPosition() +if unitpos then +return math.deg(math.asin(unitpos.x.y)) +end +return nil +end +function POSITIONABLE:GetRoll() +local unitpos=self:GetPosition() +if unitpos then +local cp=UTILS.VecCross(unitpos.x,{x=0,y=1,z=0}) +local dp=UTILS.VecDot(cp,unitpos.z) +local Roll=math.acos(dp/(UTILS.VecNorm(cp)*UTILS.VecNorm(unitpos.z))) +if unitpos.z.y>0 then +Roll=-Roll +end +return math.deg(Roll) +end +return nil +end +function POSITIONABLE:GetYaw() +local unitpos=self:GetPosition() +if unitpos then +local unitvel=self:GetVelocityVec3() +if unitvel and UTILS.VecNorm(unitvel)~=0 then +local AxialVel={} +AxialVel.x=UTILS.VecDot(unitpos.x,unitvel) +AxialVel.y=UTILS.VecDot(unitpos.y,unitvel) +AxialVel.z=UTILS.VecDot(unitpos.z,unitvel) +local Yaw=math.acos(UTILS.VecDot({x=1,y=0,z=0},{x=AxialVel.x,y=0,z=AxialVel.z})/UTILS.VecNorm({x=AxialVel.x,y=0,z=AxialVel.z})) +if AxialVel.z>0 then +Yaw=-Yaw +end +return Yaw +end +end +return nil +end +function POSITIONABLE:GetMessageText(Message,Name) +local DCSObject=self:GetDCSObject() +if DCSObject then +local Callsign=string.format("%s",((Name~=""and Name)or self:GetCallsign()~=""and self:GetCallsign())or self:GetName()) +local MessageText=string.format("%s - %s",Callsign,Message) +return MessageText +end +return nil +end +function POSITIONABLE:GetMessage(Message,Duration,Name) +local DCSObject=self:GetDCSObject() +if DCSObject then +local MessageText=self:GetMessageText(Message,Name) +return MESSAGE:New(MessageText,Duration) +end +return nil +end +function POSITIONABLE:GetMessageType(Message,MessageType,Name) +local DCSObject=self:GetDCSObject() +if DCSObject then +local MessageText=self:GetMessageText(Message,Name) +return MESSAGE:NewType(MessageText,MessageType) +end +return nil +end +function POSITIONABLE:MessageToAll(Message,Duration,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +self:GetMessage(Message,Duration,Name):ToAll() +end +return nil +end +function POSITIONABLE:MessageToCoalition(Message,Duration,MessageCoalition,Name) +self:F2({Message,Duration}) +local Name=Name or"" +local DCSObject=self:GetDCSObject() +if DCSObject then +self:GetMessage(Message,Duration,Name):ToCoalition(MessageCoalition) +end +return nil +end +function POSITIONABLE:MessageTypeToCoalition(Message,MessageType,MessageCoalition,Name) +self:F2({Message,MessageType}) +local Name=Name or"" +local DCSObject=self:GetDCSObject() +if DCSObject then +self:GetMessageType(Message,MessageType,Name):ToCoalition(MessageCoalition) +end +return nil +end +function POSITIONABLE:MessageToRed(Message,Duration,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +self:GetMessage(Message,Duration,Name):ToRed() +end +return nil +end +function POSITIONABLE:MessageToBlue(Message,Duration,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +self:GetMessage(Message,Duration,Name):ToBlue() +end +return nil +end +function POSITIONABLE:MessageToClient(Message,Duration,Client,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +self:GetMessage(Message,Duration,Name):ToClient(Client) +end +return nil +end +function POSITIONABLE:MessageToUnit(Message,Duration,MessageUnit,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +if DCSObject:isExist()then +if MessageUnit:IsAlive()then +self:GetMessage(Message,Duration,Name):ToUnit(MessageUnit) +else +BASE:E({"Message not sent to Unit; Unit is not alive...",Message=Message,MessageUnit=MessageUnit}) +end +else +BASE:E({"Message not sent to Unit; Positionable is not alive ...",Message=Message,Positionable=self,MessageUnit=MessageUnit}) +end +end +end +function POSITIONABLE:MessageToGroup(Message,Duration,MessageGroup,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +if DCSObject:isExist()then +if MessageGroup:IsAlive()then +self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup) +else +BASE:E({"Message not sent to Group; Group is not alive...",Message=Message,MessageGroup=MessageGroup}) +end +else +BASE:E({ +"Message not sent to Group; Positionable is not alive ...", +Message=Message, +Positionable=self, +MessageGroup=MessageGroup +}) +end +end +return nil +end +function POSITIONABLE:MessageToUnit(Message,Duration,MessageUnit,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +if DCSObject:isExist()then +if MessageUnit:IsAlive()then +self:GetMessage(Message,Duration,Name):ToUnit(MessageUnit) +else +BASE:E({"Message not sent to Unit; Unit is not alive...",Message=Message,MessageUnit=MessageUnit}) +end +else +BASE:E({"Message not sent to Unit; Positionable is not alive ...",Message=Message,Positionable=self,MessageUnit=MessageUnit}) +end +end +return nil +end +function POSITIONABLE:MessageTypeToGroup(Message,MessageType,MessageGroup,Name) +self:F2({Message,MessageType}) +local DCSObject=self:GetDCSObject() +if DCSObject then +if DCSObject:isExist()then +self:GetMessageType(Message,MessageType,Name):ToGroup(MessageGroup) +end +end +return nil +end +function POSITIONABLE:MessageToSetGroup(Message,Duration,MessageSetGroup,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +if DCSObject:isExist()then +MessageSetGroup:ForEachGroupAlive(function(MessageGroup) +self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup) +end) +end +end +return nil +end +function POSITIONABLE:MessageToSetUnit(Message,Duration,MessageSetUnit,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +if DCSObject:isExist()then +MessageSetUnit:ForEachUnit( +function(MessageGroup) +self:GetMessage(Message,Duration,Name):ToUnit(MessageGroup) +end +) +end +end +return nil +end +function POSITIONABLE:MessageToSetUnit(Message,Duration,MessageSetUnit,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +if DCSObject:isExist()then +MessageSetUnit:ForEachUnit( +function(MessageGroup) +self:GetMessage(Message,Duration,Name):ToUnit(MessageGroup) +end +) +end +end +return nil +end +function POSITIONABLE:Message(Message,Duration,Name) +self:F2({Message,Duration}) +local DCSObject=self:GetDCSObject() +if DCSObject then +self:GetMessage(Message,Duration,Name):ToGroup(self) +end +return nil +end +function POSITIONABLE:GetRadio() +self:F2(self) +return RADIO:New(self) +end +function POSITIONABLE:GetBeacon() +self:F2(self) +return BEACON:New(self) +end +function POSITIONABLE:LaseUnit(Target,LaserCode,Duration) +self:F2() +LaserCode=LaserCode or math.random(1000,9999) +local RecceDcsUnit=self:GetDCSObject() +local TargetVec3=Target:GetVec3() +self:F("building spot") +self.Spot=SPOT:New(self) +self.Spot:LaseOn(Target,LaserCode,Duration) +self.LaserCode=LaserCode +return self.Spot +end +function POSITIONABLE:LaseCoordinate(Coordinate,LaserCode,Duration) +self:F2() +LaserCode=LaserCode or math.random(1000,9999) +self.Spot=SPOT:New(self) +self.Spot:LaseOnCoordinate(Coordinate,LaserCode,Duration) +self.LaserCode=LaserCode +return self.Spot +end +function POSITIONABLE:LaseOff() +self:F2() +if self.Spot then +self.Spot:LaseOff() +self.Spot=nil +end +return self +end +function POSITIONABLE:IsLasing() +self:F2() +local Lasing=false +if self.Spot then +Lasing=self.Spot:IsLasing() +end +return Lasing +end +function POSITIONABLE:GetSpot() +return self.Spot +end +function POSITIONABLE:GetLaserCode() +return self.LaserCode +end +do +function POSITIONABLE:AddCargo(Cargo) +self.__.Cargo[Cargo]=Cargo +return self +end +function POSITIONABLE:GetCargo() +return self.__.Cargo +end +function POSITIONABLE:RemoveCargo(Cargo) +self.__.Cargo[Cargo]=nil +return self +end +function POSITIONABLE:HasCargo(Cargo) +return self.__.Cargo[Cargo] +end +function POSITIONABLE:ClearCargo() +self.__.Cargo={} +end +function POSITIONABLE:IsCargoEmpty() +local IsEmpty=true +for _,Cargo in pairs(self.__.Cargo)do +IsEmpty=false +break +end +return IsEmpty +end +function POSITIONABLE:CargoItemCount() +local ItemCount=0 +for CargoName,Cargo in pairs(self.__.Cargo)do +ItemCount=ItemCount+Cargo:GetCount() +end +return ItemCount +end +function POSITIONABLE:GetTroopCapacity() +local DCSunit=self:GetDCSObject() +local capacity=DCSunit:getDescentCapacity() +return capacity +end +function POSITIONABLE:GetCargoBayFreeWeight() +if not self.__.CargoBayWeightLimit then +self:SetCargoBayWeightLimit() +end +local CargoWeight=0 +for CargoName,Cargo in pairs(self.__.Cargo)do +CargoWeight=CargoWeight+Cargo:GetWeight() +end +return self.__.CargoBayWeightLimit-CargoWeight +end +POSITIONABLE.DefaultInfantryWeight=95 +POSITIONABLE.CargoBayCapacityValues={ +["Air"]={ +["C_130"]=70000, +}, +["Naval"]={ +["Type_071"]=245000, +["LHA_Tarawa"]=500000, +["Ropucha_class"]=150000, +["Dry_cargo_ship_1"]=70000, +["Dry_cargo_ship_2"]=70000, +["Higgins_boat"]=3700, +["USS_Samuel_Chase"]=25000, +["LST_Mk2"]=2100000, +["speedboat"]=500, +["Seawise_Giant"]=261000000, +}, +["Ground"]={ +["AAV7"]=25*POSITIONABLE.DefaultInfantryWeight, +["Bedford_MWD"]=8*POSITIONABLE.DefaultInfantryWeight, +["Blitz_36_6700A"]=10*POSITIONABLE.DefaultInfantryWeight, +["BMD_1"]=9*POSITIONABLE.DefaultInfantryWeight, +["BMP_1"]=8*POSITIONABLE.DefaultInfantryWeight, +["BMP_2"]=7*POSITIONABLE.DefaultInfantryWeight, +["BMP_3"]=8*POSITIONABLE.DefaultInfantryWeight, +["Boman"]=25*POSITIONABLE.DefaultInfantryWeight, +["BTR_80"]=9*POSITIONABLE.DefaultInfantryWeight, +["BTR_82A"]=9*POSITIONABLE.DefaultInfantryWeight, +["BTR_D"]=12*POSITIONABLE.DefaultInfantryWeight, +["Cobra"]=8*POSITIONABLE.DefaultInfantryWeight, +["Land_Rover_101_FC"]=11*POSITIONABLE.DefaultInfantryWeight, +["Land_Rover_109_S3"]=7*POSITIONABLE.DefaultInfantryWeight, +["LAV_25"]=6*POSITIONABLE.DefaultInfantryWeight, +["M_2_Bradley"]=6*POSITIONABLE.DefaultInfantryWeight, +["M1043_HMMWV_Armament"]=4*POSITIONABLE.DefaultInfantryWeight, +["M1045_HMMWV_TOW"]=4*POSITIONABLE.DefaultInfantryWeight, +["M1126_Stryker_ICV"]=9*POSITIONABLE.DefaultInfantryWeight, +["M1134_Stryker_ATGM"]=9*POSITIONABLE.DefaultInfantryWeight, +["M2A1_halftrack"]=9*POSITIONABLE.DefaultInfantryWeight, +["M_113"]=9*POSITIONABLE.DefaultInfantryWeight, +["Marder"]=6*POSITIONABLE.DefaultInfantryWeight, +["MCV_80"]=9*POSITIONABLE.DefaultInfantryWeight, +["MLRS_FDDM"]=4*POSITIONABLE.DefaultInfantryWeight, +["MTLB"]=25*POSITIONABLE.DefaultInfantryWeight, +["GAZ_66"]=8*POSITIONABLE.DefaultInfantryWeight, +["GAZ_3307"]=12*POSITIONABLE.DefaultInfantryWeight, +["GAZ_3308"]=14*POSITIONABLE.DefaultInfantryWeight, +["Grad_FDDM"]=6*POSITIONABLE.DefaultInfantryWeight, +["KAMAZ_Truck"]=12*POSITIONABLE.DefaultInfantryWeight, +["KrAZ6322"]=12*POSITIONABLE.DefaultInfantryWeight, +["M_818"]=12*POSITIONABLE.DefaultInfantryWeight, +["Tigr_233036"]=6*POSITIONABLE.DefaultInfantryWeight, +["TPZ"]=10*POSITIONABLE.DefaultInfantryWeight, +["UAZ_469"]=4*POSITIONABLE.DefaultInfantryWeight, +["Ural_375"]=12*POSITIONABLE.DefaultInfantryWeight, +["Ural_4320_31"]=14*POSITIONABLE.DefaultInfantryWeight, +["Ural_4320_APA_5D"]=10*POSITIONABLE.DefaultInfantryWeight, +["Ural_4320T"]=14*POSITIONABLE.DefaultInfantryWeight, +["ZBD04A"]=7*POSITIONABLE.DefaultInfantryWeight, +["VAB_Mephisto"]=8*POSITIONABLE.DefaultInfantryWeight, +["tt_KORD"]=6*POSITIONABLE.DefaultInfantryWeight, +["tt_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, +["HL_KORD"]=6*POSITIONABLE.DefaultInfantryWeight, +["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, +["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, +["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +} +} +function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) +if WeightLimit then +self.__.CargoBayWeightLimit=WeightLimit +elseif self.__.CargoBayWeightLimit~=nil then +else +local Desc=self:GetDesc() +self:F({Desc=Desc}) +local TypeName=Desc.typeName or"Unknown Type" +TypeName=string.gsub(TypeName,"[%p%s]","_") +if self:IsAir()then +local Weights=POSITIONABLE.CargoBayCapacityValues.Air +local massMax=Desc.massMax or 0 +local maxTakeoff=Weights[TypeName] +if maxTakeoff then +massMax=maxTakeoff +end +local massEmpty=Desc.massEmpty or 0 +local massFuelMax=Desc.fuelMassMax or 0 +local relFuel=math.min(self:GetFuel()or 1.0,1.0) +local massFuel=massFuelMax*relFuel +local CargoWeight=massMax-(massEmpty+massFuel) +self:T(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuelMax=%d kg (rel=%.3f), fuel=%d kg",TypeName,CargoWeight,massMax,massEmpty,massFuelMax,relFuel,massFuel)) +self.__.CargoBayWeightLimit=CargoWeight +elseif self:IsShip()then +local Weights=POSITIONABLE.CargoBayCapacityValues.Naval +self.__.CargoBayWeightLimit=(Weights[TypeName]or 50000) +else +local Weights=POSITIONABLE.CargoBayCapacityValues.Ground +local CargoBayWeightLimit=(Weights[TypeName]or 0) +self.__.CargoBayWeightLimit=CargoBayWeightLimit +end +end +self:F({CargoBayWeightLimit=self.__.CargoBayWeightLimit}) +end +function POSITIONABLE:GetCargoBayWeightLimit() +if self.__.CargoBayWeightLimit==nil then +self:SetCargoBayWeightLimit() +end +return self.__.CargoBayWeightLimit +end +end +function POSITIONABLE:Flare(FlareColor) +self:F2() +trigger.action.signalFlare(self:GetVec3(),FlareColor,0) +end +function POSITIONABLE:FlareWhite() +self:F2() +trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.White,0) +end +function POSITIONABLE:FlareYellow() +self:F2() +trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Yellow,0) +end +function POSITIONABLE:FlareGreen() +self:F2() +trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Green,0) +end +function POSITIONABLE:FlareRed() +self:F2() +local Vec3=self:GetVec3() +if Vec3 then +trigger.action.signalFlare(Vec3,trigger.flareColor.Red,0) +end +end +function POSITIONABLE:Smoke(SmokeColor,Range,AddHeight) +self:F2() +if Range then +local Vec3=self:GetRandomVec3(Range) +Vec3.y=Vec3.y+AddHeight or 0 +trigger.action.smoke(Vec3,SmokeColor) +else +local Vec3=self:GetVec3() +Vec3.y=Vec3.y+AddHeight or 0 +trigger.action.smoke(self:GetVec3(),SmokeColor) +end +end +function POSITIONABLE:SmokeGreen() +self:F2() +trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Green) +end +function POSITIONABLE:SmokeRed() +self:F2() +trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Red) +end +function POSITIONABLE:SmokeWhite() +self:F2() +trigger.action.smoke(self:GetVec3(),trigger.smokeColor.White) +end +function POSITIONABLE:SmokeOrange() +self:F2() +trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Orange) +end +function POSITIONABLE:SmokeBlue() +self:F2() +trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Blue) +end +function POSITIONABLE:IsInZone(Zone) +self:F2({self.PositionableName,Zone}) +if self:IsAlive()then +local IsInZone=Zone:IsVec3InZone(self:GetVec3()) +return IsInZone +end +return false +end +function POSITIONABLE:IsNotInZone(Zone) +self:F2({self.PositionableName,Zone}) +if self:IsAlive()then +local IsNotInZone=not Zone:IsVec3InZone(self:GetVec3()) +return IsNotInZone +else +return false +end +end +CONTROLLABLE={ +ClassName="CONTROLLABLE", +ControllableName="", +WayPointFunctions={}, +} +function CONTROLLABLE:New(ControllableName) +local self=BASE:Inherit(self,POSITIONABLE:New(ControllableName)) +self.ControllableName=ControllableName +self.TaskScheduler=SCHEDULER:New(self) +return self +end +function CONTROLLABLE:_GetController() +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local ControllableController=DCSControllable:getController() +return ControllableController +end +return nil +end +function CONTROLLABLE:GetLife() +self:F2(self.ControllableName) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local UnitLife=0 +local Units=self:GetUnits() +if#Units==1 then +local Unit=Units[1] +UnitLife=Unit:GetLife() +else +local UnitLifeTotal=0 +for UnitID,Unit in pairs(Units)do +local Unit=Unit +UnitLifeTotal=UnitLifeTotal+Unit:GetLife() +end +UnitLife=UnitLifeTotal/#Units +end +return UnitLife +end +return nil +end +function CONTROLLABLE:GetLife0() +self:F2(self.ControllableName) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local UnitLife=0 +local Units=self:GetUnits() +if#Units==1 then +local Unit=Units[1] +UnitLife=Unit:GetLife0() +else +local UnitLifeTotal=0 +for UnitID,Unit in pairs(Units)do +local Unit=Unit +UnitLifeTotal=UnitLifeTotal+Unit:GetLife0() +end +UnitLife=UnitLifeTotal/#Units +end +return UnitLife +end +return nil +end +function CONTROLLABLE:GetFuelMin() +self:F(self.ControllableName) +return nil +end +function CONTROLLABLE:GetFuelAve() +self:F(self.ControllableName) +return nil +end +function CONTROLLABLE:GetFuel() +self:F(self.ControllableName) +return nil +end +function CONTROLLABLE:ClearTasks() +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +Controller:resetTask() +return self +end +return nil +end +function CONTROLLABLE:PopCurrentTask() +self:F2() +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +Controller:popTask() +return self +end +return nil +end +function CONTROLLABLE:PushTask(DCSTask,WaitTime) +self:F2() +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local DCSControllableName=self:GetName() +local function PushTask(Controller,DCSTask) +if self and self:IsAlive()then +local Controller=self:_GetController() +Controller:pushTask(DCSTask) +else +BASE:E({DCSControllableName.." is not alive anymore.",DCSTask=DCSTask}) +end +end +if not WaitTime or WaitTime==0 then +PushTask(self,DCSTask) +else +self.TaskScheduler:Schedule(self,PushTask,{DCSTask},WaitTime) +end +return self +end +return nil +end +function CONTROLLABLE:SetTask(DCSTask,WaitTime) +self:F({"SetTask",WaitTime,DCSTask=DCSTask}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local DCSControllableName=self:GetName() +self:T2("Controllable Name = "..DCSControllableName) +local function SetTask(Controller,DCSTask) +if self and self:IsAlive()then +local Controller=self:_GetController() +Controller:setTask(DCSTask) +self:T({ControllableName=self:GetName(),DCSTask=DCSTask}) +else +BASE:E({DCSControllableName.." is not alive anymore.",DCSTask=DCSTask}) +end +end +if not WaitTime or WaitTime==0 then +SetTask(self,DCSTask) +self:T({ControllableName=self:GetName(),DCSTask=DCSTask}) +else +self.TaskScheduler:Schedule(self,SetTask,{DCSTask},WaitTime) +end +return self +end +return nil +end +function CONTROLLABLE:HasTask() +local HasTaskResult=false +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +HasTaskResult=Controller:hasTask() +end +return HasTaskResult +end +function CONTROLLABLE:TaskCondition(time,userFlag,userFlagValue,condition,duration,lastWayPoint) +local DCSStopCondition={} +DCSStopCondition.time=time +DCSStopCondition.userFlag=userFlag +DCSStopCondition.userFlagValue=userFlagValue +DCSStopCondition.condition=condition +DCSStopCondition.duration=duration +DCSStopCondition.lastWayPoint=lastWayPoint +return DCSStopCondition +end +function CONTROLLABLE:TaskControlled(DCSTask,DCSStopCondition) +local DCSTaskControlled={ +id='ControlledTask', +params={ +task=DCSTask, +stopCondition=DCSStopCondition, +}, +} +return DCSTaskControlled +end +function CONTROLLABLE:TaskCombo(DCSTasks) +local DCSTaskCombo={ +id='ComboTask', +params={ +tasks=DCSTasks, +}, +} +return DCSTaskCombo +end +function CONTROLLABLE:TaskWrappedAction(DCSCommand,Index) +local DCSTaskWrappedAction={ +id="WrappedAction", +enabled=true, +number=Index or 1, +auto=false, +params={ +action=DCSCommand, +}, +} +return DCSTaskWrappedAction +end +function CONTROLLABLE:TaskEmptyTask() +local DCSTaskWrappedAction={ +["id"]="WrappedAction", +["params"]={ +["action"]={ +["id"]="Script", +["params"]={ +["command"]="", +}, +}, +}, +} +return DCSTaskWrappedAction +end +function CONTROLLABLE:SetTaskWaypoint(Waypoint,Task) +Waypoint.task=self:TaskCombo({Task}) +self:F({Waypoint.task}) +return Waypoint.task +end +function CONTROLLABLE:SetCommand(DCSCommand) +self:F2(DCSCommand) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +Controller:setCommand(DCSCommand) +return self +end +return nil +end +function CONTROLLABLE:CommandSwitchWayPoint(FromWayPoint,ToWayPoint) +self:F2({FromWayPoint,ToWayPoint}) +local CommandSwitchWayPoint={ +id='SwitchWaypoint', +params={ +fromWaypointIndex=FromWayPoint, +goToWaypointIndex=ToWayPoint, +}, +} +self:T3({CommandSwitchWayPoint}) +return CommandSwitchWayPoint +end +function CONTROLLABLE:CommandStopRoute(StopRoute) +self:F2({StopRoute}) +local CommandStopRoute={ +id='StopRoute', +params={ +value=StopRoute, +}, +} +self:T3({CommandStopRoute}) +return CommandStopRoute +end +function CONTROLLABLE:StartUncontrolled(delay) +if delay and delay>0 then +SCHEDULER:New(nil,CONTROLLABLE.StartUncontrolled,{self},delay) +else +self:SetCommand({id='Start',params={}}) +end +return self +end +function CONTROLLABLE:CommandActivateBeacon(Type,System,Frequency,UnitID,Channel,ModeChannel,AA,Callsign,Bearing,Delay) +AA=AA or self:IsAir() +UnitID=UnitID or self:GetID() +local CommandActivateBeacon={ +id="ActivateBeacon", +params={ +["type"]=Type, +["system"]=System, +["frequency"]=Frequency, +["unitId"]=UnitID, +["channel"]=Channel, +["modeChannel"]=ModeChannel, +["AA"]=AA, +["callsign"]=Callsign, +["bearing"]=Bearing, +}, +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandActivateBeacon,{self,Type,System,Frequency,UnitID,Channel,ModeChannel,AA,Callsign,Bearing},Delay) +else +self:SetCommand(CommandActivateBeacon) +end +return self +end +function CONTROLLABLE:CommandActivateACLS(UnitID,Name,Delay) +local CommandActivateACLS={ +id='ActivateACLS', +params={ +unitId=UnitID or self:GetID(), +name=Name or"ACL", +} +} +self:T({CommandActivateACLS}) +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandActivateACLS,{self,UnitID,Name},Delay) +else +local controller=self:_GetController() +controller:setCommand(CommandActivateACLS) +end +return self +end +function CONTROLLABLE:CommandDeactivateACLS(Delay) +local CommandDeactivateACLS={ +id='DeactivateACLS', +params={} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandDeactivateACLS,{self},Delay) +else +local controller=self:_GetController() +controller:setCommand(CommandDeactivateACLS) +end +return self +end +function CONTROLLABLE:CommandActivateICLS(Channel,UnitID,Callsign,Delay) +local CommandActivateICLS={ +id="ActivateICLS", +params={ +["type"]=BEACON.Type.ICLS, +["channel"]=Channel, +["unitId"]=UnitID or self:GetID(), +["callsign"]=Callsign, +}, +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandActivateICLS,{self,Channel,UnitID,Callsign},Delay) +else +self:SetCommand(CommandActivateICLS) +end +return self +end +function CONTROLLABLE:CommandActivateLink4(Frequency,UnitID,Callsign,Delay) +local freq=Frequency or 336 +local CommandActivateLink4={ +id="ActivateLink4", +params={ +["frequency"]=freq*1000000, +["unitId"]=UnitID or self:GetID(), +["name"]=Callsign or"LNK", +} +} +self:T({CommandActivateLink4}) +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandActivateLink4,{self,Frequency,UnitID,Callsign},Delay) +else +local controller=self:_GetController() +controller:setCommand(CommandActivateLink4) +end +return self +end +function CONTROLLABLE:CommandDeactivateBeacon(Delay) +local CommandDeactivateBeacon={id='DeactivateBeacon',params={}} +local CommandDeactivateBeacon={id='DeactivateBeacon',params={}} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandDeactivateBeacon,{self},Delay) +else +self:SetCommand(CommandDeactivateBeacon) +end +return self +end +function CONTROLLABLE:CommandDeactivateLink4(Delay) +local CommandDeactivateLink4={id='DeactivateLink4',params={}} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandDeactivateLink4,{self},Delay) +else +local controller=self:_GetController() +controller:setCommand(CommandDeactivateLink4) +end +return self +end +function CONTROLLABLE:CommandDeactivateICLS(Delay) +local CommandDeactivateICLS={id='DeactivateICLS',params={}} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandDeactivateICLS,{self},Delay) +else +self:SetCommand(CommandDeactivateICLS) +end +return self +end +function CONTROLLABLE:CommandSetCallsign(CallName,CallNumber,Delay) +local CommandSetCallsign={id='SetCallsign',params={callname=CallName,number=CallNumber or 1}} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSetCallsign,{self,CallName,CallNumber},Delay) +else +self:SetCommand(CommandSetCallsign) +end +return self +end +function CONTROLLABLE:CommandEPLRS(SwitchOnOff,Delay) +if SwitchOnOff==nil then +SwitchOnOff=true +end +local CommandEPLRS={ +id='EPLRS', +params={ +value=SwitchOnOff, +groupId=self:GetID(), +}, +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandEPLRS,{self,SwitchOnOff},Delay) +else +self:T(string.format("EPLRS=%s for controllable %s (id=%s)",tostring(SwitchOnOff),tostring(self:GetName()),tostring(self:GetID()))) +self:SetCommand(CommandEPLRS) +end +return self +end +function CONTROLLABLE:CommandSetUnlimitedFuel(OnOff,Delay) +local CommandSetFuel={ +id='SetUnlimitedFuel', +params={ +value=OnOff +} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSetUnlimitedFuel,{self,OnOff},Delay) +else +self:SetCommand(CommandSetFuel) +end +return self +end +function CONTROLLABLE:CommandSetFrequency(Frequency,Modulation,Power,Delay) +local CommandSetFrequency={ +id='SetFrequency', +params={ +frequency=Frequency*1000000, +modulation=Modulation or radio.modulation.AM, +power=Power or 10, +}, +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSetFrequency,{self,Frequency,Modulation,Power},Delay) +else +self:SetCommand(CommandSetFrequency) +end +return self +end +function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay) +local CommandSetFrequencyForUnit={ +id='SetFrequencyForUnit', +params={ +frequency=Frequency*1000000, +modulation=Modulation or radio.modulation.AM, +unitId=UnitID or self:GetID(), +power=Power or 10, +}, +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID},Delay) +else +self:SetCommand(CommandSetFrequencyForUnit) +end +return self +end +function CONTROLLABLE:CommandSmokeOnOff(OnOff,Delay) +local switch=(OnOff==nil)and true or OnOff +local command={ +id='SMOKE_ON_OFF', +params={ +value=switch +} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSmokeOnOff,{self,switch},Delay) +else +self:SetCommand(command) +end +return self +end +function CONTROLLABLE:CommandSmokeON(Delay) +local command={ +id='SMOKE_ON_OFF', +params={ +value=true +} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSmokeON,{self},Delay) +else +self:SetCommand(command) +end +return self +end +function CONTROLLABLE:CommandSmokeOFF(Delay) +local command={ +id='SMOKE_ON_OFF', +params={ +value=false +} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSmokeOFF,{self},Delay) +else +self:SetCommand(command) +end +return self +end +function CONTROLLABLE:TaskEPLRS(SwitchOnOff,idx) +if SwitchOnOff==nil then +SwitchOnOff=true +end +local CommandEPLRS={ +id='EPLRS', +params={ +value=SwitchOnOff, +groupId=self:GetID(), +}, +} +return self:TaskWrappedAction(CommandEPLRS,idx or 1) +end +function CONTROLLABLE:TaskAttackGroup(AttackGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack) +local DCSTask={id='AttackGroup', +params={ +groupId=AttackGroup:GetID(), +weaponType=WeaponType or 1073741822, +expend=WeaponExpend or"Auto", +attackQtyLimit=AttackQty and true or false, +attackQty=AttackQty or 1, +directionEnabled=Direction and true or false, +direction=Direction and math.rad(Direction)or 0, +altitudeEnabled=Altitude and true or false, +altitude=Altitude, +groupAttack=GroupAttack and true or false, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskAttackUnit(AttackUnit,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType) +local DCSTask={ +id='AttackUnit', +params={ +unitId=AttackUnit:GetID(), +groupAttack=GroupAttack and GroupAttack or false, +expend=WeaponExpend or"Auto", +directionEnabled=Direction and true or false, +direction=Direction and math.rad(Direction)or 0, +altitudeEnabled=Altitude and true or false, +altitude=Altitude, +attackQtyLimit=AttackQty and true or false, +attackQty=AttackQty, +weaponType=WeaponType or 1073741822, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType,Divebomb) +local DCSTask={ +id='Bombing', +params={ +point=Vec2, +x=Vec2.x, +y=Vec2.y, +groupAttack=GroupAttack and GroupAttack or false, +expend=WeaponExpend or"Auto", +attackQtyLimit=AttackQty and true or false, +attackQty=AttackQty or 1, +directionEnabled=Direction and true or false, +direction=Direction and math.rad(Direction)or 0, +altitudeEnabled=Altitude and true or false, +altitude=Altitude or 2000, +weaponType=WeaponType or 1073741822, +attackType=Divebomb and"Dive"or nil, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskStrafing(Vec2,AttackQty,Length,WeaponType,WeaponExpend,Direction,GroupAttack) +local DCSTask={ +id='Strafing', +params={ +point=Vec2, +weaponType=WeaponType or 805337088, +expend=WeaponExpend or"Auto", +attackQty=AttackQty or 1, +attackQtyLimit=AttackQty~=nil and true or false, +direction=Direction and math.rad(Direction)or 0, +directionEnabled=Direction and true or false, +groupAttack=GroupAttack or false, +length=Length, +} +} +return DCSTask +end +function CONTROLLABLE:TaskAttackMapObject(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType) +local DCSTask={ +id='AttackMapObject', +params={ +point=Vec2, +x=Vec2.x, +y=Vec2.y, +groupAttack=GroupAttack or false, +expend=WeaponExpend or"Auto", +attackQtyLimit=AttackQty and true or false, +directionEnabled=Direction and true or false, +direction=Direction and math.rad(Direction)or 0, +altitudeEnabled=Altitude and true or false, +altitude=Altitude, +weaponType=WeaponType or 1073741822, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskCarpetBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType,CarpetLength) +local DCSTask={ +id='CarpetBombing', +params={ +attackType="Carpet", +x=Vec2.x, +y=Vec2.y, +groupAttack=GroupAttack and GroupAttack or false, +carpetLength=CarpetLength or 500, +weaponType=WeaponType or ENUMS.WeaponFlag.AnyBomb, +expend=WeaponExpend or"All", +attackQtyLimit=AttackQty and true or false, +attackQty=AttackQty or 1, +directionEnabled=Direction and true or false, +direction=Direction and math.rad(Direction)or 0, +altitudeEnabled=Altitude and true or false, +altitude=Altitude, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskFollowBigFormation(FollowControllable,Vec3,LastWaypointIndex) +local DCSTask={ +id='FollowBigFormation', +params={ +groupId=FollowControllable:GetID(), +pos=Vec3, +lastWptIndexFlag=LastWaypointIndex and true or false, +lastWptIndex=LastWaypointIndex, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskEmbarking(Coordinate,GroupSetForEmbarking,Duration,Distribution) +local g4e={} +if GroupSetForEmbarking then +for _,_group in pairs(GroupSetForEmbarking:GetSet())do +local group=_group +table.insert(g4e,group:GetID()) +end +else +self:E("ERROR: No groups for embarking specified!") +return nil +end +local groupID=self and self:GetID() +local DCSTask={ +id='Embarking', +params={ +selectedTransport=groupID, +x=Coordinate.x, +y=Coordinate.z, +groupsForEmbarking=g4e, +durationFlag=Duration and true or false, +duration=Duration, +distributionFlag=Distribution and true or false, +distribution=Distribution, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskEmbarkToTransport(Coordinate,Radius,UnitType) +local EmbarkToTransport={ +id="EmbarkToTransport", +params={ +x=Coordinate.x, +y=Coordinate.z, +zoneRadius=Radius or 200, +selectedType=UnitType, +}, +} +return EmbarkToTransport +end +function CONTROLLABLE:TaskDisembarking(Coordinate,GroupSetToDisembark) +local g4e={} +if GroupSetToDisembark then +for _,_group in pairs(GroupSetToDisembark:GetSet())do +local group=_group +table.insert(g4e,group:GetID()) +end +else +self:E("ERROR: No groups for disembarking specified!") +return nil +end +local Disembarking={ +id="Disembarking", +params={ +x=Coordinate.x, +y=Coordinate.z, +groupsForEmbarking=g4e, +}, +} +return Disembarking +end +function CONTROLLABLE:TaskOrbitCircleAtVec2(Point,Altitude,Speed) +local DCSTask={ +id='Orbit', +params={ +pattern=AI.Task.OrbitPattern.CIRCLE, +point=Point, +speed=Speed, +altitude=Altitude+land.getHeight(Point), +}, +} +return DCSTask +end +function CONTROLLABLE:TaskOrbit(Coord,Altitude,Speed,CoordRaceTrack) +local Pattern=AI.Task.OrbitPattern.CIRCLE +local P1={x=Coord.x,y=Coord.z or Coord.y} +local P2=nil +if CoordRaceTrack then +Pattern=AI.Task.OrbitPattern.RACE_TRACK +P2={x=CoordRaceTrack.x,y=CoordRaceTrack.z or CoordRaceTrack.y} +end +local Task={ +id='Orbit', +params={ +pattern=Pattern, +point=P1, +point2=P2, +speed=Speed or UTILS.KnotsToMps(250), +altitude=Altitude or Coord.y, +}, +} +return Task +end +function CONTROLLABLE:TaskOrbitCircle(Altitude,Speed,Coordinate) +self:F2({self.ControllableName,Altitude,Speed}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local OrbitVec2=Coordinate and Coordinate:GetVec2()or self:GetVec2() +return self:TaskOrbitCircleAtVec2(OrbitVec2,Altitude,Speed) +end +return nil +end +function CONTROLLABLE:TaskHoldPosition() +self:F2({self.ControllableName}) +return self:TaskOrbitCircle(30,10) +end +function CONTROLLABLE:TaskBombingRunway(Airbase,WeaponType,WeaponExpend,AttackQty,Direction,GroupAttack) +local DCSTask={ +id='BombingRunway', +params={ +runwayId=Airbase:GetID(), +weaponType=WeaponType or ENUMS.WeaponFlag.AnyBomb, +expend=WeaponExpend or AI.Task.WeaponExpend.ALL, +attackQty=AttackQty or 1, +direction=Direction and math.rad(Direction)or 0, +groupAttack=GroupAttack and true or false, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskRefueling() +local DCSTask={ +id='Refueling', +params={}, +} +return DCSTask +end +function CONTROLLABLE:TaskRecoveryTanker(CarrierGroup,Speed,Altitude,LastWptNumber) +local LastWptFlag=type(LastWptNumber)=="number"and true or false +local DCSTask={ +id="RecoveryTanker", +params={ +groupId=CarrierGroup:GetID(), +speed=Speed, +altitude=Altitude, +lastWptIndexFlag=LastWptFlag, +lastWptIndex=LastWptNumber +} +} +return DCSTask +end +function CONTROLLABLE:TaskLandAtVec2(Vec2,Duration,CombatLanding,DirectionAfterLand) +local DCSTask={ +id='Land', +params={ +point=Vec2, +durationFlag=Duration and true or false, +duration=Duration, +combatLandingFlag=CombatLanding==true and true or false, +}, +} +if DirectionAfterLand~=nil and type(DirectionAfterLand)=="number"then +DCSTask.params.directionEnabled=true +DCSTask.params.direction=math.rad(DirectionAfterLand) +end +return DCSTask +end +function CONTROLLABLE:TaskLandAtZone(Zone,Duration,RandomPoint,CombatLanding,DirectionAfterLand) +local Point=RandomPoint and Zone:GetRandomVec2()or Zone:GetVec2() +local DCSTask=CONTROLLABLE.TaskLandAtVec2(self,Point,Duration,CombatLanding,DirectionAfterLand) +return DCSTask +end +function CONTROLLABLE:TaskFollow(FollowControllable,Vec3,LastWaypointIndex) +self:F2({self.ControllableName,FollowControllable,Vec3,LastWaypointIndex}) +local LastWaypointIndexFlag=false +local lastWptIndexFlagChangedManually=false +if LastWaypointIndex then +LastWaypointIndexFlag=true +lastWptIndexFlagChangedManually=true +end +local DCSTask={ +id='Follow', +params={ +groupId=FollowControllable:GetID(), +pos=Vec3, +lastWptIndexFlag=LastWaypointIndexFlag, +lastWptIndex=LastWaypointIndex, +lastWptIndexFlagChangedManually=lastWptIndexFlagChangedManually, +}, +} +self:T3({DCSTask}) +return DCSTask +end +function CONTROLLABLE:TaskGroundEscort(FollowControllable,LastWaypointIndex,OrbitDistance,TargetTypes) +local DCSTask={ +id='GroundEscort', +params={ +groupId=FollowControllable and FollowControllable:GetID()or nil, +engagementDistMax=OrbitDistance or 2000, +lastWptIndexFlag=LastWaypointIndex and true or false, +lastWptIndex=LastWaypointIndex, +targetTypes=TargetTypes or{"Ground vehicles"}, +lastWptIndexFlagChangedManually=true, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskEscort(FollowControllable,Vec3,LastWaypointIndex,EngagementDistance,TargetTypes) +local DCSTask={ +id='Escort', +params={ +groupId=FollowControllable and FollowControllable:GetID()or nil, +pos=Vec3, +lastWptIndexFlag=LastWaypointIndex and true or false, +lastWptIndex=LastWaypointIndex, +engagementDistMax=EngagementDistance, +targetTypes=TargetTypes or{"Air"}, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskFireAtPoint(Vec2,Radius,AmmoCount,WeaponType,Altitude,ASL) +local DCSTask={ +id='FireAtPoint', +params={ +point=Vec2, +x=Vec2.x, +y=Vec2.y, +zoneRadius=Radius, +radius=Radius, +expendQty=1, +expendQtyEnabled=false, +alt_type=ASL and 0 or 1, +}, +} +if AmmoCount then +DCSTask.params.expendQty=AmmoCount +DCSTask.params.expendQtyEnabled=true +end +if Altitude then +DCSTask.params.altitude=Altitude +end +if WeaponType then +DCSTask.params.weaponType=WeaponType +end +return DCSTask +end +function CONTROLLABLE:TaskHold() +local DCSTask={id='Hold',params={}} +return DCSTask +end +function CONTROLLABLE:TaskFAC_AttackGroup(AttackGroup,WeaponType,Designation,Datalink,Frequency,Modulation,CallsignName,CallsignNumber) +local DCSTask={ +id='FAC_AttackGroup', +params={ +groupId=AttackGroup:GetID(), +weaponType=WeaponType or ENUMS.WeaponFlag.AutoDCS, +designation=Designation or"Auto", +datalink=Datalink and Datalink or true, +frequency=(Frequency or 133)*1000000, +modulation=Modulation or radio.modulation.AM, +callname=CallsignName, +number=CallsignNumber, +}, +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskEngageTargets(Distance,TargetTypes,Priority) +local DCSTask={ +id='EngageTargets', +params={ +maxDistEnabled=Distance and true or false, +maxDist=Distance, +targetTypes=TargetTypes or{"Air"}, +priority=Priority or 0, +}, +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskEngageTargetsInZone(Vec2,Radius,TargetTypes,Priority) +local DCSTask={ +id='EngageTargetsInZone', +params={ +point=Vec2, +zoneRadius=Radius, +targetTypes=TargetTypes or{"Air"}, +priority=Priority or 0 +}, +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes,Priority) +local DCSTask={ +id='EngageTargets', +key="AntiShip", +params={ +targetTypes=TargetTypes or{"Ships"}, +priority=Priority or 0 +} +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskSEAD(TargetTypes,Priority) +local DCSTask={ +id='EngageTargets', +key="SEAD", +params={ +targetTypes=TargetTypes or{"Air Defence"}, +priority=Priority or 0 +} +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskCAP(TargetTypes,Priority) +local DCSTask={ +id='EngageTargets', +key="CAP", +enabled=true, +params={ +targetTypes=TargetTypes or{"Air"}, +priority=Priority or 0 +} +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskEngageGroup(AttackGroup,Priority,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit) +local DCSTask={ +id='EngageGroup', +params={ +groupId=AttackGroup:GetID(), +weaponType=WeaponType, +expend=WeaponExpend or"Auto", +directionEnabled=Direction and true or false, +direction=Direction, +altitudeEnabled=Altitude and true or false, +altitude=Altitude, +attackQtyLimit=AttackQty and true or false, +attackQty=AttackQty, +priority=Priority or 1, +}, +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskEngageUnit(EngageUnit,Priority,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,ControllableAttack) +local DCSTask={ +id='EngageUnit', +params={ +unitId=EngageUnit:GetID(), +priority=Priority or 1, +groupAttack=GroupAttack and GroupAttack or false, +visible=Visible and Visible or false, +expend=WeaponExpend or"Auto", +directionEnabled=Direction and true or false, +direction=Direction and math.rad(Direction)or nil, +altitudeEnabled=Altitude and true or false, +altitude=Altitude, +attackQtyLimit=AttackQty and true or false, +attackQty=AttackQty, +controllableAttack=ControllableAttack, +}, +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskAWACS() +local DCSTask={ +id='AWACS', +params={}, +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskTanker() +local DCSTask={ +id='Tanker', +params={}, +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskEWR() +local DCSTask={ +id='EWR', +params={}, +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskFAC_EngageGroup(AttackGroup,Priority,WeaponType,Designation,Datalink,Frequency,Modulation,CallsignID,CallsignNumber) +local DCSTask={ +id='FAC_EngageGroup', +params={ +groupId=AttackGroup:GetID(), +weaponType=WeaponType or"Auto", +designation=Designation, +datalink=Datalink and Datalink or false, +frequency=(Frequency or 133)*1000000, +modulation=Modulation or radio.modulation.AM, +callname=CallsignID, +number=CallsignNumber, +priority=Priority or 0, +}, +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskFAC(Frequency,Modulation,CallsignID,CallsignNumber,Priority) +local DCSTask={ +id='FAC', +params={ +frequency=(Frequency or 133)*1000000, +modulation=Modulation or radio.modulation.AM, +callname=CallsignID, +number=CallsignNumber, +priority=Priority or 0 +} +} +return DCSTask +end +function CONTROLLABLE:TaskFunction(FunctionString,...) +local DCSScript={} +DCSScript[#DCSScript+1]="local MissionControllable = GROUP:Find( ... ) " +if arg and arg.n>0 then +local ArgumentKey='_'..tostring(arg):match("table: (.*)") +self:SetState(self,ArgumentKey,arg) +DCSScript[#DCSScript+1]="local Arguments = MissionControllable:GetState( MissionControllable, '"..ArgumentKey.."' ) " +DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable, unpack( Arguments ) )" +else +DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable )" +end +local DCSTask=self:TaskWrappedAction(self:CommandDoScript(table.concat(DCSScript))) +return DCSTask +end +function CONTROLLABLE:TaskMission(TaskMission) +local DCSTask={ +id='Mission', +params={ +TaskMission, +}, +} +return DCSTask +end +do +function CONTROLLABLE:PatrolRoute() +local PatrolGroup=self +if not self:IsInstanceOf("GROUP")then +PatrolGroup=self:GetGroup() +end +self:F({PatrolGroup=PatrolGroup:GetName()}) +if PatrolGroup:IsGround()or PatrolGroup:IsShip()then +local Waypoints=PatrolGroup:GetTemplateRoutePoints() +local FromCoord=PatrolGroup:GetCoordinate() +local depth=0 +local IsSub=false +if PatrolGroup:IsShip()then +local navalvec3=FromCoord:GetVec3() +if navalvec3.y<0 then +depth=navalvec3.y +IsSub=true +end +end +local Waypoint=Waypoints[1] +local Speed=Waypoint.speed or(20/3.6) +local From=FromCoord:WaypointGround(Speed) +if IsSub then +From=FromCoord:WaypointNaval(Speed,Waypoint.alt) +end +table.insert(Waypoints,1,From) +local TaskRoute=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRoute") +self:F({Waypoints=Waypoints}) +local Waypoint=Waypoints[#Waypoints] +PatrolGroup:SetTaskWaypoint(Waypoint,TaskRoute) +PatrolGroup:Route(Waypoints,2) +end +end +function CONTROLLABLE:PatrolRouteRandom(Speed,Formation,ToWaypoint) +local PatrolGroup=self +if not self:IsInstanceOf("GROUP")then +PatrolGroup=self:GetGroup() +end +self:F({PatrolGroup=PatrolGroup:GetName()}) +if PatrolGroup:IsGround()or PatrolGroup:IsShip()then +local Waypoints=PatrolGroup:GetTemplateRoutePoints() +local FromCoord=PatrolGroup:GetCoordinate() +local FromWaypoint=1 +if ToWaypoint then +FromWaypoint=ToWaypoint +end +local depth=0 +local IsSub=false +if PatrolGroup:IsShip()then +local navalvec3=FromCoord:GetVec3() +if navalvec3.y<0 then +depth=navalvec3.y +IsSub=true +end +end +local ToWaypoint +repeat +ToWaypoint=math.random(1,#Waypoints) +until(ToWaypoint~=FromWaypoint) +self:F({FromWaypoint=FromWaypoint,ToWaypoint=ToWaypoint}) +local Waypoint=Waypoints[ToWaypoint] +local ToCoord=COORDINATE:NewFromVec2({x=Waypoint.x,y=Waypoint.y}) +local Route={} +if IsSub then +Route[#Route+1]=FromCoord:WaypointNaval(Speed,depth) +Route[#Route+1]=ToCoord:WaypointNaval(Speed,Waypoint.alt) +else +Route[#Route+1]=FromCoord:WaypointGround(Speed,Formation) +Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation) +end +local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRouteRandom",Speed,Formation,ToWaypoint) +PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone) +PatrolGroup:Route(Route,1) +end +end +function CONTROLLABLE:PatrolZones(ZoneList,Speed,Formation,DelayMin,DelayMax) +if type(ZoneList)~="table"then +ZoneList={ZoneList} +end +local PatrolGroup=self +if not self:IsInstanceOf("GROUP")then +PatrolGroup=self:GetGroup() +end +DelayMin=DelayMin or 1 +if not DelayMax or DelayMaxLengthDirect*10)or(LengthRoad/LengthOnRoad*100<5)) +self:T(string.format("Length on road = %.3f km",LengthOnRoad/1000)) +self:T(string.format("Length directly = %.3f km",LengthDirect/1000)) +self:T(string.format("Length fraction = %.3f km",LengthOnRoad/LengthDirect)) +self:T(string.format("Length only road = %.3f km",LengthRoad/1000)) +self:T(string.format("Length off road = %.3f km",LengthOffRoad/1000)) +self:T(string.format("Percent on road = %.1f",LengthRoad/LengthOnRoad*100)) +end +local route={} +local canroad=false +if GotPath and LengthRoad and LengthDirect>2000 then +if LongRoad and Shortcut then +table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation)) +table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation)) +else +table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation)) +table.insert(route,PathOnRoad[2]:WaypointGround(Speed,"On Road")) +table.insert(route,PathOnRoad[#PathOnRoad-1]:WaypointGround(Speed,"On Road")) +local dist=ToCoordinate:Get2DDistance(PathOnRoad[#PathOnRoad-1]) +if dist>10 then +table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation)) +table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation)) +table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation)) +end +end +canroad=true +else +table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation)) +table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation)) +end +if WaypointFunction then +local N=#route +for n,waypoint in pairs(route)do +waypoint.task={} +waypoint.task.id="ComboTask" +waypoint.task.params={} +waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))} +end +end +return route,canroad +end +function CONTROLLABLE:TaskGroundOnRailRoads(ToCoordinate,Speed,WaypointFunction,WaypointFunctionArguments) +self:F2({ToCoordinate=ToCoordinate,Speed=Speed}) +Speed=Speed or 20 +local FromCoordinate=self:GetCoordinate() +local PathOnRail,LengthOnRail=FromCoordinate:GetPathOnRoad(ToCoordinate,false,true) +self:T(string.format("Length on railroad = %.3f km",LengthOnRail/1000)) +local route={} +if PathOnRail then +table.insert(route,PathOnRail[1]:WaypointGround(Speed,"On Railroad")) +table.insert(route,PathOnRail[2]:WaypointGround(Speed,"On Railroad")) +end +if WaypointFunction then +local N=#route +for n,waypoint in pairs(route)do +waypoint.task={} +waypoint.task.id="ComboTask" +waypoint.task.params={} +waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))} +end +end +return route +end +function CONTROLLABLE.___PassingWaypoint(controllable,n,N,waypointfunction,...) +waypointfunction(controllable,n,N,...) +end +function CONTROLLABLE:RouteAirTo(ToCoordinate,AltType,Type,Action,Speed,DelaySeconds) +local FromCoordinate=self:GetCoordinate() +local FromWP=FromCoordinate:WaypointAir() +local ToWP=ToCoordinate:WaypointAir(AltType,Type,Action,Speed) +self:Route({FromWP,ToWP},DelaySeconds) +return self +end +function CONTROLLABLE:TaskRouteToZone(Zone,Randomize,Speed,Formation) +self:F2(Zone) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local ControllablePoint=self:GetVec2() +local PointFrom={} +PointFrom.x=ControllablePoint.x +PointFrom.y=ControllablePoint.y +PointFrom.type="Turning Point" +PointFrom.action=Formation or"Cone" +PointFrom.speed=20/3.6 +local PointTo={} +local ZonePoint +if Randomize then +ZonePoint=Zone:GetRandomVec2() +else +ZonePoint=Zone:GetVec2() +end +PointTo.x=ZonePoint.x +PointTo.y=ZonePoint.y +PointTo.type="Turning Point" +if Formation then +PointTo.action=Formation +else +PointTo.action="Cone" +end +if Speed then +PointTo.speed=Speed +else +PointTo.speed=20/3.6 +end +local Points={PointFrom,PointTo} +self:T3(Points) +self:Route(Points) +return self +end +return nil +end +function CONTROLLABLE:TaskRouteToVec2(Vec2,Speed,Formation) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local ControllablePoint=self:GetVec2() +local PointFrom={} +PointFrom.x=ControllablePoint.x +PointFrom.y=ControllablePoint.y +PointFrom.type="Turning Point" +PointFrom.action=Formation or"Cone" +PointFrom.speed=20/3.6 +local PointTo={} +PointTo.x=Vec2.x +PointTo.y=Vec2.y +PointTo.type="Turning Point" +if Formation then +PointTo.action=Formation +else +PointTo.action="Cone" +end +if Speed then +PointTo.speed=Speed +else +PointTo.speed=20/3.6 +end +local Points={PointFrom,PointTo} +self:T3(Points) +self:Route(Points) +return self +end +return nil +end +end +function CONTROLLABLE:CommandDoScript(DoScript) +local DCSDoScript={ +id="Script", +params={ +command=DoScript, +}, +} +self:T3(DCSDoScript) +return DCSDoScript +end +function CONTROLLABLE:GetTaskMission() +self:F2(self.ControllableName) +return UTILS.DeepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template) +end +function CONTROLLABLE:GetTaskRoute() +self:F2(self.ControllableName) +return UTILS.DeepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template.route.points) +end +function CONTROLLABLE:CopyRoute(Begin,End,Randomize,Radius) +self:F2({Begin,End}) +local Points={} +local ControllableName=string.match(self:GetName(),".*#") +if ControllableName then +ControllableName=ControllableName:sub(1,-2) +else +ControllableName=self:GetName() +end +self:T3({ControllableName}) +local Template=_DATABASE.Templates.Controllables[ControllableName].Template +if Template then +if not Begin then +Begin=0 +end +if not End then +End=0 +end +for TPointID=Begin+1,#Template.route.points-End do +if Template.route.points[TPointID]then +Points[#Points+1]=UTILS.DeepCopy(Template.route.points[TPointID]) +if Randomize then +if not Radius then +Radius=500 +end +Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius) +Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius) +end +end +end +return Points +else +error("Template not found for Controllable : "..ControllableName) +end +return nil +end +function CONTROLLABLE:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +self:F2(self.ControllableName) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil +local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTIC or nil +local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil +local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil +local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil +local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil +local Params={} +if DetectionVisual then +Params[#Params+1]=DetectionVisual +end +if DetectionOptical then +Params[#Params+1]=DetectionOptical +end +if DetectionRadar then +Params[#Params+1]=DetectionRadar +end +if DetectionIRST then +Params[#Params+1]=DetectionIRST +end +if DetectionRWR then +Params[#Params+1]=DetectionRWR +end +if DetectionDLINK then +Params[#Params+1]=DetectionDLINK +end +self:T2({DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK}) +return self:_GetController():getDetectedTargets(Params[1],Params[2],Params[3],Params[4],Params[5],Params[6]) +end +return nil +end +function CONTROLLABLE:IsTargetDetected(DCSObject,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +self:F2(self.ControllableName) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil +local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTIC or nil +local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil +local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil +local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil +local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil +local Controller=self:_GetController() +local TargetIsDetected,TargetIsVisible,TargetKnowType,TargetKnowDistance,TargetLastTime,TargetLastPos,TargetLastVelocity +=Controller:isTargetDetected(DCSObject,DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK) +return TargetIsDetected,TargetIsVisible,TargetKnowType,TargetKnowDistance,TargetLastTime,TargetLastPos,TargetLastVelocity +end +return nil +end +function CONTROLLABLE:IsUnitDetected(Unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +self:F2(self.ControllableName) +if Unit and Unit:IsAlive()then +return self:IsTargetDetected(Unit:GetDCSObject(),DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +end +return nil +end +function CONTROLLABLE:IsGroupDetected(Group,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +self:F2(self.ControllableName) +if Group and Group:IsAlive()then +for _,_unit in pairs(Group:GetUnits())do +local unit=_unit +if unit and unit:IsAlive()then +local isdetected=self:IsUnitDetected(unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +if isdetected then +return true +end +end +end +return false +end +return nil +end +function CONTROLLABLE:GetDetectedUnitSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local unitset=SET_UNIT:New() +for DetectionObjectID,Detection in pairs(detectedtargets or{})do +local DetectedObject=Detection.object +if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then +local unit=UNIT:Find(DetectedObject) +if unit and unit:IsAlive()then +if not unitset:FindUnit(unit:GetName())then +unitset:AddUnit(unit) +end +end +end +end +return unitset +end +function CONTROLLABLE:GetDetectedGroupSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local groupset=SET_GROUP:New() +for DetectionObjectID,Detection in pairs(detectedtargets or{})do +local DetectedObject=Detection.object +if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then +local unit=UNIT:Find(DetectedObject) +if unit and unit:IsAlive()then +local group=unit:GetGroup() +if group and not groupset:FindGroup(group:GetName())then +groupset:AddGroup(group) +end +end +end +end +return groupset +end +function CONTROLLABLE:SetOption(OptionID,OptionValue) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +Controller:setOption(OptionID,OptionValue) +return self +end +return nil +end +function CONTROLLABLE:OptionROE(ROEvalue) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,ROEvalue) +elseif self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ROE,ROEvalue) +elseif self:IsShip()then +Controller:setOption(AI.Option.Naval.id.ROE,ROEvalue) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEHoldFirePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()or self:IsGround()or self:IsShip()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEHoldFire() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD) +elseif self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.WEAPON_HOLD) +elseif self:IsShip()then +Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.WEAPON_HOLD) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEReturnFirePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()or self:IsGround()or self:IsShip()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEReturnFire() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.RETURN_FIRE) +elseif self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.RETURN_FIRE) +elseif self:IsShip()then +Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.RETURN_FIRE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEOpenFirePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()or self:IsGround()or self:IsShip()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEOpenFire() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE) +elseif self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.OPEN_FIRE) +elseif self:IsShip()then +Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.OPEN_FIRE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEOpenFireWeaponFreePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEOpenFireWeaponFree() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEWeaponFreePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEWeaponFree() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_FREE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROTNoReactionPossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROTNoReaction() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROT(ROTvalue) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,ROTvalue) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROTPassiveDefensePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROTPassiveDefense() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionPreferVerticalLanding() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.PREFER_VERTICAL,true) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROTEvadeFirePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROTEvadeFire() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROTVerticalPossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROTVertical() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionAlarmStateAuto() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.AUTO) +elseif self:IsShip()then +Controller:setOption(9,0) +end +return self +end +return nil +end +function CONTROLLABLE:OptionAlarmStateGreen() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) +elseif self:IsShip()then +Controller:setOption(9,1) +end +return self +end +return nil +end +function CONTROLLABLE:OptionAlarmStateRed() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) +elseif self:IsShip()then +Controller:setOption(9,2) +end +return self +end +return nil +end +function CONTROLLABLE:OptionRTBBingoFuel(RTB) +self:F2({self.ControllableName}) +if RTB==nil then +RTB=true +end +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.RTB_ON_BINGO,RTB) +end +return self +end +return nil +end +function CONTROLLABLE:OptionRTBAmmo(WeaponsFlag) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.RTB_ON_OUT_OF_AMMO,WeaponsFlag) +end +return self +end +return nil +end +function CONTROLLABLE:OptionAllowJettisonWeaponsOnThreat() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,false) +end +return self +end +return nil +end +function CONTROLLABLE:OptionKeepWeaponsOnThreat() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,true) +end +return self +end +return nil +end +function CONTROLLABLE:OptionProhibitAfterburner(Prohibit) +self:F2({self.ControllableName}) +if Prohibit==nil then +Prohibit=true +end +if self:IsAir()then +self:SetOption(AI.Option.Air.id.PROHIBIT_AB,Prohibit) +end +return self +end +function CONTROLLABLE:OptionEvasionOfARM(Seconds) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +if Seconds==nil then Seconds=false end +Controller:setOption(AI.Option.Ground.id.EVASION_OF_ARM,Seconds) +end +end +return self +end +function CONTROLLABLE:OptionFormationInterval(meters) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +if meters==nil or meters>100 or meters<0 then meters=50 end +Controller:setOption(30,meters) +end +end +return self +end +function CONTROLLABLE:OptionECM(ECMvalue) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ECM_USING,ECMvalue or 1) +end +end +return self +end +function CONTROLLABLE:OptionECM_Never() +self:F2({self.ControllableName}) +self:OptionECM(0) +return self +end +function CONTROLLABLE:OptionECM_OnlyLockByRadar() +self:F2({self.ControllableName}) +self:OptionECM(1) +return self +end +function CONTROLLABLE:OptionECM_DetectedLockByRadar() +self:F2({self.ControllableName}) +self:OptionECM(2) +return self +end +function CONTROLLABLE:OptionECM_AlwaysOn() +self:F2({self.ControllableName}) +self:OptionECM(3) +return self +end +function CONTROLLABLE:WayPointInitialize(WayPoints) +self:F({WayPoints}) +if WayPoints then +self.WayPoints=WayPoints +else +self.WayPoints=self:GetTaskRoute() +end +return self +end +function CONTROLLABLE:GetWayPoints() +self:F() +if self.WayPoints then +return self.WayPoints +end +return nil +end +function CONTROLLABLE:WayPointFunction(WayPoint,WayPointIndex,WayPointFunction,...) +self:F2({WayPoint,WayPointIndex,WayPointFunction}) +if not self.WayPoints then +self:WayPointInitialize() +end +table.insert(self.WayPoints[WayPoint].task.params.tasks,WayPointIndex) +self.WayPoints[WayPoint].task.params.tasks[WayPointIndex]=self:TaskFunction(WayPointFunction,arg) +return self +end +function CONTROLLABLE:WayPointExecute(WayPoint,WaitTime) +self:F({WayPoint,WaitTime}) +if not WayPoint then +WayPoint=1 +end +for TaskPointID=1,WayPoint-1 do +table.remove(self.WayPoints,1) +end +self:T3(self.WayPoints) +self:SetTask(self:TaskRoute(self.WayPoints),WaitTime) +return self +end +function CONTROLLABLE:IsAirPlane() +self:F2() +local DCSObject=self:GetDCSObject() +if DCSObject then +local Category=DCSObject:getDesc().category +return Category==Unit.Category.AIRPLANE +end +return nil +end +function CONTROLLABLE:IsHelicopter() +self:F2() +local DCSObject=self:GetDCSObject() +if DCSObject then +local Category=DCSObject:getDesc().category +return Category==Unit.Category.HELICOPTER +end +return nil +end +function CONTROLLABLE:OptionRestrictBurner(RestrictBurner) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if RestrictBurner==true then +if self:IsAir()then +Controller:setOption(16,true) +end +else +if self:IsAir()then +Controller:setOption(16,false) +end +end +end +end +end +function CONTROLLABLE:OptionAAAttackRange(range) +self:F2({self.ControllableName}) +local range=range or 3 +if range<0 or range>4 then +range=3 +end +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsAir()then +self:SetOption(AI.Option.Air.id.MISSILE_ATTACK,range) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionEngageRange(EngageRange) +self:F2({self.ControllableName}) +EngageRange=EngageRange or 100 +if EngageRange<0 or EngageRange>100 then +EngageRange=100 +end +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,EngageRange) +end +end +return self +end +return nil +end +function CONTROLLABLE:SetOptionLandingStraightIn() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption("36","0") +end +return self +end +function CONTROLLABLE:SetOptionLandingForcePair() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption("36","1") +end +return self +end +function CONTROLLABLE:SetOptionLandingRestrictPair() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption("36","2") +end +return self +end +function CONTROLLABLE:SetOptionLandingOverheadBreak() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption("36","3") +end +return self +end +function CONTROLLABLE:SetOptionRadarUsing(Option) +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.RADAR_USING,Option) +end +return self +end +function CONTROLLABLE:SetOptionRadarUsingNever() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.RADAR_USING,0) +end +return self +end +function CONTROLLABLE:SetOptionRadarUsingForAttackOnly() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.RADAR_USING,1) +end +return self +end +function CONTROLLABLE:SetOptionRadarUsingForSearchIfRequired() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.RADAR_USING,2) +end +return self +end +function CONTROLLABLE:SetOptionRadarUsingForContinousSearch() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.RADAR_USING,3) +end +return self +end +function CONTROLLABLE:SetOptionWaypointPassReport(OnOff) +self:F2({self.ControllableName}) +local onoff=(OnOff==nil or OnOff==true)and false or true +if self:IsAir()then +self:SetOption(AI.Option.Air.id.PROHIBIT_WP_PASS_REPORT,onoff) +end +return self +end +function CONTROLLABLE:SetOptionRadioSilence(OnOff) +local onoff=(OnOff==true or OnOff==nil)and true or false +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.SILENCE,onoff) +end +return self +end +function CONTROLLABLE:SetOptionRadioContact(Objects) +self:F2({self.ControllableName}) +if not Objects then Objects={"Air"}end +if type(Objects)~="table"then Objects={Objects}end +if self:IsAir()then +self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_CONTACT,Objects) +end +return self +end +function CONTROLLABLE:SetOptionRadioEngage(Objects) +self:F2({self.ControllableName}) +if not Objects then Objects={"Air"}end +if type(Objects)~="table"then Objects={Objects}end +if self:IsAir()then +self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_ENGAGE,Objects) +end +return self +end +function CONTROLLABLE:SetOptionRadioKill(Objects) +self:F2({self.ControllableName}) +if not Objects then Objects={"Air"}end +if type(Objects)~="table"then Objects={Objects}end +if self:IsAir()then +self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_KILL,Objects) +end +return self +end +function CONTROLLABLE:RelocateGroundRandomInRadius(speed,radius,onroad,shortcut,formation,onland) +self:F2({self.ControllableName}) +local _coord=self:GetCoordinate() +if not _coord then +return self +end +local _radius=radius or 500 +local _speed=speed or 20 +local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100) +if onland then +for i=1,50 do +local island=_tocoord:GetSurfaceType()==land.SurfaceType.LAND and true or false +if island then break end +_tocoord=_coord:GetRandomCoordinateInRadius(_radius,100) +end +end +local _onroad=onroad or true +local _grptsk={} +local _candoroad=false +local _shortcut=shortcut or false +local _formation=formation or"Off Road" +if onroad then +_grptsk,_candoroad=self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut) +self:Route(_grptsk,5) +else +self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation) +end +return self +end +function CONTROLLABLE:OptionDisperseOnAttack(Seconds) +self:F2({self.ControllableName}) +local seconds=Seconds or 0 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK,seconds) +end +end +return self +end +return nil +end +function CONTROLLABLE:IsSubmarine() +self:F2() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitDescriptor=DCSUnit:getDesc() +if UnitDescriptor.attributes["Submarines"]==true then +return true +else +return false +end +end +return nil +end +function CONTROLLABLE:SetSpeed(Speed,Keep) +self:F2({self.ControllableName}) +local speed=Speed or 5 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +Controller:setSpeed(speed,Keep) +end +end +return self +end +function CONTROLLABLE:SetAltitude(Altitude,Keep,AltType) +self:F2({self.ControllableName}) +local altitude=Altitude or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsAir()then +Controller:setAltitude(altitude,Keep,AltType) +end +end +end +return self +end +function CONTROLLABLE:TaskAerobatics() +local DCSTaskAerobatics={ +id="Aerobatics", +params={ +["maneuversSequency"]={}, +}, +["enabled"]=true, +["auto"]=false, +} +return DCSTaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsCandle(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local CandleTask={ +["name"]="CANDLE", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +} +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],CandleTask) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsEdgeFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime,Side) +local maxrepeats=10 +local maxflight=200 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local flighttime=FlightTime or 10 +if flighttime>200 then maxflight=flighttime end +local EdgeTask={ +["name"]="EDGE_FLIGHT", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["FlightTime"]={ +["max_v"]=maxflight, +["min_v"]=1, +["order"]=6, +["step"]=0.1, +["value"]=flighttime or 10, +}, +["SIDE"]={ +["order"]=7, +["value"]=Side or 0, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],EdgeTask) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsWingoverFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime) +local maxrepeats=10 +local maxflight=200 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local flighttime=FlightTime or 10 +if flighttime>200 then maxflight=flighttime end +local WingoverTask={ +["name"]="WINGOVER_FLIGHT", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["FlightTime"]={ +["max_v"]=maxflight, +["min_v"]=1, +["order"]=6, +["step"]=0.1, +["value"]=flighttime or 10, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],WingoverTask) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsLoop(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local LoopTask={ +["name"]="LOOP", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +} +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],LoopTask) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsHorizontalEight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local LoopTask={ +["name"]="HORIZONTAL_EIGHT", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["SIDE"]={ +["order"]=6, +["value"]=Side or 0, +}, +["ROLL1"]={ +["order"]=7, +["value"]=RollDeg or 60, +}, +["ROLL2"]={ +["order"]=8, +["value"]=RollDeg or 60, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],LoopTask) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsHammerhead(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="HUMMERHEAD", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["SIDE"]={ +["order"]=6, +["value"]=Side or 0, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsSkewedLoop(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="SKEWED_LOOP", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["ROLL"]={ +["order"]=6, +["value"]=RollDeg or 60, +}, +["SIDE"]={ +["order"]=7, +["value"]=Side or 0, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg,Pull,Angle) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="TURN", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["Ny_req"]={ +["order"]=6, +["value"]=Pull or 2, +}, +["ROLL"]={ +["order"]=7, +["value"]=RollDeg or 60, +}, +["SECTOR"]={ +["order"]=8, +["value"]=Angle or 180, +}, +["SIDE"]={ +["order"]=9, +["value"]=Side or 0, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsDive(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Angle,FinalAltitude) +local maxrepeats=10 +local angle=Angle +if angle<15 then angle=15 elseif angle>90 then angle=90 end +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="DIVE", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 5000, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["Angle"]={ +["max_v"]=90, +["min_v"]=15, +["order"]=6, +["step"]=5, +["value"]=angle or 45, +}, +["FinalAltitude"]={ +["order"]=7, +["value"]=FinalAltitude or 1000, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsMilitaryTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="MILITARY_TURN", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +} +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsImmelmann(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="IMMELMAN", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +} +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsStraightFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local maxflight=200 +if Repeats>maxrepeats then maxrepeats=Repeats end +local flighttime=FlightTime or 10 +if flighttime>200 then maxflight=flighttime end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="STRAIGHT_FLIGHT", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["FlightTime"]={ +["max_v"]=maxflight, +["min_v"]=1, +["order"]=6, +["step"]=0.1, +["value"]=flighttime or 10, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsClimb(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Angle,FinalAltitude) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="CLIMB", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["Angle"]={ +["max_v"]=90, +["min_v"]=15, +["order"]=6, +["step"]=5, +["value"]=Angle or 45, +}, +["FinalAltitude"]={ +["order"]=7, +["value"]=FinalAltitude or 5000, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsSpiral(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,TurnAngle,Roll,Side,UpDown,Angle) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local updown=UpDown and 1 or 0 +local side=Side and 1 or 0 +local Task={ +["name"]="SPIRAL", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["SECTOR"]={ +["order"]=6, +["value"]=TurnAngle or 360, +}, +["ROLL"]={ +["order"]=7, +["value"]=Roll or 60, +}, +["SIDE"]={ +["order"]=8, +["value"]=side or 0, +}, +["UPDOWN"]={ +["order"]=9, +["value"]=updown or 0, +}, +["Angle"]={ +["max_v"]=90, +["min_v"]=15, +["order"]=10, +["step"]=5, +["value"]=Angle or 45, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsSplitS(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FinalSpeed) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local maxflight=200 +if Repeats>maxrepeats then maxrepeats=Repeats end +local finalspeed=FinalSpeed or 500 +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="SPLIT_S", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["FinalSpeed"]={ +["order"]=6, +["value"]=finalspeed, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsAileronRoll(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollRate,TurnAngle,FixAngle) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local maxflight=200 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="AILERON_ROLL", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["SIDE"]={ +["order"]=6, +["value"]=Side or 0, +}, +["RollRate"]={ +["max_v"]=450, +["min_v"]=15, +["order"]=7, +["step"]=5, +["value"]=RollRate or 90, +}, +["SECTOR"]={ +["order"]=8, +["value"]=TurnAngle or 360, +}, +["FIXSECTOR"]={ +["max_v"]=180, +["min_v"]=0, +["order"]=9, +["step"]=5, +["value"]=FixAngle or 0, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsForcedTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,TurnAngle,Side,FlightTime,MinSpeed) +local maxrepeats=10 +local flighttime=FlightTime or 30 +local maxtime=200 +if flighttime>200 then maxtime=flighttime end +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="FORCED_TURN", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["SECTOR"]={ +["order"]=6, +["value"]=TurnAngle or 360, +}, +["SIDE"]={ +["order"]=7, +["value"]=Side or 0, +}, +["FlightTime"]={ +["max_v"]=maxtime or 200, +["min_v"]=0, +["order"]=8, +["step"]=0.1, +["value"]=flighttime or 30, +}, +["MinSpeed"]={ +["max_v"]=3000, +["min_v"]=30, +["order"]=9, +["step"]=10, +["value"]=MinSpeed or 250, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:TaskAerobaticsBarrelRoll(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollRate,TurnAngle) +local maxrepeats=10 +if Repeats>maxrepeats then maxrepeats=Repeats end +local usesmoke=UseSmoke and 1 or 0 +local startimmediately=StartImmediately and 1 or 0 +local Task={ +["name"]="BARREL_ROLL", +["params"]={ +["RepeatQty"]={ +["max_v"]=maxrepeats, +["min_v"]=1, +["order"]=1, +["value"]=Repeats or 1, +}, +["InitAltitude"]={ +["order"]=2, +["value"]=InitAltitude or 0, +}, +["InitSpeed"]={ +["order"]=3, +["value"]=InitSpeed or 0, +}, +["UseSmoke"]={ +["order"]=4, +["value"]=usesmoke, +}, +["StartImmediatly"]={ +["order"]=5, +["value"]=startimmediately, +}, +["SIDE"]={ +["order"]=6, +["value"]=Side or 0, +}, +["RollRate"]={ +["max_v"]=450, +["min_v"]=15, +["order"]=7, +["step"]=5, +["value"]=RollRate or 90, +}, +["SECTOR"]={ +["order"]=8, +["value"]=TurnAngle or 360, +}, +} +} +table.insert(TaskAerobatics.params["maneuversSequency"],Task) +return TaskAerobatics +end +function CONTROLLABLE:PatrolRaceTrack(Point1,Point2,Altitude,Speed,Formation,AGL,Delay) +local PatrolGroup=self +if not self:IsInstanceOf("GROUP")then +PatrolGroup=self:GetGroup() +end +local delay=Delay or 1 +self:F({PatrolGroup=PatrolGroup:GetName()}) +if PatrolGroup:IsAir()then +if Formation then +PatrolGroup:SetOption(AI.Option.Air.id.FORMATION,Formation) +end +local FromCoord=PatrolGroup:GetCoordinate() +local ToCoord=Point1:GetCoordinate() +if Altitude then +local asl=true +if AGL then asl=false end +FromCoord:SetAltitude(Altitude,asl) +ToCoord:SetAltitude(Altitude,asl) +end +local Route={} +Route[#Route+1]=FromCoord:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description,timeReFuAr) +Route[#Route+1]=ToCoord:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description,timeReFuAr) +local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRaceTrack",Point2,Point1,Altitude,Speed,Formation,Delay) +PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone) +PatrolGroup:Route(Route,Delay) +end +return self +end +function CONTROLLABLE:NewIRMarker(EnableImmediately,Runtime) +self:T2("NewIRMarker") +if self:IsInstanceOf("GROUP")then +if self.IRMarkerGroup==true then return end +self.IRMarkerGroup=true +self.IRMarkerUnit=false +elseif self:IsInstanceOf("UNIT")then +if self.IRMarkerUnit==true then return end +self.IRMarkerGroup=false +self.IRMarkerUnit=true +end +self.Runtime=Runtime or 60 +if EnableImmediately and EnableImmediately==true then +self:EnableIRMarker(Runtime) +end +return self +end +function CONTROLLABLE:EnableIRMarker(Runtime) +self:T2("EnableIRMarker") +if self.IRMarkerGroup==nil then +self:NewIRMarker(true,Runtime) +return +end +if self:IsInstanceOf("GROUP")then +self:EnableIRMarkerForGroup(Runtime) +return +end +if self.timer and self.timer:IsRunning()then return self end +local Runtime=Runtime or self.Runtime +self.timer=TIMER:New(CONTROLLABLE._MarkerBlink,self) +self.timer:Start(nil,1-math.random(1,5)/10/2,Runtime) +self.IRMarkerUnit=true +return self +end +function CONTROLLABLE:DisableIRMarker() +self:T2("DisableIRMarker") +if self:IsInstanceOf("GROUP")then +self:DisableIRMarkerForGroup() +return +end +if self.spot then +self.spot=nil +end +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end +if self:IsInstanceOf("GROUP")then +self.IRMarkerGroup=nil +elseif self:IsInstanceOf("UNIT")then +self.IRMarkerUnit=nil +end +return self +end +function CONTROLLABLE:EnableIRMarkerForGroup(Runtime) +self:T2("EnableIRMarkerForGroup") +if self:IsInstanceOf("GROUP") +then +local units=self:GetUnits()or{} +for _,_unit in pairs(units)do +_unit:EnableIRMarker(Runtime) +end +self.IRMarkerGroup=true +end +return self +end +function CONTROLLABLE:DisableIRMarkerForGroup() +self:T2("DisableIRMarkerForGroup") +if self:IsInstanceOf("GROUP")then +local units=self:GetUnits()or{} +for _,_unit in pairs(units)do +_unit:DisableIRMarker() +end +self.IRMarkerGroup=nil +end +return self +end +function CONTROLLABLE:HasIRMarker() +self:T2("HasIRMarker") +if self:IsInstanceOf("GROUP")then +local units=self:GetUnits()or{} +for _,_unit in pairs(units)do +if _unit.timer and _unit.timer:IsRunning()then return true end +end +elseif self.timer and self.timer:IsRunning()then return true end +return false +end +function CONTROLLABLE._StopSpot(spot) +if spot then +spot:destroy() +end +end +function CONTROLLABLE:_MarkerBlink() +self:T2("_MarkerBlink") +if self:IsAlive()~=true then +self:DisableIRMarker() +return +end +self.timer.dT=1-(math.random(1,2)/10/2) +local _,_,unitBBHeight,_=self:GetObjectSize() +local unitPos=self:GetPositionVec3() +if self.timer:IsRunning()then +self:T2("Create Spot") +local spot=Spot.createInfraRed( +self.DCSUnit, +{x=0,y=(unitBBHeight+1),z=0}, +{x=unitPos.x,y=(unitPos.y+unitBBHeight),z=unitPos.z} +) +self.spot=spot +local offTimer=nil +local offTimer=TIMER:New(CONTROLLABLE._StopSpot,spot) +offTimer:Start(0.5) +end +return self +end +GROUP={ +ClassName="GROUP", +} +GROUP.Takeoff={ +Air=1, +Runway=2, +Hot=3, +Cold=4, +} +GROUPTEMPLATE={} +GROUPTEMPLATE.Takeoff={ +[GROUP.Takeoff.Air]={"Turning Point","Turning Point"}, +[GROUP.Takeoff.Runway]={"TakeOff","From Runway"}, +[GROUP.Takeoff.Hot]={"TakeOffParkingHot","From Parking Area Hot"}, +[GROUP.Takeoff.Cold]={"TakeOffParking","From Parking Area"} +} +GROUP.Attribute={ +AIR_TRANSPORTPLANE="Air_TransportPlane", +AIR_AWACS="Air_AWACS", +AIR_FIGHTER="Air_Fighter", +AIR_BOMBER="Air_Bomber", +AIR_TANKER="Air_Tanker", +AIR_TRANSPORTHELO="Air_TransportHelo", +AIR_ATTACKHELO="Air_AttackHelo", +AIR_UAV="Air_UAV", +AIR_OTHER="Air_OtherAir", +GROUND_APC="Ground_APC", +GROUND_TRUCK="Ground_Truck", +GROUND_INFANTRY="Ground_Infantry", +GROUND_IFV="Ground_IFV", +GROUND_ARTILLERY="Ground_Artillery", +GROUND_TANK="Ground_Tank", +GROUND_TRAIN="Ground_Train", +GROUND_EWR="Ground_EWR", +GROUND_AAA="Ground_AAA", +GROUND_SAM="Ground_SAM", +GROUND_SHORAD="Ground_SHORAD", +GROUND_BALLISTICMISSILE="Ground_BallisticMissile", +GROUND_OTHER="Ground_OtherGround", +NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", +NAVAL_WARSHIP="Naval_WarShip", +NAVAL_ARMEDSHIP="Naval_ArmedShip", +NAVAL_UNARMEDSHIP="Naval_UnarmedShip", +NAVAL_OTHER="Naval_OtherNaval", +OTHER_UNKNOWN="Other_Unknown", +} +function GROUP:NewTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID) +local GroupName=GroupTemplate.name +_DATABASE:_RegisterGroupTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID,GroupName) +local self=BASE:Inherit(self,CONTROLLABLE:New(GroupName)) +self.GroupName=GroupName +if not _DATABASE.GROUPS[GroupName]then +_DATABASE.GROUPS[GroupName]=self +end +self:SetEventPriority(4) +return self +end +function GROUP:Register(GroupName) +local self=BASE:Inherit(self,CONTROLLABLE:New(GroupName)) +self.GroupName=GroupName +self:SetEventPriority(4) +return self +end +function GROUP:Find(DCSGroup) +local GroupName=DCSGroup:getName() +local GroupFound=_DATABASE:FindGroup(GroupName) +return GroupFound +end +function GROUP:FindByName(GroupName) +local GroupFound=_DATABASE:FindGroup(GroupName) +return GroupFound +end +function GROUP:FindByMatching(Pattern) +local GroupFound=nil +for name,group in pairs(_DATABASE.GROUPS)do +if string.match(name,Pattern)then +GroupFound=group +break +end +end +return GroupFound +end +function GROUP:FindAllByMatching(Pattern) +local GroupsFound={} +for name,group in pairs(_DATABASE.GROUPS)do +if string.match(name,Pattern)then +GroupsFound[#GroupsFound+1]=group +end +end +return GroupsFound +end +function GROUP:GetDCSObject() +local DCSGroup=Group.getByName(self.GroupName) +if DCSGroup then +self.LastCallDCSObject=timer.getTime() +self.DCSObject=DCSGroup +return DCSGroup +end +return nil +end +function GROUP:GetPositionVec3() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local unit=DCSPositionable:getUnits()[1] +if unit then +local PositionablePosition=unit:getPosition().p +return PositionablePosition +end +end +return nil +end +function GROUP:IsAlive() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +if DCSGroup:isExist()then +local DCSUnit=DCSGroup:getUnit(1) +if DCSUnit then +local GroupIsAlive=DCSUnit:isActive() +return GroupIsAlive +end +end +end +return nil +end +function GROUP:IsActive() +local DCSGroup=self:GetDCSObject() +if DCSGroup and DCSGroup:isExist()then +local unit=DCSGroup:getUnit(1) +if unit then +local GroupIsActive=unit:isActive() +return GroupIsActive +end +end +return nil +end +function GROUP:Destroy(GenerateEvent,delay) +if delay and delay>0 then +self:ScheduleOnce(delay,GROUP.Destroy,self,GenerateEvent) +else +local DCSGroup=Group.getByName(self.GroupName) +if DCSGroup then +for Index,UnitData in pairs(DCSGroup:getUnits())do +if GenerateEvent and GenerateEvent==true then +if self:IsAir()then +self:CreateEventCrash(timer.getTime(),UnitData) +else +self:CreateEventDead(timer.getTime(),UnitData) +end +elseif GenerateEvent==false then +else +self:CreateEventRemoveUnit(timer.getTime(),UnitData) +end +end +USERFLAG:New(self:GetName()):Set(100) +DCSGroup:destroy() +DCSGroup=nil +end +end +return nil +end +function GROUP:GetCategory() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupCategory=DCSGroup:getCategory() +return GroupCategory +end +return nil +end +function GROUP:GetCategoryName() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local CategoryNames={ +[Group.Category.AIRPLANE]="Airplane", +[Group.Category.HELICOPTER]="Helicopter", +[Group.Category.GROUND]="Ground Unit", +[Group.Category.SHIP]="Ship", +[Group.Category.TRAIN]="Train", +} +local GroupCategory=DCSGroup:getCategory() +return CategoryNames[GroupCategory] +end +return nil +end +function GROUP:GetCoalition() +if self.GroupCoalition~=nil then +return self.GroupCoalition +else +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupCoalition=DCSGroup:getCoalition() +self.GroupCoalition=GroupCoalition +return GroupCoalition +end +end +return nil +end +function GROUP:GetCountry() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupCountry=DCSGroup:getUnit(1):getCountry() +return GroupCountry +end +return nil +end +function GROUP:HasAttribute(attribute,all) +local _units=self:GetUnits() +if _units then +local _allhave=true +local _onehas=false +for _,_unit in pairs(_units)do +local _unit=_unit +if _unit then +local _hastit=_unit:HasAttribute(attribute) +if _hastit==true then +_onehas=true +else +_allhave=false +end +end +end +if all==true then +return _allhave +else +return _onehas +end +end +return nil +end +function GROUP:GetSpeedMax() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local Units=self:GetUnits() +local speedmax=nil +for _,unit in pairs(Units)do +local unit=unit +local speed=unit:GetSpeedMax() +if speedmax==nil or speed0 then +self:ScheduleOnce(delay,GROUP.Activate,self) +else +trigger.action.activateGroup(self:GetDCSObject()) +end +return self +end +function GROUP:Deactivate(delay) +if delay and delay>0 then +self:ScheduleOnce(delay,GROUP.Deactivate,self) +else +trigger.action.deactivateGroup(self:GetDCSObject()) +end +return self +end +function GROUP:GetTypeName() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +return(GroupTypeName) +end +return nil +end +function GROUP:GetNatoReportingName() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +return UTILS.GetReportingName(GroupTypeName) +end +return"Bogey" +end +function GROUP:GetPlayerName() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local PlayerName=DCSGroup:getUnit(1):getPlayerName() +return(PlayerName) +end +return nil +end +function GROUP:GetCallsign() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupCallSign=DCSGroup:getUnit(1):getCallsign() +return GroupCallSign +end +BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function GROUP:GetVec2() +local Unit=self:GetUnit(1) +if Unit then +local vec2=Unit:GetVec2() +return vec2 +end +end +function GROUP:GetVec3() +local unit=self:GetUnit(1) +if unit then +local vec3=unit:GetVec3() +return vec3 +end +self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) +return nil +end +function GROUP:GetAverageVec3() +local units=self:GetUnits()or{} +local x=0;local y=0;local z=0;local n=0 +for _,unit in pairs(units)do +local vec3=nil +if unit and unit:IsAlive()then +vec3=unit:GetVec3() +end +if vec3 then +x=x+vec3.x +y=y+vec3.y +z=z+vec3.z +n=n+1 +end +end +if n>0 then +local Vec3={x=x/n,y=y/n,z=z/n} +return Vec3 +else +return self:GetVec3() +end +end +function GROUP:GetPointVec2() +local FirstUnit=self:GetUnit(1) +if FirstUnit then +local FirstUnitPointVec2=FirstUnit:GetPointVec2() +return FirstUnitPointVec2 +end +BASE:E({"Cannot get COORDINATE",Group=self,Alive=self:IsAlive()}) +return nil +end +function GROUP:GetAverageCoordinate() +local vec3=self:GetAverageVec3() +if vec3 then +local coord=COORDINATE:NewFromVec3(vec3) +local Heading=self:GetHeading() +coord.Heading=Heading +return coord +else +local coord=self:GetCoordinate() +if coord then +return coord +else +BASE:E({"Cannot GetAverageCoordinate",Group=self,Alive=self:IsAlive()}) +return nil +end +end +end +function GROUP:GetCoordinate() +local vec3=self:GetVec3() +local coord +if vec3 then +coord=COORDINATE:NewFromVec3(vec3) +coord.Heading=self:GetHeading()or 0 +return coord +end +local Units=self:GetUnits()or{} +for _,_unit in pairs(Units)do +local FirstUnit=_unit +if FirstUnit and FirstUnit:IsAlive()then +local FirstUnitCoordinate=FirstUnit:GetCoordinate() +if FirstUnitCoordinate then +local Heading=self:GetHeading()or 0 +FirstUnitCoordinate.Heading=Heading +return FirstUnitCoordinate +end +end +end +local DCSGroup=Group.getByName(self.GroupName) +if DCSGroup then +local DCSUnits=DCSGroup:getUnits()or{} +for _,_unit in pairs(DCSUnits)do +if Object.isExist(_unit)then +local position=_unit:getPosition() +local point=position.p~=nil and position.p or _unit:GetPoint() +if point then +local coord=COORDINATE:NewFromVec3(point) +coord.Heading=0 +local munit=UNIT:Find(_unit) +if munit then +coord.Heading=munit:GetHeading()or 0 +end +return coord +end +end +end +end +BASE:E({"Cannot GetCoordinate",Group=self,Alive=self:IsAlive()}) +end +function GROUP:GetRandomVec3(Radius) +local FirstUnit=self:GetUnit(1) +if FirstUnit then +local FirstUnitRandomPointVec3=FirstUnit:GetRandomVec3(Radius) +return FirstUnitRandomPointVec3 +end +BASE:E({"Cannot GetRandomVec3",Group=self,Alive=self:IsAlive()}) +return nil +end +function GROUP:GetHeading() +local GroupSize=self:GetSize() +local HeadingAccumulator=0 +local n=0 +local Units=self:GetUnits() +if GroupSize then +for _,unit in pairs(Units)do +if unit and unit:IsAlive()then +HeadingAccumulator=HeadingAccumulator+unit:GetHeading() +n=n+1 +end +end +return math.floor(HeadingAccumulator/n) +end +BASE:E({"Cannot GetHeading",Group=self,Alive=self:IsAlive()}) +return nil +end +function GROUP:GetFuelMin() +if not self:GetDCSObject()then +BASE:E({"Cannot GetFuel",Group=self,Alive=self:IsAlive()}) +return 0 +end +local min=65535 +local unit=nil +local tmp=nil +for UnitID,UnitData in pairs(self:GetUnits())do +if UnitData and UnitData:IsAlive()then +tmp=UnitData:GetFuel() +if tmpGroupVelocityMax then +GroupVelocityMax=UnitVelocity +end +end +end +return GroupVelocityMax +end +return nil +end +function GROUP:GetMinHeight() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupHeightMin=999999999 +for Index,UnitData in pairs(DCSGroup:getUnits())do +local UnitData=UnitData +local UnitHeight=UnitData:getPoint() +if UnitHeightGroupHeightMax then +GroupHeightMax=UnitHeight +end +end +return GroupHeightMax +end +return nil +end +function GROUP:GetTemplate() +local GroupName=self:GetName() +local template=_DATABASE:GetGroupTemplate(GroupName) +if template then +return UTILS.DeepCopy(template) +end +return nil +end +function GROUP:GetTemplateRoutePoints() +local GroupName=self:GetName() +local template=_DATABASE:GetGroupTemplate(GroupName) +if template and template.route and template.route.points then +return UTILS.DeepCopy(template.route.points) +end +end +function GROUP:SetTemplateControlled(Template,Controlled) +Template.uncontrolled=not Controlled +return Template +end +function GROUP:SetTemplateCountry(Template,CountryID) +Template.CountryID=CountryID +return Template +end +function GROUP:SetTemplateCoalition(Template,CoalitionID) +Template.CoalitionID=CoalitionID +return Template +end +function GROUP:InitHeading(Heading) +self.InitRespawnHeading=Heading +return self +end +function GROUP:InitHeight(Height) +self.InitRespawnHeight=Height +return self +end +function GROUP:InitZone(Zone) +self.InitRespawnZone=Zone +return self +end +function GROUP:InitRandomizePositionZone(PositionZone) +self.InitRespawnRandomizePositionZone=PositionZone +self.InitRespawnRandomizePositionInner=nil +self.InitRespawnRandomizePositionOuter=nil +return self +end +function GROUP:InitRandomizePositionRadius(OuterRadius,InnerRadius) +self.InitRespawnRandomizePositionZone=nil +self.InitRespawnRandomizePositionOuter=OuterRadius +self.InitRespawnRandomizePositionInner=InnerRadius +return self +end +function GROUP:InitCoordinate(coordinate) +self.InitCoord=coordinate +return self +end +function GROUP:InitRadioCommsOnOff(switch) +if switch==true or switch==nil then +self.InitRespawnRadio=true +else +self.InitRespawnRadio=false +end +return self +end +function GROUP:InitRadioFrequency(frequency) +self.InitRespawnFreq=frequency +return self +end +function GROUP:InitRadioModulation(modulation) +if modulation and modulation:lower()=="fm"then +self.InitRespawnModu=radio.modulation.FM +else +self.InitRespawnModu=radio.modulation.AM +end +return self +end +function GROUP:InitModex(modex) +if modex then +self.InitRespawnModex=tonumber(modex) +end +return self +end +function GROUP:Respawn(Template,Reset) +Template=Template or self:GetTemplate() +local function _Heading(course) +local h +if course<=180 then +h=math.rad(course) +else +h=-math.rad(360-course) +end +return h +end +local function TransFormRoute(Template,OldPos,NewPos) +if Template.route and Template.route.points then +for _,_point in ipairs(Template.route.points)do +_point.x=_point.x-OldPos.x+NewPos.x +_point.y=_point.y-OldPos.y+NewPos.y +end +end +return Template +end +if self:IsAlive()then +local OldPos=self:GetVec2() +local Zone=self.InitRespawnZone +local Vec3=Zone and Zone:GetVec3()or self:GetVec3() +local From={x=Template.x,y=Template.y} +Template.x=Vec3.x +Template.y=Vec3.z +local NewPos={x=Vec3.x,y=Vec3.z} +if Reset==true then +for UnitID,UnitData in pairs(self:GetUnits())do +local GroupUnit=UnitData +if GroupUnit:IsAlive()then +local GroupUnitVec3=GroupUnit:GetVec3() +if Zone then +if self.InitRespawnRandomizePositionZone then +GroupUnitVec3=Zone:GetRandomVec3() +else +if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then +GroupUnitVec3=COORDINATE:NewFromVec3(From):GetRandomVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner) +else +GroupUnitVec3=Zone:GetVec3() +end +end +end +if self.InitCoord then +GroupUnitVec3=self.InitCoord:GetVec3() +end +Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y +if Zone then +Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x +Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z +else +Template.units[UnitID].x=GroupUnitVec3.x +Template.units[UnitID].y=GroupUnitVec3.z +end +Template.units[UnitID].heading=_Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading()) +Template.units[UnitID].psi=-Template.units[UnitID].heading +end +end +Template=TransFormRoute(Template,OldPos,NewPos) +elseif Reset==false then +for UnitID,TemplateUnitData in pairs(Template.units)do +local GroupUnitVec3={x=TemplateUnitData.x,y=TemplateUnitData.alt,z=TemplateUnitData.y} +if Zone then +if self.InitRespawnRandomizePositionZone then +GroupUnitVec3=Zone:GetRandomVec3() +else +if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then +GroupUnitVec3=COORDINATE:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner) +else +GroupUnitVec3=Zone:GetVec3() +end +end +end +if self.InitCoord then +GroupUnitVec3=self.InitCoord:GetVec3() +end +Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y +Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x +Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z +Template.units[UnitID].heading=self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading +end +Template=TransFormRoute(Template,OldPos,NewPos) +else +local units=self:GetUnits() +for UnitID,Unit in pairs(Template.units)do +for _,_unit in pairs(units)do +local unit=_unit +if unit:GetName()==Unit.name then +local coord=unit:GetCoordinate() +local heading=unit:GetHeading() +Unit.x=coord.x +Unit.y=coord.z +Unit.alt=coord.y +Unit.heading=math.rad(heading) +Unit.psi=-Unit.heading +end +end +end +end +end +if self.InitRespawnModex then +for UnitID=1,#Template.units do +Template.units[UnitID].onboard_num=string.format("%03d",self.InitRespawnModex+(UnitID-1)) +end +end +if self.InitRespawnRadio then +Template.communication=self.InitRespawnRadio +end +if self.InitRespawnFreq then +Template.frequency=self.InitRespawnFreq +end +if self.InitRespawnModu then +Template.modulation=self.InitRespawnModu +end +self:Destroy(false) +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(Template.units) +end +self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template) +self:ResetEvents() +return self +end +function GROUP:Teleport(Coordinate) +self:InitZone(Coordinate) +return self:Respawn(nil,false) +end +function GROUP:RespawnAtCurrentAirbase(SpawnTemplate,Takeoff,Uncontrolled) +if self and self:IsAlive()then +local airbase=self:GetCoordinate():GetClosestAirbase() +if airbase then +else +self:E("ERROR: could not find closest airbase!") +return nil +end +Takeoff=Takeoff or SPAWN.Takeoff.Hot +local AirbaseCoord=airbase:GetCoordinate() +SpawnTemplate=SpawnTemplate or self:GetTemplate() +if SpawnTemplate then +local SpawnPoint=SpawnTemplate.route.points[1] +SpawnPoint.linkUnit=nil +SpawnPoint.helipadId=nil +SpawnPoint.airdromeId=nil +local AirbaseID=airbase:GetID() +local AirbaseCategory=airbase:GetAirbaseCategory() +if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.AIRDROME then +SpawnPoint.airdromeId=AirbaseID +end +SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1] +SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2] +local units=self:GetUnits() +local x +local y +for UnitID=1,#units do +local unit=units[UnitID] +local Parkingspot,TermialID,Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase) +local uc=unit:GetCoordinate() +SpawnTemplate.units[UnitID].x=uc.x +SpawnTemplate.units[UnitID].y=uc.z +SpawnTemplate.units[UnitID].alt=uc.y +SpawnTemplate.units[UnitID].parking=TermialID +SpawnTemplate.units[UnitID].parking_id=nil +end +SpawnPoint.x=SpawnTemplate.units[1].x +SpawnPoint.y=SpawnTemplate.units[1].y +SpawnPoint.alt=SpawnTemplate.units[1].alt +SpawnTemplate.x=SpawnTemplate.units[1].x +SpawnTemplate.y=SpawnTemplate.units[1].y +SpawnTemplate.uncontrolled=Uncontrolled +if self.InitRespawnRadio then +SpawnTemplate.communication=self.InitRespawnRadio +end +if self.InitRespawnFreq then +SpawnTemplate.frequency=self.InitRespawnFreq +end +if self.InitRespawnModu then +SpawnTemplate.modulation=self.InitRespawnModu +end +self:Destroy(false) +_DATABASE:Spawn(SpawnTemplate) +self:ResetEvents() +return self +end +else +self:E("WARNING: GROUP is not alive!") +end +return nil +end +function GROUP:GetTaskMission() +return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) +end +function GROUP:GetTaskRoute() +if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then +return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) +else +return{} +end +end +function GROUP:CopyRoute(Begin,End,Randomize,Radius) +local Points={} +local GroupName=string.match(self:GetName(),".*#") +if GroupName then +GroupName=GroupName:sub(1,-2) +else +GroupName=self:GetName() +end +local Template=_DATABASE.Templates.Groups[GroupName].Template +if Template then +if not Begin then +Begin=0 +end +if not End then +End=0 +end +for TPointID=Begin+1,#Template.route.points-End do +if Template.route.points[TPointID]then +Points[#Points+1]=UTILS.DeepCopy(Template.route.points[TPointID]) +if Randomize then +if not Radius then +Radius=500 +end +Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius) +Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius) +end +end +end +return Points +else +error("Template not found for Group : "..GroupName) +end +return nil +end +function GROUP:CalculateThreatLevelA2G() +local MaxThreatLevelA2G=0 +for UnitName,UnitData in pairs(self:GetUnits())do +local ThreatUnit=UnitData +local ThreatLevelA2G=ThreatUnit:GetThreatLevel() +if ThreatLevelA2G>MaxThreatLevelA2G then +MaxThreatLevelA2G=ThreatLevelA2G +end +end +return MaxThreatLevelA2G +end +function GROUP:GetThreatLevel() +local threatlevelMax=0 +for UnitName,UnitData in pairs(self:GetUnits())do +local ThreatUnit=UnitData +local threatlevel=ThreatUnit:GetThreatLevel() +if threatlevel>threatlevelMax then +threatlevelMax=threatlevel +end +end +return threatlevelMax +end +function GROUP:InAir() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local DCSUnit=DCSGroup:getUnit(1) +if DCSUnit then +local GroupInAir=DCSGroup:getUnit(1):inAir() +return GroupInAir +end +end +return nil +end +function GROUP:IsAirborne(AllUnits) +local units=self:GetUnits() +if units then +if AllUnits then +for _,_unit in pairs(units)do +local unit=_unit +if unit then +local inair=unit:InAir() +if not inair then +return false +end +end +end +return true +else +for _,_unit in pairs(units)do +local unit=_unit +if unit then +local inair=unit:InAir() +if inair then +return true +end +end +return false +end +end +end +return nil +end +function GROUP:GetDCSDesc(n) +n=n or 1 +local unit=self:GetUnit(n) +if unit and unit:IsAlive()~=nil then +local desc=unit:GetDesc() +return desc +end +return nil +end +function GROUP:GetAttribute() +local attribute=GROUP.Attribute.OTHER_UNKNOWN +if self then +local transportplane=self:HasAttribute("Transports")and self:HasAttribute("Planes") +local awacs=self:HasAttribute("AWACS") +local fighter=self:HasAttribute("Fighters")or self:HasAttribute("Interceptors")or self:HasAttribute("Multirole fighters")or(self:HasAttribute("Bombers")and not self:HasAttribute("Strategic bombers")) +local bomber=self:HasAttribute("Strategic bombers") +local tanker=self:HasAttribute("Tankers") +local uav=self:HasAttribute("UAVs") +local transporthelo=self:HasAttribute("Transport helicopters") +local attackhelicopter=self:HasAttribute("Attack helicopters") +local apc=self:HasAttribute("APC") +local truck=self:HasAttribute("Trucks")and self:GetCategory()==Group.Category.GROUND +local infantry=self:HasAttribute("Infantry") +local artillery=self:HasAttribute("Artillery") +local tank=self:HasAttribute("Old Tanks")or self:HasAttribute("Modern Tanks")or self:HasAttribute("Tanks") +local aaa=self:HasAttribute("AAA")and(not self:HasAttribute("SAM elements")) +local ballisticMissile=artillery and self:HasAttribute("SS_missile") +local shorad=self:HasAttribute("SR SAM") +local ewr=self:HasAttribute("EWR") +local ifv=self:HasAttribute("IFV") +local sam=self:HasAttribute("SAM elements")or self:HasAttribute("Optical Tracker") +local train=self:GetCategory()==Group.Category.TRAIN +local aircraftcarrier=self:HasAttribute("Aircraft Carriers") +local warship=self:HasAttribute("Heavy armed ships") +local armedship=self:HasAttribute("Armed ships") +local unarmedship=self:HasAttribute("Unarmed ships") +if fighter then +attribute=GROUP.Attribute.AIR_FIGHTER +elseif bomber then +attribute=GROUP.Attribute.AIR_BOMBER +elseif awacs then +attribute=GROUP.Attribute.AIR_AWACS +elseif transportplane then +attribute=GROUP.Attribute.AIR_TRANSPORTPLANE +elseif tanker then +attribute=GROUP.Attribute.AIR_TANKER +elseif attackhelicopter then +attribute=GROUP.Attribute.AIR_ATTACKHELO +elseif transporthelo then +attribute=GROUP.Attribute.AIR_TRANSPORTHELO +elseif uav then +attribute=GROUP.Attribute.AIR_UAV +elseif ewr then +attribute=GROUP.Attribute.GROUND_EWR +elseif sam then +attribute=GROUP.Attribute.GROUND_SAM +elseif aaa then +attribute=GROUP.Attribute.GROUND_AAA +elseif artillery and ballisticMissile then +attribute=GROUP.Attribute.GROUND_BALLISTICMISSILE +elseif artillery then +attribute=GROUP.Attribute.GROUND_ARTILLERY +elseif tank then +attribute=GROUP.Attribute.GROUND_TANK +elseif ifv then +attribute=GROUP.Attribute.GROUND_IFV +elseif apc then +attribute=GROUP.Attribute.GROUND_APC +elseif infantry then +attribute=GROUP.Attribute.GROUND_INFANTRY +elseif truck then +attribute=GROUP.Attribute.GROUND_TRUCK +elseif train then +attribute=GROUP.Attribute.GROUND_TRAIN +elseif aircraftcarrier then +attribute=GROUP.Attribute.NAVAL_AIRCRAFTCARRIER +elseif warship then +attribute=GROUP.Attribute.NAVAL_WARSHIP +elseif armedship then +attribute=GROUP.Attribute.NAVAL_ARMEDSHIP +elseif unarmedship then +attribute=GROUP.Attribute.NAVAL_UNARMEDSHIP +else +if self:IsGround()then +attribute=GROUP.Attribute.GROUND_OTHER +elseif self:IsShip()then +attribute=GROUP.Attribute.NAVAL_OTHER +elseif self:IsAir()then +attribute=GROUP.Attribute.AIR_OTHER +else +attribute=GROUP.Attribute.OTHER_UNKNOWN +end +end +end +return attribute +end +do +function GROUP:RouteRTB(RTBAirbase,Speed) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +if RTBAirbase then +local Speed=Speed or self:GetSpeedMax()*0.8 +local coord=self:GetCoordinate() +local PointFrom=coord:WaypointAirTurningPoint(nil,Speed) +local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed,RTBAirbase) +local Points={PointFrom,PointLanding} +local Template=self:GetTemplate() +Template.route.points=Points +self:Respawn(Template,true) +self:Route(Points) +else +self:ClearTasks() +end +end +return self +end +end +function GROUP:OnReSpawn(ReSpawnFunction) +self.ReSpawnFunction=ReSpawnFunction +end +do +function GROUP:HandleEvent(Event,EventFunction,...) +self:EventDispatcher():OnEventForGroup(self:GetName(),EventFunction,self,Event,...) +return self +end +function GROUP:UnHandleEvent(Event) +self:EventDispatcher():RemoveEvent(self,Event) +return self +end +function GROUP:ResetEvents() +self:EventDispatcher():Reset(self) +for UnitID,UnitData in pairs(self:GetUnits()or{})do +UnitData:ResetEvents() +end +return self +end +end +do +function GROUP:GetPlayerNames() +local HasPlayers=false +local PlayerNames={} +local Units=self:GetUnits() +for UnitID,UnitData in pairs(Units or{})do +local Unit=UnitData +local PlayerName=Unit:GetPlayerName() +if PlayerName and PlayerName~=""then +PlayerNames=PlayerNames or{} +table.insert(PlayerNames,PlayerName) +HasPlayers=true +end +end +if HasPlayers==true then +return PlayerNames +end +return nil +end +function GROUP:GetPlayerCount() +local PlayerCount=0 +local Units=self:GetUnits() +for UnitID,UnitData in pairs(Units or{})do +local Unit=UnitData +local PlayerName=Unit:GetPlayerName() +if PlayerName and PlayerName~=""then +PlayerCount=PlayerCount+1 +end +end +return PlayerCount +end +end +function GROUP:EnableEmission(switch) +local switch=switch or false +local DCSUnit=self:GetDCSObject() +if DCSUnit then +DCSUnit:enableEmission(switch) +end +return self +end +function GROUP:SetCommandInvisible(switch) +return self:CommandSetInvisible(switch) +end +function GROUP:CommandSetInvisible(switch) +if switch==nil then +switch=false +end +local SetInvisible={id='SetInvisible',params={value=switch}} +self:SetCommand(SetInvisible) +return self +end +function GROUP:SetCommandImmortal(switch) +return self:CommandSetImmortal(switch) +end +function GROUP:CommandSetImmortal(switch) +if switch==nil then +switch=false +end +local SetImmortal={id='SetImmortal',params={value=switch}} +self:SetCommand(SetImmortal) +return self +end +function GROUP:GetSkill() +local unit=self:GetUnit(1) +local name=unit:GetName() +local skill=_DATABASE.Templates.Units[name].Template.skill or"Random" +return skill +end +function GROUP:GetHighestThreat() +local units=self:GetUnits() +if units then +local threat=nil;local maxtl=0 +for _,_unit in pairs(units or{})do +local unit=_unit +if unit and unit:IsAlive()then +local tl=unit:GetThreatLevel() +if tl>maxtl then +maxtl=tl +threat=unit +end +end +end +return threat,maxtl +end +return nil,nil +end +function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,CustomFunction,...) +self:T("GetCustomCallSign") +local callsign="Ghost 1" +if self:IsAlive()then +local IsPlayer=self:IsPlayer() +local shortcallsign=self:GetCallsign()or"unknown91" +local callsignroot=string.match(shortcallsign,'(%a+)')or"Ghost" +local groupname=self:GetName() +local callnumber=string.match(shortcallsign,"(%d+)$")or"91" +local callnumbermajor=string.char(string.byte(callnumber,1)) +local callnumberminor=string.char(string.byte(callnumber,2)) +local personalized=false +local playername=shortcallsign +if IsPlayer then playername=self:GetPlayerName()end +self:T2("GetCustomCallSign outcome = "..playername) +if CustomFunction and IsPlayer then +local arguments=arg or{} +local callsign=CustomFunction(groupname,playername,unpack(arguments)) +return callsign +end +if CallsignTranslations and CallsignTranslations[callsignroot]then +callsignroot=CallsignTranslations[callsignroot] +elseif IsPlayer and string.find(groupname,"#")then +if Keepnumber then +shortcallsign=string.match(groupname,"#(.+)")or"Ghost 111" +else +shortcallsign=string.match(groupname,"#%s*([%a]+)")or"Ghost" +end +personalized=true +elseif IsPlayer and string.find(playername,"|")then +shortcallsign=string.match(playername,"|%s*([%a]+)")or string.match(self:GetPlayerName(),"|%s*([%d]+)")or"Ghost" +personalized=true +end +if personalized then +shortcallsign=string.gsub(shortcallsign,"^%s*","") +shortcallsign=string.gsub(shortcallsign,"%s*$","") +if Keepnumber then +return shortcallsign +elseif ShortCallsign then +callsign=shortcallsign.." "..callnumbermajor +else +callsign=shortcallsign.." "..callnumbermajor.." "..callnumberminor +end +return callsign +end +if ShortCallsign then +callsign=callsignroot.." "..callnumbermajor +else +callsign=callsignroot.." "..callnumbermajor.." "..callnumberminor +end +end +return callsign +end +function GROUP:SetAsRecoveryTanker(CarrierGroup,Speed,ToKIAS,Altitude,Delay,LastWaypoint) +local speed=ToKIAS==true and UTILS.KnotsToAltKIAS(Speed,Altitude)or Speed +speed=UTILS.KnotsToMps(speed) +local alt=UTILS.FeetToMeters(Altitude) +local delay=Delay or 1 +local task=self:TaskRecoveryTanker(CarrierGroup,speed,alt,LastWaypoint) +self:SetTask(task,delay) +local tankertask=self:EnRouteTaskTanker() +self:PushTask(tankertask,delay+2) +return self +end +function GROUP:GetGroupSTN() +local tSTN={} +local units=self:GetUnits() +local gname=self:GetName() +gname=string.gsub(gname,"(#%d+)$","") +local report=REPORT:New() +report:Add("Link16 S/TN Report") +report:Add("Group: "..gname) +report:Add("==================") +for _,_unit in pairs(units)do +local unit=_unit +if unit and unit:IsAlive()then +local STN,VCL,VCN,Lead=unit:GetSTN() +local name=unit:GetName() +tSTN[name]={ +STN=STN, +VCL=VCL, +VCN=VCN, +Lead=Lead, +} +local lead=Lead==true and"(*)"or"" +report:Add(string.format("| %s%s %s %s",tostring(VCL),tostring(VCN),tostring(STN),lead)) +end +end +report:Add("==================") +local text=report:Text() +return tSTN,text +end +function GROUP:IsSAM() +local issam=false +local units=self:GetUnits() +for _,_unit in pairs(units or{})do +local unit=_unit +if unit:IsSAM()then +issam=true +break +end +end +return issam +end +function GROUP:IsAAA() +local isAAA=false +local units=self:GetUnits() +for _,_unit in pairs(units or{})do +local unit=_unit +if unit:IsAAA()then +isAAA=true +break +end +end +return isAAA +end +function GROUP:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end +function GROUP:GetBoundingBox() +local bbox={min={x=math.huge,y=math.huge,z=math.huge}, +max={x=-math.huge,y=-math.huge,z=-math.huge} +} +local Units=self:GetUnits()or{} +if#Units==0 then +return nil +end +for _,unit in pairs(Units)do +if unit and unit:IsAlive()then +local ubox=unit:GetBoundingBox() +if ubox then +if ubox.min.xbbox.max.x then +bbox.max.x=ubox.max.x +end +if ubox.max.y>bbox.max.y then +bbox.max.y=ubox.max.y +end +if ubox.max.z>bbox.max.z then +bbox.max.z=ubox.max.z +end +end +end +end +return bbox +end +UNIT={ +ClassName="UNIT", +UnitName=nil, +GroupName=nil, +DCSUnit=nil, +} +function UNIT:Register(UnitName) +local self=BASE:Inherit(self,CONTROLLABLE:New(UnitName)) +self.UnitName=UnitName +local unit=Unit.getByName(self.UnitName) +if unit then +local group=unit:getGroup() +if group then +self.GroupName=group:getName() +self.groupId=group:getID() +end +self.DCSUnit=unit +end +self:SetEventPriority(3) +return self +end +function UNIT:Find(DCSUnit) +if DCSUnit then +local UnitName=DCSUnit:getName() +local UnitFound=_DATABASE:FindUnit(UnitName) +return UnitFound +end +return nil +end +function UNIT:FindByName(UnitName) +local UnitFound=_DATABASE:FindUnit(UnitName) +return UnitFound +end +function UNIT:FindByMatching(Pattern) +local GroupFound=nil +for name,group in pairs(_DATABASE.UNITS)do +if string.match(name,Pattern)then +GroupFound=group +break +end +end +return GroupFound +end +function UNIT:FindAllByMatching(Pattern) +local GroupsFound={} +for name,group in pairs(_DATABASE.UNITS)do +if string.match(name,Pattern)then +GroupsFound[#GroupsFound+1]=group +end +end +return GroupsFound +end +function UNIT:Name() +return self.UnitName +end +function UNIT:GetDCSObject() +if(not self.LastCallDCSObject)or(self.LastCallDCSObject and timer.getTime()-self.LastCallDCSObject>1)or(self.DCSObject==nil)or(self.DCSObject:isExist()==false)then +local DCSUnit=Unit.getByName(self.UnitName) +if DCSUnit then +self.LastCallDCSObject=timer.getTime() +self.DCSObject=DCSUnit +return DCSUnit +else +self.DCSObject=nil +self.LastCallDCSObject=nil +end +else +return self.DCSObject +end +return nil +end +function UNIT:GetAltitude(FromGround) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local altitude=0 +local point=DCSUnit:getPoint() +altitude=point.y +if FromGround then +local land=land.getHeight({x=point.x,y=point.z})or 0 +altitude=altitude-land +end +return altitude +end +return nil +end +function UNIT:ReSpawnAt(Coordinate,Heading) +local SpawnGroupTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplateFromUnitName(self:Name())) +local SpawnGroup=self:GetGroup() +if SpawnGroup then +local Vec3=SpawnGroup:GetVec3() +SpawnGroupTemplate.x=Coordinate.x +SpawnGroupTemplate.y=Coordinate.z +for UnitID,UnitData in pairs(SpawnGroup:GetUnits()or{})do +local GroupUnit=UnitData +if GroupUnit:IsAlive()then +local GroupUnitVec3=GroupUnit:GetVec3() +local GroupUnitHeading=GroupUnit:GetHeading() +SpawnGroupTemplate.units[UnitID].alt=GroupUnitVec3.y +SpawnGroupTemplate.units[UnitID].x=GroupUnitVec3.x +SpawnGroupTemplate.units[UnitID].y=GroupUnitVec3.z +SpawnGroupTemplate.units[UnitID].heading=GroupUnitHeading +end +end +end +for UnitTemplateID,UnitTemplateData in pairs(SpawnGroupTemplate.units)do +SpawnGroupTemplate.units[UnitTemplateID].unitId=nil +if UnitTemplateData.name==self:Name()then +SpawnGroupTemplate.units[UnitTemplateID].alt=Coordinate.y +SpawnGroupTemplate.units[UnitTemplateID].x=Coordinate.x +SpawnGroupTemplate.units[UnitTemplateID].y=Coordinate.z +SpawnGroupTemplate.units[UnitTemplateID].heading=Heading +else +local GroupUnit=UNIT:FindByName(SpawnGroupTemplate.units[UnitTemplateID].name) +if GroupUnit and GroupUnit:IsAlive()then +local GroupUnitVec3=GroupUnit:GetVec3() +local GroupUnitHeading=GroupUnit:GetHeading() +UnitTemplateData.alt=GroupUnitVec3.y +UnitTemplateData.x=GroupUnitVec3.x +UnitTemplateData.y=GroupUnitVec3.z +UnitTemplateData.heading=GroupUnitHeading +else +if SpawnGroupTemplate.units[UnitTemplateID].name~=self:Name()then +SpawnGroupTemplate.units[UnitTemplateID].delete=true +end +end +end +end +local i=1 +while i<=#SpawnGroupTemplate.units do +local UnitTemplateData=SpawnGroupTemplate.units[i] +if UnitTemplateData.delete then +table.remove(SpawnGroupTemplate.units,i) +else +i=i+1 +end +end +SpawnGroupTemplate.groupId=nil +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(SpawnGroupTemplate.units) +end +_DATABASE:Spawn(SpawnGroupTemplate) +end +function UNIT:IsActive() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitIsActive=DCSUnit:isActive() +return UnitIsActive +end +return nil +end +function UNIT:IsExist() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local exists=DCSUnit:isExist() +return exists +end +return nil +end +function UNIT:IsAlive() +local DCSUnit=self:GetDCSObject() +if DCSUnit and DCSUnit:isExist()then +local UnitIsAlive=DCSUnit:isActive() +return UnitIsAlive +end +return nil +end +function UNIT:IsDead() +return not self:IsAlive() +end +function UNIT:GetCallsign() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitCallSign=DCSUnit:getCallsign() +if UnitCallSign==""then +UnitCallSign=DCSUnit:getName() +end +return UnitCallSign +end +return nil +end +function UNIT:IsPlayer() +local group=self:GetGroup() +if not group then +return false +end +local template=group:GetTemplate() +if(template==nil)or(template.units==nil)then +local DCSObject=self:GetDCSObject() +if DCSObject then +if DCSObject:getPlayerName()~=nil then +return true +else +return false +end +else +return false +end +end +local units=template.units +for _,unit in pairs(units)do +if unit.name==self:GetName()and(unit.skill=="Client"or unit.skill=="Player")then +return true +end +end +return false +end +function UNIT:GetPlayerName() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local PlayerName=DCSUnit:getPlayerName() +return PlayerName +end +return nil +end +function UNIT:IsClient() +if _DATABASE.CLIENTS[self.UnitName]then +return true +end +return false +end +function UNIT:GetClient() +local client=_DATABASE.CLIENTS[self.UnitName] +if client then +return client +end +return nil +end +function UNIT:GetNatoReportingName() +local typename=self:GetTypeName() +return UTILS.GetReportingName(typename) +end +function UNIT:GetNumber() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitNumber=DCSUnit:getNumber() +return UnitNumber +end +return nil +end +function UNIT:GetSpeedMax() +local Desc=self:GetDesc() +if Desc then +local SpeedMax=Desc.speedMax or 0 +return SpeedMax*3.6 +end +return 0 +end +function UNIT:GetRange() +local Desc=self:GetDesc() +if Desc then +local Range=Desc.range +if Range then +Range=Range*1000 +else +Range=10000000 +end +return Range +end +return nil +end +function UNIT:IsRefuelable() +local refuelable=self:HasAttribute("Refuelable") +local system=nil +local Desc=self:GetDesc() +if Desc and Desc.tankerType then +system=Desc.tankerType +end +return refuelable,system +end +function UNIT:IsTanker() +local tanker=self:HasAttribute("Tankers") +local system=nil +if tanker then +local Desc=self:GetDesc() +if Desc and Desc.tankerType then +system=Desc.tankerType +end +local typename=self:GetTypeName() +if typename=="IL-78M"then +system=1 +elseif typename=="KC130"or typename=="KC130J"then +system=1 +elseif typename=="KC135BDA"then +system=1 +elseif typename=="KC135MPRS"then +system=1 +elseif typename=="S-3B Tanker"then +system=1 +elseif typename=="KC_10_Extender"then +system=1 +elseif typename=="KC_10_Extender_D"then +system=0 +end +end +return tanker,system +end +function UNIT:IsAmmoSupply() +local typename=self:GetTypeName() +if typename=="M 818"then +return true +elseif typename=="Ural-375"then +return true +elseif typename=="ZIL-135"then +return true +end +return false +end +function UNIT:IsFuelSupply() +local typename=self:GetTypeName() +if typename=="M978 HEMTT Tanker"then +return true +elseif typename=="ATMZ-5"then +return true +elseif typename=="ATMZ-10"then +return true +elseif typename=="ATZ-5"then +return true +end +return false +end +function UNIT:GetGroup() +local UnitGroup=GROUP:FindByName(self.GroupName) +if UnitGroup then +return UnitGroup +else +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local grp=DCSUnit:getGroup() +if grp then +local UnitGroup=GROUP:FindByName(grp:getName()) +return UnitGroup +end +end +end +return nil +end +function UNIT:GetPrefix() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitPrefix=string.match(self.UnitName,".*#"):sub(1,-2) +return UnitPrefix +end +return nil +end +function UNIT:GetAmmo() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitAmmo=DCSUnit:getAmmo() +return UnitAmmo +end +return nil +end +function UNIT:SetUnitInternalCargo(mass) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +trigger.action.setUnitInternalCargo(DCSUnit:getName(),mass) +end +return self +end +function UNIT:GetAmmunition() +local nammo=0 +local nshells=0 +local nrockets=0 +local nmissiles=0 +local nbombs=0 +local narti=0 +local nAPshells=0 +local nHEshells=0 +local unit=self +local ammotable=unit:GetAmmo() +if ammotable then +local weapons=#ammotable +for w=1,weapons do +local Nammo=ammotable[w]["count"] +local Tammo=ammotable[w]["desc"]["typeName"] +local Category=ammotable[w].desc.category +local MissileCategory=nil +if Category==Weapon.Category.MISSILE then +MissileCategory=ammotable[w].desc.missileCategory +end +if Category==Weapon.Category.SHELL then +nshells=nshells+Nammo +if ammotable[w].desc.warhead and ammotable[w].desc.warhead.explosiveMass and ammotable[w].desc.warhead.explosiveMass>0 then +narti=narti+Nammo +end +if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName,"_AP",1,true)then +nAPshells=nAPshells+Nammo +end +if ammotable[w].desc.typeName and(string.find(ammotable[w].desc.typeName,"_HE",1,true)or string.find(ammotable[w].desc.typeName,"HESH",1,true))then +nHEshells=nHEshells+Nammo +end +elseif Category==Weapon.Category.ROCKET then +nrockets=nrockets+Nammo +elseif Category==Weapon.Category.BOMB then +nbombs=nbombs+Nammo +elseif Category==Weapon.Category.MISSILE then +if MissileCategory==Weapon.MissileCategory.AAM then +nmissiles=nmissiles+Nammo +elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then +nmissiles=nmissiles+Nammo +elseif MissileCategory==Weapon.MissileCategory.BM then +nmissiles=nmissiles+Nammo +elseif MissileCategory==Weapon.MissileCategory.OTHER then +nmissiles=nmissiles+Nammo +elseif MissileCategory==Weapon.MissileCategory.SAM then +nmissiles=nmissiles+Nammo +elseif MissileCategory==Weapon.MissileCategory.CRUISE then +nmissiles=nmissiles+Nammo +end +end +end +end +nammo=nshells+nrockets+nmissiles+nbombs +return nammo,nshells,nrockets,nbombs,nmissiles,narti,nAPshells,nHEshells +end +function UNIT:HasAPShells() +local _,_,_,_,_,_,shells=self:GetAmmunition() +if shells>0 then +return true +else +return false +end +end +function UNIT:GetAPShells() +local _,_,_,_,_,_,shells=self:GetAmmunition() +return shells or 0 +end +function UNIT:GetHEShells() +local _,_,_,_,_,_,_,shells=self:GetAmmunition() +return shells or 0 +end +function UNIT:HasHEShells() +local _,_,_,_,_,_,_,shells=self:GetAmmunition() +if shells>0 then +return true +else +return false +end +end +function UNIT:HasArtiShells() +local _,_,_,_,_,shells=self:GetAmmunition() +if shells>0 then +return true +else +return false +end +end +function UNIT:GetArtiShells() +local _,_,_,_,_,shells=self:GetAmmunition() +return shells or 0 +end +function UNIT:GetSensors() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitSensors=DCSUnit:getSensors() +return UnitSensors +end +return nil +end +function UNIT:HasSensors(...) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local HasSensors=DCSUnit:hasSensors(unpack(arg)) +return HasSensors +end +return nil +end +function UNIT:HasSEAD() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitSEADAttributes=DCSUnit:getDesc().attributes +local HasSEAD=false +if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]==true or +UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]==true or +UnitSEADAttributes["Optical Tracker"]and UnitSEADAttributes["Optical Tracker"]==true +then +HasSEAD=true +end +return HasSEAD +end +return nil +end +function UNIT:GetRadar() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitRadarOn,UnitRadarObject=DCSUnit:getRadar() +return UnitRadarOn,UnitRadarObject +end +return nil,nil +end +function UNIT:GetFuel() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitFuel=DCSUnit:getFuel() +return UnitFuel +end +return nil +end +function UNIT:GetUnits() +local DCSUnit=self:GetDCSObject() +local Units={} +if DCSUnit then +Units[1]=UNIT:Find(DCSUnit) +return Units +end +return nil +end +function UNIT:GetLife() +local DCSUnit=self:GetDCSObject() +if DCSUnit and DCSUnit:isExist()then +local UnitLife=DCSUnit:getLife() +return UnitLife +end +return-1 +end +function UNIT:GetLife0() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitLife0=DCSUnit:getLife0() +return UnitLife0 +end +return 0 +end +function UNIT:GetLifeRelative() +if self and self:IsAlive()then +local life0=self:GetLife0() +local lifeN=self:GetLife() +return lifeN/life0 +end +return-1 +end +function UNIT:GetDamageRelative() +if self and self:IsAlive()then +return 1-self:GetLifeRelative() +end +return 1 +end +function UNIT:GetDrawArgumentValue(AnimationArgument) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local value=DCSUnit:getDrawArgumentValue(AnimationArgument or 0) +return value +end +return 0 +end +function UNIT:GetUnitCategory() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +return DCSUnit:getDesc().category +end +return nil +end +function UNIT:GetCategoryName() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local CategoryNames={ +[Unit.Category.AIRPLANE]="Airplane", +[Unit.Category.HELICOPTER]="Helicopter", +[Unit.Category.GROUND_UNIT]="Ground Unit", +[Unit.Category.SHIP]="Ship", +[Unit.Category.STRUCTURE]="Structure", +} +local UnitCategory=DCSUnit:getDesc().category +return CategoryNames[UnitCategory] +end +return nil +end +function UNIT:GetThreatLevel() +local ThreatLevel=0 +local ThreatText="" +local Descriptor=self:GetDesc() +if Descriptor then +local Attributes=Descriptor.attributes +if self:IsGround()then +local ThreatLevels={ +[1]="Unarmed", +[2]="Infantry", +[3]="Old Tanks & APCs", +[4]="Tanks & IFVs without ATGM", +[5]="Tanks & IFV with ATGM", +[6]="Modern Tanks", +[7]="AAA", +[8]="IR Guided SAMs", +[9]="SR SAMs", +[10]="MR SAMs", +[11]="LR SAMs" +} +if Attributes["LR SAM"]then +ThreatLevel=10 +elseif Attributes["MR SAM"]then +ThreatLevel=9 +elseif Attributes["SR SAM"]and +not Attributes["IR Guided SAM"]then +ThreatLevel=8 +elseif(Attributes["SR SAM"]or Attributes["MANPADS"])and +Attributes["IR Guided SAM"]then +ThreatLevel=7 +elseif Attributes["AAA"]then +ThreatLevel=6 +elseif Attributes["Modern Tanks"]then +ThreatLevel=5 +elseif(Attributes["Tanks"]or Attributes["IFV"])and +Attributes["ATGM"]then +ThreatLevel=4 +elseif(Attributes["Tanks"]or Attributes["IFV"])and +not Attributes["ATGM"]then +ThreatLevel=3 +elseif Attributes["Old Tanks"]or Attributes["APC"]or Attributes["Artillery"]then +ThreatLevel=2 +elseif Attributes["Infantry"]or Attributes["EWR"]then +ThreatLevel=1 +end +ThreatText=ThreatLevels[ThreatLevel+1] +end +if self:IsAir()then +local ThreatLevels={ +[1]="Unarmed", +[2]="Tanker", +[3]="AWACS", +[4]="Transport Helicopter", +[5]="UAV", +[6]="Bomber", +[7]="Strategic Bomber", +[8]="Attack Helicopter", +[9]="Battleplane", +[10]="Multirole Fighter", +[11]="Fighter" +} +if Attributes["Fighters"]then +ThreatLevel=10 +elseif Attributes["Multirole fighters"]then +ThreatLevel=9 +elseif Attributes["Interceptors"]then +ThreatLevel=9 +elseif Attributes["Battleplanes"]then +ThreatLevel=8 +elseif Attributes["Battle airplanes"]then +ThreatLevel=8 +elseif Attributes["Attack helicopters"]then +ThreatLevel=7 +elseif Attributes["Strategic bombers"]then +ThreatLevel=6 +elseif Attributes["Bombers"]then +ThreatLevel=5 +elseif Attributes["UAVs"]then +ThreatLevel=4 +elseif Attributes["Transport helicopters"]then +ThreatLevel=3 +elseif Attributes["AWACS"]then +ThreatLevel=2 +elseif Attributes["Tankers"]then +ThreatLevel=1 +end +ThreatText=ThreatLevels[ThreatLevel+1] +end +if self:IsShip()then +local ThreatLevels={ +[1]="Unarmed ship", +[2]="Light armed ships", +[3]="Corvettes", +[4]="", +[5]="Frigates", +[6]="", +[7]="Cruiser", +[8]="", +[9]="Destroyer", +[10]="", +[11]="Aircraft Carrier" +} +if Attributes["Aircraft Carriers"]then +ThreatLevel=10 +elseif Attributes["Destroyers"]then +ThreatLevel=8 +elseif Attributes["Cruisers"]then +ThreatLevel=6 +elseif Attributes["Frigates"]then +ThreatLevel=4 +elseif Attributes["Corvettes"]then +ThreatLevel=2 +elseif Attributes["Light armed ships"]then +ThreatLevel=1 +end +ThreatText=ThreatLevels[ThreatLevel+1] +end +end +return ThreatLevel,ThreatText +end +function UNIT:Explode(power,delay) +power=power or 100 +local DCSUnit=self:GetDCSObject() +if DCSUnit then +if delay and delay>0 then +SCHEDULER:New(nil,self.Explode,{self,power},delay) +else +self:GetCoordinate():Explosion(power) +end +return self +end +return nil +end +function UNIT:OtherUnitInRadius(AwaitUnit,Radius) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitVec3=self:GetVec3() +local AwaitUnitVec3=AwaitUnit:GetVec3() +if(((UnitVec3.x-AwaitUnitVec3.x)^2+(UnitVec3.z-AwaitUnitVec3.z)^2)^0.5<=Radius)then +return true +else +return false +end +end +return nil +end +function UNIT:IsFriendly(FriendlyCoalition) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitCoalition=DCSUnit:getCoalition() +local IsFriendlyResult=(UnitCoalition==FriendlyCoalition) +return IsFriendlyResult +end +return nil +end +function UNIT:IsShip() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitDescriptor=DCSUnit:getDesc() +local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP) +return IsShipResult +end +return nil +end +function UNIT:InAir(NoHeloCheck) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitInAir=DCSUnit:inAir() +local UnitCategory=DCSUnit:getDesc().category +if UnitInAir==true and UnitCategory==Unit.Category.HELICOPTER and(not NoHeloCheck)then +local VelocityVec3=DCSUnit:getVelocity() +local Velocity=UTILS.VecNorm(VelocityVec3) +local Coordinate=DCSUnit:getPoint() +local LandHeight=land.getHeight({x=Coordinate.x,y=Coordinate.z}) +local Height=Coordinate.y-LandHeight +if Velocity<1 and Height<=60 then +UnitInAir=false +end +end +return UnitInAir +end +return nil +end +do +function UNIT:HandleEvent(EventID,EventFunction) +self:EventDispatcher():OnEventForUnit(self:GetName(),EventFunction,self,EventID) +return self +end +function UNIT:UnHandleEvent(EventID) +self:EventDispatcher():RemoveEvent(self,EventID) +return self +end +function UNIT:ResetEvents() +self:EventDispatcher():Reset(self) +return self +end +end +do +function UNIT:IsDetected(TargetUnit) +local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=self:IsTargetDetected(TargetUnit:GetDCSObject()) +return TargetIsDetected +end +function UNIT:IsLOS(TargetUnit) +local IsLOS=self:GetPointVec3():IsLOS(TargetUnit:GetPointVec3()) +return IsLOS +end +function UNIT:KnowUnit(TargetUnit,TypeKnown,DistanceKnown) +if TypeKnown~=false then +TypeKnown=true +end +if DistanceKnown~=false then +DistanceKnown=true +end +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=DCSControllable:getController() +if Controller then +local object=TargetUnit:GetDCSObject() +if object then +self:I(string.format("Unit %s now knows target unit %s. Type known=%s, distance known=%s",self:GetName(),TargetUnit:GetName(),tostring(TypeKnown),tostring(DistanceKnown))) +Controller:knowTarget(object,TypeKnown,DistanceKnown) +end +end +end +end +end +function UNIT:GetTemplate() +local group=self:GetGroup() +local name=self:GetName() +if group then +local template=group:GetTemplate() +if template then +for _,unit in pairs(template.units)do +if unit.name==name then +return UTILS.DeepCopy(unit) +end +end +end +end +return nil +end +function UNIT:GetTemplatePayload() +local unit=self:GetTemplate() +if unit then +return unit.payload +end +return nil +end +function UNIT:GetTemplatePylons() +local payload=self:GetTemplatePayload() +if payload then +return payload.pylons +end +return nil +end +function UNIT:GetTemplateFuel() +local payload=self:GetTemplatePayload() +if payload then +return payload.fuel +end +return nil +end +function UNIT:EnableEmission(switch) +local switch=switch or false +local DCSUnit=self:GetDCSObject() +if DCSUnit then +DCSUnit:enableEmission(switch) +end +return self +end +function UNIT:GetSkill() +local name=self.UnitName +local skill="Random" +if _DATABASE.Templates.Units[name]and _DATABASE.Templates.Units[name].Template and _DATABASE.Templates.Units[name].Template.skill then +skill=_DATABASE.Templates.Units[name].Template.skill or"Random" +end +return skill +end +function UNIT:GetSTN() +local STN=nil +local VCL=nil +local VCN=nil +local FGL=false +local template=self:GetTemplate() +if template then +if template.AddPropAircraft then +if template.AddPropAircraft.STN_L16 then +STN=template.AddPropAircraft.STN_L16 +elseif template.AddPropAircraft.SADL_TN then +STN=template.AddPropAircraft.SADL_TN +end +VCN=template.AddPropAircraft.VoiceCallsignNumber +VCL=template.AddPropAircraft.VoiceCallsignLabel +end +if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then +FGL=template.datalinks.Link16.settings.flightLead +end +if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then +FGL=template.datalinks.SADL.settings.flightLead +end +end +return STN,VCL,VCN,FGL +end +do +function UNIT:SetAIOnOff(AIOnOff) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local DCSController=DCSUnit:getController() +if DCSController then +DCSController:setOnOff(AIOnOff) +return self +end +end +return nil +end +function UNIT:SetAIOn() +return self:SetAIOnOff(true) +end +function UNIT:SetAIOff() +return self:SetAIOnOff(false) +end +end +function UNIT:IsSAM() +if self:HasSEAD()and self:IsGround()and(not self:HasAttribute("Mobile AAA"))then +return true +end +return false +end +function UNIT:IsEWR() +if self:IsGround()then +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local attrs=DCSUnit:getDesc().attributes +return attrs["EWR"]==true +end +end +return false +end +function UNIT:IsAAA() +local unit=self +local desc=unit:GetDesc()or{} +local attr=desc.attributes or{} +if unit:HasSEAD()then +return false +end +if attr["AAA"]or attr["SAM related"]then +return true +end +return false +end +function UNIT:SetLife(Percent) +net.dostring_in("mission",string.format("a_unit_set_life_percentage(%d, %f)",self:GetID(),Percent)) +end +function UNIT:SetCarrierIlluminationMode(Mode) +UTILS.SetCarrierIlluminationMode(self:GetID(),Mode) +end +function UNIT:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end +CLIENT={ +ClassName="CLIENT", +ClientName=nil, +ClientAlive=false, +ClientTransport=false, +ClientBriefingShown=false, +_Menus={}, +_Tasks={}, +Messages={}, +Players={}, +} +function CLIENT:Find(DCSUnit,Error) +local ClientName=DCSUnit:getName() +local ClientFound=_DATABASE:FindClient(ClientName) +if ClientFound then +ClientFound:F(ClientName) +return ClientFound +end +if not Error then +error("CLIENT not found for: "..ClientName) +end +end +function CLIENT:FindByPlayerName(Name) +local foundclient=nil +_DATABASE:ForEachClient( +function(client) +if client:GetPlayerName()==Name then +foundclient=client +end +end +) +return foundclient +end +function CLIENT:FindByName(ClientName,ClientBriefing,Error) +local ClientFound=_DATABASE:FindClient(ClientName) +if ClientFound then +ClientFound:F({ClientName,ClientBriefing}) +ClientFound:AddBriefing(ClientBriefing) +ClientFound.MessageSwitch=true +return ClientFound +end +if not Error then +error("CLIENT not found for: "..ClientName) +end +end +function CLIENT:Register(ClientName) +local self=BASE:Inherit(self,UNIT:Register(ClientName)) +self.ClientName=ClientName +self.MessageSwitch=true +self.ClientAlive2=false +return self +end +function CLIENT:Transport() +self.ClientTransport=true +return self +end +function CLIENT:AddBriefing(ClientBriefing) +self.ClientBriefing=ClientBriefing +self.ClientBriefingShown=false +return self +end +function CLIENT:AddPlayer(PlayerName) +table.insert(self.Players,PlayerName) +return self +end +function CLIENT:CountPlayers() +return#self.Players or 0 +end +function CLIENT:GetPlayers() +return self.Players +end +function CLIENT:GetPlayer() +if#self.Players>0 then +return self.Players[1] +end +return nil +end +function CLIENT:RemovePlayer(PlayerName) +for i,playername in pairs(self.Players)do +if PlayerName==playername then +table.remove(self.Players,i) +break +end +end +return self +end +function CLIENT:RemovePlayers() +self.Players={} +return self +end +function CLIENT:ShowBriefing() +if not self.ClientBriefingShown then +self.ClientBriefingShown=true +local Briefing="" +if self.ClientBriefing and self.ClientBriefing~=""then +Briefing=Briefing..self.ClientBriefing +self:Message(Briefing,60,"Briefing") +end +end +return self +end +function CLIENT:ShowMissionBriefing(MissionBriefing) +self:F({self.ClientName}) +if MissionBriefing then +self:Message(MissionBriefing,60,"Mission Briefing") +end +return self +end +function CLIENT:Reset(ClientName) +self:F() +self._Menus={} +end +function CLIENT:IsMultiSeated() +self:F(self.ClientName) +local ClientMultiSeatedTypes={ +["Mi-8MT"]="Mi-8MT", +["UH-1H"]="UH-1H", +["P-51B"]="P-51B" +} +if self:IsAlive()then +local ClientTypeName=self:GetClientGroupUnit():GetTypeName() +if ClientMultiSeatedTypes[ClientTypeName]then +return true +end +end +return false +end +function CLIENT:Alive(CallBackFunction,...) +self:F() +self.ClientCallBack=CallBackFunction +self.ClientParameters=arg +self.AliveCheckScheduler=SCHEDULER:New(self,self._AliveCheckScheduler,{"Client Alive "..self.ClientName},0.1,5,0.5) +self.AliveCheckScheduler:NoTrace() +return self +end +function CLIENT:_AliveCheckScheduler(SchedulerName) +self:T2({SchedulerName,self.ClientName,self.ClientAlive2,self.ClientBriefingShown,self.ClientCallBack}) +if self:IsAlive()then +if self.ClientAlive2==false then +self:ShowBriefing() +if self.ClientCallBack then +self:T("Calling Callback function") +self.ClientCallBack(self,unpack(self.ClientParameters)) +end +self.ClientAlive2=true +end +else +if self.ClientAlive2==true then +self.ClientAlive2=false +end +end +return true +end +function CLIENT:GetDCSGroup() +self:F3() +local ClientUnit=Unit.getByName(self.ClientName) +local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE)} +for CoalitionId,CoalitionData in pairs(CoalitionsData)do +self:T3({"CoalitionData:",CoalitionData}) +for UnitId,UnitData in pairs(CoalitionData)do +self:T3({"UnitData:",UnitData}) +if UnitData and UnitData:isExist()then +if ClientUnit then +local ClientGroup=ClientUnit:getGroup() +if ClientGroup then +self:T3("ClientGroup = "..self.ClientName) +if ClientGroup:isExist()and UnitData:getGroup():isExist()then +if ClientGroup:getID()==UnitData:getGroup():getID()then +self:T3("Normal logic") +self:T3(self.ClientName.." : group found!") +self.ClientGroupID=ClientGroup:getID() +self.ClientGroupName=ClientGroup:getName() +return ClientGroup +end +else +self:T3("Bug 1.5 logic") +local ClientGroupTemplate=_DATABASE.Templates.Units[self.ClientName].GroupTemplate +self.ClientGroupID=ClientGroupTemplate.groupId +self.ClientGroupName=_DATABASE.Templates.Units[self.ClientName].GroupName +self:T3(self.ClientName.." : group found in bug 1.5 resolvement logic!") +return ClientGroup +end +end +else +end +end +end +end +if ClientUnit then +local ClientGroup=ClientUnit:getGroup() +if ClientGroup then +self:T3("ClientGroup = "..self.ClientName) +if ClientGroup:isExist()then +self:T3("Normal logic") +self:T3(self.ClientName.." : group found!") +return ClientGroup +end +end +end +self.ClientGroupID=nil +self.ClientGroupName=nil +return nil +end +function CLIENT:GetClientGroupID() +self:GetDCSGroup() +return self.ClientGroupID +end +function CLIENT:GetClientGroupName() +self:GetDCSGroup() +return self.ClientGroupName +end +function CLIENT:GetClientGroupUnit() +self:F2() +local ClientDCSUnit=Unit.getByName(self.ClientName) +self:T(self.ClientDCSUnit) +if ClientDCSUnit and ClientDCSUnit:isExist()then +local ClientUnit=_DATABASE:FindUnit(self.ClientName) +return ClientUnit +end +return nil +end +function CLIENT:GetClientGroupDCSUnit() +self:F2() +local ClientDCSUnit=Unit.getByName(self.ClientName) +if ClientDCSUnit and ClientDCSUnit:isExist()then +self:T2(ClientDCSUnit) +return ClientDCSUnit +end +end +function CLIENT:IsTransport() +self:F() +return self.ClientTransport +end +function CLIENT:ShowCargo() +self:F() +local CargoMsg="" +for CargoName,Cargo in pairs(CARGOS)do +if self==Cargo:IsLoadedInClient()then +CargoMsg=CargoMsg..Cargo.CargoName.." Type:"..Cargo.CargoType.." Weight: "..Cargo.CargoWeight.."\n" +end +end +if CargoMsg==""then +CargoMsg="empty" +end +self:Message(CargoMsg,15,"Co-Pilot: Cargo Status",30) +end +function CLIENT:Message(Message,MessageDuration,MessageCategory,MessageInterval,MessageID) +self:F({Message,MessageDuration,MessageCategory,MessageInterval}) +if self.MessageSwitch==true then +if MessageCategory==nil then +MessageCategory="Messages" +end +if MessageID~=nil then +if self.Messages[MessageID]==nil then +self.Messages[MessageID]={} +self.Messages[MessageID].MessageId=MessageID +self.Messages[MessageID].MessageTime=timer.getTime() +self.Messages[MessageID].MessageDuration=MessageDuration +if MessageInterval==nil then +self.Messages[MessageID].MessageInterval=600 +else +self.Messages[MessageID].MessageInterval=MessageInterval +end +MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) +else +if self:GetClientGroupDCSUnit()and not self:GetClientGroupDCSUnit():inAir()then +if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+10 then +MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) +self.Messages[MessageID].MessageTime=timer.getTime() +end +else +if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+self.Messages[MessageID].MessageInterval then +MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) +self.Messages[MessageID].MessageTime=timer.getTime() +end +end +end +else +MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) +end +end +end +function CLIENT:GetUCID() +local PID=NET.GetPlayerIDByName(nil,self:GetPlayerName()) +return net.get_player_info(tonumber(PID),'ucid') +end +function CLIENT:GetPlayerInfo(Attribute) +local PID=NET.GetPlayerIDByName(nil,self:GetPlayerName()) +if PID then +return net.get_player_info(tonumber(PID),Attribute) +else +return nil +end +end +STATIC={ +ClassName="STATIC", +} +function STATIC:Register(StaticName) +local self=BASE:Inherit(self,POSITIONABLE:New(StaticName)) +self.StaticName=StaticName +local DCSStatic=StaticObject.getByName(self.StaticName) +if DCSStatic then +local Life0=DCSStatic:getLife()or 1 +self.Life0=Life0 +else +self:E(string.format("Static object %s does not exist!",tostring(self.StaticName))) +end +return self +end +function STATIC:GetLife0() +return self.Life0 or 1 +end +function STATIC:GetLife() +local DCSStatic=StaticObject.getByName(self.StaticName) +if DCSStatic then +return DCSStatic:getLife()or 1 +end +return nil +end +function STATIC:Find(DCSStatic) +local StaticName=DCSStatic:getName() +local StaticFound=_DATABASE:FindStatic(StaticName) +return StaticFound +end +function STATIC:FindByName(StaticName,RaiseError) +local StaticFound=_DATABASE:FindStatic(StaticName) +self.StaticName=StaticName +if StaticFound then +return StaticFound +end +if RaiseError==nil or RaiseError==true then +error("STATIC not found for: "..StaticName) +end +return nil +end +function STATIC:Destroy(GenerateEvent) +self:F2(self.ObjectName) +local DCSObject=self:GetDCSObject() +if DCSObject then +local StaticName=DCSObject:getName() +self:F({StaticName=StaticName}) +if GenerateEvent and GenerateEvent==true then +if self:IsAir()then +self:CreateEventCrash(timer.getTime(),DCSObject) +else +self:CreateEventDead(timer.getTime(),DCSObject) +end +elseif GenerateEvent==false then +else +self:CreateEventRemoveUnit(timer.getTime(),DCSObject) +end +DCSObject:destroy() +return true +end +return nil +end +function STATIC:GetDCSObject() +local DCSStatic=StaticObject.getByName(self.StaticName) +if DCSStatic then +return DCSStatic +end +return nil +end +function STATIC:GetUnits() +self:F2({self.StaticName}) +local DCSStatic=self:GetDCSObject() +local Statics={} +if DCSStatic then +Statics[1]=STATIC:Find(DCSStatic) +self:T3(Statics) +return Statics +end +return nil +end +function STATIC:GetThreatLevel() +return 1,"Static" +end +function STATIC:SpawnAt(Coordinate,Heading,Delay) +Heading=Heading or 0 +if Delay and Delay>0 then +SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) +else +local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) +SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +end +return self +end +function STATIC:ReSpawn(CountryID,Delay) +if Delay and Delay>0 then +SCHEDULER:New(nil,self.ReSpawn,{self,CountryID},Delay) +else +CountryID=CountryID or self:GetCountry() +local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) +SpawnStatic:Spawn(nil,self.StaticName) +end +return self +end +function STATIC:ReSpawnAt(Coordinate,Heading,Delay) +if Delay and Delay>0 then +SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) +else +local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) +SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +end +return self +end +function STATIC:FindByMatching(Pattern) +local GroupFound=nil +for name,static in pairs(_DATABASE.STATICS)do +if string.match(name,Pattern)then +GroupFound=static +break +end +end +return GroupFound +end +function STATIC:FindAllByMatching(Pattern) +local GroupsFound={} +for name,static in pairs(_DATABASE.STATICS)do +if string.match(name,Pattern)then +GroupsFound[#GroupsFound+1]=static +end +end +return GroupsFound +end +function STATIC:GetStaticStorage() +local name=self:GetName() +local storage=STORAGE:NewFromStaticCargo(name) +return storage +end +function STATIC:GetCargoWeight() +local DCSObject=StaticObject.getByName(self.StaticName) +local mass=-1 +if DCSObject then +mass=DCSObject:getCargoWeight()or 0 +local masstxt=DCSObject:getCargoDisplayName()or"none" +end +return mass +end +AIRBASE={ +ClassName="AIRBASE", +CategoryName={ +[Airbase.Category.AIRDROME]="Airdrome", +[Airbase.Category.HELIPAD]="Helipad", +[Airbase.Category.SHIP]="Ship", +}, +activerwyno=nil, +} +AIRBASE.Caucasus={ +["Anapa_Vityazevo"]="Anapa-Vityazevo", +["Batumi"]="Batumi", +["Beslan"]="Beslan", +["Gelendzhik"]="Gelendzhik", +["Gudauta"]="Gudauta", +["Kobuleti"]="Kobuleti", +["Krasnodar_Center"]="Krasnodar-Center", +["Krasnodar_Pashkovsky"]="Krasnodar-Pashkovsky", +["Krymsk"]="Krymsk", +["Kutaisi"]="Kutaisi", +["Maykop_Khanskaya"]="Maykop-Khanskaya", +["Mineralnye_Vody"]="Mineralnye Vody", +["Mozdok"]="Mozdok", +["Nalchik"]="Nalchik", +["Novorossiysk"]="Novorossiysk", +["Senaki_Kolkhi"]="Senaki-Kolkhi", +["Sochi_Adler"]="Sochi-Adler", +["Soganlug"]="Soganlug", +["Sukhumi_Babushara"]="Sukhumi-Babushara", +["Tbilisi_Lochini"]="Tbilisi-Lochini", +["Vaziani"]="Vaziani", +} +AIRBASE.Nevada={ +["Beatty"]="Beatty", +["Boulder_City"]="Boulder City", +["Creech"]="Creech", +["Echo_Bay"]="Echo Bay", +["Groom_Lake"]="Groom Lake", +["Henderson_Executive"]="Henderson Executive", +["Jean"]="Jean", +["Laughlin"]="Laughlin", +["Lincoln_County"]="Lincoln County", +["McCarran_International"]="McCarran International", +["Mesquite"]="Mesquite", +["Mina"]="Mina", +["Nellis"]="Nellis", +["North_Las_Vegas"]="North Las Vegas", +["Pahute_Mesa"]="Pahute Mesa", +["Tonopah"]="Tonopah", +["Tonopah_Test_Range"]="Tonopah Test Range", +} +AIRBASE.Normandy={ +["Abbeville_Drucat"]="Abbeville Drucat", +["Amiens_Glisy"]="Amiens-Glisy", +["Argentan"]="Argentan", +["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", +["Azeville"]="Azeville", +["Barville"]="Barville", +["Bazenville"]="Bazenville", +["Beaumont_le_Roger"]="Beaumont-le-Roger", +["Beauvais_Tille"]="Beauvais-Tille", +["Beny_sur_Mer"]="Beny-sur-Mer", +["Bernay_Saint_Martin"]="Bernay Saint Martin", +["Beuzeville"]="Beuzeville", +["Biggin_Hill"]="Biggin Hill", +["Biniville"]="Biniville", +["Broglie"]="Broglie", +["Brucheville"]="Brucheville", +["Cardonville"]="Cardonville", +["Carpiquet"]="Carpiquet", +["Chailey"]="Chailey", +["Chippelle"]="Chippelle", +["Conches"]="Conches", +["Cormeilles_en_Vexin"]="Cormeilles-en-Vexin", +["Creil"]="Creil", +["Cretteville"]="Cretteville", +["Cricqueville_en_Bessin"]="Cricqueville-en-Bessin", +["Deanland"]="Deanland", +["Deauville"]="Deauville", +["Detling"]="Detling", +["Deux_Jumeaux"]="Deux Jumeaux", +["Dinan_Trelivan"]="Dinan-Trelivan", +["Dunkirk_Mardyck"]="Dunkirk-Mardyck", +["Essay"]="Essay", +["Evreux"]="Evreux", +["Farnborough"]="Farnborough", +["Fecamp_Benouville"]="Fecamp-Benouville", +["Flers"]="Flers", +["Ford"]="Ford", +["Friston"]="Friston", +["Funtington"]="Funtington", +["Goulet"]="Goulet", +["Gravesend"]="Gravesend", +["Guyancourt"]="Guyancourt", +["Hauterive"]="Hauterive", +["Heathrow"]="Heathrow", +["High_Halden"]="High Halden", +["Kenley"]="Kenley", +["Lantheuil"]="Lantheuil", +["Le_Molay"]="Le Molay", +["Lessay"]="Lessay", +["Lignerolles"]="Lignerolles", +["Longues_sur_Mer"]="Longues-sur-Mer", +["Lonrai"]="Lonrai", +["Lymington"]="Lymington", +["Lympne"]="Lympne", +["Manston"]="Manston", +["Maupertus"]="Maupertus", +["Meautis"]="Meautis", +["Merville_Calonne"]="Merville Calonne", +["Needs_Oar_Point"]="Needs Oar Point", +["Odiham"]="Odiham", +["Orly"]="Orly", +["Picauville"]="Picauville", +["Poix"]="Poix", +["Ronai"]="Ronai", +["Rouen_Boos"]="Rouen-Boos", +["Rucqueville"]="Rucqueville", +["Saint_Andre_de_lEure"]="Saint-Andre-de-lEure", +["Saint_Aubin"]="Saint-Aubin", +["Saint_Omer_Wizernes"]="Saint-Omer Wizernes", +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", +["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", +["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", +["Sommervieu"]="Sommervieu", +["Stoney_Cross"]="Stoney Cross", +["Tangmere"]="Tangmere", +["Triqueville"]="Triqueville", +["Villacoublay"]="Villacoublay", +["Vrigny"]="Vrigny", +["West_Malling"]="West Malling", +["Eastchurch"]="Eastchurch", +["Headcorn"]="Headcorn", +["Hawkinge"]="Hawkinge", +} +AIRBASE.PersianGulf={ +["Abu_Dhabi_Intl"]="Abu Dhabi Intl", +["Abu_Musa_Island"]="Abu Musa Island", +["Al_Ain_Intl"]="Al Ain Intl", +["Al_Bateen"]="Al-Bateen", +["Al_Dhafra_AFB"]="Al Dhafra AFB", +["Al_Maktoum_Intl"]="Al Maktoum Intl", +["Al_Minhad_AFB"]="Al Minhad AFB", +["Bandar_Abbas_Intl"]="Bandar Abbas Intl", +["Bandar_Lengeh"]="Bandar Lengeh", +["Bandar_e_Jask"]="Bandar-e-Jask", +["Dubai_Intl"]="Dubai Intl", +["Fujairah_Intl"]="Fujairah Intl", +["Havadarya"]="Havadarya", +["Jiroft"]="Jiroft", +["Kerman"]="Kerman", +["Khasab"]="Khasab", +["Kish_Intl"]="Kish Intl", +["Lar"]="Lar", +["Lavan_Island"]="Lavan Island", +["Liwa_AFB"]="Liwa AFB", +["Qeshm_Island"]="Qeshm Island", +["Quasoura_airport"]="Quasoura_airport", +["Ras_Al_Khaimah_Intl"]="Ras Al Khaimah Intl", +["Sas_Al_Nakheel"]="Sas Al Nakheel", +["Sharjah_Intl"]="Sharjah Intl", +["Shiraz_Intl"]="Shiraz Intl", +["Sir_Abu_Nuayr"]="Sir Abu Nuayr", +["Sirri_Island"]="Sirri Island", +["Tunb_Island_AFB"]="Tunb Island AFB", +["Tunb_Kochak"]="Tunb Kochak", +} +AIRBASE.TheChannel={ +["Abbeville_Drucat"]="Abbeville Drucat", +["Biggin_Hill"]="Biggin Hill", +["Detling"]="Detling", +["Dunkirk_Mardyck"]="Dunkirk Mardyck", +["Eastchurch"]="Eastchurch", +["Hawkinge"]="Hawkinge", +["Headcorn"]="Headcorn", +["High_Halden"]="High Halden", +["Lympne"]="Lympne", +["Manston"]="Manston", +["Merville_Calonne"]="Merville Calonne", +["Saint_Omer_Longuenesse"]="Saint Omer Longuenesse", +} +AIRBASE.Syria={ +["Abu_al_Duhur"]="Abu al-Duhur", +["Adana_Sakirpasa"]="Adana Sakirpasa", +["Akrotiri"]="Akrotiri", +["Al_Dumayr"]="Al-Dumayr", +["Al_Qusayr"]="Al Qusayr", +["Aleppo"]="Aleppo", +["An_Nasiriyah"]="An Nasiriyah", +["At_Tanf"]="At Tanf", +["Bassel_Al_Assad"]="Bassel Al-Assad", +["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["Damascus"]="Damascus", +["Deir_ez_Zor"]="Deir ez-Zor", +["Ercan"]="Ercan", +["Eyn_Shemer"]="Eyn Shemer", +["Gaziantep"]="Gaziantep", +["Gazipasa"]="Gazipasa", +["Gecitkale"]="Gecitkale", +["H"]="H", +["H3"]="H3", +["H3_Northwest"]="H3 Northwest", +["H3_Southwest"]="H3 Southwest", +["H4"]="H4", +["Haifa"]="Haifa", +["Hama"]="Hama", +["Hatay"]="Hatay", +["Herzliya"]="Herzliya", +["Incirlik"]="Incirlik", +["Jirah"]="Jirah", +["Khalkhalah"]="Khalkhalah", +["Kharab_Ishk"]="Kharab Ishk", +["King_Abdullah_II"]="King Abdullah II", +["King_Hussein_Air_College"]="King Hussein Air College", +["Kingsfield"]="Kingsfield", +["Kiryat_Shmona"]="Kiryat Shmona", +["Kuweires"]="Kuweires", +["Lakatamia"]="Lakatamia", +["Larnaca"]="Larnaca", +["Marka"]="Marka", +["Marj_Ruhayyil"]="Marj Ruhayyil", +["Marj_as_Sultan_North"]="Marj as Sultan North", +["Marj_as_Sultan_South"]="Marj as Sultan South", +["Megiddo"]="Megiddo", +["Mezzeh"]="Mezzeh", +["Minakh"]="Minakh", +["Muwaffaq_Salti"]="Muwaffaq Salti", +["Naqoura"]="Naqoura", +["Nicosia"]="Nicosia", +["Palmyra"]="Palmyra", +["Paphos"]="Paphos", +["Pinarbashi"]="Pinarbashi", +["Prince_Hassan"]="Prince Hassan", +["Qabr_as_Sitt"]="Qabr as Sitt", +["Ramat_David"]="Ramat David", +["Rayak"]="Rayak", +["Rene_Mouawad"]="Rene Mouawad", +["Rosh_Pina"]="Rosh Pina", +["Ruwayshid"]="Ruwayshid", +["Sanliurfa"]="Sanliurfa", +["Sayqal"]="Sayqal", +["Shayrat"]="Shayrat", +["Tabqa"]="Tabqa", +["Taftanaz"]="Taftanaz", +["Tal_Siman"]="Tal Siman", +["Tha_lah"]="Tha'lah", +["Tiyas"]="Tiyas", +["Wujah_Al_Hajar"]="Wujah Al Hajar", +["Ben_Gurion"]="Ben Gurion", +["Hatzor"]="Hatzor", +["Palmachim"]="Palmachim", +["Tel_Nof"]="Tel Nof", +} +AIRBASE.MarianaIslands={ +["Andersen_AFB"]="Andersen AFB", +["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", +["North_West_Field"]="North West Field", +["Olf_Orote"]="Olf Orote", +["Pagan_Airstrip"]="Pagan Airstrip", +["Rota_Intl"]="Rota Intl", +["Saipan_Intl"]="Saipan Intl", +["Tinian_Intl"]="Tinian Intl", +} +AIRBASE.MarianaIslandsWWII= +{ +["Agana"]="Agana", +["Airfield_3"]="Airfield 3", +["Charon_Kanoa"]="Charon Kanoa", +["Gurguan_Point"]="Gurguan Point", +["Isley"]="Isley", +["Kagman"]="Kagman", +["Marpi"]="Marpi", +["Orote"]="Orote", +["Pagan"]="Pagan", +["Rota"]="Rota", +["Ushi"]="Ushi", +} +AIRBASE.SouthAtlantic={ +["Almirante_Schroeders"]="Almirante Schroeders", +["Comandante_Luis_Piedrabuena"]="Comandante Luis Piedrabuena", +["Cullen"]="Cullen", +["El_Calafate"]="El Calafate", +["Franco_Bianco"]="Franco Bianco", +["Gobernador_Gregores"]="Gobernador Gregores", +["Goose_Green"]="Goose Green", +["Gull_Point"]="Gull Point", +["Hipico_Flying_Club"]="Hipico Flying Club", +["Mount_Pleasant"]="Mount Pleasant", +["O_Higgins"]="O'Higgins", +["Pampa_Guanaco"]="Pampa Guanaco", +["Port_Stanley"]="Port Stanley", +["Porvenir"]="Porvenir", +["Puerto_Natales"]="Puerto Natales", +["Puerto_Santa_Cruz"]="Puerto Santa Cruz", +["Puerto_Williams"]="Puerto Williams", +["Punta_Arenas"]="Punta Arenas", +["Rio_Chico"]="Rio Chico", +["Rio_Gallegos"]="Rio Gallegos", +["Rio_Grande"]="Rio Grande", +["Rio_Turbio"]="Rio Turbio", +["San_Carlos_FOB"]="San Carlos FOB", +["San_Julian"]="San Julian", +["Tolhuin"]="Tolhuin", +["Ushuaia"]="Ushuaia", +["Ushuaia_Helo_Port"]="Ushuaia Helo Port", +} +AIRBASE.Sinai={ +["Abu_Rudeis"]="Abu Rudeis", +["Abu_Suwayr"]="Abu Suwayr", +["Al_Bahr_al_Ahmar"]="Al Bahr al Ahmar", +["Al_Ismailiyah"]="Al Ismailiyah", +["Al_Khatatbah"]="Al Khatatbah", +["Al_Mansurah"]="Al Mansurah", +["Al_Rahmaniyah_Air_Base"]="Al Rahmaniyah Air Base", +["As_Salihiyah"]="As Salihiyah", +["AzZaqaziq"]="AzZaqaziq", +["Baluza"]="Baluza", +["Ben_Gurion"]="Ben-Gurion", +["Beni_Suef"]="Beni Suef", +["Bilbeis_Air_Base"]="Bilbeis Air Base", +["Bir_Hasanah"]="Bir Hasanah", +["Birma_Air_Base"]="Birma Air Base", +["Borg_El_Arab_International_Airport"]="Borg El Arab International Airport", +["Cairo_International_Airport"]="Cairo International Airport", +["Cairo_West"]="Cairo West", +["Damascus_Intl"]="Damascus Intl", +["Difarsuwar_Airfield"]="Difarsuwar Airfield", +["El_Arish"]="El Arish", +["El_Gora"]="El Gora", +["El_Minya"]="El Minya", +["Fayed"]="Fayed", +["Gebel_El_Basur_Air_Base"]="Gebel El Basur Air Base", +["Hatzerim"]="Hatzerim", +["Hatzor"]="Hatzor", +["Hurghada_International_Airport"]="Hurghada International Airport", +["Inshas_Airbase"]="Inshas Airbase", +["Jiyanklis_Air_Base"]="Jiyanklis Air Base", +["Kedem"]="Kedem", +["Kibrit_Air_Base"]="Kibrit Air Base", +["Kom_Awshim"]="Kom Awshim", +["Melez"]="Melez", +["Mezzeh_Air_Base"]="Mezzeh Air Base", +["Nevatim"]="Nevatim", +["Ovda"]="Ovda", +["Palmachim"]="Palmachim", +["Quwaysina"]="Quwaysina", +["Rafic_Hariri_Intl"]="Rafic Hariri Intl", +["Ramat_David"]="Ramat David", +["Ramon_Airbase"]="Ramon Airbase", +["Ramon_International_Airport"]="Ramon International Airport", +["Sde_Dov"]="Sde Dov", +["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport", +["St_Catherine"]="St Catherine", +["Tabuk"]="Tabuk", +["Tel_Nof"]="Tel Nof", +["Wadi_Abu_Rish"]="Wadi Abu Rish", +["Wadi_al_Jandali"]="Wadi al Jandali", +} +AIRBASE.Kola={ +["Banak"]="Banak", +["Bodo"]="Bodo", +["Ivalo"]="Ivalo", +["Jokkmokk"]="Jokkmokk", +["Kalixfors"]="Kalixfors", +["Kallax"]="Kallax", +["Kemi_Tornio"]="Kemi Tornio", +["Kirkenes"]="Kirkenes", +["Kiruna"]="Kiruna", +["Kuusamo"]="Kuusamo", +["Monchegorsk"]="Monchegorsk", +["Murmansk_International"]="Murmansk International", +["Olenya"]="Olenya", +["Rovaniemi"]="Rovaniemi", +["Severomorsk_1"]="Severomorsk-1", +["Severomorsk_3"]="Severomorsk-3", +["Vidsel"]="Vidsel", +["Vuojarvi"]="Vuojarvi", +["Andoya"]="Andoya", +["Alakurtti"]="Alakurtti", +["Kittila"]="Kittila", +["Bardufoss"]="Bardufoss", +["Alta"]="Alta", +["Sodankyla"]="Sodankyla", +["Enontekio"]="Enontekio", +["Evenes"]="Evenes", +["Hosio"]="Hosio", +["Kilpyavr"]="Kilpyavr", +["Afrikanda"]="Afrikanda", +["Kalevala"]="Kalevala", +["Koshka_Yavr"]="Koshka Yavr", +["Poduzhemye"]="Poduzhemye", +["Luostari_Pechenga"]="Luostari Pechenga", +} +AIRBASE.Afghanistan={ +["Bagram"]="Bagram", +["Bamyan"]="Bamyan", +["Bost"]="Bost", +["Camp_Bastion"]="Camp Bastion", +["Camp_Bastion_Heliport"]="Camp Bastion Heliport", +["Chaghcharan"]="Chaghcharan", +["Dwyer"]="Dwyer", +["Farah"]="Farah", +["Gardez"]="Gardez", +["Ghazni_Heliport"]="Ghazni Heliport", +["Herat"]="Herat", +["Jalalabad"]="Jalalabad", +["Kabul"]="Kabul", +["Kandahar"]="Kandahar", +["Kandahar_Heliport"]="Kandahar Heliport", +["Khost"]="Khost", +["Khost_Heliport"]="Khost Heliport", +["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi", +["Nimroz"]="Nimroz", +["Qala_i_Naw"]="Qala i Naw", +["Sharana"]="Sharana", +["Shindand"]="Shindand", +["Shindand_Heliport"]="Shindand Heliport", +["Tarinkot"]="Tarinkot", +["Urgoon_Heliport"]="Urgoon Heliport", +} +AIRBASE.Iraq={ +["Al_Asad_Airbase"]="Al-Asad Airbase", +["Al_Kut_Airport"]="Al-Kut Airport", +["Al_Sahra_Airport"]="Al-Sahra Airport", +["Al_Salam_Airbase"]="Al-Salam Airbase", +["Al_Taji_Airport"]="Al-Taji Airport", +["Al_Taquddum_Airport"]="Al-Taquddum Airport", +["Baghdad_International_Airport"]="Baghdad International Airport", +["Balad_Airbase"]="Balad Airbase", +["Bashur_Airport"]="Bashur Airport", +["Erbil_International_Airport"]="Erbil International Airport", +["H2_Airbase"]="H-2 Airbase", +["H3_Main_Airbase"]="H-3 Main Airbase", +["H3_Northwest_Airbase"]="H-3 Northwest Airbase", +["H3_Southwest_Airbase"]="H-3 Southwest Airbase", +["K1_Base"]="K1 Base", +["Kirkuk_International_Airport"]="Kirkuk International Airport", +["Mosul_International_Airport"]="Mosul International Airport", +["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", +} +AIRBASE.GermanyCW={ +["Airracing_Frankfurt"]="Airracing Frankfurt", +["Airracing_Koblenz"]="Airracing Koblenz", +["Airracing_Luebeck"]="Airracing Lubeck", +["Allstedt"]="Allstedt", +["Altes_Lager"]="Altes Lager", +["Bad_Duerkheim"]="Bad Durkheim", +["Barth"]="Barth", +["Bienenfarm"]="Bienenfarm", +["Bindersleben"]="Bindersleben", +["Bitburg"]="Bitburg", +["Braunschweig"]="Braunschweig", +["Bremen"]="Bremen", +["Briest"]="Briest", +["Buechel"]="Buchel", +["Bueckeburg"]="Buckeburg", +["Celle"]="Celle", +["Cochstedt"]="Cochstedt", +["Damgarten"]="Damgarten", +["Dedelow"]="Dedelow", +["Dessau"]="Dessau", +["Fassberg"]="Fassberg", +["Finow"]="Finow", +["Frankfurt"]="Frankfurt", +["Fritzlar"]="Fritzlar", +["Fulda"]="Fulda", +["Gardelegen"]="Gardelegen", +["Garz"]="Garz", +["Gatow"]="Gatow", +["Gelnhausen"]="Gelnhausen", +["Giebelstadt"]="Giebelstadt", +["Glindbruchkippe"]="Glindbruchkippe ", +["Gross_Mohrdorf"]="Gross Mohrdorf", +["Grosse_Wiese"]="Grosse Wiese", +["Guetersloh"]="Gutersloh", +["H_FRG_01"]="H FRG 01", +["H_FRG_02"]="H FRG 02", +["H_FRG_03"]="H FRG 03", +["H_FRG_04"]="H FRG 04", +["H_FRG_05"]="H FRG 05", +["H_FRG_06"]="H FRG 06", +["H_FRG_07"]="H FRG 07", +["H_FRG_08"]="H FRG 08", +["H_FRG_09"]="H FRG 09", +["H_FRG_10"]="H FRG 10", +["H_FRG_11"]="H FRG 11", +["H_FRG_12"]="H FRG 12", +["H_FRG_13"]="H FRG 13", +["H_FRG_14"]="H FRG 14", +["H_FRG_15"]="H FRG 15", +["H_FRG_16"]="H FRG 16", +["H_FRG_17"]="H FRG 17", +["H_FRG_18"]="H FRG 18", +["H_FRG_19"]="H FRG 19", +["H_FRG_20"]="H FRG 20", +["H_FRG_21"]="H FRG 21", +["H_FRG_23"]="H FRG 23", +["H_FRG_25"]="H FRG 25", +["H_FRG_27"]="H FRG 27", +["H_FRG_30"]="H FRG 30", +["H_FRG_31"]="H FRG 31", +["H_FRG_32"]="H FRG 32", +["H_FRG_34"]="H FRG 34", +["H_FRG_38"]="H FRG 38", +["H_FRG_39"]="H FRG 39", +["H_FRG_40"]="H FRG 40", +["H_FRG_41"]="H FRG 41", +["H_FRG_42"]="H FRG 42", +["H_FRG_43"]="H FRG 43", +["H_FRG_44"]="H FRG 44", +["H_FRG_45"]="H FRG 45", +["H_FRG_46"]="H FRG 46", +["H_FRG_47"]="H FRG 47", +["H_FRG_48"]="H FRG 48", +["H_FRG_49"]="H FRG 49", +["H_FRG_50"]="H FRG 50", +["H_FRG_51"]="H FRG 51", +["H_GDR_01"]="H GDR 01", +["H_GDR_02"]="H GDR 02", +["H_GDR_03"]="H GDR 03", +["H_GDR_04"]="H GDR 04", +["H_GDR_05"]="H GDR 05", +["H_GDR_06"]="H GDR 06", +["H_GDR_07"]="H GDR 07", +["H_GDR_08"]="H GDR 08", +["H_GDR_09"]="H GDR 09", +["H_GDR_10"]="H GDR 10", +["H_GDR_11"]="H GDR 11", +["H_GDR_12"]="H GDR 12", +["H_GDR_13"]="H GDR 13", +["H_GDR_14"]="H GDR 14", +["H_GDR_15"]="H GDR 15", +["H_GDR_16"]="H GDR 16", +["H_GDR_17"]="H GDR 17", +["H_GDR_18"]="H GDR 18", +["H_GDR_19"]="H GDR 19", +["H_GDR_21"]="H GDR 21", +["H_GDR_22"]="H GDR 22", +["H_GDR_24"]="H GDR 24", +["H_GDR_25"]="H GDR 25", +["H_GDR_26"]="H GDR 26", +["H_GDR_30"]="H GDR 30", +["H_GDR_31"]="H GDR 31", +["H_GDR_32"]="H GDR 32", +["H_GDR_33"]="H GDR 33", +["H_GDR_34"]="H GDR 34", +["H_Med_FRG_01"]="H Med FRG 01", +["H_Med_FRG_02"]="H Med FRG 02", +["H_Med_FRG_04"]="H Med FRG 04", +["H_Med_FRG_06"]="H Med FRG 06", +["H_Med_FRG_11"]="H Med FRG 11", +["H_Med_FRG_12"]="H Med FRG 12", +["H_Med_FRG_13"]="H Med FRG 13", +["H_Med_FRG_14"]="H Med FRG 14", +["H_Med_FRG_15"]="H Med FRG 15", +["H_Med_FRG_16"]="H Med FRG 16", +["H_Med_FRG_17"]="H Med FRG 17", +["H_Med_FRG_21"]="H Med FRG 21", +["H_Med_FRG_24"]="H Med FRG 24", +["H_Med_FRG_26"]="H Med FRG 26", +["H_Med_FRG_27"]="H Med FRG 27", +["H_Med_FRG_29"]="H Med FRG 29", +["H_Med_GDR_01"]="H Med GDR 01", +["H_Med_GDR_02"]="H Med GDR 02", +["H_Med_GDR_03"]="H Med GDR 03", +["H_Med_GDR_08"]="H Med GDR 08", +["H_Med_GDR_09"]="H Med GDR 09", +["H_Med_GDR_10"]="H Med GDR 10", +["H_Med_GDR_11"]="H Med GDR 11", +["H_Med_GDR_12"]="H Med GDR 12", +["H_Med_GDR_13"]="H Med GDR 13", +["H_Med_GDR_14"]="H Med GDR 14", +["H_Med_GDR_16"]="H Med GDR 16", +["H_Radar_FRG_02"]="H Radar FRG 02", +["H_Radar_GDR_01"]="H Radar GDR 01", +["H_Radar_GDR_02"]="H Radar GDR 02", +["H_Radar_GDR_03"]="H Radar GDR 03", +["H_Radar_GDR_04"]="H Radar GDR 04", +["H_Radar_GDR_05"]="H Radar GDR 05", +["H_Radar_GDR_06"]="H Radar GDR 06", +["H_Radar_GDR_07"]="H Radar GDR 07", +["H_Radar_GDR_08"]="H Radar GDR 08", +["H_Radar_GDR_09"]="H Radar GDR 09", +["Hahn"]="Hahn", +["Haina"]="Haina", +["Hamburg"]="Hamburg", +["Hamburg_Finkenwerder"]="Hamburg Finkenwerder", +["Hannover"]="Hannover", +["Hasselfelde"]="Hasselfelde", +["Herrenteich"]="Herrenteich", +["Hildesheim"]="Hildesheim", +["Hockenheim"]="Hockenheim", +["Holzdorf"]="Holzdorf", +["Kammermark"]="Kammermark", +["Koethen"]="Kothen", +["Laage"]="Laage", +["Langenselbold"]="Langenselbold", +["Laerz"]="Larz", +["Leipzig_Halle"]="Leipzig Halle", +["Leipzig_Mockau"]="Leipzig Mockau", +["Luebeck"]="Lubeck", +["Lueneburg"]="Luneburg", +["Mahlwinkel"]="Mahlwinkel", +["Mendig"]="Mendig", +["Merseburg"]="Merseburg", +["Neubrandenburg"]="Neubrandenburg", +["Neuruppin"]="Neuruppin", +["Northeim"]="Northeim", +["Ober_Moerlen"]="Ober-Morlen", +["Obermehler_Schlotheim"]="Obermehler Schlotheim", +["Parchim"]="Parchim", +["Peenemuende"]="Peenemunde", +["Pferdsfeld"]="Pferdsfeld", +["Pinnow"]="Pinnow", +["Pottschutthoehe"]="Pottschutthohe", +["Ramstein"]="Ramstein", +["Rinteln"]="Rinteln", +["Schoenefeld"]="Schonefeld", +["Schweinfurt"]="Schweinfurt", +["Sembach"]="Sembach", +["Spangdahlem"]="Spangdahlem", +["Sperenberg"]="Sperenberg", +["Stendal"]="Stendal", +["Tegel"]="Tegel", +["Tempelhof"]="Tempelhof", +["Templin"]="Templin", +["Tutow"]="Tutow", +["Uelzen"]="Uelzen", +["Uetersen"]="Uetersen", +["Ummern"]="Ummern", +["Verden_Scharnhorst"]="Verden-Scharnhorst", +["Walldorf"]="Walldorf", +["Waren_Vielist"]="Waren Vielist", +["Werneuchen"]="Werneuchen", +["Weser_Wuemme"]="Weser Wumme", +["Wiesbaden"]="Wiesbaden", +["Wismar"]="Wismar", +["Wittstock"]="Wittstock", +["Worms"]="Worms", +["Wunstorf"]="Wunstorf", +["Zerbst"]="Zerbst", +["Zweibruecken"]="Zweibrucken", +} +AIRBASE.TerminalType={ +Runway=16, +HelicopterOnly=40, +Shelter=68, +OpenMed=72, +SmallSizeFighter=100, +OpenBig=104, +OpenMedOrBig=176, +HelicopterUsable=216, +FighterAircraft=244, +FighterAircraftSmall=344, +} +AIRBASE.SpotStatus={ +FREE="Free", +OCCUPIED="Occupied", +RESERVED="Reserved", +} +function AIRBASE:Register(AirbaseName) +local self=BASE:Inherit(self,POSITIONABLE:New(AirbaseName)) +self.AirbaseName=AirbaseName +self.AirbaseID=self:GetID(true) +self.descriptors=self:GetDesc() +self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME +if self.category==Airbase.Category.AIRDROME then +self.isAirdrome=true +elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01"then +self.isHelipad=true +self.category=Airbase.Category.HELIPAD +elseif self.category==Airbase.Category.SHIP then +self.isShip=true +if self.descriptors.typeName=="Oil rig"or self.descriptors.typeName=="Ga"then +self.isHelipad=true +self.isShip=false +self.category=Airbase.Category.HELIPAD +_DATABASE:AddStatic(AirbaseName) +end +else +self:E("ERROR: Unknown airbase category!") +end +self:_InitRunways() +local Nrunways=#self.runways +if Nrunways>0 then +self:SetActiveRunway() +end +self:_InitParkingSpots() +if self.category==Airbase.Category.AIRDROME and(Nrunways==0 or self.NparkingTotal==self.NparkingTerminal[AIRBASE.TerminalType.HelicopterOnly])then +self.category=Airbase.Category.HELIPAD +self.isAirdrome=true +self.isHelipad=true +end +local vec2=self:GetVec2() +self:GetCoordinate() +self.storage=_DATABASE:AddStorage(AirbaseName) +if vec2 then +if self.isShip then +local unit=UNIT:FindByName(AirbaseName) +if unit then +self.AirbaseZone=ZONE_UNIT:New(AirbaseName,unit,2500) +end +else +self.AirbaseZone=ZONE_RADIUS:New(AirbaseName,vec2,2500) +end +else +self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) +end +self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) +return self +end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end +function AIRBASE:_GetCategory() +local name=self.AirbaseName +local static=StaticObject.getByName(name) +local airbase=Airbase.getByName(name) +local unit=Unit.getByName(name) +local text=string.format("\n=====================================================") +text=text..string.format("\nAirbase %s:",name) +if static then +local oc,uc=static:getCategory() +local ex=static:getCategoryEx() +text=text..string.format("\nSTATIC: oc=%d, uc=%d, ex=%d",oc,uc,ex) +text=text..string.format("\n--------------------------------------------------") +end +if unit then +local oc,uc=unit:getCategory() +local ex=unit:getCategoryEx() +text=text..string.format("\nUNIT: oc=%d, uc=%d, ex=%d",oc,uc,ex) +text=text..string.format("\n--------------------------------------------------") +end +if airbase then +local oc,uc=airbase:getCategory() +local ex=airbase:getCategoryEx() +text=text..string.format("\nAIRBASE: oc=%d, uc=%d, ex=%d",oc,uc,ex) +text=text..string.format("\n--------------------------------------------------") +text=text..UTILS.PrintTableToLog(airbase:getDesc(),nil,true) +end +text=text..string.format("\n=====================================================") +env.info(text) +end +function AIRBASE:Find(DCSAirbase) +local AirbaseName=DCSAirbase:getName() +local AirbaseFound=_DATABASE:FindAirbase(AirbaseName) +return AirbaseFound +end +function AIRBASE:FindByName(AirbaseName) +local AirbaseFound=_DATABASE:FindAirbase(AirbaseName) +return AirbaseFound +end +function AIRBASE:FindByID(id) +for name,_airbase in pairs(_DATABASE.AIRBASES)do +local airbase=_airbase +local aid=tonumber(airbase:GetID(true)) +if aid==id then +return airbase +end +end +return nil +end +function AIRBASE:GetDCSObject() +local DCSAirbase=Airbase.getByName(self.AirbaseName) +if DCSAirbase then +return DCSAirbase +end +return nil +end +function AIRBASE:GetZone() +return self.AirbaseZone +end +function AIRBASE:GetWarehouse() +local warehouse=nil +local airbase=self:GetDCSObject() +if airbase and Airbase.getWarehouse then +warehouse=airbase:getWarehouse() +end +return warehouse +end +function AIRBASE:GetStorage() +return self.storage +end +function AIRBASE:SetAutoCapture(Switch) +local airbase=self:GetDCSObject() +if airbase then +airbase:autoCapture(Switch) +end +return self +end +function AIRBASE:SetAutoCaptureON() +self:SetAutoCapture(true) +return self +end +function AIRBASE:SetAutoCaptureOFF() +self:SetAutoCapture(false) +return self +end +function AIRBASE:IsAutoCapture() +local airbase=self:GetDCSObject() +local auto=nil +if airbase then +auto=airbase:autoCaptureIsOn() +end +return auto +end +function AIRBASE:SetCoalition(Coal) +local airbase=self:GetDCSObject() +if airbase then +airbase:setCoalition(Coal) +end +return self +end +function AIRBASE.GetAllAirbases(coalition,category) +local airbases={} +for _,_airbase in pairs(_DATABASE.AIRBASES)do +local airbase=_airbase +if coalition==nil or airbase:GetCoalition()==coalition then +if category==nil or category==airbase:GetAirbaseCategory()then +table.insert(airbases,airbase) +end +end +end +return airbases +end +function AIRBASE.GetAllAirbaseNames(coalition,category) +local airbases={} +for airbasename,_airbase in pairs(_DATABASE.AIRBASES)do +local airbase=_airbase +if coalition==nil or airbase:GetCoalition()==coalition then +if category==nil or category==airbase:GetAirbaseCategory()then +table.insert(airbases,airbasename) +end +end +end +return airbases +end +function AIRBASE:GetID(unique) +if self.AirbaseID then +return unique and self.AirbaseID or math.abs(self.AirbaseID) +else +for DCSAirbaseId,DCSAirbase in ipairs(world.getAirbases())do +local AirbaseName=DCSAirbase:getName() +local airbaseID=tonumber(DCSAirbase:getID()) +local airbaseCategory=self:GetAirbaseCategory() +if AirbaseName==self.AirbaseName then +if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then +return unique and-airbaseID or airbaseID +else +return airbaseID +end +end +end +end +return nil +end +function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist) +if TerminalIdWhitelist==nil then +self.parkingWhitelist={} +return self +end +if type(TerminalIdWhitelist)~="table"then +TerminalIdWhitelist={TerminalIdWhitelist} +end +self.parkingWhitelist=TerminalIdWhitelist +return self +end +function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist) +if TerminalIdBlacklist==nil then +self.parkingBlacklist={} +return self +end +if type(TerminalIdBlacklist)~="table"then +TerminalIdBlacklist={TerminalIdBlacklist} +end +self.parkingBlacklist=TerminalIdBlacklist +return self +end +function AIRBASE:SetRadioSilentMode(Silent) +local airbase=self:GetDCSObject() +if airbase then +airbase:setRadioSilentMode(Silent) +end +return self +end +function AIRBASE:GetRadioSilentMode() +local silent=nil +local airbase=self:GetDCSObject() +if airbase then +silent=airbase:getRadioSilentMode() +end +return silent +end +function AIRBASE:GetAirbaseCategory() +return self.category +end +function AIRBASE:IsAirdrome() +return self.isAirdrome +end +function AIRBASE:IsHelipad() +return self.isHelipad +end +function AIRBASE:IsShip() +return self.isShip +end +function AIRBASE:GetParkingData(available) +self:F2(available) +local DCSAirbase=self:GetDCSObject() +local parkingdata=nil +if DCSAirbase then +parkingdata=DCSAirbase:getParking(available) +end +self:T2({parkingdata=parkingdata}) +return parkingdata +end +function AIRBASE:GetParkingSpotsNumber(termtype) +local parkingdata=self:GetParkingData(false) +local nspots=0 +for _,parkingspot in pairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +nspots=nspots+1 +end +end +return nspots +end +function AIRBASE:GetFreeParkingSpotsNumber(termtype,allowTOAC) +local parkingdata=self:GetParkingData(true) +local nfree=0 +for _,parkingspot in pairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +if(allowTOAC and allowTOAC==true)or parkingspot.TO_AC==false then +nfree=nfree+1 +end +end +end +return nfree +end +function AIRBASE:GetFreeParkingSpotsCoordinates(termtype,allowTOAC) +local parkingdata=self:GetParkingData(true) +local spots={} +for _,parkingspot in pairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +if(allowTOAC and allowTOAC==true)or parkingspot.TO_AC==false then +table.insert(spots,COORDINATE:NewFromVec3(parkingspot.vTerminalPos)) +end +end +end +return spots +end +function AIRBASE:GetParkingSpotsCoordinates(termtype) +local parkingdata=self:GetParkingData(false) +local spots={} +for _,parkingspot in ipairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +local _coord=COORDINATE:NewFromVec3(parkingspot.vTerminalPos) +table.insert(spots,_coord) +end +end +return spots +end +function AIRBASE:_InitParkingSpots() +local parkingdata=self:GetParkingData(false) +self.parking={} +self.parkingByID={} +self.NparkingTotal=0 +self.NparkingTerminal={} +for _,terminalType in pairs(AIRBASE.TerminalType)do +self.NparkingTerminal[terminalType]=0 +end +local function isClient(coord) +local clients=_DATABASE.CLIENTS +for clientname,_client in pairs(clients)do +local client=_client +if client and client.SpawnCoord then +local dist=client.SpawnCoord:Get2DDistance(coord) +if dist<2 then +return true,clientname +end +end +end +return false,nil +end +for _,spot in pairs(parkingdata)do +local park={} +park.Vec3=spot.vTerminalPos +park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos) +park.DistToRwy=spot.fDistToRW +park.Free=nil +park.TerminalID=spot.Term_Index +park.TerminalID0=spot.Term_Index_0 +park.TerminalType=spot.Term_Type +park.TOAC=spot.TO_AC +park.ClientSpot,park.ClientName=isClient(park.Coordinate) +park.AirbaseName=self.AirbaseName +self.NparkingTotal=self.NparkingTotal+1 +for _,terminalType in pairs(AIRBASE.TerminalType)do +if self._CheckTerminalType(park.TerminalType,terminalType)then +self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 +end +end +self.parkingByID[park.TerminalID]=park +table.insert(self.parking,park) +end +self.NparkingTotal=self.NparkingTotal-self.NparkingTerminal[AIRBASE.TerminalType.Runway] +return self +end +function AIRBASE:_GetParkingSpotByID(TerminalID) +return self.parkingByID[TerminalID] +end +function AIRBASE:GetParkingSpotsTable(termtype) +local parkingdata=self:GetParkingData(false) +local parkingfree=self:GetParkingData(true) +local function _isfree(_tocheck) +for _,_spot in pairs(parkingfree)do +if _spot.Term_Index==_tocheck.Term_Index then +return true +end +end +return false +end +local spots={} +for _,_spot in pairs(parkingdata)do +if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)then +local spot=self:_GetParkingSpotByID(_spot.Term_Index) +if spot then +spot.Free=_isfree(_spot) +spot.TOAC=_spot.TO_AC +spot.AirbaseName=self.AirbaseName +table.insert(spots,spot) +else +self:E(string.format("ERROR: Parking spot %s is nil!",tostring(_spot.Term_Index))) +end +end +end +return spots +end +function AIRBASE:GetFreeParkingSpotsTable(termtype,allowTOAC) +local parkingfree=self:GetParkingData(true) +local freespots={} +for _,_spot in pairs(parkingfree)do +if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)then +if(allowTOAC and allowTOAC==true)or _spot.TO_AC==false then +local spot=self:_GetParkingSpotByID(_spot.Term_Index) +spot.Free=true +spot.TOAC=_spot.TO_AC +spot.AirbaseName=self.AirbaseName +table.insert(freespots,spot) +end +end +end +return freespots +end +function AIRBASE:GetParkingSpotData(TerminalID) +local parkingdata=self:GetParkingSpotsTable() +for _,_spot in pairs(parkingdata)do +local spot=_spot +self:T({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType}) +if TerminalID==spot.TerminalID then +return spot +end +end +self:E("ERROR: Could not find spot with Terminal ID="..tostring(TerminalID)) +return nil +end +function AIRBASE:MarkParkingSpots(termtype,mark) +if mark==nil then +mark=true +end +local parkingdata=self:GetParkingSpotsTable(termtype) +local airbasename=self:GetName() +self:E(string.format("Parking spots at %s for terminal type %s:",airbasename,tostring(termtype))) +for _,_spot in pairs(parkingdata)do +local _text=string.format("Term Index=%d, Term Type=%d, Free=%s, TOAC=%s, Term ID0=%d, Dist2Rwy=%.1f m", +_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy) +if mark then +_spot.Coordinate:MarkToAll(_text) +end +local _text=string.format("%s, Term Index=%3d, Term Type=%03d, Free=%5s, TOAC=%5s, Term ID0=%3d, Dist2Rwy=%.1f m", +airbasename,_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy) +self:E(_text) +end +end +function AIRBASE:FindFreeParkingSpotForAircraft(group,terminaltype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nspots,parkingdata) +scanradius=scanradius or 50 +if scanunits==nil then +scanunits=true +end +if scanstatics==nil then +scanstatics=true +end +if scanscenery==nil then +scanscenery=false +end +if verysafe==nil then +verysafe=false +end +local function _overlap(object1,object2,dist) +local pos1=object1 +local pos2=object2 +local r1=pos1:GetBoundingRadius() +local r2=pos2:GetBoundingRadius() +if r1 and r2 then +local safedist=(r1+r2)*1.1 +local safe=(dist>safedist) +self:T2(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s",r1,r2,safedist,dist,tostring(safe))) +return safe +else +return true +end +end +local airport=self:GetName() +parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype) +local aircraft=nil +local _aircraftsize=23 +local ax=23 +local ay=7 +local az=17 +if group and group.ClassName=="GROUP"then +aircraft=group:GetUnit(1) +if aircraft then +_aircraftsize,ax,ay,az=aircraft:GetObjectSize() +end +end +local _nspots=nspots or group:GetSize() +self:T(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.",airport,_nspots,_aircraftsize,ax,ay,az,tostring(terminaltype))) +local validspots={} +local nvalid=0 +local _test=false +if _test then +return validspots +end +local markobstacles=false +for _,parkingspot in pairs(parkingdata)do +local _spot=parkingspot.Coordinate +local _termid=parkingspot.TerminalID +if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)and self:_CheckParkingLists(_termid)then +if verysafe and(parkingspot.Free==false or parkingspot.TOAC==true)then +self:T(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.",airport,parkingspot.TerminalID,tostring(parkingspot.Free),tostring(parkingspot.TOAC))) +else +local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius,scanunits,scanstatics,scanscenery) +local occupied=false +for _,unit in pairs(_units)do +local _coord=unit:GetCoordinate() +local _dist=_coord:Get2DDistance(_spot) +local _safe=_overlap(aircraft,unit,_dist) +if markobstacles then +local l,x,y,z=unit:GetObjectSize() +_coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",unit:GetName(),x,y,z,l,_dist,_termid,tostring(_safe))) +end +if scanunits and not _safe then +occupied=true +end +end +for _,static in pairs(_statics)do +local _static=STATIC:Find(static) +local _vec3=static:getPoint() +local _coord=COORDINATE:NewFromVec3(_vec3) +local _dist=_coord:Get2DDistance(_spot) +local _safe=_overlap(aircraft,_static,_dist) +if markobstacles then +local l,x,y,z=_static:GetObjectSize() +_coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",static:getName(),x,y,z,l,_dist,_termid,tostring(_safe))) +end +if scanstatics and not _safe then +occupied=true +end +end +for _,scenery in pairs(_sceneries)do +local _scenery=SCENERY:Register(scenery:getTypeName(),scenery) +local _vec3=scenery:getPoint() +local _coord=COORDINATE:NewFromVec3(_vec3) +local _dist=_coord:Get2DDistance(_spot) +local _safe=_overlap(aircraft,_scenery,_dist) +if markobstacles then +local l,x,y,z=scenery:GetObjectSize(scenery) +_coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",scenery:getTypeName(),x,y,z,l,_dist,_termid,tostring(_safe))) +end +if scanscenery and not _safe then +occupied=true +end +end +for _,_takenspot in pairs(validspots)do +local _dist=_takenspot.Coordinate:Get2DDistance(_spot) +local _safe=_overlap(aircraft,aircraft,_dist) +if not _safe then +occupied=true +end +end +if occupied then +self:T(string.format("%s: Parking spot id %d occupied.",airport,_termid)) +else +self:T(string.format("%s: Parking spot id %d free.",airport,_termid)) +if nvalid<_nspots then +table.insert(validspots,{Coordinate=_spot,TerminalID=_termid}) +end +nvalid=nvalid+1 +self:T(string.format("%s: Parking spot id %d free. Nfree=%d/%d.",airport,_termid,nvalid,_nspots)) +end +end +if nvalid>=_nspots then +return validspots +end +end +end +return validspots +end +function AIRBASE:_CheckParkingLists(TerminalID) +if self.parkingBlacklist and#self.parkingBlacklist>0 then +for _,terminalID in pairs(self.parkingBlacklist or{})do +if terminalID==TerminalID then +return false +end +end +end +if self.parkingWhitelist and#self.parkingWhitelist>0 then +for _,terminalID in pairs(self.parkingWhitelist or{})do +if terminalID==TerminalID then +return true +end +end +return false +end +return true +end +function AIRBASE._CheckTerminalType(Term_Type,termtype) +if Term_Type==nil then +return false +end +if termtype==nil then +if Term_Type==AIRBASE.TerminalType.Runway then +return false +else +return true +end +end +local match=false +if Term_Type==termtype then +match=true +end +if termtype==AIRBASE.TerminalType.OpenMedOrBig then +if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig then +match=true +end +elseif termtype==AIRBASE.TerminalType.HelicopterUsable then +if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.HelicopterOnly then +match=true +end +elseif termtype==AIRBASE.TerminalType.FighterAircraft then +if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then +match=true +end +elseif termtype==AIRBASE.TerminalType.FighterAircraftSmall then +if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then +match=true +end +end +return match +end +function AIRBASE:GetRunways() +return self.runways or{} +end +function AIRBASE:GetRunwayByName(Name) +if Name==nil then +return +end +if Name then +for _,_runway in pairs(self.runways)do +local runway=_runway +local name=self:GetRunwayName(runway) +self:T("Check Runway Name: "..name) +if name==Name:upper()then +return runway +end +end +end +self:E("ERROR: Could not find runway with name "..tostring(Name)) +return nil +end +function AIRBASE:_InitRunways(IncludeInverse) +if IncludeInverse==nil then +IncludeInverse=true +end +local Runways={} +local function _createRunway(name,course,width,length,center) +self:T("Create Runway: name = "..name) +local bearing=-1*course +local heading=math.deg(bearing) +local runway={} +local namefromheading=math.floor(heading/10) +if self.AirbaseName==AIRBASE.Syria.Beirut_Rafic_Hariri and math.abs(namefromheading-name)>1 then +runway.name=string.format("%02d",tonumber(namefromheading)) +else +runway.name=string.format("%02d",tonumber(name)) +end +runway.magheading=tonumber(runway.name)*10 +runway.idx=runway.magheading +runway.heading=heading +runway.width=width or 0 +runway.length=length or 0 +runway.center=COORDINATE:NewFromVec3(center) +if runway.heading>360 then +runway.heading=runway.heading-360 +elseif runway.heading<0 then +runway.heading=runway.heading+360 +end +if math.abs(runway.heading-runway.magheading)>60 then +self:T(string.format("WARNING: Runway %s: heading=%.1f magheading=%.1f",runway.name,runway.heading,runway.magheading)) +runway.heading=runway.heading-180 +end +if runway.heading>360 then +runway.heading=runway.heading-360 +elseif runway.heading<0 then +runway.heading=runway.heading+360 +end +runway.position=runway.center:Translate(-runway.length/2,runway.heading) +runway.endpoint=runway.center:Translate(runway.length/2,runway.heading) +local init=runway.center:GetVec3() +local width=runway.width/2 +local L2=runway.length/2 +local offset1={x=init.x+(math.cos(bearing+math.pi)*L2),y=init.z+(math.sin(bearing+math.pi)*L2)} +local offset2={x=init.x-(math.cos(bearing+math.pi)*L2),y=init.z-(math.sin(bearing+math.pi)*L2)} +local points={} +points[1]={x=offset1.x+(math.cos(bearing+(math.pi/2))*width),y=offset1.y+(math.sin(bearing+(math.pi/2))*width)} +points[2]={x=offset1.x+(math.cos(bearing-(math.pi/2))*width),y=offset1.y+(math.sin(bearing-(math.pi/2))*width)} +points[3]={x=offset2.x+(math.cos(bearing-(math.pi/2))*width),y=offset2.y+(math.sin(bearing-(math.pi/2))*width)} +points[4]={x=offset2.x+(math.cos(bearing+(math.pi/2))*width),y=offset2.y+(math.sin(bearing+(math.pi/2))*width)} +runway.zone=ZONE_POLYGON_BASE:New(string.format("%s Runway %s",self.AirbaseName,runway.name),points) +return runway +end +local airbase=self:GetDCSObject() +if airbase then +local runways=airbase:getRunways() +self:T2(runways) +if runways and#runways>0 then +for _,rwy in pairs(runways)do +self:T(rwy) +local runway=_createRunway(rwy.Name,rwy.course,rwy.width,rwy.length,rwy.position) +table.insert(Runways,runway) +if IncludeInverse then +local idx=tonumber(runway.name) +local name2=tostring(idx-18) +if idx<18 then +name2=tostring(idx+18) +end +local runway=_createRunway(name2,rwy.course-math.pi,rwy.width,rwy.length,rwy.position) +table.insert(Runways,runway) +end +end +else +self.runways={} +return{} +end +end +local rpairs={} +for i,_ri in pairs(Runways)do +local ri=_ri +for j,_rj in pairs(Runways)do +local rj=_rj +if i0 +end +for i,j in pairs(rpairs)do +local ri=Runways[i] +local rj=Runways[j] +local c0=ri.center +local a=UTILS.VecTranslate(c0,1000,ri.heading) +local b=UTILS.VecSubstract(rj.center,ri.center) +b=UTILS.VecAdd(ri.center,b) +local left=isLeft(c0,a,b) +if left then +ri.isLeft=false +rj.isLeft=true +else +ri.isLeft=true +rj.isLeft=false +end +end +self.runways=Runways +return Runways +end +function AIRBASE:GetRunwayData(magvar,mark) +local runways={} +if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then +return{} +end +local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) +if false then +for i,_coord in pairs(runwaycoords)do +local coord=_coord +coord:Translate(100,0):MarkToAll("Runway i="..i) +end +end +magvar=magvar or UTILS.GetMagneticDeclination() +local N=#runwaycoords +local N2=N/2 +local exception=false +local name=self:GetName() +if name==AIRBASE.Nevada.Jean_Airport or +name==AIRBASE.Nevada.Creech_AFB or +name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or +name==AIRBASE.PersianGulf.Dubai_Intl or +name==AIRBASE.PersianGulf.Shiraz_International_Airport or +name==AIRBASE.PersianGulf.Kish_International_Airport or +name==AIRBASE.MarianaIslands.Andersen_AFB then +exception=1 +elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and +name~=AIRBASE.Syria.Minakh and +name~=AIRBASE.Syria.Damascus and +name~=AIRBASE.Syria.Khalkhalah and +name~=AIRBASE.Syria.Marj_Ruhayyil and +name~=AIRBASE.Syria.Beirut_Rafic_Hariri then +exception=2 +end +local function f(i) +local j +if exception==1 then +j=N-(i-1) +elseif exception==2 then +if i<=N2 then +j=i+N2 +else +j=i-N2 +end +else +if i%2==0 then +j=i-1 +else +j=i+1 +end +end +if name==AIRBASE.Syria.Beirut_Rafic_Hariri then +if i==1 then +j=3 +elseif i==2 then +j=6 +elseif i==3 then +j=1 +elseif i==4 then +j=5 +elseif i==5 then +j=4 +elseif i==6 then +j=2 +end +end +if name==AIRBASE.Syria.Ramat_David then +if i==1 then +j=4 +elseif i==2 then +j=6 +elseif i==3 then +j=5 +elseif i==4 then +j=1 +elseif i==5 then +j=3 +elseif i==6 then +j=2 +end +end +return j +end +for i=1,N do +local j=f(i) +local c1=runwaycoords[i] +local c2=runwaycoords[j] +local hdg=c1:HeadingTo(c2) +local idx=string.format("%02d",UTILS.Round((hdg-magvar)/10,0)) +local runway={} +runway.heading=hdg +runway.idx=idx +runway.magheading=idx +runway.length=c1:Get2DDistance(c2) +runway.position=c1 +runway.endpoint=c2 +self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d",self:GetName(),runway.idx,runway.heading,runway.length,i,j)) +if mark then +runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m, i=%d, j=%d",runway.idx,runway.heading,magvar,runway.length,i,j)) +end +table.insert(runways,runway) +end +local rpairs={} +for i,_ri in pairs(runways)do +local ri=_ri +for j,_rj in pairs(runways)do +local rj=_rj +if i0 +end +for i,j in pairs(rpairs)do +local ri=runways[i] +local rj=runways[j] +local c0=ri.position +local a=UTILS.VecTranslate(c0,1000,ri.heading) +local b=UTILS.VecSubstract(rj.position,ri.position) +b=UTILS.VecAdd(ri.position,b) +local left=isLeft(c0,a,b) +if left then +ri.isLeft=false +rj.isLeft=true +else +ri.isLeft=true +rj.isLeft=false +end +end +return runways +end +function AIRBASE:SetActiveRunway(Name,PreferLeft) +self:SetActiveRunwayTakeoff(Name,PreferLeft) +self:SetActiveRunwayLanding(Name,PreferLeft) +end +function AIRBASE:SetActiveRunwayLanding(Name,PreferLeft) +local runway=self:GetRunwayByName(Name) +if not runway then +runway=self:GetRunwayIntoWind(PreferLeft) +end +if runway then +self:T(string.format("%s: Setting active runway for landing as %s",self.AirbaseName,self:GetRunwayName(runway))) +else +self:E("ERROR: Could not set the runway for landing!") +end +self.runwayLanding=runway +return runway +end +function AIRBASE:GetActiveRunway() +return self.runwayLanding,self.runwayTakeoff +end +function AIRBASE:GetActiveRunwayLanding() +return self.runwayLanding +end +function AIRBASE:GetActiveRunwayTakeoff() +return self.runwayTakeoff +end +function AIRBASE:SetActiveRunwayTakeoff(Name,PreferLeft) +local runway=self:GetRunwayByName(Name) +if not runway then +runway=self:GetRunwayIntoWind(PreferLeft) +end +if runway then +self:T(string.format("%s: Setting active runway for takeoff as %s",self.AirbaseName,self:GetRunwayName(runway))) +else +self:E("ERROR: Could not set the runway for takeoff!") +end +self.runwayTakeoff=runway +return runway +end +function AIRBASE:GetRunwayIntoWind(PreferLeft) +local runways=self:GetRunways() +local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3() +local norm=UTILS.VecNorm(Vwind) +local iact=1 +if norm>0 then +Vwind.x=Vwind.x/norm +Vwind.y=0 +Vwind.z=Vwind.z/norm +local dotmin=nil +for i,_runway in pairs(runways)do +local runway=_runway +if PreferLeft==nil or PreferLeft==runway.isLeft then +local alpha=math.rad(runway.heading) +local Vrunway={x=math.cos(alpha),y=0,z=math.sin(alpha)} +local dot=UTILS.VecDot(Vwind,Vrunway) +if dotmin==nil or dot radius %.1f m. Despawn = %s.",self:GetName(),unit:GetName(),group:GetName(),_i,dist,radius,tostring(despawn))) +end +end +else +self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(),unit:GetName(),group:GetName())) +end +end +else +self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(),group:GetName())) +end +return false +end +function AIRBASE:GetCategory() +return self.category +end +function AIRBASE:GetCategoryName() +return AIRBASE.CategoryName[self.category] +end +SCENERY={ +ClassName="SCENERY", +} +function SCENERY:Register(SceneryName,SceneryObject) +local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) +self.SceneryName=tostring(SceneryName) +self.SceneryObject=SceneryObject +if self.SceneryObject and self.SceneryObject.getLife then +self.Life0=self.SceneryObject:getLife()or 0 +else +self.Life0=0 +end +self.Properties={} +return self +end +function SCENERY:GetProperty(PropertyName) +return self.Properties[PropertyName] +end +function SCENERY:HasProperty(PropertyName) +return self.Properties[PropertyName]~=nil and true or false +end +function SCENERY:GetAllProperties() +return self.Properties +end +function SCENERY:SetProperty(PropertyName,PropertyValue) +self.Properties[PropertyName]=PropertyValue +return self +end +function SCENERY:GetName() +return self.SceneryName +end +function SCENERY:GetDCSObject() +return self.SceneryObject +end +function SCENERY:GetLife() +local life=0 +if self.SceneryObject and self.SceneryObject.getLife then +life=self.SceneryObject:getLife() +if life>self.Life0 then +self.Life0=math.floor(life*1.2) +end +end +return life +end +function SCENERY:GetLife0() +return self.Life0 or 0 +end +function SCENERY:IsAlive(Threshold) +if not Threshold then +return self:GetLife()>=1 and true or false +else +return self:GetRelativeLife()>Threshold and true or false +end +end +function SCENERY:IsDead(Threshold) +if not Threshold then +return self:GetLife()<1 and true or false +else +return self:GetRelativeLife()<=Threshold and true or false +end +end +function SCENERY:GetRelativeLife() +local life=self:GetLife() +local life0=self:GetLife0() +if life==0 or life0==0 then return 0 end +local rlife=math.floor((life/life0)*100) +return rlife +end +function SCENERY:GetThreatLevel() +return 0,"Scenery" +end +function SCENERY:FindByName(Name,Coordinate,Radius,Role) +local radius=Radius or 100 +local name=Name or"unknown" +local scenery=nil +local function SceneryScan(scoordinate,sradius,sname) +if scoordinate~=nil then +local Vec2=scoordinate:GetVec2() +local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius,true) +scanzone:Scan({Object.Category.SCENERY}) +local scanned=scanzone:GetScannedSceneryObjects() +local rscenery=nil +for _,_scenery in pairs(scanned)do +local scenery=_scenery +if tostring(scenery.SceneryName)==tostring(sname)then +rscenery=scenery +if Role then rscenery:SetProperty("ROLE",Role)end +break +end +end +return rscenery +end +return nil +end +if Coordinate then +scenery=SceneryScan(Coordinate,radius,name) +end +return scenery +end +function SCENERY:FindByNameInZone(Name,Zone,Radius) +local radius=Radius or 100 +local name=Name or"unknown" +if type(Zone)=="string"then +Zone=ZONE:FindByName(Zone) +end +local coordinate=Zone:GetCoordinate() +return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE")) +end +function SCENERY:FindByZoneName(ZoneName) +local zone=ZoneName +if type(ZoneName)=="string"then +zone=ZONE:FindByName(ZoneName) +end +local _id=zone:GetProperty('OBJECT ID') +if not _id then +BASE:E("**** Zone without object ID: "..ZoneName.." | Type: "..tostring(zone.ClassName)) +if string.find(zone.ClassName,"POLYGON")then +zone:Scan({Object.Category.SCENERY}) +local scanned=zone:GetScannedSceneryObjects() +for _,_scenery in(scanned)do +local scenery=_scenery +if scenery:IsAlive()then +local role=zone:GetProperty("ROLE") +if role then scenery:SetProperty("ROLE",role)end +return scenery +end +end +return nil +else +return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +end +else +return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +end +end +function SCENERY:FindAllByZoneName(ZoneName) +local zone=ZoneName +if type(ZoneName)=="string"then +zone=ZONE:FindByName(ZoneName) +end +local _id=zone:GetProperty('OBJECT ID') +if not _id then +zone:Scan({Object.Category.SCENERY}) +local scanned=zone:GetScannedSceneryObjects() +if#scanned>0 then +return scanned +else +return nil +end +else +local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +if obj then +return{obj} +else +return nil +end +end +end +function SCENERY:Destroy() +return self +end +MARKER={ +ClassName="MARKER", +Debug=false, +lid=nil, +mid=nil, +coordinate=nil, +text=nil, +message=nil, +readonly=nil, +coalition=nil, +} +_MARKERID=0 +MARKER.version="0.1.1" +function MARKER:New(Coordinate,Text) +local self=BASE:Inherit(self,FSM:New()) +self.coordinate=UTILS.DeepCopy(Coordinate) +self.text=Text +self.readonly=false +self.message="" +_MARKERID=_MARKERID+1 +self.myid=_MARKERID +self.lid=string.format("Marker #%d | ",self.myid) +self:SetStartState("Invisible") +self:AddTransition("Invisible","Added","Visible") +self:AddTransition("Visible","Removed","Invisible") +self:AddTransition("*","Changed","*") +self:AddTransition("*","TextUpdate","*") +self:AddTransition("*","CoordUpdate","*") +self:HandleEvent(EVENTS.MarkAdded) +self:HandleEvent(EVENTS.MarkRemoved) +self:HandleEvent(EVENTS.MarkChange) +return self +end +function MARKER:ReadOnly() +self.readonly=true +return self +end +function MARKER:ReadWrite() +self.readonly=false +return self +end +function MARKER:Message(Text) +self.message=Text or"" +return self +end +function MARKER:ToAll(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MARKER.ToAll,self) +else +self.toall=true +self.tocoalition=nil +self.coalition=nil +self.togroup=nil +self.groupname=nil +self.groupid=nil +if self.shown then +self:Remove() +end +self.mid=UTILS.GetMarkID() +trigger.action.markToAll(self.mid,self.text,self.coordinate:GetVec3(),self.readonly,self.message) +end +return self +end +function MARKER:ToCoalition(Coalition,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MARKER.ToCoalition,self,Coalition) +else +self.coalition=Coalition +self.tocoalition=true +self.toall=false +self.togroup=false +self.groupname=nil +self.groupid=nil +if self.shown then +self:Remove() +end +self.mid=UTILS.GetMarkID() +trigger.action.markToCoalition(self.mid,self.text,self.coordinate:GetVec3(),self.coalition,self.readonly,self.message) +end +return self +end +function MARKER:ToBlue(Delay) +self:ToCoalition(coalition.side.BLUE,Delay) +return self +end +function MARKER:ToRed(Delay) +self:ToCoalition(coalition.side.RED,Delay) +return self +end +function MARKER:ToNeutral(Delay) +self:ToCoalition(coalition.side.NEUTRAL,Delay) +return self +end +function MARKER:ToGroup(Group,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MARKER.ToGroup,self,Group) +else +if Group and Group:IsAlive()~=nil then +self.groupid=Group:GetID() +if self.groupid then +self.groupname=Group:GetName() +self.togroup=true +self.tocoalition=nil +self.coalition=nil +self.toall=nil +if self.shown then +self:Remove() +end +self.mid=UTILS.GetMarkID() +trigger.action.markToGroup(self.mid,self.text,self.coordinate:GetVec3(),self.groupid,self.readonly,self.message) +end +else +end +end +return self +end +function MARKER:UpdateText(Text,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MARKER.UpdateText,self,Text) +else +self.text=tostring(Text) +self:Refresh() +self:TextUpdate(tostring(Text)) +end +return self +end +function MARKER:UpdateCoordinate(Coordinate,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MARKER.UpdateCoordinate,self,Coordinate) +else +self.coordinate=Coordinate +self:Refresh() +self:CoordUpdate(Coordinate) +end +return self +end +function MARKER:Refresh(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MARKER.Refresh,self) +else +if self.toall then +self:ToAll() +elseif self.tocoalition then +self:ToCoalition(self.coalition) +elseif self.togroup then +local group=GROUP:FindByName(self.groupname) +self:ToGroup(group) +else +self:E(self.lid.."ERROR: unknown To in :Refresh()!") +end +end +return self +end +function MARKER:Remove(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MARKER.Remove,self) +else +if self.shown then +trigger.action.removeMark(self.mid) +end +end +return self +end +function MARKER:GetCoordinate() +return self.coordinate +end +function MARKER:GetText() +return self.text +end +function MARKER:SetText(Text) +self.text=Text and tostring(Text)or"" +return self +end +function MARKER:IsVisible() +return self:Is("Visible") +end +function MARKER:IsInvisible() +return self:Is("Invisible") +end +function MARKER:OnEventMarkAdded(EventData) +if EventData and EventData.MarkID then +local MarkID=EventData.MarkID +self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s",tostring(MarkID))) +if MarkID==self.mid then +self.shown=true +self:Added(EventData) +end +end +end +function MARKER:OnEventMarkRemoved(EventData) +if EventData and EventData.MarkID then +local MarkID=EventData.MarkID +local MarkID=EventData.MarkID +self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s",tostring(MarkID))) +if MarkID==self.mid then +self.shown=false +self:Removed(EventData) +end +end +end +function MARKER:OnEventMarkChange(EventData) +if EventData and EventData.MarkID then +local MarkID=EventData.MarkID +self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s",tostring(MarkID))) +if MarkID==self.mid then +local MarkID=EventData.MarkID +self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s",tostring(MarkID))) +if MarkID==self.mid then +self.text=tostring(EventData.MarkText) +self:Changed(EventData) +end +end +end +end +function MARKER:onafterAdded(From,Event,To,EventData) +local text=string.format("Captured event MarkAdded for myself:\n") +text=text..string.format("Marker ID = %s\n",tostring(EventData.MarkID)) +text=text..string.format("Coalition = %s\n",tostring(EventData.MarkCoalition)) +text=text..string.format("Group ID = %s\n",tostring(EventData.MarkGroupID)) +text=text..string.format("Initiator = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody") +text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere") +text=text..string.format("Text: \n%s",tostring(EventData.MarkText)) +self:T2(self.lid..text) +end +function MARKER:onafterRemoved(From,Event,To,EventData) +local text=string.format("Captured event MarkRemoved for myself:\n") +text=text..string.format("Marker ID = %s\n",tostring(EventData.MarkID)) +text=text..string.format("Coalition = %s\n",tostring(EventData.MarkCoalition)) +text=text..string.format("Group ID = %s\n",tostring(EventData.MarkGroupID)) +text=text..string.format("Initiator = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody") +text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere") +text=text..string.format("Text: \n%s",tostring(EventData.MarkText)) +self:T2(self.lid..text) +end +function MARKER:onafterChanged(From,Event,To,EventData) +local text=string.format("Captured event MarkChange for myself:\n") +text=text..string.format("Marker ID = %s\n",tostring(EventData.MarkID)) +text=text..string.format("Coalition = %s\n",tostring(EventData.MarkCoalition)) +text=text..string.format("Group ID = %s\n",tostring(EventData.MarkGroupID)) +text=text..string.format("Initiator = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody") +text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere") +text=text..string.format("Text: \n%s",tostring(EventData.MarkText)) +self:T2(self.lid..text) +end +function MARKER:onafterTextUpdate(From,Event,To,Text) +self:T(self.lid..string.format("New Marker Text:\n%s",Text)) +end +function MARKER:onafterCoordUpdate(From,Event,To,Coordinate) +self:T(self.lid..string.format("New Marker Coordinate in LL DMS: %s",Coordinate:ToStringLLDMS())) +end +WEAPON={ +ClassName="WEAPON", +verbose=0, +} +WEAPON.version="0.1.0" +function WEAPON:New(WeaponObject) +if WeaponObject==nil then +env.error("ERROR: Weapon object does NOT exist") +return nil +end +local self=BASE:Inherit(self,POSITIONABLE:New("Weapon")) +self.weapon=WeaponObject +self.desc=WeaponObject:getDesc() +self.category=self.desc.category +if self:IsMissile()and self.desc.missileCategory then +self.categoryMissile=self.desc.missileCategory +if self.desc.guidance then +self.guidance=self.desc.guidance +end +end +self.typeName=WeaponObject:getTypeName()or"Unknown Type" +self.name=WeaponObject:getName() +self.coalition=WeaponObject:getCoalition() +self.country=WeaponObject:getCountry() +self.launcher=WeaponObject:getLauncher() +self.launcherName="Unknown Launcher" +if self.launcher then +self.launcherName=self.launcher:getName() +self.launcherUnit=UNIT:Find(self.launcher) +end +self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint()) +self.lid=string.format("[%s] %s | ",self.typeName,self.name) +if self.launcherUnit then +self.releaseHeading=self.launcherUnit:GetHeading() +self.releaseAltitudeASL=self.launcherUnit:GetAltitude() +self.releaseAltitudeAGL=self.launcherUnit:GetAltitude(true) +self.releaseCoordinate=self.launcherUnit:GetCoordinate() +self.releasePitch=self.launcherUnit:GetPitch() +end +self:SetTimeStepTrack() +self:SetDistanceInterceptPoint() +local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s", +self.version,self.name,self.typeName,self.category,self.coalition,self.country,self.launcherName) +self:T(self.lid..text) +self:T2(self.desc) +return self +end +function WEAPON:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function WEAPON:SetTimeStepTrack(TimeStep) +self.dtTrack=TimeStep or 0.01 +return self +end +function WEAPON:SetDistanceInterceptPoint(Distance) +self.distIP=Distance or 50 +return self +end +function WEAPON:SetMarkImpact(Switch) +if Switch==false then +self.impactMark=false +else +self.impactMark=true +end +return self +end +function WEAPON:SetSmokeImpact(Switch,SmokeColor) +if Switch==false then +self.impactSmoke=false +else +self.impactSmoke=true +end +self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red +return self +end +function WEAPON:SetFuncTrack(FuncTrack,...) +self.trackFunc=FuncTrack +self.trackArg=arg or{} +return self +end +function WEAPON:SetFuncImpact(FuncImpact,...) +self.impactFunc=FuncImpact +self.impactArg=arg or{} +return self +end +function WEAPON:GetLauncher() +return self.launcherUnit +end +function WEAPON:GetTarget() +local target=nil +if self.weapon then +local object=self.weapon:getTarget() +if object then +local category=Object.getCategory(object) +local name=object:getName() +if name then +self:T(self.lid..string.format("Got Target Object %s, category=%d",name,category)) +if category==Object.Category.UNIT then +target=UNIT:FindByName(name) +elseif category==Object.Category.STATIC then +target=STATIC:FindByName(name,false) +elseif category==Object.Category.SCENERY then +self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!")) +else +self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!",category)) +end +end +end +end +return target +end +function WEAPON:GetTargetDistance(ConversionFunction) +local target=self:GetTarget() +local distance=nil +if target then +local tv3=target:GetVec3() +local wv3=self:GetVec3() +if tv3 and wv3 then +distance=UTILS.VecDist3D(tv3,wv3) +if ConversionFunction then +distance=ConversionFunction(distance) +end +end +end +return distance +end +function WEAPON:GetTargetName() +local target=self:GetTarget() +local name="None" +if target then +name=target:GetName() +end +return name +end +function WEAPON:GetVelocityVec3() +local Vvec3=nil +if self.weapon then +Vvec3=self.weapon:getVelocity() +end +return Vvec3 +end +function WEAPON:GetSpeed(ConversionFunction) +local speed=nil +if self.weapon then +local v=self:GetVelocityVec3() +speed=UTILS.VecNorm(v) +if ConversionFunction then +speed=ConversionFunction(speed) +end +end +return speed +end +function WEAPON:GetVec3() +local vec3=nil +if self.weapon then +vec3=self.weapon:getPoint() +end +return vec3 +end +function WEAPON:GetVec2() +local vec3=self:GetVec3() +if vec3 then +local vec2={x=vec3.x,y=vec3.z} +return vec2 +end +return nil +end +function WEAPON:GetTypeName() +return self.typeName +end +function WEAPON:GetCoalition() +return self.coalition +end +function WEAPON:GetCountry() +return self.country +end +function WEAPON:GetDCSObject() +return self.weapon +end +function WEAPON:GetImpactVec3() +return self.impactVec3 +end +function WEAPON:GetImpactCoordinate() +return self.impactCoord +end +function WEAPON:GetReleaseHeading(AccountForMagneticInclination) +AccountForMagneticInclination=AccountForMagneticInclination or true +if AccountForMagneticInclination then return UTILS.ClampAngle(self.releaseHeading-UTILS.GetMagneticDeclination())else return UTILS.ClampAngle(self.releaseHeading)end +end +function WEAPON:GetReleaseAltitudeASL() +return self.releaseAltitudeASL +end +function WEAPON:GetReleaseAltitudeAGL() +return self.releaseAltitudeAGL +end +function WEAPON:GetReleaseCoordinate() +return self.releaseCoordinate +end +function WEAPON:GetReleasePitch() +return self.releasePitch +end +function WEAPON:GetImpactHeading(AccountForMagneticInclination) +AccountForMagneticInclination=AccountForMagneticInclination or true +if AccountForMagneticInclination then return UTILS.ClampAngle(self.impactHeading-UTILS.GetMagneticDeclination())else return self.impactHeading end +end +function WEAPON:InAir() +local inAir=nil +if self.weapon then +inAir=self.weapon:inAir() +end +return inAir +end +function WEAPON:IsExist() +local isExist=nil +if self.weapon then +isExist=self.weapon:isExist() +end +return isExist +end +function WEAPON:IsBomb() +return self.category==Weapon.Category.BOMB +end +function WEAPON:IsMissile() +return self.category==Weapon.Category.MISSILE +end +function WEAPON:IsRocket() +return self.category==Weapon.Category.ROCKET +end +function WEAPON:IsShell() +return self.category==Weapon.Category.SHELL +end +function WEAPON:IsTorpedo() +return self.category==Weapon.Category.TORPEDO +end +function WEAPON:IsFoxOne() +return self.guidance==Weapon.GuidanceType.RADAR_SEMI_ACTIVE +end +function WEAPON:IsFoxTwo() +return self.guidance==Weapon.GuidanceType.IR +end +function WEAPON:IsFoxThree() +return self.guidance==Weapon.GuidanceType.RADAR_ACTIVE +end +function WEAPON:Destroy(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,WEAPON.Destroy,self,0) +else +if self.weapon then +self:T(self.lid.."Destroying Weapon NOW!") +self:StopTrack() +self.weapon:destroy() +end +end +return self +end +function WEAPON:StartTrack(Delay) +Delay=math.max(Delay or 0.001,0.001) +self:T(self.lid..string.format("Start tracking weapon in %.4f sec",Delay)) +self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon,self,timer.getTime()+Delay) +return self +end +function WEAPON:StopTrack(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,WEAPON.StopTrack,self,0) +else +if self.trackScheduleID then +timer.removeFunction(self.trackScheduleID) +end +end +return self +end +function WEAPON:_TrackWeapon(time) +if self.verbose>=20 then +self:I(self.lid..string.format("Tracking at T=%.5f",time)) +end +local status,pos3=pcall( +function() +local point=self.weapon:getPosition() +return point +end +) +if status then +self.pos3=pos3 +self.vec3=UTILS.DeepCopy(self.pos3.p) +self.coordinate:UpdateFromVec3(self.vec3) +self.last_velocity=self.weapon:getVelocity() +self.tracking=true +if self.trackFunc then +self.trackFunc(self,unpack(self.trackArg)) +end +if self.verbose>=5 then +local vec2={x=self.vec3.x,y=self.vec3.z} +local height=land.getHeight(vec2) +local agl=self.vec3.y-height +local ip=self:_GetIP(self.distIP) +local d=0 +if ip then +d=UTILS.VecDist3D(self.vec3,ip) +end +self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f",time,height,agl,d)) +end +else +local ip=self:_GetIP(self.distIP) +if self.verbose>=10 and ip then +self:I(self.lid.."Got intercept point!") +local coord=COORDINATE:NewFromVec3(ip) +coord:MarkToAll("Intercept point") +coord:SmokeBlue() +local d=UTILS.VecDist3D(ip,self.vec3) +self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters",d)) +end +self.impactVec3=ip or self.vec3 +self.impactCoord=COORDINATE:NewFromVec3(self.vec3) +self.impactHeading=UTILS.VecHdg(self.last_velocity) +if self.impactMark then +self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s",self.name,self.typeName,self.launcherName)) +end +if self.impactSmoke then +self.impactCoord:Smoke(self.impactSmokeColor) +end +if self.impactFunc then +self.impactFunc(self,unpack(self.impactArg or{})) +end +self.tracking=false +end +if self.tracking then +if self.dtTrack and self.dtTrack>=0.00001 then +return time+self.dtTrack +else +return nil +end +end +return nil +end +function WEAPON:_GetIP(Distance) +Distance=Distance or 50 +local ip=nil +if Distance>0 and self.pos3 then +ip=land.getIP(self.pos3.p,self.pos3.x,Distance or 20) +end +return ip +end +do +NET={ +ClassName="NET", +Version="0.1.4", +BlockTime=600, +BlockedPilots={}, +BlockedUCIDs={}, +BlockedSides={}, +BlockedSlots={}, +KnownPilots={}, +BlockMessage=nil, +UnblockMessage=nil, +lid=nil, +} +function NET:New() +local self=BASE:Inherit(self,FSM:New()) +self.BlockTime=600 +self.BlockedPilots={} +self.KnownPilots={} +self:SetBlockMessage() +self:SetUnblockMessage() +self.BlockedSides={} +self.BlockedSides[1]=false +self.BlockedSides[2]=false +self:SetStartState("Stopped") +self:AddTransition("Stopped","Run","Running") +self:AddTransition("*","PlayerJoined","*") +self:AddTransition("*","PlayerLeft","*") +self:AddTransition("*","PlayerDied","*") +self:AddTransition("*","PlayerEjected","*") +self:AddTransition("*","PlayerBlocked","*") +self:AddTransition("*","PlayerUnblocked","*") +self:AddTransition("*","Status","*") +self:AddTransition("*","Stop","Stopped") +self.lid=string.format("NET %s | ",self.Version) +self:Run() +return self +end +function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot) +self:T({UCID,Name,PlayerID,PlayerSide,PlayerSlot}) +local blocked=false +local TNow=timer.getTime() +if UCID and self.BlockedUCIDs[UCID]and TNow3)then +self:__PlayerJoined(1,client,name) +self.KnownPilots[name]={ +name=name, +ucid=ucid, +id=PlayerID, +side=PlayerSide, +slot=PlayerSlot, +timestamp=TNow, +} +end +return self +end +end +if data.id==EVENTS.PlayerLeaveUnit and self.KnownPilots[name]then +self:T(self.lid.."Pilot Leaving: "..name.." | UCID: "..ucid) +self:__PlayerLeft(1,data.IniUnit,name) +self.KnownPilots[name]=false +return self +end +if data.id==EVENTS.Ejection and self.KnownPilots[name]then +self:T(self.lid.."Pilot Ejecting: "..name.." | UCID: "..ucid) +self:__PlayerEjected(1,data.IniUnit,name) +self.KnownPilots[name]=false +return self +end +if(data.id==EVENTS.PilotDead or data.id==EVENTS.SelfKillPilot or data.id==EVENTS.Crash)and self.KnownPilots[name]then +self:T(self.lid.."Pilot Dead: "..name.." | UCID: "..ucid) +self:__PlayerDied(1,data.IniUnit,name) +self.KnownPilots[name]=false +return self +end +end +return self +end +function NET:BlockPlayer(Client,PlayerName,Seconds,Message) +self:T({PlayerName,Seconds,Message}) +local name=PlayerName +if Client and(not PlayerName)then +name=Client:GetPlayerName() +elseif PlayerName then +name=PlayerName +else +self:F(self.lid.."Block: No Client or PlayerName given or nothing found!") +return self +end +local ucid=self:GetPlayerUCID(Client,name) +local addon=Seconds or self.BlockTime +self.BlockedPilots[name]=timer.getTime()+addon +self.BlockedUCIDs[ucid]=timer.getTime()+addon +local message=Message or self.BlockMessage +if name then +self:SendChatToPlayer(message,name) +else +self:SendChat(name..": "..message) +end +self:__PlayerBlocked(1,Client,name,Seconds) +local PlayerID=self:GetPlayerIDByName(name) +if PlayerID and tonumber(PlayerID)~=1 then +local outcome=net.force_player_slot(tonumber(PlayerID),0,'') +end +return self +end +function NET:BlockPlayerSet(PlayerSet,Seconds,Message) +self:T({PlayerSet.Set,Seconds,Message}) +local addon=Seconds or self.BlockTime +local message=Message or self.BlockMessage +for _,_client in pairs(PlayerSet.Set)do +local name=_client:GetPlayerName() +self:BlockPlayer(_client,name,addon,message) +end +return self +end +function NET:UnblockPlayerSet(PlayerSet,Message) +self:T({PlayerSet.Set,Seconds,Message}) +local message=Message or self.UnblockMessage +for _,_client in pairs(PlayerSet.Set)do +local name=_client:GetPlayerName() +self:UnblockPlayer(_client,name,message) +end +return self +end +function NET:BlockUCID(ucid,Seconds) +self:T({ucid,Seconds}) +local addon=Seconds or self.BlockTime +self.BlockedUCIDs[ucid]=timer.getTime()+addon +return self +end +function NET:UnblockUCID(ucid) +self:T({ucid}) +self.BlockedUCIDs[ucid]=nil +return self +end +function NET:BlockSide(Side,Seconds) +local addon=Seconds or self.BlockTime +if Side==1 or Side==2 then +self.BlockedSides[Side]=timer.getTime()+addon +end +return self +end +function NET:UnblockSide(Side,Seconds) +local addon=Seconds or self.BlockTime +if Side==1 or Side==2 then +self.BlockedSides[Side]=false +end +return self +end +function NET:BlockSlot(Slot,Seconds) +self:T({Slot,Seconds}) +local addon=Seconds or self.BlockTime +self.BlockedSlots[Slot]=timer.getTime()+addon +return self +end +function NET:UnblockSlot(Slot) +self:T({Slot}) +self.BlockedSlots[Slot]=nil +return self +end +function NET:UnblockPlayer(Client,PlayerName,Message) +local name=PlayerName +if Client then +name=Client:GetPlayerName() +elseif PlayerName then +name=PlayerName +else +self:F(self.lid.."Unblock: No PlayerName given or not found!") +return self +end +local ucid=self:GetPlayerUCID(Client,name) +self.BlockedPilots[name]=nil +self.BlockedUCIDs[ucid]=nil +local message=Message or self.UnblockMessage +if name then +self:SendChatToPlayer(message,name) +else +self:SendChat(name..": "..message) +end +self:__PlayerUnblocked(1,Client,name) +return self +end +function NET:SetBlockMessage(Text) +self.BlockMessage=Text or"You are blocked from joining. Wait time is: "..self.BlockTime.." seconds!" +return self +end +function NET:SetBlockTime(Seconds) +self.BlockTime=Seconds or 600 +return self +end +function NET:SetUnblockMessage(Text) +self.UnblockMessage=Text or"You are unblocked now and can join again." +return self +end +function NET:SendChat(Message,ToAll) +if Message then +net.send_chat(Message,ToAll) +end +return self +end +function NET:GetPlayerIDByName(Name) +if not Name then return nil end +local playerList=net.get_player_list() +for i=1,#playerList do +local playerName=net.get_name(i) +if playerName==Name then +return playerList[i] +end +end +return nil +end +function NET:GetPlayerIDFromClient(Client) +self:T("GetPlayerIDFromClient") +self:T({Client=Client}) +if Client then +local name=Client:GetPlayerName() +self:T({name=name}) +local id=self:GetPlayerIDByName(name) +return id +else +return nil +end +end +function NET:SendChatToClient(Message,ToClient,FromClient) +local PlayerId=self:GetPlayerIDFromClient(ToClient) +local FromId=self:GetPlayerIDFromClient(FromClient) +if Message and PlayerId and FromId then +net.send_chat_to(Message,tonumber(PlayerId),tonumber(FromId)) +elseif Message and PlayerId then +net.send_chat_to(Message,tonumber(PlayerId)) +end +return self +end +function NET:SendChatToPlayer(Message,ToPlayer,FromPlayer) +local PlayerId=self:GetPlayerIDByName(ToPlayer) +local FromId=self:GetPlayerIDByName(FromPlayer) +if Message and PlayerId and FromId then +net.send_chat_to(Message,tonumber(PlayerId),tonumber(FromId)) +elseif Message and PlayerId then +net.send_chat_to(Message,tonumber(PlayerId)) +end +return self +end +function NET:GetPlayerList() +local plist=nil +plist=net.get_player_list() +return plist +end +function NET:GetMyPlayerID() +return net.get_my_player_id() +end +function NET:GetServerID() +return net.get_server_id() +end +function NET:GetPlayerInfo(Client,Attribute) +local PlayerID=self:GetPlayerIDFromClient(Client) +if PlayerID then +return net.get_player_info(tonumber(PlayerID),Attribute) +else +return nil +end +end +function NET:GetPlayerUCID(Client,Name) +local PlayerID=nil +if Client then +PlayerID=self:GetPlayerIDFromClient(Client) +elseif Name then +PlayerID=self:GetPlayerIDByName(Name) +else +self:E(self.lid.."Neither client nor name provided!") +end +local ucid=net.get_player_info(tonumber(PlayerID),'ucid') +return ucid +end +function NET:Kick(Client,Message) +local PlayerID=self:GetPlayerIDFromClient(Client) +if PlayerID and tonumber(PlayerID)~=1 then +return net.kick(tonumber(PlayerID),Message) +else +return false +end +end +function NET:GetPlayerStatistic(Client,StatisticID) +local PlayerID=self:GetPlayerIDFromClient(Client) +local stats=StatisticID or 0 +if stats>7 or stats<0 then stats=0 end +if PlayerID then +return net.get_stat(tonumber(PlayerID),stats) +else +return nil +end +end +function NET:GetName(Client) +local PlayerID=self:GetPlayerIDFromClient(Client) +if PlayerID then +return net.get_name(tonumber(PlayerID)) +else +return nil +end +end +function NET:GetSlot(Client) +self:T("NET.GetSlot") +local PlayerID=self:GetPlayerIDFromClient(Client) +self:T("NET.GetSlot PlayerID = "..tostring(PlayerID)) +if PlayerID then +local side,slot=net.get_slot(tonumber(PlayerID)) +self:T("NET.GetSlot side, slot = "..tostring(side)..","..tostring(slot)) +return side,slot +else +return nil,nil +end +end +function NET:ForceSlot(Client,SideID,SlotID) +local PlayerID=self:GetPlayerIDFromClient(Client) +local SlotID=SlotID or Client:GetID() +if PlayerID then +return net.force_player_slot(tonumber(PlayerID),SideID,SlotID) +else +return false +end +end +function NET:ReturnToSpectators(Client) +local outcome=self:ForceSlot(Client,0) +local sched=TIMER:New(Client.Destroy,Client,1):Start(1) +return outcome +end +function NET.Lua2Json(Lua) +return net.lua2json(Lua) +end +function NET.Json2Lua(Json) +return net.json2lua(Json) +end +function NET:DoStringIn(State,DoString) +return net.dostring_in(State,DoString) +end +function NET:Log(Message) +net.log(Message) +return self +end +function NET:GetKnownPilotData(Client,Name) +local name=Name +if Client and not Name then +name=Client:GetPlayerName() +end +if name then +return self.KnownPilots[name] +else +return nil +end +end +function NET:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local function HouseHold(tavolo) +local TNow=timer.getTime() +for _,entry in pairs(tavolo)do +if type(entry)=="number"and entry>=TNow then entry=false end +end +end +HouseHold(self.BlockedPilots) +HouseHold(self.BlockedSides) +HouseHold(self.BlockedSlots) +HouseHold(self.BlockedUCIDs) +if self:Is("Running")then +self:__Status(-60) +end +return self +end +function NET:onafterRun(From,Event,To) +self:T({From,Event,To}) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler) +self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler) +self:HandleEvent(EVENTS.PilotDead,self._EventHandler) +self:HandleEvent(EVENTS.Ejection,self._EventHandler) +self:HandleEvent(EVENTS.Crash,self._EventHandler) +self:HandleEvent(EVENTS.SelfKillPilot,self._EventHandler) +self:__Status(-10) +end +function NET:onafterStop(From,Event,To) +self:T({From,Event,To}) +self:UnHandleEvent(EVENTS.PlayerEnterUnit) +self:UnHandleEvent(EVENTS.PlayerEnterAircraft) +self:UnHandleEvent(EVENTS.PlayerLeaveUnit) +self:UnHandleEvent(EVENTS.PilotDead) +self:UnHandleEvent(EVENTS.Ejection) +self:UnHandleEvent(EVENTS.Crash) +self:UnHandleEvent(EVENTS.SelfKillPilot) +return self +end +end +STORAGE={ +ClassName="STORAGE", +verbose=0, +} +STORAGE.Liquid={ +JETFUEL=0, +GASOLINE=1, +MW50=2, +DIESEL=3, +} +STORAGE.LiquidName={ +GASOLINE="gasoline", +DIESEL="diesel", +MW50="methanol_mixture", +JETFUEL="jet_fuel", +} +STORAGE.Type={ +WEAPONS="weapons", +LIQUIDS="liquids", +AIRCRAFT="aircrafts", +} +STORAGE.version="0.1.5" +function STORAGE:New(AirbaseName) +local self=BASE:Inherit(self,BASE:New()) +self.airbase=Airbase.getByName(AirbaseName) +if Airbase.getWarehouse and self.airbase then +self.warehouse=self.airbase:getWarehouse() +end +self.lid=string.format("STORAGE %s | ",AirbaseName) +return self +end +function STORAGE:NewFromStaticCargo(StaticCargoName) +local self=BASE:Inherit(self,BASE:New()) +self.airbase=StaticObject.getByName(StaticCargoName) +if Airbase.getWarehouse then +self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) +end +self.lid=string.format("STORAGE %s | ",StaticCargoName) +return self +end +function STORAGE:NewFromDynamicCargo(DynamicCargoName) +local self=BASE:Inherit(self,BASE:New()) +self.airbase=Unit.getByName(DynamicCargoName)or StaticObject.getByName(DynamicCargoName) +if Airbase.getWarehouse then +self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) +end +self.lid=string.format("STORAGE %s | ",DynamicCargoName) +return self +end +function STORAGE:FindByName(AirbaseName) +local storage=_DATABASE:FindStorage(AirbaseName) +return storage +end +function STORAGE:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +if self.verbose>1 then +BASE:TraceOn() +BASE:TraceClass("STORAGE") +end +return self +end +function STORAGE:AddItem(Name,Amount) +self:T(self.lid..string.format("Adding %d items of %s",Amount,UTILS.OneLineSerialize(Name))) +self.warehouse:addItem(Name,Amount) +return self +end +function STORAGE:SetItem(Name,Amount) +self:T(self.lid..string.format("Setting item %s to N=%d",UTILS.OneLineSerialize(Name),Amount)) +self.warehouse:setItem(Name,Amount) +return self +end +function STORAGE:GetItemAmount(Name) +local N=self.warehouse:getItemCount(Name) +return N +end +function STORAGE:RemoveItem(Name,Amount) +self:T(self.lid..string.format("Removing N=%d of item %s",Amount,Name)) +self.warehouse:removeItem(Name,Amount) +return self +end +function STORAGE:AddLiquid(Type,Amount) +self:T(self.lid..string.format("Adding %d liquids of %s",Amount,self:GetLiquidName(Type))) +self.warehouse:addLiquid(Type,Amount) +return self +end +function STORAGE:SetLiquid(Type,Amount) +self:T(self.lid..string.format("Setting liquid %s to N=%d",self:GetLiquidName(Type),Amount)) +self.warehouse:setLiquidAmount(Type,Amount) +return self +end +function STORAGE:RemoveLiquid(Type,Amount) +self:T(self.lid..string.format("Removing N=%d of liquid %s",Amount,self:GetLiquidName(Type))) +self.warehouse:removeLiquid(Type,Amount) +return self +end +function STORAGE:GetLiquidAmount(Type) +local N=self.warehouse:getLiquidAmount(Type) +return N +end +function STORAGE:GetLiquidName(Type) +local name="Unknown" +if Type==STORAGE.Liquid.JETFUEL then +name="Jet fuel" +elseif Type==STORAGE.Liquid.GASOLINE then +name="Aircraft gasoline" +elseif Type==STORAGE.Liquid.MW50 then +name="MW 50" +elseif Type==STORAGE.Liquid.DIESEL then +name="Diesel" +else +self:E(self.lid..string.format("ERROR: Unknown liquid type %s",tostring(Type))) +end +return name +end +function STORAGE:AddAmount(Type,Amount) +if type(Type)=="number"then +self:AddLiquid(Type,Amount) +else +self:AddItem(Type,Amount) +end +return self +end +function STORAGE:RemoveAmount(Type,Amount) +if type(Type)=="number"then +self:RemoveLiquid(Type,Amount) +else +self:RemoveItem(Type,Amount) +end +return self +end +function STORAGE:SetAmount(Type,Amount) +if type(Type)=="number"then +self:SetLiquid(Type,Amount) +else +self:SetItem(Type,Amount) +end +return self +end +function STORAGE:GetAmount(Type) +local N=0 +if type(Type)=="number"then +N=self:GetLiquidAmount(Type) +else +N=self:GetItemAmount(Type) +end +return N +end +function STORAGE:IsUnlimited(Type) +local N=self:GetAmount(Type) +local unlimited=false +if N>0 then +self:RemoveAmount(Type,1) +local n=self:GetAmount(Type) +unlimited=unlimited or n>2^29 or n==N +if not unlimited then +self:AddAmount(Type,1) +end +self:T(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)",tostring(Type),tostring(unlimited),N,n)) +end +return unlimited +end +function STORAGE:IsLimited(Type) +local limited=not self:IsUnlimited(Type) +return limited +end +function STORAGE:IsUnlimitedAircraft() +local unlimited=self:IsUnlimited("A-10C") +return unlimited +end +function STORAGE:IsUnlimitedLiquids() +local unlimited=self:IsUnlimited(STORAGE.Liquid.DIESEL) +return unlimited +end +function STORAGE:IsUnlimitedWeapons() +local unlimited=self:IsUnlimited(ENUMS.Storage.weapons.bombs.Mk_82) +return unlimited +end +function STORAGE:IsLimitedAircraft() +local limited=self:IsLimited("A-10C") +return limited +end +function STORAGE:IsLimitedLiquids() +local limited=self:IsLimited(STORAGE.Liquid.DIESEL) +return limited +end +function STORAGE:IsLimitedWeapons() +local limited=self:IsLimited(ENUMS.Storage.weapons.bombs.Mk_82) +return limited +end +function STORAGE:GetInventory(Item) +local inventory=self.warehouse:getInventory(Item) +return inventory.aircraft,inventory.liquids,inventory.weapon +end +function STORAGE:SaveToFile(Path,Filename) +if not io then +BASE:E("ERROR: io not desanitized. Can't save the files.") +return false +end +if Path==nil and not lfs then +BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your given path.") +end +local ac,lq,wp=self:GetInventory() +local DataAircraft="" +local DataLiquids="" +local DataWeapons="" +if#lq>0 then +DataLiquids=DataLiquids.."Liquids in Storage:\n" +for key,amount in pairs(lq)do +DataLiquids=DataLiquids..tostring(key).."="..tostring(amount).."\n" +end +UTILS.SaveToFile(Path,Filename.."_Liquids.csv",DataLiquids) +if self.verbose and self.verbose>0 then +self:I(self.lid.."Saving Liquids to "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") +end +end +if UTILS.TableLength(ac)>0 then +DataAircraft=DataAircraft.."Aircraft in Storage:\n" +for key,amount in pairs(ac)do +DataAircraft=DataAircraft..tostring(key).."="..tostring(amount).."\n" +end +UTILS.SaveToFile(Path,Filename.."_Aircraft.csv",DataAircraft) +if self.verbose and self.verbose>0 then +self:I(self.lid.."Saving Aircraft to "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") +end +end +if UTILS.TableLength(wp)>0 then +DataWeapons=DataWeapons.."Weapons and Materiel in Storage:\n" +for _,_category in pairs(ENUMS.Storage.weapons)do +for _,_key in pairs(_category)do +local amount=self:GetAmount(_key) +if type(_key)=="table"then +_key="{"..table.concat(_key,",").."}" +end +DataWeapons=DataWeapons..tostring(_key).."="..tostring(amount).."\n" +end +end +for key,amount in pairs(ENUMS.Storage.weapons.Gazelle)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.Gazelle[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.Gazelle."..tostring(key).."="..tostring(amount).."\n" +end +for key,amount in pairs(ENUMS.Storage.weapons.CH47)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.CH47[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.CH47."..tostring(key).."="..tostring(amount).."\n" +end +for key,amount in pairs(ENUMS.Storage.weapons.UH1H)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.UH1H[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.UH1H."..tostring(key).."="..tostring(amount).."\n" +end +for key,amount in pairs(ENUMS.Storage.weapons.OH58)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.OH58[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.OH58."..tostring(key).."="..tostring(amount).."\n" +end +for key,amount in pairs(ENUMS.Storage.weapons.AH64D)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.AH64D[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.AH64D."..tostring(key).."="..tostring(amount).."\n" +end +UTILS.SaveToFile(Path,Filename.."_Weapons.csv",DataWeapons) +if self.verbose and self.verbose>0 then +self:I(self.lid.."Saving Weapons to "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") +end +end +return self +end +function STORAGE:LoadFromFile(Path,Filename) +if not io then +BASE:E("ERROR: io not desanitized. Can't read the files.") +return false +end +if Path==nil and not lfs then +BASE:E("WARNING: lfs not desanitized. File will be read from DCS installation root directory rather than your give path.") +end +if self:IsLimitedLiquids()then +local Ok,Liquids=UTILS.LoadFromFile(Path,Filename.."_Liquids.csv") +if Ok then +if self.verbose and self.verbose>0 then +self:I(self.lid.."Loading Liquids from "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") +end +for _id,_line in pairs(Liquids)do +if string.find(_line,"Storage")==nil then +local tbl=UTILS.Split(_line,"=") +local lqno=tonumber(tbl[1]) +local lqam=tonumber(tbl[2]) +self:SetLiquid(lqno,lqam) +end +end +else +self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") +end +end +if self:IsLimitedAircraft()then +local Ok,Aircraft=UTILS.LoadFromFile(Path,Filename.."_Aircraft.csv") +if Ok then +if self.verbose and self.verbose>0 then +self:I(self.lid.."Loading Aircraft from "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") +end +for _id,_line in pairs(Aircraft)do +if string.find(_line,"Storage")==nil then +local tbl=UTILS.Split(_line,"=") +local acname=tbl[1] +local acnumber=tonumber(tbl[2]) +self:SetAmount(acname,acnumber) +end +end +else +self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") +end +end +if self:IsLimitedWeapons()then +local Ok,Weapons=UTILS.LoadFromFile(Path,Filename.."_Weapons.csv") +if Ok then +if self.verbose and self.verbose>0 then +self:I(self.lid.."Loading Weapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") +end +for _id,_line in pairs(Weapons)do +if string.find(_line,"Storage")==nil then +local tbl=UTILS.Split(_line,"=") +local wpname=tbl[1] +local wpnumber=tonumber(tbl[2]) +if string.find(wpname,"{")==1 then +wpname=string.gsub(wpname,"{","") +wpname=string.gsub(wpname,"}","") +local tbl=UTILS.Split(wpname,",") +local wptbl={} +for _id,_key in ipairs(tbl)do +table.insert(wptbl,_id,_key) +end +self:SetAmount(wptbl,wpnumber) +else +self:SetAmount(wpname,wpnumber) +end +end +end +else +self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") +end +end +return self +end +function STORAGE:StartAutoSave(Path,Filename,Interval,LoadOnce) +if LoadOnce~=false then +self:LoadFromFile(Path,Filename) +end +local interval=Interval or 300 +self.SaverTimer=TIMER:New(STORAGE.SaveToFile,self,Path,Filename) +self.SaverTimer:Start(interval,interval) +return self +end +function STORAGE:StopAutoSave() +if self.SaverTimer and self.SaverTimer:IsRunning()then +self.SaverTimer:Stop() +self.SaverTimer=nil +end +return self +end +function STORAGE:FindSyriaHHelipadWarehouse(ZoneName) +local findzone=ZONE:New(ZoneName) +local base=world.getAirbases() +for i=1,#base do +local info={} +info.callsign=Airbase.getCallsign(base[i]) +info.id=Airbase.getID(base[i]) +info.point=Airbase.getPoint(base[i]) +info.coordinate=COORDINATE:NewFromVec3(info.point) +info.DCSObject=base[i] +if info.callsign=="H"and findzone:IsCoordinateInZone(info.coordinate)then +info.warehouse=info.DCSObject:getWarehouse() +info.Storage=STORAGE:New(info.callsign..info.id) +info.Storage.airbase=info.DCSObject +info.Storage.warehouse=info.warehouse +return info.Storage +end +end +end +DYNAMICCARGO={ +ClassName="DYNAMICCARGO", +verbose=0, +testing=false, +Interval=10, +} +DYNAMICCARGO.Liquid={ +JETFUEL=0, +GASOLINE=1, +MW50=2, +DIESEL=3, +} +DYNAMICCARGO.LiquidName={ +GASOLINE="gasoline", +DIESEL="diesel", +MW50="methanol_mixture", +JETFUEL="jet_fuel", +} +DYNAMICCARGO.Type={ +WEAPONS="weapons", +LIQUIDS="liquids", +AIRCRAFT="aircrafts", +} +DYNAMICCARGO.State={ +NEW="NEW", +LOADED="LOADED", +UNLOADED="UNLOADED", +REMOVED="REMOVED", +} +DYNAMICCARGO.AircraftTypes={ +["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="CH-47Fbl1", +["Mi-8MT"]="CH-47Fbl1", +} +DYNAMICCARGO.AircraftDimensions={ +["CH-47Fbl1"]={ +["width"]=4, +["height"]=6, +["length"]=11, +["ropelength"]=30, +}, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +} +DYNAMICCARGO.version="0.0.9" +function DYNAMICCARGO:Register(CargoName) +local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) +self.StaticName=CargoName +self.LastPosition=self:GetCoordinate() +self.CargoState=DYNAMICCARGO.State.NEW +self.Interval=DYNAMICCARGO.Interval or 10 +local DCSObject=self:GetDCSObject() +if DCSObject then +local warehouse=STORAGE:NewFromDynamicCargo(CargoName) +self.warehouse=warehouse +end +self.lid=string.format("DYNAMICCARGO %s",CargoName) +self.Owner=string.match(CargoName,"^(.+)|%d%d:%d%d|PKG%d+")or"None" +self.timer=TIMER:New(DYNAMICCARGO._UpdatePosition,self) +self.timer:Start(self.Interval,self.Interval) +if not _DYNAMICCARGO_HELOS then +_DYNAMICCARGO_HELOS=SET_CLIENT:New():FilterAlive():FilterFunction(DYNAMICCARGO._FilterHeloTypes):FilterStart() +end +if self.testing then +BASE:TraceOn() +BASE:TraceClass("DYNAMICCARGO") +end +return self +end +function DYNAMICCARGO:GetDCSObject() +local DCSStatic=StaticObject.getByName(self.StaticName)or Unit.getByName(self.StaticName) +if DCSStatic then +return DCSStatic +end +return nil +end +function DYNAMICCARGO:GetLastOwner() +return self.Owner +end +function DYNAMICCARGO:IsNew() +if self.CargoState and self.CargoState==DYNAMICCARGO.State.NEW then +return true +else +return false +end +end +function DYNAMICCARGO:IsLoaded() +if self.CargoState and self.CargoState==DYNAMICCARGO.State.LOADED then +return true +else +return false +end +end +function DYNAMICCARGO:IsUnloaded() +if self.CargoState and self.CargoState==DYNAMICCARGO.State.UNLOADED then +return true +else +return false +end +end +function DYNAMICCARGO:IsRemoved() +if self.CargoState and self.CargoState==DYNAMICCARGO.State.REMOVED then +return true +else +return false +end +end +function DYNAMICCARGO:GetCratesNeeded() +return 1 +end +function DYNAMICCARGO:WasDropped() +return self.CargoState==DYNAMICCARGO.State.UNLOADED and true or false +end +function DYNAMICCARGO:GetType() +return CTLD_CARGO.Enum.GCLOADABLE +end +function DYNAMICCARGO:GetLastPosition() +return self.LastPosition +end +function DYNAMICCARGO:GetState() +return self.CargoState +end +function DYNAMICCARGO:FindByName(Name) +local storage=_DATABASE:FindDynamicCargo(Name) +return storage +end +function DYNAMICCARGO:FindByMatching(Pattern) +local GroupFound=nil +for name,static in pairs(_DATABASE.DYNAMICCARGO)do +if string.match(name,Pattern)then +GroupFound=static +break +end +end +return GroupFound +end +function DYNAMICCARGO:FindAllByMatching(Pattern) +local GroupsFound={} +for name,static in pairs(_DATABASE.DYNAMICCARGO)do +if string.match(name,Pattern)then +GroupsFound[#GroupsFound+1]=static +end +end +return GroupsFound +end +function DYNAMICCARGO:GetStorageObject() +return self.warehouse +end +function DYNAMICCARGO:GetCargoWeight() +local DCSObject=self:GetDCSObject() +if DCSObject then +local weight=DCSObject:getCargoWeight() +return weight +else +return 0 +end +end +function DYNAMICCARGO:GetCargoDisplayName() +local DCSObject=self:GetDCSObject() +if DCSObject then +local weight=DCSObject:getCargoDisplayName() +return weight +else +return self.StaticName +end +end +function DYNAMICCARGO:_HeloHovering(Unit,ropelength) +local DCSUnit=Unit:GetDCSObject() +local hovering=false +local Height=0 +if DCSUnit then +local UnitInAir=DCSUnit:inAir() +local UnitCategory=DCSUnit:getDesc().category +if UnitInAir==true and UnitCategory==1 then +local VelocityVec3=DCSUnit:getVelocity() +local Velocity=UTILS.VecNorm(VelocityVec3) +local Coordinate=DCSUnit:getPoint() +local LandHeight=land.getHeight({x=Coordinate.x,y=Coordinate.z}) +Height=Coordinate.y-LandHeight +if Velocity<1 and Height<=ropelength and Height>6 then +hovering=true +end +end +return hovering,Height +end +return false +end +function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading) +local set=_DYNAMICCARGO_HELOS:GetAliveSet() +local success=false +local Helo=nil +local Playername=nil +for _,_helo in pairs(set or{})do +local helo=_helo +local name=helo:GetPlayerName()or _DATABASE:_FindPlayerNameByUnitName(helo:GetName())or"None" +self:T(self.lid.." Checking: "..name) +local hpos=helo:GetCoordinate() +local typename=helo:GetTypeName() +local dimensions=DYNAMICCARGO.AircraftDimensions[typename] +local hovering,height=self:_HeloHovering(helo,dimensions.ropelength) +local helolanded=not helo:InAir() +self:T(self.lid.." InAir: AGL/Hovering: "..hpos.y-hpos:GetLandHeight().."/"..tostring(hovering)) +if hpos and typename and dimensions then +local delta2D=hpos:Get2DDistance(pos) +local delta3D=hpos:Get3DDistance(pos) +if self.testing then +self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D)) +self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength)) +self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height)) +end +if loading~=true and(delta2D>dimensions.length or delta2D>dimensions.width)and helolanded then +success=true +Helo=helo +Playername=name +end +if loading~=true and delta3D>dimensions.ropelength then +success=true +Helo=helo +Playername=name +end +if loading==true and((delta2D0.5 then +if self.CargoState==DYNAMICCARGO.State.NEW or self.CargoState==DYNAMICCARGO.State.UNLOADED then +local isloaded,client,playername=self:_GetPossibleHeloNearby(pos,true) +self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername)) +self.CargoState=DYNAMICCARGO.State.LOADED +self.Owner=playername +_DATABASE:CreateEventDynamicCargoLoaded(self) +end +elseif self.CargoState==DYNAMICCARGO.State.LOADED then +local count=_DYNAMICCARGO_HELOS:CountAlive() +local landheight=pos:GetLandHeight() +local agl=pos.y-landheight +agl=UTILS.Round(agl,2) +self:T(self.lid.." AGL: "..agl or-1) +local isunloaded=true +local client +local playername=self.Owner +if count>0 then +self:T(self.lid.." Possible alive helos: "..count or-1) +isunloaded,client,playername=self:_GetPossibleHeloNearby(pos,false) +if isunloaded then +self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername)) +self.CargoState=DYNAMICCARGO.State.UNLOADED +self.Owner=playername +_DATABASE:CreateEventDynamicCargoUnloaded(self) +end +end +end +self.LastPosition=pos +else +if self.timer and self.timer:IsRunning()then self.timer:Stop()end +self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED") +self.CargoState=DYNAMICCARGO.State.REMOVED +_DATABASE:CreateEventDynamicCargoRemoved(self) +end +return self +end +function DYNAMICCARGO._FilterHeloTypes(client) +if not client then return false end +local typename=client:GetTypeName() +local isinclude=DYNAMICCARGO.AircraftTypes[typename]~=nil and true or false +return isinclude +end +CARGOS={} +do +CARGO={ +ClassName="CARGO", +Type=nil, +Name=nil, +Weight=nil, +CargoObject=nil, +CargoCarrier=nil, +Representable=false, +Slingloadable=false, +Moveable=false, +Containable=false, +Reported={}, +} +function CARGO:New(Type,Name,Weight,LoadRadius,NearRadius) +local self=BASE:Inherit(self,FSM:New()) +self:T({Type,Name,Weight,LoadRadius,NearRadius}) +self:SetStartState("UnLoaded") +self:AddTransition({"UnLoaded","Boarding"},"Board","Boarding") +self:AddTransition("Boarding","Boarding","Boarding") +self:AddTransition("Boarding","CancelBoarding","UnLoaded") +self:AddTransition("Boarding","Load","Loaded") +self:AddTransition("UnLoaded","Load","Loaded") +self:AddTransition("Loaded","UnBoard","UnBoarding") +self:AddTransition("UnBoarding","UnBoarding","UnBoarding") +self:AddTransition("UnBoarding","UnLoad","UnLoaded") +self:AddTransition("Loaded","UnLoad","UnLoaded") +self:AddTransition("*","Damaged","Damaged") +self:AddTransition("*","Destroyed","Destroyed") +self:AddTransition("*","Respawn","UnLoaded") +self:AddTransition("*","Reset","UnLoaded") +self.Type=Type +self.Name=Name +self.Weight=Weight or 0 +self.CargoObject=nil +self.CargoCarrier=nil +self.Representable=false +self.Slingloadable=false +self.Moveable=false +self.Containable=false +self.CargoLimit=0 +self.LoadRadius=LoadRadius or 500 +self:SetDeployed(false) +self.CargoScheduler=SCHEDULER:New() +CARGOS[self.Name]=self +return self +end +function CARGO:FindByName(CargoName) +local CargoFound=_DATABASE:FindCargo(CargoName) +return CargoFound +end +function CARGO:GetX() +if self:IsLoaded()then +return self.CargoCarrier:GetCoordinate().x +else +return self.CargoObject:GetCoordinate().x +end +end +function CARGO:GetY() +if self:IsLoaded()then +return self.CargoCarrier:GetCoordinate().z +else +return self.CargoObject:GetCoordinate().z +end +end +function CARGO:GetHeading() +if self:IsLoaded()then +return self.CargoCarrier:GetHeading() +else +return self.CargoObject:GetHeading() +end +end +function CARGO:CanSlingload() +return false +end +function CARGO:CanBoard() +return true +end +function CARGO:CanUnboard() +return true +end +function CARGO:CanLoad() +return true +end +function CARGO:CanUnload() +return true +end +function CARGO:Destroy() +if self.CargoObject then +self.CargoObject:Destroy() +end +self:Destroyed() +end +function CARGO:GetName() +return self.Name +end +function CARGO:GetObject() +if self:IsLoaded()then +return self.CargoCarrier +else +return self.CargoObject +end +end +function CARGO:GetObjectName() +if self:IsLoaded()then +return self.CargoCarrier:GetName() +else +return self.CargoObject:GetName() +end +end +function CARGO:GetCount() +return 1 +end +function CARGO:GetType() +return self.Type +end +function CARGO:GetTransportationMethod() +return self.TransportationMethod +end +function CARGO:GetCoalition() +if self:IsLoaded()then +return self.CargoCarrier:GetCoalition() +else +return self.CargoObject:GetCoalition() +end +end +function CARGO:GetCoordinate() +return self.CargoObject:GetCoordinate() +end +function CARGO:IsDestroyed() +return self:Is("Destroyed") +end +function CARGO:IsLoaded() +return self:Is("Loaded") +end +function CARGO:IsLoadedInCarrier(Carrier) +return self.CargoCarrier and self.CargoCarrier:GetName()==Carrier:GetName() +end +function CARGO:IsUnLoaded() +return self:Is("UnLoaded") +end +function CARGO:IsBoarding() +return self:Is("Boarding") +end +function CARGO:IsUnboarding() +return self:Is("UnBoarding") +end +function CARGO:IsAlive() +if self:IsLoaded()then +return self.CargoCarrier:IsAlive() +else +return self.CargoObject:IsAlive() +end +end +function CARGO:SetDeployed(Deployed) +self.Deployed=Deployed +end +function CARGO:IsDeployed() +return self.Deployed +end +function CARGO:Spawn(PointVec2) +self:T() +end +function CARGO:Flare(FlareColor) +if self:IsUnLoaded()then +trigger.action.signalFlare(self.CargoObject:GetVec3(),FlareColor,0) +end +end +function CARGO:FlareWhite() +self:Flare(trigger.flareColor.White) +end +function CARGO:FlareYellow() +self:Flare(trigger.flareColor.Yellow) +end +function CARGO:FlareGreen() +self:Flare(trigger.flareColor.Green) +end +function CARGO:FlareRed() +self:Flare(trigger.flareColor.Red) +end +function CARGO:Smoke(SmokeColor,Radius) +if self:IsUnLoaded()then +if Radius then +trigger.action.smoke(self.CargoObject:GetRandomVec3(Radius),SmokeColor) +else +trigger.action.smoke(self.CargoObject:GetVec3(),SmokeColor) +end +end +end +function CARGO:SmokeGreen() +self:Smoke(trigger.smokeColor.Green,Range) +end +function CARGO:SmokeRed() +self:Smoke(trigger.smokeColor.Red,Range) +end +function CARGO:SmokeWhite() +self:Smoke(trigger.smokeColor.White,Range) +end +function CARGO:SmokeOrange() +self:Smoke(trigger.smokeColor.Orange,Range) +end +function CARGO:SmokeBlue() +self:Smoke(trigger.smokeColor.Blue,Range) +end +function CARGO:SetLoadRadius(LoadRadius) +self.LoadRadius=LoadRadius or 150 +end +function CARGO:GetLoadRadius() +return self.LoadRadius +end +function CARGO:IsInLoadRadius(Coordinate) +self:T({Coordinate,LoadRadius=self.LoadRadius}) +local Distance=0 +if self:IsUnLoaded()then +local CargoCoordinate=self.CargoObject:GetCoordinate() +Distance=Coordinate:Get2DDistance(CargoCoordinate) +self:T(Distance) +if Distance<=self.LoadRadius then +return true +end +end +return false +end +function CARGO:IsInReportRadius(Coordinate) +self:T({Coordinate}) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +self:T(Distance) +if Distance<=self.LoadRadius then +return true +end +end +return false +end +function CARGO:IsNear(Coordinate,NearRadius) +if self.CargoObject:IsAlive()then +local Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=NearRadius then +return true +end +end +return false +end +function CARGO:IsInZone(Zone) +if self:IsLoaded()then +return Zone:IsPointVec2InZone(self.CargoCarrier:GetPointVec2()) +else +if self.CargoObject:GetSize()~=0 then +return Zone:IsPointVec2InZone(self.CargoObject:GetPointVec2()) +else +return false +end +end +return nil +end +function CARGO:GetPointVec2() +return self.CargoObject:GetPointVec2() +end +function CARGO:GetCoordinate() +return self.CargoObject:GetCoordinate() +end +function CARGO:GetWeight() +return self.Weight +end +function CARGO:SetWeight(Weight) +self.Weight=Weight +return self +end +function CARGO:GetVolume() +return self.Volume +end +function CARGO:SetVolume(Volume) +self.Volume=Volume +return self +end +function CARGO:MessageToGroup(Message,CarrierGroup,Name) +MESSAGE:New(Message,20,"Cargo "..self:GetName()):ToGroup(CarrierGroup) +end +function CARGO:Report(ReportText,Action,CarrierGroup) +if not self.Reported[CarrierGroup]or not self.Reported[CarrierGroup][Action]then +self.Reported[CarrierGroup]={} +self.Reported[CarrierGroup][Action]=true +self:MessageToGroup(ReportText,CarrierGroup) +if self.ReportFlareColor then +if not self.Reported[CarrierGroup]["Flaring"]then +self:Flare(self.ReportFlareColor) +self.Reported[CarrierGroup]["Flaring"]=true +end +end +if self.ReportSmokeColor then +if not self.Reported[CarrierGroup]["Smoking"]then +self:Smoke(self.ReportSmokeColor) +self.Reported[CarrierGroup]["Smoking"]=true +end +end +end +end +function CARGO:ReportFlare(FlareColor) +self.ReportFlareColor=FlareColor +end +function CARGO:ReportSmoke(SmokeColor) +self.ReportSmokeColor=SmokeColor +end +function CARGO:ReportReset(Action,CarrierGroup) +self.Reported[CarrierGroup][Action]=nil +end +function CARGO:ReportResetAll(CarrierGroup) +self.Reported[CarrierGroup]=nil +end +function CARGO:RespawnOnDestroyed(RespawnDestroyed) +if RespawnDestroyed then +self.onenterDestroyed=function(self) +self:Respawn() +end +else +self.onenterDestroyed=nil +end +end +end +do +CARGO_REPRESENTABLE={ +ClassName="CARGO_REPRESENTABLE" +} +function CARGO_REPRESENTABLE:New(CargoObject,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO:New(Type,Name,0,LoadRadius,NearRadius)) +self:T({Type,Name,LoadRadius,NearRadius}) +local Desc=CargoObject:GetDesc() +self:T({Desc=Desc}) +local Weight=math.random(80,120) +if Desc then +if Desc.typeName=="2B11 mortar"then +Weight=210 +else +Weight=Desc.massEmpty +end +end +self:SetWeight(Weight) +return self +end +function CARGO_REPRESENTABLE:Destroy() +self:T({CargoName=self:GetName()}) +return self +end +function CARGO_REPRESENTABLE:RouteTo(ToPointVec2,Speed) +self:F2(ToPointVec2) +local Points={} +local PointStartVec2=self.CargoObject:GetPointVec2() +Points[#Points+1]=PointStartVec2:WaypointGround(Speed) +Points[#Points+1]=ToPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoObject:TaskRoute(Points) +self.CargoObject:SetTask(TaskRoute,2) +return self +end +function CARGO_REPRESENTABLE:MessageToGroup(Message,TaskGroup,Name) +local CoordinateZone=ZONE_RADIUS:New("Zone",self:GetCoordinate():GetVec2(),500) +CoordinateZone:Scan({Object.Category.UNIT}) +for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do +local NearUnit=UNIT:Find(DCSUnit) +self:T({NearUnit=NearUnit}) +local NearUnitCoalition=NearUnit:GetCoalition() +local CargoCoalition=self:GetCoalition() +if NearUnitCoalition==CargoCoalition then +local Attributes=NearUnit:GetDesc() +self:T({Desc=Attributes}) +if NearUnit:HasAttribute("Trucks")then +MESSAGE:New(Message,20,NearUnit:GetCallsign().." reporting - Cargo "..self:GetName()):ToGroup(TaskGroup) +break +end +end +end +end +end +do +CARGO_REPORTABLE={ +ClassName="CARGO_REPORTABLE" +} +function CARGO_REPORTABLE:New(Type,Name,Weight,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO:New(Type,Name,Weight,LoadRadius,NearRadius)) +self:T({Type,Name,Weight,LoadRadius,NearRadius}) +return self +end +function CARGO_REPORTABLE:MessageToGroup(Message,TaskGroup,Name) +MESSAGE:New(Message,20,"Cargo "..self:GetName().." reporting"):ToGroup(TaskGroup) +end +end +do +CARGO_PACKAGE={ +ClassName="CARGO_PACKAGE" +} +function CARGO_PACKAGE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius)) +self:T({Type,Name,Weight,LoadRadius,NearRadius}) +self:T(CargoCarrier) +self.CargoCarrier=CargoCarrier +return self +end +function CARGO_PACKAGE:onafterOnBoard(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) +self:T() +self.CargoInAir=self.CargoCarrier:InAir() +self:T(self.CargoInAir) +if not self.CargoInAir then +local Points={} +local StartPointVec2=self.CargoCarrier:GetPointVec2() +local CargoCarrierHeading=CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +self:T({CargoCarrierHeading,CargoDeployHeading}) +local CargoDeployPointVec2=CargoCarrier:GetPointVec2():Translate(BoardDistance,CargoDeployHeading) +Points[#Points+1]=StartPointVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoCarrier:TaskRoute(Points) +self.CargoCarrier:SetTask(TaskRoute,1) +end +self:Boarded(CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) +end +function CARGO_PACKAGE:IsNear(CargoCarrier) +self:T() +local CargoCarrierPoint=CargoCarrier:GetCoordinate() +local Distance=CargoCarrierPoint:Get2DDistance(self.CargoCarrier:GetCoordinate()) +self:T(Distance) +if Distance<=self.NearRadius then +return true +else +return false +end +end +function CARGO_PACKAGE:onafterOnBoarded(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) +self:T() +if self:IsNear(CargoCarrier)then +self:__Load(1,CargoCarrier,Speed,LoadDistance,Angle) +else +self:__Boarded(1,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) +end +end +function CARGO_PACKAGE:onafterUnBoard(From,Event,To,CargoCarrier,Speed,UnLoadDistance,UnBoardDistance,Radius,Angle) +self:T() +self.CargoInAir=self.CargoCarrier:InAir() +self:T(self.CargoInAir) +if not self.CargoInAir then +self:_Next(self.FsmP.UnLoad,UnLoadDistance,Angle) +local Points={} +local StartPointVec2=CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +self:T({CargoCarrierHeading,CargoDeployHeading}) +local CargoDeployPointVec2=StartPointVec2:Translate(UnBoardDistance,CargoDeployHeading) +Points[#Points+1]=StartPointVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=CargoCarrier:TaskRoute(Points) +CargoCarrier:SetTask(TaskRoute,1) +end +self:__UnBoarded(1,CargoCarrier,Speed) +end +function CARGO_PACKAGE:onafterUnBoarded(From,Event,To,CargoCarrier,Speed) +self:T() +if self:IsNear(CargoCarrier)then +self:__UnLoad(1,CargoCarrier,Speed) +else +self:__UnBoarded(1,CargoCarrier,Speed) +end +end +function CARGO_PACKAGE:onafterLoad(From,Event,To,CargoCarrier,Speed,LoadDistance,Angle) +self:T() +self.CargoCarrier=CargoCarrier +local StartPointVec2=self.CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployPointVec2=StartPointVec2:Translate(LoadDistance,CargoDeployHeading) +local Points={} +Points[#Points+1]=StartPointVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoCarrier:TaskRoute(Points) +self.CargoCarrier:SetTask(TaskRoute,1) +end +function CARGO_PACKAGE:onafterUnLoad(From,Event,To,CargoCarrier,Speed,Distance,Angle) +self:T() +local StartPointVec2=self.CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployPointVec2=StartPointVec2:Translate(Distance,CargoDeployHeading) +self.CargoCarrier=CargoCarrier +local Points={} +Points[#Points+1]=StartPointVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoCarrier:TaskRoute(Points) +self.CargoCarrier:SetTask(TaskRoute,1) +end +end +do +CARGO_UNIT={ +ClassName="CARGO_UNIT" +} +function CARGO_UNIT:New(CargoUnit,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoUnit,Type,Name,LoadRadius,NearRadius)) +self:T({Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) +self.CargoObject=CargoUnit +self:SetEventPriority(5) +return self +end +function CARGO_UNIT:onenterUnBoarding(From,Event,To,ToPointVec2,NearRadius) +self:T({From,Event,To,ToPointVec2,NearRadius}) +local Angle=180 +local Speed=60 +local DeployDistance=9 +local RouteDistance=60 +if From=="Loaded"then +if not self:IsDestroyed()then +local CargoCarrier=self.CargoCarrier +if CargoCarrier:IsAlive()then +local CargoCarrierPointVec2=CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoRoutePointVec2=CargoCarrierPointVec2:Translate(RouteDistance,CargoDeployHeading) +local FromDirectionVec3=CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2 or CargoRoutePointVec2) +local FromAngle=CargoCarrierPointVec2:GetAngleDegrees(FromDirectionVec3) +local FromPointVec2=CargoCarrierPointVec2:Translate(DeployDistance,FromAngle) +ToPointVec2=ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius(NearRadius,DeployDistance) +if self.CargoObject then +if CargoCarrier:IsShip()then +self.CargoObject:ReSpawnAt(ToPointVec2,CargoDeployHeading) +else +self.CargoObject:ReSpawnAt(FromPointVec2,CargoDeployHeading) +end +self:T({"CargoUnits:",self.CargoObject:GetGroup():GetName()}) +self.CargoCarrier=nil +local Points={} +Points[#Points+1]=FromPointVec2:WaypointGround(Speed,"Vee") +Points[#Points+1]=ToPointVec2:WaypointGround(Speed,"Vee") +local TaskRoute=self.CargoObject:TaskRoute(Points) +self.CargoObject:SetTask(TaskRoute,1) +self:__UnBoarding(1,ToPointVec2,NearRadius) +end +else +self:Destroyed() +end +end +end +end +function CARGO_UNIT:onleaveUnBoarding(From,Event,To,ToPointVec2,NearRadius) +self:T({From,Event,To,ToPointVec2,NearRadius}) +local Angle=180 +local Speed=10 +local Distance=5 +if From=="UnBoarding"then +return true +end +end +function CARGO_UNIT:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius) +self:T({From,Event,To,ToPointVec2,NearRadius}) +self.CargoInAir=self.CargoObject:InAir() +self:T(self.CargoInAir) +if not self.CargoInAir then +end +self:__UnLoad(1,ToPointVec2,NearRadius) +end +function CARGO_UNIT:onenterUnLoaded(From,Event,To,ToPointVec2) +self:T({ToPointVec2,From,Event,To}) +local Angle=180 +local Speed=10 +local Distance=5 +if From=="Loaded"then +local StartPointVec2=self.CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployCoord=StartPointVec2:Translate(Distance,CargoDeployHeading) +ToPointVec2=ToPointVec2 or COORDINATE:New(CargoDeployCoord.x,CargoDeployCoord.z) +if self.CargoObject then +self.CargoObject:ReSpawnAt(ToPointVec2,0) +self.CargoCarrier=nil +end +end +if self.OnUnLoadedCallBack then +self.OnUnLoadedCallBack(self,unpack(self.OnUnLoadedParameters)) +self.OnUnLoadedCallBack=nil +end +end +function CARGO_UNIT:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...) +self:T({From,Event,To,CargoCarrier,NearRadius=NearRadius}) +self.CargoInAir=self.CargoObject:InAir() +local Desc=self.CargoObject:GetDesc() +local MaxSpeed=Desc.speedMaxOffRoad +local TypeName=Desc.typeName +if not self.CargoInAir then +local NearRadius=NearRadius or CargoCarrier:GetBoundingRadius()+5 +if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then +self:Load(CargoCarrier,NearRadius,...) +else +if MaxSpeed and MaxSpeed==0 or TypeName and TypeName=="Stinger comm"then +self:Load(CargoCarrier,NearRadius,...) +else +local Speed=90 +local Angle=180 +local Distance=0 +local CargoCarrierPointVec2=CargoCarrier:GetPointVec2() +local CargoCarrierHeading=CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading) +self.CargoObject:OptionAlarmStateGreen() +local Points={} +local PointStartVec2=self.CargoObject:GetPointVec2() +Points[#Points+1]=PointStartVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoObject:TaskRoute(Points) +self.CargoObject:SetTask(TaskRoute,2) +self:__Boarding(-5,CargoCarrier,NearRadius,...) +self.RunCount=0 +end +end +end +end +function CARGO_UNIT:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...) +self:T({From,Event,To,CargoCarrier:GetName(),NearRadius=NearRadius}) +self:T({IsAlive=self.CargoObject:IsAlive()}) +if CargoCarrier and CargoCarrier:IsAlive()then +if(CargoCarrier:IsAir()and not CargoCarrier:InAir())or true then +local NearRadius=NearRadius or CargoCarrier:GetBoundingRadius(NearRadius)+5 +if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then +self:__Load(-1,CargoCarrier,...) +else +if self:IsNear(CargoCarrier:GetPointVec2(),20)then +self:__Boarding(-1,CargoCarrier,NearRadius,...) +self.RunCount=self.RunCount+1 +else +self:__Boarding(-2,CargoCarrier,NearRadius,...) +self.RunCount=self.RunCount+2 +end +if self.RunCount>=40 then +self.RunCount=0 +local Speed=90 +local Angle=180 +local Distance=0 +local CargoCarrierPointVec2=CargoCarrier:GetPointVec2() +local CargoCarrierHeading=CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading) +self.CargoObject:OptionAlarmStateGreen() +local Points={} +local PointStartVec2=self.CargoObject:GetPointVec2() +Points[#Points+1]=PointStartVec2:WaypointGround(Speed,"Off road") +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed,"Off road") +local TaskRoute=self.CargoObject:TaskRoute(Points) +self.CargoObject:SetTask(TaskRoute,0.2) +end +end +else +self.CargoObject:MessageToGroup("Cancelling Boarding... Get back on the ground!",5,CargoCarrier:GetGroup(),self:GetName()) +self:CancelBoarding(CargoCarrier,NearRadius,...) +self.CargoObject:SetCommand(self.CargoObject:CommandStopRoute(true)) +end +else +self:T("Something is wrong") +end +end +function CARGO_UNIT:onenterLoaded(From,Event,To,CargoCarrier) +self:T({From,Event,To,CargoCarrier}) +self.CargoCarrier=CargoCarrier +if self.CargoObject then +self.CargoObject:Destroy(false) +end +end +function CARGO_UNIT:GetTransportationMethod() +if self:IsLoaded()then +return"for unboarding" +else +if self:IsUnLoaded()then +return"for boarding" +else +if self:IsDeployed()then +return"delivered" +end +end +end +return"" +end +end +do +CARGO_SLINGLOAD={ +ClassName="CARGO_SLINGLOAD" +} +function CARGO_SLINGLOAD:New(CargoStatic,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius)) +self:T({Type,Name,NearRadius}) +self.CargoObject=CargoStatic +_EVENTDISPATCHER:CreateEventNewCargo(self) +self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead) +self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead) +self:SetEventPriority(4) +self.NearRadius=NearRadius or 25 +return self +end +function CARGO_SLINGLOAD:OnEventCargoDead(EventData) +local Destroyed=false +if self:IsDestroyed()or self:IsUnLoaded()then +if self.CargoObject:GetName()==EventData.IniUnitName then +if not self.NoDestroy then +Destroyed=true +end +end +end +if Destroyed then +self:I({"Cargo crate destroyed: "..self.CargoObject:GetName()}) +self:Destroyed() +end +end +function CARGO_SLINGLOAD:CanSlingload() +return true +end +function CARGO_SLINGLOAD:CanBoard() +return false +end +function CARGO_SLINGLOAD:CanUnboard() +return false +end +function CARGO_SLINGLOAD:CanLoad() +return false +end +function CARGO_SLINGLOAD:CanUnload() +return false +end +function CARGO_SLINGLOAD:IsInReportRadius(Coordinate) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=self.LoadRadius then +return true +end +end +return false +end +function CARGO_SLINGLOAD:IsInLoadRadius(Coordinate) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=self.NearRadius then +return true +end +end +return false +end +function CARGO_SLINGLOAD:GetCoordinate() +return self.CargoObject:GetCoordinate() +end +function CARGO_SLINGLOAD:IsAlive() +local Alive=true +if self:IsLoaded()then +Alive=Alive==true and self.CargoCarrier:IsAlive() +else +Alive=Alive==true and self.CargoObject:IsAlive() +end +return Alive +end +function CARGO_SLINGLOAD:RouteTo(Coordinate) +end +function CARGO_SLINGLOAD:IsNear(CargoCarrier,NearRadius) +return self:IsNear(CargoCarrier:GetCoordinate(),NearRadius) +end +function CARGO_SLINGLOAD:Respawn() +if self.CargoObject then +self.CargoObject:ReSpawn() +self:__Reset(-0.1) +end +end +function CARGO_SLINGLOAD:onafterReset() +if self.CargoObject then +self:SetDeployed(false) +self:SetStartState("UnLoaded") +self.CargoCarrier=nil +_EVENTDISPATCHER:CreateEventNewCargo(self) +end +end +function CARGO_SLINGLOAD:GetTransportationMethod() +if self:IsLoaded()then +return"for sling loading" +else +if self:IsUnLoaded()then +return"for sling loading" +else +if self:IsDeployed()then +return"delivered" +end +end +end +return"" +end +end +do +CARGO_CRATE={ +ClassName="CARGO_CRATE" +} +function CARGO_CRATE:New(CargoStatic,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius)) +self:T({Type,Name,NearRadius}) +self.CargoObject=CargoStatic +_EVENTDISPATCHER:CreateEventNewCargo(self) +self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead) +self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead) +self:SetEventPriority(4) +self.NearRadius=NearRadius or 25 +return self +end +function CARGO_CRATE:OnEventCargoDead(EventData) +local Destroyed=false +if self:IsDestroyed()or self:IsUnLoaded()or self:IsBoarding()then +if self.CargoObject:GetName()==EventData.IniUnitName then +if not self.NoDestroy then +Destroyed=true +end +end +else +if self:IsLoaded()then +local CarrierName=self.CargoCarrier:GetName() +if CarrierName==EventData.IniDCSUnitName then +MESSAGE:New("Cargo is lost from carrier "..CarrierName,15):ToAll() +Destroyed=true +self.CargoCarrier:ClearCargo() +end +end +end +if Destroyed then +self:I({"Cargo crate destroyed: "..self.CargoObject:GetName()}) +self:Destroyed() +end +end +function CARGO_CRATE:onenterUnLoaded(From,Event,To,ToPointVec2) +local Angle=180 +local Speed=10 +local Distance=10 +if From=="Loaded"then +local StartCoordinate=self.CargoCarrier:GetCoordinate() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployCoord=StartCoordinate:Translate(Distance,CargoDeployHeading) +ToPointVec2=ToPointVec2 or COORDINATE:NewFromVec2({x=CargoDeployCoord.x,y=CargoDeployCoord.z}) +if self.CargoObject then +self.CargoObject:ReSpawnAt(ToPointVec2,0) +self.CargoCarrier=nil +end +end +if self.OnUnLoadedCallBack then +self.OnUnLoadedCallBack(self,unpack(self.OnUnLoadedParameters)) +self.OnUnLoadedCallBack=nil +end +end +function CARGO_CRATE:onenterLoaded(From,Event,To,CargoCarrier) +self.CargoCarrier=CargoCarrier +if self.CargoObject then +self:T("Destroying") +self.NoDestroy=true +self.CargoObject:Destroy(false) +end +end +function CARGO_CRATE:CanBoard() +return false +end +function CARGO_CRATE:CanUnboard() +return false +end +function CARGO_CRATE:CanSlingload() +return false +end +function CARGO_CRATE:IsInReportRadius(Coordinate) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=self.LoadRadius then +return true +end +end +return false +end +function CARGO_CRATE:IsInLoadRadius(Coordinate) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=self.NearRadius then +return true +end +end +return false +end +function CARGO_CRATE:GetCoordinate() +return self.CargoObject:GetCoordinate() +end +function CARGO_CRATE:IsAlive() +local Alive=true +if self:IsLoaded()then +Alive=Alive==true and self.CargoCarrier:IsAlive() +else +Alive=Alive==true and self.CargoObject:IsAlive() +end +return Alive +end +function CARGO_CRATE:RouteTo(Coordinate) +self:T({Coordinate=Coordinate}) +end +function CARGO_CRATE:IsNear(CargoCarrier,NearRadius) +self:T({NearRadius=NearRadius}) +return self:IsNear(CargoCarrier:GetCoordinate(),NearRadius) +end +function CARGO_CRATE:Respawn() +self:T({"Respawning crate "..self:GetName()}) +if self.CargoObject then +self.CargoObject:ReSpawn() +self:__Reset(-0.1) +end +end +function CARGO_CRATE:onafterReset() +self:T({"Reset crate "..self:GetName()}) +if self.CargoObject then +self:SetDeployed(false) +self:SetStartState("UnLoaded") +self.CargoCarrier=nil +_EVENTDISPATCHER:CreateEventNewCargo(self) +end +end +function CARGO_CRATE:GetTransportationMethod() +if self:IsLoaded()then +return"for unloading" +else +if self:IsUnLoaded()then +return"for loading" +else +if self:IsDeployed()then +return"delivered" +end +end +end +return"" +end +end +do +CARGO_GROUP={ +ClassName="CARGO_GROUP", +} +function CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPORTABLE:New(Type,Name,0,LoadRadius,NearRadius)) +self:T({Type,Name,LoadRadius}) +self.CargoSet=SET_CARGO:New() +self.CargoGroup=CargoGroup +self.Grouped=true +self.CargoUnitTemplate={} +self.NearRadius=NearRadius +self:SetDeployed(false) +local WeightGroup=0 +local VolumeGroup=0 +self.CargoGroup:Destroy() +local GroupName=CargoGroup:GetName() +self.CargoName=Name +self.CargoTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName)) +self.CargoTemplate.lateActivation=false +self.GroupTemplate=UTILS.DeepCopy(self.CargoTemplate) +self.GroupTemplate.name=self.CargoName.."#CARGO" +self.GroupTemplate.groupId=nil +self.GroupTemplate.units={} +for UnitID,UnitTemplate in pairs(self.CargoTemplate.units)do +UnitTemplate.name=UnitTemplate.name.."#CARGO" +local CargoUnitName=UnitTemplate.name +self.CargoUnitTemplate[CargoUnitName]=UnitTemplate +self.GroupTemplate.units[#self.GroupTemplate.units+1]=self.CargoUnitTemplate[CargoUnitName] +self.GroupTemplate.units[#self.GroupTemplate.units].unitId=nil +local Unit=UNIT:Register(CargoUnitName) +end +self.CargoGroup=GROUP:NewTemplate(self.GroupTemplate,self.GroupTemplate.CoalitionID,self.GroupTemplate.CategoryID,self.GroupTemplate.CountryID) +self.CargoObject=_DATABASE:Spawn(self.GroupTemplate) +for CargoUnitID,CargoUnit in pairs(self.CargoObject:GetUnits())do +local CargoUnitName=CargoUnit:GetName() +local Cargo=CARGO_UNIT:New(CargoUnit,Type,CargoUnitName,LoadRadius,NearRadius) +self.CargoSet:Add(CargoUnitName,Cargo) +WeightGroup=WeightGroup+Cargo:GetWeight() +end +self:SetWeight(WeightGroup) +self:T({"Weight Cargo",WeightGroup}) +_EVENTDISPATCHER:CreateEventNewCargo(self) +self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead) +self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead) +self:SetEventPriority(4) +return self +end +function CARGO_GROUP:Respawn() +self:T({"Respawning"}) +for CargoID,CargoData in pairs(self.CargoSet:GetSet())do +local Cargo=CargoData +Cargo:Destroy() +Cargo:SetStartState("UnLoaded") +end +_DATABASE:Spawn(self.GroupTemplate) +for CargoUnitID,CargoUnit in pairs(self.CargoObject:GetUnits())do +local CargoUnitName=CargoUnit:GetName() +local Cargo=CARGO_UNIT:New(CargoUnit,self.Type,CargoUnitName,self.LoadRadius) +self.CargoSet:Add(CargoUnitName,Cargo) +end +self:SetDeployed(false) +self:SetStartState("UnLoaded") +end +function CARGO_GROUP:Ungroup() +if self.Grouped==true then +self.Grouped=false +self.CargoGroup:Destroy() +for CargoUnitName,CargoUnit in pairs(self.CargoSet:GetSet())do +local CargoUnit=CargoUnit +if CargoUnit:IsUnLoaded()then +local GroupTemplate=UTILS.DeepCopy(self.CargoTemplate) +GroupTemplate.name=self.CargoName.."#CARGO#"..CargoUnitName +GroupTemplate.groupId=nil +if CargoUnit:IsUnLoaded()then +GroupTemplate.units={} +GroupTemplate.units[1]=self.CargoUnitTemplate[CargoUnitName] +GroupTemplate.units[#GroupTemplate.units].unitId=nil +GroupTemplate.units[#GroupTemplate.units].x=CargoUnit:GetX() +GroupTemplate.units[#GroupTemplate.units].y=CargoUnit:GetY() +GroupTemplate.units[#GroupTemplate.units].heading=CargoUnit:GetHeading() +end +local CargoGroup=GROUP:NewTemplate(GroupTemplate,GroupTemplate.CoalitionID,GroupTemplate.CategoryID,GroupTemplate.CountryID) +_DATABASE:Spawn(GroupTemplate) +end +end +self.CargoObject=nil +end +end +function CARGO_GROUP:Regroup() +self:T("Regroup") +if self.Grouped==false then +self.Grouped=true +local GroupTemplate=UTILS.DeepCopy(self.CargoTemplate) +GroupTemplate.name=self.CargoName.."#CARGO" +GroupTemplate.groupId=nil +GroupTemplate.units={} +for CargoUnitName,CargoUnit in pairs(self.CargoSet:GetSet())do +local CargoUnit=CargoUnit +self:T({CargoUnit:GetName(),UnLoaded=CargoUnit:IsUnLoaded()}) +if CargoUnit:IsUnLoaded()then +CargoUnit.CargoObject:Destroy() +GroupTemplate.units[#GroupTemplate.units+1]=self.CargoUnitTemplate[CargoUnitName] +GroupTemplate.units[#GroupTemplate.units].unitId=nil +GroupTemplate.units[#GroupTemplate.units].x=CargoUnit:GetX() +GroupTemplate.units[#GroupTemplate.units].y=CargoUnit:GetY() +GroupTemplate.units[#GroupTemplate.units].heading=CargoUnit:GetHeading() +end +end +self.CargoGroup=GROUP:NewTemplate(GroupTemplate,GroupTemplate.CoalitionID,GroupTemplate.CategoryID,GroupTemplate.CountryID) +self:T({"Regroup",GroupTemplate}) +self.CargoObject=_DATABASE:Spawn(GroupTemplate) +end +end +function CARGO_GROUP:OnEventCargoDead(EventData) +self:T(EventData) +local Destroyed=false +if self:IsDestroyed()or self:IsUnLoaded()or self:IsBoarding()or self:IsUnboarding()then +Destroyed=true +for CargoID,CargoData in pairs(self.CargoSet:GetSet())do +local Cargo=CargoData +if Cargo:IsAlive()then +Destroyed=false +else +Cargo:Destroyed() +end +end +else +local CarrierName=self.CargoCarrier:GetName() +if CarrierName==EventData.IniDCSUnitName then +MESSAGE:New("Cargo is lost from carrier "..CarrierName,15):ToAll() +Destroyed=true +self.CargoCarrier:ClearCargo() +end +end +if Destroyed then +self:Destroyed() +self:T({"Cargo group destroyed"}) +end +end +function CARGO_GROUP:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...) +self:T({CargoCarrier.UnitName,From,Event,To,NearRadius=NearRadius}) +NearRadius=NearRadius or self.NearRadius +self.CargoSet:ForEach( +function(Cargo,...) +self:T({"Board Unit",Cargo:GetName(),Cargo:IsDestroyed(),Cargo.CargoObject:IsAlive()}) +local CargoGroup=Cargo.CargoObject +CargoGroup:OptionAlarmStateGreen() +Cargo:__Board(1,CargoCarrier,NearRadius,...) +end,... +) +self:__Boarding(-1,CargoCarrier,NearRadius,...) +end +function CARGO_GROUP:onafterLoad(From,Event,To,CargoCarrier,...) +if From=="UnLoaded"then +for CargoID,Cargo in pairs(self.CargoSet:GetSet())do +if not Cargo:IsDestroyed()then +Cargo:Load(CargoCarrier) +end +end +end +self.CargoCarrier=CargoCarrier +self.CargoCarrier:AddCargo(self) +end +function CARGO_GROUP:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...) +local Boarded=true +local Cancelled=false +local Dead=true +self.CargoSet:Flush() +for CargoID,Cargo in pairs(self.CargoSet:GetSet())do +if not Cargo:is("Loaded") +and(not Cargo:is("Destroyed"))then +Boarded=false +end +if Cargo:is("UnLoaded")then +Cancelled=true +end +if not Cargo:is("Destroyed")then +Dead=false +end +end +if not Dead then +if not Cancelled then +if not Boarded then +self:__Boarding(-5,CargoCarrier,NearRadius,...) +else +self:T("Group Cargo is loaded") +self:__Load(1,CargoCarrier,...) +end +else +self:__CancelBoarding(1,CargoCarrier,NearRadius,...) +end +else +self:__Destroyed(1,CargoCarrier,NearRadius,...) +end +end +function CARGO_GROUP:onafterUnBoard(From,Event,To,ToPointVec2,NearRadius,...) +self:T({From,Event,To,ToPointVec2,NearRadius}) +NearRadius=NearRadius or 25 +local Timer=1 +if From=="Loaded"then +if self.CargoObject then +self.CargoObject:Destroy() +end +self.CargoSet:ForEach( +function(Cargo,NearRadius) +if not Cargo:IsDestroyed()then +local ToVec=nil +if ToPointVec2==nil then +ToVec=self.CargoCarrier:GetPointVec2():GetRandomPointVec2InRadius(2*NearRadius,NearRadius) +else +ToVec=ToPointVec2 +end +Cargo:__UnBoard(Timer,ToVec,NearRadius) +Timer=Timer+1 +end +end,{NearRadius} +) +self:__UnBoarding(1,ToPointVec2,NearRadius,...) +end +end +function CARGO_GROUP:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius,...) +local Angle=180 +local Speed=10 +local Distance=5 +if From=="UnBoarding"then +local UnBoarded=true +for CargoID,Cargo in pairs(self.CargoSet:GetSet())do +self:T({Cargo:GetName(),Cargo.current}) +if not Cargo:is("UnLoaded")and not Cargo:IsDestroyed()then +UnBoarded=false +end +end +if UnBoarded then +self:__UnLoad(1,ToPointVec2,...) +else +self:__UnBoarding(1,ToPointVec2,NearRadius,...) +end +return false +end +end +function CARGO_GROUP:onafterUnLoad(From,Event,To,ToPointVec2,...) +if From=="Loaded"then +self.CargoSet:ForEach( +function(Cargo) +local RandomVec2=nil +if ToPointVec2 then +RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20,10) +end +Cargo:UnBoard(RandomVec2) +end +) +end +self.CargoCarrier:RemoveCargo(self) +self.CargoCarrier=nil +end +function CARGO_GROUP:GetCoordinate() +local Cargo=self:GetFirstAlive() +if Cargo then +return Cargo.CargoObject:GetCoordinate() +end +return nil +end +function CARGO:GetX() +local Cargo=self:GetFirstAlive() +if Cargo then +return Cargo:GetCoordinate().x +end +return nil +end +function CARGO:GetY() +local Cargo=self:GetFirstAlive() +if Cargo then +return Cargo:GetCoordinate().z +end +return nil +end +function CARGO_GROUP:IsAlive() +local Cargo=self:GetFirstAlive() +return Cargo~=nil +end +function CARGO_GROUP:GetFirstAlive() +local CargoFirstAlive=nil +for _,Cargo in pairs(self.CargoSet:GetSet())do +if not Cargo:IsDestroyed()then +CargoFirstAlive=Cargo +break +end +end +return CargoFirstAlive +end +function CARGO_GROUP:GetCount() +return self.CargoSet:Count() +end +function CARGO_GROUP:GetGroup(Cargo) +local Cargo=Cargo or self:GetFirstAlive() +return Cargo.CargoObject:GetGroup() +end +function CARGO_GROUP:RouteTo(Coordinate) +self.CargoSet:ForEach( +function(Cargo) +Cargo.CargoObject:RouteGroundTo(Coordinate,10,"vee",0) +end +) +end +function CARGO_GROUP:IsNear(CargoCarrier,NearRadius) +self:T({NearRadius=NearRadius}) +for _,Cargo in pairs(self.CargoSet:GetSet())do +local Cargo=Cargo +if Cargo:IsAlive()then +if Cargo:IsNear(CargoCarrier:GetCoordinate(),NearRadius)then +self:T("Near") +return true +end +end +end +return nil +end +function CARGO_GROUP:IsInLoadRadius(Coordinate) +local Cargo=self:GetFirstAlive() +if Cargo then +local Distance=0 +local CargoCoordinate +if Cargo:IsLoaded()then +CargoCoordinate=Cargo.CargoCarrier:GetCoordinate() +else +CargoCoordinate=Cargo.CargoObject:GetCoordinate() +end +if CargoCoordinate then +Distance=Coordinate:Get2DDistance(CargoCoordinate) +else +return false +end +self:T({Distance=Distance,LoadRadius=self.LoadRadius}) +if Distance<=self.LoadRadius then +return true +else +return false +end +end +return nil +end +function CARGO_GROUP:IsInReportRadius(Coordinate) +local Cargo=self:GetFirstAlive() +if Cargo then +self:T({Cargo}) +local Distance=0 +if Cargo:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(Cargo.CargoObject:GetCoordinate()) +if Distance<=self.LoadRadius then +return true +end +end +end +return nil +end +function CARGO_GROUP:Flare(FlareColor) +local Cargo=self.CargoSet:GetFirst() +if Cargo then +Cargo:Flare(FlareColor) +end +end +function CARGO_GROUP:Smoke(SmokeColor,Radius) +local Cargo=self.CargoSet:GetFirst() +if Cargo then +Cargo:Smoke(SmokeColor,Radius) +end +end +function CARGO_GROUP:IsInZone(Zone) +local Cargo=self.CargoSet:GetFirst() +if Cargo then +return Cargo:IsInZone(Zone) +end +return nil +end +function CARGO_GROUP:GetTransportationMethod() +if self:IsLoaded()then +return"for unboarding" +else +if self:IsUnLoaded()then +return"for boarding" +else +if self:IsDeployed()then +return"delivered" +end +end +end +return"" +end +end +SCORING={ +ClassName="SCORING", +ClassID=0, +Players={}, +AutoSave=true, +version="1.18.4" +} +local _SCORINGCoalition={ +[1]="Red", +[2]="Blue", +} +local _SCORINGCategory={ +[Unit.Category.AIRPLANE]="Plane", +[Unit.Category.HELICOPTER]="Helicopter", +[Unit.Category.GROUND_UNIT]="Vehicle", +[Unit.Category.SHIP]="Ship", +[Unit.Category.STRUCTURE]="Structure", +} +function SCORING:New(GameName,SavePath,AutoSave) +local self=BASE:Inherit(self,BASE:New()) +if GameName then +self.GameName=GameName +else +error("A game name must be given to register the scoring results") +end +self.ScoringObjects={} +self.ScoringZones={} +self:SetMessagesToAll() +self:SetMessagesHit(false) +self:SetMessagesDestroy(true) +self:SetMessagesScore(true) +self:SetMessagesZone(true) +self:SetScaleDestroyScore(10) +self:SetScaleDestroyPenalty(30) +self:SetScoreIncrementOnHit(0) +self:SetFratricide(self.ScaleDestroyPenalty*3) +self.penaltyonfratricide=true +self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) +self.penaltyoncoalitionchange=true +self:SetDisplayMessagePrefix() +self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) +self:HandleEvent(EVENTS.Hit,self._EventOnHit) +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.PlayerLeaveUnit) +self.ScoringPlayerScan=BASE:ScheduleOnce(1,function() +for PlayerName,PlayerUnit in pairs(_DATABASE:GetPlayerUnits())do +self:_AddPlayerFromUnit(PlayerUnit) +self:SetScoringMenu(PlayerUnit:GetGroup()) +end +end) +self.AutoSavePath=SavePath +self.AutoSave=AutoSave or true +if self.AutoSave==true then +self:OpenCSV(GameName) +end +return self +end +function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) +self.DisplayMessagePrefix=DisplayMessagePrefix or"" +return self +end +function SCORING:SetScaleDestroyScore(Scale) +self.ScaleDestroyScore=Scale +return self +end +function SCORING:SetScaleDestroyPenalty(Scale) +self.ScaleDestroyPenalty=Scale +return self +end +function SCORING:AddUnitScore(ScoreUnit,Score) +local UnitName=ScoreUnit:GetName() +self.ScoringObjects[UnitName]=Score +return self +end +function SCORING:RemoveUnitScore(ScoreUnit) +local UnitName=ScoreUnit:GetName() +self.ScoringObjects[UnitName]=nil +return self +end +function SCORING:AddStaticScore(ScoreStatic,Score) +local StaticName=ScoreStatic:GetName() +self.ScoringObjects[StaticName]=Score +return self +end +function SCORING:RemoveStaticScore(ScoreStatic) +local StaticName=ScoreStatic:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreGroup(ScoreGroup,Score) +local ScoreUnits=ScoreGroup:GetUnits() +for ScoreUnitID,ScoreUnit in pairs(ScoreUnits)do +local UnitName=ScoreUnit:GetName() +self.ScoringObjects[UnitName]=Score +end +return self +end +function SCORING:AddScoreSetGroup(Set,Score) +local set=Set:GetSetObjects() +for _,_group in pairs(set)do +if _group and _group:IsAlive()then +self:AddScoreGroup(_group,Score) +end +end +local function AddScore(group) +self:AddScoreGroup(group,Score) +end +function Set:OnAfterAdded(From,Event,To,ObjectName,Object) +AddScore(Object) +end +return self +end +function SCORING:AddZoneScore(ScoreZone,Score) +local ZoneName=ScoreZone:GetName() +self.ScoringZones[ZoneName]={} +self.ScoringZones[ZoneName].ScoreZone=ScoreZone +self.ScoringZones[ZoneName].Score=Score +return self +end +function SCORING:RemoveZoneScore(ScoreZone) +local ZoneName=ScoreZone:GetName() +self.ScoringZones[ZoneName]=nil +return self +end +function SCORING:SetMessagesHit(OnOff) +self.MessagesHit=OnOff +return self +end +function SCORING:SetScoreIncrementOnHit(score) +self.ScoreIncrementOnHit=score +return self +end +function SCORING:IfMessagesHit() +return self.MessagesHit +end +function SCORING:SetMessagesDestroy(OnOff) +self.MessagesDestroy=OnOff +return self +end +function SCORING:IfMessagesDestroy() +return self.MessagesDestroy +end +function SCORING:SetMessagesScore(OnOff) +self.MessagesScore=OnOff +return self +end +function SCORING:IfMessagesScore() +return self.MessagesScore +end +function SCORING:SetMessagesZone(OnOff) +self.MessagesZone=OnOff +return self +end +function SCORING:IfMessagesZone() +return self.MessagesZone +end +function SCORING:SetMessagesToAll() +self.MessagesAudience=1 +return self +end +function SCORING:IfMessagesToAll() +return self.MessagesAudience==1 +end +function SCORING:SetMessagesToCoalition() +self.MessagesAudience=2 +return self +end +function SCORING:IfMessagesToCoalition() +return self.MessagesAudience==2 +end +function SCORING:SetFratricide(Fratricide) +self.Fratricide=Fratricide +return self +end +function SCORING:SwitchFratricide(OnOff) +self.penaltyonfratricide=OnOff +return self +end +function SCORING:SwitchTreason(OnOff) +self.penaltyoncoalitionchange=OnOff +return self +end +function SCORING:SetCoalitionChangePenalty(CoalitionChangePenalty) +self.CoalitionChangePenalty=CoalitionChangePenalty +return self +end +function SCORING:SetScoringMenu(ScoringGroup) +local Menu=MENU_GROUP:New(ScoringGroup,'Scoring and Statistics') +local ReportGroupSummary=MENU_GROUP_COMMAND:New(ScoringGroup,'Summary report players in group',Menu,SCORING.ReportScoreGroupSummary,self,ScoringGroup) +local ReportGroupDetailed=MENU_GROUP_COMMAND:New(ScoringGroup,'Detailed report players in group',Menu,SCORING.ReportScoreGroupDetailed,self,ScoringGroup) +local ReportToAllSummary=MENU_GROUP_COMMAND:New(ScoringGroup,'Summary report all players',Menu,SCORING.ReportScoreAllSummary,self,ScoringGroup) +self:SetState(ScoringGroup,"ScoringMenu",Menu) +return self +end +function SCORING:_AddPlayerFromUnit(UnitData) +self:F(UnitData) +if UnitData:IsAlive()then +local UnitName=UnitData:GetName() +local PlayerName=UnitData:GetPlayerName() +local UnitDesc=UnitData:GetDesc() +local UnitCategory=UnitDesc.category +local UnitCoalition=UnitData:GetCoalition() +local UnitTypeName=UnitData:GetTypeName() +local UnitThreatLevel,UnitThreatType=UnitData:GetThreatLevel() +self:T({PlayerName,UnitName,UnitCategory,UnitCoalition,UnitTypeName}) +if self.Players[PlayerName]==nil then +self.Players[PlayerName]={} +self.Players[PlayerName].Hit={} +self.Players[PlayerName].Destroy={} +self.Players[PlayerName].Goals={} +self.Players[PlayerName].Mission={} +self.Players[PlayerName].HitPlayers={} +self.Players[PlayerName].Score=0 +self.Players[PlayerName].Penalty=0 +self.Players[PlayerName].PenaltyCoalition=0 +self.Players[PlayerName].PenaltyWarning=0 +end +if not self.Players[PlayerName].UnitCoalition then +self.Players[PlayerName].UnitCoalition=UnitCoalition +else +if self.Players[PlayerName].UnitCoalition~=UnitCoalition and self.penaltyoncoalitionchange then +self.Players[PlayerName].Penalty=self.Players[PlayerName].Penalty+self.CoalitionChangePenalty or 50 +self.Players[PlayerName].PenaltyCoalition=self.Players[PlayerName].PenaltyCoalition+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' changed coalition from ".._SCORINGCoalition[self.Players[PlayerName].UnitCoalition].." to ".._SCORINGCoalition[UnitCoalition].. +"(changed "..self.Players[PlayerName].PenaltyCoalition.." times the coalition). "..self.CoalitionChangePenalty.." penalty points added.", +MESSAGE.Type.Information +):ToAll() +self:ScoreCSV(PlayerName,"","COALITION_PENALTY",1,-1*self.CoalitionChangePenalty,self.Players[PlayerName].UnitName,_SCORINGCoalition[self.Players[PlayerName].UnitCoalition],_SCORINGCategory[self.Players[PlayerName].UnitCategory],self.Players[PlayerName].UnitType, +UnitName,_SCORINGCoalition[UnitCoalition],_SCORINGCategory[UnitCategory],UnitData:GetTypeName()) +end +end +self.Players[PlayerName].UnitName=UnitName +self.Players[PlayerName].UnitCoalition=UnitCoalition +self.Players[PlayerName].UnitCategory=UnitCategory +self.Players[PlayerName].UnitType=UnitTypeName +self.Players[PlayerName].UNIT=UnitData +self.Players[PlayerName].ThreatLevel=UnitThreatLevel +self.Players[PlayerName].ThreatType=UnitThreatType +if self.Players[PlayerName].Penalty>self.Fratricide*0.50 and self.penaltyonfratricide then +if self.Players[PlayerName].PenaltyWarning<1 then +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than "..self.Fratricide..", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: "..self.Players[PlayerName].Penalty, +MESSAGE.Type.Information) +:ToAll() +self.Players[PlayerName].PenaltyWarning=self.Players[PlayerName].PenaltyWarning+1 +end +end +if self.Players[PlayerName].Penalty>self.Fratricide and self.penaltyonfratricide then +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!", +MESSAGE.Type.Information) +:ToAll() +UnitData:GetGroup():Destroy() +end +end +end +function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) +self:F({PlayerName,PlayerName,GoalTag,Text,Score}) +if PlayerName then +local PlayerData=self.Players[PlayerName] +PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} +PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score +PlayerData.Score=PlayerData.Score+Score +if Text then +MESSAGE:NewType(self.DisplayMessagePrefix..Text, +MESSAGE.Type.Information) +:ToAll() +end +self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,nil) +end +end +function SCORING:AddGoalScore(PlayerUnit,GoalTag,Text,Score) +local PlayerName=PlayerUnit:GetPlayerName() +self:T2({PlayerUnit.UnitName,PlayerName,GoalTag,Text,Score}) +if PlayerName then +local PlayerData=self.Players[PlayerName] +PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} +PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score +PlayerData.Score=PlayerData.Score+Score +if Text then +MESSAGE:NewType(self.DisplayMessagePrefix..Text, +MESSAGE.Type.Information) +:ToAll() +end +self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,PlayerUnit:GetName()) +end +end +function SCORING:_AddMissionTaskScore(Mission,PlayerUnit,Text,Score) +local PlayerName=PlayerUnit:GetPlayerName() +local MissionName=Mission:GetName() +self:F({Mission:GetName(),PlayerUnit.UnitName,PlayerName,Text,Score}) +if PlayerName then +local PlayerData=self.Players[PlayerName] +if not PlayerData.Mission[MissionName]then +PlayerData.Mission[MissionName]={} +PlayerData.Mission[MissionName].ScoreTask=0 +PlayerData.Mission[MissionName].ScoreMission=0 +end +self:T(PlayerName) +self:T(PlayerData.Mission[MissionName]) +PlayerData.Score=self.Players[PlayerName].Score+Score +PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score +if Text then +MESSAGE:NewType(self.DisplayMessagePrefix..Mission:GetText().." : "..Text.." Score: "..Score, +MESSAGE.Type.Information) +:ToAll() +end +self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score,PlayerUnit:GetName()) +end +end +function SCORING:_AddMissionGoalScore(Mission,PlayerName,Text,Score) +local MissionName=Mission:GetName() +self:F({Mission:GetName(),PlayerName,Text,Score}) +if PlayerName then +local PlayerData=self.Players[PlayerName] +if not PlayerData.Mission[MissionName]then +PlayerData.Mission[MissionName]={} +PlayerData.Mission[MissionName].ScoreTask=0 +PlayerData.Mission[MissionName].ScoreMission=0 +end +self:T(PlayerName) +self:T(PlayerData.Mission[MissionName]) +PlayerData.Score=self.Players[PlayerName].Score+Score +PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score +if Text then +MESSAGE:NewType(string.format("%s%s: %s! Player %s receives %d score!",self.DisplayMessagePrefix,Mission:GetText(),Text,PlayerName,Score),MESSAGE.Type.Information):ToAll() +end +self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score) +end +end +function SCORING:_AddMissionScore(Mission,Text,Score) +local MissionName=Mission:GetName() +self:F({Mission,Text,Score}) +self:F(self.Players) +for PlayerName,PlayerData in pairs(self.Players)do +self:F(PlayerData) +if PlayerData.Mission[MissionName]then +PlayerData.Score=PlayerData.Score+Score +PlayerData.Mission[MissionName].ScoreMission=PlayerData.Mission[MissionName].ScoreMission+Score +if Text then +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' has "..Text.." in "..Mission:GetText()..". "..Score.." mission score!", +MESSAGE.Type.Information) +:ToAll() +end +self:ScoreCSV(PlayerName,"","MISSION_"..MissionName:gsub(' ','_'),1,Score) +end +end +end +function SCORING:OnEventBirth(Event) +if Event.IniUnit then +Event.IniUnit.ThreatLevel,Event.IniUnit.ThreatType=Event.IniUnit:GetThreatLevel() +if Event.IniObjectCategory==1 then +local PlayerName=Event.IniUnit:GetPlayerName() +Event.IniUnit.BirthTime=timer.getTime() +if PlayerName then +self:_AddPlayerFromUnit(Event.IniUnit) +self.Players[PlayerName].PlayerKills=0 +self:SetScoringMenu(Event.IniGroup) +end +end +end +end +function SCORING:OnEventPlayerLeaveUnit(Event) +if Event.IniUnit then +local Menu=self:GetState(Event.IniUnit:GetGroup(),"ScoringMenu") +if Menu then +end +end +end +function SCORING:_EventOnHit(Event) +self:F({Event}) +local InitUnit=nil +local InitUNIT=nil +local InitUnitName="" +local InitGroup=nil +local InitGroupName="" +local InitPlayerName=nil +local InitCoalition=nil +local InitCategory=nil +local InitType=nil +local InitUnitCoalition=nil +local InitUnitCategory=nil +local InitUnitType=nil +local TargetUnit=nil +local TargetUNIT=nil +local TargetUnitName="" +local TargetGroup=nil +local TargetGroupName="" +local TargetPlayerName=nil +local TargetCoalition=nil +local TargetCategory=nil +local TargetType=nil +local TargetUnitCoalition=nil +local TargetUnitCategory=nil +local TargetUnitType=nil +local TargetIsScenery=false +if Event.IniDCSUnit then +InitUnit=Event.IniDCSUnit +InitUNIT=Event.IniUnit +InitUnitName=Event.IniDCSUnitName +InitGroup=Event.IniDCSGroup +InitGroupName=Event.IniDCSGroupName +InitPlayerName=Event.IniPlayerName +InitCoalition=Event.IniCoalition +InitCategory=Event.IniCategory +InitType=Event.IniTypeName +InitUnitCoalition=_SCORINGCoalition[InitCoalition] +InitUnitCategory=_SCORINGCategory[InitCategory] +InitUnitType=InitType +self:T({InitUnitName,InitGroupName,InitPlayerName,InitCoalition,InitCategory,InitType,InitUnitCoalition,InitUnitCategory,InitUnitType}) +end +if Event.TgtDCSUnit then +TargetUnit=Event.TgtDCSUnit +TargetUNIT=Event.TgtUnit +TargetUnitName=Event.TgtDCSUnitName +TargetGroup=Event.TgtDCSGroup +TargetGroupName=Event.TgtDCSGroupName +TargetPlayerName=Event.TgtPlayerName +TargetCoalition=Event.TgtCoalition +TargetCategory=Event.TgtCategory +TargetType=Event.TgtTypeName +if(not TargetCategory)and TargetUNIT~=nil and TargetUnit:IsInstanceOf("SCENERY")then +TargetCategory=Unit.Category.STRUCTURE +TargetIsScenery=true +end +TargetUnitCoalition=_SCORINGCoalition[TargetCoalition] +TargetUnitCategory=_SCORINGCategory[TargetCategory] +TargetUnitType=TargetType +self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType,TargetUnitCoalition,TargetUnitCategory,TargetUnitType}) +end +if InitPlayerName~=nil then +self:_AddPlayerFromUnit(InitUNIT) +if self.Players[InitPlayerName]then +if TargetPlayerName~=nil then +self:_AddPlayerFromUnit(TargetUNIT) +end +self:T("Hitting Something") +if TargetCategory then +local Player=self.Players[InitPlayerName] +Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} +Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} +local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] +PlayerHit.Score=PlayerHit.Score or 0 +PlayerHit.Penalty=PlayerHit.Penalty or 0 +PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0 +PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0 +PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0 +PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT +if PlayerHit.UNIT.ThreatType==nil then +PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() +if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then +PlayerHit.ThreatLevel=1 +PlayerHit.ThreatType="Unknown" +end +else +PlayerHit.ThreatLevel=PlayerHit.UNIT.ThreatLevel +PlayerHit.ThreatType=PlayerHit.UNIT.ThreatType +end +if timer.getTime()-PlayerHit.TimeStamp>1 then +PlayerHit.TimeStamp=timer.getTime() +if TargetPlayerName~=nil then +Player.HitPlayers[TargetPlayerName]=true +end +local Score=0 +if InitCoalition then +if InitCoalition==TargetCoalition then +local Penalty=10 +Player.Penalty=Player.Penalty+Penalty +PlayerHit.Penalty=PlayerHit.Penalty+Penalty +PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1 +if TargetPlayerName~=nil then +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. ".. +"Penalty: -"..Penalty..". Score Total:"..Player.Score-Player.Penalty, +MESSAGE.Type.Update) +:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +else +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly target "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. ".. +"Penalty: -"..Penalty..". Score Total:"..Player.Score-Player.Penalty, +MESSAGE.Type.Update) +:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +end +self:ScoreCSV(InitPlayerName,TargetPlayerName,"HIT_PENALTY",1,-10,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +if TargetPlayerName~=nil then +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.ScoreHit.." times. ".. +"Score: "..PlayerHit.Score..". Score Total:"..Player.Score-Player.Penalty, +MESSAGE.Type.Update) +:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +elseif TargetIsScenery~=true then +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy target "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.ScoreHit.." times. ".. +"Score: "..PlayerHit.Score..". Score Total:"..Player.Score-Player.Penalty, +MESSAGE.Type.Update) +:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +elseif TargetIsScenery==true then +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit scenery object.".." Score: "..PlayerHit.Score..". Score Total:"..Player.Score-Player.Penalty, +MESSAGE.Type.Update) +:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +end +self:ScoreCSV(InitPlayerName,TargetPlayerName,"HIT_SCORE",1,1,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +end +else +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit nothing special.", +MESSAGE.Type.Update) +:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(InitPlayerName,"","HIT_SCORE",1,0,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType) +end +end +end +end +elseif InitPlayerName==nil then +end +if Event.WeaponPlayerName~=nil then +self:_AddPlayerFromUnit(Event.WeaponUNIT) +if self.Players[Event.WeaponPlayerName]then +if TargetPlayerName~=nil then +self:_AddPlayerFromUnit(TargetUNIT) +end +self:T("Hitting Scenery") +if TargetCategory then +local Player=self.Players[Event.WeaponPlayerName] +Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} +Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} +local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] +PlayerHit.Score=PlayerHit.Score or 0 +PlayerHit.Penalty=PlayerHit.Penalty or 0 +PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0 +PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0 +PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0 +PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT +if PlayerHit.UNIT.ThreatType==nil then +PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() +if PlayerHit.ThreatType==nil then +PlayerHit.ThreatLevel=1 +PlayerHit.ThreatType="Unknown" +end +else +PlayerHit.ThreatLevel=PlayerHit.UNIT.ThreatLevel +PlayerHit.ThreatType=PlayerHit.UNIT.ThreatType +end +if timer.getTime()-PlayerHit.TimeStamp>1 then +PlayerHit.TimeStamp=timer.getTime() +local Score=0 +if InitCoalition then +if InitCoalition==TargetCoalition then +local Penalty=10 +Player.Penalty=Player.Penalty+Penalty +PlayerHit.Penalty=PlayerHit.Penalty+Penalty +PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1*self.ScaleDestroyPenalty +MESSAGE +:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit friendly target ".. +TargetUnitCategory.." ( "..TargetType.." ) ".. +"Penalty: -"..Penalty.." = "..Player.Score-Player.Penalty, +MESSAGE.Type.Update +) +:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_PENALTY",1,-10,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit enemy target "..TargetUnitCategory.." ( "..TargetType.." ) ".. +"Score: "..PlayerHit.Score..". Score Total:"..Player.Score-Player.Penalty, +MESSAGE.Type.Update) +:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +end +else +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit scenery object.", +MESSAGE.Type.Update) +:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) +end +end +end +end +end +end +function SCORING:_EventOnDeadOrCrash(Event) +self:F({Event}) +local TargetUnit=nil +local TargetGroup=nil +local TargetUnitName="" +local TargetGroupName="" +local TargetPlayerName="" +local TargetCoalition=nil +local TargetCategory=nil +local TargetType=nil +local TargetUnitCoalition=nil +local TargetUnitCategory=nil +local TargetUnitType=nil +if Event.IniDCSUnit then +TargetUnit=Event.IniUnit +TargetUnitName=Event.IniDCSUnitName +TargetGroup=Event.IniDCSGroup +TargetGroupName=Event.IniDCSGroupName +TargetPlayerName=Event.IniPlayerName +TargetCoalition=Event.IniCoalition +TargetCategory=Event.IniCategory +TargetType=Event.IniTypeName +TargetUnitCoalition=_SCORINGCoalition[TargetCoalition] +TargetUnitCategory=_SCORINGCategory[TargetCategory] +TargetUnitType=TargetType +self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType}) +end +for PlayerName,Player in pairs(self.Players)do +if Player then +self:T("Something got destroyed") +local InitUnitName=Player.UnitName +local InitUnitType=Player.UnitType +local InitCoalition=Player.UnitCoalition +local InitCategory=Player.UnitCategory +local InitUnitCoalition=_SCORINGCoalition[InitCoalition] +local InitUnitCategory=_SCORINGCategory[InitCategory] +self:T({InitUnitName,InitUnitType,InitUnitCoalition,InitCoalition,InitUnitCategory,InitCategory}) +local Destroyed=false +if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and(TargetUnit.BirthTime==nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp>TargetUnit.BirthTime)then +local TargetThreatLevel=Player.Hit[TargetCategory][TargetUnitName].ThreatLevel +local TargetThreatType=Player.Hit[TargetCategory][TargetUnitName].ThreatType +Player.Destroy[TargetCategory]=Player.Destroy[TargetCategory]or{} +Player.Destroy[TargetCategory][TargetType]=Player.Destroy[TargetCategory][TargetType]or{} +local TargetDestroy=Player.Destroy[TargetCategory][TargetType] +TargetDestroy.Score=TargetDestroy.Score or 0 +TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy or 0 +TargetDestroy.Penalty=TargetDestroy.Penalty or 0 +TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy or 0 +if TargetCoalition then +if InitCoalition==TargetCoalition then +local ThreatLevelTarget=TargetThreatLevel +local ThreatTypeTarget=TargetThreatType +local ThreatLevelPlayer=Player.ThreatLevel/10+1 +local ThreatPenalty=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyPenalty/10) +self:F({ThreatLevel=ThreatPenalty,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer}) +Player.Penalty=Player.Penalty+ThreatPenalty +TargetDestroy.Penalty=TargetDestroy.Penalty+ThreatPenalty +TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy+1 +if Player.HitPlayers[TargetPlayerName]then +self:OnKillPvP(PlayerName,TargetPlayerName,true) +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. +"Penalty: -"..ThreatPenalty.." = "..Player.Score-Player.Penalty, +MESSAGE.Type.Information) +:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) +else +self:OnKillPvE(PlayerName,TargetUnitName,true,TargetThreatLevel,Player.ThreatLevel,ThreatPenalty) +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly target "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. +"Penalty: -"..ThreatPenalty.." = "..Player.Score-Player.Penalty, +MESSAGE.Type.Information) +:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) +end +self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_PENALTY",1,ThreatPenalty,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +Destroyed=true +else +local ThreatLevelTarget=TargetThreatLevel +local ThreatTypeTarget=TargetThreatType +local ThreatLevelPlayer=Player.ThreatLevel/10+1 +local ThreatScore=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyScore/10) +self:F({ThreatLevel=ThreatScore,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer}) +Player.Score=Player.Score+ThreatScore +TargetDestroy.Score=TargetDestroy.Score+ThreatScore +TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy+1 +if Player.HitPlayers[TargetPlayerName]then +if Player.PlayerKills~=nil then +Player.PlayerKills=Player.PlayerKills+1 +else +Player.PlayerKills=1 +end +self:OnKillPvP(PlayerName,TargetPlayerName,false,TargetThreatLevel,Player.ThreatLevel,ThreatScore) +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. +"Score: +"..ThreatScore.." = "..Player.Score-Player.Penalty, +MESSAGE.Type.Information) +:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) +else +self:OnKillPvE(PlayerName,TargetUnitName,false,TargetThreatLevel,Player.ThreatLevel,ThreatScore) +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. +"Score: +"..ThreatScore.." = "..Player.Score-Player.Penalty, +MESSAGE.Type.Information) +:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) +end +self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,ThreatScore,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +Destroyed=true +local UnitName=TargetUnit:GetName() +local Score=self.ScoringObjects[UnitName] +if Score then +Player.Score=Player.Score+Score +TargetDestroy.Score=TargetDestroy.Score+Score +MESSAGE:NewType(self.DisplayMessagePrefix.."Special target '"..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".." destroyed! ".. +"Player '"..PlayerName.."' receives an extra "..Score.." points! Total: "..Player.Score-Player.Penalty, +MESSAGE.Type.Information) +:ToAllIf(self:IfMessagesScore()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesScore()and self:IfMessagesToCoalition()) +self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +Destroyed=true +end +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then +Player.Score=Player.Score+Score +TargetDestroy.Score=TargetDestroy.Score+Score +MESSAGE:NewType(self.DisplayMessagePrefix.."Target destroyed in zone '"..ScoreZone:GetName().."'.".. +"Player '"..PlayerName.."' receives an extra "..Score.." points! ".."Total: "..Player.Score-Player.Penalty, +MESSAGE.Type.Information) +:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition()) +self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +Destroyed=true +end +end +end +else +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then +Player.Score=Player.Score+Score +TargetDestroy.Score=TargetDestroy.Score+Score +MESSAGE:NewType(self.DisplayMessagePrefix.."Scenery destroyed in zone '"..ScoreZone:GetName().."'.".. +"Player '"..PlayerName.."' receives an extra "..Score.." points! ".."Total: "..Player.Score-Player.Penalty, +MESSAGE.Type.Information) +:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition()) +self:ScoreCSV(PlayerName,"","DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType) +Destroyed=true +end +end +end +if Destroyed then +Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 +end +end +end +end +end +function SCORING:ReportDetailedPlayerHits(PlayerName) +local ScoreMessage="" +local PlayerScore=0 +local PlayerPenalty=0 +local PlayerData=self.Players[PlayerName] +if PlayerData then +self:T("Score Player: "..PlayerName) +local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] +local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] +local InitUnitType=PlayerData.UnitType +local InitUnitName=PlayerData.UnitName +local ScoreMessageHits="" +for CategoryID,CategoryName in pairs(_SCORINGCategory)do +self:T(CategoryName) +if PlayerData.Hit[CategoryID]then +self:T("Hit scores exist for player "..PlayerName) +local Score=0 +local ScoreHit=0 +local Penalty=0 +local PenaltyHit=0 +for UnitName,UnitData in pairs(PlayerData.Hit[CategoryID])do +Score=Score+UnitData.Score +ScoreHit=ScoreHit+UnitData.ScoreHit +Penalty=Penalty+UnitData.Penalty +PenaltyHit=UnitData.PenaltyHit +end +local ScoreMessageHit=string.format("%s: %d ",CategoryName,Score-Penalty) +self:T(ScoreMessageHit) +ScoreMessageHits=ScoreMessageHits..ScoreMessageHit +PlayerScore=PlayerScore+Score +PlayerPenalty=PlayerPenalty+Penalty +else +end +end +if ScoreMessageHits~=""then +ScoreMessage="Hits: "..ScoreMessageHits +end +end +return ScoreMessage,PlayerScore,PlayerPenalty +end +function SCORING:ReportDetailedPlayerDestroys(PlayerName) +local ScoreMessage="" +local PlayerScore=0 +local PlayerPenalty=0 +local PlayerData=self.Players[PlayerName] +if PlayerData then +self:T("Score Player: "..PlayerName) +local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] +local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] +local InitUnitType=PlayerData.UnitType +local InitUnitName=PlayerData.UnitName +local ScoreMessageDestroys="" +for CategoryID,CategoryName in pairs(_SCORINGCategory)do +if PlayerData.Destroy[CategoryID]then +self:T("Destroy scores exist for player "..PlayerName) +local Score=0 +local ScoreDestroy=0 +local Penalty=0 +local PenaltyDestroy=0 +for UnitName,UnitData in pairs(PlayerData.Destroy[CategoryID])do +self:F({UnitData=UnitData}) +if UnitData~={}then +Score=Score+UnitData.Score +ScoreDestroy=ScoreDestroy+UnitData.ScoreDestroy +Penalty=Penalty+UnitData.Penalty +PenaltyDestroy=PenaltyDestroy+UnitData.PenaltyDestroy +end +end +local ScoreMessageDestroy=string.format(" %s: %d ",CategoryName,Score-Penalty) +self:T(ScoreMessageDestroy) +ScoreMessageDestroys=ScoreMessageDestroys..ScoreMessageDestroy +PlayerScore=PlayerScore+Score +PlayerPenalty=PlayerPenalty+Penalty +else +end +end +if ScoreMessageDestroys~=""then +ScoreMessage="Destroys: "..ScoreMessageDestroys +end +end +return ScoreMessage,PlayerScore,PlayerPenalty +end +function SCORING:ReportDetailedPlayerCoalitionChanges(PlayerName) +local ScoreMessage="" +local PlayerScore=0 +local PlayerPenalty=0 +local PlayerData=self.Players[PlayerName] +if PlayerData then +self:T("Score Player: "..PlayerName) +local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] +local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] +local InitUnitType=PlayerData.UnitType +local InitUnitName=PlayerData.UnitName +local ScoreMessageCoalitionChangePenalties="" +if PlayerData.PenaltyCoalition~=0 then +ScoreMessageCoalitionChangePenalties=ScoreMessageCoalitionChangePenalties..string.format(" -%d (%d changed)",PlayerData.Penalty,PlayerData.PenaltyCoalition) +PlayerPenalty=PlayerPenalty+PlayerData.Penalty +end +if ScoreMessageCoalitionChangePenalties~=""then +ScoreMessage=ScoreMessage.."Coalition Penalties: "..ScoreMessageCoalitionChangePenalties +end +end +return ScoreMessage,PlayerScore,PlayerPenalty +end +function SCORING:ReportDetailedPlayerGoals(PlayerName) +local ScoreMessage="" +local PlayerScore=0 +local PlayerPenalty=0 +local PlayerData=self.Players[PlayerName] +if PlayerData then +self:T("Score Player: "..PlayerName) +local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] +local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] +local InitUnitType=PlayerData.UnitType +local InitUnitName=PlayerData.UnitName +local ScoreMessageGoal="" +local ScoreGoal=0 +local ScoreTask=0 +for GoalName,GoalData in pairs(PlayerData.Goals)do +ScoreGoal=ScoreGoal+GoalData.Score +ScoreMessageGoal=ScoreMessageGoal.."'"..GoalName.."':"..GoalData.Score.."; " +end +PlayerScore=PlayerScore+ScoreGoal +if ScoreMessageGoal~=""then +ScoreMessage="Goals: "..ScoreMessageGoal +end +end +return ScoreMessage,PlayerScore,PlayerPenalty +end +function SCORING:ReportDetailedPlayerMissions(PlayerName) +local ScoreMessage="" +local PlayerScore=0 +local PlayerPenalty=0 +local PlayerData=self.Players[PlayerName] +if PlayerData then +self:T("Score Player: "..PlayerName) +local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition] +local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory] +local InitUnitType=PlayerData.UnitType +local InitUnitName=PlayerData.UnitName +local ScoreMessageMission="" +local ScoreMission=0 +local ScoreTask=0 +for MissionName,MissionData in pairs(PlayerData.Mission)do +ScoreMission=ScoreMission+MissionData.ScoreMission +ScoreTask=ScoreTask+MissionData.ScoreTask +ScoreMessageMission=ScoreMessageMission.."'"..MissionName.."'; " +end +PlayerScore=PlayerScore+ScoreMission+ScoreTask +if ScoreMessageMission~=""then +ScoreMessage="Tasks: "..ScoreTask.." Mission: "..ScoreMission.." ( "..ScoreMessageMission..")" +end +end +return ScoreMessage,PlayerScore,PlayerPenalty +end +function SCORING:ReportScoreGroupSummary(PlayerGroup) +local PlayerMessage="" +self:T("Report Score Group Summary") +local PlayerUnits=PlayerGroup:GetUnits() +for UnitID,PlayerUnit in pairs(PlayerUnits)do +local PlayerUnit=PlayerUnit +local PlayerName=PlayerUnit:GetPlayerName() +if PlayerName then +local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName) +ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits +self:F({ReportHits,ScoreHits,PenaltyHits}) +local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName) +ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys +self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys}) +local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName) +ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges +self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges}) +local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName) +ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals +self:F({ReportGoals,ScoreGoals,PenaltyGoals}) +local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName) +ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions +self:F({ReportMissions,ScoreMissions,PenaltyMissions}) +local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions +local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions +PlayerMessage=string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", +PlayerName, +PlayerScore-PlayerPenalty, +PlayerScore, +PlayerPenalty +) +MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) +end +end +end +function SCORING:ReportScoreGroupDetailed(PlayerGroup) +local PlayerMessage="" +self:T("Report Score Group Detailed") +local PlayerUnits=PlayerGroup:GetUnits() +for UnitID,PlayerUnit in pairs(PlayerUnits)do +local PlayerUnit=PlayerUnit +local PlayerName=PlayerUnit:GetPlayerName() +if PlayerName then +local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName) +ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits +self:F({ReportHits,ScoreHits,PenaltyHits}) +local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName) +ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys +self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys}) +local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName) +ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges +self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges}) +local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName) +ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals +self:F({ReportGoals,ScoreGoals,PenaltyGoals}) +local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName) +ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions +self:F({ReportMissions,ScoreMissions,PenaltyMissions}) +local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions +local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions +PlayerMessage= +string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s", +PlayerName, +PlayerScore-PlayerPenalty, +PlayerScore, +PlayerPenalty, +ReportHits, +ReportDestroys, +ReportCoalitionChanges, +ReportGoals, +ReportMissions +) +MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) +end +end +end +function SCORING:ReportScoreAllSummary(PlayerGroup) +local PlayerMessage="" +self:T({"Summary Score Report of All Players",Players=self.Players}) +for PlayerName,PlayerData in pairs(self.Players)do +self:T({PlayerName=PlayerName,PlayerGroup=PlayerGroup}) +if PlayerName then +local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName) +ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits +self:F({ReportHits,ScoreHits,PenaltyHits}) +local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName) +ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys +self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys}) +local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName) +ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges +self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges}) +local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName) +ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals +self:F({ReportGoals,ScoreGoals,PenaltyGoals}) +local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName) +ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions +self:F({ReportMissions,ScoreMissions,PenaltyMissions}) +local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions +local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions +PlayerMessage= +string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", +PlayerName, +PlayerScore-PlayerPenalty, +PlayerScore, +PlayerPenalty +) +MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) +end +end +end +function SCORING:SecondsToClock(sSeconds) +local nSeconds=sSeconds +if nSeconds==0 then +return"00:00:00"; +else +local nHours=string.format("%02.f",math.floor(nSeconds/3600)); +local nMins=string.format("%02.f",math.floor(nSeconds/60-(nHours*60))); +local nSecs=string.format("%02.f",math.floor(nSeconds-nHours*3600-nMins*60)); +return nHours..":"..nMins..":"..nSecs +end +end +function SCORING:OpenCSV(ScoringCSV) +self:F(ScoringCSV) +if lfs and io and os and self.AutoSave==true then +if ScoringCSV then +self.ScoringCSV=ScoringCSV +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local fdir=path..self.ScoringCSV.." "..os.date("%Y-%m-%d %H-%M-%S")..".csv" +self.CSVFile,self.err=io.open(fdir,"w+") +if not self.CSVFile then +error("Error: Cannot open CSV file in "..lfs.writedir()) +end +self.CSVFile:write('"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoalition","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n') +self.RunTime=os.date("%y-%m-%d_%H-%M-%S") +else +error("A string containing the CSV file name must be given.") +end +else +self:F("The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used...") +end +return self +end +function SCORING:ScoreCSV(PlayerName,TargetPlayerName,ScoreType,ScoreTimes,ScoreAmount,PlayerUnitName,PlayerUnitCoalition,PlayerUnitCategory,PlayerUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +local ScoreTime=self:SecondsToClock(timer.getTime()) +PlayerName=PlayerName:gsub('"','_') +TargetPlayerName=TargetPlayerName or"" +TargetPlayerName=TargetPlayerName:gsub('"','_') +if PlayerUnitName and PlayerUnitName~=''then +local PlayerUnit=Unit.getByName(PlayerUnitName) +if PlayerUnit then +if not PlayerUnitCategory then +PlayerUnitCategory=_SCORINGCategory[PlayerUnit:getDesc().category] +end +if not PlayerUnitCoalition then +PlayerUnitCoalition=_SCORINGCoalition[PlayerUnit:getCoalition()] +end +if not PlayerUnitType then +PlayerUnitType=PlayerUnit:getTypeName() +end +else +PlayerUnitName='' +PlayerUnitCategory='' +PlayerUnitCoalition='' +PlayerUnitType='' +end +else +PlayerUnitName='' +PlayerUnitCategory='' +PlayerUnitCoalition='' +PlayerUnitType='' +end +TargetUnitCoalition=TargetUnitCoalition or"" +TargetUnitCategory=TargetUnitCategory or"" +TargetUnitType=TargetUnitType or"" +TargetUnitName=TargetUnitName or"" +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then +self.CSVFile:write( +'"'..self.GameName..'"'..','.. +'"'..self.RunTime..'"'..','.. +''..ScoreTime..''..','.. +'"'..PlayerName..'"'..','.. +'"'..TargetPlayerName..'"'..','.. +'"'..ScoreType..'"'..','.. +'"'..PlayerUnitCoalition..'"'..','.. +'"'..PlayerUnitCategory..'"'..','.. +'"'..PlayerUnitType..'"'..','.. +'"'..PlayerUnitName..'"'..','.. +'"'..TargetUnitCoalition..'"'..','.. +'"'..TargetUnitCategory..'"'..','.. +'"'..TargetUnitType..'"'..','.. +'"'..TargetUnitName..'"'..','.. +''..ScoreTimes..''..','.. +''..ScoreAmount +) +self.CSVFile:write("\n") +end +end +function SCORING:CloseCSV() +if lfs and io and os and self.AutoSave then +self.CSVFile:close() +end +end +function SCORING:SwitchAutoSave(OnOff) +self.AutoSave=OnOff +return self +end +function SCORING:OnKillPvP(PlayerName,TargetPlayerName,IsTeamKill,TargetThreatLevel,PlayerThreatLevel,Score) +end +function SCORING:OnKillPvE(PlayerName,TargetUnitName,IsTeamKill,TargetThreatLevel,PlayerThreatLevel,Score) +end +CLEANUP_AIRBASE={ +ClassName="CLEANUP_AIRBASE", +TimeInterval=0.2, +CleanUpList={}, +} +CLEANUP_AIRBASE.__={} +CLEANUP_AIRBASE.__.Airbases={} +function CLEANUP_AIRBASE:New(AirbaseNames) +local self=BASE:Inherit(self,BASE:New()) +self:F({AirbaseNames}) +if type(AirbaseNames)=='table'then +for AirbaseID,AirbaseName in pairs(AirbaseNames)do +self:AddAirbase(AirbaseName) +end +else +local AirbaseName=AirbaseNames +self:AddAirbase(AirbaseName) +end +self:HandleEvent(EVENTS.Birth,self.__.OnEventBirth) +self.__.CleanUpScheduler=SCHEDULER:New(self,self.__.CleanUpSchedule,{},1,self.TimeInterval) +self:HandleEvent(EVENTS.EngineShutdown,self.__.EventAddForCleanUp) +self:HandleEvent(EVENTS.EngineStartup,self.__.EventAddForCleanUp) +self:HandleEvent(EVENTS.Hit,self.__.EventAddForCleanUp) +self:HandleEvent(EVENTS.PilotDead,self.__.OnEventCrash) +self:HandleEvent(EVENTS.Dead,self.__.OnEventCrash) +self:HandleEvent(EVENTS.Crash,self.__.OnEventCrash) +for UnitName,Unit in pairs(_DATABASE.UNITS)do +local Unit=Unit +if Unit:IsAlive()~=nil then +if self:IsInAirbase(Unit:GetVec2())then +self:F({UnitName=UnitName}) +self.CleanUpList[UnitName]={} +self.CleanUpList[UnitName].CleanUpUnit=Unit +self.CleanUpList[UnitName].CleanUpGroup=Unit:GetGroup() +self.CleanUpList[UnitName].CleanUpGroupName=Unit:GetGroup():GetName() +self.CleanUpList[UnitName].CleanUpUnitName=Unit:GetName() +end +end +end +return self +end +function CLEANUP_AIRBASE:AddAirbase(AirbaseName) +self.__.Airbases[AirbaseName]=AIRBASE:FindByName(AirbaseName) +self:F({"Airbase:",AirbaseName,self.__.Airbases[AirbaseName]:GetDesc()}) +return self +end +function CLEANUP_AIRBASE:RemoveAirbase(AirbaseName) +self.__.Airbases[AirbaseName]=nil +return self +end +function CLEANUP_AIRBASE:SetCleanMissiles(CleanMissiles) +if CleanMissiles then +self:HandleEvent(EVENTS.Shot,self.__.OnEventShot) +else +self:UnHandleEvent(EVENTS.Shot) +end +end +function CLEANUP_AIRBASE.__:IsInAirbase(Vec2) +local InAirbase=false +for AirbaseName,Airbase in pairs(self.__.Airbases)do +local Airbase=Airbase +if Airbase:GetZone():IsVec2InZone(Vec2)then +InAirbase=true +break; +end +end +return InAirbase +end +function CLEANUP_AIRBASE.__:DestroyUnit(CleanUpUnit) +self:F({CleanUpUnit}) +if CleanUpUnit then +local CleanUpUnitName=CleanUpUnit:GetName() +local CleanUpGroup=CleanUpUnit:GetGroup() +if CleanUpGroup:IsAlive()then +local CleanUpGroupUnits=CleanUpGroup:GetUnits() +if#CleanUpGroupUnits==1 then +local CleanUpGroupName=CleanUpGroup:GetName() +CleanUpGroup:Destroy() +else +CleanUpUnit:Destroy() +end +self.CleanUpList[CleanUpUnitName]=nil +end +end +end +function CLEANUP_AIRBASE.__:DestroyMissile(MissileObject) +self:F({MissileObject}) +if MissileObject and MissileObject:isExist()then +MissileObject:destroy() +self:T("MissileObject Destroyed") +end +end +function CLEANUP_AIRBASE.__:OnEventBirth(EventData) +self:F({EventData}) +if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()~=nil then +if self:IsInAirbase(EventData.IniUnit:GetVec2())then +self.CleanUpList[EventData.IniDCSUnitName]={} +self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit=EventData.IniUnit +self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup=EventData.IniGroup +self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroupName=EventData.IniDCSGroupName +self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnitName=EventData.IniDCSUnitName +end +end +end +function CLEANUP_AIRBASE.__:OnEventCrash(Event) +self:F({Event}) +if Event.IniDCSUnitName and Event.IniCategory==Object.Category.UNIT then +self.CleanUpList[Event.IniDCSUnitName]={} +self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit=Event.IniUnit +self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup=Event.IniGroup +self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName=Event.IniDCSGroupName +self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName=Event.IniDCSUnitName +end +end +function CLEANUP_AIRBASE.__:OnEventShot(Event) +self:F({Event}) +if self:IsInAirbase(Event.IniUnit:GetVec2())then +self:DestroyMissile(Event.Weapon) +end +end +function CLEANUP_AIRBASE.__:OnEventHit(Event) +self:F({Event}) +if Event.IniUnit then +if self:IsInAirbase(Event.IniUnit:GetVec2())then +self:T({"Life: ",Event.IniDCSUnitName,' = ',Event.IniUnit:GetLife(),"/",Event.IniUnit:GetLife0()}) +if Event.IniUnit:GetLife()0 then +local MoveProbability=(self.MoveMaximum*100)/self.AliveUnits +self:T('Move Probability = '..MoveProbability) +for MovementUnitName,MovementGroupName in pairs(self.MoveUnits)do +local MovementGroup=Group.getByName(MovementGroupName) +if MovementGroup and MovementGroup:isExist()then +local MoveOrStop=math.random(1,100) +self:T('MoveOrStop = '..MoveOrStop) +if MoveOrStop<=MoveProbability then +self:T('Group continues moving = '..MovementGroupName) +trigger.action.groupContinueMoving(MovementGroup) +else +self:T('Group stops moving = '..MovementGroupName) +trigger.action.groupStopMoving(MovementGroup) +end +else +self.MoveUnits[MovementUnitName]=nil +end +end +end +return true +end +SEAD={ +ClassName="SEAD", +TargetSkill={ +Average={Evade=30,DelayOn={40,60}}, +Good={Evade=20,DelayOn={30,50}}, +High={Evade=15,DelayOn={20,40}}, +Excellent={Evade=10,DelayOn={10,30}} +}, +SEADGroupPrefixes={}, +SuppressedGroups={}, +EngagementRange=75, +Padding=15, +CallBack=nil, +UseCallBack=false, +debug=false, +WeaponTrack=false, +} +SEAD.Harms={ +["AGM_88"]="AGM_88", +["AGM_122"]="AGM_122", +["AGM_84"]="AGM_84", +["AGM_45"]="AGM_45", +["AGM_65"]="AGM_65", +["ALARM"]="ALARM", +["LD-10"]="LD-10", +["X_58"]="X_58", +["X_28"]="X_28", +["X_25"]="X_25", +["X_31"]="X_31", +["Kh25"]="Kh25", +["BGM_109"]="BGM_109", +["AGM_154"]="AGM_154", +["HY-2"]="HY-2", +["ADM_141A"]="ADM_141A", +} +SEAD.HarmData={ +["AGM_88"]={150,3}, +["AGM_45"]={12,2}, +["AGM_65"]={16,0.9}, +["AGM_122"]={16.5,2.3}, +["AGM_84"]={280,0.8}, +["ALARM"]={45,2}, +["LD-10"]={60,4}, +["X_58"]={70,4}, +["X_28"]={80,2.5}, +["X_25"]={25,0.76}, +["X_31"]={150,3}, +["Kh25"]={25,0.8}, +["BGM_109"]={460,0.705}, +["AGM_154"]={130,0.61}, +["HY-2"]={90,1}, +["ADM_141A"]={126,0.6}, +} +function SEAD:New(SEADGroupPrefixes,Padding) +local self=BASE:Inherit(self,FSM:New()) +self:T(SEADGroupPrefixes) +if type(SEADGroupPrefixes)=='table'then +for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do +self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix +end +else +self.SEADGroupPrefixes[SEADGroupPrefixes]=SEADGroupPrefixes +end +local padding=Padding or 10 +if padding<10 then padding=10 end +self.Padding=padding +self.UseEmissionsOnOff=true +self.debug=false +self.CallBack=nil +self.UseCallBack=false +self:HandleEvent(EVENTS.Shot,self.HandleEventShot) +self:SetStartState("Running") +self:AddTransition("*","ManageEvasion","*") +self:AddTransition("*","CalculateHitZone","*") +self:I("*** SEAD - Started Version 0.4.9") +return self +end +function SEAD:UpdateSet(SEADGroupPrefixes) +self:T(SEADGroupPrefixes) +if type(SEADGroupPrefixes)=='table'then +for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do +self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix +end +else +self.SEADGroupPrefixes[SEADGroupPrefixes]=SEADGroupPrefixes +end +return self +end +function SEAD:SetEngagementRange(range) +self:T({range}) +range=range or 75 +if range<0 or range>100 then +range=75 +end +self.EngagementRange=range +self:T(string.format("*** SEAD - Engagement range set to %s",range)) +return self +end +function SEAD:SetPadding(Padding) +self:T({Padding}) +local padding=Padding or 10 +if padding<10 then padding=10 end +self.Padding=padding +return self +end +function SEAD:SwitchEmissions(Switch) +self:T({Switch}) +self.UseEmissionsOnOff=Switch +return self +end +function SEAD:AddCallBack(Object) +self:T({Class=Object.ClassName}) +self.CallBack=Object +self.UseCallBack=true +return self +end +function SEAD:_CheckHarms(WeaponName) +self:T({WeaponName}) +local hit=false +local name="" +for _,_name in pairs(SEAD.Harms)do +if string.find(WeaponName,_name,1,true)then +hit=true +name=_name +break +end +end +return hit,name +end +function SEAD:_GetDistance(_point1,_point2) +self:T("_GetDistance") +if _point1 and _point2 then +local distance1=_point1:Get2DDistance(_point2) +local distance2=_point1:DistanceFromPointVec2(_point2) +if distance1 and type(distance1)=="number"then +return distance1 +elseif distance2 and type(distance2)=="number"then +return distance2 +else +self:E("*****Cannot calculate distance!") +self:E({_point1,_point2}) +return-1 +end +else +self:E("******Cannot calculate distance!") +self:E({_point1,_point2}) +return-1 +end +end +function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName) +self:T("**** Calculating hit zone for "..(SEADWeaponName or"None")) +if SEADWeapon and SEADWeapon:isExist()then +local position=SEADWeapon:getPosition() +local mheight=height +local wph=math.atan2(position.x.z,position.x.x) +if wph<0 then +wph=wph+2*math.pi +end +wph=math.deg(wph) +local wpndata=SEAD.HarmData["AGM_88"] +if string.find(SEADWeaponName,"154",1)then +wpndata=SEAD.HarmData["AGM_154"] +end +local mveloc=math.floor(wpndata[2]*340.29) +local c1=(2*mheight*9.81)/(mveloc^2) +local c2=(mveloc^2)/9.81 +local Ropt=c2*math.sqrt(c1+1) +if height<=5000 then +Ropt=Ropt*0.72 +elseif height<=7500 then +Ropt=Ropt*0.82 +elseif height<=10000 then +Ropt=Ropt*0.87 +elseif height<=12500 then +Ropt=Ropt*0.98 +end +for n=1,3 do +local dist=Ropt-((n-1)*20000) +local predpos=pos0:Translate(dist,wph) +if predpos then +local targetzone=ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000) +if self.debug then +predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false) +targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true) +end +local seadset=SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce() +local tgtgrp=seadset:GetRandom() +local _targetgroup=nil +local _targetgroupname="none" +local _targetskill="Random" +if tgtgrp and tgtgrp:IsAlive()then +_targetgroup=tgtgrp +_targetgroupname=tgtgrp:GetName() +_targetskill=tgtgrp:GetUnit(1):GetSkill() +self:T("*** Found Target = ".._targetgroupname) +self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup,20) +end +end +end +end +return self +end +function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset,Weapon) +local timeoffset=timeoffset or 0 +if _targetskill=="Random"then +local Skills={"Average","Good","High","Excellent"} +_targetskill=Skills[math.random(1,4)] +end +if self.TargetSkill[_targetskill]then +local _evade=math.random(1,100) +if(_evade>self.TargetSkill[_targetskill].Evade)then +self:T("*** SEAD - Evading") +local _targetpos=_targetgroup:GetCoordinate() +local _distance=self:_GetDistance(SEADPlanePos,_targetpos) +local hit,data=self:_CheckHarms(SEADWeaponName) +local wpnspeed=666 +local reach=10 +if hit then +local wpndata=SEAD.HarmData[data] +reach=wpndata[1]*1.1 +local mach=wpndata[2] +wpnspeed=math.floor(mach*340.29) +if Weapon and Weapon:GetSpeed()>0 then +wpnspeed=Weapon:GetSpeed() +self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed)) +end +end +local _tti=math.floor(_distance/wpnspeed)-timeoffset +if _distance>0 then +_distance=math.floor(_distance/1000) +else +_distance=0 +end +self:T(string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec",_targetskill,_distance,reach,_tti)) +if reach>=_distance then +self:T("*** SEAD - Shot in Reach") +local function SuppressionStart(args) +self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2])) +local grp=args[1] +local name=args[2] +local attacker=args[3] +if self.UseEmissionsOnOff then +grp:EnableEmission(false) +end +grp:OptionAlarmStateGreen() +grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond",true) +if self.UseCallBack then +local object=self.CallBack +object:SeadSuppressionStart(grp,name,attacker) +end +end +local function SuppressionStop(args) +self:T(string.format("*** SEAD - %s Radar On",args[2])) +local grp=args[1] +local name=args[2] +if self.UseEmissionsOnOff then +grp:EnableEmission(true) +end +grp:OptionAlarmStateRed() +grp:OptionEngageRange(self.EngagementRange) +self.SuppressedGroups[name]=false +if self.UseCallBack then +local object=self.CallBack +object:SeadSuppressionEnd(grp,name) +end +end +local delay=math.random(self.TargetSkill[_targetskill].DelayOn[1],self.TargetSkill[_targetskill].DelayOn[2]) +if delay>_tti then delay=delay/2 end +if _tti>600 then delay=_tti-90 end +local SuppressionStartTime=timer.getTime()+delay +local SuppressionEndTime=timer.getTime()+delay+_tti+self.Padding+delay +local _targetgroupname=_targetgroup:GetName() +if not self.SuppressedGroups[_targetgroupname]then +self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) +timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname,SEADGroup},SuppressionStartTime) +timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) +self.SuppressedGroups[_targetgroupname]=true +if self.UseCallBack then +local object=self.CallBack +object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime,SEADGroup) +end +end +end +end +end +return self +end +function SEAD:HandleEventShot(EventData) +self:T({EventData.id}) +local SEADWeapon=EventData.Weapon +local SEADWeaponName=EventData.WeaponName or"None" +if self:_CheckHarms(SEADWeaponName)then +local SEADPlane=EventData.IniUnit +if not SEADPlane then return self end +local SEADGroup=EventData.IniGroup +local SEADPlanePos=SEADPlane:GetCoordinate() +local SEADUnit=EventData.IniDCSUnit +local SEADUnitName=EventData.IniDCSUnitName +local WeaponWrapper=WEAPON:New(EventData.Weapon) +self:T("*** SEAD - Missile Launched = "..SEADWeaponName) +self:T('*** SEAD - Weapon Match') +if self.WeaponTrack==true then +WeaponWrapper:SetFuncTrack(function(weapon)env.info(string.format("*** Weapon Speed: %d m/s",weapon:GetSpeed()or-1))end) +WeaponWrapper:StartTrack(0.1) +WeaponWrapper:StopTrack(30) +end +local _targetskill="Random" +local _targetgroupname="none" +local _target=EventData.Weapon:getTarget() +if not _target or self.debug then +self:E("***** SEAD - No target data for "..(SEADWeaponName or"None")) +if string.find(SEADWeaponName,"AGM_88",1,true)or string.find(SEADWeaponName,"AGM_154",1,true)then +self:T("**** Tracking AGM-88/154 with no target data.") +local pos0=SEADPlane:GetCoordinate() +local fheight=SEADPlane:GetHeight() +self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName) +end +return self +end +local targetcat=Object.getCategory(_target) +local _targetUnit=nil +local _targetgroup=nil +self:T(string.format("*** Targetcat = %d",targetcat)) +if targetcat==Object.Category.UNIT then +self:T("*** Target Category UNIT") +_targetUnit=UNIT:Find(_target) +if _targetUnit and _targetUnit:IsAlive()then +_targetgroup=_targetUnit:GetGroup() +_targetgroupname=_targetgroup:GetName() +local _targetUnitName=_targetUnit:GetName() +_targetUnit:GetSkill() +_targetskill=_targetUnit:GetSkill() +end +elseif targetcat==Object.Category.STATIC then +self:T("*** Target Category STATIC") +local seadset=SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce() +local targetpoint=_target:getPoint()or{x=0,y=0,z=0} +local tgtcoord=COORDINATE:NewFromVec3(targetpoint) +local tgtgrp=seadset:FindNearestGroupFromPointVec2(tgtcoord) +if tgtgrp and tgtgrp:IsAlive()then +_targetgroup=tgtgrp +_targetgroupname=tgtgrp:GetName() +_targetskill=tgtgrp:GetUnit(1):GetSkill() +self:T("*** Found Target = ".._targetgroupname) +end +end +local SEADGroupFound=false +for SEADGroupPrefixID,SEADGroupPrefix in pairs(self.SEADGroupPrefixes)do +self:T("Target = ".._targetgroupname.." | Prefix = "..SEADGroupPrefix) +if string.find(_targetgroupname,SEADGroupPrefix,1,true)then +SEADGroupFound=true +self:T('*** SEAD - Group Match Found') +break +end +end +if SEADGroupFound==true then +if string.find(SEADWeaponName,"ADM_141",1,true)then +self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,2,WeaponWrapper) +else +self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper) +end +end +end +return self +end +ESCORT={ +ClassName="ESCORT", +EscortName=nil, +EscortClient=nil, +EscortGroup=nil, +EscortMode=1, +MODE={ +FOLLOW=1, +MISSION=2, +}, +Targets={}, +FollowScheduler=nil, +ReportTargets=true, +OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE, +OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, +SmokeDirectionVector=false, +TaskPoints={} +} +function ESCORT:New(EscortClient,EscortGroup,EscortName,EscortBriefing) +local self=BASE:Inherit(self,BASE:New()) +self:F({EscortClient,EscortGroup,EscortName}) +self.EscortClient=EscortClient +self.EscortGroup=EscortGroup +self.EscortName=EscortName +self.EscortBriefing=EscortBriefing +self.EscortSetGroup=SET_GROUP:New() +self.EscortSetGroup:AddObject(self.EscortGroup) +self.EscortSetGroup:Flush() +self.Detection=DETECTION_UNITS:New(self.EscortSetGroup,15000) +self.EscortGroup.Detection=self.Detection +if not self.EscortClient._EscortGroups then +self.EscortClient._EscortGroups={} +end +if not self.EscortClient._EscortGroups[EscortGroup:GetName()]then +self.EscortClient._EscortGroups[EscortGroup:GetName()]={} +self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortGroup=self.EscortGroup +self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortName=self.EscortName +self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection=self.EscortGroup.Detection +end +self.EscortMenu=MENU_GROUP:New(self.EscortClient:GetGroup(),self.EscortName) +self.EscortGroup:WayPointInitialize(1) +self.EscortGroup:OptionROTVertical() +self.EscortGroup:OptionROEOpenFire() +if not EscortBriefing then +EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") reporting! ".. +"We're escorting your flight. ".. +"Use the Radio Menu and F10 and use the options under + "..EscortName.."\n", +60,EscortClient +) +else +EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") "..EscortBriefing, +60,EscortClient +) +end +self.FollowDistance=100 +self.CT1=0 +self.GT1=0 +self.FollowScheduler,self.FollowSchedule=SCHEDULER:New(self,self._FollowScheduler,{},1,.5,.01) +self.FollowScheduler:Stop(self.FollowSchedule) +self.EscortMode=ESCORT.MODE.MISSION +return self +end +function ESCORT:SetDetection(Detection) +self.Detection=Detection +self.EscortGroup.Detection=self.Detection +self.EscortClient._EscortGroups[self.EscortGroup:GetName()].Detection=self.EscortGroup.Detection +Detection:__Start(1) +end +function ESCORT:TestSmokeDirectionVector(SmokeDirection) +self.SmokeDirectionVector=(SmokeDirection==true)and true or false +end +function ESCORT:Menus() +self:F() +self:MenuFollowAt(100) +self:MenuFollowAt(200) +self:MenuFollowAt(300) +self:MenuFollowAt(400) +self:MenuScanForTargets(100,60) +self:MenuHoldAtEscortPosition(30) +self:MenuHoldAtLeaderPosition(30) +self:MenuFlare() +self:MenuSmoke() +self:MenuReportTargets(60) +self:MenuAssistedAttack() +self:MenuROE() +self:MenuEvasion() +self:MenuResumeMission() +return self +end +function ESCORT:MenuFollowAt(Distance) +self:F(Distance) +if self.EscortGroup:IsAir()then +if not self.EscortMenuReportNavigation then +self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu) +end +if not self.EscortMenuJoinUpAndFollow then +self.EscortMenuJoinUpAndFollow={} +end +self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1]=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Join-Up and Follow at "..Distance,self.EscortMenuReportNavigation,ESCORT._JoinUpAndFollow,self,Distance) +self.EscortMode=ESCORT.MODE.FOLLOW +end +return self +end +function ESCORT:MenuHoldAtEscortPosition(Height,Seconds,MenuTextFormat) +self:F({Height,Seconds,MenuTextFormat}) +if self.EscortGroup:IsAir()then +if not self.EscortMenuHold then +self.EscortMenuHold=MENU_GROUP:New(self.EscortClient:GetGroup(),"Hold position",self.EscortMenu) +end +if not Height then +Height=30 +end +if not Seconds then +Seconds=0 +end +local MenuText="" +if not MenuTextFormat then +if Seconds==0 then +MenuText=string.format("Hold at %d meter",Height) +else +MenuText=string.format("Hold at %d meter for %d seconds",Height,Seconds) +end +else +if Seconds==0 then +MenuText=string.format(MenuTextFormat,Height) +else +MenuText=string.format(MenuTextFormat,Height,Seconds) +end +end +if not self.EscortMenuHoldPosition then +self.EscortMenuHoldPosition={} +end +self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1]=MENU_GROUP_COMMAND +:New( +self.EscortClient:GetGroup(), +MenuText, +self.EscortMenuHold, +ESCORT._HoldPosition, +self, +self.EscortGroup, +Height, +Seconds +) +end +return self +end +function ESCORT:MenuHoldAtLeaderPosition(Height,Seconds,MenuTextFormat) +self:F({Height,Seconds,MenuTextFormat}) +if self.EscortGroup:IsAir()then +if not self.EscortMenuHold then +self.EscortMenuHold=MENU_GROUP:New(self.EscortClient:GetGroup(),"Hold position",self.EscortMenu) +end +if not Height then +Height=30 +end +if not Seconds then +Seconds=0 +end +local MenuText="" +if not MenuTextFormat then +if Seconds==0 then +MenuText=string.format("Rejoin and hold at %d meter",Height) +else +MenuText=string.format("Rejoin and hold at %d meter for %d seconds",Height,Seconds) +end +else +if Seconds==0 then +MenuText=string.format(MenuTextFormat,Height) +else +MenuText=string.format(MenuTextFormat,Height,Seconds) +end +end +if not self.EscortMenuHoldAtLeaderPosition then +self.EscortMenuHoldAtLeaderPosition={} +end +self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1]=MENU_GROUP_COMMAND +:New( +self.EscortClient:GetGroup(), +MenuText, +self.EscortMenuHold, +ESCORT._HoldPosition, +{ParamSelf=self, +ParamOrbitGroup=self.EscortClient, +ParamHeight=Height, +ParamSeconds=Seconds +} +) +end +return self +end +function ESCORT:MenuScanForTargets(Height,Seconds,MenuTextFormat) +self:F({Height,Seconds,MenuTextFormat}) +if self.EscortGroup:IsAir()then +if not self.EscortMenuScan then +self.EscortMenuScan=MENU_GROUP:New(self.EscortClient:GetGroup(),"Scan for targets",self.EscortMenu) +end +if not Height then +Height=100 +end +if not Seconds then +Seconds=30 +end +local MenuText="" +if not MenuTextFormat then +if Seconds==0 then +MenuText=string.format("At %d meter",Height) +else +MenuText=string.format("At %d meter for %d seconds",Height,Seconds) +end +else +if Seconds==0 then +MenuText=string.format(MenuTextFormat,Height) +else +MenuText=string.format(MenuTextFormat,Height,Seconds) +end +end +if not self.EscortMenuScanForTargets then +self.EscortMenuScanForTargets={} +end +self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1]=MENU_GROUP_COMMAND +:New( +self.EscortClient:GetGroup(), +MenuText, +self.EscortMenuScan, +ESCORT._ScanTargets, +self, +30 +) +end +return self +end +function ESCORT:MenuFlare(MenuTextFormat) +self:F() +if not self.EscortMenuReportNavigation then +self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu) +end +local MenuText="" +if not MenuTextFormat then +MenuText="Flare" +else +MenuText=MenuTextFormat +end +if not self.EscortMenuFlare then +self.EscortMenuFlare=MENU_GROUP:New(self.EscortClient:GetGroup(),MenuText,self.EscortMenuReportNavigation,ESCORT._Flare,self) +self.EscortMenuFlareGreen=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release green flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Green,"Released a green flare!") +self.EscortMenuFlareRed=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release red flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Red,"Released a red flare!") +self.EscortMenuFlareWhite=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release white flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.White,"Released a white flare!") +self.EscortMenuFlareYellow=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release yellow flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Yellow,"Released a yellow flare!") +end +return self +end +function ESCORT:MenuSmoke(MenuTextFormat) +self:F() +if not self.EscortGroup:IsAir()then +if not self.EscortMenuReportNavigation then +self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu) +end +local MenuText="" +if not MenuTextFormat then +MenuText="Smoke" +else +MenuText=MenuTextFormat +end +if not self.EscortMenuSmoke then +self.EscortMenuSmoke=MENU_GROUP:New(self.EscortClient:GetGroup(),"Smoke",self.EscortMenuReportNavigation,ESCORT._Smoke,self) +self.EscortMenuSmokeGreen=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release green smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Green,"Releasing green smoke!") +self.EscortMenuSmokeRed=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release red smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Red,"Releasing red smoke!") +self.EscortMenuSmokeWhite=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release white smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.White,"Releasing white smoke!") +self.EscortMenuSmokeOrange=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release orange smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!") +self.EscortMenuSmokeBlue=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release blue smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Blue,"Releasing blue smoke!") +end +end +return self +end +function ESCORT:MenuReportTargets(Seconds) +self:F({Seconds}) +if not self.EscortMenuReportNearbyTargets then +self.EscortMenuReportNearbyTargets=MENU_GROUP:New(self.EscortClient:GetGroup(),"Report targets",self.EscortMenu) +end +if not Seconds then +Seconds=30 +end +self.EscortMenuReportNearbyTargetsNow=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets now!",self.EscortMenuReportNearbyTargets,ESCORT._ReportNearbyTargetsNow,self) +self.EscortMenuReportNearbyTargetsOn=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets on",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,true) +self.EscortMenuReportNearbyTargetsOff=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets off",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,false) +self.EscortMenuAttackNearbyTargets=MENU_GROUP:New(self.EscortClient:GetGroup(),"Attack targets",self.EscortMenu) +self.ReportTargetsScheduler,self.ReportTargetsSchedulerID=SCHEDULER:New(self,self._ReportTargetsScheduler,{},1,Seconds) +return self +end +function ESCORT:MenuAssistedAttack() +self:F() +self.EscortMenuTargetAssistance=MENU_GROUP:New(self.EscortClient:GetGroup(),"Request assistance from",self.EscortMenu) +return self +end +function ESCORT:MenuROE(MenuTextFormat) +self:F(MenuTextFormat) +if not self.EscortMenuROE then +self.EscortMenuROE=MENU_GROUP:New(self.EscortClient:GetGroup(),"ROE",self.EscortMenu) +if self.EscortGroup:OptionROEHoldFirePossible()then +self.EscortMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Hold Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEHoldFire(),"Holding weapons!") +end +if self.EscortGroup:OptionROEReturnFirePossible()then +self.EscortMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Return Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEReturnFire(),"Returning fire!") +end +if self.EscortGroup:OptionROEOpenFirePossible()then +self.EscortMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Open Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEOpenFire(),"Opening fire on designated targets!!") +end +if self.EscortGroup:OptionROEWeaponFreePossible()then +self.EscortMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Weapon Free",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEWeaponFree(),"Opening fire on targets of opportunity!") +end +end +return self +end +function ESCORT:MenuEvasion(MenuTextFormat) +self:F(MenuTextFormat) +if self.EscortGroup:IsAir()then +if not self.EscortMenuEvasion then +self.EscortMenuEvasion=MENU_GROUP:New(self.EscortClient:GetGroup(),"Evasion",self.EscortMenu) +if self.EscortGroup:OptionROTNoReactionPossible()then +self.EscortMenuEvasionNoReaction=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Fight until death",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTNoReaction(),"Fighting until death!") +end +if self.EscortGroup:OptionROTPassiveDefensePossible()then +self.EscortMenuEvasionPassiveDefense=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Use flares, chaff and jammers",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTPassiveDefense(),"Defending using jammers, chaff and flares!") +end +if self.EscortGroup:OptionROTEvadeFirePossible()then +self.EscortMenuEvasionEvadeFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Evade enemy fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTEvadeFire(),"Evading on enemy fire!") +end +if self.EscortGroup:OptionROTVerticalPossible()then +self.EscortMenuOptionEvasionVertical=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Go below radar and evade fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTVertical(),"Evading on enemy fire with vertical manoeuvres!") +end +end +end +return self +end +function ESCORT:MenuResumeMission() +self:F() +if not self.EscortMenuResumeMission then +self.EscortMenuResumeMission=MENU_GROUP:New(self.EscortClient:GetGroup(),"Resume mission from",self.EscortMenu) +end +return self +end +function ESCORT:_HoldPosition(OrbitGroup,OrbitHeight,OrbitSeconds) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +local OrbitUnit=OrbitGroup:GetUnit(1) +self.FollowScheduler:Stop(self.FollowSchedule) +local PointFrom={} +local GroupVec3=EscortGroup:GetUnit(1):GetVec3() +PointFrom={} +PointFrom.x=GroupVec3.x +PointFrom.y=GroupVec3.z +PointFrom.speed=250 +PointFrom.type=AI.Task.WaypointType.TURNING_POINT +PointFrom.alt=GroupVec3.y +PointFrom.alt_type=AI.Task.AltitudeType.BARO +local OrbitPoint=OrbitUnit:GetVec2() +local PointTo={} +PointTo.x=OrbitPoint.x +PointTo.y=OrbitPoint.y +PointTo.speed=250 +PointTo.type=AI.Task.WaypointType.TURNING_POINT +PointTo.alt=OrbitHeight +PointTo.alt_type=AI.Task.AltitudeType.BARO +PointTo.task=EscortGroup:TaskOrbitCircleAtVec2(OrbitPoint,OrbitHeight,0) +local Points={PointFrom,PointTo} +EscortGroup:OptionROEHoldFire() +EscortGroup:OptionROTPassiveDefense() +EscortGroup:SetTask(EscortGroup:TaskRoute(Points)) +EscortGroup:MessageToClient("Orbiting at location.",10,EscortClient) +end +function ESCORT:_JoinUpAndFollow(Distance) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +self.Distance=Distance +self:JoinUpAndFollow(EscortGroup,EscortClient,self.Distance) +end +function ESCORT:JoinUpAndFollow(EscortGroup,EscortClient,Distance) +self:F({EscortGroup,EscortClient,Distance}) +self.FollowScheduler:Stop(self.FollowSchedule) +EscortGroup:OptionROEHoldFire() +EscortGroup:OptionROTPassiveDefense() +self.EscortMode=ESCORT.MODE.FOLLOW +self.CT1=0 +self.GT1=0 +self.FollowScheduler:Start(self.FollowSchedule) +EscortGroup:MessageToClient("Rejoining and Following at "..Distance.."!",30,EscortClient) +end +function ESCORT:_Flare(Color,Message) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +EscortGroup:GetUnit(1):Flare(Color) +EscortGroup:MessageToClient(Message,10,EscortClient) +end +function ESCORT:_Smoke(Color,Message) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +EscortGroup:GetUnit(1):Smoke(Color) +EscortGroup:MessageToClient(Message,10,EscortClient) +end +function ESCORT:_ReportNearbyTargetsNow() +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +self:_ReportTargetsScheduler() +end +function ESCORT:_SwitchReportNearbyTargets(ReportTargets) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +self.ReportTargets=ReportTargets +if self.ReportTargets then +if not self.ReportTargetsScheduler then +self.ReportTargetsScheduler:Schedule(self,self._ReportTargetsScheduler,{},1,30) +end +else +self.ReportTargetsScheduler:Remove(self.ReportTargetsSchedulerID) +self.ReportTargetsScheduler=nil +end +end +function ESCORT:_ScanTargets(ScanDuration) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +self.FollowScheduler:Stop(self.FollowSchedule) +if EscortGroup:IsHelicopter()then +EscortGroup:PushTask( +EscortGroup:TaskControlled( +EscortGroup:TaskOrbitCircle(200,20), +EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) +),1) +elseif EscortGroup:IsAirPlane()then +EscortGroup:PushTask( +EscortGroup:TaskControlled( +EscortGroup:TaskOrbitCircle(1000,500), +EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) +),1) +end +EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortClient) +if self.EscortMode==ESCORT.MODE.FOLLOW then +self.FollowScheduler:Start(self.FollowSchedule) +end +end +function _Resume(EscortGroup) +env.info('_Resume') +local Escort=EscortGroup:GetState(EscortGroup,"Escort") +env.info("EscortMode = "..Escort.EscortMode) +if Escort.EscortMode==ESCORT.MODE.FOLLOW then +Escort:JoinUpAndFollow(EscortGroup,Escort.EscortClient,Escort.Distance) +end +end +function ESCORT:_AttackTarget(DetectedItem) +local EscortGroup=self.EscortGroup +self:F(EscortGroup) +local EscortClient=self.EscortClient +self.FollowScheduler:Stop(self.FollowSchedule) +if EscortGroup:IsAir()then +EscortGroup:OptionROEOpenFire() +EscortGroup:OptionROTPassiveDefense() +EscortGroup:SetState(EscortGroup,"Escort",self) +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit) +end +end,Tasks +) +Tasks[#Tasks+1]=EscortGroup:TaskFunction("_Resume",{"''"}) +EscortGroup:SetTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +else +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50) +end +end,Tasks +) +EscortGroup:SetTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +end +EscortGroup:MessageToClient("Engaging Designated Unit!",10,EscortClient) +end +function ESCORT:_AssistTarget(EscortGroupAttack,DetectedItem) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +self.FollowScheduler:Stop(self.FollowSchedule) +if EscortGroupAttack:IsAir()then +EscortGroupAttack:OptionROEOpenFire() +EscortGroupAttack:OptionROTVertical() +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroupAttack:TaskAttackUnit(DetectedUnit) +end +end,Tasks +) +Tasks[#Tasks+1]=EscortGroupAttack:TaskOrbitCircle(500,350) +EscortGroupAttack:SetTask( +EscortGroupAttack:TaskCombo( +Tasks +),1 +) +else +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroupAttack:TaskFireAtPoint(DetectedUnit:GetVec2(),50) +end +end,Tasks +) +EscortGroupAttack:SetTask( +EscortGroupAttack:TaskCombo( +Tasks +),1 +) +end +EscortGroupAttack:MessageToClient("Assisting with the destroying the enemy unit!",10,EscortClient) +end +function ESCORT:_ROE(EscortROEFunction,EscortROEMessage) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +pcall(function()EscortROEFunction()end) +EscortGroup:MessageToClient(EscortROEMessage,10,EscortClient) +end +function ESCORT:_ROT(EscortROTFunction,EscortROTMessage) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +pcall(function()EscortROTFunction()end) +EscortGroup:MessageToClient(EscortROTMessage,10,EscortClient) +end +function ESCORT:_ResumeMission(WayPoint) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +self.FollowScheduler:Stop(self.FollowSchedule) +local WayPoints=EscortGroup:GetTaskRoute() +self:T(WayPoint,WayPoints) +for WayPointIgnore=1,WayPoint do +table.remove(WayPoints,1) +end +SCHEDULER:New(EscortGroup,EscortGroup.SetTask,{EscortGroup:TaskRoute(WayPoints)},1) +EscortGroup:MessageToClient("Resuming mission from waypoint "..WayPoint..".",10,EscortClient) +end +function ESCORT:RegisterRoute() +self:F() +local EscortGroup=self.EscortGroup +local TaskPoints=EscortGroup:GetTaskRoute() +self:T(TaskPoints) +return TaskPoints +end +function ESCORT:_FollowScheduler() +self:F({self.FollowDistance}) +self:T({self.EscortClient.UnitName,self.EscortGroup.GroupName}) +if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then +local ClientUnit=self.EscortClient:GetClientGroupUnit() +local GroupUnit=self.EscortGroup:GetUnit(1) +local FollowDistance=self.FollowDistance +self:T({ClientUnit.UnitName,GroupUnit.UnitName}) +if self.CT1==0 and self.GT1==0 then +self.CV1=ClientUnit:GetVec3() +self:T({"self.CV1",self.CV1}) +self.CT1=timer.getTime() +self.GV1=GroupUnit:GetVec3() +self.GT1=timer.getTime() +else +local CT1=self.CT1 +local CT2=timer.getTime() +local CV1=self.CV1 +local CV2=ClientUnit:GetVec3() +self.CT1=CT2 +self.CV1=CV2 +local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5 +local CT=CT2-CT1 +local CS=(3600/CT)*(CD/1000) +self:T2({"Client:",CS,CD,CT,CV2,CV1,CT2,CT1}) +local GT1=self.GT1 +local GT2=timer.getTime() +local GV1=self.GV1 +local GV2=GroupUnit:GetVec3() +self.GT1=GT2 +self.GV1=GV2 +local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5 +local GT=GT2-GT1 +local GS=(3600/GT)*(GD/1000) +self:T2({"Group:",GS,GD,GT,GV2,GV1,GT2,GT1}) +local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z} +local GH2={x=GV2.x,y=CV2.y,z=GV2.z} +local alpha=math.atan2(GV.z,GV.x) +local CVI={x=CV2.x+FollowDistance*math.cos(alpha), +y=GH2.y, +z=CV2.z+FollowDistance*math.sin(alpha), +} +local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z} +local DVu={x=DV.x/FollowDistance,y=DV.y/FollowDistance,z=DV.z/FollowDistance} +local GDV={x=DVu.x*CS*8+CVI.x,y=CVI.y,z=DVu.z*CS*8+CVI.z} +if self.SmokeDirectionVector==true then +trigger.action.smoke(GDV,trigger.smokeColor.Red) +end +self:T2({"CV2:",CV2}) +self:T2({"CVI:",CVI}) +self:T2({"GDV:",GDV}) +local CatchUpDistance=((GDV.x-GV2.x)^2+(GDV.y-GV2.y)^2+(GDV.z-GV2.z)^2)^0.5 +local Time=10 +local CatchUpSpeed=(CatchUpDistance-(CS*8.4))/Time +local Speed=CS+CatchUpSpeed +if Speed<0 then +Speed=0 +end +self:T({"Client Speed, Escort Speed, Speed, FollowDistance, Time:",CS,GS,Speed,FollowDistance,Time}) +self.EscortGroup:RouteToVec3(GDV,Speed/3.6) +end +return true +end +return false +end +function ESCORT:_ReportTargetsScheduler() +self:F(self.EscortGroup:GetName()) +if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then +local EscortGroupName=self.EscortGroup:GetName() +self.EscortMenuAttackNearbyTargets:RemoveSubMenus() +if self.EscortMenuTargetAssistance then +self.EscortMenuTargetAssistance:RemoveSubMenus() +end +local DetectedItems=self.Detection:GetDetectedItems() +self:F(DetectedItems) +local DetectedTargets=false +local DetectedMsgs={} +for ClientEscortGroupName,EscortGroupData in pairs(self.EscortClient._EscortGroups)do +local ClientEscortTargets=EscortGroupData.Detection +for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do +self:F({DetectedItemIndex,DetectedItem}) +local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,EscortGroupData.EscortGroup,_DATABASE:GetPlayerSettings(self.EscortClient:GetPlayerName())) +if ClientEscortGroupName==EscortGroupName then +local DetectedMsg=DetectedItemReportSummary:Text("\n") +DetectedMsgs[#DetectedMsgs+1]=DetectedMsg +self:T(DetectedMsg) +MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(), +DetectedMsg, +self.EscortMenuAttackNearbyTargets, +ESCORT._AttackTarget, +self, +DetectedItem +) +else +if self.EscortMenuTargetAssistance then +local DetectedMsg=DetectedItemReportSummary:Text("\n") +self:T(DetectedMsg) +local MenuTargetAssistance=MENU_GROUP:New(self.EscortClient:GetGroup(),EscortGroupData.EscortName,self.EscortMenuTargetAssistance) +MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(), +DetectedMsg, +MenuTargetAssistance, +ESCORT._AssistTarget, +self, +EscortGroupData.EscortGroup, +DetectedItem +) +end +end +DetectedTargets=true +end +end +self:F(DetectedMsgs) +if DetectedTargets then +self.EscortGroup:MessageToClient("Reporting detected targets:\n"..table.concat(DetectedMsgs,"\n"),20,self.EscortClient) +else +self.EscortGroup:MessageToClient("No targets detected.",10,self.EscortClient) +end +return true +end +return false +end +MISSILETRAINER={ +ClassName="MISSILETRAINER", +TrackingMissiles={}, +} +function MISSILETRAINER._Alive(Client,self) +if self.Briefing then +Client:Message(self.Briefing,15,"Trainer") +end +if self.MenusOnOff==true then +Client:Message("Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).",15,"Trainer") +Client.MainMenu=MENU_GROUP:New(Client:GetGroup(),"Missile Trainer",nil) +Client.MenuMessages=MENU_GROUP:New(Client:GetGroup(),"Messages",Client.MainMenu) +Client.MenuOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages On",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=true}) +Client.MenuOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages Off",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=false}) +Client.MenuTracking=MENU_GROUP:New(Client:GetGroup(),"Tracking",Client.MainMenu) +Client.MenuTrackingToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=true}) +Client.MenuTrackingToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=false}) +Client.MenuTrackOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking On",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=true}) +Client.MenuTrackOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking Off",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=false}) +Client.MenuTrackIncrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Increase",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=-1}) +Client.MenuTrackDecrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Decrease",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=1}) +Client.MenuAlerts=MENU_GROUP:New(Client:GetGroup(),"Alerts",Client.MainMenu) +Client.MenuAlertsToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=true}) +Client.MenuAlertsToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=false}) +Client.MenuHitsOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=true}) +Client.MenuHitsOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=false}) +Client.MenuLaunchesOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=true}) +Client.MenuLaunchesOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=false}) +Client.MenuDetails=MENU_GROUP:New(Client:GetGroup(),"Details",Client.MainMenu) +Client.MenuDetailsDistanceOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=true}) +Client.MenuDetailsDistanceOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=false}) +Client.MenuDetailsBearingOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=true}) +Client.MenuDetailsBearingOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=false}) +Client.MenuDistance=MENU_GROUP:New(Client:GetGroup(),"Set distance to plane",Client.MainMenu) +Client.MenuDistance50=MENU_GROUP_COMMAND:New(Client:GetGroup(),"50 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=50/1000}) +Client.MenuDistance100=MENU_GROUP_COMMAND:New(Client:GetGroup(),"100 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=100/1000}) +Client.MenuDistance150=MENU_GROUP_COMMAND:New(Client:GetGroup(),"150 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=150/1000}) +Client.MenuDistance200=MENU_GROUP_COMMAND:New(Client:GetGroup(),"200 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=200/1000}) +else +if Client.MainMenu then +Client.MainMenu:Remove() +end +end +local ClientID=Client:GetID() +self:T(ClientID) +if not self.TrackingMissiles[ClientID]then +self.TrackingMissiles[ClientID]={} +end +self.TrackingMissiles[ClientID].Client=Client +if not self.TrackingMissiles[ClientID].MissileData then +self.TrackingMissiles[ClientID].MissileData={} +end +end +function MISSILETRAINER:New(Distance,Briefing) +local self=BASE:Inherit(self,BASE:New()) +self:F(Distance) +if Briefing then +self.Briefing=Briefing +end +self.Schedulers={} +self.SchedulerID=0 +self.MessageInterval=2 +self.MessageLastTime=timer.getTime() +self.Distance=Distance/1000 +self:HandleEvent(EVENTS.Shot) +self.DBClients=SET_CLIENT:New():FilterStart() +self.DBClients:ForEachClient( +function(Client) +self:F("ForEach:"..Client.UnitName) +Client:Alive(self._Alive,self) +end +) +self.MessagesOnOff=true +self.TrackingToAll=false +self.TrackingOnOff=true +self.TrackingFrequency=3 +self.AlertsToAll=true +self.AlertsHitsOnOff=true +self.AlertsLaunchesOnOff=true +self.DetailsRangeOnOff=true +self.DetailsBearingOnOff=true +self.MenusOnOff=true +self.TrackingMissiles={} +self.TrackingScheduler=SCHEDULER:New(self,self._TrackMissiles,{},0.5,0.05,0) +return self +end +function MISSILETRAINER:InitMessagesOnOff(MessagesOnOff) +self:F(MessagesOnOff) +self.MessagesOnOff=MessagesOnOff +if self.MessagesOnOff==true then +MESSAGE:New("Messages ON",15,"Menu"):ToAll() +else +MESSAGE:New("Messages OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitTrackingToAll(TrackingToAll) +self:F(TrackingToAll) +self.TrackingToAll=TrackingToAll +if self.TrackingToAll==true then +MESSAGE:New("Missile tracking to all players ON",15,"Menu"):ToAll() +else +MESSAGE:New("Missile tracking to all players OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitTrackingOnOff(TrackingOnOff) +self:F(TrackingOnOff) +self.TrackingOnOff=TrackingOnOff +if self.TrackingOnOff==true then +MESSAGE:New("Missile tracking ON",15,"Menu"):ToAll() +else +MESSAGE:New("Missile tracking OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitTrackingFrequency(TrackingFrequency) +self:F(TrackingFrequency) +self.TrackingFrequency=self.TrackingFrequency+TrackingFrequency +if self.TrackingFrequency<0.5 then +self.TrackingFrequency=0.5 +end +if self.TrackingFrequency then +MESSAGE:New("Missile tracking frequency is "..self.TrackingFrequency.." seconds.",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitAlertsToAll(AlertsToAll) +self:F(AlertsToAll) +self.AlertsToAll=AlertsToAll +if self.AlertsToAll==true then +MESSAGE:New("Alerts to all players ON",15,"Menu"):ToAll() +else +MESSAGE:New("Alerts to all players OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitAlertsHitsOnOff(AlertsHitsOnOff) +self:F(AlertsHitsOnOff) +self.AlertsHitsOnOff=AlertsHitsOnOff +if self.AlertsHitsOnOff==true then +MESSAGE:New("Alerts Hits ON",15,"Menu"):ToAll() +else +MESSAGE:New("Alerts Hits OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitAlertsLaunchesOnOff(AlertsLaunchesOnOff) +self:F(AlertsLaunchesOnOff) +self.AlertsLaunchesOnOff=AlertsLaunchesOnOff +if self.AlertsLaunchesOnOff==true then +MESSAGE:New("Alerts Launches ON",15,"Menu"):ToAll() +else +MESSAGE:New("Alerts Launches OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitRangeOnOff(DetailsRangeOnOff) +self:F(DetailsRangeOnOff) +self.DetailsRangeOnOff=DetailsRangeOnOff +if self.DetailsRangeOnOff==true then +MESSAGE:New("Range display ON",15,"Menu"):ToAll() +else +MESSAGE:New("Range display OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitBearingOnOff(DetailsBearingOnOff) +self:F(DetailsBearingOnOff) +self.DetailsBearingOnOff=DetailsBearingOnOff +if self.DetailsBearingOnOff==true then +MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll() +else +MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitMenusOnOff(MenusOnOff) +self:F(MenusOnOff) +self.MenusOnOff=MenusOnOff +if self.MenusOnOff==true then +MESSAGE:New("Menus are ENABLED (only when a player rejoins a slot)",15,"Menu"):ToAll() +else +MESSAGE:New("Menus are DISABLED",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER._MenuMessages(MenuParameters) +local self=MenuParameters.MenuSelf +if MenuParameters.MessagesOnOff~=nil then +self:InitMessagesOnOff(MenuParameters.MessagesOnOff) +end +if MenuParameters.TrackingToAll~=nil then +self:InitTrackingToAll(MenuParameters.TrackingToAll) +end +if MenuParameters.TrackingOnOff~=nil then +self:InitTrackingOnOff(MenuParameters.TrackingOnOff) +end +if MenuParameters.TrackingFrequency~=nil then +self:InitTrackingFrequency(MenuParameters.TrackingFrequency) +end +if MenuParameters.AlertsToAll~=nil then +self:InitAlertsToAll(MenuParameters.AlertsToAll) +end +if MenuParameters.AlertsHitsOnOff~=nil then +self:InitAlertsHitsOnOff(MenuParameters.AlertsHitsOnOff) +end +if MenuParameters.AlertsLaunchesOnOff~=nil then +self:InitAlertsLaunchesOnOff(MenuParameters.AlertsLaunchesOnOff) +end +if MenuParameters.DetailsRangeOnOff~=nil then +self:InitRangeOnOff(MenuParameters.DetailsRangeOnOff) +end +if MenuParameters.DetailsBearingOnOff~=nil then +self:InitBearingOnOff(MenuParameters.DetailsBearingOnOff) +end +if MenuParameters.Distance~=nil then +self.Distance=MenuParameters.Distance +MESSAGE:New("Hit detection distance set to "..(self.Distance*1000).." meters",15,"Menu"):ToAll() +end +end +function MISSILETRAINER:OnEventShot(EVentData) +self:F({EVentData}) +local TrainerSourceDCSUnit=EVentData.IniDCSUnit +local TrainerSourceDCSUnitName=EVentData.IniDCSUnitName +local TrainerWeapon=EVentData.Weapon +local TrainerWeaponName=EVentData.WeaponName +self:T("Missile Launched = "..TrainerWeaponName) +local TrainerTargetDCSUnit=TrainerWeapon:getTarget() +if TrainerTargetDCSUnit then +local TrainerTargetDCSUnitName=Unit.getName(TrainerTargetDCSUnit) +local TrainerTargetSkill=_DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill +self:T(TrainerTargetDCSUnitName) +local Client=self.DBClients:FindClient(TrainerTargetDCSUnitName) +if Client then +local TrainerSourceUnit=UNIT:Find(TrainerSourceDCSUnit) +local TrainerTargetUnit=UNIT:Find(TrainerTargetDCSUnit) +if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then +local Message=MESSAGE:New( +string.format("%s launched a %s", +TrainerSourceUnit:GetTypeName(), +TrainerWeaponName +)..self:_AddRange(Client,TrainerWeapon)..self:_AddBearing(Client,TrainerWeapon),5,"Launch Alert") +if self.AlertsToAll then +Message:ToAll() +else +Message:ToClient(Client) +end +end +local ClientID=Client:GetID() +self:T(ClientID) +local MissileData={} +MissileData.TrainerSourceUnit=TrainerSourceUnit +MissileData.TrainerWeapon=TrainerWeapon +MissileData.TrainerTargetUnit=TrainerTargetUnit +MissileData.TrainerWeaponTypeName=TrainerWeapon:getTypeName() +MissileData.TrainerWeaponLaunched=true +table.insert(self.TrackingMissiles[ClientID].MissileData,MissileData) +end +else +if(TrainerWeapon:getTypeName()=="9M311")then +SCHEDULER:New(TrainerWeapon,TrainerWeapon.destroy,{},1) +else +end +end +end +function MISSILETRAINER:_AddRange(Client,TrainerWeapon) +local RangeText="" +if self.DetailsRangeOnOff then +local PositionMissile=TrainerWeapon:getPoint() +local TargetVec3=Client:GetVec3() +local Range=((PositionMissile.x-TargetVec3.x)^2+ +(PositionMissile.y-TargetVec3.y)^2+ +(PositionMissile.z-TargetVec3.z)^2 +)^0.5/1000 +RangeText=string.format(", at %4.2fkm",Range) +end +return RangeText +end +function MISSILETRAINER:_AddBearing(Client,TrainerWeapon) +local BearingText="" +if self.DetailsBearingOnOff then +local PositionMissile=TrainerWeapon:getPoint() +local TargetVec3=Client:GetVec3() +self:T2({TargetVec3,PositionMissile}) +local DirectionVector={x=PositionMissile.x-TargetVec3.x,y=PositionMissile.y-TargetVec3.y,z=PositionMissile.z-TargetVec3.z} +local DirectionRadians=math.atan2(DirectionVector.z,DirectionVector.x) +if DirectionRadians<0 then +DirectionRadians=DirectionRadians+2*math.pi +end +local DirectionDegrees=DirectionRadians*180/math.pi +BearingText=string.format(", %d degrees",DirectionDegrees) +end +return BearingText +end +function MISSILETRAINER:_TrackMissiles() +self:F2() +local ShowMessages=false +if self.MessagesOnOff and self.MessageLastTime+self.TrackingFrequency<=timer.getTime()then +self.MessageLastTime=timer.getTime() +ShowMessages=true +end +for ClientDataID,ClientData in pairs(self.TrackingMissiles)do +local Client=ClientData.Client +if Client and Client:IsAlive()then +for MissileDataID,MissileData in pairs(ClientData.MissileData)do +self:T3(MissileDataID) +local TrainerSourceUnit=MissileData.TrainerSourceUnit +local TrainerWeapon=MissileData.TrainerWeapon +local TrainerTargetUnit=MissileData.TrainerTargetUnit +local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName +local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched +if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then +local PositionMissile=TrainerWeapon:getPosition().p +local TargetVec3=Client:GetVec3() +local Distance=((PositionMissile.x-TargetVec3.x)^2+ +(PositionMissile.y-TargetVec3.y)^2+ +(PositionMissile.z-TargetVec3.z)^2 +)^0.5/1000 +if Distance<=self.Distance then +TrainerWeapon:destroy() +if self.MessagesOnOff==true and self.AlertsHitsOnOff==true then +self:T("killed") +local Message=MESSAGE:New( +string.format("%s launched by %s killed %s", +TrainerWeapon:getTypeName(), +TrainerSourceUnit:GetTypeName(), +TrainerTargetUnit:GetPlayerName() +),15,"Hit Alert") +if self.AlertsToAll==true then +Message:ToAll() +else +Message:ToClient(Client) +end +MissileData=nil +table.remove(ClientData.MissileData,MissileDataID) +self:T(ClientData.MissileData) +end +end +else +if not(TrainerWeapon and TrainerWeapon:isExist())then +if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then +local Message=MESSAGE:New( +string.format("%s launched by %s self destructed!", +TrainerWeaponTypeName, +TrainerSourceUnit:GetTypeName() +),5,"Tracking") +if self.AlertsToAll==true then +Message:ToAll() +else +Message:ToClient(Client) +end +end +MissileData=nil +table.remove(ClientData.MissileData,MissileDataID) +self:T(ClientData.MissileData) +end +end +end +else +self.TrackingMissiles[ClientDataID]=nil +end +end +if ShowMessages==true and self.MessagesOnOff==true and self.TrackingOnOff==true then +for ClientDataID,ClientData in pairs(self.TrackingMissiles)do +local Client=ClientData.Client +ClientData.MessageToClient="" +ClientData.MessageToAll="" +for TrackingDataID,TrackingData in pairs(self.TrackingMissiles)do +for MissileDataID,MissileData in pairs(TrackingData.MissileData)do +local TrainerSourceUnit=MissileData.TrainerSourceUnit +local TrainerWeapon=MissileData.TrainerWeapon +local TrainerTargetUnit=MissileData.TrainerTargetUnit +local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName +local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched +if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then +if ShowMessages==true then +local TrackingTo +TrackingTo=string.format(" -> %s", +TrainerWeaponTypeName +) +if ClientDataID==TrackingDataID then +if ClientData.MessageToClient==""then +ClientData.MessageToClient="Missiles to You:\n" +end +ClientData.MessageToClient=ClientData.MessageToClient..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).."\n" +else +if self.TrackingToAll==true then +if ClientData.MessageToAll==""then +ClientData.MessageToAll="Missiles to other Players:\n" +end +ClientData.MessageToAll=ClientData.MessageToAll..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).." ( "..TrainerTargetUnit:GetPlayerName().." )\n" +end +end +end +end +end +end +if ClientData.MessageToClient~=""or ClientData.MessageToAll~=""then +local Message=MESSAGE:New(ClientData.MessageToClient..ClientData.MessageToAll,1,"Tracking"):ToClient(Client) +end +end +end +return true +end +ATC_GROUND={ +ClassName="ATC_GROUND", +SetClient=nil, +Airbases=nil, +AirbaseNames=nil, +} +function ATC_GROUND:New(Airbases,AirbaseList) +local self=BASE:Inherit(self,BASE:New()) +self:T({self.ClassName,Airbases}) +self.Airbases=Airbases +self.AirbaseList=AirbaseList +self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart() +for AirbaseID,Airbase in pairs(self.Airbases)do +if Airbase.ZoneBoundary then +Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary "..AirbaseID,Airbase.ZoneBoundary) +else +Airbase.ZoneBoundary=_DATABASE:FindAirbase(AirbaseID):GetZone() +end +Airbase.ZoneRunways={} +if Airbase.PointsRunways then +for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do +Airbase.ZoneRunways[PointsRunwayID]=ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID,PointsRunway) +end +end +Airbase.Monitor=self.AirbaseList and false or true +end +for AirbaseID,AirbaseName in pairs(self.AirbaseList or{})do +self.Airbases[AirbaseName].Monitor=true +end +self.SetClient:ForEachClient( +function(Client) +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +Client:SetState(self,"IsOffRunway",false) +Client:SetState(self,"OffRunwayWarnings",0) +Client:SetState(self,"Taxi",false) +end +) +SSB=USERFLAG:New("SSB") +SSB:Set(100) +return self +end +function ATC_GROUND:SmokeRunways(SmokeColor) +for AirbaseID,Airbase in pairs(self.Airbases)do +for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do +Airbase.ZoneRunways[PointsRunwayID]:SmokeZone(SmokeColor) +end +end +end +function ATC_GROUND:SetKickSpeed(KickSpeed,Airbase) +if not Airbase then +self.KickSpeed=KickSpeed +else +self.Airbases[Airbase].KickSpeed=KickSpeed +end +return self +end +function ATC_GROUND:SetKickSpeedKmph(KickSpeed,Airbase) +self:SetKickSpeed(UTILS.KmphToMps(KickSpeed),Airbase) +return self +end +function ATC_GROUND:SetKickSpeedMiph(KickSpeedMiph,Airbase) +self:SetKickSpeed(UTILS.MiphToMps(KickSpeedMiph),Airbase) +return self +end +function ATC_GROUND:SetMaximumKickSpeed(MaximumKickSpeed,Airbase) +if not Airbase then +self.MaximumKickSpeed=MaximumKickSpeed +else +self.Airbases[Airbase].MaximumKickSpeed=MaximumKickSpeed +end +return self +end +function ATC_GROUND:SetMaximumKickSpeedKmph(MaximumKickSpeed,Airbase) +self:SetMaximumKickSpeed(UTILS.KmphToMps(MaximumKickSpeed),Airbase) +return self +end +function ATC_GROUND:SetMaximumKickSpeedMiph(MaximumKickSpeedMiph,Airbase) +self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase) +return self +end +function ATC_GROUND:_AirbaseMonitor() +self.SetClient:ForEachClient( +function(Client) +if Client:IsAlive()then +local IsOnGround=Client:InAir()==false +for AirbaseID,AirbaseMeta in pairs(self.Airbases)do +self:T(AirbaseID,AirbaseMeta.KickSpeed) +if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then +local NotInRunwayZone=true +for ZoneRunwayID,ZoneRunway in pairs(AirbaseMeta.ZoneRunways)do +NotInRunwayZone=(Client:IsNotInZone(ZoneRunway)==true)and NotInRunwayZone or false +end +if NotInRunwayZone then +if IsOnGround then +local Taxi=Client:GetState(self,"Taxi") +self:T(Taxi) +if Taxi==false then +local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed) +Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is ".. +Velocity:ToString(),20,"ATC") +Client:SetState(self,"Taxi",true) +end +local Velocity=VELOCITY_POSITIONABLE:New(Client) +local IsAboveRunway=Client:IsAboveRunway() +self:T(IsAboveRunway,IsOnGround) +if IsOnGround then +local Speeding=false +if AirbaseMeta.MaximumKickSpeed then +if Velocity:Get()>AirbaseMeta.MaximumKickSpeed then +Speeding=true +end +else +if Velocity:Get()>self.MaximumKickSpeed then +Speeding=true +end +end +if Speeding==true then +MESSAGE:New("Penalty! Player "..Client:GetPlayerName().. +" has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() +Client:Destroy() +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +end +end +if IsOnGround then +local Speeding=false +if AirbaseMeta.KickSpeed then +if Velocity:Get()>AirbaseMeta.KickSpeed then +Speeding=true +end +else +if Velocity:Get()>self.KickSpeed then +Speeding=true +end +end +if Speeding==true then +local IsSpeeding=Client:GetState(self,"Speeding") +if IsSpeeding==true then +local SpeedingWarnings=Client:GetState(self,"Warnings") +self:T(SpeedingWarnings) +if SpeedingWarnings<=3 then +Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is ".. +Velocity:ToString(),5,"ATC") +Client:SetState(self,"Warnings",SpeedingWarnings+1) +else +MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() +Client:Destroy() +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +end +else +Client:Message("Attention! You are speeding on the taxiway, slow down! Your speed is ".. +Velocity:ToString(),5,"ATC") +Client:SetState(self,"Speeding",true) +Client:SetState(self,"Warnings",1) +end +else +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +end +end +if IsOnGround and not IsAboveRunway then +local IsOffRunway=Client:GetState(self,"IsOffRunway") +if IsOffRunway==true then +local OffRunwayWarnings=Client:GetState(self,"OffRunwayWarnings") +self:T(OffRunwayWarnings) +if OffRunwayWarnings<=3 then +Client:Message("Warning "..OffRunwayWarnings.."/3! Airbase traffic rule violation! Get back on the taxi immediately!",5,"ATC") +Client:SetState(self,"OffRunwayWarnings",OffRunwayWarnings+1) +else +MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() +Client:Destroy() +Client:SetState(self,"IsOffRunway",false) +Client:SetState(self,"OffRunwayWarnings",0) +end +else +Client:Message("Attention! You are off the taxiway. Get back on the taxiway immediately!",5,"ATC") +Client:SetState(self,"IsOffRunway",true) +Client:SetState(self,"OffRunwayWarnings",1) +end +else +Client:SetState(self,"IsOffRunway",false) +Client:SetState(self,"OffRunwayWarnings",0) +end +end +else +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +Client:SetState(self,"IsOffRunway",false) +Client:SetState(self,"OffRunwayWarnings",0) +local Taxi=Client:GetState(self,"Taxi") +if Taxi==true then +Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC") +Client:SetState(self,"Taxi",false) +end +end +end +end +else +Client:SetState(self,"Taxi",false) +end +end +) +return true +end +ATC_GROUND_UNIVERSAL={ +ClassName="ATC_GROUND_UNIVERSAL", +Version="0.0.2", +SetClient=nil, +Airbases=nil, +AirbaseList=nil, +KickSpeed=nil, +} +function ATC_GROUND_UNIVERSAL:New(AirbaseList) +local self=BASE:Inherit(self,BASE:New()) +self:T({self.ClassName}) +self.Airbases={} +self.AirbaseList=AirbaseList +if not self.AirbaseList then +self.AirbaseList={} +for _name,_base in pairs(_DATABASE.AIRBASES)do +if _base and _base.isAirdrome==true then +self.AirbaseList[_name]=_name +self.Airbases[_name]={} +end +end +else +for _,_name in pairs(AirbaseList)do +local airbase=_DATABASE:FindAirbase(_name) +if airbase and(airbase.isAirdrome==true)then +self.Airbases[_name]={} +end +end +end +self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart() +for AirbaseID,Airbase in pairs(self.Airbases)do +if Airbase.ZoneBoundary then +Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary "..AirbaseID,Airbase.ZoneBoundary) +else +Airbase.ZoneBoundary=_DATABASE:FindAirbase(AirbaseID):GetZone() +end +Airbase.ZoneRunways=AIRBASE:FindByName(AirbaseID):GetRunways() +Airbase.Monitor=self.AirbaseList and false or true +end +for AirbaseID,AirbaseName in pairs(self.AirbaseList or{})do +self.Airbases[AirbaseName].Monitor=true +end +self.SetClient:ForEachClient( +function(Client) +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +Client:SetState(self,"IsOffRunway",false) +Client:SetState(self,"OffRunwayWarnings",0) +Client:SetState(self,"Taxi",false) +end +) +SSB=USERFLAG:New("SSB") +SSB:Set(100) +self.KickSpeed=UTILS.KnotsToMps(10) +self:SetMaximumKickSpeedMiph(30) +return self +end +function ATC_GROUND_UNIVERSAL:SetAirbaseBoundaries(Airbase,Zone) +self.Airbases[Airbase].ZoneBoundary=Zone +return self +end +function ATC_GROUND_UNIVERSAL:SmokeRunways(SmokeColor) +local SmokeColor=SmokeColor or SMOKECOLOR.Red +for AirbaseID,Airbase in pairs(self.Airbases)do +if Airbase.ZoneRunways then +for _,_runwaydata in pairs(Airbase.ZoneRunways)do +local runwaydata=_runwaydata +runwaydata.zone:SmokeZone(SmokeColor) +end +end +end +return self +end +function ATC_GROUND_UNIVERSAL:DrawRunways(Color) +local Color=Color or{1,0,0} +for AirbaseID,Airbase in pairs(self.Airbases)do +if Airbase.ZoneRunways then +for _,_runwaydata in pairs(Airbase.ZoneRunways)do +local runwaydata=_runwaydata +runwaydata.zone:DrawZone(-1,Color) +end +end +end +return self +end +function ATC_GROUND_UNIVERSAL:DrawBoundaries(Color) +local Color=Color or{1,0,0} +for AirbaseID,Airbase in pairs(self.Airbases)do +if Airbase.ZoneBoundary then +Airbase.ZoneBoundary:DrawZone(-1,Color) +end +end +return self +end +function ATC_GROUND_UNIVERSAL:SetKickSpeed(KickSpeed,Airbase) +if not Airbase then +self.KickSpeed=KickSpeed +else +self.Airbases[Airbase].KickSpeed=KickSpeed +end +return self +end +function ATC_GROUND_UNIVERSAL:SetKickSpeedKmph(KickSpeed,Airbase) +self:SetKickSpeed(UTILS.KmphToMps(KickSpeed),Airbase) +return self +end +function ATC_GROUND_UNIVERSAL:SetKickSpeedMiph(KickSpeedMiph,Airbase) +self:SetKickSpeed(UTILS.MiphToMps(KickSpeedMiph),Airbase) +return self +end +function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeed(MaximumKickSpeed,Airbase) +if not Airbase then +self.MaximumKickSpeed=MaximumKickSpeed +else +self.Airbases[Airbase].MaximumKickSpeed=MaximumKickSpeed +end +return self +end +function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedKmph(MaximumKickSpeed,Airbase) +self:SetMaximumKickSpeed(UTILS.KmphToMps(MaximumKickSpeed),Airbase) +return self +end +function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedMiph(MaximumKickSpeedMiph,Airbase) +self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase) +return self +end +function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() +self:I("_AirbaseMonitor") +self.SetClient:ForEachClient( +function(Client) +if Client:IsAlive()then +local IsOnGround=Client:InAir()==false +for AirbaseID,AirbaseMeta in pairs(self.Airbases)do +self:T(AirbaseID,AirbaseMeta.KickSpeed) +if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then +local NotInRunwayZone=true +if AirbaseMeta.ZoneRunways then +for _,_runwaydata in pairs(AirbaseMeta.ZoneRunways)do +local runwaydata=_runwaydata +NotInRunwayZone=(Client:IsNotInZone(_runwaydata.zone)==true)and NotInRunwayZone or false +end +end +if NotInRunwayZone then +local Taxi=Client:GetState(self,"Taxi") +if IsOnGround then +self:T(Taxi) +if Taxi==false then +local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed) +Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is ".. +Velocity:ToString(),20,"ATC") +Client:SetState(self,"Taxi",true) +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +end +local Velocity=VELOCITY_POSITIONABLE:New(Client) +local IsAboveRunway=Client:IsAboveRunway() +self:T({IsAboveRunway,IsOnGround,Velocity:Get()}) +if IsOnGround and not Taxi then +local Speeding=false +if AirbaseMeta.MaximumKickSpeed then +if Velocity:Get()>AirbaseMeta.MaximumKickSpeed then +Speeding=true +end +else +if Velocity:Get()>self.MaximumKickSpeed then +Speeding=true +end +end +if Speeding==true then +Client:SetState(self,"Speeding",true) +local SpeedingWarnings=Client:GetState(self,"Warnings") +Client:SetState(self,"Warnings",SpeedingWarnings+1) +Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is ".. +Velocity:ToString(),5,"ATC") +end +end +if IsOnGround then +local Speeding=false +if AirbaseMeta.KickSpeed then +if Velocity:Get()>AirbaseMeta.KickSpeed then +Speeding=true +end +else +if Velocity:Get()>self.KickSpeed then +Speeding=true +end +end +if Speeding==true then +local IsSpeeding=Client:GetState(self,"Speeding") +if IsSpeeding==true then +local SpeedingWarnings=Client:GetState(self,"Warnings") +self:T(SpeedingWarnings) +if SpeedingWarnings<=3 then +Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is ".. +Velocity:ToString(),5,"ATC") +Client:SetState(self,"Warnings",SpeedingWarnings+1) +else +MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() +Client:Destroy() +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +end +else +Client:Message("Attention! You are speeding on the taxiway, slow down! Your speed is ".. +Velocity:ToString(),5,"ATC") +Client:SetState(self,"Speeding",true) +Client:SetState(self,"Warnings",1) +end +else +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +end +end +if IsOnGround and not IsAboveRunway then +local IsOffRunway=Client:GetState(self,"IsOffRunway") +if IsOffRunway==true then +local OffRunwayWarnings=Client:GetState(self,"OffRunwayWarnings") +self:T(OffRunwayWarnings) +if OffRunwayWarnings<=3 then +Client:Message("Warning "..OffRunwayWarnings.."/3! Airbase traffic rule violation! Get back on the taxi immediately!",5,"ATC") +Client:SetState(self,"OffRunwayWarnings",OffRunwayWarnings+1) +else +MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll() +Client:Destroy() +Client:SetState(self,"IsOffRunway",false) +Client:SetState(self,"OffRunwayWarnings",0) +end +else +Client:Message("Attention! You are off the taxiway. Get back on the taxiway immediately!",5,"ATC") +Client:SetState(self,"IsOffRunway",true) +Client:SetState(self,"OffRunwayWarnings",1) +end +else +Client:SetState(self,"IsOffRunway",false) +Client:SetState(self,"OffRunwayWarnings",0) +end +end +else +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) +Client:SetState(self,"IsOffRunway",false) +Client:SetState(self,"OffRunwayWarnings",0) +local Taxi=Client:GetState(self,"Taxi") +if Taxi==true then +Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC") +Client:SetState(self,"Taxi",false) +end +end +end +end +else +Client:SetState(self,"Taxi",false) +end +end +) +return true +end +function ATC_GROUND_UNIVERSAL:Start(RepeatScanSeconds) +RepeatScanSeconds=RepeatScanSeconds or 0.05 +self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) +return self +end +ATC_GROUND_CAUCASUS={ +ClassName="ATC_GROUND_CAUCASUS", +} +function ATC_GROUND_CAUCASUS:New(AirbaseNames) +local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) +self:SetKickSpeedKmph(50) +self:SetMaximumKickSpeedKmph(150) +return self +end +function ATC_GROUND_CAUCASUS:Start(RepeatScanSeconds) +RepeatScanSeconds=RepeatScanSeconds or 0.05 +self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) +end +ATC_GROUND_NEVADA={ +ClassName="ATC_GROUND_NEVADA", +} +function ATC_GROUND_NEVADA:New(AirbaseNames) +local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) +self:SetKickSpeedKmph(50) +self:SetMaximumKickSpeedKmph(150) +return self +end +function ATC_GROUND_NEVADA:Start(RepeatScanSeconds) +RepeatScanSeconds=RepeatScanSeconds or 0.05 +self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) +end +ATC_GROUND_NORMANDY={ +ClassName="ATC_GROUND_NORMANDY", +} +function ATC_GROUND_NORMANDY:New(AirbaseNames) +local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) +self:SetKickSpeedKmph(40) +self:SetMaximumKickSpeedKmph(100) +return self +end +function ATC_GROUND_NORMANDY:Start(RepeatScanSeconds) +RepeatScanSeconds=RepeatScanSeconds or 0.05 +self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) +end +ATC_GROUND_PERSIANGULF={ +ClassName="ATC_GROUND_PERSIANGULF", +} +function ATC_GROUND_PERSIANGULF:New(AirbaseNames) +local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) +self:SetKickSpeedKmph(50) +self:SetMaximumKickSpeedKmph(150) +end +function ATC_GROUND_PERSIANGULF:Start(RepeatScanSeconds) +RepeatScanSeconds=RepeatScanSeconds or 0.05 +self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) +end +ATC_GROUND_MARIANAISLANDS={ +ClassName="ATC_GROUND_MARIANAISLANDS", +} +function ATC_GROUND_MARIANAISLANDS:New(AirbaseNames) +local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames)) +self:SetKickSpeedKmph(50) +self:SetMaximumKickSpeedKmph(150) +return self +end +function ATC_GROUND_MARIANAISLANDS:Start(RepeatScanSeconds) +RepeatScanSeconds=RepeatScanSeconds or 0.05 +self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds) +end +do +DETECTION_BASE={ +ClassName="DETECTION_BASE", +DetectionSetGroup=nil, +DetectionRange=nil, +DetectedObjects={}, +DetectionRun=0, +DetectedObjectsIdentified={}, +DetectedItems={}, +DetectedItemsByIndex={}, +} +function DETECTION_BASE:New(DetectionSet) +local self=BASE:Inherit(self,FSM:New()) +self.DetectedItemCount=0 +self.DetectedItemMax=0 +self.DetectedItems={} +self.DetectionSet=DetectionSet +self.RefreshTimeInterval=30 +self:InitDetectVisual(nil) +self:InitDetectOptical(nil) +self:InitDetectRadar(nil) +self:InitDetectRWR(nil) +self:InitDetectIRST(nil) +self:InitDetectDLINK(nil) +self:FilterCategories({ +Unit.Category.AIRPLANE, +Unit.Category.GROUND_UNIT, +Unit.Category.HELICOPTER, +Unit.Category.SHIP, +Unit.Category.STRUCTURE +}) +self:SetFriendliesRange(6000) +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Detecting") +self:AddTransition("Detecting","Detect","Detecting") +self:AddTransition("Detecting","Detection","Detecting") +self:AddTransition("Detecting","Detected","Detecting") +self:AddTransition("Detecting","DetectedItem","Detecting") +self:AddTransition("*","Stop","Stopped") +return self +end +do +function DETECTION_BASE:onafterStart(From,Event,To) +self:__Detect(1) +end +function DETECTION_BASE:onafterDetect(From,Event,To) +local DetectDelay=0.15 +self.DetectionCount=0 +self.DetectionRun=0 +self:UnIdentifyAllDetectedObjects() +local DetectionTimeStamp=timer.getTime() +for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects)do +self.DetectedObjects[DetectionObjectName].IsDetected=false +self.DetectedObjects[DetectionObjectName].IsVisible=false +self.DetectedObjects[DetectionObjectName].KnowDistance=nil +self.DetectedObjects[DetectionObjectName].LastTime=nil +self.DetectedObjects[DetectionObjectName].LastPos=nil +self.DetectedObjects[DetectionObjectName].LastVelocity=nil +self.DetectedObjects[DetectionObjectName].Distance=10000000 +end +self.DetectionCount=self:CountAliveRecce() +local DetectionInterval=self.DetectionCount/(self.RefreshTimeInterval-1) +self:ForEachAliveRecce(function(DetectionGroup) +self:__Detection(DetectDelay,DetectionGroup,DetectionTimeStamp) +DetectDelay=DetectDelay+DetectionInterval +end) +self:__Detect(-self.RefreshTimeInterval) +end +function DETECTION_BASE:CountAliveRecce() +return self.DetectionSet:CountAlive() +end +function DETECTION_BASE:ForEachAliveRecce(IteratorFunction,...) +self:F2(arg) +self.DetectionSet:ForEachGroupAlive(IteratorFunction,arg) +return self +end +function DETECTION_BASE:onafterDetection(From,Event,To,Detection,DetectionTimeStamp) +self:T({DetectedObjects=self.DetectedObjects}) +self.DetectionRun=self.DetectionRun+1 +local HasDetectedObjects=false +if Detection and Detection:IsAlive()then +self:T({"DetectionGroup is Alive",Detection:GetName()}) +local DetectionGroupName=Detection:GetName() +local DetectionUnit=Detection:GetFirstUnitAlive() +local DetectedUnits={} +local DetectedTargets=DetectionUnit:GetDetectedTargets( +self.DetectVisual, +self.DetectOptical, +self.DetectRadar, +self.DetectIRST, +self.DetectRWR, +self.DetectDLINK +) +for DetectionObjectID,Detection in pairs(DetectedTargets or{})do +local DetectedObject=Detection.object +if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then +local DetectedObjectName=DetectedObject:getName() +if not self.DetectedObjects[DetectedObjectName]then +self.DetectedObjects[DetectedObjectName]=self.DetectedObjects[DetectedObjectName]or{} +self.DetectedObjects[DetectedObjectName].Name=DetectedObjectName +self.DetectedObjects[DetectedObjectName].Object=DetectedObject +end +end +end +for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects or{})do +local DetectedObject=DetectedObjectData.Object +if DetectedObject:isExist()then +local TargetIsDetected,TargetIsVisible,TargetKnowType,TargetKnowDistance,TargetLastTime,TargetLastPos,TargetLastVelocity=DetectionUnit:IsTargetDetected( +DetectedObject, +self.DetectVisual, +self.DetectOptical, +self.DetectRadar, +self.DetectIRST, +self.DetectRWR, +self.DetectDLINK +) +local DetectionAccepted=true +local DetectedObjectName=DetectedObject:getName() +local DetectedObjectType=DetectedObject:getTypeName() +local DetectedObjectVec3=DetectedObject:getPoint() +local DetectedObjectVec2={x=DetectedObjectVec3.x,y=DetectedObjectVec3.z} +local DetectionGroupVec3=Detection:GetVec3()or{x=0,y=0,z=0} +local DetectionGroupVec2={x=DetectionGroupVec3.x,y=DetectionGroupVec3.z} +local Distance=((DetectedObjectVec3.x-DetectionGroupVec3.x)^2+ +(DetectedObjectVec3.y-DetectionGroupVec3.y)^2+ +(DetectedObjectVec3.z-DetectionGroupVec3.z)^2 +)^0.5/1000 +local DetectedUnitCategory=DetectedObject:getDesc().category +DetectionAccepted=self._.FilterCategories[DetectedUnitCategory]~=nil and DetectionAccepted or false +if self.AcceptRange and Distance*1000>self.AcceptRange then +DetectionAccepted=false +end +if self.AcceptZones then +local AnyZoneDetection=false +for AcceptZoneID,AcceptZone in pairs(self.AcceptZones)do +local AcceptZone=AcceptZone +if AcceptZone:IsVec2InZone(DetectedObjectVec2)then +AnyZoneDetection=true +end +end +if not AnyZoneDetection then +DetectionAccepted=false +end +end +if self.RejectZones then +for RejectZoneID,RejectZone in pairs(self.RejectZones)do +local RejectZone=RejectZone +if RejectZone:IsVec2InZone(DetectedObjectVec2)==true then +DetectionAccepted=false +end +end +end +if self.RadarBlur then +MESSAGE:New("Radar Blur",10):ToLogIf(self.debug):ToAllIf(self.verbose) +local minheight=self.RadarBlurMinHeight or 250 +local thresheight=self.RadarBlurThresHeight or 90 +local thresblur=self.RadarBlurThresBlur or 85 +local dist=math.floor(Distance) +if dist<=self.RadarBlurClosing then +thresheight=(((dist*dist)/self.RadarBlurClosingSquare)*thresheight) +thresblur=(((dist*dist)/self.RadarBlurClosingSquare)*thresblur) +end +local fheight=math.floor(math.random(1,10000)/100) +local fblur=math.floor(math.random(1,10000)/100) +local unit=UNIT:FindByName(DetectedObjectName) +if unit and unit:IsAlive()then +local AGL=unit:GetAltitude(true) +MESSAGE:New("Unit "..DetectedObjectName.." is at "..math.floor(AGL).."m. Distance "..math.floor(Distance).."km.",10):ToLogIf(self.debug):ToAllIf(self.verbose) +MESSAGE:New(string.format("fheight = %d/%d | fblur = %d/%d",fheight,thresheight,fblur,thresblur),10):ToLogIf(self.debug):ToAllIf(self.verbose) +if fblur>thresblur then DetectionAccepted=false end +if AGL<=minheight and fheight0 and self.DetectionRun==self.DetectionCount then +for DetectedObjectName,DetectedObject in pairs(self.DetectedObjects)do +if self.DetectedObjects[DetectedObjectName].IsDetected==true and self.DetectedObjects[DetectedObjectName].DetectionTimeStamp+300<=DetectionTimeStamp then +self.DetectedObjects[DetectedObjectName].IsDetected=false +end +end +self:CreateDetectionItems() +for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do +self:UpdateDetectedItemDetection(DetectedItem) +self:CleanDetectionItem(DetectedItem,DetectedItemID) +if DetectedItem then +self:__DetectedItem(0.1,DetectedItem) +end +end +end +end +end +do +function DETECTION_BASE:CleanDetectionItem(DetectedItem,DetectedItemID) +local DetectedSet=DetectedItem.Set +if DetectedSet:Count()==0 then +self:RemoveDetectedItem(DetectedItemID) +end +return self +end +function DETECTION_BASE:ForgetDetectedUnit(UnitName) +local DetectedItems=self:GetDetectedItems() +for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do +local DetectedSet=self:GetDetectedItemSet(DetectedItem) +if DetectedSet then +DetectedSet:RemoveUnitsByName(UnitName) +end +end +return self +end +function DETECTION_BASE:CreateDetectionItems() +self:F("Error, in DETECTION_BASE class...") +return self +end +end +do +function DETECTION_BASE:InitDetectVisual(DetectVisual) +self.DetectVisual=DetectVisual +return self +end +function DETECTION_BASE:InitDetectOptical(DetectOptical) +self:F2() +self.DetectOptical=DetectOptical +return self +end +function DETECTION_BASE:InitDetectRadar(DetectRadar) +self:F2() +self.DetectRadar=DetectRadar +return self +end +function DETECTION_BASE:InitDetectIRST(DetectIRST) +self:F2() +self.DetectIRST=DetectIRST +return self +end +function DETECTION_BASE:InitDetectRWR(DetectRWR) +self:F2() +self.DetectRWR=DetectRWR +return self +end +function DETECTION_BASE:InitDetectDLINK(DetectDLINK) +self:F2() +self.DetectDLINK=DetectDLINK +return self +end +end +do +function DETECTION_BASE:FilterCategories(FilterCategories) +self:F2() +self._.FilterCategories={} +if type(FilterCategories)=="table"then +for CategoryID,Category in pairs(FilterCategories)do +self._.FilterCategories[Category]=Category +end +else +self._.FilterCategories[FilterCategories]=FilterCategories +end +return self +end +function DETECTION_BASE:SetRadarBlur(minheight,thresheight,thresblur,closing) +self.RadarBlur=true +self.RadarBlurMinHeight=minheight or 250 +self.RadarBlurThresHeight=thresheight or 90 +self.RadarBlurThresBlur=thresblur or 85 +self.RadarBlurClosing=closing or 20 +self.RadarBlurClosingSquare=self.RadarBlurClosing*self.RadarBlurClosing +return self +end +end +do +function DETECTION_BASE:SetRefreshTimeInterval(RefreshTimeInterval) +self:F2() +self.RefreshTimeInterval=RefreshTimeInterval +return self +end +end +do +function DETECTION_BASE:SetFriendliesRange(FriendliesRange) +self:F2() +self.FriendliesRange=FriendliesRange +return self +end +end +do +function DETECTION_BASE:SetIntercept(Intercept,InterceptDelay) +self:F2() +self.Intercept=Intercept +self.InterceptDelay=InterceptDelay +return self +end +end +do +function DETECTION_BASE:SetAcceptRange(AcceptRange) +self:F2() +self.AcceptRange=AcceptRange +return self +end +function DETECTION_BASE:SetAcceptZones(AcceptZones) +self:F2() +if type(AcceptZones)=="table"then +if AcceptZones.ClassName and AcceptZones:IsInstanceOf(ZONE_BASE)then +self.AcceptZones={AcceptZones} +else +self.AcceptZones=AcceptZones +end +else +self:F({"AcceptZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",AcceptZones}) +error() +end +return self +end +function DETECTION_BASE:SetRejectZones(RejectZones) +self:F2() +if type(RejectZones)=="table"then +if RejectZones.ClassName and RejectZones:IsInstanceOf(ZONE_BASE)then +self.RejectZones={RejectZones} +else +self.RejectZones=RejectZones +end +else +self:F({"RejectZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",RejectZones}) +error() +end +return self +end +end +do +function DETECTION_BASE:SetDistanceProbability(DistanceProbability) +self:F2() +self.DistanceProbability=DistanceProbability +return self +end +function DETECTION_BASE:SetAlphaAngleProbability(AlphaAngleProbability) +self:F2() +self.AlphaAngleProbability=AlphaAngleProbability +return self +end +function DETECTION_BASE:SetZoneProbability(ZoneArray) +self:F2() +self.ZoneProbability=ZoneArray +return self +end +end +do +function DETECTION_BASE:AcceptChanges(DetectedItem) +DetectedItem.Changed=false +DetectedItem.Changes={} +return self +end +function DETECTION_BASE:AddChangeItem(DetectedItem,ChangeCode,ItemUnitType) +DetectedItem.Changed=true +local ID=DetectedItem.ID +DetectedItem.Changes=DetectedItem.Changes or{} +DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{} +DetectedItem.Changes[ChangeCode].ID=ID +DetectedItem.Changes[ChangeCode].ItemUnitType=ItemUnitType +self:F({"Change on Detected Item:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ItemUnitType=ItemUnitType}) +return self +end +function DETECTION_BASE:AddChangeUnit(DetectedItem,ChangeCode,ChangeUnitType) +DetectedItem.Changed=true +local ID=DetectedItem.ID +DetectedItem.Changes=DetectedItem.Changes or{} +DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{} +DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]or 0 +DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]+1 +DetectedItem.Changes[ChangeCode].ID=ID +self:F({"Change on Detected Unit:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ChangeUnitType=ChangeUnitType}) +return self +end +end +do +function DETECTION_BASE:SetFriendlyPrefixes(FriendlyPrefixes) +self.FriendlyPrefixes=self.FriendlyPrefixes or{} +if type(FriendlyPrefixes)~="table"then +FriendlyPrefixes={FriendlyPrefixes} +end +for PrefixID,Prefix in pairs(FriendlyPrefixes)do +self:F({FriendlyPrefix=Prefix}) +self.FriendlyPrefixes[Prefix]=Prefix +end +return self +end +function DETECTION_BASE:IsFriendliesNearBy(DetectedItem,Category) +return(DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category]~=nil)or false +end +function DETECTION_BASE:GetFriendliesNearBy(DetectedItem,Category) +return DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] +end +function DETECTION_BASE:IsFriendliesNearIntercept(DetectedItem) +return DetectedItem.FriendliesNearIntercept~=nil or false +end +function DETECTION_BASE:GetFriendliesNearIntercept(DetectedItem) +return DetectedItem.FriendliesNearIntercept +end +function DETECTION_BASE:GetFriendliesDistance(DetectedItem) +return DetectedItem.FriendliesDistance +end +function DETECTION_BASE:IsPlayersNearBy(DetectedItem) +return DetectedItem.PlayersNearBy~=nil +end +function DETECTION_BASE:GetPlayersNearBy(DetectedItem) +return DetectedItem.PlayersNearBy +end +function DETECTION_BASE:ReportFriendliesNearBy(TargetData) +local DetectedItem=TargetData.DetectedItem +local DetectedSet=TargetData.DetectedItem.Set +local DetectedUnit=DetectedSet:GetFirst() +DetectedItem.FriendliesNearBy=nil +if DetectedUnit and DetectedUnit:IsAlive()then +local DetectedUnitCoord=DetectedUnit:GetCoordinate() +local InterceptCoord=TargetData.InterceptCoord or DetectedUnitCoord +local SphereSearch={ +id=world.VolumeType.SPHERE, +params={ +point=InterceptCoord:GetVec3(), +radius=self.FriendliesRange, +} +} +local FindNearByFriendlies=function(FoundDCSUnit,ReportGroupData) +local DetectedItem=ReportGroupData.DetectedItem +local DetectedSet=ReportGroupData.DetectedItem.Set +local DetectedUnit=DetectedSet:GetFirst() +local DetectedUnitCoord=DetectedUnit:GetCoordinate() +local InterceptCoord=ReportGroupData.InterceptCoord or DetectedUnitCoord +local ReportSetGroup=ReportGroupData.ReportSetGroup +local EnemyCoalition=DetectedUnit:GetCoalition() +local FoundUnitCoalition=FoundDCSUnit:getCoalition() +local FoundUnitCategory=FoundDCSUnit:getDesc().category +local FoundUnitName=FoundDCSUnit:getName() +local FoundUnitGroupName=FoundDCSUnit:getGroup():getName() +local EnemyUnitName=DetectedUnit:GetName() +local FoundUnitInReportSetGroup=ReportSetGroup:FindGroup(FoundUnitGroupName)~=nil +if FoundUnitInReportSetGroup==true then +for PrefixID,Prefix in pairs(self.FriendlyPrefixes or{})do +if string.find(FoundUnitName,Prefix:gsub("-","%%-"),1)then +FoundUnitInReportSetGroup=false +break +end +end +end +if FoundUnitCoalition~=EnemyCoalition and FoundUnitInReportSetGroup==false then +local FriendlyUnit=UNIT:Find(FoundDCSUnit) +local FriendlyUnitName=FriendlyUnit:GetName() +local FriendlyUnitCategory=FriendlyUnit:GetDesc().category +DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{} +DetectedItem.FriendliesNearBy[FoundUnitCategory]=DetectedItem.FriendliesNearBy[FoundUnitCategory]or{} +DetectedItem.FriendliesNearBy[FoundUnitCategory][FriendlyUnitName]=FriendlyUnit +local Distance=DetectedUnitCoord:Get2DDistance(FriendlyUnit:GetCoordinate()) +DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{} +DetectedItem.FriendliesDistance[Distance]=FriendlyUnit +return true +end +return true +end +world.searchObjects(Object.Category.UNIT,SphereSearch,FindNearByFriendlies,TargetData) +DetectedItem.PlayersNearBy=nil +_DATABASE:ForEachPlayer( +function(PlayerUnitName) +local PlayerUnit=UNIT:FindByName(PlayerUnitName) +if PlayerUnit and PlayerUnit:IsAlive()then +local coord=PlayerUnit:GetCoordinate() +if coord and coord:IsInRadius(DetectedUnitCoord,self.FriendliesRange)then +local PlayerUnitCategory=PlayerUnit:GetDesc().category +if(not self.FriendliesCategory)or(self.FriendliesCategory and(self.FriendliesCategory==PlayerUnitCategory))then +local PlayerUnitName=PlayerUnit:GetName() +DetectedItem.PlayersNearBy=DetectedItem.PlayersNearBy or{} +DetectedItem.PlayersNearBy[PlayerUnitName]=PlayerUnit +DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{} +DetectedItem.FriendliesNearBy[PlayerUnitCategory]=DetectedItem.FriendliesNearBy[PlayerUnitCategory]or{} +DetectedItem.FriendliesNearBy[PlayerUnitCategory][PlayerUnitName]=PlayerUnit +local Distance=DetectedUnitCoord:Get2DDistance(PlayerUnit:GetCoordinate()) +DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{} +DetectedItem.FriendliesDistance[Distance]=PlayerUnit +end +end +end +end) +end +self:F({Friendlies=DetectedItem.FriendliesNearBy,Players=DetectedItem.PlayersNearBy}) +end +end +function DETECTION_BASE:IsDetectedObjectIdentified(DetectedObject) +local DetectedObjectName=DetectedObject.Name +if DetectedObjectName then +local DetectedObjectIdentified=self.DetectedObjectsIdentified[DetectedObjectName]==true +return DetectedObjectIdentified +else +return nil +end +end +function DETECTION_BASE:IdentifyDetectedObject(DetectedObject) +local DetectedObjectName=DetectedObject.Name +self.DetectedObjectsIdentified[DetectedObjectName]=true +end +function DETECTION_BASE:UnIdentifyDetectedObject(DetectedObject) +local DetectedObjectName=DetectedObject.Name +self.DetectedObjectsIdentified[DetectedObjectName]=false +end +function DETECTION_BASE:UnIdentifyAllDetectedObjects() +self.DetectedObjectsIdentified={} +end +function DETECTION_BASE:GetDetectedObject(ObjectName) +self:F2({ObjectName=ObjectName}) +if ObjectName then +local DetectedObject=self.DetectedObjects[ObjectName] +if DetectedObject then +local DetectedUnit=UNIT:FindByName(ObjectName) +if DetectedUnit and DetectedUnit:IsAlive()then +if self:IsDetectedObjectIdentified(DetectedObject)==false then +return DetectedObject +end +end +end +end +return nil +end +function DETECTION_BASE:GetDetectedUnitTypeName(DetectedUnit) +if DetectedUnit and DetectedUnit:IsAlive()then +local DetectedUnitName=DetectedUnit:GetName() +local DetectedObject=self.DetectedObjects[DetectedUnitName] +if DetectedObject then +if DetectedObject.KnowType then +return DetectedUnit:GetTypeName() +else +return"Unknown" +end +else +return"Unknown" +end +else +return"Dead:"..DetectedUnit:GetName() +end +return"Undetected:"..DetectedUnit:GetName() +end +function DETECTION_BASE:AddDetectedItem(ItemPrefix,DetectedItemKey,Set) +local DetectedItem={} +self.DetectedItemCount=self.DetectedItemCount+1 +self.DetectedItemMax=self.DetectedItemMax+1 +DetectedItemKey=DetectedItemKey or self.DetectedItemMax +self.DetectedItems[DetectedItemKey]=DetectedItem +self.DetectedItemsByIndex[DetectedItemKey]=DetectedItem +DetectedItem.Index=DetectedItemKey +DetectedItem.Set=Set or SET_UNIT:New():FilterDeads():FilterCrashes() +DetectedItem.ItemID=ItemPrefix.."."..self.DetectedItemMax +DetectedItem.ID=self.DetectedItemMax +DetectedItem.Removed=false +if self.Locking then +self:LockDetectedItem(DetectedItem) +end +return DetectedItem +end +function DETECTION_BASE:AddDetectedItemZone(ItemPrefix,DetectedItemKey,Set,Zone) +self:F({ItemPrefix,DetectedItemKey,Set,Zone}) +local DetectedItem=self:AddDetectedItem(ItemPrefix,DetectedItemKey,Set) +DetectedItem.Zone=Zone +return DetectedItem +end +function DETECTION_BASE:RemoveDetectedItem(DetectedItemKey) +local DetectedItem=self.DetectedItems[DetectedItemKey] +if DetectedItem then +self.DetectedItemCount=self.DetectedItemCount-1 +local DetectedItemIndex=DetectedItem.Index +self.DetectedItemsByIndex[DetectedItemIndex]=nil +self.DetectedItems[DetectedItemKey]=nil +end +end +function DETECTION_BASE:GetDetectedItems() +return self.DetectedItems +end +function DETECTION_BASE:GetDetectedItemsByIndex() +return self.DetectedItemsByIndex +end +function DETECTION_BASE:GetDetectedItemsCount() +local DetectedCount=self.DetectedItemCount +return DetectedCount +end +function DETECTION_BASE:GetDetectedItemByKey(Key) +self:F({DetectedItems=self.DetectedItems}) +local DetectedItem=self.DetectedItems[Key] +if DetectedItem then +return DetectedItem +end +return nil +end +function DETECTION_BASE:GetDetectedItemByIndex(Index) +self:F({self.DetectedItemsByIndex}) +local DetectedItem=self.DetectedItemsByIndex[Index] +if DetectedItem then +return DetectedItem +end +return nil +end +function DETECTION_BASE:GetDetectedItemID(DetectedItem) +return DetectedItem and DetectedItem.ItemID or"" +end +function DETECTION_BASE:GetDetectedID(Index) +local DetectedItem=self.DetectedItemsByIndex[Index] +if DetectedItem then +return DetectedItem.ID +end +return"" +end +function DETECTION_BASE:GetDetectedItemSet(DetectedItem) +local DetectedSetUnit=DetectedItem and DetectedItem.Set +if DetectedSetUnit then +return DetectedSetUnit +end +return nil +end +function DETECTION_BASE:UpdateDetectedItemDetection(DetectedItem) +local IsDetected=false +for UnitName,UnitData in pairs(DetectedItem.Set:GetSet())do +local DetectedObject=self.DetectedObjects[UnitName] +self:F({UnitName=UnitName,IsDetected=DetectedObject.IsDetected}) +if DetectedObject.IsDetected then +IsDetected=true +break +end +end +self:F({IsDetected=DetectedItem.IsDetected}) +DetectedItem.IsDetected=IsDetected +return IsDetected +end +function DETECTION_BASE:IsDetectedItemDetected(DetectedItem) +return DetectedItem.IsDetected +end +do +function DETECTION_BASE:GetDetectedItemZone(DetectedItem) +local DetectedZone=DetectedItem and DetectedItem.Zone +if DetectedZone then +return DetectedZone +end +local Detected +return nil +end +end +function DETECTION_BASE:LockDetectedItems() +for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do +self:LockDetectedItem(DetectedItem) +end +self.Locking=true +return self +end +function DETECTION_BASE:UnlockDetectedItems() +for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do +self:UnlockDetectedItem(DetectedItem) +end +self.Locking=nil +return self +end +function DETECTION_BASE:IsDetectedItemLocked(DetectedItem) +return self.Locking and DetectedItem.Locked==true +end +function DETECTION_BASE:LockDetectedItem(DetectedItem) +DetectedItem.Locked=true +return self +end +function DETECTION_BASE:UnlockDetectedItem(DetectedItem) +DetectedItem.Locked=nil +return self +end +function DETECTION_BASE:SetDetectedItemCoordinate(DetectedItem,Coordinate,DetectedItemUnit) +self:F({Coordinate=Coordinate}) +if DetectedItem then +if DetectedItemUnit then +DetectedItem.Coordinate=Coordinate +DetectedItem.Coordinate:SetHeading(DetectedItemUnit:GetHeading()) +DetectedItem.Coordinate.y=DetectedItemUnit:GetAltitude() +DetectedItem.Coordinate:SetVelocity(DetectedItemUnit:GetVelocityMPS()) +end +end +end +function DETECTION_BASE:GetDetectedItemCoordinate(DetectedItem) +self:F({DetectedItem=DetectedItem}) +if DetectedItem then +return DetectedItem.Coordinate +end +return nil +end +function DETECTION_BASE:GetDetectedItemCoordinates() +local Coordinates={} +for DetectedItemID,DetectedItem in pairs(self:GetDetectedItems())do +Coordinates[DetectedItem]=self:GetDetectedItemCoordinate(DetectedItem) +end +return Coordinates +end +function DETECTION_BASE:SetDetectedItemThreatLevel(DetectedItem) +local DetectedSet=DetectedItem.Set +if DetectedItem then +DetectedItem.ThreatLevel,DetectedItem.ThreatText=DetectedSet:CalculateThreatLevelA2G() +end +end +function DETECTION_BASE:GetDetectedItemThreatLevel(DetectedItem) +self:F({DetectedItem=DetectedItem}) +if DetectedItem then +self:F({ThreatLevel=DetectedItem.ThreatLevel,ThreatText=DetectedItem.ThreatText}) +return DetectedItem.ThreatLevel or 0,DetectedItem.ThreatText or"" +end +return nil,"" +end +function DETECTION_BASE:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings) +self:F() +return nil +end +function DETECTION_BASE:DetectedReportDetailed(AttackGroup) +self:F() +return nil +end +function DETECTION_BASE:GetDetectionSet() +local DetectionSet=self.DetectionSet +return DetectionSet +end +function DETECTION_BASE:NearestRecce(DetectedItem) +local NearestRecce=nil +local DistanceRecce=1000000000 +for RecceGroupName,RecceGroup in pairs(self.DetectionSet:GetSet())do +if RecceGroup and RecceGroup:IsAlive()then +for RecceUnit,RecceUnit in pairs(RecceGroup:GetUnits())do +if RecceUnit:IsActive()then +local RecceUnitCoord=RecceUnit:GetCoordinate() +local Distance=RecceUnitCoord:Get2DDistance(self:GetDetectedItemCoordinate(DetectedItem)) +if Distance0 then +DetectedItemCoordText=DetectedItemCoordinate:ToStringA2A(AttackGroup,Settings) +else +DetectedItemCoordText=DetectedItemCoordinate:ToStringA2G(AttackGroup,Settings) +end +local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem) +local DetectedItemsCount=DetectedSet:Count() +local DetectedItemsTypes=DetectedSet:GetTypeNames() +local Report=REPORT:New() +Report:Add(DetectedItemID..", "..DetectedItemCoordText) +Report:Add(string.format("Threat: [%s%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G))) +Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes)) +return Report +end +return nil +end +function DETECTION_AREAS:DetectedReportDetailed(AttackGroup) +self:F() +local Report=REPORT:New() +for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do +local DetectedItem=DetectedItem +local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup) +Report:SetTitle("Detected areas:") +Report:Add(ReportSummary:Text()) +end +local ReportText=Report:Text() +return ReportText +end +function DETECTION_AREAS:CalculateIntercept(DetectedItem) +local DetectedCoord=DetectedItem.Coordinate +local DetectedSpeed=DetectedCoord:GetVelocity() +local DetectedHeading=DetectedCoord:GetHeading() +if self.Intercept then +local DetectedSet=DetectedItem.Set +local TranslateDistance=DetectedSpeed*self.InterceptDelay +local InterceptCoord=DetectedCoord:Translate(TranslateDistance,DetectedHeading) +DetectedItem.InterceptCoord=InterceptCoord +else +DetectedItem.InterceptCoord=DetectedCoord +end +end +function DETECTION_AREAS:SmokeDetectedUnits() +self:F2() +self._SmokeDetectedUnits=true +return self +end +function DETECTION_AREAS:FlareDetectedUnits() +self:F2() +self._FlareDetectedUnits=true +return self +end +function DETECTION_AREAS:SmokeDetectedZones() +self:F2() +self._SmokeDetectedZones=true +return self +end +function DETECTION_AREAS:FlareDetectedZones() +self:F2() +self._FlareDetectedZones=true +return self +end +function DETECTION_AREAS:BoundDetectedZones() +self:F2() +self._BoundDetectedZones=true +return self +end +function DETECTION_AREAS:GetChangeText(DetectedItem) +self:F(DetectedItem) +local MT={} +for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do +if ChangeCode=="AA"then +MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."." +end +if ChangeCode=="RAU"then +MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target." +end +if ChangeCode=="AAU"then +MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."." +end +if ChangeCode=="RA"then +MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area." +end +if ChangeCode=="AU"then +local MTUT={} +for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do +if ChangeUnitType~="ID"then +MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType +end +end +MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."." +end +if ChangeCode=="RU"then +local MTUT={} +for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do +if ChangeUnitType~="ID"then +MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType +end +end +MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."." +end +end +return table.concat(MT,"\n") +end +function DETECTION_AREAS:CreateDetectionItems() +self:F("Checking Detected Items for new Detected Units ...") +for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do +local DetectedItem=DetectedItemData +if DetectedItem then +self:T2({"Detected Item ID: ",DetectedItemID}) +local DetectedSet=DetectedItem.Set +local AreaExists=false +self:T3({"Zone Center Unit:",DetectedItem.Zone.ZoneUNIT.UnitName}) +local DetectedZoneObject=self:GetDetectedObject(DetectedItem.Zone.ZoneUNIT.UnitName) +self:T3({"Detected Zone Object:",DetectedItem.Zone:GetName(),DetectedZoneObject}) +if DetectedZoneObject then +AreaExists=true +else +DetectedSet:RemoveUnitsByName(DetectedItem.Zone.ZoneUNIT.UnitName) +self:AddChangeItem(DetectedItem,'RAU',self:GetDetectedUnitTypeName(DetectedItem.Zone.ZoneUNIT)) +for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do +local DetectedUnit=DetectedUnitData +local DetectedObject=self:GetDetectedObject(DetectedUnit.UnitName) +local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit) +if DetectedObject then +self:IdentifyDetectedObject(DetectedObject) +AreaExists=true +DetectedItem.Zone=ZONE_UNIT:New(DetectedUnit:GetName(),DetectedUnit,self.DetectionZoneRange) +self:AddChangeItem(DetectedItem,"AAU",DetectedUnitTypeName) +break +else +DetectedSet:Remove(DetectedUnitName) +self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName) +end +end +end +if AreaExists then +for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do +local DetectedUnit=DetectedUnitData +local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit) +local DetectedObject=nil +if DetectedUnit:IsAlive()then +DetectedObject=self:GetDetectedObject(DetectedUnit:GetName()) +end +if DetectedObject then +if DetectedUnit:IsInZone(DetectedItem.Zone)then +self:IdentifyDetectedObject(DetectedObject) +DetectedSet:AddUnit(DetectedUnit) +else +DetectedSet:Remove(DetectedUnitName) +self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName) +end +else +self:AddChangeUnit(DetectedItem,"RU","destroyed target") +DetectedSet:Remove(DetectedUnitName) +end +end +else +self:RemoveDetectedItem(DetectedItemID) +self:AddChangeItem(DetectedItem,"RA") +end +end +end +for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do +local DetectedObject=self:GetDetectedObject(DetectedUnitName) +if DetectedObject then +local DetectedUnit=UNIT:FindByName(DetectedUnitName) +local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit) +local AddedToDetectionArea=false +for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do +local DetectedItem=DetectedItemData +if DetectedItem then +local DetectedSet=DetectedItem.Set +if not self:IsDetectedObjectIdentified(DetectedObject)and DetectedUnit:IsInZone(DetectedItem.Zone)then +self:IdentifyDetectedObject(DetectedObject) +DetectedSet:AddUnit(DetectedUnit) +AddedToDetectionArea=true +self:AddChangeUnit(DetectedItem,"AU",DetectedUnitTypeName) +end +end +end +if AddedToDetectionArea==false then +local DetectedItem=self:AddDetectedItemZone("AREA",nil, +SET_UNIT:New():FilterDeads():FilterCrashes(), +ZONE_UNIT:New(DetectedUnitName,DetectedUnit,self.DetectionZoneRange) +) +DetectedItem.Set:AddUnit(DetectedUnit) +self:AddChangeItem(DetectedItem,"AA",DetectedUnitTypeName) +end +end +end +for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do +local DetectedItem=DetectedItemData +local DetectedSet=DetectedItem.Set +local DetectedFirstUnit=DetectedSet:GetFirst() +local DetectedZone=DetectedItem.Zone +local DetectedZoneCoord=DetectedZone:GetCoordinate() +self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit) +self:CalculateIntercept(DetectedItem) +local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSet}) +local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then +DetectedItem.Changed=true +end +self:SetDetectedItemThreatLevel(DetectedItem) +self:NearestRecce(DetectedItem) +if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then +DetectedZone.ZoneUNIT:SmokeRed() +end +DetectedSet:ForEachUnit( +function(DetectedUnit) +if DetectedUnit:IsAlive()then +if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then +DetectedUnit:FlareGreen() +end +if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then +DetectedUnit:SmokeGreen() +end +end +end) +if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then +DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90)) +end +if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then +DetectedZone:SmokeZone(SMOKECOLOR.White,30) +end +if DETECTION_AREAS._BoundDetectedZones or self._BoundDetectedZones then +self.CountryID=DetectedSet:GetFirst():GetCountry() +DetectedZone:BoundZone(12,self.CountryID) +end +end +end +end +do +DETECTION_ZONES={ +ClassName="DETECTION_ZONES", +DetectionZoneRange=nil, +} +function DETECTION_ZONES:New(DetectionSetZone,DetectionCoalition) +local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetZone)) +self.DetectionSetZone=DetectionSetZone +self.DetectionCoalition=DetectionCoalition +self._SmokeDetectedUnits=false +self._FlareDetectedUnits=false +self._SmokeDetectedZones=false +self._FlareDetectedZones=false +self._BoundDetectedZones=false +return self +end +function DETECTION_ZONES:CountAliveRecce() +return self.DetectionSetZone:Count() +end +function DETECTION_ZONES:ForEachAliveRecce(IteratorFunction,...) +self:F2(arg) +self.DetectionSetZone:ForEachZone(IteratorFunction,arg) +return self +end +function DETECTION_ZONES:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings) +self:F({DetectedItem=DetectedItem}) +local DetectedItemID=self:GetDetectedItemID(DetectedItem) +if DetectedItem then +local DetectedSet=self:GetDetectedItemSet(DetectedItem) +local ReportSummaryItem +local DetectedZone=self:GetDetectedItemZone(DetectedItem) +local DetectedItemCoordinate=DetectedZone:GetCoordinate() +local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings) +local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem) +local DetectedItemsCount=DetectedSet:Count() +local DetectedItemsTypes=DetectedSet:GetTypeNames() +local Report=REPORT:New() +Report:Add(DetectedItemID..", "..DetectedItemCoordText) +Report:Add(string.format("Threat: [%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G))) +Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes)) +Report:Add(string.format("Detected: %s",DetectedItem.IsDetected and"yes"or"no")) +return Report +end +return nil +end +function DETECTION_ZONES:DetectedReportDetailed(AttackGroup) +self:F() +local Report=REPORT:New() +for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do +local DetectedItem=DetectedItem +local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup) +Report:SetTitle("Detected areas:") +Report:Add(ReportSummary:Text()) +end +local ReportText=Report:Text() +return ReportText +end +function DETECTION_ZONES:CalculateIntercept(DetectedItem) +local DetectedCoord=DetectedItem.Coordinate +DetectedItem.InterceptCoord=DetectedCoord +end +function DETECTION_ZONES:SmokeDetectedUnits() +self:F2() +self._SmokeDetectedUnits=true +return self +end +function DETECTION_ZONES:FlareDetectedUnits() +self:F2() +self._FlareDetectedUnits=true +return self +end +function DETECTION_ZONES:SmokeDetectedZones() +self:F2() +self._SmokeDetectedZones=true +return self +end +function DETECTION_ZONES:FlareDetectedZones() +self:F2() +self._FlareDetectedZones=true +return self +end +function DETECTION_ZONES:BoundDetectedZones() +self:F2() +self._BoundDetectedZones=true +return self +end +function DETECTION_ZONES:GetChangeText(DetectedItem) +self:F(DetectedItem) +local MT={} +for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do +if ChangeCode=="AA"then +MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."." +end +if ChangeCode=="RAU"then +MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target." +end +if ChangeCode=="AAU"then +MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."." +end +if ChangeCode=="RA"then +MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area." +end +if ChangeCode=="AU"then +local MTUT={} +for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do +if ChangeUnitType~="ID"then +MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType +end +end +MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."." +end +if ChangeCode=="RU"then +local MTUT={} +for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do +if ChangeUnitType~="ID"then +MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType +end +end +MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."." +end +end +return table.concat(MT,"\n") +end +function DETECTION_ZONES:CreateDetectionItems() +self:F("Checking Detected Items for new Detected Units ...") +local DetectedUnits=SET_UNIT:New() +for ZoneName,DetectionZone in pairs(self.DetectionSetZone:GetSet())do +local DetectedItem=self:GetDetectedItemByKey(ZoneName) +if DetectedItem==nil then +DetectedItem=self:AddDetectedItemZone("ZONE",ZoneName,nil,DetectionZone) +end +local DetectedItemSetUnit=self:GetDetectedItemSet(DetectedItem) +DetectionZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) +local ZoneUnits=DetectionZone:GetScannedUnits() +for DCSUnitID,DCSUnit in pairs(ZoneUnits)do +local UnitName=DCSUnit:getName() +local ZoneUnit=UNIT:FindByName(UnitName) +local ZoneUnitCoalition=ZoneUnit:GetCoalition() +if ZoneUnitCoalition==self.DetectionCoalition then +if DetectedItemSetUnit:FindUnit(UnitName)==nil and DetectedUnits:FindUnit(UnitName)==nil then +self:F("Adding "..UnitName) +DetectedItemSetUnit:AddUnit(ZoneUnit) +DetectedUnits:AddUnit(ZoneUnit) +end +end +end +end +for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do +local DetectedItem=DetectedItemData +local DetectedSet=self:GetDetectedItemSet(DetectedItem) +local DetectedFirstUnit=DetectedSet:GetFirst() +local DetectedZone=self:GetDetectedItemZone(DetectedItem) +local DetectedZoneCoord=DetectedZone:GetCoordinate() +self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit) +self:CalculateIntercept(DetectedItem) +local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSetGroup}) +local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then +DetectedItem.Changed=true +end +self:SetDetectedItemThreatLevel(DetectedItem) +if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then +DetectedZone:SmokeZone(SMOKECOLOR.Red,30) +end +DetectedSet:ForEachUnit( +function(DetectedUnit) +if DetectedUnit:IsAlive()then +if DETECTION_ZONES._FlareDetectedUnits or self._FlareDetectedUnits then +DetectedUnit:FlareGreen() +end +if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then +DetectedUnit:SmokeGreen() +end +end +end +) +if DETECTION_ZONES._FlareDetectedZones or self._FlareDetectedZones then +DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90)) +end +if DETECTION_ZONES._SmokeDetectedZones or self._SmokeDetectedZones then +DetectedZone:SmokeZone(SMOKECOLOR.White,30) +end +if DETECTION_ZONES._BoundDetectedZones or self._BoundDetectedZones then +self.CountryID=DetectedSet:GetFirst():GetCountry() +DetectedZone:BoundZone(12,self.CountryID) +end +end +end +function DETECTION_ZONES:onafterDetection(From,Event,To,Detection,DetectionTimeStamp) +self.DetectionRun=self.DetectionRun+1 +if self.DetectionCount>0 and self.DetectionRun==self.DetectionCount then +self:CreateDetectionItems() +for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do +self:UpdateDetectedItemDetection(DetectedItem) +self:CleanDetectionItem(DetectedItem,DetectedItemID) +if DetectedItem then +self:__DetectedItem(0.1,DetectedItem) +end +end +self:__Detect(-self.RefreshTimeInterval) +end +end +function DETECTION_ZONES:UpdateDetectedItemDetection(DetectedItem) +local IsDetected=true +DetectedItem.IsDetected=true +return IsDetected +end +end +do +DESIGNATE={ +ClassName="DESIGNATE", +} +function DESIGNATE:New(CC,Detection,AttackSet,Mission) +local self=BASE:Inherit(self,FSM:New()) +self:F({Detection}) +self:SetStartState("Designating") +self:AddTransition("*","Detect","*") +self:AddTransition("*","LaseOn","Lasing") +self:AddTransition("Lasing","Lasing","Lasing") +self:AddTransition("*","LaseOff","Designate") +self:AddTransition("*","Smoke","*") +self:AddTransition("*","Illuminate","*") +self:AddTransition("*","DoneSmoking","*") +self:AddTransition("*","DoneIlluminating","*") +self:AddTransition("*","Status","*") +self.CC=CC +self.Detection=Detection +self.AttackSet=AttackSet +self.RecceSet=Detection:GetDetectionSet() +self.Recces={} +self.Designating={} +self:SetDesignateName() +self:SetLaseDuration() +self:SetFlashStatusMenu(false) +self:SetFlashDetectionMessages(true) +self:SetMission(Mission) +self:SetLaserCodes({1688,1130,4785,6547,1465,4578}) +self:SetAutoLase(false,false) +self:SetThreatLevelPrioritization(false) +self:SetMaximumDesignations(5) +self:SetMaximumDistanceDesignations(8000) +self:SetMaximumMarkings(2) +self:SetDesignateMenu() +self.LaserCodesUsed={} +self.MenuLaserCodes={} +self.Detection:__Start(2) +self:__Detect(-15) +self.MarkScheduler=SCHEDULER:New(self) +return self +end +function DESIGNATE:SetFlashStatusMenu(FlashMenu) +self.FlashStatusMenu={} +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +self.FlashStatusMenu[AttackGroup]=FlashMenu +end +) +return self +end +function DESIGNATE:SetFlashDetectionMessages(FlashDetectionMessage) +self.FlashDetectionMessage={} +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +self.FlashDetectionMessage[AttackGroup]=FlashDetectionMessage +end +) +return self +end +function DESIGNATE:SetMaximumDesignations(MaximumDesignations) +self.MaximumDesignations=MaximumDesignations +return self +end +function DESIGNATE:SetMaximumDistanceGroundDesignation(MaximumDistanceGroundDesignation) +self.MaximumDistanceGroundDesignation=MaximumDistanceGroundDesignation +return self +end +function DESIGNATE:SetMaximumDistanceAirDesignation(MaximumDistanceAirDesignation) +self.MaximumDistanceAirDesignation=MaximumDistanceAirDesignation +return self +end +function DESIGNATE:SetMaximumDistanceDesignations(MaximumDistanceDesignations) +self.MaximumDistanceDesignations=MaximumDistanceDesignations +return self +end +function DESIGNATE:SetMaximumMarkings(MaximumMarkings) +self.MaximumMarkings=MaximumMarkings +return self +end +function DESIGNATE:SetLaserCodes(LaserCodes) +self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes} +self:F({LaserCodes=self.LaserCodes}) +self.LaserCodesUsed={} +return self +end +function DESIGNATE:AddMenuLaserCode(LaserCode,MenuText) +self.MenuLaserCodes[LaserCode]=MenuText +self:SetDesignateMenu() +return self +end +function DESIGNATE:RemoveMenuLaserCode(LaserCode) +self.MenuLaserCodes[LaserCode]=nil +self:SetDesignateMenu() +return self +end +function DESIGNATE:SetDesignateName(DesignateName) +self.DesignateName="Designation"..(DesignateName and(" for "..DesignateName)or"") +return self +end +function DESIGNATE:SetLaseDuration(LaseDuration) +self.LaseDuration=LaseDuration or 120 +return self +end +function DESIGNATE:GenerateLaserCodes() +self.LaserCodes={} +local function containsDigit(_number,_numberToFind) +local _thisNumber=_number +local _thisDigit=0 +while _thisNumber~=0 do +_thisDigit=_thisNumber%10 +_thisNumber=math.floor(_thisNumber/10) +if _thisDigit==_numberToFind then +return true +end +end +return false +end +local _code=1111 +local _count=1 +while _code<1777 and _count<30 do +while true do +_code=_code+1 +if not containsDigit(_code,8) +and not containsDigit(_code,9) +and not containsDigit(_code,0)then +self:T(_code) +table.insert(self.LaserCodes,_code) +break +end +end +_count=_count+1 +end +self.LaserCodesUsed={} +return self +end +function DESIGNATE:SetAutoLase(AutoLase,Message) +self.AutoLase=AutoLase or false +if Message then +local AutoLaseOnOff=(self.AutoLase==true)and"On"or"Off" +local CC=self.CC:GetPositionable() +if CC then +CC:MessageToSetGroup(self.DesignateName..": Auto Lase "..AutoLaseOnOff..".",15,self.AttackSet) +end +end +self:CoordinateLase() +self:SetDesignateMenu() +return self +end +function DESIGNATE:SetThreatLevelPrioritization(Prioritize) +self.ThreatLevelPrioritization=Prioritize +return self +end +function DESIGNATE:SetMission(Mission) +self.Mission=Mission +return self +end +function DESIGNATE:onafterDetect() +self:__Detect(-math.random(60)) +self:DesignationScope() +self:CoordinateLase() +self:SendStatus() +self:SetDesignateMenu() +return self +end +function DESIGNATE:DesignationScope() +local DetectedItems=self.Detection:GetDetectedItemsByIndex() +local DetectedItemCount=0 +for DesignateIndex,Designating in pairs(self.Designating)do +local DetectedItem=self.Detection:GetDetectedItemByIndex(DesignateIndex) +if DetectedItem then +local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem) +self:F({IsDetected=IsDetected}) +if IsDetected==false then +self:F("Removing") +self.Designating[DesignateIndex]=nil +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +if AttackGroup:IsAlive()==true then +local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ") +self.CC:GetPositionable():MessageToGroup("Targets out of LOS\n"..DetectionText,10,AttackGroup,self.DesignateName) +end +end +) +else +DetectedItemCount=DetectedItemCount+1 +end +else +self.Designating[DesignateIndex]=nil +end +end +if DetectedItemCount<5 then +for DesignateIndex,DetectedItem in pairs(DetectedItems)do +local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem) +if IsDetected==true then +self:F({DistanceRecce=DetectedItem.DistanceRecce}) +if DetectedItem.DistanceRecce<=self.MaximumDistanceDesignations then +if self.Designating[DesignateIndex]==nil then +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +if self.FlashDetectionMessage[AttackGroup]==true then +local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ") +self.CC:GetPositionable():MessageToGroup("Targets detected at \n"..DetectionText,10,AttackGroup,self.DesignateName) +end +end +) +self.Designating[DesignateIndex]="" +break +end +end +end +end +end +return self +end +function DESIGNATE:CoordinateLase() +local DetectedItems=self.Detection:GetDetectedItemsByIndex() +for DesignateIndex,Designating in pairs(self.Designating)do +local DetectedItem=DetectedItems[DesignateIndex] +if DetectedItem then +if self.AutoLase then +self:LaseOn(DesignateIndex,self.LaseDuration) +end +end +end +return self +end +function DESIGNATE:SendStatus(MenuAttackGroup) +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +if self.FlashStatusMenu[AttackGroup]or(MenuAttackGroup and(AttackGroup:GetName()==MenuAttackGroup:GetName()))then +local DetectedReport=REPORT:New("Targets ready for Designation:") +local DetectedItems=self.Detection:GetDetectedItemsByIndex() +for DesignateIndex,Designating in pairs(self.Designating)do +local DetectedItem=DetectedItems[DesignateIndex] +if DetectedItem then +local Report=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup,nil,true):Text(", ") +DetectedReport:Add(string.rep("-",40)) +DetectedReport:Add(" - "..Report) +if string.find(Designating,"L")then +DetectedReport:Add(" - ".."Lasing Targets") +end +if string.find(Designating,"S")then +DetectedReport:Add(" - ".."Smoking Targets") +end +if string.find(Designating,"I")then +DetectedReport:Add(" - ".."Illuminating Area") +end +end +end +local CC=self.CC:GetPositionable() +CC:MessageTypeToGroup(DetectedReport:Text("\n"),MESSAGE.Type.Information,AttackGroup,self.DesignateName) +local DesignationReport=REPORT:New("Marking Targets:") +self.RecceSet:ForEachGroupAlive( +function(RecceGroup) +local RecceUnits=RecceGroup:GetUnits() +for UnitID,RecceData in pairs(RecceUnits)do +local Recce=RecceData +if Recce:IsLasing()then +DesignationReport:Add(" - "..Recce:GetMessageText("Marking "..Recce:GetSpot().Target:GetTypeName().." with laser "..Recce:GetSpot().LaserCode..".")) +end +end +end +) +CC:MessageTypeToGroup(DesignationReport:Text(),MESSAGE.Type.Information,AttackGroup,self.DesignateName) +end +end +) +return self +end +function DESIGNATE:SetMenu(AttackGroup) +self.MenuDesignate=self.MenuDesignate or{} +local MissionMenu=nil +if self.Mission then +MissionMenu=self.Mission:GetMenu(AttackGroup) +end +local MenuTime=timer.getTime() +self.MenuDesignate[AttackGroup]=MENU_GROUP_DELAYED:New(AttackGroup,self.DesignateName,MissionMenu):SetTime(MenuTime):SetTag(self.DesignateName) +local MenuDesignate=self.MenuDesignate[AttackGroup] +if self.AutoLase then +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Auto Lase Off",MenuDesignate,self.MenuAutoLase,self,false):SetTime(MenuTime):SetTag(self.DesignateName) +else +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Auto Lase On",MenuDesignate,self.MenuAutoLase,self,true):SetTime(MenuTime):SetTag(self.DesignateName) +end +local StatusMenu=MENU_GROUP_DELAYED:New(AttackGroup,"Status",MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName) +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Report Status",StatusMenu,self.MenuStatus,self,AttackGroup):SetTime(MenuTime):SetTag(self.DesignateName) +if self.FlashStatusMenu[AttackGroup]then +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Flash Status Report Off",StatusMenu,self.MenuFlashStatus,self,AttackGroup,false):SetTime(MenuTime):SetTag(self.DesignateName) +else +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Flash Status Report On",StatusMenu,self.MenuFlashStatus,self,AttackGroup,true):SetTime(MenuTime):SetTag(self.DesignateName) +end +local DesignateCount=0 +for DesignateIndex,Designating in pairs(self.Designating)do +local DetectedItem=self.Detection:GetDetectedItemByIndex(DesignateIndex) +if DetectedItem then +local Coord=self.Detection:GetDetectedItemCoordinate(DetectedItem) +local ID=self.Detection:GetDetectedItemID(DetectedItem) +local MenuText=ID +if DetectedItem.DesignateMenuName then +MenuText=string.format("(%3s) %s",Designating,DetectedItem.DesignateMenuName) +else +MenuText=string.format("(%3s) %s",Designating,MenuText) +end +local DetectedMenu=MENU_GROUP_DELAYED:New(AttackGroup,MenuText,MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName) +if string.find(Designating,"L",1,true)==nil then +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Search other target",DetectedMenu,self.MenuForget,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName) +for LaserCode,MenuText in pairs(self.MenuLaserCodes)do +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,string.format(MenuText,LaserCode),DetectedMenu,self.MenuLaseCode,self,DesignateIndex,self.LaseDuration,LaserCode):SetTime(MenuTime):SetTag(self.DesignateName) +end +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Lase with random laser code(s)",DetectedMenu,self.MenuLaseOn,self,DesignateIndex,self.LaseDuration):SetTime(MenuTime):SetTag(self.DesignateName) +else +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Stop lasing",DetectedMenu,self.MenuLaseOff,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName) +end +if string.find(Designating,"S",1,true)==nil then +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke red",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Red):SetTime(MenuTime):SetTag(self.DesignateName) +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke blue",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Blue):SetTime(MenuTime):SetTag(self.DesignateName) +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke green",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Green):SetTime(MenuTime):SetTag(self.DesignateName) +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke white",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.White):SetTime(MenuTime):SetTag(self.DesignateName) +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke orange",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Orange):SetTime(MenuTime):SetTag(self.DesignateName) +end +if string.find(Designating,"I",1,true)==nil then +MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Illuminate",DetectedMenu,self.MenuIlluminate,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName) +end +end +DesignateCount=DesignateCount+1 +if DesignateCount>10 then +break +end +end +MenuDesignate:Remove(MenuTime,self.DesignateName) +MenuDesignate:Set() +end +function DESIGNATE:SetDesignateMenu() +self.AttackSet:Flush(self) +local Delay=1 +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +self:ScheduleOnce(Delay,self.SetMenu,self,AttackGroup) +Delay=Delay+1 +end +) +return self +end +function DESIGNATE:MenuStatus(AttackGroup) +self:F("Status") +self:SendStatus(AttackGroup) +end +function DESIGNATE:MenuFlashStatus(AttackGroup,Flash) +self:F("Flash Status") +self.FlashStatusMenu[AttackGroup]=Flash +self:SetDesignateMenu() +end +function DESIGNATE:MenuForget(Index) +self:F("Forget") +self.Designating[Index]="" +self:SetDesignateMenu() +end +function DESIGNATE:MenuAutoLase(AutoLase) +self:F("AutoLase") +self:SetAutoLase(AutoLase,true) +end +function DESIGNATE:MenuSmoke(Index,Color) +self:F("Designate through Smoke") +if string.find(self.Designating[Index],"S")==nil then +self.Designating[Index]=self.Designating[Index].."S" +end +self:Smoke(Index,Color) +self:SetDesignateMenu() +end +function DESIGNATE:MenuIlluminate(Index) +self:F("Designate through Illumination") +if string.find(self.Designating[Index],"I",1,true)==nil then +self.Designating[Index]=self.Designating[Index].."I" +end +self:__Illuminate(1,Index) +self:SetDesignateMenu() +end +function DESIGNATE:MenuLaseOn(Index,Duration) +self:F("Designate through Lase") +self:__LaseOn(1,Index,Duration) +self:SetDesignateMenu() +end +function DESIGNATE:MenuLaseCode(Index,Duration,LaserCode) +self:F("Designate through Lase using "..LaserCode) +self:__LaseOn(1,Index,Duration,LaserCode) +self:SetDesignateMenu() +end +function DESIGNATE:MenuLaseOff(Index,Duration) +self:F("Lasing off") +self.Designating[Index]=string.gsub(self.Designating[Index],"L","") +self:__LaseOff(1,Index) +self:SetDesignateMenu() +end +function DESIGNATE:onafterLaseOn(From,Event,To,Index,Duration,LaserCode) +if string.find(self.Designating[Index],"L",1,true)==nil then +self.Designating[Index]=self.Designating[Index].."L" +self.LaseStart=timer.getTime() +self.LaseDuration=Duration +self:Lasing(Index,Duration,LaserCode) +end +end +function DESIGNATE:onafterLasing(From,Event,To,Index,Duration,LaserCodeRequested) +local DetectedItem=self.Detection:GetDetectedItemByIndex(Index) +local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem) +local MarkingCount=0 +local MarkedTypes={} +for TargetUnit,RecceData in pairs(self.Recces)do +local Recce=RecceData +self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()}) +if not Recce:IsLasing()then +local LaserCode=Recce:GetLaserCode() +self:F({ClearingLaserCode=LaserCode}) +self.LaserCodesUsed[LaserCode]=nil +self.Recces[TargetUnit]=nil +end +end +if LaserCodeRequested then +for TargetUnit,RecceData in pairs(self.Recces)do +local Recce=RecceData +self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()}) +if Recce:IsLasing()then +Recce:LaseOff() +local LaserCode=Recce:GetLaserCode() +self:F({ClearingLaserCode=LaserCode}) +self.LaserCodesUsed[LaserCode]=nil +self.Recces[TargetUnit]=nil +break +end +end +end +if TargetSetUnit==nil then return end +if self.AutoLase or(not self.AutoLase and(self.LaseStart+Duration>=timer.getTime()))then +TargetSetUnit:ForEachUnitPerThreatLevel(10,0, +function(TargetUnit) +self:F({TargetUnit=TargetUnit:GetName()}) +if MarkingCount0 then +self:T2(self.lid..string.format("Stopping RAT in %d sec!",delay)) +self:ScheduleOnce(delay,RAT.Stop,self) +else +self:T(self.lid.."Stopping RAT: Clearing schedulers and unhandling events!") +if self.sid_Activate then +self.Scheduler:ScheduleStop(self.sid_Activate) +end +if self.sid_Spawn then +self.Scheduler:ScheduleStop(self.sid_Spawn) +end +if self.sid_Status then +self.Scheduler:ScheduleStop(self.sid_Status) +end +if self.Scheduler then +self.Scheduler:Clear() +end +self.norespawn=true +self:UnHandleEvent(EVENTS.Birth) +self:UnHandleEvent(EVENTS.EngineStartup) +self:UnHandleEvent(EVENTS.Takeoff) +self:UnHandleEvent(EVENTS.Land) +self:UnHandleEvent(EVENTS.EngineShutdown) +self:UnHandleEvent(EVENTS.Dead) +self:UnHandleEvent(EVENTS.Crash) +self:UnHandleEvent(EVENTS.Hit) +end +end +function RAT:Spawn(naircraft) +if self.spawninitialized==true then +self:E("ERROR: Spawn function should only be called once per RAT object! Exiting and returning nil.") +return nil +else +self.spawninitialized=true +end +self.ngroups=naircraft or 1 +if self.ATCswitch and not RAT.ATC.init then +RAT._ATCInit(self.airports_map) +end +if self.f10menu and not RAT.MenuF10 then +RAT.MenuF10=MENU_MISSION:New("RAT") +end +self:_SetCoalitionTable() +self:_GetAirportsOfCoalition() +if not self.SubMenuName then +self.SubMenuName=self.alias +end +if self.departure_Azone~=nil then +self.departure_ports=self:_GetAirportsInZone(self.departure_Azone) +end +if self.destination_Azone~=nil then +self.destination_ports=self:_GetAirportsInZone(self.destination_Azone) +end +if self.addfriendlydepartures then +self:_AddFriendlyAirports(self.departure_ports) +end +if self.addfriendlydestinations then +self:_AddFriendlyAirports(self.destination_ports) +end +if self.FLcruise==nil then +if self.category==RAT.cat.plane then +self.FLcruise=200*RAT.unit.FL2m +else +self.FLcruise=005*RAT.unit.FL2m +end +end +if self.category==RAT.cat.heli then +self.mindist=50 +end +self:_CheckConsistency() +local text=string.format("\n******************************************************\n") +text=text..string.format("Spawning %i aircraft from template %s of type %s.\n",self.ngroups,self.SpawnTemplatePrefix,self.aircraft.type) +text=text..string.format("Alias: %s\n",self.alias) +text=text..string.format("Category: %s\n",self.category) +text=text..string.format("Friendly coalitions: %s\n",self.friendly) +text=text..string.format("Number of airports on map : %i\n",#self.airports_map) +text=text..string.format("Number of friendly airports: %i\n",#self.airports) +text=text..string.format("Totally random departure: %s\n",tostring(self.random_departure)) +if not self.random_departure then +text=text..string.format("Number of departure airports: %d\n",self.Ndeparture_Airports) +text=text..string.format("Number of departure zones : %d\n",self.Ndeparture_Zones) +end +text=text..string.format("Totally random destination: %s\n",tostring(self.random_destination)) +if not self.random_destination then +text=text..string.format("Number of destination airports: %d\n",self.Ndestination_Airports) +text=text..string.format("Number of destination zones : %d\n",self.Ndestination_Zones) +end +text=text..string.format("Min dist to destination: %4.1f\n",self.mindist) +text=text..string.format("Max dist to destination: %4.1f\n",self.maxdist) +text=text..string.format("Terminal type: %s\n",tostring(self.termtype)) +text=text..string.format("Takeoff type: %i\n",self.takeoff) +text=text..string.format("Landing type: %i\n",self.landing) +text=text..string.format("Commute: %s\n",tostring(self.commute)) +text=text..string.format("Journey: %s\n",tostring(self.continuejourney)) +text=text..string.format("Destination Zone: %s\n",tostring(self.destinationzone)) +text=text..string.format("Return Zone: %s\n",tostring(self.returnzone)) +text=text..string.format("Spawn delay: %4.1f\n",self.spawndelay) +text=text..string.format("Spawn interval: %4.1f\n",self.spawninterval) +text=text..string.format("Respawn delay: %s\n",tostring(self.respawn_delay)) +text=text..string.format("Respawn off: %s\n",tostring(self.norespawn)) +text=text..string.format("Respawn after landing: %s\n",tostring(self.respawn_at_landing)) +text=text..string.format("Respawn after take-off: %s\n",tostring(self.respawn_after_takeoff)) +text=text..string.format("Respawn after crash: %s\n",tostring(self.respawn_after_crash)) +text=text..string.format("Respawn in air: %s\n",tostring(self.respawn_inair)) +text=text..string.format("ROE: %s\n",tostring(self.roe)) +text=text..string.format("ROT: %s\n",tostring(self.rot)) +text=text..string.format("Immortal: %s\n",tostring(self.immortal)) +text=text..string.format("Invisible: %s\n",tostring(self.invisible)) +text=text..string.format("Vclimb: %4.1f\n",self.Vclimb) +text=text..string.format("AlphaDescent: %4.2f\n",self.AlphaDescent) +text=text..string.format("Vcruisemax: %s\n",tostring(self.Vcruisemax)) +text=text..string.format("FLcruise = %6.1f km = FL%3.0f\n",self.FLcruise/1000,self.FLcruise/RAT.unit.FL2m) +text=text..string.format("FLuser: %s\n",tostring(self.Fluser)) +text=text..string.format("FLminuser: %s\n",tostring(self.FLminuser)) +text=text..string.format("FLmaxuser: %s\n",tostring(self.FLmaxuser)) +text=text..string.format("Place markers: %s\n",tostring(self.placemarkers)) +text=text..string.format("Report status: %s\n",tostring(self.reportstatus)) +text=text..string.format("Status interval: %4.1f\n",self.statusinterval) +text=text..string.format("Time inactive: %4.1f\n",self.Tinactive) +text=text..string.format("Create F10 menu : %s\n",tostring(self.f10menu)) +text=text..string.format("F10 submenu name: %s\n",self.SubMenuName) +text=text..string.format("ATC enabled : %s\n",tostring(self.ATCswitch)) +text=text..string.format("Radio comms : %s\n",tostring(self.radio)) +text=text..string.format("Radio frequency : %s\n",tostring(self.frequency)) +text=text..string.format("Radio modulation : %s\n",tostring(self.frequency)) +text=text..string.format("Tail # prefix : %s\n",tostring(self.onboardnum)) +text=text..string.format("Check on runway: %s\n",tostring(self.checkonrunway)) +text=text..string.format("Max respawn attempts: %s\n",tostring(self.onrunwaymaxretry)) +text=text..string.format("Check on top: %s\n",tostring(self.checkontop)) +text=text..string.format("Uncontrolled: %s\n",tostring(self.uncontrolled)) +if self.uncontrolled and self.activate_uncontrolled then +text=text..string.format("Uncontrolled max : %4.1f\n",self.activate_max) +text=text..string.format("Uncontrolled delay: %4.1f\n",self.activate_delay) +text=text..string.format("Uncontrolled delta: %4.1f\n",self.activate_delta) +text=text..string.format("Uncontrolled frand: %4.1f\n",self.activate_frand) +end +if self.livery then +text=text..string.format("Available liveries:\n") +for _,livery in pairs(self.livery)do +text=text..string.format("- %s\n",livery) +end +end +text=text..string.format("******************************************************\n") +self:T(self.lid..text) +if self.f10menu then +self.Menu[self.SubMenuName]=MENU_MISSION:New(self.SubMenuName,RAT.MenuF10) +self.Menu[self.SubMenuName]["groups"]=MENU_MISSION:New("Groups",self.Menu[self.SubMenuName]) +MENU_MISSION_COMMAND:New("Spawn new group",self.Menu[self.SubMenuName],self._SpawnWithRoute,self) +MENU_MISSION_COMMAND:New("Delete markers",self.Menu[self.SubMenuName],self._DeleteMarkers,self) +MENU_MISSION_COMMAND:New("Status report",self.Menu[self.SubMenuName],self.Status,self,true) +end +local Tstart=self.spawndelay +local dt=self.spawninterval +if self.takeoff==RAT.wp.runway and not self.random_departure then +dt=math.max(dt,180) +end +local Tstop=Tstart+dt*(self.ngroups-1) +self.sid_Status=self:ScheduleRepeat(Tstart+1,self.statusinterval,nil,nil,RAT.Status,self) +self:HandleEvent(EVENTS.Birth,self._OnBirth) +self:HandleEvent(EVENTS.EngineStartup,self._OnEngineStartup) +self:HandleEvent(EVENTS.Takeoff,self._OnTakeoff) +self:HandleEvent(EVENTS.Land,self._OnLand) +self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutdown) +self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.Hit,self._OnHit) +if self.ngroups==0 then +return nil +end +self.sid_Spawn=self:ScheduleRepeat(Tstart,dt,0.0,Tstop,RAT._SpawnWithRoute,self) +if self.uncontrolled and self.activate_uncontrolled then +self.sid_Activate=self:ScheduleRepeat(self.activate_delay,self.activate_delta,self.activate_frand,nil,RAT._ActivateUncontrolled,self) +end +return true +end +function RAT:_CheckConsistency() +self:F2() +if not self.random_departure then +for _,name in pairs(self.departure_ports)do +if self:_AirportExists(name)then +self.Ndeparture_Airports=self.Ndeparture_Airports+1 +elseif self:_ZoneExists(name)then +self.Ndeparture_Zones=self.Ndeparture_Zones+1 +end +end +if self.Ndeparture_Zones>0 and self.takeoff~=RAT.wp.air then +self.takeoff=RAT.wp.air +self:E(self.lid..string.format("WARNING: At least one zone defined as departure and takeoff is NOT set to air. Enabling air start for RAT group %s!",self.alias)) +end +if self.Ndeparture_Airports==0 and self.Ndeparture_Zone==0 then +self.random_departure=true +local text=string.format("No airports or zones found given in SetDeparture(). Enabling random departure airports for RAT group %s!",self.alias) +self:E(self.lid.."ERROR: "..text) +MESSAGE:New(text,30):ToAll() +end +end +if not self.random_destination then +for _,name in pairs(self.destination_ports)do +if self:_AirportExists(name)then +self.Ndestination_Airports=self.Ndestination_Airports+1 +elseif self:_ZoneExists(name)then +self.Ndestination_Zones=self.Ndestination_Zones+1 +end +end +if self.Ndestination_Zones>0 and self.landing~=RAT.wp.air and not self.returnzone then +self.landing=RAT.wp.air +self.destinationzone=true +self:E(self.lid.."WARNING: At least one zone defined as destination and landing is NOT set to air. Enabling destination zone!") +end +if self.Ndestination_Airports==0 and self.Ndestination_Zones==0 then +self.random_destination=true +local text="No airports or zones found given in SetDestination(). Enabling random destination airports!" +self:E(self.lid.."ERROR: "..text) +MESSAGE:New(text,30):ToAll() +end +end +if self.destinationzone and self.returnzone then +self:E(self.lid.."ERROR: Destination zone _and_ return to zone not possible! Disabling return to zone.") +self.returnzone=false +end +if self.returnzone and self.takeoff==RAT.wp.air then +self.landing=RAT.wp.air +end +if self.FLminuser then +self.FLminuser=math.min(self.FLminuser,self.aircraft.ceiling) +end +if self.FLmaxuser then +self.FLmaxuser=math.min(self.FLmaxuser,self.aircraft.ceiling) +end +if self.FLcruise then +self.FLcruise=math.min(self.FLcruise,self.aircraft.ceiling) +end +if self.FLminuser and self.FLmaxuser then +if self.FLminuser>self.FLmaxuser then +local min=self.FLminuser +local max=self.FLmaxuser +self.FLminuser=max +self.FLmaxuser=min +end +end +if self.FLminuser and self.FLcruiseself.FLmaxuser then +self.FLcruise=self.FLmaxuser +end +if self.uncontrolled then +self.takeoff=RAT.wp.cold +end +end +function RAT:SetCoalition(friendly) +self:F2(friendly) +if friendly:lower()=="sameonly"then +self.friendly=RAT.coal.sameonly +elseif friendly:lower()=="neutral"then +self.friendly=RAT.coal.neutral +else +self.friendly=RAT.coal.same +end +return self +end +function RAT:SetCoalitionAircraft(color) +self:F2(color) +if color:lower()=="blue"then +self.coalition=coalition.side.BLUE +if not self.country then +self.country=country.id.USA +end +elseif color:lower()=="red"then +self.coalition=coalition.side.RED +if not self.country then +self.country=country.id.RUSSIA +end +elseif color:lower()=="neutral"then +self.coalition=coalition.side.NEUTRAL +if not self.country then +self.country=country.id.SWITZERLAND +end +end +return self +end +function RAT:SetCountry(id) +self:F2(id) +self.country=id +return self +end +function RAT:SetTerminalType(termtype) +self:F2(termtype) +self.termtype=termtype +return self +end +function RAT:SetParkingScanRadius(radius) +self:F2(radius) +self.parkingscanradius=radius or 50 +return self +end +function RAT:SetParkingScanSceneryON() +self:F2() +self.parkingscanscenery=true +return self +end +function RAT:SetParkingScanSceneryOFF() +self:F2() +self.parkingscanscenery=false +return self +end +function RAT:SetParkingSpotSafeON() +self:F2() +self.parkingverysafe=true +return self +end +function RAT:SetParkingSpotSafeOFF() +self:F2() +self.parkingverysafe=false +return self +end +function RAT:SetDespawnAirOFF() +self.despawnair=false +return self +end +function RAT:SetTakeoff(type) +self:F2(type) +local _Type +if type:lower()=="takeoff-cold"or type:lower()=="cold"then +_Type=RAT.wp.cold +elseif type:lower()=="takeoff-hot"or type:lower()=="hot"then +_Type=RAT.wp.hot +elseif type:lower()=="takeoff-runway"or type:lower()=="runway"then +_Type=RAT.wp.runway +elseif type:lower()=="air"then +_Type=RAT.wp.air +else +_Type=RAT.wp.coldorhot +end +self.takeoff=_Type +return self +end +function RAT:SetTakeoffCold() +self.takeoff=RAT.wp.cold +return self +end +function RAT:SetTakeoffHot() +self.takeoff=RAT.wp.hot +return self +end +function RAT:SetTakeoffRunway() +self.takeoff=RAT.wp.runway +return self +end +function RAT:SetTakeoffColdOrHot() +self.takeoff=RAT.wp.coldorhot +return self +end +function RAT:SetTakeoffAir() +self.takeoff=RAT.wp.air +return self +end +function RAT:SetDeparture(departurenames) +self:F2(departurenames) +self.random_departure=false +local names +if type(departurenames)=="table"then +names=departurenames +elseif type(departurenames)=="string"then +names={departurenames} +else +self:E(self.lid.."ERROR: Input parameter must be a string or a table in SetDeparture()!") +end +for _,name in pairs(names)do +if self:_AirportExists(name)then +table.insert(self.departure_ports,name) +elseif self:_ZoneExists(name)then +table.insert(self.departure_ports,name) +else +self:E(self.lid.."ERROR: No departure airport or zone found with name "..name) +end +end +return self +end +function RAT:SetDestination(destinationnames) +self:F2(destinationnames) +self.random_destination=false +local names +if type(destinationnames)=="table"then +names=destinationnames +elseif type(destinationnames)=="string"then +names={destinationnames} +else +self:E(self.lid.."ERROR: Input parameter must be a string or a table in SetDestination()!") +end +for _,name in pairs(names)do +if self:_AirportExists(name)then +table.insert(self.destination_ports,name) +elseif self:_ZoneExists(name)then +table.insert(self.destination_ports,name) +else +self:E(self.lid.."ERROR: No destination airport or zone found with name "..name) +end +end +return self +end +function RAT:DestinationZone() +self:F2() +self.destinationzone=true +self.landing=RAT.wp.air +return self +end +function RAT:ReturnZone() +self:F2() +self.returnzone=true +return self +end +function RAT:SetDestinationsFromZone(zone) +self:F2(zone) +self.random_destination=false +self.destination_Azone=zone +return self +end +function RAT:SetDeparturesFromZone(zone) +self:F2(zone) +self.random_departure=false +self.departure_Azone=zone +return self +end +function RAT:AddFriendlyAirportsToDepartures() +self:F2() +self.addfriendlydepartures=true +return self +end +function RAT:AddFriendlyAirportsToDestinations() +self:F2() +self.addfriendlydestinations=true +return self +end +function RAT:ExcludedAirports(ports) +self:F2(ports) +if type(ports)=="string"then +self.excluded_ports={ports} +else +self.excluded_ports=ports +end +return self +end +function RAT:SetAISkill(skill) +self:F2(skill) +if skill:lower()=="average"then +self.skill="Average" +elseif skill:lower()=="good"then +self.skill="Good" +elseif skill:lower()=="excellent"then +self.skill="Excellent" +elseif skill:lower()=="random"then +self.skill="Random" +else +self.skill="High" +end +return self +end +function RAT:Livery(skins) +self:F2(skins) +if type(skins)=="string"then +self.livery={skins} +else +self.livery=skins +end +return self +end +function RAT:ChangeAircraft(actype) +self:F2(actype) +self.actype=actype +return self +end +function RAT:ContinueJourney() +self:F2() +self.continuejourney=true +self.commute=false +return self +end +function RAT:Commute(starshape) +self:F2() +self.commute=true +self.continuejourney=false +if starshape then +self.starshape=starshape +else +self.starshape=false +end +return self +end +function RAT:SetSpawnDelay(delay) +self:F2(delay) +delay=delay or 5 +self.spawndelay=math.max(0.5,delay) +return self +end +function RAT:SetSpawnInterval(interval) +self:F2(interval) +interval=interval or 5 +self.spawninterval=math.max(0.5,interval) +return self +end +function RAT:SetSpawnLimit(Nmax) +self.NspawnMax=Nmax +return self +end +function RAT:RespawnAfterLanding(delay) +self:F2(delay) +self.respawn_at_landing=true +self:SetRespawnDelay(delay) +return self +end +function RAT:SetRespawnDelay(delay) +self:F2(delay) +delay=delay or 1.0 +delay=math.max(1.0,delay) +self.respawn_delay=delay +return self +end +function RAT:NoRespawn() +self:F2() +self.norespawn=true +return self +end +function RAT:SetMaxRespawnTriedWhenSpawnedOnRunway(n) +self:F2(n) +n=n or 3 +self.onrunwaymaxretry=n +return self +end +function RAT:RespawnAfterTakeoff() +self:F2() +self.respawn_after_takeoff=true +return self +end +function RAT:RespawnAfterCrashON() +self:F2() +self.respawn_after_crash=true +return self +end +function RAT:RespawnAfterCrashOFF() +self:F2() +self.respawn_after_crash=false +return self +end +function RAT:RespawnInAirAllowed() +self:F2() +self.respawn_inair=true +return self +end +function RAT:RespawnInAirNotAllowed() +self:F2() +self.respawn_inair=false +return self +end +function RAT:CheckOnRunway(switch,distance) +self:F2(switch) +if switch==nil then +switch=true +end +self.checkonrunway=switch +self.onrunwayradius=distance or 75 +return self +end +function RAT:CheckOnTop(switch,radius) +self:F2(switch) +if switch==nil then +switch=true +end +self.checkontop=switch +self.ontopradius=radius or 2 +return self +end +function RAT:RadioON() +self:F2() +self.radio=true +return self +end +function RAT:RadioOFF() +self:F2() +self.radio=false +return self +end +function RAT:RadioFrequency(frequency) +self:F2(frequency) +self.frequency=frequency +return self +end +function RAT:RadioModulation(modulation) +self:F2(modulation) +if modulation=="AM"then +self.modulation=radio.modulation.AM +elseif modulation=="FM"then +self.modulation=radio.modulation.FM +else +self.modulation=radio.modulation.AM +end +return self +end +function RAT:RadioMenuON() +self:F2() +self.f10menu=true +return self +end +function RAT:RadioMenuOFF() +self:F2() +self.f10menu=false +return self +end +function RAT:Invisible() +self:F2() +self.invisible=true +return self +end +function RAT:SetEPLRS(switch) +if switch==nil or switch==true then +self.eplrs=true +else +self.eplrs=false +end +return self +end +function RAT:Immortal() +self:F2() +self.immortal=true +return self +end +function RAT:Uncontrolled() +self:F2() +self.uncontrolled=true +return self +end +function RAT:ActivateUncontrolled(maxactivated,delay,delta,frand) +self:F2({max=maxactivated,delay=delay,delta=delta,rand=frand}) +self.activate_uncontrolled=true +self.activate_max=maxactivated or 1 +self.activate_delay=delay or 1 +self.activate_delta=delta or 1 +self.activate_frand=frand or 0 +self.activate_delay=math.max(self.activate_delay,1) +self.activate_delta=math.max(self.activate_delta,0) +self.activate_frand=math.max(self.activate_frand,0) +self.activate_frand=math.min(self.activate_frand,1) +return self +end +function RAT:TimeDestroyInactive(time) +self:F2(time) +time=time or self.Tinactive +time=math.max(time,60) +self.Tinactive=time +return self +end +function RAT:SetMaxCruiseSpeed(speed) +self:F2(speed) +self.Vcruisemax=speed/3.6 +return self +end +function RAT:SetClimbRate(rate) +self:F2(rate) +rate=rate or self.Vclimb +rate=math.max(rate,100) +rate=math.min(rate,15000) +self.Vclimb=rate +return self +end +function RAT:SetDescentAngle(angle) +self:F2(angle) +angle=angle or self.AlphaDescent +angle=math.max(angle,0.5) +angle=math.min(angle,50) +self.AlphaDescent=angle +return self +end +function RAT:SetROE(roe) +self:F2(roe) +if roe=="return"then +self.roe=RAT.ROE.returnfire +elseif roe=="free"then +self.roe=RAT.ROE.weaponfree +else +self.roe=RAT.ROE.weaponhold +end +return self +end +function RAT:SetROT(rot) +self:F2(rot) +if rot=="passive"then +self.rot=RAT.ROT.passive +elseif rot=="evade"then +self.rot=RAT.ROT.evade +else +self.rot=RAT.ROT.noreaction +end +return self +end +function RAT:MenuName(name) +self:F2(name) +self.SubMenuName=tostring(name) +return self +end +function RAT:EnableATC(switch) +self:F2(switch) +if switch==nil then +switch=true +end +self.ATCswitch=switch +return self +end +function RAT:ATC_Messages(switch) +self:F2(switch) +if switch==nil then +switch=true +end +RAT.ATC.messages=switch +return self +end +function RAT:ATC_Clearance(n) +self:F2(n) +RAT.ATC.Nclearance=n or 2 +return self +end +function RAT:ATC_Delay(time) +self:F2(time) +RAT.ATC.delay=time or 240 +return self +end +function RAT:SetMinDistance(dist) +self:F2(dist) +self.mindist=math.max(100,dist*1000) +return self +end +function RAT:SetMaxDistance(dist) +self:F2(dist) +self.maxdist=dist*1000 +return self +end +function RAT:_Debug(switch) +self:F2(switch) +if switch==nil then +switch=true +end +self.Debug=switch +return self +end +function RAT:Debugmode() +self:F2() +self.Debug=true +return self +end +function RAT:StatusReports(switch) +self:F2(switch) +if switch==nil then +switch=true +end +self.reportstatus=switch +return self +end +function RAT:PlaceMarkers(switch) +self:F2(switch) +if switch==nil then +switch=true +end +self.placemarkers=switch +return self +end +function RAT:SetFL(FL) +self:F2(FL) +FL=FL or self.FLcruise +FL=math.max(FL,0) +self.FLuser=FL*RAT.unit.FL2m +return self +end +function RAT:SetFLmax(FL) +self:F2(FL) +self.FLmaxuser=FL*RAT.unit.FL2m +return self +end +function RAT:SetMaxCruiseAltitude(alt) +self:F2(alt) +self.FLmaxuser=alt +return self +end +function RAT:SetFLmin(FL) +self:F2(FL) +self.FLminuser=FL*RAT.unit.FL2m +return self +end +function RAT:SetMinCruiseAltitude(alt) +self:F2(alt) +self.FLminuser=alt +return self +end +function RAT:SetFLcruise(FL) +self:F2(FL) +self.FLcruise=FL*RAT.unit.FL2m +return self +end +function RAT:SetCruiseAltitude(alt) +self:F2(alt) +self.FLcruise=alt +return self +end +function RAT:SetOnboardNum(tailnumprefix,zero) +self:F2({tailnumprefix=tailnumprefix,zero=zero}) +self.onboardnum=tailnumprefix +if zero~=nil then +self.onboardnum0=zero +end +return self +end +function RAT:_InitAircraft(DCSgroup) +self:F2(DCSgroup) +local DCSunit=DCSgroup:getUnit(1) +local DCSdesc=DCSunit:getDesc() +local DCScategory=DCSgroup:getCategory() +local DCStype=DCSunit:getTypeName() +if DCScategory==Group.Category.AIRPLANE then +self.category=RAT.cat.plane +elseif DCScategory==Group.Category.HELICOPTER then +self.category=RAT.cat.heli +else +self.category="other" +self:E(self.lid.."ERROR: Group of RAT is neither airplane nor helicopter!") +end +self.aircraft.type=DCStype +self.aircraft.fuel=DCSunit:getFuel() +self.aircraft.Rmax=DCSdesc.range*RAT.unit.nm2m +self.aircraft.Reff=self.aircraft.Rmax*self.aircraft.fuel*0.95 +self.aircraft.Vmax=DCSdesc.speedMax +self.aircraft.Vymax=DCSdesc.VyMax +self.aircraft.ceiling=DCSdesc.Hmax +if DCSdesc.box then +self.aircraft.length=DCSdesc.box.max.x +self.aircraft.height=DCSdesc.box.max.y +self.aircraft.width=DCSdesc.box.max.z +elseif DCStype=="Mirage-F1CE"then +self.aircraft.length=16 +self.aircraft.height=5 +self.aircraft.width=9 +elseif DCStype=="Saab340"then +self.aircraft.length=19.73 +self.aircraft.height=6.97 +self.aircraft.width=21.44 +end +self.aircraft.box=math.max(self.aircraft.length,self.aircraft.width) +local text=string.format("\n******************************************************\n") +text=text..string.format("Aircraft parameters:\n") +text=text..string.format("Template group = %s\n",self.SpawnTemplatePrefix) +text=text..string.format("Alias = %s\n",self.alias) +text=text..string.format("Category = %s\n",self.category) +text=text..string.format("Type = %s\n",self.aircraft.type) +text=text..string.format("Length (x) = %6.1f m\n",self.aircraft.length) +text=text..string.format("Width (z) = %6.1f m\n",self.aircraft.width) +text=text..string.format("Height (y) = %6.1f m\n",self.aircraft.height) +text=text..string.format("Max air speed = %6.1f m/s\n",self.aircraft.Vmax) +text=text..string.format("Max climb speed = %6.1f m/s\n",self.aircraft.Vymax) +text=text..string.format("Initial Fuel = %6.1f\n",self.aircraft.fuel*100) +text=text..string.format("Max range = %6.1f km\n",self.aircraft.Rmax/1000) +text=text..string.format("Eff range = %6.1f km (with 95 percent initial fuel amount)\n",self.aircraft.Reff/1000) +text=text..string.format("Ceiling = %6.1f km = FL%3.0f\n",self.aircraft.ceiling/1000,self.aircraft.ceiling/RAT.unit.FL2m) +text=text..string.format("******************************************************\n") +self:T(self.lid..text) +end +function RAT:_SpawnWithRoute(_departure,_destination,_takeoff,_landing,_livery,_waypoint,_lastpos,_nrespawn,parkingdata) +self:F({rat=self.lid,departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,waypoint=_waypoint,lastpos=_lastpos,nrespawn=_nrespawn}) +if self.NspawnMax and self.SpawnIndex>=self.NspawnMax then +self:T(self.lid..string.format("Max limit of spawns reached %d >= %d! Will not spawn any more groups",self.NspawnMax,self.SpawnIndex)) +return +else +self:T2(self.lid..string.format("Spawning with spawn index=%d",self.SpawnIndex)) +end +local takeoff=self.takeoff +local landing=self.landing +if _takeoff then +takeoff=_takeoff +end +if _landing then +landing=_landing +end +if takeoff==RAT.wp.coldorhot then +local temp={RAT.wp.cold,RAT.wp.hot} +takeoff=temp[math.random(2)] +end +local nrespawn=0 +if _nrespawn then +nrespawn=_nrespawn +end +local departure,destination,waypoints,wpdesc,wpstatus=self:_SetRoute(takeoff,landing,_departure,_destination,_waypoint) +if not(departure and destination and waypoints)then +return nil +end +local livery +if _livery then +livery=_livery +elseif self.livery then +livery=self.livery[math.random(#self.livery)] +local text=string.format("Chosen livery for group %s: %s",self:_AnticipatedGroupName(),livery) +self:T(self.lid..text) +else +livery=nil +end +local uncontrolled=self.uncontrolled +local isFlightcontrol=self:_IsFlightControlAirbase(departure) +if takeoff~=RAT.wp.air and departure and isFlightcontrol then +takeoff=RAT.wp.cold +uncontrolled=true +end +local successful=self:_ModifySpawnTemplate(waypoints,livery,_lastpos,departure,takeoff,parkingdata,uncontrolled) +if not successful then +return nil +end +local group=self:SpawnWithIndex(self.SpawnIndex) +local groupname=group:GetName() +local flightgroup=FLIGHTGROUP:New(group) +if self.ATCswitch then +flightgroup.holdtime=nil +end +flightgroup.stuckDespawn=false +self.alive=self.alive+1 +self:T(self.lid..string.format("Alive groups counter now = %d.",self.alive)) +if self.ATCswitch and landing==RAT.wp.landing then +local airbasename=destination:GetName() +if self.returnzone then +airbasename=departure:GetName() +end +if not self:_IsFlightControlAirbase(airbasename)then +self:_ATCAddFlight(groupname,airbasename) +end +end +if self.placemarkers then +self:_PlaceMarkers(waypoints,wpdesc,self.SpawnIndex) +end +if isFlightcontrol and not self.activate_uncontrolled then +local N=math.random(120) +self:T(self.lid..string.format("Flight will be ready for takeoff in %d seconds",N)) +flightgroup:SetReadyForTakeoff(true,N) +end +if self.invisible then +flightgroup:SetDefaultInvisible(true) +flightgroup:SwitchInvisible(true) +end +if self.immortal then +flightgroup:SetDefaultImmortal(true) +flightgroup:SwitchImmortal(true) +end +if self.eplrs then +flightgroup:SetDefaultEPLRS(true) +flightgroup:SwitchEPLRS(true) +end +self:_SetROE(flightgroup,self.roe) +self:_SetROT(flightgroup,self.rot) +local ratcraft={} +ratcraft.index=self.SpawnIndex +ratcraft.group=group +ratcraft.flightgroup=flightgroup +ratcraft.destination=destination +ratcraft.departure=departure +ratcraft.waypoints=waypoints +ratcraft.airborne=group:InAir() +ratcraft.nunits=group:GetInitialSize() +ratcraft.Pnow=group:GetCoordinate() +ratcraft.Distance=0 +ratcraft.takeoff=takeoff +ratcraft.landing=landing +ratcraft.wpdesc=wpdesc +ratcraft.wpstatus=wpstatus +ratcraft.active=not uncontrolled +ratcraft.status=RAT.status.Spawned +ratcraft.livery=livery +ratcraft.despawnme=false +ratcraft.nrespawn=nrespawn +self.ratcraft[self.SpawnIndex]=ratcraft +if self.f10menu then +local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex) +self.Menu[self.SubMenuName].groups[self.SpawnIndex]=MENU_MISSION:New(name,self.Menu[self.SubMenuName].groups) +self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"]=MENU_MISSION:New("Set ROE",self.Menu[self.SubMenuName].groups[self.SpawnIndex]) +MENU_MISSION_COMMAND:New("Weapons hold",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,flightgroup,RAT.ROE.weaponhold) +MENU_MISSION_COMMAND:New("Weapons free",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,flightgroup,RAT.ROE.weaponfree) +MENU_MISSION_COMMAND:New("Return fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,flightgroup,RAT.ROE.returnfire) +self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"]=MENU_MISSION:New("Set ROT",self.Menu[self.SubMenuName].groups[self.SpawnIndex]) +MENU_MISSION_COMMAND:New("No reaction",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,flightgroup,RAT.ROT.noreaction) +MENU_MISSION_COMMAND:New("Passive defense",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,flightgroup,RAT.ROT.passive) +MENU_MISSION_COMMAND:New("Evade on fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,flightgroup,RAT.ROT.evade) +MENU_MISSION_COMMAND:New("Despawn group",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._Despawn,self,group) +MENU_MISSION_COMMAND:New("Place markers",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._PlaceMarkers,self,waypoints,self.SpawnIndex) +MENU_MISSION_COMMAND:New("Status report",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self.Status,self,true,self.SpawnIndex) +end +function flightgroup.OnAfterPassingWaypoint(Flightgroup,From,Event,To,Waypoint) +local waypoint=Waypoint +local flightgroup=Flightgroup +local wpid=waypoint.uid +local ratcraft=self:_GetRatcraftFromGroup(flightgroup.group) +local wpdescription=tostring(ratcraft.wpdesc[wpid]) +local wpstatus=ratcraft.wpstatus[wpid] +self:T(self.lid..string.format("RAT passed waypoint %s [uid=%d]: %s [status=%s]",waypoint.name,wpid,wpdescription,wpstatus)) +self:_SetStatus(group,wpstatus) +if waypoint.uid==3 then +end +end +function flightgroup.OnAfterPassedFinalWaypoint(flightgroup,From,Event,To) +self:T(self.lid..string.format("RAT passed FINAL waypoint")) +local ratcraft=self:_GetRatcraftFromGroup(flightgroup.group) +local text=string.format("Flight %s arrived at final destination %s.",group:GetName(),destination:GetName()) +MESSAGE:New(text,10):ToAllIf(self.reportstatus) +self:T(self.lid..text) +if landing==RAT.wp.air then +local text=string.format("Activating despawn switch for flight %s! Group will be detroyed soon.",group:GetName()) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:T(self.lid..text) +ratcraft.despawnme=true +end +end +function flightgroup.OnAfterRTB(flightgroup,From,Event,To,airbase,SpeedTo,SpeedHold,SpeedLand) +self:T(self.lid..string.format("RAT group is RTB")) +end +function flightgroup.OnAfterHolding(Flightgroup,From,Event,To) +local flightgroup=Flightgroup +local ratcraft=self:_GetRatcraftFromGroup(flightgroup.group) +local destinationname=ratcraft.destination:GetName() +local text=string.format("Flight %s to %s ATC: Holding and awaiting landing clearance.",groupname,destinationname) +self:T(self.lid..text) +MESSAGE:New(text,10):ToAllIf(self.reportstatus) +local fc=_DATABASE:GetFlightControl(destinationname) +if self.ATCswitch and not fc then +self:T(self.lid..string.format("RAT group is HOLDING ==> ATCRegisterFlight")) +if self.f10menu then +MENU_MISSION_COMMAND:New("Clear for landing",self.Menu[self.SubMenuName].groups[self.SpawnIndex],flightgroup.ClearToLand,flightgroup) +end +RAT._ATCRegisterFlight(groupname,timer.getTime()) +end +end +function flightgroup.OnAfterLanded(Flightgroup,From,Event,To,Airport) +self:T(self.lid..string.format("RAT group landed at airbase")) +end +function flightgroup.OnAfterArrived(Flightgroup,From,Event,To) +self:T(self.lid..string.format("RAT group arrived")) +end +function flightgroup.OnAfterStuck(Flightgroup,From,Event,To,Stucktime) +local flightgroup=Flightgroup +self:T(self.lid..string.format("Group %s got stuck for %d seconds",flightgroup:GetName(),Stucktime)) +if Stucktime>10*60 then +self:_Respawn(flightgroup.group) +end +end +return self.SpawnIndex +end +function RAT:_IsFlightControlAirbase(airbase) +if type(airbase)=="table"then +airbase=airbase:GetName() +end +if airbase then +local fc=_DATABASE:GetFlightControl(airbase) +if fc then +self:T(self.lid..string.format("Airbase %s has a FLIGHTCONTROL running",airbase)) +return true +else +return false +end +end +return nil +end +function RAT:ClearForLanding(name) +trigger.action.setUserFlag(name,1) +local flagvalue=trigger.misc.getUserFlag(name) +self:T(self.lid.."ATC: User flag value (landing) for "..name.." set to "..flagvalue) +end +function RAT:_Respawn(group,lastpos,delay) +if delay and delay>0 then +self:ScheduleOnce(delay,RAT._Respawn,self,group,lastpos,0) +else +if group then +self:T(self.lid..string.format("Respawning ratcraft from group %s",group:GetName())) +else +self:E(self.lid..string.format("ERROR: group is nil in _Respawn!")) +return nil +end +local ratcraft=self:_GetRatcraftFromGroup(group) +lastpos=lastpos or group:GetCoordinate() +local departure=ratcraft.departure +local destination=ratcraft.destination +local takeoff=ratcraft.takeoff +local landing=ratcraft.landing +local livery=ratcraft.livery +local lastwp=ratcraft.waypoints[#ratcraft.waypoints] +local flightgroup=ratcraft.flightgroup +local parkingdata=nil +if self.continuejourney or self.commute then +for _,_element in pairs(flightgroup.elements)do +local element=_element +if element.parking then +if parkingdata==nil then +parkingdata={} +end +self:T(self.lid..string.format("Element %s was parking at spot id=%d",element.name,element.parking.TerminalID)) +table.insert(parkingdata,UTILS.DeepCopy(element.parking)) +else +self:E(self.lid..string.format("WARNING: Element %s did NOT have a not parking spot!",tostring(element.name))) +end +end +end +self:_Despawn(ratcraft.group) +local _departure=nil +local _destination=nil +local _takeoff=nil +local _landing=nil +local _livery=nil +local _lastwp=nil +local _lastpos=nil +if self.continuejourney then +_departure=destination:GetName() +_livery=livery +if landing==RAT.wp.landing and not(self.respawn_at_landing or self.respawn_after_takeoff)then +if destination:GetCategory()==4 then +_lastpos=lastpos +end +end +if self.destinationzone then +_takeoff=RAT.wp.air +_landing=RAT.wp.air +elseif self.returnzone then +_takeoff=self.takeoff +if self.takeoff==RAT.wp.air then +_landing=RAT.wp.air +else +_landing=RAT.wp.landing +end +_departure=departure:GetName() +else +_takeoff=self.takeoff +_landing=self.landing +end +elseif self.commute then +if self.starshape==true then +if destination:GetName()==self.homebase then +_departure=self.homebase +_destination=nil +else +_departure=destination:GetName() +_destination=self.homebase +end +else +_departure=destination:GetName() +_destination=departure:GetName() +end +_livery=livery +if landing==RAT.wp.landing and lastpos and not(self.respawn_at_landing or self.respawn_after_takeoff)then +if destination:GetCategory()==4 then +_lastpos=lastpos +end +end +if self.destinationzone then +if self.takeoff==RAT.wp.air then +_takeoff=RAT.wp.air +_landing=RAT.wp.air +else +if takeoff==RAT.wp.air then +_takeoff=self.takeoff +_landing=RAT.wp.air +else +_takeoff=RAT.wp.air +_landing=RAT.wp.landing +end +end +elseif self.returnzone then +_departure=departure:GetName() +_destination=destination:GetName() +_takeoff=self.takeoff +_landing=self.landing +end +end +if _takeoff==RAT.wp.air and(self.continuejourney or self.commute)then +_lastwp=lastwp +end +self:T2({departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,lastwp=_lastwp}) +local respawndelay=self.respawn_delay or 1 +self:T(self.lid..string.format("%s delayed respawn in %.1f seconds.",self.alias,respawndelay)) +self:ScheduleOnce(respawndelay,RAT._SpawnWithRoute,self,_departure,_destination,_takeoff,_landing,_livery,nil,_lastpos,nil,parkingdata) +end +end +function RAT:_Despawn(group,delay) +if delay and delay>0 then +self:ScheduleOnce(delay,RAT._Despawn,self,group,0) +else +if group then +local index=self:GetSpawnIndexFromGroup(group) +if index then +self:T(self.lid..string.format("Despawning group %s (index=%d)",group:GetName(),index)) +local ratcraft=self.ratcraft[index] +ratcraft.flightgroup:Despawn() +ratcraft.flightgroup:__Stop(0.1) +self.ratcraft[index].group=nil +self.ratcraft[index]["status"]="Dead" +self.ratcraft[index]=nil +if self.f10menu and self.SubMenuName~=nil then +self.Menu[self.SubMenuName]["groups"][index]:Remove() +end +end +end +end +end +function RAT:_SetRoute(takeoff,landing,_departure,_destination,_waypoint) +local VxCruiseMax +if self.Vcruisemax then +VxCruiseMax=math.min(self.Vcruisemax,self.aircraft.Vmax) +else +VxCruiseMax=math.min(self.aircraft.Vmax*0.90,250) +end +local VxCruiseMin=math.min(VxCruiseMax*0.70,166) +local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax) +local VxClimb=math.min(self.aircraft.Vmax*0.90,200) +local VxDescent=math.min(self.aircraft.Vmax*0.60,140) +local VxHolding=VxDescent*0.9 +local VxFinal=VxHolding*0.9 +local VyClimb=math.min(self.Vclimb*RAT.unit.ft2meter/60,self.aircraft.Vymax) +local AlphaClimb=math.asin(VyClimb/VxClimb) +local AlphaDescent=math.rad(self.AlphaDescent) +local FLcruise_expect=self.FLcruise +local departure=nil +if _departure then +if self:_AirportExists(_departure)then +departure=AIRBASE:FindByName(_departure) +if takeoff==RAT.wp.air then +departure=departure:GetZone() +end +elseif self:_ZoneExists(_departure)then +departure=ZONE:FindByName(_departure) +else +local text=string.format("ERROR! Specified departure airport %s does not exist for %s.",_departure,self.alias) +self:E(self.lid..text) +end +else +departure=self:_PickDeparture(takeoff) +if self.commute and self.starshape==true and self.homebase==nil then +self.homebase=departure:GetName() +end +end +if not departure then +local text=string.format("ERROR! No valid departure airport could be found for %s.",self.alias) +self:E(self.lid..text) +return nil +end +local Pdeparture +if takeoff==RAT.wp.air then +if _waypoint then +Pdeparture=COORDINATE:New(_waypoint.x,_waypoint.alt,_waypoint.y) +else +local vec2=departure:GetRandomVec2() +Pdeparture=COORDINATE:NewFromVec2(vec2) +end +else +Pdeparture=departure:GetCoordinate() +end +local H_departure +if takeoff==RAT.wp.air then +local Hmin +if self.category==RAT.cat.plane then +Hmin=1000 +else +Hmin=50 +end +H_departure=self:_Randomize(FLcruise_expect*0.7,0.3,Pdeparture.y+Hmin,FLcruise_expect) +if self.FLminuser then +H_departure=math.max(H_departure,self.FLminuser) +end +if _waypoint then +H_departure=_waypoint.alt +end +else +H_departure=Pdeparture.y +end +local mindist=self.mindist +if self.FLminuser then +local hclimb=self.FLminuser-H_departure +local hdescent=self.FLminuser-H_departure +local Dclimb,Ddescent,Dtot=self:_MinDistance(AlphaClimb,AlphaDescent,hclimb,hdescent) +if takeoff==RAT.wp.air and landing==RAT.wpair then +mindist=0 +elseif takeoff==RAT.wp.air then +mindist=Ddescent +elseif landing==RAT.wp.air then +mindist=Dclimb +else +mindist=Dtot +end +mindist=math.max(self.mindist,mindist) +local text=string.format("Adjusting min distance to %d km (for given min FL%03d)",mindist/1000,self.FLminuser/RAT.unit.FL2m) +self:T(self.lid..text) +end +local destination=nil +if _destination then +if self:_AirportExists(_destination)then +destination=AIRBASE:FindByName(_destination) +if landing==RAT.wp.air or self.returnzone then +destination=destination:GetZone() +end +elseif self:_ZoneExists(_destination)then +destination=ZONE:FindByName(_destination) +else +local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!",_destination,self.alias) +self:E(self.lid.."ERROR: "..text) +end +else +local random=self.random_destination +if self.continuejourney and _departure and#self.destination_ports<3 then +random=true +end +local mylanding=landing +local acrange=self.aircraft.Reff +if self.returnzone then +mylanding=RAT.wp.air +acrange=self.aircraft.Reff/2 +end +destination=self:_PickDestination(departure,Pdeparture,mindist,math.min(acrange,self.maxdist),random,mylanding) +end +if not destination then +local text=string.format("No valid destination airport could be found for %s!",self.alias) +MESSAGE:New(text,60):ToAll() +self:E(self.lid.."ERROR: "..text) +return nil +end +if destination:GetName()==departure:GetName()then +local text=string.format("%s: Destination and departure are identical. Airport/zone %s.",self.alias,destination:GetName()) +MESSAGE:New(text,30):ToAll() +self:E(self.lid.."ERROR: "..text) +end +local Preturn +local destination_returnzone +if self.returnzone then +local vec2=destination:GetRandomVec2() +Preturn=COORDINATE:NewFromVec2(vec2) +destination_returnzone=destination +destination=departure +end +local Pdestination +if landing==RAT.wp.air then +local vec2=destination:GetRandomVec2() +Pdestination=COORDINATE:NewFromVec2(vec2) +else +Pdestination=destination:GetCoordinate() +end +local H_destination=Pdestination.y +local Rhmin=8000 +local Rhmax=20000 +if self.category==RAT.cat.heli then +Rhmin=500 +Rhmax=1000 +end +local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax,Rhmin) +local Pholding=COORDINATE:NewFromVec2(Vholding) +local H_holding=Pholding.y +local h_holding +if self.category==RAT.cat.plane then +h_holding=1200 +else +h_holding=150 +end +h_holding=self:_Randomize(h_holding,0.2) +local Hh_holding=H_holding+h_holding +if landing==RAT.wp.air then +Hh_holding=H_departure +end +local d_holding=Pholding:Get2DDistance(Pdestination) +local heading +local d_total +if self.returnzone then +heading=self:_Course(Pdeparture,Preturn) +d_total=Pdeparture:Get2DDistance(Preturn)+Preturn:Get2DDistance(Pholding) +else +heading=self:_Course(Pdeparture,Pholding) +d_total=Pdeparture:Get2DDistance(Pholding) +end +if takeoff==RAT.wp.air then +local H_departure_max +if landing==RAT.wp.air then +H_departure_max=H_departure +else +H_departure_max=d_total*math.tan(AlphaDescent)+Hh_holding +end +H_departure=math.min(H_departure,H_departure_max) +end +local deltaH=math.abs(H_departure-Hh_holding) +local phi=math.atan(deltaH/d_total) +local phi_climb +local phi_descent +if(H_departure>Hh_holding)then +phi_climb=AlphaClimb+phi +phi_descent=AlphaDescent-phi +else +phi_climb=AlphaClimb-phi +phi_descent=AlphaDescent+phi +end +local D_total +if self.returnzone then +D_total=math.sqrt(deltaH*deltaH+d_total/2*d_total/2) +else +D_total=math.sqrt(deltaH*deltaH+d_total*d_total) +end +local gamma=math.rad(180)-phi_climb-phi_descent +local a=D_total*math.sin(phi_climb)/math.sin(gamma) +local b=D_total*math.sin(phi_descent)/math.sin(gamma) +local hphi_max=b*math.sin(phi_climb) +local hphi_max2=a*math.sin(phi_descent) +local h_max1=b*math.sin(AlphaClimb) +local h_max2=a*math.sin(AlphaDescent) +local h_max +if(H_departure>Hh_holding)then +h_max=math.min(h_max1,h_max2) +else +h_max=math.max(h_max1,h_max2) +end +local FLmax=h_max+H_departure +local FLmin=math.max(H_departure,Hh_holding) +if self.category==RAT.cat.heli then +FLmin=math.max(H_departure,H_destination)+50 +FLmax=math.max(H_departure,H_destination)+1000 +end +FLmax=math.min(FLmax,self.aircraft.ceiling) +if self.FLminuser then +FLmin=math.max(self.FLminuser,FLmin) +end +if self.FLmaxuser then +FLmax=math.min(self.FLmaxuser,FLmax) +end +if FLmin>FLmax then +FLmin=FLmax +end +if FLcruise_expectFLmax then +FLcruise_expect=FLmax +end +local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax) +if self.FLuser then +FLcruise=self.FLuser +FLcruise=math.max(FLcruise,FLmin) +FLcruise=math.min(FLcruise,FLmax) +end +local h_climb=FLcruise-H_departure +local h_descent=FLcruise-Hh_holding +local d_climb=h_climb/math.tan(AlphaClimb) +local d_descent=h_descent/math.tan(AlphaDescent) +local d_cruise=d_total-d_climb-d_descent +local text=string.format("\n******************************************************\n") +text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix) +text=text..string.format("Alias = %s\n",self.alias) +text=text..string.format("Group name = %s\n\n",self:_AnticipatedGroupName()) +text=text..string.format("Speeds:\n") +text=text..string.format("VxCruiseMin = %6.1f m/s = %5.1f km/h\n",VxCruiseMin,VxCruiseMin*3.6) +text=text..string.format("VxCruiseMax = %6.1f m/s = %5.1f km/h\n",VxCruiseMax,VxCruiseMax*3.6) +text=text..string.format("VxCruise = %6.1f m/s = %5.1f km/h\n",VxCruise,VxCruise*3.6) +text=text..string.format("VxClimb = %6.1f m/s = %5.1f km/h\n",VxClimb,VxClimb*3.6) +text=text..string.format("VxDescent = %6.1f m/s = %5.1f km/h\n",VxDescent,VxDescent*3.6) +text=text..string.format("VxHolding = %6.1f m/s = %5.1f km/h\n",VxHolding,VxHolding*3.6) +text=text..string.format("VxFinal = %6.1f m/s = %5.1f km/h\n",VxFinal,VxFinal*3.6) +text=text..string.format("VyClimb = %6.1f m/s\n",VyClimb) +text=text..string.format("\nDistances:\n") +text=text..string.format("d_climb = %6.1f km\n",d_climb/1000) +text=text..string.format("d_cruise = %6.1f km\n",d_cruise/1000) +text=text..string.format("d_descent = %6.1f km\n",d_descent/1000) +text=text..string.format("d_holding = %6.1f km\n",d_holding/1000) +text=text..string.format("d_total = %6.1f km\n",d_total/1000) +text=text..string.format("\nHeights:\n") +text=text..string.format("H_departure = %6.1f m ASL\n",H_departure) +text=text..string.format("H_destination = %6.1f m ASL\n",H_destination) +text=text..string.format("H_holding = %6.1f m ASL\n",H_holding) +text=text..string.format("h_climb = %6.1f m\n",h_climb) +text=text..string.format("h_descent = %6.1f m\n",h_descent) +text=text..string.format("h_holding = %6.1f m\n",h_holding) +text=text..string.format("delta H = %6.1f m\n",deltaH) +text=text..string.format("FLmin = %6.1f m ASL = FL%03d\n",FLmin,FLmin/RAT.unit.FL2m) +text=text..string.format("FLcruise = %6.1f m ASL = FL%03d\n",FLcruise,FLcruise/RAT.unit.FL2m) +text=text..string.format("FLmax = %6.1f m ASL = FL%03d\n",FLmax,FLmax/RAT.unit.FL2m) +text=text..string.format("\nAngles:\n") +text=text..string.format("Alpha climb = %6.2f Deg\n",math.deg(AlphaClimb)) +text=text..string.format("Alpha descent = %6.2f Deg\n",math.deg(AlphaDescent)) +text=text..string.format("Phi (slope) = %6.2f Deg\n",math.deg(phi)) +text=text..string.format("Phi climb = %6.2f Deg\n",math.deg(phi_climb)) +text=text..string.format("Phi descent = %6.2f Deg\n",math.deg(phi_descent)) +if self.Debug then +local h_climb_max=FLmax-H_departure +local h_descent_max=FLmax-Hh_holding +local d_climb_max=h_climb_max/math.tan(AlphaClimb) +local d_descent_max=h_descent_max/math.tan(AlphaDescent) +local d_cruise_max=d_total-d_climb_max-d_descent_max +text=text..string.format("Heading = %6.1f Deg\n",heading) +text=text..string.format("\nSSA triangle:\n") +text=text..string.format("D_total = %6.1f km\n",D_total/1000) +text=text..string.format("gamma = %6.1f Deg\n",math.deg(gamma)) +text=text..string.format("a = %6.1f m\n",a) +text=text..string.format("b = %6.1f m\n",b) +text=text..string.format("hphi_max = %6.1f m\n",hphi_max) +text=text..string.format("hphi_max2 = %6.1f m\n",hphi_max2) +text=text..string.format("h_max1 = %6.1f m\n",h_max1) +text=text..string.format("h_max2 = %6.1f m\n",h_max2) +text=text..string.format("h_max = %6.1f m\n",h_max) +text=text..string.format("\nMax heights and distances:\n") +text=text..string.format("d_climb_max = %6.1f km\n",d_climb_max/1000) +text=text..string.format("d_cruise_max = %6.1f km\n",d_cruise_max/1000) +text=text..string.format("d_descent_max = %6.1f km\n",d_descent_max/1000) +text=text..string.format("h_climb_max = %6.1f m\n",h_climb_max) +text=text..string.format("h_descent_max = %6.1f m\n",h_descent_max) +end +text=text..string.format("******************************************************\n") +self:T2(self.lid..text) +if d_cruise<0 then +d_cruise=100 +end +local wp={} +local c={} +local waypointdescriptions={} +local waypointstatus={} +local wpholding=nil +local wpfinal=nil +c[#c+1]=Pdeparture +wp[#wp+1]=self:_Waypoint(#wp+1,"Departure",takeoff,c[#wp+1],VxClimb,H_departure,departure) +waypointdescriptions[#wp]="Departure" +waypointstatus[#wp]=RAT.status.Departure +if takeoff==RAT.wp.air then +if d_climb<5000 or d_cruise<5000 then +d_cruise=d_cruise+d_climb +else +c[#c+1]=c[#c]:Translate(d_climb,heading) +wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +waypointdescriptions[#wp]="Begin of Cruise" +waypointstatus[#wp]=RAT.status.Cruise +end +else +c[#c+1]=c[#c]:Translate(d_climb/2,heading) +c[#c+1]=c[#c]:Translate(d_climb/2,heading) +wp[#wp+1]=self:_Waypoint(#wp+1,"Climb",RAT.wp.climb,c[#wp+1],VxClimb,H_departure+(FLcruise-H_departure)/2) +waypointdescriptions[#wp]="Climb" +waypointstatus[#wp]=RAT.status.Climb +wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +waypointdescriptions[#wp]="Begin of Cruise" +waypointstatus[#wp]=RAT.status.Cruise +end +if self.returnzone then +c[#c+1]=Preturn +wp[#wp+1]=self:_Waypoint(#wp+1,"Return Zone",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +waypointdescriptions[#wp]="Return Zone" +waypointstatus[#wp]=RAT.status.Uturn +end +if landing==RAT.wp.air then +c[#c+1]=Pdestination +wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",RAT.wp.finalwp,c[#wp+1],VxCruise,FLcruise) +waypointdescriptions[#wp]="Final Destination" +waypointstatus[#wp]=RAT.status.Destination +elseif self.returnzone then +c[#c+1]=c[#c]:Translate(d_cruise/2,heading-180) +wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +waypointdescriptions[#wp]="End of Cruise" +waypointstatus[#wp]=RAT.status.Descent +else +c[#c+1]=c[#c]:Translate(d_cruise,heading) +wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +waypointdescriptions[#wp]="End of Cruise" +waypointstatus[#wp]=RAT.status.Descent +end +if landing==RAT.wp.landing then +if self.returnzone then +c[#c+1]=c[#c]:Translate(d_descent/2,heading-180) +wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2) +waypointdescriptions[#wp]="Descent" +waypointstatus[#wp]=RAT.status.DescentHolding +else +c[#c+1]=c[#c]:Translate(d_descent/2,heading) +wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2) +waypointdescriptions[#wp]="Descent" +waypointstatus[#wp]=RAT.status.DescentHolding +end +end +if landing==RAT.wp.landing then +c[#c+1]=Pdestination +wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",landing,c[#wp+1],VxFinal,H_destination,destination) +waypointdescriptions[#wp]="Final Destination" +waypointstatus[#wp]=RAT.status.Destination +end +wpfinal=#wp +local waypoints={} +for _,p in ipairs(wp)do +table.insert(waypoints,p) +end +self:_Routeinfo(waypoints,"Waypoint info in set_route:",waypointdescriptions) +if self.returnzone then +return departure,destination_returnzone,waypoints,waypointdescriptions,waypointstatus +else +return departure,destination,waypoints,waypointdescriptions,waypointstatus +end +end +function RAT:_PickDeparture(takeoff) +local departures={} +if self.random_departure then +for _,_airport in pairs(self.airports)do +local airport=_airport +local name=airport:GetName() +if not self:_Excluded(name)then +if takeoff==RAT.wp.air then +table.insert(departures,airport:GetZone()) +else +local nspots=1 +if self.termtype~=nil then +nspots=airport:GetParkingSpotsNumber(self.termtype) +end +if nspots>0 then +table.insert(departures,airport) +end +end +end +end +else +for _,name in pairs(self.departure_ports)do +local dep=nil +if self:_AirportExists(name)then +if takeoff==RAT.wp.air then +dep=AIRBASE:FindByName(name):GetZone() +else +dep=AIRBASE:FindByName(name) +if self.termtype~=nil and dep~=nil then +local _dep=dep +local nspots=_dep:GetParkingSpotsNumber(self.termtype) +if nspots==0 then +dep=nil +end +end +end +elseif self:_ZoneExists(name)then +if takeoff==RAT.wp.air then +dep=ZONE:FindByName(name) +else +self:E(self.lid..string.format("ERROR! Takeoff is not in air. Cannot use %s as departure.",name)) +end +else +self:E(self.lid..string.format("ERROR: No airport or zone found with name %s.",name)) +end +if dep then +table.insert(departures,dep) +end +end +end +self:T(self.lid..string.format("Number of possible departures for %s= %d",self.alias,#departures)) +local departure=departures[math.random(#departures)] +local text +if departure and departure:GetName()then +if takeoff==RAT.wp.air then +text=string.format("%s: Chosen departure zone: %s",self.alias,departure:GetName()) +else +text=string.format("%s: Chosen departure airport: %s (ID %d)",self.alias,departure:GetName(),departure:GetID()) +end +self:T(self.lid..text) +else +self:E(self.lid..string.format("ERROR! No departure airport or zone found for %s.",self.alias)) +departure=nil +end +return departure +end +function RAT:_PickDestination(departure,q,minrange,maxrange,random,landing) +minrange=minrange or self.mindist +maxrange=maxrange or self.maxdist +local destinations={} +if random then +for _,_airport in pairs(self.airports)do +local airport=_airport +local name=airport:GetName() +if self:_IsFriendly(name)and not self:_Excluded(name)and name~=departure:GetName()then +local distance=q:Get2DDistance(airport:GetCoordinate()) +if distance>=minrange and distance<=maxrange then +if landing==RAT.wp.air then +table.insert(destinations,airport:GetZone()) +else +local nspot=1 +if self.termtype then +nspot=airport:GetParkingSpotsNumber(self.termtype) +end +if nspot>0 then +table.insert(destinations,airport) +end +end +end +end +end +else +for _,name in pairs(self.destination_ports)do +if name~=departure:GetName()then +local dest=nil +if self:_AirportExists(name)then +if landing==RAT.wp.air then +dest=AIRBASE:FindByName(name):GetZone() +else +dest=AIRBASE:FindByName(name) +local nspot=1 +if self.termtype then +nspot=dest:GetParkingSpotsNumber(self.termtype) +end +if nspot==0 then +dest=nil +end +end +elseif self:_ZoneExists(name)then +if landing==RAT.wp.air then +dest=ZONE:FindByName(name) +else +self:E(self.lid..string.format("ERROR! Landing is not in air. Cannot use zone %s as destination!",name)) +end +else +self:E(self.lid..string.format("ERROR! No airport or zone found with name %s",name)) +end +if dest then +local distance=q:Get2DDistance(dest:GetCoordinate()) +if distance>=minrange and distance<=maxrange then +table.insert(destinations,dest) +else +local text=string.format("Destination %s is ouside range. Distance = %5.1f km, min = %5.1f km, max = %5.1f km.",name,distance,minrange,maxrange) +self:T(self.lid..text) +end +end +end +end +end +self:T(self.lid..string.format("Number of possible destinations = %s.",#destinations)) +if#destinations>0 then +local function compare(a,b) +local qa=q:Get2DDistance(a:GetCoordinate()) +local qb=q:Get2DDistance(b:GetCoordinate()) +return qa0 then +destination=destinations[math.random(#destinations)] +local text +if landing==RAT.wp.air then +text=string.format("%s: Chosen destination zone: %s.",self.alias,destination:GetName()) +else +text=string.format("%s Chosen destination airport: %s (ID %d).",self.alias,destination:GetName(),destination:GetID()) +end +self:T(self.lid..text) +else +self:E(self.lid.."ERROR! No destination airport or zone found.") +destination=nil +end +return destination +end +function RAT:_GetAirportsInZone(zone) +local airports={} +for _,airport in pairs(self.airports)do +local name=airport:GetName() +local coord=airport:GetCoordinate() +if zone:IsPointVec3InZone(coord)then +table.insert(airports,name) +end +end +return airports +end +function RAT:_Excluded(port) +for _,name in pairs(self.excluded_ports)do +if name==port then +return true +end +end +return false +end +function RAT:_IsFriendly(port) +for _,airport in pairs(self.airports)do +local name=airport:GetName() +if name==port then +return true +end +end +return false +end +function RAT:_GetAirportsOfMap() +local _coalition +for i=0,2 do +if i==0 then +_coalition=coalition.side.NEUTRAL +elseif i==1 then +_coalition=coalition.side.RED +elseif i==2 then +_coalition=coalition.side.BLUE +end +local ab=coalition.getAirbases(i) +for _,airbase in pairs(ab)do +local _id=airbase:getID() +local _p=airbase:getPosition().p +local _name=airbase:getName() +local _myab=AIRBASE:FindByName(_name) +if _myab then +table.insert(self.airports_map,_myab) +local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName() +self:T(self.lid..text) +else +self:E(self.lid..string.format("WARNING: Airbase %s does not exsist as MOOSE object!",tostring(_name))) +end +end +end +end +function RAT:_GetAirportsOfCoalition() +for _,coalition in pairs(self.ctable)do +for _,_airport in pairs(self.airports_map)do +local airport=_airport +local category=airport:GetAirbaseCategory() +if airport:GetCoalition()==coalition then +local condition1=self.category==RAT.cat.plane and category==Airbase.Category.HELIPAD +local condition2=self.category==RAT.cat.plane and category==Airbase.Category.SHIP +if not(condition1 or condition2)then +table.insert(self.airports,airport) +end +end +end +end +if#self.airports==0 then +local text=string.format("No possible departure/destination airports found for RAT %s.",tostring(self.alias)) +MESSAGE:New(text,10):ToAll() +self:E(self.lid..text) +end +end +function RAT:Status(message,forID) +self:T(self.lid.."Checking status") +local Tnow=timer.getTime() +local nalive=0 +for spawnindex,_ratcraft in pairs(self.ratcraft)do +local ratcraft=_ratcraft +self:T(self.lid..string.format("Ratcraft Index=%s",tostring(spawnindex))) +local group=ratcraft.group +if group and group:IsAlive()then +nalive=nalive+1 +self:T(self.lid..string.format("Ratcraft Index=%s is ALIVE",tostring(spawnindex))) +local prefix=self:_GetPrefixFromGroup(group) +local life=self:_GetLife(group) +local fuel=group:GetFuel()*100.0 +local airborne=group:InAir() +local coords=group:GetCoordinate() +local alt=coords~=nil and coords.y or 1000 +local departure=ratcraft.departure:GetName() +local destination=ratcraft.destination:GetName() +local type=self.aircraft.type +local status=ratcraft.status +local active=ratcraft.active +local Nunits=ratcraft.nunits +local N0units=group:GetInitialSize() +local Dtravel=0 +if coords and ratcraft.Pnow then +local Dtravel=coords:Get2DDistance(ratcraft.Pnow) +ratcraft.Pnow=coords +end +ratcraft.Distance=ratcraft.Distance+Dtravel +local Ddestination=-1 +if ratcraft.Pnow then +Ddestination=ratcraft.Pnow:Get2DDistance(ratcraft.destination:GetCoordinate()) +end +if(forID and spawnindex==forID)or(not forID)then +local text=string.format("ID %i of flight %s",spawnindex,prefix) +if N0units>1 then +text=text..string.format(" (%d/%d)\n",Nunits,N0units) +else +text=text.."\n" +end +if self.commute then +text=text..string.format("%s commuting between %s and %s\n",type,departure,destination) +elseif self.continuejourney then +text=text..string.format("%s travelling from %s to %s (and continueing form there)\n",type,departure,destination) +else +text=text..string.format("%s travelling from %s to %s\n",type,departure,destination) +end +text=text..string.format("Status: %s",status) +if airborne then +text=text.." [airborne]\n" +else +text=text.." [on ground]\n" +end +text=text..string.format("Fuel = %3.0f %%\n",fuel) +text=text..string.format("Life = %3.0f %%\n",life) +text=text..string.format("FL%03d = %i m ASL\n",alt/RAT.unit.FL2m,alt) +text=text..string.format("Distance travelled = %6.1f km\n",ratcraft["Distance"]/1000) +text=text..string.format("Distance to dest = %6.1f km",Ddestination/1000) +self:T(self.lid..text) +if message then +MESSAGE:New(text,20):ToAll() +end +end +if ratcraft.despawnme then +if self.norespawn or self.respawn_after_takeoff then +if self.despawnair then +self:T(self.lid..string.format("[STATUS despawnme] Flight %s will be despawned NOW and NO new group is created!",self.alias)) +self:_Despawn(group) +end +else +self:T(self.lid..string.format("[STATUS despawnme] Flight %s will be despawned NOW and a new group is respawned!",self.alias)) +self:_Respawn(group) +end +end +else +local text=string.format("Group does not exist in loop ratcraft status for spawn index=%d",spawnindex) +self:T2(self.lid..text) +self:T2(ratcraft) +end +end +local text=string.format("Alive groups of %s: %d, nalive=%d/%d",self.alias,self.alive,nalive,self.ngroups) +self:T(self.lid..text) +MESSAGE:New(text,20):ToAllIf(message and not forID) +end +function RAT:_RemoveRatcraft(ratcraft) +self.ratcraft[ratcraft.index]=nil +return self +end +function RAT:_GetRatcraftFromGroup(group) +local index=self:GetSpawnIndexFromGroup(group) +local ratcraft=self.ratcraft[index] +return ratcraft +end +function RAT:_GetLife(group) +local life=0.0 +if group and group:IsAlive()then +local unit=group:GetUnit(1) +if unit then +life=unit:GetLife()/unit:GetLife0()*100 +else +self:T2(self.lid.."ERROR! Unit does not exist in RAT_Getlife(). Returning zero.") +end +else +self:T2(self.lid.."ERROR! Group does not exist in RAT_Getlife(). Returning zero.") +end +return life +end +function RAT:_SetStatus(group,status) +if group and group:IsAlive()then +local ratcraft=self:_GetRatcraftFromGroup(group) +if ratcraft then +ratcraft.status=status +local no1=status==RAT.status.Departure +local no2=status==RAT.status.EventBirthAir +local no3=status==RAT.status.Holding +local text=string.format("Flight %s: %s",group:GetName(),status) +self:T(self.lid..text) +if not(no1 or no2 or no3)then +MESSAGE:New(text,10):ToAllIf(self.reportstatus) +end +end +end +end +function RAT:_GetRatcraftFromGroup(group) +if group then +local index=self:GetSpawnIndexFromGroup(group) +if self.ratcraft[index]then +return self.ratcraft[index] +end +end +return nil +end +function RAT:GetStatus(group) +if group and group:IsAlive()then +local ratcraft=self:_GetRatcraftFromGroup(group) +if ratcraft then +return ratcraft.status +end +end +return"nonexistant" +end +function RAT:_OnBirth(EventData) +self:F3(EventData) +self:T3(self.lid.."Captured event birth!") +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix and EventPrefix==self.alias then +local text="Event: Group "..SpawnGroup:GetName().." was born." +self:T(self.lid..text) +local status="unknown in birth" +if SpawnGroup:InAir()then +status=RAT.status.EventBirthAir +elseif self.uncontrolled then +status=RAT.status.Uncontrolled +else +status=RAT.status.EventBirth +end +self:_SetStatus(SpawnGroup,status) +local i=self:GetSpawnIndexFromGroup(SpawnGroup) +local ratcraft=self.ratcraft[i] +local _departure=ratcraft.departure:GetName() +local _destination=ratcraft.destination:GetName() +local _nrespawn=ratcraft.nrespawn +local _takeoff=ratcraft.takeoff +local _landing=ratcraft.landing +local _livery=ratcraft.livery +local _airbase=AIRBASE:FindByName(_departure) +local onrunway=false +if _airbase then +if self.checkonrunway and _takeoff~=RAT.wp.runway and _takeoff~=RAT.wp.air then +onrunway=_airbase:CheckOnRunWay(SpawnGroup,self.onrunwayradius,false) +end +end +if onrunway then +local text=string.format("ERROR: RAT group of %s was spawned on runway. Group #%d will be despawned immediately!",self.alias,i) +MESSAGE:New(text,30):ToAllIf(self.Debug) +self:E(self.lid..text) +if self.Debug then +SpawnGroup:FlareRed() +end +self:_Despawn(SpawnGroup) +if(self.Ndeparture_Airports>=2 or self.random_departure)and _nrespawn new state %s.",SpawnGroup:GetName(),currentstate,status) +self:T(self.lid..text) +self:_Respawn(SpawnGroup,nil,3) +else +text="Event: Group "..SpawnGroup:GetName().." will be destroyed now" +self:T(self.lid..text) +self:_Despawn(SpawnGroup) +end +end +end +end +else +self:T2(self.lid.."ERROR: Group does not exist in RAT:_OnEngineShutdown().") +end +end +function RAT:_OnHit(EventData) +self:F3(EventData) +self:T(self.lid..string.format("Captured event Hit by %s! Initiator %s. Target %s",self.alias,tostring(EventData.IniUnitName),tostring(EventData.TgtUnitName))) +local SpawnGroup=EventData.TgtGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix and EventPrefix==self.alias then +self:T(self.lid..string.format("Event: Group %s was hit. Unit %s.",SpawnGroup:GetName(),tostring(EventData.TgtUnitName))) +local text=string.format("%s, unit %s was hit!",self.alias,EventData.TgtUnitName) +MESSAGE:New(text,10):ToAllIf(self.reportstatus or self.Debug) +end +end +end +function RAT:_OnDeadOrCrash(EventData) +self:F3(EventData) +self:T3(self.lid.."Captured event DeadOrCrash!") +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix then +if EventPrefix==self.alias then +self.alive=self.alive-1 +local text=string.format("Event: Group %s crashed or died. Alive counter = %d.",SpawnGroup:GetName(),self.alive) +self:T(self.lid..text) +if EventData.id==world.event.S_EVENT_CRASH then +self:_OnCrash(EventData) +elseif EventData.id==world.event.S_EVENT_DEAD then +self:_OnDead(EventData) +end +end +end +end +end +function RAT:_OnDead(EventData) +self:F3(EventData) +self:T3(self.lid.."Captured event Dead!") +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix then +if EventPrefix==self.alias then +local text=string.format("Event: Group %s died. Unit %s.",SpawnGroup:GetName(),EventData.IniUnitName) +self:T(self.lid..text) +local status=RAT.status.EventDead +self:_SetStatus(SpawnGroup,status) +end +end +else +self:T2(self.lid.."ERROR: Group does not exist in RAT:_OnDead().") +end +end +function RAT:_OnCrash(EventData) +self:F3(EventData) +self:T3(self.lid.."Captured event Crash!") +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix and EventPrefix==self.alias then +local ratcraft=self:_GetRatcraftFromGroup(SpawnGroup) +if ratcraft then +ratcraft.nunits=ratcraft.nunits-1 +local _n0=SpawnGroup:GetInitialSize() +local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.",SpawnGroup:GetName(),EventData.IniUnitName,ratcraft.nunits,_n0) +self:T(self.lid..text) +local status=RAT.status.EventCrash +self:_SetStatus(SpawnGroup,status) +if ratcraft.nunits==0 and self.respawn_after_crash and not self.norespawn then +self:T(self.lid..string.format("No units left of group %s. Group will be respawned now.",SpawnGroup:GetName())) +self:_Respawn(SpawnGroup) +end +else +self:E(self.lid..string.format("ERROR: Could not find ratcraft object for crashed group %s!",SpawnGroup:GetName())) +end +end +else +if self.Debug then +self:E(self.lid.."ERROR: Group does not exist in RAT:_OnCrash()!") +end +end +end +function RAT:_Waypoint(index,description,Type,Coord,Speed,Altitude,Airport) +local _Altitude=Altitude or Coord.y +local Hland=Coord:GetLandHeight() +local _Type=nil +local _Action=nil +local _alttype="RADIO" +if Type==RAT.wp.cold then +_Type="TakeOffParking" +_Action="From Parking Area" +_Altitude=10 +_alttype="RADIO" +elseif Type==RAT.wp.hot then +_Type="TakeOffParkingHot" +_Action="From Parking Area Hot" +_Altitude=10 +_alttype="RADIO" +elseif Type==RAT.wp.runway then +_Type="TakeOff" +_Action="From Parking Area" +_Altitude=10 +_alttype="RADIO" +elseif Type==RAT.wp.air then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.climb then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.cruise then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.descent then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.holding then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.landing then +_Type="Land" +_Action="Landing" +_Altitude=10 +_alttype="RADIO" +elseif Type==RAT.wp.finalwp then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +else +self:E(self.lid.."ERROR: Unknown waypoint type in RAT:Waypoint() function!") +_Type="Turning Point" +_Action="Turning Point" +_alttype="RADIO" +end +local text=string.format("\n******************************************************\n") +text=text..string.format("Waypoint = %d\n",index) +text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix) +text=text..string.format("Alias = %s\n",self.alias) +text=text..string.format("Type: %i - %s\n",Type,_Type) +text=text..string.format("Action: %s\n",_Action) +text=text..string.format("Coord: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n",Coord.x/1000,Coord.z/1000,Coord.y) +text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots\n",Speed,Speed*3.6,Speed*1.94384) +text=text..string.format("Land = %6.1f m ASL\n",Hland) +text=text..string.format("Altitude = %6.1f m (%s)\n",_Altitude,_alttype) +if Airport then +if Type==RAT.wp.air then +text=text..string.format("Zone = %s\n",Airport:GetName()) +else +text=text..string.format("Airport = %s\n",Airport:GetName()) +end +else +text=text..string.format("No airport/zone specified\n") +end +text=text.."******************************************************\n" +self:T2(self.lid..text) +local RoutePoint={} +RoutePoint.x=Coord.x +RoutePoint.y=Coord.z +RoutePoint.alt=_Altitude +RoutePoint.alt_type=_alttype +RoutePoint.type=_Type +RoutePoint.action=_Action +RoutePoint.speed=Speed +RoutePoint.speed_locked=true +RoutePoint.ETA=nil +RoutePoint.ETA_locked=false +RoutePoint.name=description +if(Airport~=nil)and(Type~=RAT.wp.air)then +local AirbaseID=Airport:GetID() +local AirbaseCategory=Airport:GetAirbaseCategory() +if AirbaseCategory==Airbase.Category.SHIP then +RoutePoint.linkUnit=AirbaseID +RoutePoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.HELIPAD then +RoutePoint.linkUnit=AirbaseID +RoutePoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.AIRDROME then +RoutePoint.airdromeId=AirbaseID +else +self:T(self.lid.."Unknown Airport category in _Waypoint()!") +end +end +return RoutePoint +end +function RAT:_Routeinfo(waypoints,comment,waypointdescriptions) +local text=string.format("\n******************************************************\n") +text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix) +if comment then +text=text..comment.."\n" +end +text=text..string.format("Number of waypoints = %i\n",#waypoints) +for i=1,#waypoints do +local p=waypoints[i] +text=text..string.format("WP #%i: x = %6.1f km, y = %6.1f km, alt = %6.1f m %s\n",i-1,p.x/1000,p.y/1000,p.alt,waypointdescriptions[i]) +end +local total=0.0 +for i=1,#waypoints-1 do +local point1=waypoints[i] +local point2=waypoints[i+1] +local x1=point1.x +local y1=point1.y +local x2=point2.x +local y2=point2.y +local d=math.sqrt((x1-x2)^2+(y1-y2)^2) +local heading=self:_Course(point1,point2) +total=total+d +text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %03d : %s - %s\n",i-1,i,d/1000,heading,waypointdescriptions[i],waypointdescriptions[i+1]) +end +text=text..string.format("Total distance = %6.1f km\n",total/1000) +text=text..string.format("******************************************************\n") +self:T2(self.lid..text) +return total +end +function RAT:_AnticipatedGroupName(index) +local index=index or self.SpawnIndex+1 +return string.format("%s#%03d",self.alias,index) +end +function RAT:_ActivateUncontrolled() +local idx={} +local rat={} +local nactive=0 +for spawnindex,_ratcraft in pairs(self.ratcraft)do +local ratcraft=_ratcraft +local group=ratcraft.group +if group and group:IsAlive()then +local text=string.format("Uncontrolled: Group = %s (spawnindex = %d), active = %s",ratcraft.group:GetName(),spawnindex,tostring(ratcraft.active)) +self:T2(self.lid..text) +if ratcraft.active then +nactive=nactive+1 +else +table.insert(idx,spawnindex) +end +end +end +local text=string.format("Uncontrolled: Ninactive = %d, Nactive = %d (of max %d)",#idx,nactive,self.activate_max) +self:T(self.lid..text) +if#idx>0 and nactive=1 then +for i=1,nunits do +table.insert(parkingspots,spots[1].Coordinate) +table.insert(parkingindex,spots[1].TerminalID) +end +PointVec3=spots[1].Coordinate +else +_notenough=true +end +elseif spawnonairport then +if nfree>=nunits then +for i=1,nunits do +table.insert(parkingspots,spots[i].Coordinate) +table.insert(parkingindex,spots[i].TerminalID) +end +else +_notenough=true +end +end +if _notenough then +if self.respawn_inair and not self.SpawnUnControlled then +self:E(self.lid..string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,departure:GetName())) +spawnonground=false +spawnonship=false +spawnonfarp=false +spawnonrunway=false +waypoints[1].type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] +waypoints[1].action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] +PointVec3.x=PointVec3.x+math.random(-1500,1500) +PointVec3.z=PointVec3.z+math.random(-1500,1500) +if self.category==RAT.cat.heli then +PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) +else +PointVec3.y=PointVec3:GetLandHeight()+math.random(500,3000) +end +else +self:E(self.lid..string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,departure:GetName())) +return nil +end +end +else +end +for UnitID=1,nunits do +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x +local SY=UnitTemplate.y +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=PointVec3.x+(SX-BX) +local TY=PointVec3.z+(SY-BY) +if spawnonground then +if spawnonship or spawnonfarp or spawnonrunway or automatic then +self:T(self.lid..string.format("RAT group %s spawning at farp, ship or runway %s.",self.alias,departure:GetName())) +SpawnTemplate.units[UnitID].x=PointVec3.x +SpawnTemplate.units[UnitID].y=PointVec3.z +SpawnTemplate.units[UnitID].alt=PointVec3.y +else +self:T(self.lid..string.format("RAT group %s spawning at airbase %s on parking spot id %d",self.alias,departure:GetName(),parkingindex[UnitID])) +SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x +SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z +SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y +end +else +self:T(self.lid..string.format("RAT group %s spawning in air at %s.",self.alias,departure:GetName())) +SpawnTemplate.units[UnitID].x=TX +SpawnTemplate.units[UnitID].y=TY +SpawnTemplate.units[UnitID].alt=PointVec3.y +end +if self.Debug then +local unitspawn=COORDINATE:New(SpawnTemplate.units[UnitID].x,SpawnTemplate.units[UnitID].alt,SpawnTemplate.units[UnitID].y) +unitspawn:MarkToAll(string.format("RAT %s Spawnplace unit #%d",self.alias,UnitID)) +end +UnitTemplate.parking=nil +UnitTemplate.parking_id=nil +if parkingindex[UnitID]and not automatic then +UnitTemplate.parking=parkingindex[UnitID] +end +self:T2(self.lid..string.format("RAT group %s unit number %d: Parking = %s",self.alias,UnitID,tostring(UnitTemplate.parking))) +self:T2(self.lid..string.format("RAT group %s unit number %d: Parking ID = %s",self.alias,UnitID,tostring(UnitTemplate.parking_id))) +SpawnTemplate.units[UnitID].heading=heading +SpawnTemplate.units[UnitID].psi=-heading +if livery then +SpawnTemplate.units[UnitID].livery_id=livery +end +if self.actype then +SpawnTemplate.units[UnitID]["type"]=self.actype +end +SpawnTemplate.units[UnitID]["skill"]=self.skill +if self.onboardnum then +SpawnTemplate.units[UnitID]["onboard_num"]=string.format("%s%d%02d",self.onboardnum,(self.SpawnIndex-1)%10,(self.onboardnum0-1)+UnitID) +end +SpawnTemplate.CoalitionID=self.coalition +if self.country then +SpawnTemplate.CountryID=self.country +end +end +for i,wp in ipairs(waypoints)do +SpawnTemplate.route.points[i]=wp +end +SpawnTemplate.x=PointVec3.x +SpawnTemplate.y=PointVec3.z +if self.radio then +SpawnTemplate.communication=self.radio +end +if self.frequency then +SpawnTemplate.frequency=self.frequency +end +if self.modulation then +SpawnTemplate.modulation=self.modulation +end +self:T(SpawnTemplate) +end +end +return true +end +function RAT._ATCInit(airports_map) +if not RAT.ATC.init then +local text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay +BASE:I(RAT.id..text) +for _,ap in pairs(airports_map)do +local airbase=ap +local name=airbase:GetName() +local fc=_DATABASE:GetFlightControl(name) +if not fc then +local airport={} +airport.queue={} +airport.busy=false +airport.onfinal={} +airport.Nonfinal=0 +airport.traffic=0 +airport.Tlastclearance=nil +RAT.ATC.airport[name]=airport +end +end +SCHEDULER:New(nil,RAT._ATCCheck,{},5,15) +SCHEDULER:New(nil,RAT._ATCStatus,{},5,60) +RAT.ATC.T0=timer.getTime() +end +RAT.ATC.init=true +end +function RAT:_ATCAddFlight(name,dest) +BASE:I(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) +local flight={} +flight.destination=dest +flight.Tarrive=-1 +flight.holding=-1 +flight.Tarrive=-1 +RAT.ATC.flight[name]=flight +end +function RAT._ATCDelFlight(t,entry) +for k,_ in pairs(t)do +if k==entry then +BASE:I(RAT.id..string.format("Removing flight %s from queue",entry)) +t[entry]=nil +end +end +end +function RAT._ATCRegisterFlight(name,time) +BASE:I(RAT.id..string.format("Flight %s registered at ATC for landing clearance.",name)) +RAT.ATC.flight[name].Tarrive=time +RAT.ATC.flight[name].holding=0 +end +function RAT._ATCStatus() +local Tnow=timer.getTime() +for name,_flight in pairs(RAT.ATC.flight)do +local flight=_flight +local hold=RAT.ATC.flight[name].holding +local dest=RAT.ATC.flight[name].destination +local airport=RAT.ATC.airport[dest] +if airport then +if hold>=0 then +local busy="Runway state is unknown" +if airport.Nonfinal>0 then +busy="Runway is occupied by "..airport.Nonfinal +else +busy="Runway is currently clear" +end +local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.",dest,name,hold/60,hold%60,busy) +BASE:I(RAT.id..text) +elseif hold==RAT.ATC.onfinal then +local Tfinal=Tnow-flight.Tonfinal +local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.",dest,name,Tfinal/60,Tfinal%60) +BASE:I(RAT.id..text) +elseif hold==RAT.ATC.unregistered then +else +BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") +end +else +end +end +end +function RAT._ATCCheck() +RAT._ATCQueue() +local Tnow=timer.getTime() +for airportname,_airport in pairs(RAT.ATC.airport)do +local airport=_airport +for qID,flightname in pairs(airport.queue)do +local flight=RAT.ATC.flight[flightname] +local nqueue=#airport.queue +local landing1=false +if airport.Tlastclearance then +landing1=(Tnow-airport.Tlastclearance>RAT.ATC.delay)and airport.Nonfinal=0 then +flight.holding=Tnow-flight.Tarrive +end +local hold=flight.holding +local dest=flight.destination +if hold>=0 and airport==dest then +_queue[#_queue+1]={name,hold} +end +end +local function compare(a,b) +return a[2]>b[2] +end +table.sort(_queue,compare) +RAT.ATC.airport[airport].queue={} +for k,v in ipairs(_queue)do +table.insert(RAT.ATC.airport[airport].queue,v[1]) +end +end +end +RATMANAGER={ +ClassName="RATMANAGER", +Debug=false, +rat={}, +name={}, +alive={}, +planned={}, +min={}, +nrat=0, +ntot=nil, +Tcheck=60, +dTspawn=1.0, +manager=nil, +managerid=nil, +} +RATMANAGER.id="RATMANAGER | " +function RATMANAGER:New(ntot) +local self=BASE:Inherit(self,BASE:New()) +self.ntot=ntot or 1 +self:I(RATMANAGER.id..string.format("Creating manager for %d groups",ntot)) +return self +end +function RATMANAGER:Add(ratobject,min) +ratobject.norespawn=true +ratobject.f10menu=false +self.nrat=self.nrat+1 +self.rat[self.nrat]=ratobject +self.alive[self.nrat]=0 +self.planned[self.nrat]=0 +self.name[self.nrat]=ratobject.alias +self.min[self.nrat]=min or 1 +self:T(RATMANAGER.id..string.format("Adding ratobject %s with min flights = %d",self.name[self.nrat],self.min[self.nrat])) +ratobject:Spawn(0) +return self +end +function RATMANAGER:Start(delay) +delay=delay or 5 +if delay and delay>0 then +local text=string.format(RATMANAGER.id.."RAT manager will be started in %d seconds.\n",delay) +text=text..string.format("Managed groups:\n") +for i=1,self.nrat do +text=text..string.format("- %s with min groups %d\n",self.name[i],self.min[i]) +end +text=text..string.format("Number of constantly alive groups %d",self.ntot) +self:E(text) +self:ScheduleOnce(delay,RATMANAGER.Start,self,0) +else +local n=0 +for i=1,self.nrat do +n=n+self.min[i] +end +self.ntot=math.max(self.ntot,n) +local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive) +local time=0.0 +for i=1,self.nrat do +for j=1,N[i]do +time=time+self.dTspawn +self:ScheduleOnce(time,RAT._SpawnWithRoute,self.rat[i]) +end +end +for i=1,self.nrat do +local rat=self.rat[i] +if rat.uncontrolled and rat.activate_uncontrolled then +local Tactivate=math.max(time+1,rat.activate_delay) +self:ScheduleRepeat(Tactivate,rat.activate_delta,rat.activate_frand,nil,rat._ActivateUncontrolled,rat) +end +end +local TstartManager=math.max(time+1,self.Tcheck) +self.manager,self.managerid=SCHEDULER:New(self,self._Manage,{self},TstartManager,self.Tcheck) +local text=string.format(RATMANAGER.id.."Starting RAT manager with scheduler ID %s in %d seconds. Repeat interval %d seconds.",self.managerid,TstartManager,self.Tcheck) +self:I(text) +end +return self +end +function RATMANAGER:Stop(delay) +delay=delay or 1 +if delay and delay>0 then +self:I(RATMANAGER.id..string.format("Manager will be stopped in %d seconds.",delay)) +self:ScheduleOnce(delay,RATMANAGER.Stop,self,0) +else +self:I(RATMANAGER.id..string.format("Stopping manager with scheduler ID %s",self.managerid)) +self.manager:Stop(self.managerid) +end +return self +end +function RATMANAGER:SetTcheck(dt) +self.Tcheck=dt or 60 +return self +end +function RATMANAGER:SetTspawn(dt) +self.dTspawn=dt or 1.0 +return self +end +function RATMANAGER:_Manage() +local ntot=self:_Count() +self:T(RATMANAGER.id..string.format("Number of alive groups %d. New groups to be spawned %d.",ntot,self.ntot-ntot)) +local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive) +local time=0.0 +for i=1,self.nrat do +for j=1,N[i]do +time=time+self.dTspawn +self.planned[i]=self.planned[i]+1 +self:ScheduleOnce(time,RATMANAGER._Spawn,self,i) +end +end +end +function RATMANAGER:_Spawn(i) +local rat=self.rat[i] +rat:_SpawnWithRoute() +self.planned[i]=self.planned[i]-1 +end +function RATMANAGER:_Count() +local ntotal=0 +for i=1,self.nrat do +local n=0 +local ratobject=self.rat[i] +for spawnindex,ratcraft in pairs(ratobject.ratcraft)do +local group=ratcraft.group +if group and group:IsAlive()then +n=n+1 +end +end +self.alive[i]=n +ntotal=ntotal+n +local text=string.format("Number of alive groups of %s = %d, planned=%d",self.name[i],n,self.planned[i]) +self:T(RATMANAGER.id..text) +end +return ntotal +end +function RATMANAGER:_RollDice(nrat,ntot,min,alive) +local function sum(A,index) +local summe=0 +for _,i in ipairs(index)do +summe=summe+A[i] +end +return summe +end +local N={} +local M={} +local P={} +for i=1,nrat do +local a=alive[i]+self.planned[i] +N[#N+1]=0 +M[#M+1]=math.max(a,min[i]) +P[#P+1]=math.max(min[i]-a,0) +end +local mini={} +local maxi={} +local rattab={} +for i=1,nrat do +table.insert(rattab,i) +end +local done={} +local nnew=ntot +for i=1,nrat do +nnew=nnew-alive[i]-self.planned[i] +end +for i=1,nrat-1 do +local r=math.random(#rattab) +local j=rattab[r] +table.remove(rattab,r) +table.insert(done,j) +local sN=sum(N,done) +local sP=sum(P,rattab) +maxi[j]=nnew-sN-sP +mini[j]=P[j] +if maxi[j]>=mini[j]then +N[j]=math.random(mini[j],maxi[j]) +else +N[j]=0 +end +self:T3(string.format("RATMANAGER: i=%d, alive=%d, planned=%d, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d",j,alive[j],self.planned[i],min[j],mini[j],maxi[j],N[j],sN,sP)) +end +local j=rattab[1] +N[j]=nnew-sum(N,done) +mini[j]=nnew-sum(N,done) +maxi[j]=nnew-sum(N,done) +table.remove(rattab,1) +table.insert(done,j) +local text=RATMANAGER.id.."\n" +for i=1,nrat do +text=text..string.format("%s: i=%d, alive=%d, planned=%d, min=%d, mini=%d, maxi=%d, add=%d\n",self.name[i],i,alive[i],self.planned[i],min[i],mini[i],maxi[i],N[i]) +end +text=text..string.format("Total # of groups to add = %d",sum(N,done)) +self:T(text) +return N +end +RANGE={ +ClassName="RANGE", +Debug=false, +verbose=0, +id=nil, +rangename=nil, +location=nil, +messages=true, +rangeradius=5000, +rangezone=nil, +strafeTargets={}, +bombingTargets={}, +nbombtargets=0, +nstrafetargets=0, +MenuAddedTo={}, +planes={}, +strafeStatus={}, +strafePlayerResults={}, +bombPlayerResults={}, +PlayerSettings={}, +dtBombtrack=0.005, +BombtrackThreshold=25000, +Tmsg=30, +examinergroupname=nil, +examinerexclusive=nil, +strafemaxalt=914, +ndisplayresult=10, +BombSmokeColor=SMOKECOLOR.Red, +StrafeSmokeColor=SMOKECOLOR.Green, +StrafePitSmokeColor=SMOKECOLOR.White, +illuminationminalt=500, +illuminationmaxalt=1000, +scorebombdistance=1000, +TdelaySmoke=3.0, +trackbombs=true, +trackrockets=true, +trackmissiles=true, +defaultsmokebomb=true, +autosave=false, +instructorfreq=nil, +instructor=nil, +rangecontrolfreq=nil, +rangecontrol=nil, +soundpath="Range Soundfiles/", +targetsheet=nil, +targetpath=nil, +targetprefix=nil, +Coalition=nil, +ceilingaltitude=20000, +ceilingenabled=false, +} +RANGE.Defaults={ +goodhitrange=25, +strafemaxalt=914, +dtBombtrack=0.005, +Tmsg=30, +ndisplayresult=10, +rangeradius=5000, +TdelaySmoke=3.0, +boxlength=3000, +boxwidth=300, +goodpass=20, +foulline=610 +} +RANGE.TargetType={ +UNIT="Unit", +STATIC="Static", +COORD="Coordinate", +SCENERY="Scenery" +} +RANGE.Sound={ +RC0={filename="RC-0.ogg",duration=0.60}, +RC1={filename="RC-1.ogg",duration=0.47}, +RC2={filename="RC-2.ogg",duration=0.43}, +RC3={filename="RC-3.ogg",duration=0.50}, +RC4={filename="RC-4.ogg",duration=0.58}, +RC5={filename="RC-5.ogg",duration=0.54}, +RC6={filename="RC-6.ogg",duration=0.61}, +RC7={filename="RC-7.ogg",duration=0.53}, +RC8={filename="RC-8.ogg",duration=0.34}, +RC9={filename="RC-9.ogg",duration=0.54}, +RCAccuracy={filename="RC-Accuracy.ogg",duration=0.67}, +RCDegrees={filename="RC-Degrees.ogg",duration=0.59}, +RCExcellentHit={filename="RC-ExcellentHit.ogg",duration=0.76}, +RCExcellentPass={filename="RC-ExcellentPass.ogg",duration=0.89}, +RCFeet={filename="RC-Feet.ogg",duration=0.49}, +RCFor={filename="RC-For.ogg",duration=0.64}, +RCGoodHit={filename="RC-GoodHit.ogg",duration=0.52}, +RCGoodPass={filename="RC-GoodPass.ogg",duration=0.62}, +RCHitsOnTarget={filename="RC-HitsOnTarget.ogg",duration=0.88}, +RCImpact={filename="RC-Impact.ogg",duration=0.61}, +RCIneffectiveHit={filename="RC-IneffectiveHit.ogg",duration=0.86}, +RCIneffectivePass={filename="RC-IneffectivePass.ogg",duration=0.99}, +RCInvalidHit={filename="RC-InvalidHit.ogg",duration=2.97}, +RCLeftStrafePitTooQuickly={filename="RC-LeftStrafePitTooQuickly.ogg",duration=3.09}, +RCPercent={filename="RC-Percent.ogg",duration=0.56}, +RCPoorHit={filename="RC-PoorHit.ogg",duration=0.54}, +RCPoorPass={filename="RC-PoorPass.ogg",duration=0.68}, +RCRollingInOnStrafeTarget={filename="RC-RollingInOnStrafeTarget.ogg",duration=1.38}, +RCTotalRoundsFired={filename="RC-TotalRoundsFired.ogg",duration=1.22}, +RCWeaponImpactedTooFar={filename="RC-WeaponImpactedTooFar.ogg",duration=3.73}, +IR0={filename="IR-0.ogg",duration=0.55}, +IR1={filename="IR-1.ogg",duration=0.41}, +IR2={filename="IR-2.ogg",duration=0.37}, +IR3={filename="IR-3.ogg",duration=0.41}, +IR4={filename="IR-4.ogg",duration=0.37}, +IR5={filename="IR-5.ogg",duration=0.43}, +IR6={filename="IR-6.ogg",duration=0.55}, +IR7={filename="IR-7.ogg",duration=0.43}, +IR8={filename="IR-8.ogg",duration=0.38}, +IR9={filename="IR-9.ogg",duration=0.55}, +IRDecimal={filename="IR-Decimal.ogg",duration=0.54}, +IRMegaHertz={filename="IR-MegaHertz.ogg",duration=0.87}, +IREnterRange={filename="IR-EnterRange.ogg",duration=4.83}, +IRExitRange={filename="IR-ExitRange.ogg",duration=3.10}, +} +RANGE.Names={} +RANGE.MenuF10={} +RANGE.MenuF10Root=nil +RANGE.version="2.8.1" +function RANGE:New(RangeName,Coalition) +local self=BASE:Inherit(self,FSM:New()) +self.rangename=RangeName or"Practice Range" +self.Coalition=Coalition +self.lid=string.format("RANGE %s | ",self.rangename) +local text=string.format("Script version %s - creating new RANGE object %s.",RANGE.version,self.rangename) +self:I(self.lid..text) +self:SetDefaultPlayerSmokeBomb() +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","Impact","*") +self:AddTransition("*","RollingIn","*") +self:AddTransition("*","StrafeResult","*") +self:AddTransition("*","EnterRange","*") +self:AddTransition("*","ExitRange","*") +self:AddTransition("*","Save","*") +self:AddTransition("*","Load","*") +return self +end +function RANGE:onafterStart() +local _location=nil +local _count=0 +for _,_target in pairs(self.bombingTargets)do +_count=_count+1 +if _location==nil then +_location=self:_GetBombTargetCoordinate(_target) +end +end +self.nbombtargets=_count +_count=0 +for _,_target in pairs(self.strafeTargets)do +_count=_count+1 +for _,_unit in pairs(_target.targets)do +if _location==nil then +_location=_unit:GetCoordinate() +end +end +end +self.nstrafetargets=_count +if self.location==nil then +self.location=_location +end +if self.location==nil then +local text=string.format("ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.",self.nstrafetargets,self.nbombtargets) +self:E(self.lid..text) +return +end +if self.rangezone==nil then +self.rangezone=ZONE_RADIUS:New(self.rangename,{x=self.location.x,y=self.location.z},self.rangeradius) +end +local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.",self.rangename,self.nstrafetargets,self.nbombtargets) +self:I(self.lid..text) +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.Hit) +self:HandleEvent(EVENTS.Shot) +for _,_target in pairs(self.bombingTargets)do +local target=_target +if target.move and target.type==RANGE.TargetType.UNIT and target.speed>1 then +target.target:PatrolZones({self.rangezone},target.speed*0.75,ENUMS.Formation.Vehicle.OffRoad) +end +end +if self.rangecontrolfreq and not self.useSRS then +self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq,nil,self.rangename) +self.rangecontrol.schedonce=true +self.rangecontrol:SetDigit(0,self.Sound.RC0.filename,self.Sound.RC0.duration,self.soundpath) +self.rangecontrol:SetDigit(1,self.Sound.RC1.filename,self.Sound.RC1.duration,self.soundpath) +self.rangecontrol:SetDigit(2,self.Sound.RC2.filename,self.Sound.RC2.duration,self.soundpath) +self.rangecontrol:SetDigit(3,self.Sound.RC3.filename,self.Sound.RC3.duration,self.soundpath) +self.rangecontrol:SetDigit(4,self.Sound.RC4.filename,self.Sound.RC4.duration,self.soundpath) +self.rangecontrol:SetDigit(5,self.Sound.RC5.filename,self.Sound.RC5.duration,self.soundpath) +self.rangecontrol:SetDigit(6,self.Sound.RC6.filename,self.Sound.RC6.duration,self.soundpath) +self.rangecontrol:SetDigit(7,self.Sound.RC7.filename,self.Sound.RC7.duration,self.soundpath) +self.rangecontrol:SetDigit(8,self.Sound.RC8.filename,self.Sound.RC8.duration,self.soundpath) +self.rangecontrol:SetDigit(9,self.Sound.RC9.filename,self.Sound.RC9.duration,self.soundpath) +self.rangecontrol:SetSenderCoordinate(self.location) +self.rangecontrol:SetSenderUnitName(self.rangecontrolrelayname) +self.rangecontrol:Start(1,0.1) +if self.instructorfreq and not self.useSRS then +self.instructor=RADIOQUEUE:New(self.instructorfreq,nil,self.rangename) +self.instructor.schedonce=true +self.instructor:SetDigit(0,self.Sound.IR0.filename,self.Sound.IR0.duration,self.soundpath) +self.instructor:SetDigit(1,self.Sound.IR1.filename,self.Sound.IR1.duration,self.soundpath) +self.instructor:SetDigit(2,self.Sound.IR2.filename,self.Sound.IR2.duration,self.soundpath) +self.instructor:SetDigit(3,self.Sound.IR3.filename,self.Sound.IR3.duration,self.soundpath) +self.instructor:SetDigit(4,self.Sound.IR4.filename,self.Sound.IR4.duration,self.soundpath) +self.instructor:SetDigit(5,self.Sound.IR5.filename,self.Sound.IR5.duration,self.soundpath) +self.instructor:SetDigit(6,self.Sound.IR6.filename,self.Sound.IR6.duration,self.soundpath) +self.instructor:SetDigit(7,self.Sound.IR7.filename,self.Sound.IR7.duration,self.soundpath) +self.instructor:SetDigit(8,self.Sound.IR8.filename,self.Sound.IR8.duration,self.soundpath) +self.instructor:SetDigit(9,self.Sound.IR9.filename,self.Sound.IR9.duration,self.soundpath) +self.instructor:SetSenderCoordinate(self.location) +self.instructor:SetSenderUnitName(self.instructorrelayname) +self.instructor:Start(1,0.1) +end +end +if self.autosave then +self:Load() +end +if self.Debug then +self:_MarkTargetsOnMap() +self:_SmokeBombTargets() +self:_SmokeStrafeTargets() +self:_SmokeStrafeTargetBoxes() +self.rangezone:SmokeZone(SMOKECOLOR.White) +end +self:__Status(-10) +end +function RANGE:SetMenuRoot(menu) +self.menuF10root=menu +return self +end +function RANGE:SetMaxStrafeAlt(maxalt) +self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt +return self +end +function RANGE:SetBombtrackTimestep(dt) +self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack +return self +end +function RANGE:SetMessageTimeDuration(time) +self.Tmsg=time or RANGE.Defaults.Tmsg +return self +end +function RANGE:SetAutosaveOn() +self.autosave=true +return self +end +function RANGE:SetAutosaveOff() +self.autosave=false +return self +end +function RANGE:SetTargetSheet(path,prefix) +if io then +self.targetsheet=true +self.targetpath=path +self.targetprefix=prefix +else +self:E(self.lid.."ERROR: io is not desanitized. Cannot save target sheet.") +end +return self +end +function RANGE:SetFunkManOn(Port,Host) +self.funkmanSocket=SOCKET:New(Port,Host) +return self +end +function RANGE:SetMessageToExaminer(examinergroupname,exclusively) +self.examinergroupname=examinergroupname +self.examinerexclusive=exclusively +return self +end +function RANGE:SetDisplayedMaxPlayerResults(nmax) +self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult +return self +end +function RANGE:SetRangeRadius(radius) +self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius +return self +end +function RANGE:SetDefaultPlayerSmokeBomb(switch) +if switch==true or switch==nil then +self.defaultsmokebomb=true +else +self.defaultsmokebomb=false +end +return self +end +function RANGE:SetBombtrackThreshold(distance) +self.BombtrackThreshold=(distance or 25)*1000 +return self +end +function RANGE:SetRangeLocation(coordinate) +self.location=coordinate +return self +end +function RANGE:SetRangeZone(zone) +if zone and type(zone)=="string"then +zone=ZONE:FindByName(zone) +end +self.rangezone=zone +return self +end +function RANGE:SetRangeCeiling(altitude) +self:T(self.lid.."SetRangeCeiling") +if altitude and type(altitude)=="number"then +self.ceilingaltitude=altitude +else +self:E(self.lid.."Altitude either not provided or is not a number, using default setting (20000).") +self.ceilingaltitude=20000 +end +return self +end +function RANGE:EnableRangeCeiling(enabled) +self:T(self.lid.."EnableRangeCeiling") +if enabled and type(enabled)=="boolean"then +self.ceilingenabled=enabled +else +self:E(self.lid.."Enabled either not provide or is not a boolean, using default setting (false).") +self.ceilingenabled=false +end +return self +end +function RANGE:SetBombTargetSmokeColor(colorid) +self.BombSmokeColor=colorid or SMOKECOLOR.Red +return self +end +function RANGE:SetScoreBombDistance(distance) +self.scorebombdistance=distance or 1000 +return self +end +function RANGE:SetStrafeTargetSmokeColor(colorid) +self.StrafeSmokeColor=colorid or SMOKECOLOR.Green +return self +end +function RANGE:SetStrafePitSmokeColor(colorid) +self.StrafePitSmokeColor=colorid or SMOKECOLOR.White +return self +end +function RANGE:SetSmokeTimeDelay(delay) +self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke +return self +end +function RANGE:DebugON() +self.Debug=true +return self +end +function RANGE:DebugOFF() +self.Debug=false +return self +end +function RANGE:SetMessagesOFF() +self.messages=false +return self +end +function RANGE:SetMessagesON() +self.messages=true +return self +end +function RANGE:TrackBombsON() +self.trackbombs=true +return self +end +function RANGE:TrackBombsOFF() +self.trackbombs=false +return self +end +function RANGE:TrackRocketsON() +self.trackrockets=true +return self +end +function RANGE:TrackRocketsOFF() +self.trackrockets=false +return self +end +function RANGE:TrackMissilesON() +self.trackmissiles=true +return self +end +function RANGE:TrackMissilesOFF() +self.trackmissiles=false +return self +end +function RANGE:SetSRS(PathToSRS,Port,Coalition,Frequency,Modulation,Volume,PathToGoogleKey) +if PathToSRS or MSRS.path then +self.useSRS=true +self.controlmsrs=MSRS:New(PathToSRS or MSRS.path,Frequency or 256,Modulation or radio.modulation.AM) +self.controlmsrs:SetPort(Port or MSRS.port) +self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE) +self.controlmsrs:SetLabel("RANGEC") +self.controlmsrs:SetVolume(Volume or 1.0) +self.controlsrsQ=MSRSQUEUE:New("CONTROL") +self.instructmsrs=MSRS:New(PathToSRS or MSRS.path,Frequency or 305,Modulation or radio.modulation.AM) +self.instructmsrs:SetPort(Port or MSRS.port) +self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE) +self.instructmsrs:SetLabel("RANGEI") +self.instructmsrs:SetVolume(Volume or 1.0) +self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") +if PathToGoogleKey then +self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) +self.controlmsrs:SetProvider(MSRS.Provider.GOOGLE) +self.instructmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) +self.instructmsrs:SetProvider(MSRS.Provider.GOOGLE) +end +else +self:E(self.lid..string.format("ERROR: No SRS path specified!")) +end +return self +end +function RANGE:SetSRSRangeControl(frequency,modulation,voice,culture,gender,relayunitname) +if not self.instructmsrs then +self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeControl!") +return self +end +self.rangecontrolfreq=frequency or 256 +self.controlmsrs:SetFrequencies(self.rangecontrolfreq) +self.controlmsrs:SetModulations(modulation or radio.modulation.AM) +self.controlmsrs:SetVoice(voice) +self.controlmsrs:SetCulture(culture or"en-US") +self.controlmsrs:SetGender(gender or"female") +self.rangecontrol=true +if relayunitname then +local unit=UNIT:FindByName(relayunitname) +local Coordinate=unit:GetCoordinate() +self.rangecontrolrelayname=relayunitname +end +return self +end +function RANGE:SetSRSRangeInstructor(frequency,modulation,voice,culture,gender,relayunitname) +if not self.instructmsrs then +self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeInstructor!") +return self +end +self.instructorfreq=frequency or 305 +self.instructmsrs:SetFrequencies(self.instructorfreq) +self.instructmsrs:SetModulations(modulation or radio.modulation.AM) +self.instructmsrs:SetVoice(voice) +self.instructmsrs:SetCulture(culture or"en-US") +self.instructmsrs:SetGender(gender or"male") +self.instructor=true +if relayunitname then +local unit=UNIT:FindByName(relayunitname) +local Coordinate=unit:GetCoordinate() +self.instructmsrs:SetCoordinate(Coordinate) +self.instructorrelayname=relayunitname +end +return self +end +function RANGE:SetRangeControl(frequency,relayunitname) +self.rangecontrolfreq=frequency or 256 +self.rangecontrolrelayname=relayunitname +return self +end +function RANGE:SetInstructorRadio(frequency,relayunitname) +self.instructorfreq=frequency or 305 +self.instructorrelayname=relayunitname +return self +end +function RANGE:SetSoundfilesPath(path) +self.soundpath=tostring(path or"Range Soundfiles/") +self:T2(self.lid..string.format("Setting sound files path to %s",self.soundpath)) +return self +end +function RANGE:SetSoundfilesInfo(csvfile) +local function getSound(filename) +for key,_soundfile in pairs(self.Sound)do +local soundfile=_soundfile +if filename==soundfile.filename then +return soundfile +end +end +return nil +end +local data=UTILS.ReadCSV(csvfile) +if data then +for i,sound in pairs(data)do +local soundfile=getSound(sound.filename..".ogg") +if soundfile then +soundfile.duration=tonumber(sound.duration) +else +self:E(string.format("ERROR: Could not get info for sound file %s",sound.filename)) +end +end +else +self:E(string.format("ERROR: Could not read sound csv file!")) +end +return self +end +function RANGE:AddStrafePit(targetnames,boxlength,boxwidth,heading,inverseheading,goodpass,foulline) +self:F({targetnames=targetnames,boxlength=boxlength,boxwidth=boxwidth,heading=heading,inverseheading=inverseheading,goodpass=goodpass,foulline=foulline}) +if type(targetnames)~="table"then +targetnames={targetnames} +end +local _targets={} +local center=nil +local ntargets=0 +for _i,_name in ipairs(targetnames)do +local _isstatic=self:_CheckStatic(_name) +local unit=nil +if _isstatic==true then +self:T(self.lid..string.format("Adding STATIC object %s as strafe target #%d.",_name,_i)) +unit=STATIC:FindByName(_name,false) +elseif _isstatic==false then +self:T(self.lid..string.format("Adding UNIT object %s as strafe target #%d.",_name,_i)) +unit=UNIT:FindByName(_name) +else +local text=string.format("ERROR! Could not find ANY strafe target object with name %s.",_name) +self:E(self.lid..text) +end +if unit then +table.insert(_targets,unit) +if center==nil then +center=unit +end +ntargets=ntargets+1 +end +end +if ntargets==0 then +local text=string.format("ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s",self.rangename) +self:E(self.lid..text) +return +end +local l=boxlength or RANGE.Defaults.boxlength +local w=(boxwidth or RANGE.Defaults.boxwidth)/2 +local heading=heading or center:GetHeading() +if inverseheading~=nil then +if inverseheading then +heading=heading-180 +end +end +if heading<0 then +heading=heading+360 +end +if heading>360 then +heading=heading-360 +end +goodpass=goodpass or RANGE.Defaults.goodpass +foulline=foulline or RANGE.Defaults.foulline +local Ccenter=center:GetCoordinate() +local _name=center:GetName() +local p={} +p[#p+1]=Ccenter:Translate(w,heading+90) +p[#p+1]=p[#p]:Translate(l,heading) +p[#p+1]=p[#p]:Translate(2*w,heading-90) +p[#p+1]=p[#p]:Translate(-l,heading) +local pv2={} +for i,p in ipairs(p)do +pv2[i]={x=p.x,y=p.z} +end +local _polygon=ZONE_POLYGON_BASE:New(_name,pv2) +local st={} +st.name=_name +st.polygon=_polygon +st.coordinate=Ccenter +st.goodPass=goodpass +st.targets=_targets +st.foulline=foulline +st.smokepoints=p +st.heading=heading +table.insert(self.strafeTargets,st) +local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f",_name,ntargets,heading,l,w,goodpass,foulline) +self:T(self.lid..text) +return self +end +function RANGE:AddStrafePitGroup(group,boxlength,boxwidth,heading,inverseheading,goodpass,foulline) +self:F({group=group,boxlength=boxlength,boxwidth=boxwidth,heading=heading,inverseheading=inverseheading,goodpass=goodpass,foulline=foulline}) +if group and group:IsAlive()then +local _units=group:GetUnits() +local _names={} +for _,_unit in ipairs(_units)do +local _unit=_unit +if _unit and _unit:IsAlive()then +local _name=_unit:GetName() +table.insert(_names,_name) +end +end +self:AddStrafePit(_names,boxlength,boxwidth,heading,inverseheading,goodpass,foulline) +end +return self +end +function RANGE:AddBombingTargets(targetnames,goodhitrange,randommove) +self:F({targetnames=targetnames,goodhitrange=goodhitrange,randommove=randommove}) +if type(targetnames)~="table"then +targetnames={targetnames} +end +goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange +for _,name in pairs(targetnames)do +local _isstatic=self:_CheckStatic(name) +if _isstatic==true then +local _static=STATIC:FindByName(name) +self:T2(self.lid..string.format("Adding static bombing target %s with hit range %d.",name,goodhitrange,false)) +self:AddBombingTargetUnit(_static,goodhitrange) +elseif _isstatic==false then +local _unit=UNIT:FindByName(name) +self:T2(self.lid..string.format("Adding unit bombing target %s with hit range %d.",name,goodhitrange,randommove)) +self:AddBombingTargetUnit(_unit,goodhitrange,randommove) +else +self:E(self.lid..string.format("ERROR! Could not find bombing target %s.",name)) +end +end +return self +end +function RANGE:AddBombingTargetUnit(unit,goodhitrange,randommove) +self:F({unit=unit,goodhitrange=goodhitrange,randommove=randommove}) +local name=unit:GetName() +local _isstatic=self:_CheckStatic(name) +goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange +if randommove==nil or _isstatic==true then +randommove=false +end +if _isstatic==true then +self:T(self.lid..string.format("Adding STATIC bombing target %s with good hit range %d. Random move = %s.",name,goodhitrange,tostring(randommove))) +elseif _isstatic==false then +self:T(self.lid..string.format("Adding UNIT bombing target %s with good hit range %d. Random move = %s.",name,goodhitrange,tostring(randommove))) +else +self:E(self.lid..string.format("ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!",name)) +end +local speed=0 +if _isstatic==false then +speed=self:_GetSpeed(unit) +end +local target={} +target.name=name +target.target=unit +target.goodhitrange=goodhitrange +target.move=randommove +target.speed=speed +target.coordinate=unit:GetCoordinate() +if _isstatic then +target.type=RANGE.TargetType.STATIC +else +target.type=RANGE.TargetType.UNIT +end +table.insert(self.bombingTargets,target) +return self +end +function RANGE:AddBombingTargetCoordinate(coord,name,goodhitrange) +local target={} +target.name=name or"Bomb Target" +target.target=nil +target.goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange +target.move=false +target.speed=0 +target.coordinate=coord +target.type=RANGE.TargetType.COORD +table.insert(self.bombingTargets,target) +return self +end +function RANGE:AddBombingTargetScenery(scenery,goodhitrange) +local name=scenery:GetName() +goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange +if name then +self:T(self.lid..string.format("Adding SCENERY bombing target %s with good hit range %d",name,goodhitrange)) +else +self:E(self.lid..string.format("ERROR! No bombing target with name %s could be found!",name)) +end +local target={} +target.name=name +target.target=scenery +target.goodhitrange=goodhitrange +target.move=false +target.speed=0 +target.coordinate=scenery:GetCoordinate() +target.type=RANGE.TargetType.SCENERY +table.insert(self.bombingTargets,target) +return self +end +function RANGE:AddBombingTargetGroup(group,goodhitrange,randommove) +self:F({group=group,goodhitrange=goodhitrange,randommove=randommove}) +if group and type(group)=="string"then +group=GROUP:FindByName(group) +end +if group then +local _units=group:GetUnits() +for _,_unit in pairs(_units)do +if _unit and _unit:IsAlive()then +self:AddBombingTargetUnit(_unit,goodhitrange,randommove) +end +end +end +return self +end +function RANGE:GetFoullineDistance(namepit,namefoulline) +self:F({namepit=namepit,namefoulline=namefoulline}) +local _staticpit=self:_CheckStatic(namepit) +local _staticfoul=self:_CheckStatic(namefoulline) +local pit=nil +if _staticpit==true then +pit=STATIC:FindByName(namepit,false) +elseif _staticpit==false then +pit=UNIT:FindByName(namepit) +else +self:E(self.lid..string.format("ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.",namepit)) +end +local foul=nil +if _staticfoul==true then +foul=STATIC:FindByName(namefoulline,false) +elseif _staticfoul==false then +foul=UNIT:FindByName(namefoulline) +else +self:E(self.lid..string.format("ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.",namefoulline)) +end +local fouldist=0 +if pit~=nil and foul~=nil then +fouldist=pit:GetCoordinate():Get2DDistance(foul:GetCoordinate()) +else +self:E(self.lid..string.format("ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.",namepit,namefoulline)) +end +self:T(self.lid..string.format("Foul line distance = %.1f m.",fouldist)) +return fouldist +end +function RANGE:OnEventBirth(EventData) +self:F({eventbirth=EventData}) +if not EventData.IniPlayerName then return end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName,EventData.IniPlayerName) +self:T3(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."BIRTH: player = "..tostring(_playername)) +if _unit and _playername then +local _uid=_unit:GetID() +local _group=_unit:GetGroup() +local _gid=_group:GetID() +local _callsign=_unit:GetCallsign() +local text=string.format("Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)",_playername,_callsign,_unitName,_uid,_group:GetName(),_gid) +self:T(self.lid..text) +self.strafeStatus[_uid]=nil +if self.Coalition then +if EventData.IniCoalition==self.Coalition then +self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName) +end +else +self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName) +end +self.PlayerSettings[_playername]={} +self.PlayerSettings[_playername].smokebombimpact=self.defaultsmokebomb +self.PlayerSettings[_playername].flaredirecthits=false +self.PlayerSettings[_playername].smokecolor=SMOKECOLOR.Blue +self.PlayerSettings[_playername].flarecolor=FLARECOLOR.Red +self.PlayerSettings[_playername].delaysmoke=true +self.PlayerSettings[_playername].messages=true +self.PlayerSettings[_playername].client=CLIENT:FindByName(_unitName,nil,true) +self.PlayerSettings[_playername].unitname=_unitName +self.PlayerSettings[_playername].unit=_unit +self.PlayerSettings[_playername].playername=_playername +self.PlayerSettings[_playername].airframe=EventData.IniUnit:GetTypeName() +self.PlayerSettings[_playername].inzone=false +if self.planes[_uid]~=true then +self.timerCheckZone=TIMER:New(self._CheckInZone,self,EventData.IniUnitName):Start(1,1) +self.planes[_uid]=true +end +end +end +function RANGE:OnEventHit(EventData) +self:F({eventhit=EventData}) +self:T3(self.lid.."HIT: Ini unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."HIT: Ini group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."HIT: Tgt target = "..tostring(EventData.TgtUnitName)) +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit==nil or _playername==nil then +return +end +local _unitID=_unit:GetID() +local target=EventData.TgtUnit +local targetname=EventData.TgtUnitName +local _currentTarget=self.strafeStatus[_unitID] +if _currentTarget and target and target:IsAlive()then +local playerPos=_unit:GetCoordinate() +local targetPos=target:GetCoordinate() +for _,_target in pairs(_currentTarget.zone.targets)do +if _target and _target:IsAlive()and _target:GetName()==targetname then +local dist=playerPos:Get2DDistance(targetPos) +if dist>_currentTarget.zone.foulline then +_currentTarget.hits=_currentTarget.hits+1 +if _unit and _playername and self.PlayerSettings[_playername].flaredirecthits then +targetPos:Flare(self.PlayerSettings[_playername].flarecolor) +end +else +if _currentTarget.pastfoulline==false and _unit and _playername then +local _d=_currentTarget.zone.foulline +local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.",self:_myname(_unitName),_d,targetname) +if self.useSRS then +local ttstext=string.format("%s, Invalid hit! You already passed foul line distance of %d meters for target %s.",self:_myname(_unitName),_d,targetname) +self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2) +end +self:_DisplayMessageToGroup(_unit,text) +self:T2(self.lid..text) +_currentTarget.pastfoulline=true +end +end +end +end +end +for _,_bombtarget in pairs(self.bombingTargets)do +local _target=_bombtarget.target +if _target and _target:IsAlive()and _bombtarget.name==targetname then +if _unit and _playername then +if self.PlayerSettings[_playername].flaredirecthits then +local targetPos=_target:GetCoordinate() +targetPos:Flare(self.PlayerSettings[_playername].flarecolor) +end +end +end +end +end +function RANGE._OnImpact(weapon,self,playerData,attackHdg,attackAlt,attackVel) +if not playerData then return end +local _closetTarget=nil +local _distance=nil +local _closeCoord=nil +local _hitquality="POOR" +local _callsign=self:_myname(playerData.unitname) +local _playername=playerData.playername +local _unit=playerData.unit +local impactcoord=weapon:GetImpactCoordinate() +local insidezone=self.rangezone:IsCoordinateInZone(impactcoord) +if playerData and playerData.smokebombimpact and insidezone then +if playerData.delaysmoke then +impactcoord:Smoke(playerData.smokecolor,30,self.TdelaySmoke) +else +impactcoord:Smoke(playerData.smokecolor,30) +end +end +for _,_bombtarget in pairs(self.bombingTargets)do +local bombtarget=_bombtarget +local targetcoord=self:_GetBombTargetCoordinate(_bombtarget) +if targetcoord then +local _temp=impactcoord:Get2DDistance(targetcoord) +if _distance==nil or _temp<_distance then +_distance=_temp +_closetTarget=bombtarget +_closeCoord=targetcoord +if _distance<=1.53 then +_hitquality="SHACK" +elseif _distance<=0.5*bombtarget.goodhitrange then +_hitquality="EXCELLENT" +elseif _distance<=bombtarget.goodhitrange then +_hitquality="GOOD" +elseif _distance<=2*bombtarget.goodhitrange then +_hitquality="INEFFECTIVE" +else +_hitquality="POOR" +end +end +end +end +if _distance and _distance<=self.scorebombdistance then +if not self.bombPlayerResults[_playername]then +self.bombPlayerResults[_playername]={} +end +local _results=self.bombPlayerResults[_playername] +local result={} +result.command=SOCKET.DataType.BOMBRESULT +result.name=_closetTarget.name or"unknown" +result.distance=_distance +result.radial=_closeCoord:HeadingTo(impactcoord) +result.weapon=weapon:GetTypeName()or"unknown" +result.quality=_hitquality +result.player=playerData.playername +result.time=timer.getAbsTime() +result.clock=UTILS.SecondsToClock(result.time,true) +result.midate=UTILS.GetDCSMissionDate() +result.theatre=env.mission.theatre +result.airframe=playerData.airframe +result.roundsFired=0 +result.roundsHit=0 +result.roundsQuality="N/A" +result.rangename=self.rangename +result.attackHdg=attackHdg +result.attackVel=attackVel +result.attackAlt=attackAlt +if os and os.date then +result.date=os.date() +else +self:E(self.lid.."os or os.date() not available") +result.date="n/a" +end +table.insert(_results,result) +self:Impact(result,playerData) +elseif insidezone then +local _message=string.format("%s, weapon impacted too far from nearest range target (>%.1f km). No score!",_callsign,self.scorebombdistance/1000) +if self.useSRS then +local ttstext=string.format("%s, weapon impacted too far from nearest range target, mor than %.1f kilometer. No score!",_callsign,self.scorebombdistance/1000) +self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2) +end +self:_DisplayMessageToGroup(_unit,_message,nil,false) +if self.rangecontrol then +if self.useSRS then +self.controlsrsQ:NewTransmission(_message,nil,self.controlmsrs,nil,1) +else +self.rangecontrol:NewTransmission(self.Sound.RCWeaponImpactedTooFar.filename,self.Sound.RCWeaponImpactedTooFar.duration,self.soundpath,nil,nil,_message,self.subduration) +end +end +else +self:T(self.lid.."Weapon impacted outside range zone.") +end +end +function RANGE:OnEventShot(EventData) +self:F({eventshot=EventData}) +if EventData.Weapon==nil or EventData.IniDCSUnit==nil or EventData.IniPlayerName==nil then +return +end +local weapon=WEAPON:New(EventData.weapon) +local _track=(weapon:IsBomb()and self.trackbombs)or(weapon:IsRocket()and self.trackrockets)or(weapon:IsMissile()and self.trackmissiles) +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName,EventData.IniPlayerName) +local dPR=self.BombtrackThreshold*2 +if _unit and _playername then +dPR=_unit:GetCoordinate():Get2DDistance(self.location) +self:T(self.lid..string.format("Range %s, player %s, player-range distance = %d km.",self.rangename,_playername,dPR/1000)) +end +if _track and dPR<=self.BombtrackThreshold and _unit and _playername and self.PlayerSettings[_playername]then +local playerData=self.PlayerSettings[_playername] +if not playerData then return end +local attackHdg=_unit:GetHeading() +local attackAlt=_unit:GetHeight() +attackAlt=UTILS.MetersToFeet(attackAlt) +local attackVel=_unit:GetVelocityKNOTS() +self:T(self.lid..string.format("RANGE %s: Tracking %s - %s.",self.rangename,weapon:GetTypeName(),weapon:GetName())) +weapon:SetFuncImpact(RANGE._OnImpact,self,playerData,attackHdg,attackAlt,attackVel) +self:T(self.lid..string.format("Range %s, player %s: Tracking of weapon starts in 0.1 seconds.",self.rangename,_playername)) +weapon:StartTrack(0.1) +end +end +function RANGE:onafterStatus(From,Event,To) +if self.verbose>0 then +local fsmstate=self:GetState() +local text=string.format("Range status: %s",fsmstate) +if self.instructor then +local alive="N/A" +if self.instructorrelayname then +local relay=UNIT:FindByName(self.instructorrelayname) +if relay then +alive=tostring(relay:IsAlive()) +end +end +text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)",self.instructorfreq,tostring(self.instructorrelayname),alive) +end +if self.rangecontrol then +local alive="N/A" +if self.rangecontrolrelayname then +local relay=UNIT:FindByName(self.rangecontrolrelayname) +if relay then +alive=tostring(relay:IsAlive()) +end +end +text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)",self.rangecontrolfreq,tostring(self.rangecontrolrelayname),alive) +end +self:T(self.lid..text) +end +self:_CheckPlayers() +self:__Status(-10) +end +function RANGE:onafterEnterRange(From,Event,To,player) +if self.instructor and self.rangecontrol then +if self.useSRS then +local text=string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz",self.rangecontrolfreq) +local ttstext=string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.",self.rangecontrolfreq) +local group=player.client:GetGroup() +self.instructsrsQ:NewTransmission(ttstext,nil,self.instructmsrs,nil,1,{group},text,10) +else +local RF=UTILS.Split(string.format("%.3f",self.rangecontrolfreq),".") +self.instructor:NewTransmission(self.Sound.IREnterRange.filename,self.Sound.IREnterRange.duration,self.soundpath) +self.instructor:Number2Transmission(RF[1]) +if tonumber(RF[2])>0 then +self.instructor:NewTransmission(self.Sound.IRDecimal.filename,self.Sound.IRDecimal.duration,self.soundpath) +self.instructor:Number2Transmission(RF[2]) +end +self.instructor:NewTransmission(self.Sound.IRMegaHertz.filename,self.Sound.IRMegaHertz.duration,self.soundpath) +end +end +end +function RANGE:onafterExitRange(From,Event,To,player) +if self.instructor then +if self.useSRS then +local text="You left the bombing range zone. " +local r=math.random(5) +if r==1 then +text=text.."Have a nice day!" +elseif r==2 then +text=text.."Take care and bye bye!" +elseif r==3 then +text=text.."Talk to you soon!" +elseif r==4 then +text=text.."See you in two weeks!" +elseif r==5 then +text=text.."!" +end +self.instructsrsQ:NewTransmission(text,nil,self.instructmsrs,nil,1,{player.client:GetGroup()},text,10) +else +self.instructor:NewTransmission(self.Sound.IRExitRange.filename,self.Sound.IRExitRange.duration,self.soundpath) +end +end +end +function RANGE:onafterImpact(From,Event,To,result,player) +local targetname=nil +if#self.bombingTargets>1 then +targetname=result.name +end +local text=string.format("%s, impact %03d° for %d ft (%d m)",player.playername,result.radial,UTILS.MetersToFeet(result.distance),result.distance) +if targetname then +text=text..string.format(" from bulls of target %s.",targetname) +else +text=text.."." +end +text=text..string.format(" %s hit.",result.quality) +if self.rangecontrol then +if self.useSRS then +local group=player.client:GetGroup() +self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10) +else +self.rangecontrol:NewTransmission(self.Sound.RCImpact.filename,self.Sound.RCImpact.duration,self.soundpath,nil,nil,text,self.subduration) +self.rangecontrol:Number2Transmission(string.format("%03d",result.radial),nil,0.1) +self.rangecontrol:NewTransmission(self.Sound.RCDegrees.filename,self.Sound.RCDegrees.duration,self.soundpath) +self.rangecontrol:NewTransmission(self.Sound.RCFor.filename,self.Sound.RCFor.duration,self.soundpath) +self.rangecontrol:Number2Transmission(string.format("%d",UTILS.MetersToFeet(result.distance))) +self.rangecontrol:NewTransmission(self.Sound.RCFeet.filename,self.Sound.RCFeet.duration,self.soundpath) +if result.quality=="POOR"then +self.rangecontrol:NewTransmission(self.Sound.RCPoorHit.filename,self.Sound.RCPoorHit.duration,self.soundpath,nil,0.5) +elseif result.quality=="INEFFECTIVE"then +self.rangecontrol:NewTransmission(self.Sound.RCIneffectiveHit.filename,self.Sound.RCIneffectiveHit.duration,self.soundpath,nil,0.5) +elseif result.quality=="GOOD"then +self.rangecontrol:NewTransmission(self.Sound.RCGoodHit.filename,self.Sound.RCGoodHit.duration,self.soundpath,nil,0.5) +elseif result.quality=="EXCELLENT"then +self.rangecontrol:NewTransmission(self.Sound.RCExcellentHit.filename,self.Sound.RCExcellentHit.duration,self.soundpath,nil,0.5) +end +end +end +if player.unitname and not self.useSRS then +local unit=UNIT:FindByName(player.unitname) +self:_DisplayMessageToGroup(unit,text,nil,true) +self:T(self.lid..text) +end +if self.autosave then +self:Save() +end +if self.funkmanSocket then +self.funkmanSocket:SendTable(result) +end +end +function RANGE:onafterStrafeResult(From,Event,To,player,result) +if self.funkmanSocket then +self.funkmanSocket:SendTable(result) +end +end +function RANGE:onbeforeSave(From,Event,To) +if io and lfs then +return true +else +self:E(self.lid..string.format("WARNING: io and/or lfs not desanitized. Cannot save player results.")) +return false +end +end +function RANGE:onafterSave(From,Event,To) +local function _savefile(filename,data) +local f=io.open(filename,"wb") +if f then +f:write(data) +f:close() +self:T(self.lid..string.format("Saving player results to file %s",tostring(filename))) +else +self:E(self.lid..string.format("ERROR: Could not save results to file %s",tostring(filename))) +end +end +local path=self.targetpath or lfs.writedir()..[[Logs\]] +local filename=path..string.format("RANGE-%s_BombingResults.csv",self.rangename) +local scores="Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time" +for playername,results in pairs(self.bombPlayerResults)do +for i,_result in pairs(results)do +local result=_result +local distance=result.distance +local weapon=result.weapon +local target=result.name +local radial=result.radial +local quality=result.quality +local time=UTILS.SecondsToClock(result.time,true) +local airframe=result.airframe +local date=result.date or"n/a" +scores=scores..string.format("\n%s,%d,%s,%.2f,%03d,%s,%s,%s,%s,%s",playername,i,target,distance,radial,quality,weapon,airframe,time,date) +end +end +_savefile(filename,scores) +end +function RANGE:onbeforeLoad(From,Event,To) +if io and lfs then +return true +else +self:E(self.lid..string.format("WARNING: io and/or lfs not desanitized. Cannot load player results.")) +return false +end +end +function RANGE:onafterLoad(From,Event,To) +local function _loadfile(filename) +local f=io.open(filename,"rb") +if f then +local data=f:read("*all") +f:close() +return data +else +self:E(self.lid..string.format("WARNING: Could not load player results from file %s. File might not exist just yet.",tostring(filename))) +return nil +end +end +local path=self.targetpath or lfs.writedir()..[[Logs\]] +local filename=path..string.format("RANGE-%s_BombingResults.csv",self.rangename) +local text=string.format("Loading player bomb results from file %s",filename) +self:T(self.lid..text) +local data=_loadfile(filename) +if data then +local results=UTILS.Split(data,"\n") +table.remove(results,1) +self.bombPlayerResults={} +for _,_result in pairs(results)do +local resultdata=UTILS.Split(_result,",") +local result={} +local playername=resultdata[1] +result.player=playername +result.name=tostring(resultdata[3]) +result.distance=tonumber(resultdata[4]) +result.radial=tonumber(resultdata[5]) +result.quality=tostring(resultdata[6]) +result.weapon=tostring(resultdata[7]) +result.airframe=tostring(resultdata[8]) +result.time=UTILS.ClockToSeconds(resultdata[9]or"00:00:00") +result.date=resultdata[10]or"n/a" +self.bombPlayerResults[playername]=self.bombPlayerResults[playername]or{} +table.insert(self.bombPlayerResults[playername],result) +end +end +end +function RANGE:_SaveTargetSheet(_playername,result) +local function _savefile(filename,data) +local f=io.open(filename,"wb") +if f then +f:write(data) +f:close() +else +env.info("RANGEBOSS EDIT - could not save target sheet to file") +end +end +local path=self.targetpath +if lfs then +path=path or lfs.writedir()..[[Logs\]] +end +local filename=nil +for i=1,9999 do +if self.targetprefix then +filename=string.format("%s_%s-%04d.csv",self.targetprefix,result.airframe,i) +else +local name=UTILS.ReplaceIllegalCharacters(_playername,"_") +filename=string.format("RANGERESULTS-%s_Targetsheet-%s-%04d.csv",self.rangename,name,i) +end +if path~=nil then +filename=path.."\\"..filename +end +local _exists=UTILS.FileExists(filename) +if not _exists then +break +end +end +local data="Name,Target,Rounds Fired,Rounds Hit,Rounds Quality,Airframe,Mission Time,OS Time\n" +local target=result.name +local airframe=result.airframe +local roundsFired=result.roundsFired +local roundsHit=result.roundsHit +local strafeResult=result.roundsQuality +local time=UTILS.SecondsToClock(result.time) +local date="n/a" +if os then +date=os.date() +end +data=data..string.format("%s,%s,%d,%d,%s,%s,%s,%s",_playername,target,roundsFired,roundsHit,strafeResult,airframe,time,date) +_savefile(filename,data) +end +function RANGE:_DisplayMyStrafePitResults(_unitName) +self:F(_unitName) +local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local _message=string.format("My Top %d Strafe Pit Results:\n",self.ndisplayresult) +local _results=self.strafePlayerResults[_playername] +if _results==nil then +_message=string.format("%s: No Score yet.",_playername) +else +local _sort=function(a,b) +return a.roundsHit>b.roundsHit +end +table.sort(_results,_sort) +local _bestMsg="" +local _count=1 +for _,_result in pairs(_results)do +local result=_result +_message=_message..string.format("\n[%d] Hits %d - %s - %s",_count,result.roundsHit,result.name,result.roundsQuality) +if _bestMsg==""then +_bestMsg=string.format("Hits %d - %s - %s",result.roundsHit,result.name,result.roundsQuality) +end +if _count==self.ndisplayresult then +break +end +_count=_count+1 +end +_message=_message.."\n\nBEST: ".._bestMsg +end +self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer) +end +end +function RANGE:_DisplayStrafePitResults(_unitName) +self:F(_unitName) +local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local _playerResults={} +local _message=string.format("Strafe Pit Results - Top %d Players:\n",self.ndisplayresult) +for _playerName,_results in pairs(self.strafePlayerResults)do +local _best=nil +for _,_result in pairs(_results)do +if _best==nil or _result.roundsHit>_best.roundsHit then +_best=_result +end +end +if _best~=nil then +local text=string.format("%s: Hits %i - %s - %s",_playerName,_best.roundsHit,_best.name,_best.roundsQuality) +table.insert(_playerResults,{msg=text,hits=_best.roundsHit}) +end +end +local _sort=function(a,b) +return a.hits>b.hits +end +table.sort(_playerResults,_sort) +for _i=1,math.min(#_playerResults,self.ndisplayresult)do +_message=_message..string.format("\n[%d] %s",_i,_playerResults[_i].msg) +end +if#_playerResults<1 then +_message=_message.."No player scored yet." +end +self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer) +end +end +function RANGE:_DisplayMyBombingResults(_unitName) +self:F(_unitName) +local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local _message=string.format("My Top %d Bombing Results:\n",self.ndisplayresult) +local _results=self.bombPlayerResults[_playername] +if _results==nil then +_message=_playername..": No Score yet." +else +local _sort=function(a,b) +return a.distance180 then +heading=heading-180 +else +heading=heading+180 +end +local mycoord=coord:ToStringA2G(_unit,_settings) +_text=_text..string.format("\n- %s: heading %03d°\n%s",_strafepit.name,heading,mycoord) +end +self:_DisplayMessageToGroup(_unit,_text,nil,true,true,_multiplayer) +end +end +function RANGE:_DisplayRangeWeather(_unitname) +self:F(_unitname) +local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local text="" +local coord=unit:GetCoordinate() +if self.location then +local position=self.location +local T=position:GetTemperature() +local P=position:GetPressure() +local Wd,Ws=position:GetWind() +local Bn,Bd=UTILS.BeaufortScale(Ws) +local WD=string.format('%03d°',Wd) +local Ts=string.format("%d°C",T) +local hPa2inHg=0.0295299830714 +local hPa2mmHg=0.7500615613030 +local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS +local tT=string.format("%d°C",T) +local tW=string.format("%.1f m/s",Ws) +local tP=string.format("%.1f mmHg",P*hPa2mmHg) +if settings:IsImperial()then +tW=string.format("%.1f knots",UTILS.MpsToKnots(Ws)) +tP=string.format("%.2f inHg",P*hPa2inHg) +end +text=text..string.format("Weather Report at %s:\n",self.rangename) +text=text..string.format("--------------------------------------------------\n") +text=text..string.format("Temperature %s\n",tT) +text=text..string.format("Wind from %s at %s (%s)\n",WD,tW,Bd) +text=text..string.format("QFE %.1f hPa = %s",P,tP) +else +text=string.format("No range location defined for range %s.",self.rangename) +end +self:_DisplayMessageToGroup(unit,text,nil,true,true,_multiplayer) +self:T2(self.lid..text) +else +self:T(self.lid..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s",_unitname)) +end +end +function RANGE:_CheckPlayers() +for playername,_playersettings in pairs(self.PlayerSettings)do +local playersettings=_playersettings +local unitname=playersettings.unitname +local unit=UNIT:FindByName(unitname) +if unit and unit:IsAlive()then +local unitalt=unit:GetAltitude(false) +local unitaltinfeet=UTILS.MetersToFeet(unitalt) +if unit:IsInZone(self.rangezone)and(not self.ceilingenabled or unitaltinfeet0 then +accur=_result.hits/shots*100 +if accur>100 then +accur=100 +end +end +local resulttext="" +if _result.pastfoulline==true then +resulttext="* INVALID - PASSED FOUL LINE *" +_sound=self.Sound.RCPoorPass +else +if accur>=90 then +resulttext="DEADEYE PASS" +_sound=self.Sound.RCExcellentPass +elseif accur>=75 then +resulttext="EXCELLENT PASS" +_sound=self.Sound.RCExcellentPass +elseif accur>=50 then +resulttext="GOOD PASS" +_sound=self.Sound.RCGoodPass +elseif accur>=25 then +resulttext="INEFFECTIVE PASS" +_sound=self.Sound.RCIneffectivePass +else +resulttext="POOR PASS" +_sound=self.Sound.RCPoorPass +end +end +local _text=string.format("%s, hits on target %s: %d",self:_myname(_unitName),_result.zone.name,_result.hits) +local ttstext=string.format("%s, hits on target %s: %d.",self:_myname(_unitName),_result.zone.name,_result.hits) +if shots and accur then +_text=_text..string.format("\nTotal rounds fired %d. Accuracy %.1f %%.",shots,accur) +ttstext=ttstext..string.format(". Total rounds fired %d. Accuracy %.1f percent.",shots,accur) +end +_text=_text..string.format("\n%s",resulttext) +ttstext=ttstext..string.format(" %s",resulttext) +self:_DisplayMessageToGroup(_unit,_text) +local result={} +result.command=SOCKET.DataType.STRAFERESULT +result.player=_playername +result.name=_result.zone.name or"unknown" +result.time=timer.getAbsTime() +result.clock=UTILS.SecondsToClock(result.time) +result.midate=UTILS.GetDCSMissionDate() +result.theatre=env.mission.theatre +result.roundsFired=shots +result.roundsHit=_result.hits +result.roundsQuality=resulttext +result.strafeAccuracy=accur +result.rangename=self.rangename +result.airframe=playerData.airframe +result.invalid=_result.pastfoulline +self:StrafeResult(playerData,result) +if playerData and playerData.targeton and self.targetsheet then +self:_SaveTargetSheet(_playername,result) +end +if self.rangecontrol then +if self.useSRS then +self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,1) +else +self.rangecontrol:NewTransmission(self.Sound.RCHitsOnTarget.filename,self.Sound.RCHitsOnTarget.duration,self.soundpath) +self.rangecontrol:Number2Transmission(string.format("%d",_result.hits)) +if shots and accur then +self.rangecontrol:NewTransmission(self.Sound.RCTotalRoundsFired.filename,self.Sound.RCTotalRoundsFired.duration,self.soundpath,nil,0.2) +self.rangecontrol:Number2Transmission(string.format("%d",shots),nil,0.2) +self.rangecontrol:NewTransmission(self.Sound.RCAccuracy.filename,self.Sound.RCAccuracy.duration,self.soundpath,nil,0.2) +self.rangecontrol:Number2Transmission(string.format("%d",UTILS.Round(accur,0))) +self.rangecontrol:NewTransmission(self.Sound.RCPercent.filename,self.Sound.RCPercent.duration,self.soundpath) +end +self.rangecontrol:NewTransmission(_sound.filename,_sound.duration,self.soundpath,nil,0.5) +end +end +self.strafeStatus[_unitID]=nil +local _stats=self.strafePlayerResults[_playername]or{} +table.insert(_stats,result) +self.strafePlayerResults[_playername]=_stats +end +end +else +for _,_targetZone in pairs(self.strafeTargets)do +local target=_targetZone +local zone=target.polygon +local unitinzone=checkme(target.heading,zone) +if unitinzone then +local _ammo=self:_GetAmmo(_unitName) +self.strafeStatus[_unitID]={hits=0,zone=target,time=1,ammo=_ammo,pastfoulline=false} +local _msg=string.format("%s, rolling in on strafe pit %s.",self:_myname(_unitName),target.name) +if self.rangecontrol then +if self.useSRS then +self.controlsrsQ:NewTransmission(_msg,nil,self.controlmsrs,nil,1) +else +self.rangecontrol:NewTransmission(self.Sound.RCRollingInOnStrafeTarget.filename,self.Sound.RCRollingInOnStrafeTarget.duration,self.soundpath) +end +end +self:_DisplayMessageToGroup(_unit,_msg,10,true) +self:RollingIn(playerData,target) +break +end +end +end +end +end +function RANGE:_AddF10Commands(_unitName) +self:F(_unitName) +local _unit,playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and playername then +local group=_unit:GetGroup() +local _gid=group:GetID() +if group and _gid then +if not self.MenuAddedTo[_gid]then +self.MenuAddedTo[_gid]=true +local _rootMenu=nil +if self.menuF10root then +_rootMenu=self.menuF10root +self:T2(self.lid..string.format("Creating F10 menu for group %s",group:GetName())) +elseif RANGE.MenuF10Root then +_rootMenu=RANGE.MenuF10Root +else +if RANGE.MenuF10[_gid]==nil then +self:T2(self.lid..string.format("Creating F10 menu 'On the Range' for group %s",group:GetName())) +else +self:T2(self.lid..string.format("F10 menu 'On the Range' already EXISTS for group %s",group:GetName())) +end +_rootMenu=RANGE.MenuF10[_gid]or MENU_GROUP:New(group,"On the Range") +end +local _rangePath=MENU_GROUP:New(group,self.rangename,_rootMenu) +local _infoPath=MENU_GROUP:New(group,"Range Info",_rangePath) +local _markPath=MENU_GROUP:New(group,"Mark Targets",_rangePath) +local _statsPath=MENU_GROUP:New(group,"Statistics",_rangePath) +local _settingsPath=MENU_GROUP:New(group,"My Settings",_rangePath) +local _mysmokePath=MENU_GROUP:New(group,"Smoke Color",_settingsPath) +local _myflarePath=MENU_GROUP:New(group,"Flare Color",_settingsPath) +local _MoMap=MENU_GROUP_COMMAND:New(group,"Mark On Map",_markPath,self._MarkTargetsOnMap,self,_unitName) +local _IllRng=MENU_GROUP_COMMAND:New(group,"Illuminate Range",_markPath,self._IlluminateBombTargets,self,_unitName) +local _SSpit=MENU_GROUP_COMMAND:New(group,"Smoke Strafe Pits",_markPath,self._SmokeStrafeTargetBoxes,self,_unitName) +local _SStgts=MENU_GROUP_COMMAND:New(group,"Smoke Strafe Tgts",_markPath,self._SmokeStrafeTargets,self,_unitName) +local _SBtgts=MENU_GROUP_COMMAND:New(group,"Smoke Bomb Tgts",_markPath,self._SmokeBombTargets,self,_unitName) +local _AllSR=MENU_GROUP_COMMAND:New(group,"All Strafe Results",_statsPath,self._DisplayStrafePitResults,self,_unitName) +local _AllBR=MENU_GROUP_COMMAND:New(group,"All Bombing Results",_statsPath,self._DisplayBombingResults,self,_unitName) +local _MySR=MENU_GROUP_COMMAND:New(group,"My Strafe Results",_statsPath,self._DisplayMyStrafePitResults,self,_unitName) +local _MyBR=MENU_GROUP_COMMAND:New(group,"My Bomb Results",_statsPath,self._DisplayMyBombingResults,self,_unitName) +local _ResetST=MENU_GROUP_COMMAND:New(group,"Reset All Stats",_statsPath,self._ResetRangeStats,self,_unitName) +local _BlueSM=MENU_GROUP_COMMAND:New(group,"Blue Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Blue) +local _GrSM=MENU_GROUP_COMMAND:New(group,"Green Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Green) +local _OrSM=MENU_GROUP_COMMAND:New(group,"Orange Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Orange) +local _ReSM=MENU_GROUP_COMMAND:New(group,"Red Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Red) +local _WhSm=MENU_GROUP_COMMAND:New(group,"White Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.White) +local _GrFl=MENU_GROUP_COMMAND:New(group,"Green Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Green) +local _ReFl=MENU_GROUP_COMMAND:New(group,"Red Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Red) +local _WhFl=MENU_GROUP_COMMAND:New(group,"White Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.White) +local _YeFl=MENU_GROUP_COMMAND:New(group,"Yellow Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Yellow) +local _SmDe=MENU_GROUP_COMMAND:New(group,"Smoke Delay On/Off",_settingsPath,self._SmokeBombDelayOnOff,self,_unitName) +local _SmIm=MENU_GROUP_COMMAND:New(group,"Smoke Impact On/Off",_settingsPath,self._SmokeBombImpactOnOff,self,_unitName) +local _FlHi=MENU_GROUP_COMMAND:New(group,"Flare Hits On/Off",_settingsPath,self._FlareDirectHitsOnOff,self,_unitName) +local _AlMeA=MENU_GROUP_COMMAND:New(group,"All Messages On/Off",_settingsPath,self._MessagesToPlayerOnOff,self,_unitName) +local _TrpSh=MENU_GROUP_COMMAND:New(group,"Targetsheet On/Off",_settingsPath,self._TargetsheetOnOff,self,_unitName) +local _WeIn=MENU_GROUP_COMMAND:New(group,"General Info",_infoPath,self._DisplayRangeInfo,self,_unitName) +local _WeRe=MENU_GROUP_COMMAND:New(group,"Weather Report",_infoPath,self._DisplayRangeWeather,self,_unitName) +local _BoTgtgs=MENU_GROUP_COMMAND:New(group,"Bombing Targets",_infoPath,self._DisplayBombTargets,self,_unitName) +local _StrPits=MENU_GROUP_COMMAND:New(group,"Strafe Pits",_infoPath,self._DisplayStrafePits,self,_unitName):Refresh() +end +else +self:E(self.lid.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName or"N/A") +end +else +self:E(self.lid.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName or"N/A") +end +end +function RANGE:_GetBombTargetCoordinate(target) +local coord=nil +if target.type==RANGE.TargetType.UNIT then +if target.target and target.target:IsAlive()then +coord=target.target:GetCoordinate() +target.coordinate=coord +else +coord=target.coordinate +end +elseif target.type==RANGE.TargetType.STATIC then +coord=target.coordinate +elseif target.type==RANGE.TargetType.COORD then +coord=target.coordinate +elseif target.type==RANGE.TargetType.SCENERY then +coord=target.coordinate +else +self:E(self.lid.."ERROR: Unknown target type.") +end +return coord +end +function RANGE:_GetAmmo(unitname) +self:F2(unitname) +local ammo=0 +local unit,playername=self:_GetPlayerUnitAndName(unitname) +if unit and playername then +local has_ammo=false +local ammotable=unit:GetAmmo() +self:T2({ammotable=ammotable}) +if ammotable~=nil then +local weapons=#ammotable +self:T2(self.lid..string.format("Number of weapons %d.",weapons)) +for w=1,weapons do +local Nammo=ammotable[w]["count"] +local Tammo=ammotable[w]["desc"]["typeName"] +if string.match(Tammo,"shell")then +ammo=ammo+Nammo +local text=string.format("Player %s has %d rounds ammo of type %s",playername,Nammo,Tammo) +self:T(self.lid..text) +else +local text=string.format("Player %s has %d ammo of type %s",playername,Nammo,Tammo) +self:T(self.lid..text) +end +end +end +end +return ammo +end +function RANGE:_MarkTargetsOnMap(_unitName) +self:F(_unitName) +local group=nil +if _unitName then +group=UNIT:FindByName(_unitName):GetGroup() +end +for _,_bombtarget in pairs(self.bombingTargets)do +local bombtarget=_bombtarget +local coord=self:_GetBombTargetCoordinate(_bombtarget) +if group then +coord:MarkToGroup(string.format("Bomb target %s:\n%s\n%s",bombtarget.name,coord:ToStringLLDMS(),coord:ToStringBULLS(group:GetCoalition())),group) +else +coord:MarkToAll(string.format("Bomb target %s",bombtarget.name)) +end +end +for _,_strafepit in pairs(self.strafeTargets)do +for _,_target in pairs(_strafepit.targets)do +local _target=_target +if _target and _target:IsAlive()then +local coord=_target:GetCoordinate() +if group then +coord:MarkToGroup(string.format("Strafe target %s:\n%s\n%s",_target:GetName(),coord:ToStringLLDMS(),coord:ToStringBULLS(group:GetCoalition())),group) +else +coord:MarkToAll("Strafe target ".._target:GetName()) +end +end +end +end +if _unitName then +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +local text=string.format("%s, %s, range targets are now marked on F10 map.",self.rangename,_playername) +self:_DisplayMessageToGroup(_unit,text,5) +end +end +function RANGE:_IlluminateBombTargets(_unitName) +self:F(_unitName) +local bomb={} +for _,_bombtarget in pairs(self.bombingTargets)do +local _target=_bombtarget.target +local coord=self:_GetBombTargetCoordinate(_bombtarget) +if coord then +table.insert(bomb,coord) +end +end +if#bomb>0 then +local coord=bomb[math.random(#bomb)] +local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z) +c:IlluminationBomb() +end +local strafe={} +for _,_strafepit in pairs(self.strafeTargets)do +for _,_target in pairs(_strafepit.targets)do +local _target=_target +if _target and _target:IsAlive()then +local coord=_target:GetCoordinate() +table.insert(strafe,coord) +end +end +end +if#strafe>0 then +local coord=strafe[math.random(#strafe)] +local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z) +c:IlluminationBomb() +end +if _unitName then +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +local text=string.format("%s, %s, range targets are illuminated.",self.rangename,_playername) +self:_DisplayMessageToGroup(_unit,text,5) +end +end +function RANGE:_ResetRangeStats(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +self.strafePlayerResults[_playername]=nil +self.bombPlayerResults[_playername]=nil +local text=string.format("%s, %s, your range stats were cleared.",self.rangename,_playername) +self:DisplayMessageToGroup(_unit,text,5,false,true) +end +end +function RANGE:_DisplayMessageToGroup(_unit,_text,_time,_clear,display,_togroup) +self:F({unit=_unit,text=_text,time=_time,clear=_clear}) +_time=_time or self.Tmsg +if _clear==nil or _clear==false then +_clear=false +else +_clear=true +end +if self.messages==false then +return +end +if _unit and _unit:IsAlive()then +local _gid=_unit:GetGroup():GetID() +local _grp=_unit:GetGroup() +local _,playername=self:_GetPlayerUnitAndName(_unit:GetName()) +local playermessage=self.PlayerSettings[playername].messages +if _gid and(playermessage==true or display)and(not self.examinerexclusive)then +if _togroup and _grp then +local m=MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp) +else +local m=MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit) +end +end +if self.examinergroupname~=nil then +local _examinerid=GROUP:FindByName(self.examinergroupname) +if _examinerid then +local m=MESSAGE:New(_text,_time,nil,_clear):ToGroup(_examinerid) +end +end +end +end +function RANGE:_SmokeBombImpactOnOff(unitname) +self:F(unitname) +local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname) +if unit and playername then +local text +if self.PlayerSettings[playername].smokebombimpact==true then +self.PlayerSettings[playername].smokebombimpact=false +text=string.format("%s, %s, smoking impact points of bombs is now OFF.",self.rangename,playername) +else +self.PlayerSettings[playername].smokebombimpact=true +text=string.format("%s, %s, smoking impact points of bombs is now ON.",self.rangename,playername) +end +self:_DisplayMessageToGroup(unit,text,5,false,true) +end +end +function RANGE:_SmokeBombDelayOnOff(unitname) +self:F(unitname) +local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname) +if unit and playername then +local text +if self.PlayerSettings[playername].delaysmoke==true then +self.PlayerSettings[playername].delaysmoke=false +text=string.format("%s, %s, delayed smoke of bombs is now OFF.",self.rangename,playername) +else +self.PlayerSettings[playername].delaysmoke=true +text=string.format("%s, %s, delayed smoke of bombs is now ON.",self.rangename,playername) +end +self:_DisplayMessageToGroup(unit,text,5,false,true) +end +end +function RANGE:_MessagesToPlayerOnOff(unitname) +self:F(unitname) +local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname) +if unit and playername then +local text +if self.PlayerSettings[playername].messages==true then +text=string.format("%s, %s, display of ALL messages is now OFF.",self.rangename,playername) +else +text=string.format("%s, %s, display of ALL messages is now ON.",self.rangename,playername) +end +self:_DisplayMessageToGroup(unit,text,5,false,true) +self.PlayerSettings[playername].messages=not self.PlayerSettings[playername].messages +end +end +function RANGE:_TargetsheetOnOff(_unitname) +self:F2(_unitname) +local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.PlayerSettings[playername] +if playerData then +local text="" +if self.targetsheet then +playerData.targeton=not playerData.targeton +if playerData and playerData.targeton==true then +text=string.format("Roger, your targetsheets are now SAVED.") +else +text=string.format("Affirm, your targetsheets are NOT SAVED.") +end +else +text="Negative, target sheet data recorder is broken on this range." +end +self:_DisplayMessageToGroup(unit,text,5,false,false) +end +end +end +function RANGE:_FlareDirectHitsOnOff(unitname) +self:F(unitname) +local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname) +if unit and playername then +local text +if self.PlayerSettings[playername].flaredirecthits==true then +self.PlayerSettings[playername].flaredirecthits=false +text=string.format("%s, %s, flaring direct hits is now OFF.",self.rangename,playername) +else +self.PlayerSettings[playername].flaredirecthits=true +text=string.format("%s, %s, flaring direct hits is now ON.",self.rangename,playername) +end +self:_DisplayMessageToGroup(unit,text,5,false,true) +end +end +function RANGE:_SmokeBombTargets(unitname) +self:F(unitname) +for _,_bombtarget in pairs(self.bombingTargets)do +local _target=_bombtarget.target +local coord=self:_GetBombTargetCoordinate(_bombtarget) +if coord then +coord:Smoke(self.BombSmokeColor) +end +end +if unitname then +local unit,playername=self:_GetPlayerUnitAndName(unitname) +local text=string.format("%s, %s, bombing targets are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.BombSmokeColor)) +self:_DisplayMessageToGroup(unit,text,5) +end +end +function RANGE:_SmokeStrafeTargets(unitname) +self:F(unitname) +for _,_target in pairs(self.strafeTargets)do +_target.coordinate:Smoke(self.StrafeSmokeColor) +end +if unitname then +local unit,playername=self:_GetPlayerUnitAndName(unitname) +local text=string.format("%s, %s, strafing tragets are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.StrafeSmokeColor)) +self:_DisplayMessageToGroup(unit,text,5) +end +end +function RANGE:_SmokeStrafeTargetBoxes(unitname) +self:F(unitname) +for _,_target in pairs(self.strafeTargets)do +local zone=_target.polygon +zone:SmokeZone(self.StrafePitSmokeColor,4) +for _,_point in pairs(_target.smokepoints)do +_point:SmokeOrange() +end +end +if unitname then +local unit,playername=self:_GetPlayerUnitAndName(unitname) +local text=string.format("%s, %s, strafing pit approach boxes are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.StrafePitSmokeColor)) +self:_DisplayMessageToGroup(unit,text,5) +end +end +function RANGE:_playersmokecolor(_unitName,color) +self:F({unitname=_unitName,color=color}) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +self.PlayerSettings[_playername].smokecolor=color +local text=string.format("%s, %s, your bomb impacts are now smoked in %s.",self.rangename,_playername,self:_smokecolor2text(color)) +self:_DisplayMessageToGroup(_unit,text,5) +end +end +function RANGE:_playerflarecolor(_unitName,color) +self:F({unitname=_unitName,color=color}) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +self.PlayerSettings[_playername].flarecolor=color +local text=string.format("%s, %s, your direct hits are now flared in %s.",self.rangename,_playername,self:_flarecolor2text(color)) +self:_DisplayMessageToGroup(_unit,text,5) +end +end +function RANGE:_smokecolor2text(color) +self:F(color) +local txt="" +if color==SMOKECOLOR.Blue then +txt="blue" +elseif color==SMOKECOLOR.Green then +txt="green" +elseif color==SMOKECOLOR.Orange then +txt="orange" +elseif color==SMOKECOLOR.Red then +txt="red" +elseif color==SMOKECOLOR.White then +txt="white" +else +txt=string.format("unknown color (%s)",tostring(color)) +end +return txt +end +function RANGE:_flarecolor2text(color) +self:F(color) +local txt="" +if color==FLARECOLOR.Green then +txt="green" +elseif color==FLARECOLOR.Red then +txt="red" +elseif color==FLARECOLOR.White then +txt="white" +elseif color==FLARECOLOR.Yellow then +txt="yellow" +else +txt=string.format("unknown color (%s)",tostring(color)) +end +return txt +end +function RANGE:_CheckStatic(name) +self:F2(name) +local _DCSstatic=StaticObject.getByName(name) +if _DCSstatic and _DCSstatic:isExist()then +local _MOOSEstatic=STATIC:FindByName(name,false) +if not _MOOSEstatic then +self:T(self.lid..string.format("Adding DCS static to MOOSE database. Name = %s.",name)) +_DATABASE:AddStatic(name) +end +return true +else +self:T3(self.lid..string.format("No static object with name %s exists.",name)) +end +if UNIT:FindByName(name)then +return false +else +self:T3(self.lid..string.format("No unit object with name %s exists.",name)) +end +return nil +end +function RANGE:_GetSpeed(controllable) +self:F2(controllable) +local desc=controllable:GetDesc() +local speed=0 +if desc then +speed=desc.speedMax*3.6 +self:T({speed=speed}) +end +return speed +end +function RANGE:_GetPlayerUnitAndName(_unitName,PlayerName) +if _unitName~=nil then +local multiplayer=false +local DCSunit=Unit.getByName(_unitName) +if DCSunit and DCSunit.getPlayerName then +local playername=DCSunit:getPlayerName()or PlayerName or"None" +local unit=UNIT:Find(DCSunit) +self:T2({DCSunit=DCSunit,unit=unit,playername=playername}) +if DCSunit and unit and playername then +self:F2(playername) +local grp=unit:GetGroup() +if grp and grp:CountAliveUnits()>1 then +multiplayer=true +end +return unit,playername,multiplayer +end +end +end +return nil,nil,nil +end +function RANGE:_myname(unitname) +self:F2(unitname) +local pname="Ghost 1 1" +local unit=UNIT:FindByName(unitname) +if unit and unit:IsAlive()then +local grp=unit:GetGroup() +if grp and grp:IsAlive()then +pname=grp:GetCustomCallSign(true,true) +end +end +return pname +end +do +ZONE_GOAL={ +ClassName="ZONE_GOAL", +Goal=nil, +SmokeTime=nil, +SmokeScheduler=nil, +SmokeColor=nil, +SmokeZone=nil, +} +function ZONE_GOAL:New(Zone) +BASE:I({Zone=Zone}) +local self=BASE:Inherit(self,BASE:New()) +if type(Zone)=="string"then +self=BASE:Inherit(self,ZONE_POLYGON:NewFromGroupName(Zone)) +else +self=BASE:Inherit(self,ZONE_RADIUS:New(Zone:GetName(),Zone:GetVec2(),Zone:GetRadius())) +self:F({Zone=Zone}) +end +self.Goal=GOAL:New() +self.SmokeTime=nil +self:SetSmokeZone(true) +self:AddTransition("*","DestroyedUnit","*") +return self +end +function ZONE_GOAL:GetZone() +return self +end +function ZONE_GOAL:GetZoneName() +return self:GetName() +end +function ZONE_GOAL:SetSmokeZone(switch) +self.SmokeZone=switch +return self +end +function ZONE_GOAL:Smoke(SmokeColor) +self:F({SmokeColor=SmokeColor}) +self.SmokeColor=SmokeColor +end +function ZONE_GOAL:Flare(FlareColor) +self:FlareZone(FlareColor,30) +end +function ZONE_GOAL:onafterGuard() +self:F("Guard") +if self.SmokeZone and not self.SmokeScheduler then +self.SmokeScheduler=self:ScheduleRepeat(1,1,0.1,nil,self.StatusSmoke,self) +end +end +function ZONE_GOAL:StatusSmoke() +self:F({self.SmokeTime,self.SmokeColor}) +if self.SmokeZone then +local CurrentTime=timer.getTime() +if self.SmokeTime==nil or self.SmokeTime+300<=CurrentTime then +if self.SmokeColor then +self:GetCoordinate():Smoke(self.SmokeColor) +self.SmokeTime=CurrentTime +end +end +end +end +function ZONE_GOAL:__Destroyed(EventData) +self:F({"EventDead",EventData}) +self:F({EventData.IniUnit}) +if EventData.IniDCSUnit then +local Vec3=EventData.IniDCSUnit:getPosition().p +self:F({Vec3=Vec3}) +if Vec3 and self:IsVec3InZone(Vec3)then +local PlayerHits=_DATABASE.HITS[EventData.IniUnitName] +if PlayerHits then +for PlayerName,PlayerHit in pairs(PlayerHits.Players or{})do +self.Goal:AddPlayerContribution(PlayerName) +self:DestroyedUnit(EventData.IniUnitName,PlayerName) +end +end +end +end +end +function ZONE_GOAL:MonitorDestroyedUnits() +self:HandleEvent(EVENTS.Dead,self.__Destroyed) +self:HandleEvent(EVENTS.Crash,self.__Destroyed) +end +end +do +ZONE_GOAL_COALITION={ +ClassName="ZONE_GOAL_COALITION", +Coalition=nil, +PreviousCoalition=nil, +UnitCategories=nil, +ObjectCategories=nil, +} +ZONE_GOAL_COALITION.States={} +function ZONE_GOAL_COALITION:New(Zone,Coalition,UnitCategories) +if not Zone then +BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITION!") +return nil +end +local self=BASE:Inherit(self,ZONE_GOAL:New(Zone)) +self:F({Zone=Zone,Coalition=Coalition}) +self:SetCoalition(Coalition or coalition.side.NEUTRAL) +self:SetUnitCategories(UnitCategories) +self:SetObjectCategories() +return self +end +function ZONE_GOAL_COALITION:SetCoalition(Coalition) +self.PreviousCoalition=self.Coalition or Coalition +self.Coalition=Coalition +return self +end +function ZONE_GOAL_COALITION:SetUnitCategories(UnitCategories) +if UnitCategories and type(UnitCategories)~="table"then +UnitCategories={UnitCategories} +end +self.UnitCategories=UnitCategories or{Unit.Category.GROUND_UNIT} +return self +end +function ZONE_GOAL_COALITION:SetObjectCategories(ObjectCategories) +if ObjectCategories and type(ObjectCategories)~="table"then +ObjectCategories={ObjectCategories} +end +self.ObjectCategories=ObjectCategories or{Object.Category.UNIT,Object.Category.STATIC} +return self +end +function ZONE_GOAL_COALITION:GetCoalition() +return self.Coalition +end +function ZONE_GOAL_COALITION:GetPreviousCoalition() +return self.PreviousCoalition +end +function ZONE_GOAL_COALITION:GetCoalitionName() +return UTILS.GetCoalitionName(self.Coalition) +end +function ZONE_GOAL_COALITION:StatusZone() +local State=self:GetState() +local text=string.format("Zone state=%s, Owner=%s, Scanning...",State,self:GetCoalitionName()) +self:F(text) +self:Scan(self.ObjectCategories,self.UnitCategories) +return self +end +end +do +ZONE_CAPTURE_COALITION={ +ClassName="ZONE_CAPTURE_COALITION", +MarkBlue=nil, +MarkRed=nil, +StartInterval=nil, +RepeatInterval=nil, +HitsOn=nil, +HitTimeLast=nil, +HitTimeAttackOver=nil, +MarkOn=nil, +} +function ZONE_CAPTURE_COALITION:New(Zone,Coalition,UnitCategories,ObjectCategories) +local self=BASE:Inherit(self,ZONE_GOAL_COALITION:New(Zone,Coalition,UnitCategories)) +self:F({Zone=Zone,Coalition=Coalition,UnitCategories=UnitCategories,ObjectCategories=ObjectCategories}) +self:SetObjectCategories(ObjectCategories) +self:SetSmokeZone(false) +self:SetMarkZone(true) +self:SetStartState("Empty") +do +end +do +end +do +end +do +end +self:AddTransition("*","Guard","Guarded") +self:AddTransition("*","Empty","Empty") +self:AddTransition({"Guarded","Empty"},"Attack","Attacked") +self:AddTransition({"Guarded","Attacked","Empty"},"Capture","Captured") +_EVENTDISPATCHER:CreateEventNewZoneGoal(self) +return self +end +function ZONE_CAPTURE_COALITION:Start(StartInterval,RepeatInterval) +self.StartInterval=StartInterval or 1 +self.RepeatInterval=RepeatInterval or 15 +if self.ScheduleStatusZone then +self:ScheduleStop(self.ScheduleStatusZone) +end +self.ScheduleStatusZone=self:ScheduleRepeat(self.StartInterval,self.RepeatInterval,0.1,nil,self.StatusZone,self) +self:HandleEvent(EVENTS.Hit,self.OnEventHit) +self:Mark() +return self +end +function ZONE_CAPTURE_COALITION:Stop() +if self.ScheduleStatusZone then +self:ScheduleStop(self.ScheduleStatusZone) +end +if self.SmokeScheduler then +self:ScheduleStop(self.SmokeScheduler) +end +self:UnHandleEvent(EVENTS.Hit) +end +function ZONE_CAPTURE_COALITION:SetMonitorHits(Switch,TimeAttackOver) +self.HitsOn=Switch +self.HitTimeAttackOver=TimeAttackOver or 5*60 +return self +end +function ZONE_CAPTURE_COALITION:SetMarkZone(Switch) +if Switch==nil or Switch==true then +self.MarkOn=true +else +self.MarkOn=false +end +return self +end +function ZONE_CAPTURE_COALITION:OnEventHit(EventData) +if self.HitsOn then +local UnitHit=EventData.TgtUnit +if UnitHit and UnitHit.ClassName~="SCENERY"then +if UnitHit and UnitHit:IsInZone(self)and UnitHit:GetCoalition()==self.Coalition then +self.HitTimeLast=timer.getTime() +if self:GetState()~="Attacked"then +self:F2("Hit ==> Attack") +self:Attack() +end +end +end +end +end +function ZONE_CAPTURE_COALITION:onafterGuard() +self:F2("After Guard") +if self.SmokeZone and not self.SmokeScheduler then +self.SmokeScheduler=self:ScheduleRepeat(self.StartInterval,self.RepeatInterval,0.1,nil,self.StatusSmoke,self) +end +end +function ZONE_CAPTURE_COALITION:onenterGuarded() +self:F2("Enter Guarded") +self:Mark() +end +function ZONE_CAPTURE_COALITION:onenterCaptured() +self:F2("Enter Captured") +local NewCoalition=self:GetScannedCoalition() +self:F({NewCoalition=NewCoalition}) +self:SetCoalition(NewCoalition) +self:Mark() +self.Goal:Achieved() +end +function ZONE_CAPTURE_COALITION:onenterEmpty() +self:F2("Enter Empty") +self:Mark() +end +function ZONE_CAPTURE_COALITION:onenterAttacked() +self:F2("Enter Attacked") +self:Mark() +end +function ZONE_CAPTURE_COALITION:IsEmpty() +local IsEmpty=self:IsNoneInZone() +self:F({IsEmpty=IsEmpty}) +return IsEmpty +end +function ZONE_CAPTURE_COALITION:IsGuarded() +local IsGuarded=self:IsAllInZoneOfCoalition(self.Coalition) +self:F({IsGuarded=IsGuarded}) +return IsGuarded +end +function ZONE_CAPTURE_COALITION:IsCaptured() +local IsCaptured=self:IsAllInZoneOfOtherCoalition(self.Coalition) +self:F({IsCaptured=IsCaptured}) +return IsCaptured +end +function ZONE_CAPTURE_COALITION:IsAttacked() +local IsAttacked=self:IsSomeInZoneOfCoalition(self.Coalition) +self:F({IsAttacked=IsAttacked}) +return IsAttacked +end +function ZONE_CAPTURE_COALITION:StatusZone() +local State=self:GetState() +self:GetParent(self,ZONE_CAPTURE_COALITION).StatusZone(self) +local Tnow=timer.getTime() +if State~="Guarded"and self:IsGuarded()then +if self.HitTimeLast==nil or Tnow>=self.HitTimeLast+self.HitTimeAttackOver then +self:Guard() +self.HitTimeLast=nil +end +end +if State~="Empty"and self:IsEmpty()then +self:Empty() +end +if State~="Attacked"and self:IsAttacked()then +self:Attack() +end +if State~="Captured"and self:IsCaptured()then +self:Capture() +end +local unitset=self:GetScannedSetUnit() +local nRed=0 +local nBlue=0 +for _,object in pairs(unitset:GetSet())do +local coal=object:GetCoalition() +if object:IsAlive()then +if coal==coalition.side.RED then +nRed=nRed+1 +elseif coal==coalition.side.BLUE then +nBlue=nBlue+1 +end +end +end +if false then +local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s",self:GetZoneName(),self:GetCoalitionName(),UTILS.GetCoalitionName(self:GetPreviousCoalition()),nBlue,nRed,State) +local NewState=self:GetState() +if NewState~=State then +text=text..string.format(" --> %s",NewState) +end +self:I(text) +end +end +function ZONE_CAPTURE_COALITION:Mark() +if self.MarkOn then +local Coord=self:GetCoordinate() +local ZoneName=self:GetZoneName() +local State=self:GetState() +if self.MarkRed then +Coord:RemoveMark(self.MarkRed) +end +if self.MarkBlue then +Coord:RemoveMark(self.MarkBlue) +end +if self.Coalition==coalition.side.BLUE then +self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Blue\nGuard Zone: "..ZoneName.."\nStatus: "..State) +self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Blue\nCapture Zone: "..ZoneName.."\nStatus: "..State) +elseif self.Coalition==coalition.side.RED then +self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Red\nGuard Zone: "..ZoneName.."\nStatus: "..State) +self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Red\nCapture Zone: "..ZoneName.."\nStatus: "..State) +else +self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State) +self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State) +end +end +end +end +ARTY={ +ClassName="ARTY", +lid=nil, +Debug=false, +targets={}, +moves={}, +currentTarget=nil, +currentMove=nil, +Nammo0=0, +Nshells0=0, +Nrockets0=0, +Nmissiles0=0, +Nukes0=0, +Nillu0=0, +Nsmoke0=0, +StatusInterval=10, +WaitForShotTime=300, +DCSdesc=nil, +Type=nil, +DisplayName=nil, +groupname=nil, +alias=nil, +clusters={}, +ismobile=true, +iscargo=false, +cargogroup=nil, +IniGroupStrength=0, +IsArtillery=nil, +RearmingDistance=100, +RearmingGroup=nil, +RearmingGroupSpeed=nil, +RearmingGroupOnRoad=false, +RearmingGroupCoord=nil, +RearmingPlaceCoord=nil, +RearmingArtyOnRoad=false, +InitialCoord=nil, +report=true, +ammoshells={}, +ammorockets={}, +ammomissiles={}, +Nshots=0, +minrange=300, +maxrange=1000000, +nukewarhead=75000, +Nukes=nil, +nukefire=false, +nukefires=nil, +nukerange=nil, +Nillu=nil, +illuPower=1000000, +illuMinalt=500, +illuMaxalt=1000, +Nsmoke=nil, +smokeColor=SMOKECOLOR.Red, +relocateafterfire=false, +relocateRmin=300, +relocateRmax=800, +markallow=false, +markkey=nil, +markreadonly=false, +autorelocate=false, +autorelocatemaxdist=50000, +autorelocateonroad=false, +coalition=nil, +respawnafterdeath=false, +respawndelay=nil +} +ARTY.WeaponType={ +Auto=1073741822, +Cannon=805306368, +Rockets=30720, +CruiseMissile=2097152, +TacticalNukes=666, +IlluminationShells=667, +SmokeShells=668, +} +ARTY.db={ +["LeFH_18-40-105"]={ +displayname="FH LeFH-18 105mm", +minrange=500, +maxrange=10500, +reloadtime=nil, +}, +["M2A1-105"]={ +displayname="FH M2A1 105mm", +minrange=500, +maxrange=11500, +reloadtime=nil, +}, +["Pak40"]={ +displayname="FH Pak 40 75mm", +minrange=500, +maxrange=3000, +reloadtime=nil, +}, +["L118_Unit"]={ +displayname="L118 Light Artillery Gun", +minrange=500, +maxrange=17500, +reloadtime=nil, +}, +["Smerch"]={ +displayname="MLRS 9A52 Smerch CM 300mm", +minrange=20000, +maxrange=70000, +reloadtime=2160, +}, +["Smerch_HE"]={ +displayname="MLRS 9A52 Smerch HE 300mm", +minrange=20000, +maxrange=70000, +reloadtime=2160, +}, +["Uragan_BM-27"]={ +displayname="MLRS 9K57 Uragan BM-27 220mm", +minrange=11500, +maxrange=35800, +reloadtime=840, +}, +["Grad-URAL"]={ +displayname="MLRS BM-21 Grad 122mm", +minrange=5000, +maxrange=19000, +reloadtime=420, +}, +["HL_B8M1"]={ +displayname="MLRS HL with B8M1 80mm", +minrange=500, +maxrange=5000, +reloadtime=nil, +}, +["tt_B8M1"]={ +displayname="MLRS LC with B8M1 80mm", +minrange=500, +maxrange=5000, +reloadtime=nil, +}, +["MLRS"]={ +displayname="MLRS M270 227mm", +minrange=10000, +maxrange=32000, +reloadtime=540, +}, +["2B11 mortar"]={ +displayname="Mortar 2B11 120mm", +minrange=500, +maxrange=7000, +reloadtime=30, +}, +["PLZ05"]={ +displayname="PLZ-05", +minrange=500, +maxrange=23500, +reloadtime=nil, +}, +["SAU Gvozdika"]={ +displayname="SPH 2S1 Gvozdika 122mm", +minrange=300, +maxrange=15000, +reloadtime=nil, +}, +["SAU Msta"]={ +displayname="SPH 2S19 Msta 152mm", +minrange=300, +maxrange=23500, +reloadtime=nil, +}, +["SAU Akatsia"]={ +displayname="SPH 2S3 Akatsia 152mm", +minrange=300, +maxrange=17000, +reloadtime=nil, +}, +["SpGH_Dana"]={ +displayname="SPH Dana vz77 152mm", +minrange=300, +maxrange=18700, +reloadtime=nil, +}, +["M-109"]={ +displayname="SPH M109 Paladin 155mm", +minrange=300, +maxrange=22000, +reloadtime=nil, +}, +["M12_GMC"]={ +displayname="SPH M12 GMC 155mm", +minrange=300, +maxrange=18200, +reloadtime=nil, +}, +["Wespe124"]={ +displayname="SPH Sd.Kfz.124 Wespe 105mm", +minrange=300, +maxrange=7000, +reloadtime=nil, +}, +["T155_Firtina"]={ +displayname="SPH T155 Firtina 155mm", +minrange=300, +maxrange=41000, +reloadtime=nil, +}, +["SAU 2-C9"]={ +displayname="SPM 2S9 Nona 120mm M", +minrange=500, +maxrange=7000, +reloadtime=nil, +}, +} +ARTY.version="1.3.3" +function ARTY:New(group,alias) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) +if type(group)=="string"then +self.groupname=group +group=GROUP:FindByName(group) +if not group then +self:E(string.format("ERROR: Requested ARTY group %s does not exist! (Has to be a MOOSE group.)",self.groupname)) +return nil +end +end +if group then +self:T(string.format("ARTY script version %s. Added group %s.",ARTY.version,group:GetName())) +else +self:E("ERROR: Requested ARTY group does not exist! (Has to be a MOOSE group.)") +return nil +end +if not(group:IsGround()or group:IsShip())then +self:E(string.format("ERROR: ARTY group %s has to be a GROUND or SHIP group!",group:GetName())) +return nil +end +self:SetControllable(group) +self.groupname=group:GetName() +self.coalition=group:GetCoalition() +if alias~=nil then +self.alias=tostring(alias) +else +self.alias=self.groupname +end +self.lid=string.format("ARTY %s | ",self.alias) +self.InitialCoord=group:GetCoordinate() +local DCSgroup=Group.getByName(group:GetName()) +local DCSunit=DCSgroup:getUnit(1) +self.DCSdesc=DCSunit:getDesc() +self:T3(self.lid.."DCS descriptors for group "..group:GetName()) +for id,desc in pairs(self.DCSdesc)do +self:T3({id=id,desc=desc}) +end +self.SpeedMax=group:GetSpeedMax() +if self.SpeedMax>3.6 then +self.ismobile=true +else +self.ismobile=false +end +self.dtTrack=0.2 +self.Speed=self.SpeedMax*0.7 +self.DisplayName=self.DCSdesc.displayName +self.IsArtillery=DCSunit:hasAttribute("Artillery") +self.Type=group:GetTypeName() +self.IniGroupStrength=#group:GetUnits() +self:AddTransition("*","Start","CombatReady") +self:AddTransition("CombatReady","OpenFire","Firing") +self:AddTransition("Firing","CeaseFire","CombatReady") +self:AddTransition("CombatReady","Winchester","OutOfAmmo") +self:AddTransition({"CombatReady","OutOfAmmo"},"Rearm","Rearming") +self:AddTransition("Rearming","Rearmed","Rearmed") +self:AddTransition("*","Move","Moving") +self:AddTransition("Moving","Arrived","Arrived") +self:AddTransition("*","NewTarget","*") +self:AddTransition("*","CombatReady","CombatReady") +self:AddTransition("*","Status","*") +self:AddTransition("*","NewMove","*") +self:AddTransition("*","Dead","*") +self:AddTransition("*","Respawn","CombatReady") +self:AddTransition("*","Loaded","InTransit") +self:AddTransition("InTransit","UnLoaded","CombatReady") +self:AddTransition("Rearming","Arrived","Rearming") +self:AddTransition("Rearming","Move","Rearming") +return self +end +function ARTY:NewFromCargoGroup(cargogroup,alias) +if cargogroup then +BASE:T(string.format("ARTY script version %s. Added CARGO group %s.",ARTY.version,cargogroup:GetName())) +else +BASE:E("ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)") +return nil +end +local group=cargogroup:GetObject() +local arty=ARTY:New(group,alias) +arty.iscargo=true +arty.cargogroup=cargogroup +return arty +end +function ARTY:AssignTargetCoord(coord,prio,radius,nshells,maxengage,time,weapontype,name,unique) +self:F({coord=coord,prio=prio,radius=radius,nshells=nshells,maxengage=maxengage,time=time,weapontype=weapontype,name=name,unique=unique}) +nshells=nshells or 5 +radius=radius or 100 +maxengage=maxengage or 1 +prio=prio or 50 +prio=math.max(1,prio) +prio=math.min(100,prio) +if unique==nil then +unique=false +end +weapontype=weapontype or ARTY.WeaponType.Auto +local text=nil +if coord:IsInstanceOf("GROUP")then +text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a GROUP. Converting to COORDINATE..." +coord=coord:GetCoordinate() +elseif coord:IsInstanceOf("UNIT")then +text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a UNIT. Converting to COORDINATE..." +coord=coord:GetCoordinate() +elseif coord:IsInstanceOf("POSITIONABLE")then +text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a POSITIONABLE. Converting to COORDINATE..." +coord=coord:GetCoordinate() +elseif coord:IsInstanceOf("COORDINATE")then +else +text="ERROR: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter!" +MESSAGE:New(text,30):ToAll() +self:E(self.lid..text) +return nil +end +if text~=nil then +self:E(self.lid..text) +end +local _name=name or coord:ToStringLLDMS() +local _unique=true +_name,_unique=self:_CheckName(self.targets,_name,not unique) +if unique==true and _unique==false then +self:T(self.lid..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!",self.groupname,_name)) +return nil +end +local _time +if type(time)=="string"then +_time=self:_ClockToSeconds(time) +elseif type(time)=="number"then +_time=timer.getAbsTime()+time +else +_time=timer.getAbsTime() +end +local _target={name=_name,coord=coord,radius=radius,nshells=nshells,engaged=0,underfire=false,prio=prio,maxengage=maxengage,time=_time,weapontype=weapontype} +table.insert(self.targets,_target) +self:__NewTarget(1,_target) +return _name +end +function ARTY:AssignAttackGroup(group,prio,radius,nshells,maxengage,time,weapontype,name,unique) +nshells=nshells or 5 +radius=radius or 100 +maxengage=maxengage or 1 +prio=prio or 50 +prio=math.max(1,prio) +prio=math.min(100,prio) +if unique==nil then +unique=false +end +weapontype=weapontype or ARTY.WeaponType.Auto +if type(group)=="string"then +group=GROUP:FindByName(group) +end +if group and group:IsAlive()then +local coord=group:GetCoordinate() +local _name=group:GetName() +local _unique=true +_name,_unique=self:_CheckName(self.targets,_name,not unique) +if unique==true and _unique==false then +self:T(self.lid..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!",self.groupname,_name)) +return nil +end +local _time +if type(time)=="string"then +_time=self:_ClockToSeconds(time) +elseif type(time)=="number"then +_time=timer.getAbsTime()+time +else +_time=timer.getAbsTime() +end +local target={} +target.attackgroup=true +target.name=_name +target.coord=coord +target.radius=radius +target.nshells=nshells +target.engaged=0 +target.underfire=false +target.prio=prio +target.time=_time +target.maxengage=maxengage +target.weapontype=weapontype +table.insert(self.targets,target) +self:__NewTarget(1,target) +return _name +else +self:E("ERROR: Group does not exist!") +end +return nil +end +function ARTY:AssignMoveCoord(coord,time,speed,onroad,cancel,name,unique) +self:F({coord=coord,time=time,speed=speed,onroad=onroad,cancel=cancel,name=name,unique=unique}) +if not self.ismobile then +self:T(self.lid..string.format("%s: group is immobile. Rejecting move request!",self.groupname)) +return nil +end +if unique==nil then +unique=false +end +local _name=name or coord:ToStringLLDMS() +local _unique=true +_name,_unique=self:_CheckName(self.moves,_name,not unique) +if unique==true and _unique==false then +self:T(self.lid..string.format("%s: move %s should have a unique name but name was already given. Rejecting move!",self.groupname,_name)) +return nil +end +if speed then +speed=math.min(speed,self.SpeedMax) +elseif self.Speed then +speed=self.Speed +else +speed=self.SpeedMax*0.7 +end +if onroad==nil then +onroad=false +end +if cancel==nil then +cancel=false +end +local _time +if type(time)=="string"then +_time=self:_ClockToSeconds(time) +elseif type(time)=="number"then +_time=timer.getAbsTime()+time +else +_time=timer.getAbsTime() +end +local _move={name=_name,coord=coord,time=_time,speed=speed,onroad=onroad,cancel=cancel} +table.insert(self.moves,_move) +return _name +end +function ARTY:SetAlias(alias) +self:F({alias=alias}) +self.alias=tostring(alias) +return self +end +function ARTY:AddToCluster(clusters) +self:F({clusters=clusters}) +local names +if type(clusters)=="table"then +names=clusters +elseif type(clusters)=="string"then +names={clusters} +else +self:E(self.lid.."ERROR: Input parameter must be a string or a table in ARTY:AddToCluster()!") +return +end +for _,cluster in pairs(names)do +table.insert(self.clusters,cluster) +end +return self +end +function ARTY:SetMinFiringRange(range) +self:F({range=range}) +self.minrange=range*1000 or 100 +return self +end +function ARTY:SetMaxFiringRange(range) +self:F({range=range}) +self.maxrange=range*1000 or 1000*1000 +return self +end +function ARTY:SetStatusInterval(interval) +self:F({interval=interval}) +self.StatusInterval=interval or 10 +return self +end +function ARTY:SetTrackInterval(interval) +self.dtTrack=interval or 0.2 +return self +end +function ARTY:SetWaitForShotTime(waittime) +self:F({waittime=waittime}) +self.WaitForShotTime=waittime or 300 +return self +end +function ARTY:SetRearmingDistance(distance) +self:F({distance=distance}) +self.RearmingDistance=distance or 100 +return self +end +function ARTY:SetRearmingGroup(group) +self:F({group=group}) +self.RearmingGroup=group +return self +end +function ARTY:SetRearmingGroupSpeed(speed) +self:F({speed=speed}) +self.RearmingGroupSpeed=speed +return self +end +function ARTY:SetRearmingGroupOnRoad(onroad) +self:F({onroad=onroad}) +if onroad==nil then +onroad=true +end +self.RearmingGroupOnRoad=onroad +return self +end +function ARTY:SetRearmingArtyOnRoad(onroad) +self:F({onroad=onroad}) +if onroad==nil then +onroad=true +end +self.RearmingArtyOnRoad=onroad +return self +end +function ARTY:SetRearmingPlace(coord) +self:F({coord=coord}) +self.RearmingPlaceCoord=coord +return self +end +function ARTY:SetAutoRelocateToFiringRange(maxdistance,onroad) +self:F({distance=maxdistance,onroad=onroad}) +self.autorelocate=true +self.autorelocatemaxdist=maxdistance or 50 +self.autorelocatemaxdist=self.autorelocatemaxdist*1000 +if onroad==nil then +onroad=false +end +self.autorelocateonroad=onroad +return self +end +function ARTY:SetAutoRelocateAfterEngagement(rmax,rmin) +self.relocateafterfire=true +self.relocateRmax=rmax or 800 +self.relocateRmin=rmin or 300 +self.relocateRmin=math.min(self.relocateRmin,self.relocateRmax) +return self +end +function ARTY:SetReportON() +self.report=true +return self +end +function ARTY:SetReportOFF() +self.report=false +return self +end +function ARTY:SetRespawnOnDeath(delay) +self.respawnafterdeath=true +self.respawndelay=delay +return self +end +function ARTY:SetDebugON() +self.Debug=true +return self +end +function ARTY:SetDebugOFF() +self.Debug=false +return self +end +function ARTY:SetSpeed(speed) +self.Speed=speed +return self +end +function ARTY:RemoveTarget(name) +self:F2(name) +local id=self:_GetTargetIndexByName(name) +if id then +self:T(self.lid..string.format("Group %s: Removing target %s (id=%d).",self.groupname,name,id)) +table.remove(self.targets,id) +if self.markallow then +local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) +if batteryname==self.groupname and markTargetID~=nil then +COORDINATE:RemoveMark(markTargetID) +end +end +end +self:T(self.lid..string.format("Group %s: Number of targets = %d.",self.groupname,#self.targets)) +end +function ARTY:RemoveMove(name) +self:F2(name) +local id=self:_GetMoveIndexByName(name) +if id then +self:T(self.lid..string.format("Group %s: Removing move %s (id=%d).",self.groupname,name,id)) +table.remove(self.moves,id) +if self.markallow then +local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) +if batteryname==self.groupname and markMoveID~=nil then +COORDINATE:RemoveMark(markMoveID) +end +end +end +self:T(self.lid..string.format("Group %s: Number of moves = %d.",self.groupname,#self.moves)) +end +function ARTY:RemoveAllTargets() +self:F2() +for _,target in pairs(self.targets)do +self:RemoveTarget(target.name) +end +end +function ARTY:SetShellTypes(tableofnames) +self:F2(tableofnames) +self.ammoshells={} +for _,_type in pairs(tableofnames)do +table.insert(self.ammoshells,_type) +end +return self +end +function ARTY:SetRocketTypes(tableofnames) +self:F2(tableofnames) +self.ammorockets={} +for _,_type in pairs(tableofnames)do +table.insert(self.ammorockets,_type) +end +return self +end +function ARTY:SetMissileTypes(tableofnames) +self:F2(tableofnames) +self.ammomissiles={} +for _,_type in pairs(tableofnames)do +table.insert(self.ammomissiles,_type) +end +return self +end +function ARTY:SetTacNukeShells(n) +self.Nukes=n +return self +end +function ARTY:SetTacNukeWarhead(strength) +self.nukewarhead=strength or 0.075 +self.nukewarhead=self.nukewarhead*1000*1000 +return self +end +function ARTY:SetIlluminationShells(n,power) +self.Nillu=n +self.illuPower=power or 1.0 +self.illuPower=self.illuPower*1000000 +return self +end +function ARTY:SetIlluminationMinMaxAlt(minalt,maxalt) +self.illuMinalt=minalt or 500 +self.illuMaxalt=maxalt or 1000 +if self.illuMinalt>self.illuMaxalt then +self.illuMinalt=self.illuMaxalt +end +return self +end +function ARTY:SetSmokeShells(n,color) +self.Nsmoke=n +self.smokeColor=color or SMOKECOLOR.Red +return self +end +function ARTY:SetTacNukeFires(nfires,range) +self.nukefire=true +self.nukefires=nfires +self.nukerange=range +return self +end +function ARTY:SetMarkAssignmentsOn(key,readonly) +self.markkey=key +self.markallow=true +if readonly==nil then +self.markreadonly=false +end +return self +end +function ARTY:SetMarkTargetsOff() +self.markallow=false +self.markkey=nil +return self +end +function ARTY:onafterStart(Controllable,From,Event,To) +self:_EventFromTo("onafterStart",Event,From,To) +local text=string.format("Started ARTY version %s for group %s.",ARTY.version,Controllable:GetName()) +self:I(self.lid..text) +MESSAGE:New(text,5):ToAllIf(self.Debug) +self.Nammo0,self.Nshells0,self.Nrockets0,self.Nmissiles0,self.Narty0=self:GetAmmo(self.Debug) +if self.nukerange==nil then +self.nukerange=1500/75000*self.nukewarhead +end +if self.nukefires==nil then +self.nukefires=20/1000/1000*self.nukerange*self.nukerange +end +if self.Nukes~=nil then +self.Nukes0=math.min(self.Nukes,self.Nshells0) +else +self.Nukes=0 +self.Nukes0=0 +end +if self.Nillu~=nil then +self.Nillu0=math.min(self.Nillu,self.Nshells0) +else +self.Nillu=0 +self.Nillu0=0 +end +if self.Nsmoke~=nil then +self.Nsmoke0=math.min(self.Nsmoke,self.Nshells0) +else +self.Nsmoke=0 +self.Nsmoke0=0 +end +local _dbproperties=self:_CheckDB(self.Type) +self:T({dbproperties=_dbproperties}) +if _dbproperties~=nil then +for property,value in pairs(_dbproperties)do +self:T({property=property,value=value}) +self[property]=value +end +end +if not self.ismobile then +self.RearmingPlaceCoord=nil +self.relocateafterfire=false +self.autorelocate=false +end +self.Speed=math.min(self.Speed,self.SpeedMax) +if self.RearmingGroup then +local speedmax=self.RearmingGroup:GetSpeedMax() +self:T(self.lid..string.format("%s, rearming group %s max speed = %.1f km/h.",self.groupname,self.RearmingGroup:GetName(),speedmax)) +if self.RearmingGroupSpeed==nil then +self.RearmingGroupSpeed=speedmax*0.5 +else +self.RearmingGroupSpeed=math.min(self.RearmingGroupSpeed,self.RearmingGroup:GetSpeedMax()) +end +else +self.RearmingGroupSpeed=23 +end +local text=string.format("\n******************************************************\n") +text=text..string.format("Arty group = %s\n",self.groupname) +text=text..string.format("Arty alias = %s\n",self.alias) +text=text..string.format("Artillery attribute = %s\n",tostring(self.IsArtillery)) +text=text..string.format("Type = %s\n",self.Type) +text=text..string.format("Display Name = %s\n",self.DisplayName) +text=text..string.format("Number of units = %d\n",self.IniGroupStrength) +text=text..string.format("Speed max = %.1f km/h\n",self.SpeedMax) +text=text..string.format("Speed default = %.1f km/h\n",self.Speed) +text=text..string.format("Is mobile = %s\n",tostring(self.ismobile)) +text=text..string.format("Is cargo = %s\n",tostring(self.iscargo)) +text=text..string.format("Min range = %.1f km\n",self.minrange/1000) +text=text..string.format("Max range = %.1f km\n",self.maxrange/1000) +text=text..string.format("Total ammo count = %d\n",self.Nammo0) +text=text..string.format("Number of shells = %d\n",self.Nshells0) +text=text..string.format("Number of rockets = %d\n",self.Nrockets0) +text=text..string.format("Number of missiles = %d\n",self.Nmissiles0) +text=text..string.format("Number of nukes = %d\n",self.Nukes0) +text=text..string.format("Nuclear warhead = %d tons TNT\n",self.nukewarhead/1000) +text=text..string.format("Nuclear demolition = %d m\n",self.nukerange) +text=text..string.format("Nuclear fires = %d (active=%s)\n",self.nukefires,tostring(self.nukefire)) +text=text..string.format("Number of illum. = %d\n",self.Nillu0) +text=text..string.format("Illuminaton Power = %.3f mcd\n",self.illuPower/1000000) +text=text..string.format("Illuminaton Minalt = %d m\n",self.illuMinalt) +text=text..string.format("Illuminaton Maxalt = %d m\n",self.illuMaxalt) +text=text..string.format("Number of smoke = %d\n",self.Nsmoke0) +text=text..string.format("Smoke color = %d\n",self.smokeColor) +if self.RearmingGroup or self.RearmingPlaceCoord then +text=text..string.format("Rearming safe dist. = %d m\n",self.RearmingDistance) +end +if self.RearmingGroup then +text=text..string.format("Rearming group = %s\n",self.RearmingGroup:GetName()) +text=text..string.format("Rearming group speed= %d km/h\n",self.RearmingGroupSpeed) +text=text..string.format("Rearming group roads= %s\n",tostring(self.RearmingGroupOnRoad)) +end +if self.RearmingPlaceCoord then +local dist=self.InitialCoord:Get2DDistance(self.RearmingPlaceCoord) +text=text..string.format("Rearming coord dist = %d m\n",dist) +text=text..string.format("Rearming ARTY roads = %s\n",tostring(self.RearmingArtyOnRoad)) +end +text=text..string.format("Relocate after fire = %s\n",tostring(self.relocateafterfire)) +text=text..string.format("Relocate min dist. = %d m\n",self.relocateRmin) +text=text..string.format("Relocate max dist. = %d m\n",self.relocateRmax) +text=text..string.format("Auto move in range = %s\n",tostring(self.autorelocate)) +text=text..string.format("Auto move dist. max = %.1f km\n",self.autorelocatemaxdist/1000) +text=text..string.format("Auto move on road = %s\n",tostring(self.autorelocateonroad)) +text=text..string.format("Marker assignments = %s\n",tostring(self.markallow)) +text=text..string.format("Marker auth. key = %s\n",tostring(self.markkey)) +text=text..string.format("Marker readonly = %s\n",tostring(self.markreadonly)) +text=text..string.format("Clusters:\n") +for _,cluster in pairs(self.clusters)do +text=text..string.format("- %s\n",tostring(cluster)) +end +text=text..string.format("******************************************************\n") +text=text..string.format("Targets:\n") +for _,target in pairs(self.targets)do +text=text..string.format("- %s\n",self:_TargetInfo(target)) +local possible=self:_CheckWeaponTypePossible(target) +if not possible then +self:E(self.lid..string.format("WARNING: Selected weapon type %s is not possible",self:_WeaponTypeName(target.weapontype))) +end +if self.Debug then +local zone=ZONE_RADIUS:New(target.name,target.coord:GetVec2(),target.radius) +zone:BoundZone(180,coalition.side.NEUTRAL) +end +end +text=text..string.format("Moves:\n") +for i=1,#self.moves do +text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i])) +end +text=text..string.format("******************************************************\n") +text=text..string.format("Shell types:\n") +for _,_type in pairs(self.ammoshells)do +text=text..string.format("- %s\n",_type) +end +text=text..string.format("Rocket types:\n") +for _,_type in pairs(self.ammorockets)do +text=text..string.format("- %s\n",_type) +end +text=text..string.format("Missile types:\n") +for _,_type in pairs(self.ammomissiles)do +text=text..string.format("- %s\n",_type) +end +text=text..string.format("******************************************************") +if self.Debug then +self:I(self.lid..text) +else +self:T(self.lid..text) +end +self.Controllable:OptionROEHoldFire() +self:HandleEvent(EVENTS.Shot) +self:HandleEvent(EVENTS.Dead) +if self.markallow then +world.addEventHandler(self) +end +self:__Status(self.StatusInterval) +end +function ARTY:_CheckDB(displayname) +for _type,_properties in pairs(ARTY.db)do +self:T({type=_type,properties=_properties}) +if _type==displayname then +self:T({type=_type,properties=_properties}) +return _properties +end +end +return nil +end +function ARTY:_StatusReport(display) +if display==nil then +display=false +end +local Nammo,Nshells,Nrockets,Nmissiles,Narty=self:GetAmmo() +local Nnukes=self.Nukes +local Nillu=self.Nillu +local Nsmoke=self.Nsmoke +local Tnow=timer.getTime() +local Clock=self:_SecondsToClock(timer.getAbsTime()) +local text=string.format("\n******************* STATUS ***************************\n") +text=text..string.format("ARTY group = %s\n",self.groupname) +text=text..string.format("Clock = %s\n",Clock) +text=text..string.format("FSM state = %s\n",self:GetState()) +text=text..string.format("Total ammo count = %d\n",Nammo) +text=text..string.format("Number of shells = %d\n",Narty) +text=text..string.format("Number of rockets = %d\n",Nrockets) +text=text..string.format("Number of missiles = %d\n",Nmissiles) +text=text..string.format("Number of nukes = %d\n",Nnukes) +text=text..string.format("Number of illum. = %d\n",Nillu) +text=text..string.format("Number of smoke = %d\n",Nsmoke) +if self.currentTarget then +text=text..string.format("Current Target = %s\n",tostring(self.currentTarget.name)) +text=text..string.format("Curr. Tgt assigned = %d\n",Tnow-self.currentTarget.Tassigned) +else +text=text..string.format("Current Target = %s\n","none") +end +text=text..string.format("Nshots curr. Target = %d\n",self.Nshots) +text=text..string.format("Targets:\n") +for i=1,#self.targets do +text=text..string.format("- %s\n",self:_TargetInfo(self.targets[i])) +end +if self.currentMove then +text=text..string.format("Current Move = %s\n",tostring(self.currentMove.name)) +else +text=text..string.format("Current Move = %s\n","none") +end +text=text..string.format("Moves:\n") +for i=1,#self.moves do +text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i])) +end +text=text..string.format("******************************************************") +env.info(self.lid..text) +MESSAGE:New(text,20):Clear():ToCoalitionIf(self.coalition,display) +end +function ARTY._FuncTrack(weapon,self,target) +local _coord=weapon.coordinate +local _dist=_coord:Get2DDistance(target.coord) +local _destroyweapon=false +self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m",self.groupname,_dist)) +if target.weapontype==ARTY.WeaponType.IlluminationShells then +if _dist0 +local _trackillu=self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu>0 +local _tracksmoke=self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0 +if _tracknuke or _trackillu or _tracksmoke then +self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.",self.groupname)) +local weapon=WEAPON:New(EventData.weapon) +weapon:SetTimeStepTrack(self.dtTrack) +local target=UTILS.DeepCopy(self.currentTarget) +weapon:SetFuncTrack(ARTY._FuncTrack,self,target) +weapon:SetFuncImpact(ARTY._FuncImpact,self,target) +weapon:StartTrack(2) +end +local _nammo,_nshells,_nrockets,_nmissiles,_narty=self:GetAmmo() +if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes then +self.Nukes=self.Nukes-1 +end +if self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells then +self.Nillu=self.Nillu-1 +end +if self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells then +self.Nsmoke=self.Nsmoke-1 +end +local _outofammo=false +if _nammo==0 then +self:T(self.lid..string.format("Group %s completely out of ammo.",self.groupname)) +_outofammo=true +end +local _partlyoutofammo=self:_CheckOutOfAmmo({self.currentTarget}) +local _weapontype=self:_WeaponTypeName(self.currentTarget.weapontype) +self:T(self.lid..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d",self.groupname,_nammo,_narty,_nrockets,_nmissiles)) +self:T(self.lid..string.format("Group %s uses weapontype %s for current target.",self.groupname,_weapontype)) +local _ceasefire=false +local _relocate=false +if self.Nshots>=self.currentTarget.nshells then +local text=string.format("Group %s stop firing on target %s.",self.groupname,self.currentTarget.name) +self:T(self.lid..text) +MESSAGE:New(text,5):ToAllIf(self.Debug) +_ceasefire=true +_relocate=self.relocateafterfire +end +if _outofammo or _partlyoutofammo then +_ceasefire=true +end +if _relocate then +self:_Relocate() +end +if _ceasefire then +self:CeaseFire(self.currentTarget) +end +else +self:E(self.lid..string.format("WARNING: No current target for group %s?!",self.groupname)) +end +end +end +end +function ARTY:onEvent(Event) +if Event==nil or Event.idx==nil then +self:T3("Skipping onEvent. Event or Event.idx unknown.") +return true +end +self:T2(string.format("Event captured = %s",tostring(self.groupname))) +self:T2(string.format("Event id = %s",tostring(Event.id))) +self:T2(string.format("Event time = %s",tostring(Event.time))) +self:T2(string.format("Event idx = %s",tostring(Event.idx))) +self:T2(string.format("Event coalition = %s",tostring(Event.coalition))) +self:T2(string.format("Event group id = %s",tostring(Event.groupID))) +self:T2(string.format("Event text = %s",tostring(Event.text))) +if Event.initiator~=nil then +local _unitname=Event.initiator:getName() +self:T2(string.format("Event ini unit name = %s",tostring(_unitname))) +end +if Event.id==world.event.S_EVENT_MARK_ADDED then +self:T2({event="S_EVENT_MARK_ADDED",battery=self.groupname,vec3=Event.pos}) +elseif Event.id==world.event.S_EVENT_MARK_CHANGE then +self:T({event="S_EVENT_MARK_CHANGE",battery=self.groupname,vec3=Event.pos}) +self:_OnEventMarkChange(Event) +elseif Event.id==world.event.S_EVENT_MARK_REMOVED then +self:T2({event="S_EVENT_MARK_REMOVED",battery=self.groupname,vec3=Event.pos}) +self:_OnEventMarkRemove(Event) +end +end +function ARTY:_OnEventMarkRemove(Event) +local batterycoalition=self.coalition +if Event.text~=nil and Event.text:find("BATTERY")then +local _cancelmove=false +local _canceltarget=false +local _name="" +local _id=nil +if Event.text:find("Marked Relocation")then +_cancelmove=true +_name=self:_MarkMoveName(Event.idx) +_id=self:_GetMoveIndexByName(_name) +elseif Event.text:find("Marked Target")then +_canceltarget=true +_name=self:_MarkTargetName(Event.idx) +_id=self:_GetTargetIndexByName(_name) +else +return +end +if _id==nil then +return +end +if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then +local _validkey=self:_MarkerKeyAuthentification(Event.text) +if _validkey then +if _cancelmove then +if self.currentMove and self.currentMove.name==_name then +self.Controllable:ClearTasks() +self:Arrived() +else +self:RemoveMove(_name) +end +elseif _canceltarget then +if self.currentTarget and self.currentTarget.name==_name then +self:CeaseFire(self.currentTarget) +self:RemoveTarget(_name) +else +self:RemoveTarget(_name) +end +end +end +end +end +end +function ARTY:_OnEventMarkChange(Event) +if Event.text~=nil and Event.text:lower():find("arty")then +local vec3={y=Event.pos.y,x=Event.pos.x,z=Event.pos.z} +local _coord=COORDINATE:NewFromVec3(vec3) +_coord.y=_coord:GetLandHeight() +local batterycoalition=self.coalition +local batteryname=self.groupname +if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then +local _assign=self:_Markertext(Event.text) +if _assign==nil or not(_assign.engage or _assign.move or _assign.request or _assign.cancel or _assign.set)then +self:T(self.lid..string.format("WARNING: %s, no keyword ENGAGE, MOVE, REQUEST, CANCEL or SET in mark text! Command will not be executed. Text:\n%s",self.groupname,Event.text)) +return +end +local _assigned=false +if _assign.everyone then +_assigned=true +else +for _,bat in pairs(_assign.battery)do +if self.groupname==bat then +_assigned=true +end +end +for _,alias in pairs(_assign.aliases)do +if self.alias==alias then +_assigned=true +end +end +for _,bat in pairs(_assign.cluster)do +for _,cluster in pairs(self.clusters)do +if cluster==bat then +_assigned=true +end +end +end +end +if not _assigned then +self:T3(self.lid..string.format("INFO: ARTY group %s was not addressed! Mark text:\n%s",self.groupname,Event.text)) +return +else +if self.Controllable and self.Controllable:IsAlive()then +else +self:T3(self.lid..string.format("INFO: ARTY group %s was addressed but is NOT alive! Mark text:\n%s",self.groupname,Event.text)) +return +end +end +if _assign.coord then +_coord=_assign.coord +end +local _validkey=self:_MarkerKeyAuthentification(Event.text) +if _assign.request and _validkey then +if _assign.requestammo then +self:_MarkRequestAmmo() +end +if _assign.requestmoves then +self:_MarkRequestMoves() +end +if _assign.requesttargets then +self:_MarkRequestTargets() +end +if _assign.requeststatus then +self:_MarkRequestStatus() +end +if _assign.requestrearming then +self:Rearm() +end +return +end +if _assign.cancel and _validkey then +if _assign.cancelmove and self.currentMove then +self.Controllable:ClearTasks() +self:Arrived() +elseif _assign.canceltarget and self.currentTarget then +self.currentTarget.engaged=self.currentTarget.engaged+1 +self:CeaseFire(self.currentTarget) +elseif _assign.cancelrearm and self:is("Rearming")then +local nammo=self:GetAmmo() +if nammo>0 then +self:Rearmed() +else +self:Winchester() +end +end +return +end +if _assign.set and _validkey then +if _assign.setrearmingplace and self.ismobile then +self:SetRearmingPlace(_coord) +_coord:RemoveMark(Event.idx) +_coord:MarkToCoalition(string.format("Rearming place for battery %s",self.groupname),self.coalition,false,string.format("New rearming place for battery %s defined.",self.groupname)) +if self.Debug then +_coord:SmokeOrange() +end +end +if _assign.setrearminggroup then +_coord:RemoveMark(Event.idx) +local rearminggroupcoord=_assign.setrearminggroup:GetCoordinate() +rearminggroupcoord:MarkToCoalition(string.format("Rearming group for battery %s",self.groupname),self.coalition,false,string.format("New rearming group for battery %s defined.",self.groupname)) +self:SetRearmingGroup(_assign.setrearminggroup) +if self.Debug then +rearminggroupcoord:SmokeOrange() +end +end +return +end +if _validkey then +_coord:RemoveMark(Event.idx) +local _id=UTILS._MarkID+1 +if _assign.move then +local _name=self:_MarkMoveName(_id) +local text=string.format("%s, received new relocation assignment.",self.alias) +text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) +MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) +local _movename=self:AssignMoveCoord(_coord,_assign.time,_assign.speed,_assign.onroad,_assign.movecanceltarget,_name,true) +if _movename~=nil then +local _mid=self:_GetMoveIndexByName(_movename) +local _move=self.moves[_mid] +local clock=tostring(self:_SecondsToClock(_move.time)) +local _markertext=_movename..string.format(", Time=%s, Speed=%d km/h, Use Roads=%s.",clock,_move.speed,tostring(_move.onroad)) +local _randomcoord=_coord:GetRandomCoordinateInRadius(100) +_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly) +else +local text=string.format("%s, relocation not possible.",self.alias) +MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) +end +else +local _name=self:_MarkTargetName(_id) +local text=string.format("%s, received new target assignment.",self.alias) +text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) +if _assign.time then +text=text..string.format("\nTime %s",_assign.time) +end +if _assign.prio then +text=text..string.format("\nPrio %d",_assign.prio) +end +if _assign.radius then +text=text..string.format("\nRadius %d m",_assign.radius) +end +if _assign.nshells then +text=text..string.format("\nShots %d",_assign.nshells) +end +if _assign.maxengage then +text=text..string.format("\nEngagements %d",_assign.maxengage) +end +if _assign.weapontype then +text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) +end +MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) +local _targetname=self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype,_name,true) +if _targetname~=nil then +local _tid=self:_GetTargetIndexByName(_targetname) +local _target=self.targets[_tid] +local clock=tostring(self:_SecondsToClock(_target.time)) +local weapon=self:_WeaponTypeName(_target.weapontype) +local _markertext=_targetname..string.format(", Priority=%d, Radius=%d m, Shots=%d, Engagements=%d, Weapon=%s, Time=%s",_target.prio,_target.radius,_target.nshells,_target.maxengage,weapon,clock) +local _randomcoord=_coord:GetRandomCoordinateInRadius(250) +_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly) +end +end +end +end +end +end +function ARTY:OnEventDead(EventData) +self:F(EventData) +local _name=self.groupname +if EventData and EventData.IniGroupName and EventData.IniGroupName==_name then +local unitname=tostring(EventData.IniUnitName) +self:T(self.lid..string.format("%s: Captured dead event for unit %s.",_name,unitname)) +self:Dead(unitname) +end +end +function ARTY:onafterStatus(Controllable,From,Event,To) +self:_EventFromTo("onafterStatus",Event,From,To) +local nammo,nshells,nrockets,nmissiles,narty=self:GetAmmo() +if self.iscargo and self.cargogroup then +if self.cargogroup:IsLoaded()and not self:is("InTransit")then +self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.",self.alias)) +self:Loaded() +elseif self.cargogroup:IsUnLoaded()then +self:T(self.lid..string.format("Group %s has been unloaded from the carrier.",self.alias)) +self:UnLoaded() +end +end +local fsmstate=self:GetState() +self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d",fsmstate,nammo,narty,self.Nsmoke,self.Nillu,self.Nukes,self.nukewarhead/1000000,nrockets,nmissiles)) +if self.Controllable and self.Controllable:IsAlive()then +if self.Debug then +self:_StatusReport() +end +if self:is("Moving")then +self:T2(self.lid..string.format("%s: Moving",Controllable:GetName())) +end +if self:is("Rearming")then +local _rearmed=self:_CheckRearmed() +if _rearmed then +self:T2(self.lid..string.format("%s: Rearming ==> Rearmed",Controllable:GetName())) +self:Rearmed() +end +end +if self:is("Rearmed")then +local distance=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) +self:T2(self.lid..string.format("%s: Rearmed. Distance ARTY to InitalCoord = %d m",Controllable:GetName(),distance)) +if distance<=self.RearmingDistance then +self:T2(self.lid..string.format("%s: Rearmed ==> CombatReady",Controllable:GetName())) +self:CombatReady() +end +end +if self:is("Arrived")then +self:T2(self.lid..string.format("%s: Arrived ==> CombatReady",Controllable:GetName())) +self:CombatReady() +end +if self:is("Firing")then +self:_CheckShootingStarted() +end +self:_CheckTargetsInRange() +local notpossible={} +for i=1,#self.targets do +local _target=self.targets[i] +local possible=self:_CheckWeaponTypePossible(_target) +if not possible then +table.insert(notpossible,_target.name) +end +end +for _,targetname in pairs(notpossible)do +self:E(self.lid..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.",self.groupname,targetname)) +self:RemoveTarget(targetname) +end +local _timedTarget=self:_CheckTimedTargets() +local _normalTarget=self:_CheckNormalTargets() +local _move=self:_CheckMoves() +if _move then +self:Move(_move) +elseif _timedTarget then +if self.currentTarget then +self:CeaseFire(self.currentTarget) +end +if self:is("CombatReady")then +self:OpenFire(_timedTarget) +end +elseif _normalTarget then +if self:is("CombatReady")then +self:OpenFire(_normalTarget) +end +end +local gotsome=false +if#self.targets>0 then +for i=1,#self.targets do +local _target=self.targets[i] +if self:_CheckWeaponTypeAvailable(_target)>0 then +gotsome=true +end +end +else +gotsome=true +end +if(nammo==0 or not gotsome)and not(self:is("Moving")or self:is("Rearming")or self:is("OutOfAmmo"))then +self:Winchester() +end +if self:is("OutOfAmmo")then +self:T2(self.lid..string.format("%s: OutOfAmmo ==> Rearm ==> Rearming",Controllable:GetName())) +self:Rearm() +end +self:__Status(self.StatusInterval) +elseif self.iscargo then +if self.cargogroup and self.cargogroup:IsAlive()then +if self:is("InTransit")then +self:__Status(-5) +end +end +else +self:E(self.lid..string.format("Arty group %s is not alive!",self.groupname)) +end +end +function ARTY:onbeforeLoaded(Controllable,From,Event,To) +if self.currentTarget then +self:CeaseFire(self.currentTarget) +end +return true +end +function ARTY:onafterUnLoaded(Controllable,From,Event,To) +self:CombatReady() +end +function ARTY:onenterCombatReady(Controllable,From,Event,To) +self:_EventFromTo("onenterCombatReady",Event,From,To) +self:T3(self.lid..string.format("onenterComabReady, from=%s, event=%s, to=%s",From,Event,To)) +end +function ARTY:onbeforeOpenFire(Controllable,From,Event,To,target) +self:_EventFromTo("onbeforeOpenFire",Event,From,To) +if self.currentTarget then +self:E(self.lid..string.format("ERROR: Group %s already has a target %s!",self.groupname,self.currentTarget.name)) +return false +end +if not self:_TargetInRange(target)then +self:E(self.lid..string.format("ERROR: Group %s, target %s is out of range!",self.groupname,self.currentTarget.name)) +return false +end +local nfire=self:_CheckWeaponTypeAvailable(target) +target.nshells=math.min(target.nshells,nfire) +if target.nshells<1 then +local text=string.format("%s, no ammo left to engage target %s with selected weapon type %s.") +return false +end +return true +end +function ARTY:onafterOpenFire(Controllable,From,Event,To,target) +self:_EventFromTo("onafterOpenFire",Event,From,To) +local id=self:_GetTargetIndexByName(target.name) +if id then +self.targets[id].underfire=true +self.currentTarget=target +self.currentTarget.Tassigned=timer.getTime() +end +local range=Controllable:GetCoordinate():Get2DDistance(target.coord) +local Nammo,Nshells,Nrockets,Nmissiles,Narty=self:GetAmmo() +local nfire=Narty +local _type="shots" +if target.weapontype==ARTY.WeaponType.Auto then +nfire=Nammo +_type="shots" +elseif target.weapontype==ARTY.WeaponType.Cannon then +nfire=Narty +_type="shells" +elseif target.weapontype==ARTY.WeaponType.TacticalNukes then +nfire=self.Nukes +_type="nuclear shells" +elseif target.weapontype==ARTY.WeaponType.IlluminationShells then +nfire=self.Nillu +_type="illumination shells" +elseif target.weapontype==ARTY.WeaponType.SmokeShells then +nfire=self.Nsmoke +_type="smoke shells" +elseif target.weapontype==ARTY.WeaponType.Rockets then +nfire=Nrockets +_type="rockets" +elseif target.weapontype==ARTY.WeaponType.CruiseMissile then +nfire=Nmissiles +_type="cruise missiles" +end +target.nshells=math.min(target.nshells,nfire) +local text=string.format("%s, opening fire on target %s with %d %s. Distance %.1f km.",Controllable:GetName(),target.name,target.nshells,_type,range/1000) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report) +if target.attackgroup then +self:_AttackGroup(target) +else +self:_FireAtCoord(target.coord,target.radius,target.nshells,target.weapontype) +end +end +function ARTY:onafterCeaseFire(Controllable,From,Event,To,target) +self:_EventFromTo("onafterCeaseFire",Event,From,To) +if target then +local text=string.format("%s, ceasing fire on target %s.",Controllable:GetName(),target.name) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report) +local id=self:_GetTargetIndexByName(target.name) +if id then +if self.Nshots>0 then +self.targets[id].engaged=self.targets[id].engaged+1 +self.targets[id].time=nil +end +self.targets[id].underfire=false +end +if target.engaged>=target.maxengage then +self:RemoveTarget(target.name) +end +self.Controllable:OptionROEHoldFire() +self.Controllable:ClearTasks() +else +self:E(self.lid..string.format("ERROR: No target in cease fire for group %s.",self.groupname)) +end +self.Nshots=0 +self.currentTarget=nil +end +function ARTY:onafterWinchester(Controllable,From,Event,To) +self:_EventFromTo("onafterWinchester",Event,From,To) +local text=string.format("%s, winchester!",Controllable:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +end +function ARTY:onbeforeRearm(Controllable,From,Event,To) +self:_EventFromTo("onbeforeRearm",Event,From,To) +local _rearmed=self:_CheckRearmed() +if _rearmed then +self:T(self.lid..string.format("%s, group is already armed to the teeth. Rearming request denied!",self.groupname)) +return false +else +self:T(self.lid..string.format("%s, group might be rearmed.",self.groupname)) +end +if self.RearmingGroup and self.RearmingGroup:IsAlive()then +return true +elseif self.RearmingPlaceCoord then +return true +else +return false +end +end +function ARTY:onafterRearm(Controllable,From,Event,To) +self:_EventFromTo("onafterRearm",Event,From,To) +local coordARTY=self.Controllable:GetCoordinate() +self.InitialCoord=coordARTY +local coordRARM=nil +if self.RearmingGroup then +coordRARM=self.RearmingGroup:GetCoordinate() +self.RearmingGroupCoord=coordRARM +end +if self.RearmingGroup and self.RearmingPlaceCoord and self.ismobile then +local text=string.format("%s, %s, request rearming at rearming place.",Controllable:GetName(),self.RearmingGroup:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) +local dR=coordRARM:Get2DDistance(self.RearmingPlaceCoord) +if dA>self.RearmingDistance then +local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2) +self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true) +end +if dR>self.RearmingDistance then +local ToCoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2) +self:_Move(self.RearmingGroup,ToCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad) +end +elseif self.RearmingGroup then +local text=string.format("%s, %s, request rearming.",Controllable:GetName(),self.RearmingGroup:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +local distance=coordARTY:Get2DDistance(coordRARM) +if distance>self.RearmingDistance then +self:_Move(self.RearmingGroup,self:_VicinityCoord(coordARTY),self.RearmingGroupSpeed,self.RearmingGroupOnRoad) +end +elseif self.RearmingPlaceCoord then +local text=string.format("%s, moving to rearming place.",Controllable:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) +if dA>self.RearmingDistance then +local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord) +self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true) +end +end +end +function ARTY:onafterRearmed(Controllable,From,Event,To) +self:_EventFromTo("onafterRearmed",Event,From,To) +local text=string.format("%s, rearming complete.",Controllable:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +self.Nukes=self.Nukes0 +self.Nillu=self.Nillu0 +self.Nsmoke=self.Nsmoke0 +local dist=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) +if dist>self.RearmingDistance then +self:AssignMoveCoord(self.InitialCoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE REARMING COMPLETE",true) +end +if self.RearmingGroup and self.RearmingGroup:IsAlive()then +local d=self.RearmingGroup:GetCoordinate():Get2DDistance(self.RearmingGroupCoord) +if d>self.RearmingDistance then +self:_Move(self.RearmingGroup,self.RearmingGroupCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad) +else +self.RearmingGroup:ClearTasks() +end +end +end +function ARTY:_CheckRearmed() +self:F2() +local nammo,nshells,nrockets,nmissiles,narty=self:GetAmmo() +local units=self.Controllable:GetUnits() +local nunits=0 +if units then +nunits=#units +end +local FullAmmo=self.Nammo0*nunits/self.IniGroupStrength +local _rearmpc=nammo/FullAmmo*100 +if _rearmpc>1 then +local text=string.format("%s, rearming %d %% complete.",self.alias,_rearmpc) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +end +if nammo>=FullAmmo then +return true +else +return false +end +end +function ARTY:onbeforeMove(Controllable,From,Event,To,move) +self:_EventFromTo("onbeforeMove",Event,From,To) +if not self.ismobile then +return false +end +if self.currentTarget then +if move.cancel then +self:CeaseFire(self.currentTarget) +else +return false +end +end +return true +end +function ARTY:onafterMove(Controllable,From,Event,To,move) +self:_EventFromTo("onafterMove",Event,From,To) +self.Controllable:OptionAlarmStateGreen() +self.Controllable:OptionROEHoldFire() +local _Speed=math.min(move.speed,self.SpeedMax) +if self.Debug then +move.coord:SmokeRed() +end +self.currentMove=move +self:_Move(self.Controllable,move.coord,move.speed,move.onroad) +end +function ARTY:onafterArrived(Controllable,From,Event,To) +self:_EventFromTo("onafterArrived",Event,From,To) +self.Controllable:OptionAlarmStateAuto() +local text=string.format("%s, arrived at destination.",Controllable:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +if self.currentMove then +self:RemoveMove(self.currentMove.name) +self.currentMove=nil +end +end +function ARTY:onafterNewTarget(Controllable,From,Event,To,target) +self:_EventFromTo("onafterNewTarget",Event,From,To) +local text=string.format("Adding new target %s.",target.name) +MESSAGE:New(text,5):ToAllIf(self.Debug) +self:T(self.lid..text) +end +function ARTY:onafterNewMove(Controllable,From,Event,To,move) +self:_EventFromTo("onafterNewTarget",Event,From,To) +local text=string.format("Adding new move %s.",move.name) +MESSAGE:New(text,5):ToAllIf(self.Debug) +self:T(self.lid..text) +end +function ARTY:onafterDead(Controllable,From,Event,To,Unitname) +self:_EventFromTo("onafterDead",Event,From,To) +local nunits=self.Controllable:CountAliveUnits() +local text=string.format("%s, our unit %s just died! %d units left.",self.groupname,Unitname,nunits) +MESSAGE:New(text,5):ToAllIf(self.Debug) +self:I(self.lid..text) +if nunits==0 then +if self.currentTarget then +self:CeaseFire(self.currentTarget) +end +if self.respawnafterdeath then +if not self.respawning then +self.respawning=true +self:__Respawn(self.respawndelay or 1) +end +else +self:Stop() +end +end +end +function ARTY:onafterRespawn(Controllable,From,Event,To) +self:_EventFromTo("onafterRespawn",Event,From,To) +self:I("Respawning arty group") +local group=self.Controllable +self.Controllable=group:Respawn() +self.respawning=false +self:__Status(-1) +end +function ARTY:onafterStop(Controllable,From,Event,To) +self:_EventFromTo("onafterStop",Event,From,To) +self:I(self.lid..string.format("Stopping ARTY FSM for group %s.",tostring(Controllable:GetName()))) +if self.currentTarget then +self:CeaseFire(self.currentTarget) +end +self:UnHandleEvent(EVENTS.Shot) +self:UnHandleEvent(EVENTS.Dead) +end +function ARTY:_FireAtCoord(coord,radius,nshells,weapontype) +self:F({coord=coord,radius=radius,nshells=nshells}) +local group=self.Controllable +if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then +weapontype=ARTY.WeaponType.Cannon +end +if group:HasTask()then +group:ClearTasks() +end +group:OptionROEOpenFire() +local vec2=coord:GetVec2() +local fire=group:TaskFireAtPoint(vec2,radius,nshells,weapontype) +group:SetTask(fire,1) +end +function ARTY:_AttackGroup(target) +local group=self.Controllable +local weapontype=target.weapontype +if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then +weapontype=ARTY.WeaponType.Cannon +end +if group:HasTask()then +group:ClearTasks() +end +group:OptionROEOpenFire() +local targetgroup=GROUP:FindByName(target.name) +local fire=group:TaskAttackGroup(targetgroup,weapontype,AI.Task.WeaponExpend.ONE,1) +group:SetTask(fire,1) +end +function ARTY:_NuclearBlast(_coord) +local S0=self.nukewarhead +local R0=self.nukerange +local N0=self.nukefires +_coord:Explosion(S0) +_coord:BigSmokeAndFireHuge() +local _fires={} +for i=1,N0 do +local _fire=_coord:GetRandomCoordinateInRadius(R0) +local _dist=_fire:Get2DDistance(_coord) +table.insert(_fires,{distance=_dist,coord=_fire}) +end +local _sort=function(a,b)return a.distance_nmax +if _gotit then +self:AssignMoveCoord(_new,nil,nil,false,false,"RELOCATION MOVE AFTER FIRING") +end +end +function ARTY:GetAmmo(display) +self:F3({display=display}) +if display==nil then +display=false +end +local nammo=0 +local nshells=0 +local nrockets=0 +local nmissiles=0 +local nartyshells=0 +local units=self.Controllable:GetUnits() +if units==nil then +return nammo,nshells,nrockets,nmissiles +end +for _,_unit in pairs(units)do +local unit=_unit +if unit then +local text=string.format("ARTY group %s - unit %s:\n",self.groupname,unit:GetName()) +local ammotable=unit:GetAmmo() +if ammotable~=nil then +local weapons=#ammotable +if display then +self:I(self.lid..string.format("Number of weapons %d.",weapons)) +self:I({ammotable=ammotable}) +self:I(self.lid.."Ammotable:") +for id,bla in pairs(ammotable)do +self:I({id=id,ammo=bla}) +end +end +for w=1,weapons do +local Nammo=ammotable[w]["count"] +local Tammo=ammotable[w]["desc"]["typeName"] +local _weaponString=self:_split(Tammo,"%.") +local _weaponName=_weaponString[#_weaponString] +local Category=ammotable[w].desc.category +local MissileCategory=nil +if Category==Weapon.Category.MISSILE then +MissileCategory=ammotable[w].desc.missileCategory +end +local _gotshell=false +if#self.ammoshells>0 then +for _,_type in pairs(self.ammoshells)do +if string.match(Tammo,_type)and Category==Weapon.Category.SHELL then +_gotshell=true +end +end +else +if Category==Weapon.Category.SHELL then +_gotshell=true +end +end +local _gotrocket=false +if#self.ammorockets>0 then +for _,_type in pairs(self.ammorockets)do +if string.match(Tammo,_type)and Category==Weapon.Category.ROCKET then +_gotrocket=true +end +end +else +if Category==Weapon.Category.ROCKET then +_gotrocket=true +end +end +local _gotmissile=false +if#self.ammomissiles>0 then +for _,_type in pairs(self.ammomissiles)do +if string.match(Tammo,_type)and Category==Weapon.Category.MISSILE then +_gotmissile=true +end +end +else +if Category==Weapon.Category.MISSILE then +_gotmissile=true +end +end +if _gotshell then +nshells=nshells+Nammo +local _,_,_,_,_,shells=unit:GetAmmunition() +nartyshells=nartyshells+shells +text=text..string.format("- %d shells of type %s\n",Nammo,_weaponName) +elseif _gotrocket then +nrockets=nrockets+Nammo +text=text..string.format("- %d rockets of type %s\n",Nammo,_weaponName) +elseif _gotmissile then +if MissileCategory==Weapon.MissileCategory.CRUISE then +nmissiles=nmissiles+Nammo +end +text=text..string.format("- %d %s missiles of type %s\n",Nammo,self:_MissileCategoryName(MissileCategory),_weaponName) +else +text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,Tammo,Category,tostring(MissileCategory)) +end +end +end +if display then +self:I(self.lid..text) +else +self:T3(self.lid..text) +end +MESSAGE:New(text,10):ToAllIf(display) +end +end +nammo=nshells+nrockets+nmissiles +return nammo,nshells,nrockets,nmissiles,nartyshells +end +function ARTY:_MissileCategoryName(categorynumber) +local cat="unknown" +if categorynumber==Weapon.MissileCategory.AAM then +cat="air-to-air" +elseif categorynumber==Weapon.MissileCategory.SAM then +cat="surface-to-air" +elseif categorynumber==Weapon.MissileCategory.BM then +cat="ballistic" +elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then +cat="anti-ship" +elseif categorynumber==Weapon.MissileCategory.CRUISE then +cat="cruise" +elseif categorynumber==Weapon.MissileCategory.OTHER then +cat="other" +end +return cat +end +function ARTY:_MarkerKeyAuthentification(text) +local batterycoalition=self.coalition +local mykey=nil +if self.markkey~=nil then +local keywords=self:_split(text,",") +for _,key in pairs(keywords)do +local s=self:_split(key," ") +local val=s[2] +if key:lower():find("key")then +mykey=tonumber(val) +self:T(self.lid..string.format("Authorisation Key=%s.",val)) +end +end +end +local _validkey=true +if self.markkey~=nil then +_validkey=false +if mykey~=nil then +_validkey=self.markkey==mykey +end +self:T2(self.lid..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s",self.groupname,tostring(self.markkey),tostring(mykey),tostring(_validkey))) +local text="" +if mykey==nil then +text=string.format("%s, authorization required but did not receive a key!",self.alias) +elseif _validkey==false then +text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!",self.alias,tostring(mykey)) +elseif _validkey==true then +text=string.format("%s, authentification successful!",self.alias) +end +MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) +end +return _validkey +end +function ARTY:_Markertext(text) +self:F(text) +local assignment={} +assignment.battery={} +assignment.aliases={} +assignment.cluster={} +assignment.everyone=false +assignment.move=false +assignment.engage=false +assignment.request=false +assignment.cancel=false +assignment.set=false +assignment.readonly=false +assignment.movecanceltarget=false +assignment.cancelmove=false +assignment.canceltarget=false +assignment.cancelrearm=false +assignment.setrearmingplace=false +assignment.setrearminggroup=false +if text:lower():find("arty engage")or text:lower():find("arty attack")then +assignment.engage=true +elseif text:lower():find("arty move")or text:lower():find("arty relocate")then +assignment.move=true +elseif text:lower():find("arty request")then +assignment.request=true +elseif text:lower():find("arty cancel")then +assignment.cancel=true +elseif text:lower():find("arty set")then +assignment.set=true +else +self:E(self.lid..'ERROR: Neither "ARTY ENGAGE" nor "ARTY MOVE" nor "ARTY RELOCATE" nor "ARTY REQUEST" nor "ARTY CANCEL" nor "ARTY SET" keyword specified!') +return nil +end +local keywords=self:_split(text,",") +self:T({keywords=keywords}) +for _,keyphrase in pairs(keywords)do +local str=self:_split(keyphrase," ") +local key=str[1] +local val=str[2] +self:T3(self.lid..string.format("%s, keyphrase = %s, key = %s, val = %s",self.groupname,tostring(keyphrase),tostring(key),tostring(val))) +if key:lower():find("battery")then +local v=self:_split(keyphrase,'"') +for i=2,#v,2 do +table.insert(assignment.battery,v[i]) +self:T2(self.lid..string.format("Key Battery=%s.",v[i])) +end +elseif key:lower():find("alias")then +local v=self:_split(keyphrase,'"') +for i=2,#v,2 do +table.insert(assignment.aliases,v[i]) +self:T2(self.lid..string.format("Key Aliases=%s.",v[i])) +end +elseif key:lower():find("cluster")then +local v=self:_split(keyphrase,'"') +for i=2,#v,2 do +table.insert(assignment.cluster,v[i]) +self:T2(self.lid..string.format("Key Cluster=%s.",v[i])) +end +elseif keyphrase:lower():find("everyone")or keyphrase:lower():find("all batteries")or keyphrase:lower():find("allbatteries")then +assignment.everyone=true +self:T(self.lid..string.format("Key Everyone=true.")) +elseif keyphrase:lower():find("irrevocable")or keyphrase:lower():find("readonly")then +assignment.readonly=true +self:T2(self.lid..string.format("Key Readonly=true.")) +elseif(assignment.engage or assignment.move)and key:lower():find("time")then +if val:lower():find("now")then +assignment.time=self:_SecondsToClock(timer.getTime0()+2) +else +assignment.time=val +end +self:T2(self.lid..string.format("Key Time=%s.",val)) +elseif assignment.engage and key:lower():find("shot")then +assignment.nshells=tonumber(val) +self:T(self.lid..string.format("Key Shot=%s.",val)) +elseif assignment.engage and key:lower():find("prio")then +assignment.prio=tonumber(val) +self:T2(string.format("Key Prio=%s.",val)) +elseif assignment.engage and key:lower():find("maxengage")then +assignment.maxengage=tonumber(val) +self:T2(self.lid..string.format("Key Maxengage=%s.",val)) +elseif assignment.engage and key:lower():find("radius")then +assignment.radius=tonumber(val) +self:T2(self.lid..string.format("Key Radius=%s.",val)) +elseif assignment.engage and key:lower():find("weapon")then +if val:lower():find("cannon")then +assignment.weapontype=ARTY.WeaponType.Cannon +elseif val:lower():find("rocket")then +assignment.weapontype=ARTY.WeaponType.Rockets +elseif val:lower():find("missile")then +assignment.weapontype=ARTY.WeaponType.CruiseMissile +elseif val:lower():find("nuke")then +assignment.weapontype=ARTY.WeaponType.TacticalNukes +elseif val:lower():find("illu")then +assignment.weapontype=ARTY.WeaponType.IlluminationShells +elseif val:lower():find("smoke")then +assignment.weapontype=ARTY.WeaponType.SmokeShells +else +assignment.weapontype=ARTY.WeaponType.Auto +end +self:T2(self.lid..string.format("Key Weapon=%s.",val)) +elseif(assignment.move or assignment.set)and key:lower():find("speed")then +assignment.speed=tonumber(val) +self:T2(self.lid..string.format("Key Speed=%s.",val)) +elseif(assignment.move or assignment.set)and(keyphrase:lower():find("on road")or keyphrase:lower():find("onroad")or keyphrase:lower():find("use road"))then +assignment.onroad=true +self:T2(self.lid..string.format("Key Onroad=true.")) +elseif assignment.move and(keyphrase:lower():find("cancel target")or keyphrase:lower():find("canceltarget"))then +assignment.movecanceltarget=true +self:T2(self.lid..string.format("Key Cancel Target (before move)=true.")) +elseif assignment.request and keyphrase:lower():find("rearm")then +assignment.requestrearming=true +self:T2(self.lid..string.format("Key Request Rearming=true.")) +elseif assignment.request and keyphrase:lower():find("ammo")then +assignment.requestammo=true +self:T2(self.lid..string.format("Key Request Ammo=true.")) +elseif assignment.request and keyphrase:lower():find("target")then +assignment.requesttargets=true +self:T2(self.lid..string.format("Key Request Targets=true.")) +elseif assignment.request and keyphrase:lower():find("status")then +assignment.requeststatus=true +self:T2(self.lid..string.format("Key Request Status=true.")) +elseif assignment.request and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then +assignment.requestmoves=true +self:T2(self.lid..string.format("Key Request Moves=true.")) +elseif assignment.cancel and(keyphrase:lower():find("engagement")or keyphrase:lower():find("attack")or keyphrase:lower():find("target"))then +assignment.canceltarget=true +self:T2(self.lid..string.format("Key Cancel Target=true.")) +elseif assignment.cancel and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then +assignment.cancelmove=true +self:T2(self.lid..string.format("Key Cancel Move=true.")) +elseif assignment.cancel and keyphrase:lower():find("rearm")then +assignment.cancelrearm=true +self:T2(self.lid..string.format("Key Cancel Rearm=true.")) +elseif assignment.set and keyphrase:lower():find("rearming place")then +assignment.setrearmingplace=true +self:T(self.lid..string.format("Key Set Rearming Place=true.")) +elseif assignment.set and keyphrase:lower():find("rearming group")then +local v=self:_split(keyphrase,'"') +local groupname=v[2] +local group=GROUP:FindByName(groupname) +if group and group:IsAlive()then +assignment.setrearminggroup=group +end +self:T2(self.lid..string.format("Key Set Rearming Group = %s.",tostring(groupname))) +elseif key:lower():find("lldms")then +local _flat="%d+:%d+:%d+%s*[N,S]" +local _flon="%d+:%d+:%d+%s*[W,E]" +local _lat=keyphrase:match(_flat) +local _lon=keyphrase:match(_flon) +self:T2(self.lid..string.format("Key LLDMS: lat=%s, long=%s format=DMS",_lat,_lon)) +if _lat and _lon then +local _latitude,_longitude=self:_LLDMS2DD(_lat,_lon) +self:T2(self.lid..string.format("Key LLDMS: lat=%.3f, long=%.3f format=DD",_latitude,_longitude)) +if _latitude and _longitude then +assignment.coord=COORDINATE:NewFromLLDD(_latitude,_longitude) +end +end +end +end +return assignment +end +function ARTY:_MarkRequestAmmo() +self:GetAmmo(true) +end +function ARTY:_MarkRequestStatus() +self:_StatusReport(true) +end +function ARTY:_MarkRequestMoves() +local text=string.format("%s, relocations:",self.groupname) +if#self.moves>0 then +for _,move in pairs(self.moves)do +if self.currentMove and move.name==self.currentMove.name then +text=text..string.format("\n- %s (current)",self:_MoveInfo(move)) +else +text=text..string.format("\n- %s",self:_MoveInfo(move)) +end +end +else +text=text..string.format("\n- no queued relocations") +end +MESSAGE:New(text,20):Clear():ToCoalition(self.coalition) +end +function ARTY:_MarkRequestTargets() +local text=string.format("%s, targets:",self.groupname) +if#self.targets>0 then +for _,target in pairs(self.targets)do +if self.currentTarget and target.name==self.currentTarget.name then +text=text..string.format("\n- %s (current)",self:_TargetInfo(target)) +else +text=text..string.format("\n- %s",self:_TargetInfo(target)) +end +end +else +text=text..string.format("\n- no queued targets") +end +MESSAGE:New(text,20):Clear():ToCoalition(self.coalition) +end +function ARTY:_MarkTargetName(markerid) +return string.format("BATTERY=%s, Marked Target ID=%d",self.groupname,markerid) +end +function ARTY:_MarkMoveName(markerid) +return string.format("BATTERY=%s, Marked Relocation ID=%d",self.groupname,markerid) +end +function ARTY:_GetMarkIDfromName(name) +local keywords=self:_split(name,",") +local battery=nil +local markTID=nil +local markMID=nil +for _,key in pairs(keywords)do +local str=self:_split(key,"=") +local par=str[1] +local val=str[2] +if par:find("BATTERY")then +battery=val +end +if par:find("Marked Target ID")then +markTID=tonumber(val) +end +if par:find("Marked Relocation ID")then +markMID=tonumber(val) +end +end +return battery,markTID,markMID +end +function ARTY:_SortTargetQueuePrio() +self:F2() +local function _sort(a,b) +return(a.engaged_target.engaged and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then +self:T2(self.lid..string.format("Found NORMAL target %s",self:_TargetInfo(_target))) +return _target +end +end +return nil +end +function ARTY:_CheckTimedTargets() +self:F3() +local Tnow=timer.getAbsTime() +self:_SortQueueTime(self.targets) +if self:is("Rearming")then +return nil +end +for i=1,#self.targets do +local _target=self.targets[i] +self:T3(self.lid..string.format("Check TIMED target %d: %s",i,self:_TargetInfo(_target))) +if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then +if self.currentTarget then +if self.currentTarget.prio>_target.prio then +self:T2(self.lid..string.format("Found TIMED HIGH PRIO target %s.",self:_TargetInfo(_target))) +return _target +end +else +self:T2(self.lid..string.format("Found TIMED target %s.",self:_TargetInfo(_target))) +return _target +end +end +end +return nil +end +function ARTY:_CheckMoves() +self:F3() +local Tnow=timer.getAbsTime() +self:_SortQueueTime(self.moves) +local firing=false +if self.currentTarget then +firing=true +end +for i=1,#self.moves do +local _move=self.moves[i] +if string.find(_move.name,"REARMING MOVE")and((self.currentMove and self.currentMove.name~=_move.name)or self.currentMove==nil)then +return _move +elseif(Tnow>=_move.time)and(firing==false or _move.cancel)and(not self.currentMove)and(not self:is("Rearming"))then +return _move +end +end +return nil +end +function ARTY:_CheckShootingStarted() +self:F2() +if self.currentTarget then +local Tnow=timer.getTime() +local name=self.currentTarget.name +local dt=Tnow-self.currentTarget.Tassigned +if self.Nshots==0 then +self:T(self.lid..string.format("%s, waiting for %d seconds for first shot on target %s.",self.groupname,dt,name)) +end +self:T(string.format("dt = %d WaitTime = %d | shots = %d TargetShells = %d",dt,self.WaitForShotTime,self.Nshots,self.currentTarget.nshells)) +if(dt>self.WaitForShotTime and self.Nshots==0)or(self.currentTarget.nshells<=self.Nshots)then +self:T(self.lid..string.format("%s, no shot event after %d seconds. Removing current target %s from list.",self.groupname,self.WaitForShotTime,name)) +self:CeaseFire(self.currentTarget) +self:RemoveTarget(name) +end +end +end +function ARTY:_GetTargetIndexByName(name) +self:F2(name) +for i=1,#self.targets do +local targetname=self.targets[i].name +self:T3(self.lid..string.format("Have target with name %s. Index = %d",targetname,i)) +if targetname==name then +self:T2(self.lid..string.format("Found target with name %s. Index = %d",name,i)) +return i +end +end +self:T2(self.lid..string.format("WARNING: Target with name %s could not be found. (This can happen.)",name)) +return nil +end +function ARTY:_GetMoveIndexByName(name) +self:F2(name) +for i=1,#self.moves do +local movename=self.moves[i].name +self:T3(self.lid..string.format("Have move with name %s. Index = %d",movename,i)) +if movename==name then +self:T2(self.lid..string.format("Found move with name %s. Index = %d",name,i)) +return i +end +end +self:T2(self.lid..string.format("WARNING: Move with name %s could not be found. (This can happen.)",name)) +return nil +end +function ARTY:_CheckOutOfAmmo(targets) +local _nammo,_nshells,_nrockets,_nmissiles,_narty=self:GetAmmo() +local _partlyoutofammo=false +for _,Target in pairs(targets)do +if Target.weapontype==ARTY.WeaponType.Auto and _nammo==0 then +self:T(self.lid..string.format("Group %s, auto weapon requested for target %s but all ammo is empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.Cannon and _narty==0 then +self:T(self.lid..string.format("Group %s, cannons requested for target %s but shells empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes<=0 then +self:T(self.lid..string.format("Group %s, tactical nukes requested for target %s but nukes empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu<=0 then +self:T(self.lid..string.format("Group %s, illumination shells requested for target %s but illumination shells empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke<=0 then +self:T(self.lid..string.format("Group %s, smoke shells requested for target %s but smoke shells empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then +self:T(self.lid..string.format("Group %s, rockets requested for target %s but rockets empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.CruiseMissile and _nmissiles==0 then +self:T(self.lid..string.format("Group %s, cruise missiles requested for target %s but all missiles empty.",self.groupname,Target.name)) +_partlyoutofammo=true +end +end +return _partlyoutofammo +end +function ARTY:_CheckWeaponTypeAvailable(target) +local Nammo,Nshells,Nrockets,Nmissiles,Narty=self:GetAmmo() +local nfire=Nammo +if target.weapontype==ARTY.WeaponType.Auto then +nfire=Nammo +elseif target.weapontype==ARTY.WeaponType.Cannon then +nfire=Narty +elseif target.weapontype==ARTY.WeaponType.TacticalNukes then +nfire=self.Nukes +elseif target.weapontype==ARTY.WeaponType.IlluminationShells then +nfire=self.Nillu +elseif target.weapontype==ARTY.WeaponType.SmokeShells then +nfire=self.Nsmoke +elseif target.weapontype==ARTY.WeaponType.Rockets then +nfire=Nrockets +elseif target.weapontype==ARTY.WeaponType.CruiseMissile then +nfire=Nmissiles +end +return nfire +end +function ARTY:_CheckWeaponTypePossible(target) +local possible=false +if target.weapontype==ARTY.WeaponType.Auto then +possible=self.Nammo0>0 +elseif target.weapontype==ARTY.WeaponType.Cannon then +possible=self.Nshells0>0 +elseif target.weapontype==ARTY.WeaponType.TacticalNukes then +possible=self.Nukes0>0 +elseif target.weapontype==ARTY.WeaponType.IlluminationShells then +possible=self.Nillu0>0 +elseif target.weapontype==ARTY.WeaponType.SmokeShells then +possible=self.Nsmoke0>0 +elseif target.weapontype==ARTY.WeaponType.Rockets then +possible=self.Nrockets0>0 +elseif target.weapontype==ARTY.WeaponType.CruiseMissile then +possible=self.Nmissiles0>0 +end +return possible +end +function ARTY:_CheckName(givennames,name,makeunique) +self:F2({givennames=givennames,name=name}) +local newname=name +local counter=1 +local n=1 +local nmax=100 +if makeunique==nil then +makeunique=true +end +repeat +local _unique=true +for _,_target in pairs(givennames)do +local _givenname=_target.name +if _givenname==newname then +_unique=false +end +self:T3(self.lid..string.format("%d: givenname = %s, newname=%s, unique = %s, makeunique = %s",n,tostring(_givenname),newname,tostring(_unique),tostring(makeunique))) +end +if _unique==false and makeunique==true then +newname=string.format("%s #%02d",name,counter) +counter=counter+1 +end +if _unique==false and makeunique==false then +self:T3(self.lid..string.format("Name %s is not unique. Return false.",tostring(newname))) +return name,false +end +n=n+1 +until(_unique or n==nmax) +self:T3(self.lid..string.format("Original name %s, new name = %s",name,newname)) +return newname,true +end +function ARTY:_TargetInRange(target,message) +self:F3(target) +if message==nil then +message=false +end +self:T3({controllable=self.Controllable,targetcoord=target.coord}) +local _dist=self.Controllable:GetCoordinate():Get2DDistance(target.coord) +local _inrange=true +local _tooclose=false +local _toofar=false +local text="" +if _distself.maxrange then +_inrange=false +_toofar=true +text=string.format("%s, target is out of range. Distance of %.1f km is greater than max range of %.1f km.",self.alias,_dist/1000,self.maxrange/1000) +end +if not _inrange then +self:T(self.lid..text) +MESSAGE:New(text,5):ToCoalitionIf(self.coalition,(self.report and message)or(self.Debug and message)) +end +local _remove=false +if not(self.ismobile or self.iscargo)and _inrange==false then +_remove=true +end +return _inrange,_toofar,_tooclose,_remove +end +function ARTY:_WeaponTypeName(tnumber) +self:F2(tnumber) +local name="unknown" +if tnumber==ARTY.WeaponType.Auto then +name="Auto" +elseif tnumber==ARTY.WeaponType.Cannon then +name="Cannons" +elseif tnumber==ARTY.WeaponType.Rockets then +name="Rockets" +elseif tnumber==ARTY.WeaponType.CruiseMissile then +name="Cruise Missiles" +elseif tnumber==ARTY.WeaponType.TacticalNukes then +name="Tactical Nukes" +elseif tnumber==ARTY.WeaponType.IlluminationShells then +name="Illumination Shells" +elseif tnumber==ARTY.WeaponType.SmokeShells then +name="Smoke Shells" +end +return name +end +function ARTY:_VicinityCoord(coord,rmin,rmax) +self:F2({coord=coord,rmin=rmin,rmax=rmax}) +rmin=rmin or 20 +rmax=rmax or 80 +local vec2=coord:GetRandomVec2InRadius(rmax,rmin) +local pops=COORDINATE:NewFromVec2(vec2) +self:T3(self.lid..string.format("Vicinity distance = %d (rmin=%d, rmax=%d)",pops:Get2DDistance(coord),rmin,rmax)) +return pops +end +function ARTY:_EventFromTo(BA,Event,From,To) +local text=string.format("%s: %s EVENT %s: %s --> %s",BA,self.groupname,Event,From,To) +self:T3(self.lid..text) +end +function ARTY:_split(str,sep) +self:F3({str=str,sep=sep}) +local result={} +local regex=("([^%s]+)"):format(sep) +for each in str:gmatch(regex)do +table.insert(result,each) +end +return result +end +function ARTY:_TargetInfo(target) +local clock=tostring(self:_SecondsToClock(target.time)) +local weapon=self:_WeaponTypeName(target.weapontype) +local _underfire=tostring(target.underfire) +return string.format("%s: prio=%d, radius=%d, nshells=%d, engaged=%d/%d, weapontype=%s, time=%s, underfire=%s, attackgroup=%s", +target.name,target.prio,target.radius,target.nshells,target.engaged,target.maxengage,weapon,clock,_underfire,tostring(target.attackgroup)) +end +function ARTY:_MoveInfo(move) +self:F3(move) +local _clock=self:_SecondsToClock(move.time) +return string.format("%s: time=%s, speed=%d, onroad=%s, cancel=%s",move.name,_clock,move.speed,tostring(move.onroad),tostring(move.cancel)) +end +function ARTY:_LLDMS2DD(l1,l2) +self:F2(l1,l2) +local _latlong={l1,l2} +local _latitude=nil +local _longitude=nil +for _,ll in pairs(_latlong)do +local _format="%d+:%d+:%d+" +local _ldms=ll:match(_format) +if _ldms then +local _dms=self:_split(_ldms,":") +local _deg=tonumber(_dms[1]) +local _min=tonumber(_dms[2]) +local _sec=tonumber(_dms[3]) +local function DMS2DD(d,m,s) +return d+m/60+s/3600 +end +if ll:match("N")then +_latitude=DMS2DD(_deg,_min,_sec) +elseif ll:match("S")then +_latitude=-DMS2DD(_deg,_min,_sec) +elseif ll:match("W")then +_longitude=-DMS2DD(_deg,_min,_sec) +elseif ll:match("E")then +_longitude=DMS2DD(_deg,_min,_sec) +end +local text=string.format("DMS %02d Deg %02d min %02d sec",_deg,_min,_sec) +self:T2(self.lid..text) +end +end +local text=string.format("\nLatitude %s",tostring(_latitude)) +text=text..string.format("\nLongitude %s",tostring(_longitude)) +self:T2(self.lid..text) +return _latitude,_longitude +end +function ARTY:_SecondsToClock(seconds) +self:F3({seconds=seconds}) +if seconds==nil then +return nil +end +local seconds=tonumber(seconds) +local _seconds=seconds%(60*60*24) +if seconds<=0 then +return nil +else +local hours=string.format("%02.f",math.floor(_seconds/3600)) +local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60))) +local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60)) +local days=string.format("%d",seconds/(60*60*24)) +return hours..":"..mins..":"..secs.."+"..days +end +end +function ARTY:_ClockToSeconds(clock) +self:F3({clock=clock}) +if clock==nil then +return nil +end +local seconds=0 +local dsplit=self:_split(clock,"+") +if#dsplit>1 then +seconds=seconds+tonumber(dsplit[2])*60*60*24 +end +local tsplit=self:_split(dsplit[1],":") +local i=1 +for _,time in ipairs(tsplit)do +if i==1 then +seconds=seconds+tonumber(time)*60*60 +elseif i==2 then +seconds=seconds+tonumber(time)*60 +elseif i==3 then +seconds=seconds+tonumber(time) +end +i=i+1 +end +self:T3(self.lid..string.format("Clock %s = %d seconds",clock,seconds)) +return seconds +end +SUPPRESSION={ +ClassName="SUPPRESSION", +Debug=false, +lid=nil, +flare=false, +smoke=false, +DCSdesc=nil, +Type=nil, +IsInfantry=nil, +SpeedMax=nil, +Tsuppress_ave=15, +Tsuppress_min=5, +Tsuppress_max=25, +TsuppressOver=nil, +IniGroupStrength=nil, +Nhit=0, +Formation="Off road", +Speed=4, +MenuON=false, +FallbackON=false, +FallbackWait=60, +FallbackDist=100, +FallbackHeading=nil, +TakecoverON=false, +TakecoverWait=120, +TakecoverRange=300, +hideout=nil, +PminFlee=10, +PmaxFlee=90, +RetreatZone=nil, +RetreatDamage=nil, +RetreatWait=7200, +CurrentAlarmState="unknown", +CurrentROE="unknown", +DefaultAlarmState="Auto", +DefaultROE="Weapon Free", +eventmoose=true, +waypoints={}, +} +SUPPRESSION.ROE={ +Hold="Weapon Hold", +Free="Weapon Free", +Return="Return Fire", +} +SUPPRESSION.AlarmState={ +Auto="Auto", +Green="Green", +Red="Red", +} +SUPPRESSION.MenuF10=nil +SUPPRESSION.version="0.9.4" +function SUPPRESSION:New(group) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) +if group then +self.lid=string.format("SUPPRESSION %s | ",tostring(group:GetName())) +self:T(self.lid..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s",SUPPRESSION.version,group:GetName())) +else +self:E("SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group)") +return nil +end +if group:IsGround()==false then +self:E(self.lid..string.format("SUPPRESSION fire group %s has to be a GROUND group!",group:GetName())) +return nil +end +self:SetControllable(group) +self.DCSdesc=group:GetDCSDesc(1) +self.SpeedMax=group:GetSpeedMax() +self.Speed=self.SpeedMax +self.IsInfantry=group:GetUnit(1):HasAttribute("Infantry") +self.Type=group:GetTypeName() +self.IniGroupStrength=#group:GetUnits() +self:SetDefaultROE("Free") +self:SetDefaultAlarmState("Auto") +self:AddTransition("*","Start","CombatReady") +self:AddTransition("*","Status","*") +self:AddTransition("CombatReady","Hit","Suppressed") +self:AddTransition("Suppressed","Hit","Suppressed") +self:AddTransition("Suppressed","Recovered","CombatReady") +self:AddTransition("Suppressed","TakeCover","TakingCover") +self:AddTransition("Suppressed","FallBack","FallingBack") +self:AddTransition("*","Retreat","Retreating") +self:AddTransition("TakingCover","FightBack","CombatReady") +self:AddTransition("FallingBack","FightBack","CombatReady") +self:AddTransition("Retreating","Retreated","Retreated") +self:AddTransition("*","OutOfAmmo","*") +self:AddTransition("*","Dead","*") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("TakingCover","Hit","TakingCover") +self:AddTransition("FallingBack","Hit","FallingBack") +return self +end +function SUPPRESSION:SetSuppressionTime(Tave,Tmin,Tmax) +self:F({Tave=Tave,Tmin=Tmin,Tmax=Tmax}) +self.Tsuppress_min=Tmin or self.Tsuppress_min +self.Tsuppress_min=math.max(self.Tsuppress_min,1) +self.Tsuppress_max=Tmax or self.Tsuppress_max +self.Tsuppress_max=math.max(self.Tsuppress_max,self.Tsuppress_min) +self.Tsuppress_ave=Tave or self.Tsuppress_ave +self.Tsuppress_ave=math.max(self.Tsuppress_min) +self.Tsuppress_ave=math.min(self.Tsuppress_max) +self:T(self.lid..string.format("Set ave suppression time to %d seconds.",self.Tsuppress_ave)) +self:T(self.lid..string.format("Set min suppression time to %d seconds.",self.Tsuppress_min)) +self:T(self.lid..string.format("Set max suppression time to %d seconds.",self.Tsuppress_max)) +end +function SUPPRESSION:SetRetreatZone(zone) +self:F({zone=zone}) +self.RetreatZone=zone +end +function SUPPRESSION:DebugOn() +self:F() +self.Debug=true +end +function SUPPRESSION:FlareOn() +self:F() +self.flare=true +end +function SUPPRESSION:SmokeOn() +self:F() +self.smoke=true +end +function SUPPRESSION:SetFormation(formation) +self:F(formation) +self.Formation=formation or"Vee" +end +function SUPPRESSION:SetSpeed(speed) +self:F(speed) +self.Speed=speed or self.SpeedMax +self.Speed=math.min(self.Speed,self.SpeedMax) +end +function SUPPRESSION:Fallback(switch) +self:F(switch) +if switch==nil then +switch=true +end +self.FallbackON=switch +end +function SUPPRESSION:SetFallbackDistance(distance) +self:F(distance) +self.FallbackDist=distance +end +function SUPPRESSION:SetFallbackWait(time) +self:F(time) +self.FallbackWait=time +end +function SUPPRESSION:Takecover(switch) +self:F(switch) +if switch==nil then +switch=true +end +self.TakecoverON=switch +end +function SUPPRESSION:SetTakecoverWait(time) +self:F(time) +self.TakecoverWait=time +end +function SUPPRESSION:SetTakecoverRange(range) +self:F(range) +self.TakecoverRange=range +end +function SUPPRESSION:SetTakecoverPlace(Hideout) +self.hideout=Hideout +end +function SUPPRESSION:SetMinimumFleeProbability(probability) +self:F(probability) +self.PminFlee=probability or 10 +end +function SUPPRESSION:SetMaximumFleeProbability(probability) +self:F(probability) +self.PmaxFlee=probability or 90 +end +function SUPPRESSION:SetRetreatDamage(damage) +self:F(damage) +self.RetreatDamage=damage or 50 +end +function SUPPRESSION:SetRetreatWait(time) +self:F(time) +self.RetreatWait=time or 7200 +end +function SUPPRESSION:SetDefaultAlarmState(alarmstate) +self:F(alarmstate) +if alarmstate:lower()=="auto"then +self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto +elseif alarmstate:lower()=="green"then +self.DefaultAlarmState=SUPPRESSION.AlarmState.Green +elseif alarmstate:lower()=="red"then +self.DefaultAlarmState=SUPPRESSION.AlarmState.Red +else +self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto +end +end +function SUPPRESSION:SetDefaultROE(roe) +self:F(roe) +if roe:lower()=="free"then +self.DefaultROE=SUPPRESSION.ROE.Free +elseif roe:lower()=="hold"then +self.DefaultROE=SUPPRESSION.ROE.Hold +elseif roe:lower()=="return"then +self.DefaultROE=SUPPRESSION.ROE.Return +else +self.DefaultROE=SUPPRESSION.ROE.Free +end +end +function SUPPRESSION:MenuOn(switch) +self:F(switch) +if switch==nil then +switch=true +end +self.MenuON=switch +end +function SUPPRESSION:_CreateMenuGroup() +local SubMenuName=self.Controllable:GetName() +local MenuGroup=MENU_MISSION:New(SubMenuName,SUPPRESSION.MenuF10) +MENU_MISSION_COMMAND:New("Fallback!",MenuGroup,self.OrderFallBack,self) +MENU_MISSION_COMMAND:New("Take Cover!",MenuGroup,self.OrderTakeCover,self) +MENU_MISSION_COMMAND:New("Retreat!",MenuGroup,self.OrderRetreat,self) +MENU_MISSION_COMMAND:New("Report Status",MenuGroup,self.Status,self,true) +end +function SUPPRESSION:OrderFallBack() +local group=self.Controllable +local vicinity=group:GetCoordinate():GetRandomVec2InRadius(150,100) +local coord=COORDINATE:NewFromVec2(vicinity) +self:FallBack(self.Controllable) +end +function SUPPRESSION:OrderTakeCover() +local Hideout=self.hideout +if self.hideout==nil then +Hideout=self:_SearchHideout() +end +self:TakeCover(Hideout) +end +function SUPPRESSION:OrderRetreat() +self:Retreat() +end +function SUPPRESSION:StatusReport(message) +local group=self.Controllable +local nunits=group:CountAliveUnits() +local roe=self.CurrentROE +local state=self.CurrentAlarmState +local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife() +local ammotot=group:GetAmmunition() +local detectedG=group:GetDetectedGroupSet():CountAlive() +local detectedU=group:GetDetectedUnitSet():Count() +local text=string.format("State %s, Units=%d/%d, ROE=%s, AlarmState=%s, Hits=%d, Life(min/max/ave/ave0)=%d/%d/%d/%d, Total Ammo=%d, Detected=%d/%d", +self:GetState(),nunits,self.IniGroupStrength,self.CurrentROE,self.CurrentAlarmState,self.Nhit,life_min,life_max,life_ave,life_ave0,ammotot,detectedG,detectedU) +MESSAGE:New(text,10):ToAllIf(message or self.Debug) +self:I(self.lid..text) +end +function SUPPRESSION:onafterStart(Controllable,From,Event,To) +self:_EventFromTo("onafterStart",Event,From,To) +local text=string.format("Started SUPPRESSION for group %s.",Controllable:GetName()) +self:I(self.lid..text) +MESSAGE:New(text,10):ToAllIf(self.Debug) +local rzone="not defined" +if self.RetreatZone then +rzone=self.RetreatZone:GetName() +end +if self.RetreatDamage==nil then +if self.RetreatZone then +if self.IniGroupStrength==1 then +self.RetreatDamage=60.0 +elseif self.IniGroupStrength==2 then +self.RetreatDamage=50.0 +else +self.RetreatDamage=66.5 +end +else +self.RetreatDamage=100 +end +end +if self.MenuON then +if not SUPPRESSION.MenuF10 then +SUPPRESSION.MenuF10=MENU_MISSION:New("Suppression") +end +self:_CreateMenuGroup() +end +self:_SetAlarmState(self.DefaultAlarmState) +self:_SetROE(self.DefaultROE) +local text=string.format("\n******************************************************\n") +text=text..string.format("Suppressed group = %s\n",Controllable:GetName()) +text=text..string.format("Type = %s\n",self.Type) +text=text..string.format("IsInfantry = %s\n",tostring(self.IsInfantry)) +text=text..string.format("Group strength = %d\n",self.IniGroupStrength) +text=text..string.format("Average time = %5.1f seconds\n",self.Tsuppress_ave) +text=text..string.format("Minimum time = %5.1f seconds\n",self.Tsuppress_min) +text=text..string.format("Maximum time = %5.1f seconds\n",self.Tsuppress_max) +text=text..string.format("Default ROE = %s\n",self.DefaultROE) +text=text..string.format("Default AlarmState = %s\n",self.DefaultAlarmState) +text=text..string.format("Fall back ON = %s\n",tostring(self.FallbackON)) +text=text..string.format("Fall back distance = %5.1f m\n",self.FallbackDist) +text=text..string.format("Fall back wait = %5.1f seconds\n",self.FallbackWait) +text=text..string.format("Fall back heading = %s degrees\n",tostring(self.FallbackHeading)) +text=text..string.format("Take cover ON = %s\n",tostring(self.TakecoverON)) +text=text..string.format("Take cover search = %5.1f m\n",self.TakecoverRange) +text=text..string.format("Take cover wait = %5.1f seconds\n",self.TakecoverWait) +text=text..string.format("Min flee probability = %5.1f\n",self.PminFlee) +text=text..string.format("Max flee probability = %5.1f\n",self.PmaxFlee) +text=text..string.format("Retreat zone = %s\n",rzone) +text=text..string.format("Retreat damage = %5.1f %%\n",self.RetreatDamage) +text=text..string.format("Retreat wait = %5.1f seconds\n",self.RetreatWait) +text=text..string.format("Speed = %5.1f km/h\n",self.Speed) +text=text..string.format("Speed max = %5.1f km/h\n",self.SpeedMax) +text=text..string.format("Formation = %s\n",self.Formation) +text=text..string.format("******************************************************\n") +self:T(self.lid..text) +if self.eventmoose then +self:HandleEvent(EVENTS.Hit,self._OnEventHit) +self:HandleEvent(EVENTS.Dead,self._OnEventDead) +else +world.addEventHandler(self) +end +self:__Status(-1) +end +function SUPPRESSION:onafterStatus(Controllable,From,Event,To) +local group=self.Controllable +if group then +local nunits=group:CountAliveUnits() +if nunits>0 then +local nammo=group:GetAmmunition() +if nammo==0 then +self:OutOfAmmo() +end +self:StatusReport(false) +if self:GetState()~="Stopped"then +self:__Status(-30) +end +else +self:Stop() +end +else +self:Stop() +end +end +function SUPPRESSION:onafterHit(Controllable,From,Event,To,Unit,AttackUnit) +self:_EventFromTo("onafterHit",Event,From,To) +if From=="CombatReady"or From=="Suppressed"then +self:_Suppress() +end +local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife() +local Damage=100-life_ave0 +local RetreatCondition=Damage>=self.RetreatDamage-0.01 and self.RetreatZone +local Pflee=(self.PmaxFlee-self.PminFlee)/self.RetreatDamage*math.min(Damage,self.RetreatDamage)+self.PminFlee +local P=math.random(0,100) +local FleeCondition=P Prand ==> Flee)\n",Controllable:GetName(),Pflee,P) +self:T(self.lid..text) +if Damage>=99.9 then +return +end +if RetreatCondition then +self:Retreat() +elseif FleeCondition then +if self.FallbackON and AttackUnit:IsGround()then +self:FallBack(AttackUnit) +elseif self.TakecoverON then +local Hideout=self.hideout +if self.hideout==nil then +Hideout=self:_SearchHideout() +end +self:TakeCover(Hideout) +end +end +end +function SUPPRESSION:onbeforeRecovered(Controllable,From,Event,To) +self:_EventFromTo("onbeforeRecovered",Event,From,To) +local Tnow=timer.getTime() +self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d",Tnow,self.TsuppressionOver)) +if Tnow>=self.TsuppressionOver then +return true +else +return false +end +end +function SUPPRESSION:onafterRecovered(Controllable,From,Event,To) +self:_EventFromTo("onafterRecovered",Event,From,To) +if Controllable and Controllable:IsAlive()then +local text=string.format("Group %s has recovered!",Controllable:GetName()) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:T(self.lid..text) +self:_SetROE() +if self.flare or self.Debug then +Controllable:FlareGreen() +end +end +end +function SUPPRESSION:onafterFightBack(Controllable,From,Event,To) +self:_EventFromTo("onafterFightBack",Event,From,To) +self:_SetROE() +self:_SetAlarmState() +local group=Controllable +local Waypoints=group:GetTemplateRoutePoints() +group:Route(Waypoints,5) +end +function SUPPRESSION:onbeforeFallBack(Controllable,From,Event,To,AttackUnit) +self:_EventFromTo("onbeforeFallBack",Event,From,To) +if From=="FallingBack"then +return false +else +return true +end +end +function SUPPRESSION:onafterFallBack(Controllable,From,Event,To,AttackUnit) +self:_EventFromTo("onafterFallback",Event,From,To) +self:T(self.lid..string.format("Group %s is falling back after %d hits.",Controllable:GetName(),self.Nhit)) +local ACoord=AttackUnit:GetCoordinate() +local DCoord=Controllable:GetCoordinate() +local heading=self:_Heading(ACoord,DCoord) +if self.FallbackHeading then +heading=self.FallbackHeading +end +local Coord=DCoord:Translate(self.FallbackDist,heading) +if self.Debug then +local MarkerID=Coord:MarkToAll("Fall back position for group "..Controllable:GetName()) +end +if self.smoke or self.Debug then +Coord:SmokeBlue() +end +self:_SetROE(SUPPRESSION.ROE.Hold) +self:_SetAlarmState(SUPPRESSION.AlarmState.Auto) +self:_Run(Coord,self.Speed,self.Formation,self.FallbackWait) +end +function SUPPRESSION:onbeforeTakeCover(Controllable,From,Event,To,Hideout) +self:_EventFromTo("onbeforeTakeCover",Event,From,To) +if From=="TakingCover"then +return false +end +if Hideout~=nil then +return true +else +return false +end +end +function SUPPRESSION:onafterTakeCover(Controllable,From,Event,To,Hideout) +self:_EventFromTo("onafterTakeCover",Event,From,To) +if self.Debug then +local MarkerID=Hideout:MarkToAll(string.format("Hideout for group %s",Controllable:GetName())) +end +if self.smoke or self.Debug then +Hideout:SmokeBlue() +end +self:_SetROE(SUPPRESSION.ROE.Hold) +self:_SetAlarmState(SUPPRESSION.AlarmState.Green) +self:_Run(Hideout,self.Speed,self.Formation,self.TakecoverWait) +end +function SUPPRESSION:onafterOutOfAmmo(Controllable,From,Event,To) +self:_EventFromTo("onafterOutOfAmmo",Event,From,To) +self:I(self.lid..string.format("Out of ammo!")) +if self.RetreatZone then +self:Retreat() +end +end +function SUPPRESSION:onbeforeRetreat(Controllable,From,Event,To) +self:_EventFromTo("onbeforeRetreat",Event,From,To) +if From=="Retreating"then +local text=string.format("Group %s is already retreating.",tostring(Controllable:GetName())) +self:T2(self.lid..text) +return false +else +return true +end +end +function SUPPRESSION:onafterRetreat(Controllable,From,Event,To) +self:_EventFromTo("onafterRetreat",Event,From,To) +local text=string.format("Group %s is retreating! Alarm state green.",Controllable:GetName()) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:T(self.lid..text) +local ZoneCoord=self.RetreatZone:GetRandomCoordinate() +local ZoneVec2=ZoneCoord:GetVec2() +if self.smoke or self.Debug then +ZoneCoord:SmokeBlue() +end +if self.Debug then +self.RetreatZone:SmokeZone(SMOKECOLOR.Red,12) +end +self:_SetROE(SUPPRESSION.ROE.Hold) +self:_SetAlarmState(SUPPRESSION.AlarmState.Green) +self:_Run(ZoneCoord,self.Speed,self.Formation,self.RetreatWait) +end +function SUPPRESSION:onbeforeRetreated(Controllable,From,Event,To) +self:_EventFromTo("onbeforeRetreated",Event,From,To) +local inzone=self.RetreatZone:IsVec3InZone(Controllable:GetVec3()) +return inzone +end +function SUPPRESSION:onafterRetreated(Controllable,From,Event,To) +self:_EventFromTo("onafterRetreated",Event,From,To) +self:_SetROE(SUPPRESSION.ROE.Return) +self:_SetAlarmState(SUPPRESSION.AlarmState.Auto) +end +function SUPPRESSION:onafterDead(Controllable,From,Event,To) +self:_EventFromTo("onafterDead",Event,From,To) +local group=self.Controllable +if group then +local nunits=group:CountAliveUnits() +local text=string.format("Group %s: One of our units just died! %d units left.",self.Controllable:GetName(),nunits) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:T(self.lid..text) +if nunits==0 then +self:Stop() +end +else +self:Stop() +end +end +function SUPPRESSION:onafterStop(Controllable,From,Event,To) +self:_EventFromTo("onafterStop",Event,From,To) +local text=string.format("Stopping SUPPRESSION for group %s",self.Controllable:GetName()) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:I(self.lid..text) +self.CallScheduler:Clear() +if self.mooseevents then +self:UnHandleEvent(EVENTS.Dead) +self:UnHandleEvent(EVENTS.Hit) +else +world.removeEventHandler(self) +end +end +function SUPPRESSION:onEvent(Event) +if Event==nil or Event.initiator==nil or Unit.getByName(Event.initiator:getName())==nil then +return true +end +local EventData={} +if Event.initiator then +EventData.IniDCSUnit=Event.initiator +EventData.IniUnitName=Event.initiator:getName() +EventData.IniDCSGroup=Event.initiator:getGroup() +EventData.IniGroupName=Event.initiator:getGroup():getName() +EventData.IniGroup=GROUP:FindByName(EventData.IniGroupName) +EventData.IniUnit=UNIT:FindByName(EventData.IniUnitName) +end +if Event.target then +EventData.TgtDCSUnit=Event.target +EventData.TgtUnitName=Event.target:getName() +EventData.TgtDCSGroup=Event.target:getGroup() +EventData.TgtGroupName=Event.target:getGroup():getName() +EventData.TgtGroup=GROUP:FindByName(EventData.TgtGroupName) +EventData.TgtUnit=UNIT:FindByName(EventData.TgtUnitName) +end +if Event.id==world.event.S_EVENT_HIT then +self:_OnEventHit(EventData) +end +if Event.id==world.event.S_EVENT_DEAD then +self:_OnEventDead(EventData) +end +end +function SUPPRESSION:_OnEventHit(EventData) +self:F3(EventData) +local GroupNameSelf=self.Controllable:GetName() +local GroupNameTgt=EventData.TgtGroupName +local TgtUnit=EventData.TgtUnit +local tgt=EventData.TgtDCSUnit +local IniUnit=EventData.IniUnit +if GroupNameTgt==GroupNameSelf then +self:T(self.lid..string.format("Hit event at t = %5.1f",timer.getTime())) +if self.flare or self.Debug then +TgtUnit:FlareRed() +end +self.Nhit=self.Nhit+1 +self:T(self.lid..string.format("Group %s has just been hit %d times.",self.Controllable:GetName(),self.Nhit)) +local life=tgt:getLife()/(tgt:getLife0()+1)*100 +self:T2(self.lid..string.format("Target unit life = %5.1f",life)) +self:__Hit(3,TgtUnit,IniUnit) +end +end +function SUPPRESSION:_OnEventDead(EventData) +local GroupNameSelf=self.Controllable:GetName() +local GroupNameIni=EventData.IniGroupName +if GroupNameIni==GroupNameSelf then +local IniUnit=EventData.IniUnit +local IniUnitName=EventData.IniUnitName +if EventData.IniUnit then +self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES exist! Unit name %s.",GroupNameIni,IniUnitName)) +else +self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES NOT not exist! Unit name %s.",GroupNameIni,IniUnitName)) +end +if EventData.IniDCSUnit then +self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES exist! Unit name %s.",GroupNameIni,IniUnitName)) +else +self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES NOT exist! Unit name %s.",GroupNameIni,IniUnitName)) +end +if IniUnit and(self.flare or self.Debug)then +IniUnit:FlareWhite() +self:T(self.lid..string.format("Flare Dead MOOSE unit.")) +end +if EventData.IniDCSUnit and(self.flare or self.Debug)then +local p=EventData.IniDCSUnit:getPosition().p +trigger.action.signalFlare(p,trigger.flareColor.Yellow,0) +self:T(self.lid..string.format("Flare Dead DCS unit.")) +end +self:Status() +self:__Dead(0.1) +end +end +function SUPPRESSION:_Suppress() +local Tnow=timer.getTime() +local Controllable=self.Controllable +self:_SetROE(SUPPRESSION.ROE.Hold) +local sigma=(self.Tsuppress_max-self.Tsuppress_min)/4 +local Tsuppress=self:_Random_Gaussian(self.Tsuppress_ave,sigma,self.Tsuppress_min,self.Tsuppress_max) +local renew=true +if self.TsuppressionOver~=nil then +if Tsuppress+Tnow>self.TsuppressionOver then +self.TsuppressionOver=Tnow+Tsuppress +else +renew=false +end +else +self.TsuppressionOver=Tnow+Tsuppress +end +if renew then +self:__Recovered(self.TsuppressionOver-Tnow) +end +local text=string.format("Group %s is suppressed for %d seconds. Suppression ends at %d:%02d.",Controllable:GetName(),Tsuppress,self.TsuppressionOver/60,self.TsuppressionOver%60) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:T(self.lid..text) +end +function SUPPRESSION:_Run(fin,speed,formation,wait) +speed=speed or 20 +formation=formation or ENUMS.Formation.Vehicle.OffRoad +wait=wait or 30 +local group=self.Controllable +if group and group:IsAlive()then +local ini=group:GetCoordinate() +local dist=ini:Get2DDistance(fin) +local heading=self:_Heading(ini,fin) +local wp={} +local tasks={} +wp[1]=ini:WaypointGround(speed,formation) +if self.Debug then +local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)",#wp,self.Controllable:GetName())) +end +local ConditionWait=group:TaskCondition(nil,nil,nil,nil,wait,nil) +local TaskHold=group:TaskHold() +local TaskComboFin={} +TaskComboFin[#TaskComboFin+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint",self,#wp,true) +TaskComboFin[#TaskComboFin+1]=group:TaskControlled(TaskHold,ConditionWait) +wp[#wp+1]=fin:WaypointGround(speed,formation,TaskComboFin) +if self.Debug then +local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)",#wp,self.Controllable:GetName())) +end +group:Route(wp) +else +self:E(self.lid..string.format("ERROR: Group is not alive!")) +end +end +function SUPPRESSION._Passing_Waypoint(group,Fsm,i,final) +local text=string.format("Group %s passing waypoint %d (final=%s)",group:GetName(),i,tostring(final)) +MESSAGE:New(text,10):ToAllIf(Fsm.Debug) +if Fsm.Debug then +env.info(Fsm.lid..text) +end +if final then +if Fsm:is("Retreating")then +Fsm:Retreated() +else +Fsm:FightBack() +end +end +end +function SUPPRESSION:_SearchHideout() +local Zone=ZONE_GROUP:New("Zone_Hiding",self.Controllable,self.TakecoverRange) +local gpos=self.Controllable:GetCoordinate() +Zone:Scan(Object.Category.SCENERY) +local hideouts={} +for SceneryTypeName,SceneryData in pairs(Zone:GetScannedScenery())do +for SceneryName,SceneryObject in pairs(SceneryData)do +local SceneryObject=SceneryObject +local spos=SceneryObject:GetCoordinate() +local distance=spos:Get2DDistance(gpos) +if self.Debug then +local MarkerID=SceneryObject:GetCoordinate():MarkToAll(string.format("%s scenery object %s",self.Controllable:GetName(),SceneryObject:GetTypeName())) +local text=string.format("%s scenery: %s, Coord %s",self.Controllable:GetName(),SceneryObject:GetTypeName(),SceneryObject:GetCoordinate():ToStringLLDMS()) +self:T2(self.lid..text) +end +table.insert(hideouts,{object=SceneryObject,distance=distance}) +end +end +local Hideout=nil +if#hideouts>0 then +self:T(self.lid.."Number of hideouts "..#hideouts) +local _sort=function(a,b)return a.distancelife_max then +life_max=life +end +life_ave=life_ave+life +if self.Debug then +local text=string.format("n=%02d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f",n,unit:GetLife(),unit:GetLife0(),life_min,life_max,life_ave/n,groupstrength) +self:T2(self.lid..text) +end +end +end +if n==0 then +return 0,0,0,0,0 +end +life_ave0=life_ave/self.IniGroupStrength +life_ave=life_ave/n +return life_min,life_max,life_ave,life_ave0,groupstrength +else +return 0,0,0,0,0 +end +end +function SUPPRESSION:_Heading(a,b) +local dx=b.x-a.x +local dy=b.z-a.z +local angle=math.deg(math.atan2(dy,dx)) +if angle<0 then +angle=360+angle +end +return angle +end +function SUPPRESSION:_Random_Gaussian(x0,sigma,xmin,xmax) +sigma=sigma or 5 +local r +local gotit=false +local i=0 +while not gotit do +local x1=math.random() +local x2=math.random() +r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0 +i=i+1 +if(r>=xmin and r<=xmax)or i>100 then +gotit=true +end +end +return r +end +function SUPPRESSION:_SetROE(roe) +local group=self.Controllable +roe=roe or self.DefaultROE +self.CurrentROE=roe +if roe==SUPPRESSION.ROE.Free then +group:OptionROEOpenFire() +elseif roe==SUPPRESSION.ROE.Hold then +group:OptionROEHoldFire() +elseif roe==SUPPRESSION.ROE.Return then +group:OptionROEReturnFire() +else +self:E(self.lid.."Unknown ROE requested: "..tostring(roe)) +group:OptionROEOpenFire() +self.CurrentROE=SUPPRESSION.ROE.Free +end +local text=string.format("Group %s now has ROE %s.",self.Controllable:GetName(),self.CurrentROE) +self:T(self.lid..text) +end +function SUPPRESSION:_SetAlarmState(state) +local group=self.Controllable +state=state or self.DefaultAlarmState +self.CurrentAlarmState=state +if state==SUPPRESSION.AlarmState.Auto then +group:OptionAlarmStateAuto() +elseif state==SUPPRESSION.AlarmState.Green then +group:OptionAlarmStateGreen() +elseif state==SUPPRESSION.AlarmState.Red then +group:OptionAlarmStateRed() +else +self:E(self.lid.."Unknown alarm state requested: "..tostring(state)) +group:OptionAlarmStateAuto() +self.CurrentAlarmState=SUPPRESSION.AlarmState.Auto +end +local text=string.format("Group %s now has Alarm State %s.",self.Controllable:GetName(),self.CurrentAlarmState) +self:T(self.lid..text) +end +function SUPPRESSION:_EventFromTo(BA,Event,From,To) +local text=string.format("\n%s: %s EVENT %s: %s --> %s",BA,self.Controllable:GetName(),Event,From,To) +self:T2(self.lid..text) +end +PSEUDOATC={ +ClassName="PSEUDOATC", +group={}, +Debug=false, +mdur=30, +mrefresh=120, +talt=3, +chatty=true, +eventsmoose=true, +reportplayername=false, +} +PSEUDOATC.id="PseudoATC | " +PSEUDOATC.version="0.10.5" +function PSEUDOATC:New() +local self=BASE:Inherit(self,BASE:New()) +self:E(PSEUDOATC.id..string.format("PseudoATC version %s",PSEUDOATC.version)) +return self +end +function PSEUDOATC:Start() +self:F() +self:I(PSEUDOATC.id.."Starting PseudoATC") +self:HandleEvent(EVENTS.Birth,self._OnBirth) +self:HandleEvent(EVENTS.Land,self._PlayerLanded) +self:HandleEvent(EVENTS.Takeoff,self._PlayerTakeOff) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft) +self:HandleEvent(EVENTS.Crash,self._PlayerLeft) +end +function PSEUDOATC:DebugOn() +self.Debug=true +end +function PSEUDOATC:DebugOff() +self.Debug=false +end +function PSEUDOATC:ChattyOn() +self.chatty=true +end +function PSEUDOATC:ChattyOff() +self.chatty=false +end +function PSEUDOATC:SetMessageDuration(duration) +self.mdur=duration or 30 +end +function PSEUDOATC:SetReportPlayername() +self.reportplayername=true +return self +end +function PSEUDOATC:SetMenuRefresh(interval) +self.mrefresh=interval or 120 +end +function PSEUDOATC:SetEventsMoose(switch) +self.eventsmoose=switch +end +function PSEUDOATC:SetReportAltInterval(interval) +self.talt=interval or 3 +end +function PSEUDOATC:_OnBirth(EventData) +self:F({EventData=EventData}) +local _unitName=EventData.IniUnitName +local _unit=EventData.IniUnit +local _playername=EventData.IniPlayerName +if _unit and _playername then +self:PlayerEntered(_unit) +end +end +function PSEUDOATC:_PlayerLeft(EventData) +self:F({EventData=EventData}) +local _unitName=EventData.IniUnitName +local _unit=EventData.IniUnit +local _playername=EventData.IniPlayerName +if _unit and _playername then +self:PlayerLeft(_unit) +end +end +function PSEUDOATC:_PlayerLanded(EventData) +self:F({EventData=EventData}) +local _unitName=EventData.IniUnitName +local _unit=EventData.IniUnit +local _playername=EventData.IniPlayerName +local _base=nil +local _baseName=nil +if EventData.place then +_base=EventData.place +_baseName=EventData.place:getName() +end +if _unit and _playername and _base then +self:PlayerLanded(_unit,_baseName) +end +end +function PSEUDOATC:_PlayerTakeOff(EventData) +self:F({EventData=EventData}) +local _unitName=EventData.IniUnitName +local _unit=EventData.IniUnit +local _playername=EventData.IniPlayerName +local _base=nil +local _baseName=nil +if EventData.place then +_base=EventData.place +_baseName=EventData.place:getName() +end +if _unit and _playername and _base then +self:PlayerTakeOff(_unit,_baseName) +end +end +function PSEUDOATC:PlayerEntered(unit) +self:F2({unit=unit}) +local group=unit:GetGroup() +local GID=group:GetID() +local GroupName=group:GetName() +local PlayerName=unit:GetPlayerName() +local UnitName=unit:GetName() +local CallSign=unit:GetCallsign() +local UID=unit:GetDCSObject():getID() +if not self.group[GID]then +self.group[GID]={} +self.group[GID].player={} +end +self.group[GID].player[UID]={} +self.group[GID].player[UID].group=group +self.group[GID].player[UID].unit=unit +self.group[GID].player[UID].groupname=GroupName +self.group[GID].player[UID].unitname=UnitName +self.group[GID].player[UID].playername=PlayerName +self.group[GID].player[UID].callsign=CallSign +self.group[GID].player[UID].waypoints=group:GetTaskRoute() +local text=string.format("Player %s entered unit %s of group %s (id=%d).",PlayerName,UnitName,GroupName,GID) +self:T(PSEUDOATC.id..text) +MESSAGE:New(text,30):ToAllIf(self.Debug) +local countPlayerInGroup=0 +for _ in pairs(self.group[GID].player)do countPlayerInGroup=countPlayerInGroup+1 end +if countPlayerInGroup<=1 then +self.group[GID].menu_main=missionCommands.addSubMenuForGroup(GID,"Pseudo ATC") +end +self:MenuCreatePlayer(GID,UID) +self:LocalAirports(GID,UID) +self:MenuAirports(GID,UID) +self:MenuWaypoints(GID,UID) +self.group[GID].player[UID].scheduler,self.group[GID].player[UID].schedulerid=SCHEDULER:New(nil,self.MenuRefresh,{self,GID,UID},self.mrefresh,self.mrefresh) +end +function PSEUDOATC:PlayerLanded(unit,place) +self:F2({unit=unit,place=place}) +local group=unit:GetGroup() +local GID=group:GetID() +local UID=unit:GetDCSObject():getID() +local PlayerName=unit:GetPlayerName()or"Ghost" +local UnitName=unit:GetName()or"Ghostplane" +local GroupName=group:GetName()or"Ghostgroup" +if self.Debug then +local text=string.format("Player %s in unit %s of group %s landed at %s.",PlayerName,UnitName,GroupName,place) +self:T(PSEUDOATC.id..text) +MESSAGE:New(text,30):ToAllIf(self.Debug) +end +self:AltitudeTimerStop(GID,UID) +if place and self.chatty then +local text=string.format("Touchdown! Welcome to %s pilot %s. Have a nice day!",place,PlayerName) +MESSAGE:New(text,self.mdur):ToGroup(group) +end +end +function PSEUDOATC:PlayerTakeOff(unit,place) +self:F2({unit=unit,place=place}) +local group=unit:GetGroup() +local PlayerName=unit:GetPlayerName()or"Ghost" +local UnitName=unit:GetName()or"Ghostplane" +local GroupName=group:GetName()or"Ghostgroup" +local CallSign=unit:GetCallsign()or"Ghost11" +if self.Debug then +local text=string.format("Player %s in unit %s of group %s took off at %s.",PlayerName,UnitName,GroupName,place) +self:T(PSEUDOATC.id..text) +MESSAGE:New(text,30):ToAllIf(self.Debug) +end +if place and self.chatty then +local text=string.format("%s, %s, you are airborne. Have a safe trip!",place,CallSign) +if self.reportplayername then +text=string.format("%s, %s, you are airborne. Have a safe trip!",place,PlayerName) +end +MESSAGE:New(text,self.mdur):ToGroup(group) +end +end +function PSEUDOATC:PlayerLeft(unit) +self:F({unit=unit}) +local group=unit:GetGroup() +local GID=group:GetID() +local UID=unit:GetDCSObject():getID() +if self.group[GID]and self.group[GID].player and self.group[GID].player[UID]then +local PlayerName=self.group[GID].player[UID].playername +local CallSign=self.group[GID].player[UID].callsign +local UnitName=self.group[GID].player[UID].unitname +local GroupName=self.group[GID].player[UID].groupname +local text=string.format("Player %s (callsign %s) of group %s just left unit %s.",PlayerName,CallSign,GroupName,UnitName) +self:T(PSEUDOATC.id..text) +MESSAGE:New(text,30):ToAllIf(self.Debug) +if self.group[GID].player[UID].schedulerid then +self.group[GID].player[UID].scheduler:Stop(self.group[GID].player[UID].schedulerid) +end +self:AltitudeTimerStop(GID,UID) +if self.group[GID].player[UID].menu_own then +missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_own) +end +local countPlayerInGroup=0 +for _ in pairs(self.group[GID].player)do countPlayerInGroup=countPlayerInGroup+1 end +if self.group[GID].menu_main and countPlayerInGroup==1 then +missionCommands.removeItemForGroup(GID,self.group[GID].menu_main) +end +self.group[GID].player[UID]=nil +end +end +function PSEUDOATC:MenuRefresh(GID,UID) +self:F({GID=GID,UID=UID}) +local text=string.format("Refreshing menues for player %s in group %s.",self.group[GID].player[UID].playername,self.group[GID].player[UID].groupname) +self:T(PSEUDOATC.id..text) +MESSAGE:New(text,30):ToAllIf(self.Debug) +self:MenuClear(GID,UID) +self:LocalAirports(GID,UID) +self:MenuAirports(GID,UID) +self:MenuWaypoints(GID,UID) +end +function PSEUDOATC:MenuCreatePlayer(GID,UID) +self:F({GID=GID,UID=UID}) +local PlayerName=self.group[GID].player[UID].playername +self.group[GID].player[UID].menu_own=missionCommands.addSubMenuForGroup(GID,PlayerName,self.group[GID].menu_main) +end +function PSEUDOATC:MenuClear(GID,UID) +self:F({GID=GID,UID=UID}) +local text=string.format("Clearing menus for player %s in group %s.",self.group[GID].player[UID].playername,self.group[GID].player[UID].groupname) +self:T(PSEUDOATC.id..text) +MESSAGE:New(text,30):ToAllIf(self.Debug) +if self.group[GID].player[UID].menu_airports then +missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_airports) +self.group[GID].player[UID].menu_airports=nil +else +self:T2(PSEUDOATC.id.."No airports to clear menus.") +end +if self.group[GID].player[UID].menu_waypoints then +missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_waypoints) +self.group[GID].player[UID].menu_waypoints=nil +end +if self.group[GID].player[UID].menu_reportalt then +missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_reportalt) +self.group[GID].player[UID].menu_reportalt=nil +end +if self.group[GID].player[UID].menu_requestalt then +missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_requestalt) +self.group[GID].player[UID].menu_requestalt=nil +end +end +function PSEUDOATC:MenuAirports(GID,UID) +self:F({GID=GID,UID=UID}) +self.group[GID].player[UID].menu_airports=missionCommands.addSubMenuForGroup(GID,"Local Airports",self.group[GID].player[UID].menu_own) +local i=0 +for _,airport in pairs(self.group[GID].player[UID].airports)do +i=i+1 +if i>10 then +break +end +local name=airport.name +local d=airport.distance +local pos=AIRBASE:FindByName(name):GetCoordinate() +local submenu=missionCommands.addSubMenuForGroup(GID,name,self.group[GID].player[UID].menu_airports) +missionCommands.addCommandForGroup(GID,"Weather Report",submenu,self.ReportWeather,self,GID,UID,pos,name) +missionCommands.addCommandForGroup(GID,"Request BR",submenu,self.ReportBR,self,GID,UID,pos,name) +self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d",name,GID)) +end +end +function PSEUDOATC:MenuWaypoints(GID,UID) +self:F({GID=GID,UID=UID}) +local callsign=self.group[GID].player[UID].callsign +self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).",callsign,GID)) +if#self.group[GID].player[UID].waypoints>0 then +self.group[GID].player[UID].menu_waypoints=missionCommands.addSubMenuForGroup(GID,"Waypoints",self.group[GID].player[UID].menu_own) +local j=0 +for i,wp in pairs(self.group[GID].player[UID].waypoints)do +j=j+1 +if j>10 then +break +end +local pos=COORDINATE:New(wp.x,wp.alt,wp.y) +local name=string.format("Waypoint %d",i-1) +if wp.name and wp.name~=""then +name=string.format("Waypoint %s",wp.name) +end +local submenu=missionCommands.addSubMenuForGroup(GID,name,self.group[GID].player[UID].menu_waypoints) +missionCommands.addCommandForGroup(GID,"Weather Report",submenu,self.ReportWeather,self,GID,UID,pos,name) +missionCommands.addCommandForGroup(GID,"Request BR",submenu,self.ReportBR,self,GID,UID,pos,name) +end +end +self.group[GID].player[UID].menu_reportalt=missionCommands.addCommandForGroup(GID,"Talk me down",self.group[GID].player[UID].menu_own,self.AltidudeTimerToggle,self,GID,UID) +self.group[GID].player[UID].menu_requestalt=missionCommands.addCommandForGroup(GID,"Request altitude",self.group[GID].player[UID].menu_own,self.ReportHeight,self,GID,UID) +end +function PSEUDOATC:ReportWeather(GID,UID,position,location) +self:F({GID=GID,UID=UID,position=position,location=location}) +local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS +local text=string.format("Local weather at %s:\n",location) +local Pqnh=position:GetPressure(0) +local Pqfe=position:GetPressure() +local hPa2inHg=0.0295299830714 +local hPa2mmHg=0.7500615613030 +local _Pqnh=string.format("%.2f inHg",Pqnh*hPa2inHg) +local _Pqfe=string.format("%.2f inHg",Pqfe*hPa2inHg) +if settings:IsMetric()then +_Pqnh=string.format("%.1f mmHg",Pqnh*hPa2mmHg) +_Pqfe=string.format("%.1f mmHg",Pqfe*hPa2mmHg) +end +text=text..string.format("QFE %.1f hPa = %s.\n",Pqfe,_Pqfe) +text=text..string.format("QNH %.1f hPa = %s.\n",Pqnh,_Pqnh) +local T=position:GetTemperature() +local _T=string.format('%d°F',UTILS.CelsiusToFahrenheit(T)) +if settings:IsMetric()then +_T=string.format('%d°C',T) +end +local text=text..string.format("Temperature %s\n",_T) +local Dir,Vel=position:GetWind() +local Bn,Bd=UTILS.BeaufortScale(Vel) +local Ds=string.format('%03d°',Dir) +local Vs=string.format("%.1f knots",UTILS.MpsToKnots(Vel)) +if settings:IsMetric()then +Vs=string.format('%.1f m/s',Vel) +end +local text=text..string.format("%s, Wind from %s at %s (%s).",self.group[GID].player[UID].playername,Ds,Vs,Bd) +self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,text,self.mdur,true) +end +function PSEUDOATC:ReportBR(GID,UID,position,location) +self:F({GID=GID,UID=UID,position=position,location=location}) +local unit=self.group[GID].player[UID].unit +local coord=unit:GetCoordinate() +local angle=coord:HeadingTo(position) +local range=coord:Get2DDistance(position) +local Bs=string.format('%03d°',angle) +local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS +local Rs=string.format("%.1f NM",UTILS.MetersToNM(range)) +if settings:IsMetric()then +Rs=string.format("%.1f km",range/1000) +end +local text=string.format("%s: Bearing %s, Range %s.",location,Bs,Rs) +self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,text,self.mdur,true) +end +function PSEUDOATC:ReportHeight(GID,UID,dt,_clear) +self:F({GID=GID,UID=UID,dt=dt}) +local dt=dt or self.mdur +if _clear==nil then +_clear=false +end +local function get_AGL(p) +local agl=0 +local vec2={x=p.x,y=p.z} +local ground=land.getHeight(vec2) +local agl=p.y-ground +return agl +end +local unit=self.group[GID].player[UID].unit +if unit and unit:IsAlive()then +local position=unit:GetCoordinate() +local height=get_AGL(position) +local callsign=unit:GetCallsign() +local PlayerName=self.group[GID].player[UID].playername +local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS +local Hs=string.format("%d ft",UTILS.MetersToFeet(height)) +if settings:IsMetric()then +Hs=string.format("%d m",height) +end +local _text=string.format("%s, your altitude is %s AGL.",callsign,Hs) +if self.reportplayername then +_text=string.format("%s, your altitude is %s AGL.",PlayerName,Hs) +end +if _clear==false then +_text=_text..string.format(" FL%03d.",position.y/30.48) +end +self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,_text,dt,_clear) +return height +end +return 0 +end +function PSEUDOATC:AltidudeTimerToggle(GID,UID) +self:F({GID=GID,UID=UID}) +if self.group[GID].player[UID].altimerid then +self:AltitudeTimerStop(GID,UID) +else +self:AltitudeTimeStart(GID,UID) +end +end +function PSEUDOATC:AltitudeTimeStart(GID,UID) +self:F({GID=GID,UID=UID}) +self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.",UID)) +self.group[GID].player[UID].altimer,self.group[GID].player[UID].altimerid=SCHEDULER:New(nil,self.ReportHeight,{self,GID,UID,1,true},1,3) +end +function PSEUDOATC:AltitudeTimerStop(GID,UID) +self:F({GID=GID,UID=UID}) +self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.",UID)) +if self.group[GID].player[UID].altimerid then +self.group[GID].player[UID].altimer:Stop(self.group[GID].player[UID].altimerid) +end +self.group[GID].player[UID].altimer=nil +self.group[GID].player[UID].altimerid=nil +end +function PSEUDOATC:LocalAirports(GID,UID) +self:F({GID=GID,UID=UID}) +self.group[GID].player[UID].airports=nil +self.group[GID].player[UID].airports={} +local pos=self.group[GID].player[UID].unit:GetCoordinate() +for i=0,2 do +local airports=coalition.getAirbases(i) +for _,airbase in pairs(airports)do +local name=airbase:getName() +local a=AIRBASE:FindByName(name) +if a then +local q=a:GetCoordinate() +local d=q:Get2DDistance(pos) +table.insert(self.group[GID].player[UID].airports,{distance=d,name=name}) +end +end +end +local function compare(a,b) +return a.distance0 then +local samecoalition=anycoalition or Coalition==warehouse:GetCoalition() +if samecoalition and not(warehouse:IsNotReadyYet()or warehouse:IsStopped()or warehouse:IsDestroyed())then +local nassets=warehouse:GetNumberOfAssets(Descriptor,DescriptorValue) +local enough=true +if Descriptor and DescriptorValue then +enough=nassets>=MinAssets +end +if enough and(distmin==nil or dist=1 then +local FSMstate=self:GetState() +local coalition=self:GetCoalitionName() +local country=self:GetCountryName() +self:I(self.lid..string.format("State=%s %s [%s]: Assets=%d, Requests: waiting=%d, pending=%d",FSMstate,country,coalition,#self.stock,#self.queue,#self.pending)) +end +self:_JobDone() +self:_DisplayStatus() +self:_CheckConquered() +if self:IsRunwayOperational()==false then +local Trepair=self:GetRunwayRepairtime() +self:I(self.lid..string.format("Runway destroyed! Will be repaired in %d sec",Trepair)) +if Trepair==0 then +self.runwaydestroyed=nil +self:RunwayRepaired() +end +end +self:_CheckRequestConsistancy(self.queue) +if self:IsRunning()or self:IsAttacked()then +local request=self:_CheckQueue() +if request then +self:Request(request) +end +end +if self.verbosity>2 then +self:_PrintQueue(self.queue,"Queue waiting") +self:_PrintQueue(self.pending,"Queue pending") +end +self:_UpdateWarehouseMarkText() +if self.Debug then +self:_DisplayStockItems(self.stock) +end +self:__Status(-self.dTstatus) +end +function WAREHOUSE:_JobDone() +local done={} +for _,request in pairs(self.pending)do +local request=request +if request.born then +local ncargo=0 +if request.cargogroupset then +ncargo=request.cargogroupset:Count() +end +local ntransport=0 +if request.transportgroupset then +ntransport=request.transportgroupset:Count() +end +local ncargotot=request.nasset +local ncargodelivered=request.ndelivered +local ncargodead=ncargotot-ncargodelivered-ncargo +local ntransporttot=request.ntransport +local ntransporthome=request.ntransporthome +local ntransportdead=ntransporttot-ntransporthome-ntransport +local text=string.format("Request id=%d: Cargo: Ntot=%d, Nalive=%d, Ndelivered=%d, Ndead=%d | Transport: Ntot=%d, Nalive=%d, Nhome=%d, Ndead=%d", +request.uid,ncargotot,ncargo,ncargodelivered,ncargodead,ntransporttot,ntransport,ntransporthome,ntransportdead) +self:T(self.lid..text) +if ncargo==0 then +if not self.delivered[request.uid]then +self:Delivered(request) +end +if ntransport==0 then +if self.verbosity>=1 then +local text=string.format("Warehouse %s: Job on request id=%d for warehouse %s done!\n",self.alias,request.uid,request.warehouse.alias) +text=text..string.format("- %d of %d assets delivered. Casualties %d.",ncargodelivered,ncargotot,ncargodead) +if request.ntransport>0 then +text=text..string.format("\n- %d of %d transports returned home. Casualties %d.",ntransporthome,ntransporttot,ntransportdead) +end +self:_InfoMessage(text,20) +end +table.insert(done,request) +else +for _,_group in pairs(request.transportgroupset:GetSetObjects())do +local group=_group +if group and group:IsAlive()then +local category=group:GetCategory() +local speed=group:GetVelocityKMH() +local notmoving=speed<1 +local airbase=group:GetCoordinate():GetClosestAirbase():GetName() +local athomebase=self.airbase and self.airbase:GetName()==airbase +local onground=not group:InAir() +local inspawnzone=group:IsPartlyOrCompletelyInZone(self.spawnzone) +local ishome=false +if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then +ishome=inspawnzone and onground and notmoving +elseif category==Group.Category.AIRPLANE then +ishome=athomebase and onground and notmoving +end +local text=string.format("Group %s: speed=%d km/h, onground=%s , airbase=%s, spawnzone=%s ==> ishome=%s",group:GetName(),speed,tostring(onground),airbase,tostring(inspawnzone),tostring(ishome)) +self:T(self.lid..text) +if ishome then +local text=string.format("Warehouse %s: Transport group arrived back home and no cargo left for request id=%d.\nSending transport group %s back to stock.",self.alias,request.uid,group:GetName()) +self:T(self.lid..text) +if self.Debug then +group:SmokeRed() +end +self:Arrived(group) +end +end +end +end +else +if ntransport==0 and request.ntransport>0 then +local ncargoalive=0 +for _,_group in pairs(request.cargogroupset:GetSetObjects())do +local groupname=_group:GetName() +local group=GROUP:FindByName(groupname.."#CARGO") +if group and group:IsAlive()then +if group:IsPartlyOrCompletelyInZone(self.spawnzone)then +if self.Debug then +group:SmokeBlue() +end +self:AddAsset(group) +ncargoalive=ncargoalive+1 +end +end +end +self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!",self.alias,request.uid,ncargoalive)) +end +end +end +end +for _,request in pairs(done)do +self:_DeleteQueueItem(request,self.pending) +end +end +function WAREHOUSE:_CheckAssetStatus() +local function _CheckGroup(_request,_group) +local request=_request +local group=_group +if group and group:IsAlive()then +local category=group:GetCategory() +for _,_unit in pairs(group:GetUnits())do +local unit=_unit +if unit and unit:IsAlive()then +local unitid=unit:GetID() +local life9=unit:GetLife() +local life0=unit:GetLife0() +local life=life9/life0*100 +local speed=unit:GetVelocityMPS() +local onground=unit:InAir() +local problem=false +if life<10 then +self:T(string.format("Unit %s is heavily damaged!",unit:GetName())) +end +if speed<1 and unit:GetSpeedMax()>1 and onground then +self:T(string.format("Unit %s is not moving!",unit:GetName())) +problem=true +end +if problem then +if request.assetproblem[unitid]then +local deltaT=timer.getAbsTime()-request.assetproblem[unitid] +if deltaT>300 then +unit:Destroy() +end +else +request.assetproblem[unitid]=timer.getAbsTime() +end +end +end +end +end +end +for _,request in pairs(self.pending)do +local request=request +if request.cargogroupset then +for _,_group in pairs(request.cargogroupset:GetSet())do +local group=_group +_CheckGroup(request,group) +end +end +if request.transportgroupset then +for _,group in pairs(request.transportgroupset:GetSet())do +_CheckGroup(request,group) +end +end +end +end +function WAREHOUSE:onafterAddAsset(From,Event,To,group,ngroups,forceattribute,forcecargobay,forceweight,loadradius,skill,liveries,assignment,other) +self:T({group=group,ngroups=ngroups,forceattribute=forceattribute,forcecargobay=forcecargobay,forceweight=forceweight}) +local n=ngroups or 1 +if type(group)=="string"then +group=GROUP:FindByName(group) +end +if liveries and type(liveries)=="string"then +liveries={liveries} +end +if group then +local wid,aid,rid=self:_GetIDsFromGroup(group) +if wid and aid and rid then +local warehouse=self:FindWarehouseInDB(wid) +if warehouse then +local request=warehouse:_GetRequestOfGroup(group,warehouse.pending) +if request then +local istransport=warehouse:_GroupIsTransport(group,request) +if istransport==true then +request.ntransporthome=request.ntransporthome+1 +request.transportgroupset:Remove(group:GetName(),true) +local ntrans=request.transportgroupset:Count() +self:T2(warehouse.lid..string.format("Transport %d of %s returned home. TransportSet=%d",request.ntransporthome,tostring(request.ntransport),ntrans)) +elseif istransport==false then +request.ndelivered=request.ndelivered+1 +local namewo=self:_GetNameWithOut(group) +request.cargogroupset:Remove(namewo,true) +local ncargo=request.cargogroupset:Count() +self:T2(warehouse.lid..string.format("Cargo %s: %d of %s delivered. CargoSet=%d",namewo,request.ndelivered,tostring(request.nasset),ncargo)) +else +self:E(warehouse.lid..string.format("WARNING: Group %s is neither cargo nor transport! Need to investigate...",group:GetName())) +end +if assignment==nil and request.assignment~=nil then +assignment=request.assignment +end +end +end +local asset=self:FindAssetInDB(group) +if asset~=nil then +self:_DebugMessage(string.format("Warehouse %s: Adding KNOWN asset uid=%d with attribute=%s to stock.",self.alias,asset.uid,asset.attribute),5) +if liveries then +if type(liveries)=="table"then +asset.livery=liveries[math.random(#liveries)] +else +asset.livery=liveries +end +end +asset.skill=skill or asset.skill +asset.wid=self.uid +asset.rid=nil +asset.spawned=false +asset.requested=false +asset.isReserved=false +asset.iscargo=nil +asset.arrived=nil +if group:IsAlive()==true then +asset.damage=asset.life0-group:GetLife() +end +table.insert(self.stock,asset) +self:__NewAsset(0.1,asset,assignment or"") +else +self:_ErrorMessage(string.format("ERROR: Known asset could not be found in global warehouse db!"),0) +end +else +self:_DebugMessage(string.format("Warehouse %s: Adding %d NEW assets of group %s to stock",self.alias,n,tostring(group:GetName())),5) +local assets=self:_RegisterAsset(group,n,forceattribute,forcecargobay,forceweight,loadradius,liveries,skill,assignment) +for _,asset in pairs(assets)do +asset.wid=self.uid +asset.rid=nil +table.insert(self.stock,asset) +self:__NewAsset(0.1,asset,assignment or"") +end +end +if group:IsAlive()==true then +self:_DebugMessage(string.format("Removing group %s",group:GetName()),5) +local opsgroup=_DATABASE:GetOpsGroup(group:GetName()) +if opsgroup then +opsgroup:Despawn(0,true) +opsgroup:__Stop(-0.01) +else +group:Destroy() +end +else +local opsgroup=_DATABASE:GetOpsGroup(group:GetName()) +if opsgroup then +opsgroup:Stop() +end +end +else +self:E(self.lid.."ERROR: Unknown group added as asset!") +self:E({unknowngroup=group}) +end +end +function WAREHOUSE:_RegisterAsset(group,ngroups,forceattribute,forcecargobay,forceweight,loadradius,liveries,skill,assignment) +self:F({groupname=group:GetName(),ngroups=ngroups,forceattribute=forceattribute,forcecargobay=forcecargobay,forceweight=forceweight}) +local n=ngroups or 1 +local function _GetObjectSize(DCSdesc) +if DCSdesc.box then +local x=DCSdesc.box.max.x-DCSdesc.box.min.x +local y=DCSdesc.box.max.y-DCSdesc.box.min.y +local z=DCSdesc.box.max.z-DCSdesc.box.min.z +return math.max(x,z),x,y,z +end +return 0,0,0,0 +end +local templategroupname=group:GetName() +local Descriptors=group:GetUnit(1):GetDesc() +local Category=group:GetCategory() +local TypeName=group:GetTypeName() +local SpeedMax=group:GetSpeedMax() +local RangeMin=group:GetRange() +local smax,sx,sy,sz=_GetObjectSize(Descriptors) +local weight=0 +local cargobay={} +local cargobaytot=0 +local cargobaymax=0 +local weights={} +for _i,_unit in pairs(group:GetUnits())do +local unit=_unit +local Desc=unit:GetDesc() +local unitweight=forceweight or Desc.massEmpty +if unitweight then +weight=weight+unitweight +weights[_i]=unitweight +end +local cargomax=0 +local massfuel=Desc.fuelMassMax or 0 +local massempty=Desc.massEmpty or 0 +local massmax=Desc.massMax or 0 +cargomax=massmax-massfuel-massempty +self:T3(self.lid..string.format("Unit name=%s: mass empty=%.1f kg, fuel=%.1f kg, max=%.1f kg ==> cargo=%.1f kg",unit:GetName(),unitweight,massfuel,massmax,cargomax)) +local bay=forcecargobay or unit:GetCargoBayFreeWeight() +table.insert(cargobay,bay) +cargobaytot=cargobaytot+bay +if bay>cargobaymax then +cargobaymax=bay +end +end +local attribute=forceattribute or self:_GetAttribute(group) +local assets={} +for i=1,n do +local asset={} +_WAREHOUSEDB.AssetID=_WAREHOUSEDB.AssetID+1 +asset.uid=_WAREHOUSEDB.AssetID +asset.templatename=templategroupname +asset.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) +asset.category=Category +asset.unittype=TypeName +asset.nunits=#asset.template.units +asset.range=RangeMin +asset.speedmax=SpeedMax +asset.size=smax +asset.weight=weight +asset.weights=weights +asset.DCSdesc=Descriptors +asset.attribute=attribute +asset.cargobay=cargobay +asset.cargobaytot=cargobaytot +asset.cargobaymax=cargobaymax +asset.loadradius=loadradius +if liveries then +asset.livery=liveries[math.random(#liveries)] +end +asset.skill=skill +asset.assignment=assignment +asset.spawned=false +asset.requested=false +asset.isReserved=false +asset.life0=group:GetLife0() +asset.damage=0 +asset.spawngroupname=string.format("%s_AID-%d",templategroupname,asset.uid) +if i==1 then +self:_AssetItemInfo(asset) +end +_WAREHOUSEDB.Assets[asset.uid]=asset +table.insert(assets,asset) +end +return assets +end +function WAREHOUSE:_AssetItemInfo(asset) +local text=string.format("\nNew asset with id=%d for warehouse %s:\n",asset.uid,self.alias) +text=text..string.format("Spawngroup name= %s\n",asset.spawngroupname) +text=text..string.format("Template name = %s\n",asset.templatename) +text=text..string.format("Unit type = %s\n",asset.unittype) +text=text..string.format("Attribute = %s\n",asset.attribute) +text=text..string.format("Category = %d\n",asset.category) +text=text..string.format("Units # = %d\n",asset.nunits) +text=text..string.format("Speed max = %5.2f km/h\n",asset.speedmax) +text=text..string.format("Range max = %5.2f km\n",asset.range/1000) +text=text..string.format("Size max = %5.2f m\n",asset.size) +text=text..string.format("Weight total = %5.2f kg\n",asset.weight) +text=text..string.format("Cargo bay tot = %5.2f kg\n",asset.cargobaytot) +text=text..string.format("Cargo bay max = %5.2f kg\n",asset.cargobaymax) +text=text..string.format("Load radius = %s m\n",tostring(asset.loadradius)) +text=text..string.format("Skill = %s\n",tostring(asset.skill)) +text=text..string.format("Livery = %s",tostring(asset.livery)) +self:I(self.lid..text) +self:T({DCSdesc=asset.DCSdesc}) +self:T3({Template=asset.template}) +end +function WAREHOUSE:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end +function WAREHOUSE:onafterNewAsset(From,Event,To,asset,assignment) +self:T(self.lid..string.format("New asset %s id=%d with assignment %s.",tostring(asset.templatename),asset.uid,tostring(assignment))) +end +function WAREHOUSE:onbeforeAddRequest(From,Event,To,warehouse,AssetDescriptor,AssetDescriptorValue,nAsset,TransportType,nTransport,Assignment,Prio) +local okay=true +if AssetDescriptor==WAREHOUSE.Descriptor.ATTRIBUTE then +local gotit=false +for _,attribute in pairs(WAREHOUSE.Attribute)do +if AssetDescriptorValue==attribute then +gotit=true +end +end +if not gotit then +self:_ErrorMessage("ERROR: Invalid request. Asset attribute is unknown!",5) +okay=false +end +elseif AssetDescriptor==WAREHOUSE.Descriptor.CATEGORY then +local gotit=false +for _,category in pairs(Group.Category)do +if AssetDescriptorValue==category then +gotit=true +end +end +if not gotit then +self:_ErrorMessage("ERROR: Invalid request. Asset category is unknown!",5) +okay=false +end +elseif AssetDescriptor==WAREHOUSE.Descriptor.GROUPNAME then +if type(AssetDescriptorValue)~="string"then +self:_ErrorMessage("ERROR: Invalid request. Asset template name must be passed as a string!",5) +okay=false +end +elseif AssetDescriptor==WAREHOUSE.Descriptor.UNITTYPE then +if type(AssetDescriptorValue)~="string"then +self:_ErrorMessage("ERROR: Invalid request. Asset unit type must be passed as a string!",5) +okay=false +end +elseif AssetDescriptor==WAREHOUSE.Descriptor.ASSIGNMENT then +if type(AssetDescriptorValue)~="string"then +self:_ErrorMessage("ERROR: Invalid request. Asset assignment type must be passed as a string!",5) +okay=false +end +elseif AssetDescriptor==WAREHOUSE.Descriptor.ASSETLIST then +if type(AssetDescriptorValue)~="table"then +self:_ErrorMessage("ERROR: Invalid request. Asset assignment type must be passed as a table!",5) +okay=false +end +else +self:_ErrorMessage("ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, GROUPNAME, UNITTYPE or ASSIGNMENT!",5) +okay=false +end +if self:IsStopped()then +self:_ErrorMessage("ERROR: Invalid request. Warehouse is stopped!",0) +okay=false +end +if self:IsDestroyed()and not self.respawnafterdestroyed then +self:_ErrorMessage("ERROR: Invalid request. Warehouse is destroyed!",0) +okay=false +end +return okay +end +function WAREHOUSE:onafterAddRequest(From,Event,To,warehouse,AssetDescriptor,AssetDescriptorValue,nAsset,TransportType,nTransport,Prio,Assignment) +nAsset=nAsset or 1 +TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED +Prio=Prio or 50 +if nTransport==nil then +if TransportType==WAREHOUSE.TransportType.SELFPROPELLED then +nTransport=0 +else +nTransport=1 +end +end +local toself=false +if self.warehouse:GetName()==warehouse.warehouse:GetName()then +toself=true +end +self.queueid=self.queueid+1 +local request={ +uid=self.queueid, +prio=Prio, +warehouse=warehouse, +assetdesc=AssetDescriptor, +assetdescval=AssetDescriptorValue, +nasset=nAsset, +transporttype=TransportType, +ntransport=nTransport, +assignment=tostring(Assignment), +airbase=warehouse:GetAirbase(), +category=warehouse:GetAirbaseCategory(), +ndelivered=0, +ntransporthome=0, +assets={}, +toself=toself, +} +table.insert(self.queue,request) +local descval="assetlist" +if request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST then +else +descval=tostring(request.assetdescval) +end +local text=string.format("Warehouse %s: New request from warehouse %s.\nDescriptor %s=%s, #assets=%s; Transport=%s, #transports=%s.", +self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),request.transporttype,tostring(request.ntransport)) +self:_DebugMessage(text,5) +end +function WAREHOUSE:onbeforeRequest(From,Event,To,Request) +self:T3({warehouse=self.alias,request=Request}) +local distance=self:GetCoordinate():Get2DDistance(Request.warehouse:GetCoordinate()) +local _assets=Request.cargoassets +if Request.nasset==0 then +local text=string.format("Warehouse %s: Request denied! Zero assets were requested.",self.alias) +self:_InfoMessage(text,10) +return false +end +for _,_asset in pairs(_assets)do +local asset=_asset +if asset.range=1 then +local text=string.format("Warehouse %s: Processing request id=%d from warehouse %s.\n",self.alias,Request.uid,Request.warehouse.alias) +text=text..string.format("Requested %s assets of %s=%s.\n",tostring(Request.nasset),Request.assetdesc,Request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST and"Asset list"or Request.assetdescval) +text=text..string.format("Transports %s of type %s.",tostring(Request.ntransport),tostring(Request.transporttype)) +self:_InfoMessage(text,5) +end +Request.timestamp=timer.getAbsTime() +self:_SpawnAssetRequest(Request) +local _assetstock=Request.transportassets +local Parking={} +if Request.transportcategory==Group.Category.AIRPLANE or Request.transportcategory==Group.Category.HELICOPTER then +Parking=self:_FindParkingForAssets(self.airbase,_assetstock) +end +local _transportassets={} +for i=1,Request.ntransport do +local _assetitem=_assetstock[i] +local _alias=_assetitem.spawngroupname +_assetitem.rid=Request.uid +_assetitem.spawned=false +_assetitem.iscargo=false +_assetitem.arrived=false +local spawngroup=nil +Request.assets[_assetitem.uid]=_assetitem +if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then +spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],true) +elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then +spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],false) +elseif Request.transporttype==WAREHOUSE.TransportType.APC then +spawngroup=self:_SpawnAssetGroundNaval(_alias,_assetitem,Request,self.spawnzone) +elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then +self:_ErrorMessage("ERROR: Cargo transport by train not supported yet!") +return +elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.NAVALCARRIER +or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then +spawngroup=self:_SpawnAssetGroundNaval(_alias,_assetitem,Request,self.portzone) +elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then +self:_ErrorMessage("ERROR: Transport type selfpropelled was already handled above. We should not get here!") +return +else +self:_ErrorMessage("ERROR: Unknown transport type!") +return +end +if spawngroup then +self:__AssetSpawned(0.01,spawngroup,_assetitem,Request) +end +end +Request.assetproblem={} +table.insert(self.pending,Request) +self:_DeleteQueueItem(Request,self.queue) +end +function WAREHOUSE:onafterRequestSpawned(From,Event,To,Request,CargoGroupSet,TransportGroupSet) +local _cargotype=Request.cargoattribute +local _cargocategory=Request.cargocategory +if Request.toself then +self:_DebugMessage(string.format("Selfrequest! Current status %s",self:GetState())) +self:__SelfRequest(1,CargoGroupSet,Request) +return +end +if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then +self:T2(self.lid..string.format("Got selfpropelled request for %d assets.",CargoGroupSet:Count())) +for _,_group in pairs(CargoGroupSet:GetSetObjects())do +local group=_group +if _cargocategory==Group.Category.GROUND then +self:T2(self.lid..string.format("Route ground group %s.",group:GetName())) +local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate() +if self.Debug then +ToCoordinate:MarkToAll(string.format("Destination of group %s",group:GetName())) +end +self:_RouteGround(group,Request) +elseif _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then +self:T2(self.lid..string.format("Route airborne group %s.",group:GetName())) +self:_RouteAir(group) +elseif _cargocategory==Group.Category.SHIP then +self:T2(self.lid..string.format("Route naval group %s.",group:GetName())) +self:_RouteNaval(group,Request) +elseif _cargocategory==Group.Category.TRAIN then +self:T2(self.lid..string.format("Route train group %s.",group:GetName())) +self:_RouteTrain(group,Request.warehouse.rail) +else +self:E(self.lid..string.format("ERROR: unknown category %s for self propelled cargo %s!",tostring(_cargocategory),tostring(group:GetName()))) +end +end +Request.transportgroupset=TransportGroupSet +return +end +local _boardradius=500 +if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then +_boardradius=5000 +elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then +elseif Request.transporttype==WAREHOUSE.TransportType.APC then +elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER +or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then +_boardradius=6000 +end +local CargoGroups=SET_CARGO:New() +for _,_group in pairs(CargoGroupSet:GetSetObjects())do +local asset=self:FindAssetInDB(_group) +local cargogroup=CARGO_GROUP:New(_group,_cargotype,_group:GetName(),_boardradius,asset.loadradius) +cargogroup:SetWeight(asset.weight) +CargoGroups:AddCargo(cargogroup) +end +local CargoTransport +if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then +local PickupAirbaseSet=SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName())) +local DeployAirbaseSet=SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName())) +CargoTransport=AI_CARGO_DISPATCHER_AIRPLANE:New(TransportGroupSet,CargoGroups,PickupAirbaseSet,DeployAirbaseSet) +CargoTransport:SetHomeZone(ZONE_AIRBASE:New(self.airbase:GetName())) +elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then +local PickupZoneSet=SET_ZONE:New():AddZone(self.spawnzone) +local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.spawnzone) +CargoTransport=AI_CARGO_DISPATCHER_HELICOPTER:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet) +CargoTransport:SetHomeZone(self.spawnzone) +elseif Request.transporttype==WAREHOUSE.TransportType.APC then +local PickupZoneSet=SET_ZONE:New():AddZone(self.spawnzone) +local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.spawnzone) +CargoTransport=AI_CARGO_DISPATCHER_APC:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet,0) +CargoTransport:SetHomeZone(self.spawnzone) +elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER +or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then +local PickupZoneSet=SET_ZONE:New():AddZone(self.portzone) +PickupZoneSet:AddZone(self.harborzone) +local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.harborzone) +local remotename=Request.warehouse.warehouse:GetName() +local ShippingLane=self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])] +CargoTransport=AI_CARGO_DISPATCHER_SHIP:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet,ShippingLane) +CargoTransport:SetHomeZone(self.portzone) +else +self:E(self.lid.."ERROR: Unknown transporttype!") +end +local pickupouter=200 +local pickupinner=0 +local deployouter=200 +local deployinner=0 +if Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER +or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then +pickupouter=1000 +pickupinner=20 +deployouter=1000 +deployinner=0 +else +pickupouter=200 +pickupinner=0 +if self.spawnzone.Radius~=nil then +pickupouter=self.spawnzone.Radius +pickupinner=20 +end +deployouter=200 +deployinner=0 +if self.spawnzone.Radius~=nil then +deployouter=Request.warehouse.spawnzone.Radius +deployinner=20 +end +end +CargoTransport:SetPickupRadius(pickupouter,pickupinner) +CargoTransport:SetDeployRadius(deployouter,deployinner) +Request.carriercargo={} +for _,carriergroup in pairs(TransportGroupSet:GetSetObjects())do +local asset=self:FindAssetInDB(carriergroup) +for _i,_carrierunit in pairs(carriergroup:GetUnits())do +local carrierunit=_carrierunit +Request.carriercargo[carrierunit:GetName()]={} +local cargobay=asset.cargobay[_i] +carrierunit:SetCargoBayWeightLimit(cargobay) +self:T2(self.lid..string.format("Cargo bay weight limit of carrier unit %s: %.1f kg.",carrierunit:GetName(),carrierunit:GetCargoBayFreeWeight())) +end +end +function CargoTransport:OnAfterPickedUp(From,Event,To,Carrier,PickupZone) +local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") +local text=string.format("Carrier group %s picked up at pickup zone %s.",Carrier:GetName(),PickupZone:GetName()) +warehouse:T(warehouse.lid..text) +end +function CargoTransport:OnAfterDeployed(From,Event,To,Carrier,DeployZone) +local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") +end +function CargoTransport:OnAfterHome(From,Event,To,Carrier,Coordinate,Speed,Height,HomeZone) +local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") +local text=string.format("Carrier group %s going home to zone %s.",Carrier:GetName(),HomeZone:GetName()) +warehouse:T(warehouse.lid..text) +end +function CargoTransport:OnAfterLoaded(From,Event,To,Carrier,Cargo,CarrierUnit,PickupZone) +local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") +local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone %s",Carrier:GetName(),Cargo:GetName(),CarrierUnit:GetName(),PickupZone:GetName()) +warehouse:T(warehouse.lid..text) +local group=Cargo:GetObject() +local request=warehouse:_GetRequestOfGroup(group,warehouse.pending) +table.insert(request.carriercargo[CarrierUnit:GetName()],warehouse:_GetNameWithOut(Cargo:GetName())) +end +function CargoTransport:OnAfterUnloaded(From,Event,To,Carrier,Cargo,CarrierUnit,DeployZone) +local warehouse=Carrier:GetState(Carrier,"WAREHOUSE") +local group=Cargo:GetObject() +local text=string.format("Cargo group %s was unloaded from carrier unit %s.",tostring(group:GetName()),tostring(CarrierUnit:GetName())) +warehouse:T(warehouse.lid..text) +warehouse:Arrived(group) +end +function CargoTransport:OnAfterBackHome(From,Event,To,Carrier) +local carrier=Carrier +local warehouse=carrier:GetState(carrier,"WAREHOUSE") +carrier:SmokeWhite() +local text=string.format("Carrier %s is back home at warehouse %s.",tostring(Carrier:GetName()),tostring(warehouse.warehouse:GetName())) +MESSAGE:New(text,5):ToAllIf(warehouse.Debug) +warehouse:I(warehouse.lid..text) +warehouse:__Arrived(1,Carrier) +end +CargoTransport:__Start(5) +end +function WAREHOUSE:onafterUnloaded(From,Event,To,group) +self:_DebugMessage(string.format("Cargo %s unloaded!",tostring(group:GetName())),5) +if group and group:IsAlive()then +if self.Debug then +group:SmokeWhite() +end +local speedmax=group:GetSpeedMax() +if group:IsGround()then +if speedmax>1 then +group:RouteGroundTo(self.spawnzone:GetRandomCoordinate(),speedmax*0.5,AI.Task.VehicleFormation.RANK,3) +else +self:Arrived(group) +end +elseif group:IsAir()then +self:Arrived(group) +elseif group:IsShip()then +self:Arrived(group) +end +else +self:E(self.lid..string.format("ERROR unloaded Cargo group is not alive!")) +end +end +function WAREHOUSE:onbeforeArrived(From,Event,To,group) +local asset=self:FindAssetInDB(group) +if asset then +if asset.flightgroup and not asset.arrived then +asset.arrived=true +return false +end +if asset.arrived==true then +return false +else +asset.arrived=true +return true +end +end +end +function WAREHOUSE:onafterArrived(From,Event,To,group) +if self.Debug then +group:SmokeOrange() +end +local request=self:_GetRequestOfGroup(group,self.pending) +if request then +local warehouse=request.warehouse +local istransport=self:_GroupIsTransport(group,request) +if istransport==true then +warehouse=self +elseif istransport==false then +warehouse=request.warehouse +else +self:E(self.lid..string.format("ERROR: Group %s is neither cargo nor transport",group:GetName())) +return +end +self:_DebugMessage(string.format("Group %s arrived at warehouse %s!",tostring(group:GetName()),warehouse.alias),5) +if group:IsGround()and group:GetSpeedMax()>1 then +group:RouteGroundTo(warehouse:GetCoordinate(),group:GetSpeedMax()*0.3,"Off Road") +end +self:T(self.lid.."Asset arrived at warehouse adding in 60 sec") +warehouse:__AddAsset(60,group) +end +end +function WAREHOUSE:onafterDelivered(From,Event,To,request) +if self.verbosity>=1 then +local text=string.format("Warehouse %s: All assets delivered to warehouse %s!",self.alias,request.warehouse.alias) +self:_InfoMessage(text,5) +end +if self.Debug then +self:_Fireworks(request.warehouse:GetCoordinate()) +end +self.delivered[request.uid]=true +end +function WAREHOUSE:onafterSelfRequest(From,Event,To,groupset,request) +self:_DebugMessage(string.format("Assets spawned at warehouse %s after self request!",self.alias)) +for _,_group in pairs(groupset:GetSetObjects())do +local group=_group +if self.Debug then +group:FlareGreen() +end +end +if self:IsAttacked()then +if self.autodefence then +for _,_group in pairs(groupset:GetSetObjects())do +local group=_group +local speedmax=group:GetSpeedMax() +if group:IsGround()and speedmax>1 and group:IsNotInZone(self.zone)then +group:RouteGroundTo(self.zone:GetRandomCoordinate(),0.8*speedmax,"Off Road") +end +end +end +table.insert(self.defending,request) +end +end +function WAREHOUSE:onafterAttacked(From,Event,To,Coalition,Country) +local text=string.format("Warehouse %s: We are under attack!",self.alias) +self:_InfoMessage(text) +if self.Debug then +self:GetCoordinate():SmokeOrange() +end +if self.autodefence then +local nground=self:GetNumberOfAssets(WAREHOUSE.Descriptor.CATEGORY,Group.Category.GROUND) +local text=string.format("Warehouse auto defence activated.\n") +if nground>0 then +text=text..string.format("Deploying all %d ground assets.",nground) +self:AddRequest(self,WAREHOUSE.Descriptor.CATEGORY,Group.Category.GROUND,WAREHOUSE.Quantity.ALL,nil,nil,0,"AutoDefence") +else +text=text..string.format("No ground assets currently available.") +end +self:_InfoMessage(text) +else +local text=string.format("Warehouse auto defence inactive.") +self:I(self.lid..text) +end +end +function WAREHOUSE:onafterDefeated(From,Event,To) +local text=string.format("Warehouse %s: Enemy attack was defeated!",self.alias) +self:_InfoMessage(text) +if self.Debug then +self:GetCoordinate():SmokeGreen() +end +if self.autodefence then +for _,request in pairs(self.defending)do +for _,_group in pairs(request.cargogroupset:GetSetObjects())do +local group=_group +local speed=group:GetSpeedMax() +if group:IsGround()and speed>1 then +group:RouteGroundTo(self:GetCoordinate(),speed*0.3) +end +self:__AddAsset(60,group) +end +end +self.defending=nil +self.defending={} +end +end +function WAREHOUSE:onafterRespawn(From,Event,To) +local text=string.format("Respawning warehouse %s",self.alias) +self:_InfoMessage(text) +self.warehouse:ReSpawn() +end +function WAREHOUSE:onbeforeChangeCountry(From,Event,To,Country) +local currentCountry=self:GetCountry() +local text=string.format("Warehouse %s: request to change country %d-->%d",self.alias,currentCountry,Country) +self:_DebugMessage(text,10) +if currentCountry~=Country then +return true +end +return false +end +function WAREHOUSE:onafterChangeCountry(From,Event,To,Country) +local CoalitionOld=self:GetCoalition() +self.warehouse:ReSpawn(Country) +local CoalitionNew=self:GetCoalition() +self.queue=nil +self.queue={} +if self.airbasename then +local airbase=AIRBASE:FindByName(self.airbasename) +local airbaseCoalition=airbase:GetCoalition() +if CoalitionNew==airbaseCoalition then +self.airbase=airbase +else +self.airbase=nil +end +end +if self.Debug then +if CoalitionNew==coalition.side.RED then +self:GetCoordinate():SmokeRed() +elseif CoalitionNew==coalition.side.BLUE then +self:GetCoordinate():SmokeBlue() +end +end +end +function WAREHOUSE:onbeforeCaptured(From,Event,To,Coalition,Country) +self:ChangeCountry(Country) +end +function WAREHOUSE:onafterCaptured(From,Event,To,Coalition,Country) +local text=string.format("Warehouse %s: We were captured by enemy coalition (side=%d)!",self.alias,Coalition) +self:_InfoMessage(text) +end +function WAREHOUSE:onafterAirbaseCaptured(From,Event,To,Coalition) +local text=string.format("Warehouse %s: Our airbase %s was captured by the enemy (coalition=%d)!",self.alias,self.airbasename,Coalition) +self:_InfoMessage(text) +if self.Debug then +if Coalition==coalition.side.RED then +self.airbase:GetCoordinate():SmokeRed() +elseif Coalition==coalition.side.BLUE then +self.airbase:GetCoordinate():SmokeBlue() +end +end +self.airbase=nil +end +function WAREHOUSE:onafterAirbaseRecaptured(From,Event,To,Coalition) +local text=string.format("Warehouse %s: We recaptured our airbase %s from the enemy (coalition=%d)!",self.alias,self.airbasename,Coalition) +self:_InfoMessage(text) +self.airbase=AIRBASE:FindByName(self.airbasename) +if self.Debug then +if Coalition==coalition.side.RED then +self.airbase:GetCoordinate():SmokeRed() +elseif Coalition==coalition.side.BLUE then +self.airbase:GetCoordinate():SmokeBlue() +end +end +end +function WAREHOUSE:onafterRunwayDestroyed(From,Event,To) +local text=string.format("Warehouse %s: Runway %s destroyed!",self.alias,self.airbasename) +self:_InfoMessage(text) +self.runwaydestroyed=timer.getAbsTime() +return self +end +function WAREHOUSE:onafterRunwayRepaired(From,Event,To) +local text=string.format("Warehouse %s: Runway %s repaired!",self.alias,self.airbasename) +self:_InfoMessage(text) +self.runwaydestroyed=nil +return self +end +function WAREHOUSE:onafterAssetSpawned(From,Event,To,group,asset,request) +local text=string.format("Asset %s from request id=%d was spawned!",asset.spawngroupname,request.uid) +self:T(self.lid..text) +asset.spawned=true +asset.spawngroupname=group:GetName() +self:_DeleteStockItem(asset) +if asset.iscargo==true then +request.cargogroupset=request.cargogroupset or SET_GROUP:New() +request.cargogroupset:AddGroup(group) +else +request.transportgroupset=request.transportgroupset or SET_GROUP:New() +request.transportgroupset:AddGroup(group) +end +group:SetState(group,"WAREHOUSE",self) +local n=0 +for _,_asset in pairs(request.assets)do +local assetitem=_asset +self:T(self.lid..string.format("Asset %s spawned %s as %s",assetitem.templatename,tostring(assetitem.spawned),tostring(assetitem.spawngroupname))) +if assetitem.spawned then +n=n+1 +else +end +end +if n==request.nasset+request.ntransport then +self:T(self.lid..string.format("All assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned. Calling RequestSpawned",n,request.nasset,request.ntransport,request.uid)) +self:RequestSpawned(request,request.cargogroupset,request.transportgroupset) +else +self:T(self.lid..string.format("Not all assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned YET",n,request.nasset,request.ntransport,request.uid)) +end +end +function WAREHOUSE:onafterAssetDead(From,Event,To,asset,request) +if asset and request then +local text=string.format("Asset %s from request id=%d is dead!",asset.templatename,request.uid) +self:T(self.lid..text) +local groupname=asset.spawngroupname +local NoTriggerEvent=true +if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then +if request.cargogroupset then +request.cargogroupset:Remove(groupname,NoTriggerEvent) +self:T(self.lid..string.format("Removed selfpropelled cargo %s: ncargo=%d.",groupname,request.cargogroupset:Count())) +else +self:E(self.lid..string.format("ERROR: cargogroupset is nil for request ID=%s!",tostring(request.uid))) +end +else +local istransport=not asset.iscargo +if istransport==true then +request.transportgroupset:Remove(groupname,NoTriggerEvent) +self:T(self.lid..string.format("Removed transport %s: ntransport=%d",groupname,request.transportgroupset:Count())) +elseif istransport==false then +request.cargogroupset:Remove(groupname,NoTriggerEvent) +self:T(self.lid..string.format("Removed transported cargo %s outside carrier: ncargo=%d",groupname,request.cargogroupset:Count())) +else +end +end +else +self:E(self.lid.."ERROR: Asset and/or Request is nil in onafterAssetDead") +end +end +function WAREHOUSE:onafterDestroyed(From,Event,To) +local text=string.format("Warehouse %s was destroyed! Assets lost %d. Respawn=%s",self.alias,#self.stock,tostring(self.respawnafterdestroyed)) +self:_InfoMessage(text) +if self.respawnafterdestroyed then +if self.respawndelay then +self:Pause() +self:__Respawn(self.respawndelay) +else +self:Respawn() +end +else +for k,_ in pairs(self.queue)do +self.queue[k]=nil +end +for k,_ in pairs(self.stock)do +end +for k=#self.stock,1,-1 do +self.stock[k]=nil +end +end +end +function WAREHOUSE:onafterSave(From,Event,To,path,filename) +local function _savefile(filename,data) +local f=assert(io.open(filename,"wb")) +f:write(data) +f:close() +end +filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias) +if path~=nil then +filename=path.."\\"..filename +end +local text=string.format("Saving warehouse assets to file %s",filename) +MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report) +self:I(self.lid..text) +local warehouseassets="" +warehouseassets=warehouseassets..string.format("coalition=%d\n",self:GetCoalition()) +warehouseassets=warehouseassets..string.format("country=%d\n",self:GetCountry()) +for _,_asset in pairs(self.stock)do +local asset=_asset +local assetstring="" +for key,value in pairs(asset)do +if key=="templatename"or key=="attribute"or key=="cargobay"or key=="weight"or key=="loadradius"or key=="livery"or key=="skill"or key=="assignment"then +local name +if type(value)=="table"then +name=string.format("%s=%s;",key,value[1]) +else +name=string.format("%s=%s;",key,value) +end +assetstring=assetstring..name +end +self:I(string.format("Loaded asset: %s",assetstring)) +end +warehouseassets=warehouseassets..assetstring.."\n" +end +_savefile(filename,warehouseassets) +end +function WAREHOUSE:onbeforeLoad(From,Event,To,path,filename) +local function _fileexists(name) +local f=io.open(name,"r") +if f~=nil then +io.close(f) +return true +else +return false +end +end +filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias) +if path~=nil then +filename=path.."\\"..filename +end +local exists=_fileexists(filename) +if exists then +return true +else +self:_ErrorMessage(string.format("ERROR: file %s does not exist! Cannot load assets.",filename),60) +return false +end +end +function WAREHOUSE:onafterLoad(From,Event,To,path,filename) +local function _loadfile(filename) +local f=assert(io.open(filename,"rb")) +local data=f:read("*all") +f:close() +return data +end +filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias) +if path~=nil then +filename=path.."\\"..filename +end +local text=string.format("Loading warehouse assets from file %s",filename) +MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report) +self:I(self.lid..text) +local data=_loadfile(filename) +local assetdata=UTILS.Split(data,"\n") +local Coalition +local Country +local assets={} +for _,asset in pairs(assetdata)do +local descriptors=UTILS.Split(asset,";") +local asset={} +local isasset=false +for _,descriptor in pairs(descriptors)do +local keyval=UTILS.Split(descriptor,"=") +if#keyval==2 then +if keyval[1]=="coalition"then +Coalition=tonumber(keyval[2]) +elseif keyval[1]=="country"then +Country=tonumber(keyval[2]) +else +isasset=true +local key=keyval[1] +local val=keyval[2] +if val=="nil"then +val=nil +end +if key=="cargobay"or key=="weight"or key=="loadradius"then +asset[key]=tonumber(val) +else +asset[key]=val +end +end +end +end +if isasset then +table.insert(assets,asset) +end +end +if Country~=self:GetCountry()then +self:T(self.lid..string.format("Changing warehouse country %d-->%d on loading assets.",self:GetCountry(),Country)) +self:ChangeCountry(Country) +end +for _,_asset in pairs(assets)do +local asset=_asset +local group=GROUP:FindByName(asset.templatename) +if group then +self:AddAsset(group,1,asset.attribute,asset.cargobay,asset.weight,asset.loadradius,asset.skill,asset.livery,asset.assignment) +else +self:E(string.format("ERROR: Group %s doest not exit. Cannot be loaded as asset.",tostring(asset.templatename))) +end +end +end +function WAREHOUSE:_SpawnAssetRequest(Request) +self:F2({requestUID=Request.uid}) +local cargoassets=Request.cargoassets +local Parking={} +if Request.cargocategory==Group.Category.AIRPLANE or Request.cargocategory==Group.Category.HELICOPTER then +Parking=self:_FindParkingForAssets(self.airbase,cargoassets)or{} +end +local UnControlled=true +for i=1,#cargoassets do +local asset=cargoassets[i] +if not asset.spawned then +asset.iscargo=true +asset.rid=Request.uid +local _alias=asset.spawngroupname +Request.assets[asset.uid]=asset +local _group=nil +if asset.category==Group.Category.GROUND then +_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.spawnzone,Request.lateActivation) +elseif asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then +if Parking[asset.uid]then +_group=self:_SpawnAssetAircraft(_alias,asset,Request,Parking[asset.uid],UnControlled,Request.lateActivation) +else +_group=self:_SpawnAssetAircraft(_alias,asset,Request,nil,UnControlled,Request.lateActivation) +end +elseif asset.category==Group.Category.TRAIN then +if self.rail then +_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.spawnzone,Request.lateActivation) +end +elseif asset.category==Group.Category.SHIP then +_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.portzone,Request.lateActivation) +else +self:E(self.lid.."ERROR: Unknown asset category!") +end +if _group then +self:__AssetSpawned(0.01,_group,asset,Request) +end +end +end +end +function WAREHOUSE:_SpawnAssetGroundNaval(alias,asset,request,spawnzone,lateactivated) +if asset and(asset.category==Group.Category.GROUND or asset.category==Group.Category.SHIP or asset.category==Group.Category.TRAIN)then +local template=self:_SpawnAssetPrepareTemplate(asset,alias) +template.route.points[1]={} +local coord=spawnzone:GetRandomCoordinate() +if asset.category==Group.Category.TRAIN then +coord=self.rail +end +for i=1,#template.units do +local unit=template.units[i] +local SX=unit.x or 0 +local SY=unit.y or 0 +local BX=asset.template.route.points[1].x +local BY=asset.template.route.points[1].y +local TX=coord.x+(SX-BX) +local TY=coord.z+(SY-BY) +template.units[i].x=TX +template.units[i].y=TY +if asset.livery then +unit.livery_id=asset.livery +end +if asset.skill then +unit.skill=asset.skill +end +end +template.lateActivation=lateactivated +template.route.points[1].x=coord.x +template.route.points[1].y=coord.z +template.x=coord.x +template.y=coord.z +template.alt=coord.y +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(template.units) +end +local group=_DATABASE:Spawn(template) +return group +end +return nil +end +function WAREHOUSE:_SpawnAssetAircraft(alias,asset,request,parking,uncontrolled,lateactivated) +if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then +local template=self:_SpawnAssetPrepareTemplate(asset,alias) +local _type=COORDINATE.WaypointType.TakeOffParking +local _action=COORDINATE.WaypointAction.FromParkingArea +if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then +_type=COORDINATE.WaypointType.TakeOffParkingHot +_action=COORDINATE.WaypointAction.FromParkingAreaHot +uncontrolled=false +end +local airstart=asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TurningPoint or false +if airstart then +_type=COORDINATE.WaypointType.TurningPoint +_action=COORDINATE.WaypointAction.TurningPoint +uncontrolled=false +end +if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then +if request.toself then +local coord=self.airbase:GetCoordinate() +if airstart then +coord:SetAltitude(math.random(1000,2000)) +end +local wp=coord:WaypointAir("RADIO",_type,_action,0,false,self.airbase,{},"Parking") +template.route.points={wp} +else +template.route.points=self:_GetFlightplan(asset,self.airbase,request.warehouse.airbase) +end +else +template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action,0,true,self.airbase,nil,"Spawnpoint") +end +local AirbaseID=self.airbase:GetID() +local AirbaseCategory=self:GetAirbaseCategory() +if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then +else +if parking and#parking<#template.units and not airstart then +local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.",#parking,#template.units) +self:_DebugMessage(text) +return nil +end +end +for i=1,#template.units do +local unit=template.units[i] +if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then +local coord=self.airbase:GetCoordinate() +unit.x=coord.x +unit.y=coord.z +unit.alt=coord.y +if airstart then +unit.alt=math.random(1000,2000) +end +unit.parking_id=nil +unit.parking=nil +else +local coord=nil +local terminal=nil +if airstart then +coord=self.airbase:GetCoordinate():SetAltitude(math.random(1000,2000)) +else +coord=parking[i].Coordinate +terminal=parking[i].TerminalID +end +if self.Debug and terminal then +local text=string.format("Spawnplace unit %s terminal %d.",unit.name,terminal) +coord:MarkToAll(text) +env.info(text) +end +unit.x=coord.x +unit.y=coord.z +unit.alt=coord.y +unit.parking_id=nil +unit.parking=terminal +end +if asset.livery then +unit.livery_id=asset.livery +end +if asset.skill then +unit.skill=asset.skill +end +if asset.payload then +unit.payload=asset.payload.pylons +end +if asset.modex then +unit.onboard_num=asset.modex[i] +end +if asset.callsign then +unit.callsign=asset.callsign[i] +end +end +template.x=template.units[1].x +template.y=template.units[1].y +template.uncontrolled=uncontrolled +self:T2({airtemplate=template}) +local group=_DATABASE:Spawn(template) +return group +end +return nil +end +function WAREHOUSE:_SpawnAssetPrepareTemplate(asset,alias) +local template=UTILS.DeepCopy(asset.template) +template.name=alias +template.CoalitionID=self:GetCoalition() +template.CountryID=self:GetCountry() +template.groupId=nil +template.lateActivation=false +if asset.missionTask then +self:T(self.lid..string.format("Setting mission task to %s",tostring(asset.missionTask))) +template.task=asset.missionTask +end +template.route={} +template.route.routeRelativeTOT=true +template.route.points={} +for i=1,#template.units do +local unit=template.units[i] +unit.unitId=nil +unit.name=string.format("%s-%02d",template.name,i) +end +return template +end +function WAREHOUSE:_RouteGround(group,request) +if group and group:IsAlive()then +local _speed=group:GetSpeedMax()*0.7 +local Waypoints={} +local hasoffroad=self:HasConnectionOffRoad(request.warehouse,self.Debug) +if hasoffroad then +local remotename=request.warehouse.warehouse:GetName() +local path=self.offroadpaths[remotename][math.random(#self.offroadpaths[remotename])] +for i=1,#path do +local coord=path[i] +local Waypoint=coord:WaypointGround(_speed,"Off Road") +table.insert(Waypoints,Waypoint) +end +else +Waypoints=group:TaskGroundOnRoad(request.warehouse.road,_speed,"Off Road",false,self.road) +local FromWP=group:GetCoordinate():WaypointGround(_speed,"Off Road") +table.insert(Waypoints,1,FromWP) +end +for n,wp in ipairs(Waypoints)do +local tf=self:_SimpleTaskFunctionWP("warehouse:_PassingWaypoint",group,n,#Waypoints) +group:SetTaskWaypoint(wp,tf) +end +group:Route(Waypoints,1) +group:OptionROEReturnFire() +group:OptionAlarmStateGreen() +end +end +function WAREHOUSE:_RouteNaval(group,request) +if group and group:IsAlive()then +local _speed=group:GetSpeedMax()*0.8 +local remotename=request.warehouse.warehouse:GetName() +local lane=self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])] +if lane then +local Waypoints={} +for i=1,#lane do +local coord=lane[i] +local Waypoint=coord:WaypointGround(_speed) +table.insert(Waypoints,Waypoint) +end +local TaskFunction=self:_SimpleTaskFunction("warehouse:_Arrived",group) +local Waypoint=Waypoints[#Waypoints] +group:SetTaskWaypoint(Waypoint,TaskFunction) +group:Route(Waypoints,1) +group:OptionROEReturnFire() +else +self:E(self.lid..string.format("ERROR: No shipping lane defined for Naval asset!")) +end +end +end +function WAREHOUSE:_RouteAir(aircraft) +if aircraft and aircraft:IsAlive()~=nil then +self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s",aircraft:GetName(),tostring(aircraft:IsAlive()))) +if self.flightcontrol then +local fg=FLIGHTGROUP:New(aircraft) +fg:SetReadyForTakeoff(true) +else +aircraft:StartUncontrolled(math.random(60)) +end +self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s (after start command)",aircraft:GetName(),tostring(aircraft:IsAlive()))) +aircraft:OptionROEReturnFire() +aircraft:OptionROTPassiveDefense() +else +self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!",tostring(aircraft:GetName()),tostring(aircraft:IsAlive()))) +end +end +function WAREHOUSE:_RouteTrain(Group,Coordinate,Speed) +if Group and Group:IsAlive()then +local _speed=Speed or Group:GetSpeedMax()*0.6 +local Waypoints=Group:TaskGroundOnRailRoads(Coordinate,Speed) +local TaskFunction=self:_SimpleTaskFunction("warehouse:_Arrived",Group) +local Waypoint=Waypoints[#Waypoints] +Group:SetTaskWaypoint(Waypoint,TaskFunction) +Group:Route(Waypoints,1) +end +end +function WAREHOUSE:_Arrived(group) +self:_DebugMessage(string.format("Group %s arrived!",tostring(group:GetName()))) +if group then +self:__Arrived(1,group) +end +end +function WAREHOUSE:_PassingWaypoint(group,n,N) +self:T(self.lid..string.format("Group %s passing waypoint %d of %d!",tostring(group:GetName()),n,N)) +if n==N then +self:__Arrived(1,group) +end +end +function WAREHOUSE:GetAssetByID(id) +if id then +return _WAREHOUSEDB.Assets[id] +else +return nil +end +end +function WAREHOUSE:GetAssetByName(GroupName) +local name=self:_GetNameWithOut(GroupName) +local _,aid,_=self:_GetIDsFromGroup(GROUP:FindByName(name)) +if aid then +return _WAREHOUSEDB.Assets[aid] +else +return nil +end +end +function WAREHOUSE:GetRequestByID(id) +if id then +for _,_request in pairs(self.queue)do +local request=_request +if request.uid==id then +return request,true +end +end +for _,_request in pairs(self.pending)do +local request=_request +if request.uid==id then +return request,false +end +end +end +return nil,nil +end +function WAREHOUSE:_OnEventBirth(EventData) +self:T3(self.lid..string.format("Warehouse %s (id=%s) captured event birth!",self.alias,self.uid)) +if EventData and EventData.IniGroup then +local group=EventData.IniGroup +local wid,aid,rid=self:_GetIDsFromGroup(group) +if wid==self.uid then +local asset=self:GetAssetByID(aid) +local request=self:GetRequestByID(rid) +if asset and request then +self:T(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s",self.alias,request.uid,asset.uid,EventData.IniUnitName,tostring(asset.spawned))) +request.born=true +else +self:E(self.lid..string.format("ERROR: Either asset AID=%s or request RID=%s are nil in event birth of unit %s",tostring(aid),tostring(rid),tostring(EventData.IniUnitName))) +end +else +end +end +end +function WAREHOUSE:_OnEventEngineStartup(EventData) +self:T3(self.lid..string.format("Warehouse %s captured event engine startup!",self.alias)) +if EventData and EventData.IniGroup then +local group=EventData.IniGroup +local wid,aid,rid=self:_GetIDsFromGroup(group) +if wid==self.uid then +self:T(self.lid..string.format("Warehouse %s captured event engine startup of its asset unit %s.",self.alias,EventData.IniUnitName)) +end +end +end +function WAREHOUSE:_OnEventTakeOff(EventData) +self:T3(self.lid..string.format("Warehouse %s captured event takeoff!",self.alias)) +if EventData and EventData.IniGroup then +local group=EventData.IniGroup +local wid,aid,rid=self:_GetIDsFromGroup(group) +if wid==self.uid then +self:T(self.lid..string.format("Warehouse %s captured event takeoff of its asset unit %s.",self.alias,EventData.IniUnitName)) +end +end +end +function WAREHOUSE:_OnEventLanding(EventData) +self:T3(self.lid..string.format("Warehouse %s captured event landing!",self.alias)) +if EventData and EventData.IniGroup then +local group=EventData.IniGroup +local wid,aid,rid=self:_GetIDsFromGroup(group) +if wid~=nil and wid==self.uid then +self:T(self.lid..string.format("Warehouse %s captured event landing of its asset unit %s.",self.alias,EventData.IniUnitName)) +end +end +end +function WAREHOUSE:_OnEventEngineShutdown(EventData) +self:T3(self.lid..string.format("Warehouse %s captured event engine shutdown!",self.alias)) +if EventData and EventData.IniGroup then +local group=EventData.IniGroup +local wid,aid,rid=self:_GetIDsFromGroup(group) +if wid==self.uid then +self:T(self.lid..string.format("Warehouse %s captured event engine shutdown of its asset unit %s.",self.alias,EventData.IniUnitName)) +end +end +end +function WAREHOUSE:_OnEventArrived(EventData) +if EventData and EventData.IniUnit then +local unit=EventData.IniUnit +if unit and unit:IsAlive()==true and unit:InAir()==false then +local group=EventData.IniGroup +local wid,aid,rid=self:_GetIDsFromGroup(group) +if wid~=nil and aid~=nil and rid~=nil then +if self.uid==wid then +local request=self:_GetRequestOfGroup(group,self.pending) +if request then +local istransport=self:_GroupIsTransport(group,request) +local closest=group:GetCoordinate():GetClosestAirbase() +local rightairbase=closest:GetName()==request.warehouse:GetAirbase():GetName() +if istransport==false and rightairbase then +local nunits=#group:GetUnits() +local dt=10*(nunits-1)+1 +if self.verbosity>=1 then +local text=string.format("Air asset group %s from warehouse %s arrived at its destination. Trigger Arrived event in %d sec",group:GetName(),self.alias,dt) +self:_InfoMessage(text) +end +self:__Arrived(dt,group) +end +end +end +else +self:T3(string.format("Group that arrived did not belong to a warehouse. Warehouse ID=%s, Asset ID=%s, Request ID=%s.",tostring(wid),tostring(aid),tostring(rid))) +end +end +end +end +function WAREHOUSE:_OnEventCrashOrDead(EventData) +self:T3(self.lid..string.format("Warehouse %s captured event dead or crash!",self.alias)) +if EventData then +if EventData.IniUnitName then +local warehousename=self.warehouse:GetName() +if EventData.IniUnitName==warehousename then +self:_DebugMessage(string.format("Warehouse %s alias %s was destroyed!",warehousename,self.alias)) +self:Destroyed() +end +if self.airbase and self.airbasename and self.airbasename==EventData.IniUnitName then +if self:IsRunwayOperational()then +self:RunwayDestroyed() +else +self.runwaydestroyed=timer.getAbsTime() +end +end +end +self:T2(self.lid..string.format("Warehouse %s captured event dead or crash or unit %s",self.alias,tostring(EventData.IniUnitName))) +if EventData.IniGroup then +local group=EventData.IniGroup +local wid,aid,rid=self:_GetIDsFromGroup(group) +if wid==self.uid then +self:T(self.lid..string.format("Warehouse %s captured event dead or crash of its asset unit %s",self.alias,EventData.IniUnitName)) +for _,request in pairs(self.pending)do +local request=request +if request.uid==rid then +self:_UnitDead(EventData.IniUnit,EventData.IniGroup,request) +end +end +end +end +end +end +function WAREHOUSE:_UnitDead(deadunit,deadgroup,request) +local opsgroup=_DATABASE:FindOpsGroup(deadgroup) +if opsgroup then +return nil +end +local nalive=deadgroup:CountAliveUnits() +local groupdead=false +if nalive>0 then +groupdead=false +else +groupdead=true +end +local asset=self:FindAssetInDB(deadgroup) +local unitname=self:_GetNameWithOut(deadunit) +local groupname=self:_GetNameWithOut(deadgroup) +if groupdead then +self:T(self.lid..string.format("Group %s (transport=%s) is dead!",groupname,tostring(self:_GroupIsTransport(deadgroup,request)))) +if self.Debug then +deadgroup:SmokeWhite() +end +self:AssetDead(asset,request) +end +local NoTriggerEvent=true +if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then +if not asset.iscargo then +local cargogroupnames=request.carriercargo[unitname] +if cargogroupnames then +for _,cargoname in pairs(cargogroupnames)do +request.cargogroupset:Remove(cargoname,NoTriggerEvent) +self:T(self.lid..string.format("Removed transported cargo %s inside dead carrier %s: ncargo=%d",cargoname,unitname,request.cargogroupset:Count())) +end +end +else +self:E(self.lid..string.format("ERROR: Group %s is neither cargo nor transport!",deadgroup:GetName())) +end +end +end +function WAREHOUSE:_OnEventBaseCaptured(EventData) +self:T3(self.lid..string.format("Warehouse %s captured event base captured!",self.alias)) +if self.airbasename==nil then +return +end +if EventData and EventData.Place then +local airbase=EventData.Place +if EventData.PlaceName==self.airbasename then +local NewCoalitionAirbase=airbase:GetCoalition() +self:T(self.lid..string.format("Airbase of warehouse %s (coalition ID=%d) was captured! New owner coalition ID=%d.",self.alias,self:GetCoalition(),NewCoalitionAirbase)) +if self.airbase==nil then +if NewCoalitionAirbase==self:GetCoalition()then +self:AirbaseRecaptured(NewCoalitionAirbase) +end +else +if NewCoalitionAirbase~=self:GetCoalition()then +self:AirbaseCaptured(NewCoalitionAirbase) +end +end +end +end +end +function WAREHOUSE:_OnEventMissionEnd(EventData) +self:T3(self.lid..string.format("Warehouse %s captured event mission end!",self.alias)) +if self.autosave then +self:Save(self.autosavepath,self.autosavefile) +end +end +function WAREHOUSE:_CheckConquered() +local coord=self.zone:GetCoordinate() +local radius=self.zone:GetRadius() +local gotunits,_,_,units,_,_=coord:ScanObjects(radius,true,false,false) +local Nblue=0 +local Nred=0 +local Nneutral=0 +local CountryBlue=nil +local CountryRed=nil +local CountryNeutral=nil +if gotunits then +for _,_unit in pairs(units)do +local unit=_unit +local distance=coord:Get2DDistance(unit:GetCoord()) +if unit:IsGround()and unit:IsAlive()and distance<=radius then +local _coalition=unit:GetCoalition() +local _country=unit:GetCountry() +self:T2(self.lid..string.format("Unit %s in warehouse zone of radius=%d m. Coalition=%d, country=%d. Distance = %d m.",unit:GetName(),radius,_coalition,_country,distance)) +if _coalition==coalition.side.BLUE then +Nblue=Nblue+1 +CountryBlue=_country +elseif _coalition==coalition.side.RED then +Nred=Nred+1 +CountryRed=_country +else +Nneutral=Nneutral+1 +CountryNeutral=_country +end +end +end +end +self:T(self.lid..string.format("Ground troops in warehouse zone: blue=%d, red=%d, neutral=%d",Nblue,Nred,Nneutral)) +local newcoalition=self:GetCoalition() +local newcountry=self:GetCountry() +if Nblue>0 and Nred==0 and Nneutral==0 then +newcoalition=coalition.side.BLUE +newcountry=CountryBlue +elseif Nblue==0 and Nred>0 and Nneutral==0 then +newcoalition=coalition.side.RED +newcountry=CountryRed +elseif Nblue==0 and Nred==0 and Nneutral>0 then +end +if self:IsAttacked()and newcoalition~=self:GetCoalition()then +self:Captured(newcoalition,newcountry) +return +end +if self:GetCoalition()==coalition.side.BLUE then +if self:IsRunning()and Nred>0 then +self:Attacked(coalition.side.RED,CountryRed) +end +if self:IsAttacked()and Nred==0 then +self:Defeated() +end +elseif self:GetCoalition()==coalition.side.RED then +if self:IsRunning()and Nblue>0 then +self:Attacked(coalition.side.BLUE,CountryBlue) +end +if self:IsAttacked()and Nblue==0 then +self:Defeated() +end +elseif self:GetCoalition()==coalition.side.NEUTRAL then +if self:IsRunning()and Nred>0 then +self:Attacked(coalition.side.RED,CountryRed) +elseif self:IsRunning()and Nblue>0 then +self:Attacked(coalition.side.BLUE,CountryBlue) +end +end +end +function WAREHOUSE:_CheckAirbaseOwner() +if self.airbasename then +local airbase=AIRBASE:FindByName(self.airbasename) +local airbasecurrentcoalition=airbase:GetCoalition() +if self.airbase then +if self:GetCoalition()~=airbasecurrentcoalition then +self.airbase=nil +end +else +if self:GetCoalition()==airbasecurrentcoalition then +self.airbase=airbase +end +end +end +end +function WAREHOUSE:_CheckRequestConsistancy(queue) +self:T3(self.lid..string.format("Number of queued requests = %d",#queue)) +local invalid={} +for _,_request in pairs(queue)do +local request=_request +self:T2(self.lid..string.format("Checking request id=%d.",request.uid)) +local valid=true +if request.nasset==0 then +self:E(self.lid..string.format("ERROR: INVALID request. Request for zero assets not possible. Can happen when, e.g. \"all\" ground assets are requests but none in stock.")) +valid=false +end +if self:GetCoalition()~=request.warehouse:GetCoalition()then +self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is of wrong coalition! Own coalition %s != %s of requesting warehouse.",self:GetCoalitionName(),request.warehouse:GetCoalitionName())) +valid=false +end +if request.warehouse:IsStopped()then +self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is stopped!")) +valid=false +end +if request.warehouse:IsDestroyed()and not self.respawnafterdestroyed then +self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is destroyed!")) +valid=false +end +if valid==false then +self:E(self.lid..string.format("Got invalid request id=%d.",request.uid)) +table.insert(invalid,request) +else +self:T3(self.lid..string.format("Got valid request id=%d.",request.uid)) +end +end +for _,_request in pairs(invalid)do +self:E(self.lid..string.format("Deleting INVALID request id=%d.",_request.uid)) +self:_DeleteQueueItem(_request,self.queue) +end +end +function WAREHOUSE:_CheckRequestValid(request) +local _assets,_nassets,_enough=self:_FilterStock(self.stock,request.assetdesc,request.assetdescval,request.nasset) +if#_assets==0 then +return true +end +local nasset=request.nasset +if type(request.nasset)=="string"then +nasset=self:_QuantityRel2Abs(request.nasset,_nassets) +end +local text=string.format("Request valid? Number of assets: requested=%s=%d, selected=%d, total=%d, enough=%s.",tostring(request.nasset),nasset,#_assets,_nassets,tostring(_enough)) +self:T(text) +local asset=_assets[1] +local asset_plane=asset.category==Group.Category.AIRPLANE +local asset_helo=asset.category==Group.Category.HELICOPTER +local asset_ground=asset.category==Group.Category.GROUND +local asset_train=asset.category==Group.Category.TRAIN +local asset_naval=asset.category==Group.Category.SHIP +local asset_air=asset_helo or asset_plane +local valid=true +local requestcategory=request.warehouse:GetAirbaseCategory() +if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then +if asset_air then +if asset_plane then +if requestcategory==Airbase.Category.HELIPAD or self:GetAirbaseCategory()==Airbase.Category.HELIPAD then +self:E("ERROR: Incorrect request. Asset airplane requested but warehouse or requestor is HELIPAD/FARP!") +valid=false +end +elseif asset_helo then +if self:GetAirbaseCategory()==-1 or requestcategory==-1 then +self:E("ERROR: Incorrect request. Helos need a AIRBASE/HELIPAD/SHIP as home/destination base!") +valid=false +end +end +if self.airbase==nil or request.airbase==nil then +self:E("ERROR: Incorrect request. Either warehouse or requesting warehouse does not have any kind of airbase!") +valid=false +else +local termtype_dep=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory()) +local termtype_des=asset.terminalType or self:_GetTerminal(asset.attribute,request.warehouse:GetAirbaseCategory()) +local np_departure=self.airbase:GetParkingSpotsNumber(termtype_dep) +local np_destination=request.airbase:GetParkingSpotsNumber(termtype_des) +self:T(string.format("Asset attribute = %s, DEPARTURE: terminal type = %d, spots = %d, DESTINATION: terminal type = %d, spots = %d",asset.attribute,termtype_dep,np_departure,termtype_des,np_destination)) +if np_departure0 then +local asset=_assets[1] +_assetattribute=_assets[1].attribute +_assetcategory=_assets[1].category +_assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false +if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then +if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then +if self.airbase.storage then +local nS=self.airbase.storage:GetAmount(asset.unittype) +local nA=asset.nunits*request.nasset +if nS NOT enough to spawn the requested %d asset units (%d groups)", +self.alias,nS,asset.unittype,nA,request.nasset) +self:_InfoMessage(text,5) +return false +end +end +if self:IsRunwayOperational()or _assetairstart then +if _assetairstart then +else +local Parking=self:_FindParkingForAssets(self.airbase,_assets) +if Parking==nil then +local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.",self.alias) +self:_InfoMessage(text,5) +return false +end +end +else +local text=string.format("Warehouse %s: Request denied! Runway is still destroyed",self.alias) +self:_InfoMessage(text,5) +return false +end +else +local text=string.format("Warehouse %s: Request denied! No airbase",self.alias) +self:_InfoMessage(text,5) +return false +end +end +request.cargoassets=_assets +end +if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then +_transports=self:_GetTransportsForAssets(request) +if#_transports>0 then +local _transportattribute=_transports[1].attribute +local _transportcategory=_transports[1].category +if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then +if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then +if self:IsRunwayOperational()then +local Parking=self:_FindParkingForAssets(self.airbase,_transports) +if Parking==nil then +local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.",self.alias) +self:_InfoMessage(text,5) +return false +end +else +local text=string.format("Warehouse %s: Request denied! Runway is still destroyed",self.alias) +self:_InfoMessage(text,5) +return false +end +else +local text=string.format("Warehouse %s: Request denied! No airbase currently!",self.alias) +self:_InfoMessage(text,5) +return false +end +end +else +local text=string.format("Warehouse %s: Request denied! Not enough transport carriers available at the moment.",self.alias) +self:_InfoMessage(text,5) +return false +end +else +if _assetcategory==Group.Category.GROUND then +local dist=self.warehouse:GetCoordinate():Get2DDistance(self.spawnzone:GetCoordinate()) +if dist>self.spawnzonemaxdist then +local text=string.format("Warehouse %s: Request denied! Not close enough to spawn zone. Distance = %d m. We need to be at least within %d m range to spawn.",self.alias,dist,self.spawnzonemaxdist) +self:_InfoMessage(text,5) +return false +end +elseif _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then +end +end +request.cargoassets=_assets +request.cargoattribute=_assets[1].attribute +request.cargocategory=_assets[1].category +request.nasset=#_assets +local text=string.format("Selected cargo assets, attibute=%s, category=%d:\n",request.cargoattribute,request.cargocategory) +for _i,_asset in pairs(_assets)do +local asset=_asset +text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d",_i,asset.templatename,asset.unittype,asset.category,asset.nunits) +end +self:T(self.lid..text) +if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then +request.transportassets=_transports +request.transportattribute=_transports[1].attribute +request.transportcategory=_transports[1].category +request.ntransport=#_transports +local text=string.format("Selected transport assets, attibute=%s, category=%d:\n",request.transportattribute,request.transportcategory) +for _i,_asset in pairs(_transports)do +local asset=_asset +text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d\n",_i,asset.templatename,asset.unittype,asset.category,asset.nunits) +end +self:T(self.lid..text) +end +return true +end +function WAREHOUSE:_GetTransportsForAssets(request) +local transports=self:_FilterStock(self.stock,WAREHOUSE.Descriptor.ATTRIBUTE,request.transporttype,nil,true) +local cargoassets=UTILS.DeepCopy(request.cargoassets) +local cargoset=request.transportcargoset +local function sort_transports(a,b) +return a.cargobaymax>b.cargobaymax +end +local function sort_cargoassets(a,b) +return a.weight>b.weight +end +table.sort(transports,sort_transports) +table.sort(cargoassets,sort_cargoassets) +self:T2(self.lid.."Transport capability:") +local totalbay=0 +for i=1,#transports do +local transport=transports[i] +for j=1,transport.nunits do +totalbay=totalbay+transport.cargobay[j] +self:T2(self.lid..string.format("Cargo bay = %d (unit=%d)",transport.cargobay[j],j)) +end +end +self:T2(self.lid..string.format("Total capacity = %d",totalbay)) +self:T2(self.lid.."Cargo weight:") +local totalcargoweight=0 +for i=1,#cargoassets do +local asset=cargoassets[i] +totalcargoweight=totalcargoweight+asset.weight +self:T2(self.lid..string.format("weight = %d",asset.weight)) +end +self:T2(self.lid..string.format("Total weight = %d",totalcargoweight)) +local used_transports={} +for i=1,#transports do +local transport=transports[i] +local putintocarrier={} +local used=false +for k=1,transport.nunits do +local cargobay=transport.cargobay[k] +for j,asset in pairs(cargoassets)do +local asset=asset +local delta=cargobay-asset.weight +if delta>=0 then +cargobay=cargobay-asset.weight +self:T3(self.lid..string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d",transport.templatename,k,asset.uid,transport.cargobay[k],cargobay,asset.weight)) +table.insert(putintocarrier,j) +used=true +else +self:T2(self.lid..string.format("Carrier unit %s too small for cargo asset %s ==> cannot be used! Cargo bay - asset weight = %d kg",transport.templatename,asset.templatename,delta)) +end +end +end +for j=#putintocarrier,1,-1 do +local nput=putintocarrier[j] +local cargo=cargoassets[nput] +if cargo then +self:T2(self.lid..string.format("Cargo id=%d assigned for carrier id=%d",cargo.uid,transport.uid)) +table.remove(cargoassets,nput) +end +end +if used then +table.insert(used_transports,transport) +end +local ntrans=self:_QuantityRel2Abs(request.ntransport,#transports) +if#used_transports>=ntrans then +request.ntransport=#used_transports +break +end +end +local text=string.format("Used Transports for request %d to warehouse %s:\n",request.uid,request.warehouse.alias) +local totalcargobay=0 +for _i,_transport in pairs(used_transports)do +local transport=_transport +text=text..string.format("%d) %s: cargobay tot = %d kg, cargobay max = %d kg, nunits=%d\n",_i,transport.unittype,transport.cargobaytot,transport.cargobaymax,transport.nunits) +totalcargobay=totalcargobay+transport.cargobaytot +end +text=text..string.format("Total cargo bay capacity = %.1f kg\n",totalcargobay) +text=text..string.format("Total cargo weight = %.1f kg\n",totalcargoweight) +text=text..string.format("Minimum number of runs = %.1f",totalcargoweight/totalcargobay) +self:_DebugMessage(text) +return used_transports +end +function WAREHOUSE:_QuantityRel2Abs(relative,ntot) +local nabs=0 +if type(relative)=="string"then +if relative==WAREHOUSE.Quantity.ALL then +nabs=ntot +elseif relative==WAREHOUSE.Quantity.THREEQUARTERS then +nabs=UTILS.Round(ntot*3/4) +elseif relative==WAREHOUSE.Quantity.HALF then +nabs=UTILS.Round(ntot/2) +elseif relative==WAREHOUSE.Quantity.THIRD then +nabs=UTILS.Round(ntot/3) +elseif relative==WAREHOUSE.Quantity.QUARTER then +nabs=UTILS.Round(ntot/4) +else +nabs=math.min(1,ntot) +end +else +nabs=relative +end +self:T2(self.lid..string.format("Relative %s: tot=%d, abs=%.2f",tostring(relative),ntot,nabs)) +return nabs +end +function WAREHOUSE:_CheckQueue() +self:_SortQueue() +local request=nil +local invalid={} +local gotit=false +for _,_qitem in ipairs(self.queue)do +local qitem=_qitem +local valid=self:_CheckRequestValid(qitem) +local okay=false +if valid then +okay=self:_CheckRequestNow(qitem) +else +table.insert(invalid,qitem) +end +if okay and valid and not gotit then +request=qitem +gotit=true +break +end +end +for _,_request in pairs(invalid)do +self:T(self.lid..string.format("Deleting invalid request id=%d.",_request.uid)) +self:_DeleteQueueItem(_request,self.queue) +end +return request +end +function WAREHOUSE:_SimpleTaskFunction(Function,group) +self:F2({Function}) +local warehouse=self.warehouse:GetName() +local groupname=group:GetName() +local DCSScript={} +DCSScript[#DCSScript+1]=string.format('local mygroup = GROUP:FindByName(\"%s\") ',groupname) +if self.isUnit then +DCSScript[#DCSScript+1]=string.format("local mywarehouse = UNIT:FindByName(\"%s\") ",warehouse) +else +DCSScript[#DCSScript+1]=string.format("local mywarehouse = STATIC:FindByName(\"%s\") ",warehouse) +end +DCSScript[#DCSScript+1]=string.format('local warehouse = mywarehouse:GetState(mywarehouse, \"WAREHOUSE\") ') +DCSScript[#DCSScript+1]=string.format('%s(mygroup)',Function) +local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript))) +return DCSTask +end +function WAREHOUSE:_SimpleTaskFunctionWP(Function,group,n,N) +self:F2({Function}) +local warehouse=self.warehouse:GetName() +local groupname=group:GetName() +local DCSScript={} +DCSScript[#DCSScript+1]=string.format('local mygroup = GROUP:FindByName(\"%s\") ',groupname) +if self.isUnit then +DCSScript[#DCSScript+1]=string.format("local mywarehouse = UNIT:FindByName(\"%s\") ",warehouse) +else +DCSScript[#DCSScript+1]=string.format("local mywarehouse = STATIC:FindByName(\"%s\") ",warehouse) +end +DCSScript[#DCSScript+1]=string.format('local warehouse = mywarehouse:GetState(mywarehouse, \"WAREHOUSE\") ') +DCSScript[#DCSScript+1]=string.format('%s(mygroup, %d, %d)',Function,n,N) +local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript))) +return DCSTask +end +function WAREHOUSE:_GetTerminal(_attribute,_category) +local _terminal=AIRBASE.TerminalType.OpenBig +if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER or _attribute==WAREHOUSE.Attribute.AIR_UAV then +_terminal=AIRBASE.TerminalType.FighterAircraft +elseif _attribute==WAREHOUSE.Attribute.AIR_BOMBER or _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTPLANE or _attribute==WAREHOUSE.Attribute.AIR_TANKER or _attribute==WAREHOUSE.Attribute.AIR_AWACS then +_terminal=AIRBASE.TerminalType.OpenBig +elseif _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO then +_terminal=AIRBASE.TerminalType.HelicopterUsable +else +end +if _category==Airbase.Category.SHIP then +if not(_attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO)then +_terminal=AIRBASE.TerminalType.OpenMedOrBig +end +end +return _terminal +end +function WAREHOUSE:_FindParkingForAssets(airbase,assets) +local scanradius=25 +local scanunits=true +local scanstatics=true +local scanscenery=false +local verysafe=false +local function _overlap(l1,l2,dist) +local safedist=(l1/2+l2/2)*1.05 +local safe=(dist>safedist) +self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s",l1,l2,safedist,dist,tostring(safe))) +return safe +end +local function _clients() +local coords={} +if not self.allowSpawnOnClientSpots then +local clients=_DATABASE.CLIENTS +for clientname,client in pairs(clients)do +local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) +if template then +local units=template.units +for i,unit in pairs(units)do +local coord=COORDINATE:New(unit.x,unit.alt,unit.y) +coords[unit.name]=coord +end +end +end +end +return coords +end +local parkingdata=airbase.parking +local obstacles={} +self.clientcoords=self.clientcoords or _clients() +for clientname,_coord in pairs(self.clientcoords)do +table.insert(obstacles,{coord=_coord,size=15,name=clientname,type="client"}) +end +for _,parkingspot in pairs(parkingdata)do +local _spot=parkingspot.Coordinate +local _termid=parkingspot.TerminalID +local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius,scanunits,scanstatics,scanscenery) +for _,_unit in pairs(_units)do +local unit=_unit +local _coord=unit:GetVec3() +local _size=self:_GetObjectSize(unit:GetDCSObject()) +local _name=unit:GetName() +if unit and unit:IsAlive()then +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="unit"}) +end +end +for _,static in pairs(_statics)do +local _coord=static:getPoint() +local _name=static:getName() +local _size=self:_GetObjectSize(static) +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="static"}) +end +for _,scenery in pairs(_sceneries)do +local _coord=scenery:getPoint() +local _name=scenery:getTypeName() +local _size=self:_GetObjectSize(scenery) +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="scenery"}) +end +end +local parking={} +for _,asset in pairs(assets)do +local _asset=asset +if not _asset.spawned then +local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory()) +parking[_asset.uid]={} +for i=1,_asset.nunits do +local assetname=_asset.spawngroupname.."-"..tostring(i) +local gotit=false +for _,_parkingspot in pairs(parkingdata)do +local parkingspot=_parkingspot +local valid=true +if asset.parkingIDs then +valid=self:_CheckParkingAsset(parkingspot,asset) +else +local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype) +local validParking=self:_CheckParkingValid(parkingspot) +local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID) +valid=validTerminal and validParking and validBWlist +end +if valid then +local _spot=parkingspot.Coordinate +local _termid=parkingspot.TerminalID +local free=true +local problem=nil +for _,obstacle in pairs(obstacles)do +local dist=_spot:Get2DDistance(obstacle.coord) +local safe=_overlap(_asset.size,obstacle.size,dist) +if not safe then +self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE",assetname,_asset.uid,_termid,dist)) +free=false +problem=obstacle +problem.dist=dist +break +else +end +end +if free then +table.insert(parking[_asset.uid],parkingspot) +self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!",_termid,assetname,_asset.uid)) +table.insert(obstacles,{coord=_spot,size=_asset.size,name=assetname,type="asset"}) +gotit=true +break +else +if self.Debug then +local coord=problem.coord +if coord then +local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.",problem.name,problem.type,_termid,problem.size,problem.dist) +self:I(self.lid..text) +coord:MarkToAll(text) +end +else +self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!",_termid)) +end +end +else +self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported",parkingspot.TerminalID,parkingspot.TerminalType)) +end +end +if not gotit then +self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]",assetname,_asset.uid)) +return nil +end +end +end +end +return parking +end +function WAREHOUSE:_GetRequestOfGroup(group,queue) +local wid,aid,rid=self:_GetIDsFromGroup(group) +for _,_request in pairs(queue)do +local request=_request +if request.uid==rid then +return request +end +end +end +function WAREHOUSE:_GroupIsTransport(group,request) +local asset=self:FindAssetInDB(group) +if asset and asset.iscargo~=nil then +return not asset.iscargo +else +local groupname=self:_GetNameWithOut(group) +if request.transportgroupset then +local transporters=request.transportgroupset:GetSetObjects() +for _,transport in pairs(transporters)do +if transport:GetName()==groupname then +return true +end +end +end +if request.cargogroupset then +local cargos=request.cargogroupset:GetSetObjects() +for _,cargo in pairs(cargos)do +if self:_GetNameWithOut(cargo)==groupname then +return false +end +end +end +end +return nil +end +function WAREHOUSE:_GetNameWithOut(group) +local groupname=type(group)=="string"and group or group:GetName() +if groupname:find("CARGO")then +local name=groupname:gsub("#CARGO","") +return name +else +return groupname +end +end +function WAREHOUSE:_GetIDsFromGroup(group) +if group then +local groupname=group:GetName() +local wid,aid,rid=self:_GetIDsFromGroupName(groupname) +return wid,aid,rid +else +self:E("WARNING: Group not found in GetIDsFromGroup() function!") +end +end +function WAREHOUSE:_GetIDsFromGroupName(groupname) +local function analyse(text) +local unspawned=UTILS.Split(text,"#")[1] +local keywords=UTILS.Split(unspawned,"_") +local _wid=nil +local _aid=nil +local _rid=nil +for _,keys in pairs(keywords)do +local str=UTILS.Split(keys,"-") +local key=str[1] +local val=str[2] +if key:find("WID")then +_wid=tonumber(val) +elseif key:find("AID")then +_aid=tonumber(val) +elseif key:find("RID")then +_rid=tonumber(val) +end +end +return _wid,_aid,_rid +end +local wid,aid,rid=analyse(groupname) +local asset=self:GetAssetByID(aid) +if asset then +wid=asset.wid +rid=asset.rid +end +self:T3(self.lid..string.format("Group Name = %s",tostring(groupname))) +self:T3(self.lid..string.format("Warehouse ID = %s",tostring(wid))) +self:T3(self.lid..string.format("Asset ID = %s",tostring(aid))) +self:T3(self.lid..string.format("Request ID = %s",tostring(rid))) +return wid,aid,rid +end +function WAREHOUSE:FilterStock(descriptor,attribute,nmax,mobile) +return self:_FilterStock(self.stock,descriptor,attribute,nmax,mobile) +end +function WAREHOUSE:_FilterStock(stock,descriptor,attribute,nmax,mobile) +nmax=nmax or WAREHOUSE.Quantity.ALL +if mobile==nil then +mobile=false +end +local filtered={} +if descriptor==WAREHOUSE.Descriptor.ASSETLIST then +local ntot=0 +for _,_rasset in pairs(attribute)do +local rasset=_rasset +for _,_asset in ipairs(stock)do +local asset=_asset +if rasset.uid==asset.uid then +table.insert(filtered,asset) +break +end +end +end +return filtered,#filtered,#filtered>=#attribute +end +local ntot=0 +for _,_asset in ipairs(stock)do +local asset=_asset +local ismobile=asset.speedmax>0 +if asset[descriptor]==attribute then +if(mobile==true and ismobile)or mobile==false then +ntot=ntot+1 +end +end +end +if ntot==0 then +return filtered,ntot,false +end +nmax=self:_QuantityRel2Abs(nmax,ntot) +for _i,_asset in ipairs(stock)do +local asset=_asset +if asset[descriptor]==attribute then +if(mobile and asset.speedmax>0)or(not mobile)then +table.insert(filtered,asset) +if nmax~=nil and#filtered>=nmax then +return filtered,ntot,true +end +end +end +end +return filtered,ntot,ntot>=nmax +end +function WAREHOUSE:_HasAttribute(group,attribute) +if group then +local groupattribute=self:_GetAttribute(group) +return groupattribute==attribute +end +return false +end +function WAREHOUSE:_GetAttribute(group) +local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN +if group then +local groupCat=group:GetCategory() +local transportplane=group:HasAttribute("Transports")and group:HasAttribute("Planes")and groupCat==Group.Category.AIRPLANE +local awacs=group:HasAttribute("AWACS") +local fighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers")) +local bomber=group:HasAttribute("Strategic bombers") +local tanker=group:HasAttribute("Tankers") +local uav=group:HasAttribute("UAVs") +local transporthelo=group:HasAttribute("Transport helicopters") +local attackhelicopter=group:HasAttribute("Attack helicopters") +local apc=group:HasAttribute("APC") +local truck=group:HasAttribute("Trucks")and group:GetCategory()==Group.Category.GROUND +local infantry=group:HasAttribute("Infantry") +local ifv=group:HasAttribute("IFV") +local artillery=group:HasAttribute("Artillery") +local tank=group:HasAttribute("Old Tanks")or group:HasAttribute("Modern Tanks") +local aaa=group:HasAttribute("AAA") +local ewr=group:HasAttribute("EWR") +local sam=group:HasAttribute("SAM elements")and(not group:HasAttribute("AAA")) +local train=group:GetCategory()==Group.Category.TRAIN +local aircraftcarrier=group:HasAttribute("Aircraft Carriers") +local warship=group:HasAttribute("Heavy armed ships") +local armedship=group:HasAttribute("Armed ships")or group:HasAttribute("Armed Ship") +local unarmedship=group:HasAttribute("Unarmed ships") +if transportplane then +attribute=WAREHOUSE.Attribute.AIR_TRANSPORTPLANE +elseif awacs then +attribute=WAREHOUSE.Attribute.AIR_AWACS +elseif fighter then +attribute=WAREHOUSE.Attribute.AIR_FIGHTER +elseif bomber then +attribute=WAREHOUSE.Attribute.AIR_BOMBER +elseif tanker then +attribute=WAREHOUSE.Attribute.AIR_TANKER +elseif transporthelo then +attribute=WAREHOUSE.Attribute.AIR_TRANSPORTHELO +elseif attackhelicopter then +attribute=WAREHOUSE.Attribute.AIR_ATTACKHELO +elseif uav then +attribute=WAREHOUSE.Attribute.AIR_UAV +elseif apc then +attribute=WAREHOUSE.Attribute.GROUND_APC +elseif ifv then +attribute=WAREHOUSE.Attribute.GROUND_IFV +elseif infantry then +attribute=WAREHOUSE.Attribute.GROUND_INFANTRY +elseif artillery then +attribute=WAREHOUSE.Attribute.GROUND_ARTILLERY +elseif tank then +attribute=WAREHOUSE.Attribute.GROUND_TANK +elseif aaa then +attribute=WAREHOUSE.Attribute.GROUND_AAA +elseif ewr then +attribute=WAREHOUSE.Attribute.GROUND_EWR +elseif sam then +attribute=WAREHOUSE.Attribute.GROUND_SAM +elseif truck then +attribute=WAREHOUSE.Attribute.GROUND_TRUCK +elseif train then +attribute=WAREHOUSE.Attribute.GROUND_TRAIN +elseif aircraftcarrier then +attribute=WAREHOUSE.Attribute.NAVAL_AIRCRAFTCARRIER +elseif warship then +attribute=WAREHOUSE.Attribute.NAVAL_WARSHIP +elseif armedship then +attribute=WAREHOUSE.Attribute.NAVAL_ARMEDSHIP +elseif unarmedship then +attribute=WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP +else +if group:IsGround()then +attribute=WAREHOUSE.Attribute.GROUND_OTHER +elseif group:IsShip()then +attribute=WAREHOUSE.Attribute.NAVAL_OTHER +elseif group:IsAir()then +attribute=WAREHOUSE.Attribute.AIR_OTHER +else +attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN +end +end +end +return attribute +end +function WAREHOUSE:_GetObjectSize(DCSobject) +local DCSdesc=DCSobject:getDesc() +if DCSdesc.box then +local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) +local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) +local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) +return math.max(x,z),x,y,z +end +return 0,0,0,0 +end +function WAREHOUSE:GetStockInfo(stock) +local _data={} +for _j,_attribute in pairs(WAREHOUSE.Attribute)do +local n=0 +for _i,_item in pairs(stock)do +local _ite=_item +if _ite.attribute==_attribute then +n=n+1 +end +end +_data[_attribute]=n +end +return _data +end +function WAREHOUSE:_DeleteStockItem(stockitem) +for i=1,#self.stock do +local item=self.stock[i] +if item.uid==stockitem.uid then +table.remove(self.stock,i) +break +end +end +end +function WAREHOUSE:_DeleteQueueItem(qitem,queue) +for i=1,#queue do +local _item=queue[i] +if _item.uid==qitem.uid then +self:T(self.lid..string.format("Deleting queue item id=%d.",qitem.uid)) +table.remove(queue,i) +break +end +end +end +function WAREHOUSE:_DeleteQueueItemByID(qitemID,queue) +for i=1,#queue do +local _item=queue[i] +if _item.uid==qitemID then +self:T(self.lid..string.format("Deleting queue item id=%d.",qitemID)) +table.remove(queue,i) +break +end +end +end +function WAREHOUSE:_SortQueue() +self:F3() +local function _sort(a,b) +return(a.prio=2 then +local total="Empty" +if#queue>0 then +total=string.format("Total = %d",#queue) +end +local text=string.format("%s at %s: %s",name,self.alias,total) +for i,qitem in ipairs(queue)do +local qitem=qitem +local uid=qitem.uid +local prio=qitem.prio +local clock="N/A" +if qitem.timestamp then +clock=tostring(UTILS.SecondsToClock(qitem.timestamp)) +end +local assignment=tostring(qitem.assignment) +local requestor=qitem.warehouse.alias +local airbasename=qitem.warehouse:GetAirbaseName() +local requestorAirbaseCat=qitem.warehouse:GetAirbaseCategory() +local assetdesc=qitem.assetdesc +local assetdescval=qitem.assetdescval +if assetdesc==WAREHOUSE.Descriptor.ASSETLIST then +assetdescval="Asset list" +end +local nasset=tostring(qitem.nasset) +local ndelivered=tostring(qitem.ndelivered) +local ncargogroupset="N/A" +if qitem.cargogroupset then +ncargogroupset=tostring(qitem.cargogroupset:Count()) +end +local transporttype="N/A" +if qitem.transporttype then +transporttype=qitem.transporttype +end +local ntransport="N/A" +if qitem.ntransport then +ntransport=tostring(qitem.ntransport) +end +local ntransportalive="N/A" +if qitem.transportgroupset then +ntransportalive=tostring(qitem.transportgroupset:Count()) +end +local ntransporthome="N/A" +if qitem.ntransporthome then +ntransporthome=tostring(qitem.ntransporthome) +end +text=text..string.format( +"\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%s / #alive=%s / #home=%s", +i,uid,prio,clock,assignment,requestor,airbasename,requestorAirbaseCat,assetdesc,assetdescval,nasset,ncargogroupset,ndelivered,transporttype,ntransport,ntransportalive,ntransporthome) +end +if#queue==0 then +self:I(self.lid..text) +else +if total~="Empty"then +self:I(self.lid..text) +end +end +end +end +function WAREHOUSE:_DisplayStatus() +if self.verbosity>=3 then +local text=string.format("\n------------------------------------------------------\n") +text=text..string.format("Warehouse %s status: %s\n",self.alias,self:GetState()) +text=text..string.format("------------------------------------------------------\n") +text=text..string.format("Coalition name = %s\n",self:GetCoalitionName()) +text=text..string.format("Country name = %s\n",self:GetCountryName()) +text=text..string.format("Airbase name = %s (category=%d)\n",self:GetAirbaseName(),self:GetAirbaseCategory()) +text=text..string.format("Queued requests = %d\n",#self.queue) +text=text..string.format("Pending requests = %d\n",#self.pending) +text=text..string.format("------------------------------------------------------\n") +text=text..self:_GetStockAssetsText() +self:I(text) +end +end +function WAREHOUSE:_GetStockAssetsText(messagetoall) +local _data=self:GetStockInfo(self.stock) +local text="Stock:\n" +local total=0 +for _attribute,_count in pairs(_data)do +if _count>0 then +local attribute=tostring(UTILS.Split(_attribute,"_")[2]) +text=text..string.format("%s = %d\n",attribute,_count) +total=total+_count +end +end +text=text..string.format("===================\n") +text=text..string.format("Total = %d\n",total) +text=text..string.format("------------------------------------------------------\n") +MESSAGE:New(text,10):ToAllIf(messagetoall) +return text +end +function WAREHOUSE:_UpdateWarehouseMarkText() +if self.markerOn then +local text=string.format("Warehouse state: %s\nTotal assets in stock %d:\n",self:GetState(),#self.stock) +for _attribute,_count in pairs(self:GetStockInfo(self.stock)or{})do +if _count>0 then +local attribute=tostring(UTILS.Split(_attribute,"_")[2]) +text=text..string.format("%s=%d, ",attribute,_count) +end +end +local coordinate=self:GetCoordinate() +local coalition=self:GetCoalition() +if not self.markerWarehouse then +self.markerWarehouse=MARKER:New(coordinate,text):ToCoalition(coalition) +else +local refresh=false +if self.markerWarehouse.text~=text then +self.markerWarehouse.text=text +refresh=true +end +if self.markerWarehouse.coordinate~=coordinate then +self.markerWarehouse.coordinate=coordinate +refresh=true +end +if self.markerWarehouse.coalition~=coalition then +self.markerWarehouse.coalition=coalition +refresh=true +end +if refresh then +self.markerWarehouse:Refresh() +end +end +end +end +function WAREHOUSE:_DisplayStockItems(stock) +local text=self.lid..string.format("Warehouse %s stock assets:",self.alias) +for _i,_stock in pairs(stock)do +local mystock=_stock +local name=mystock.templatename +local category=mystock.category +local cargobaymax=mystock.cargobaymax +local cargobaytot=mystock.cargobaytot +local nunits=mystock.nunits +local range=mystock.range +local size=mystock.size +local speed=mystock.speedmax +local uid=mystock.uid +local unittype=mystock.unittype +local weight=mystock.weight +local attribute=mystock.attribute +text=text..string.format("\n%02d) uid=%d, name=%s, unittype=%s, category=%d, attribute=%s, nunits=%d, speed=%.1f km/h, range=%.1f km, size=%.1f m, weight=%.1f kg, cargobax max=%.1f kg tot=%.1f kg", +_i,uid,name,unittype,category,attribute,nunits,speed,range/1000,size,weight,cargobaymax,cargobaytot) +end +self:T3(text) +end +function WAREHOUSE:_Fireworks(coord) +coord=coord or self:GetCoordinate() +for i=1,91 do +local color=math.random(0,3) +coord:Flare(color,i-1) +end +end +function WAREHOUSE:_InfoMessage(text,duration) +duration=duration or 20 +if duration>0 and self.Debug or self.Report then +MESSAGE:New(text,duration):ToCoalition(self:GetCoalition()) +end +self:I(self.lid..text) +end +function WAREHOUSE:_DebugMessage(text,duration) +duration=duration or 20 +if self.Debug and duration>0 then +MESSAGE:New(text,duration):ToAllIf(self.Debug) +end +self:T(self.lid..text) +end +function WAREHOUSE:_ErrorMessage(text,duration) +duration=duration or 20 +if duration>0 then +MESSAGE:New(text,duration):ToAll() +end +self:E(self.lid..text) +end +function WAREHOUSE:_GetMaxHeight(D,alphaC,alphaD,Hdep,Hdest,Deltahhold) +local Hhold=Hdest+Deltahhold +local hdest=Hdest-Hdep +local hhold=hdest+Deltahhold +local Dp=math.sqrt(D^2+hhold^2) +local alphaS=math.atan(hdest/D) +local alphaH=math.atan(hhold/D) +local alphaCp=alphaC-alphaH +local alphaDp=alphaD+alphaH +local gammap=math.pi-alphaCp-alphaDp +local sCp=Dp*math.sin(alphaDp)/math.sin(gammap) +local sDp=Dp*math.sin(alphaCp)/math.sin(gammap) +local hmax=sCp*math.sin(alphaC) +if self.Debug then +env.info(string.format("Hdep = %.3f km",Hdep/1000)) +env.info(string.format("Hdest = %.3f km",Hdest/1000)) +env.info(string.format("DetaHold= %.3f km",Deltahhold/1000)) +env.info() +env.info(string.format("D = %.3f km",D/1000)) +env.info(string.format("Dp = %.3f km",Dp/1000)) +env.info() +env.info(string.format("alphaC = %.3f Deg",math.deg(alphaC))) +env.info(string.format("alphaCp = %.3f Deg",math.deg(alphaCp))) +env.info() +env.info(string.format("alphaD = %.3f Deg",math.deg(alphaD))) +env.info(string.format("alphaDp = %.3f Deg",math.deg(alphaDp))) +env.info() +env.info(string.format("alphaS = %.3f Deg",math.deg(alphaS))) +env.info(string.format("alphaH = %.3f Deg",math.deg(alphaH))) +env.info() +env.info(string.format("sCp = %.3f km",sCp/1000)) +env.info(string.format("sDp = %.3f km",sDp/1000)) +env.info() +env.info(string.format("hmax = %.3f km",hmax/1000)) +env.info() +local hdescent=hmax-hhold +local dClimb=hmax/math.tan(alphaC) +local dDescent=(hmax-hhold)/math.tan(alphaD) +local dCruise=D-dClimb-dDescent +env.info(string.format("hmax = %.3f km",hmax/1000)) +env.info(string.format("hdescent = %.3f km",hdescent/1000)) +env.info(string.format("Dclimb = %.3f km",dClimb/1000)) +env.info(string.format("Dcruise = %.3f km",dCruise/1000)) +env.info(string.format("Ddescent = %.3f km",dDescent/1000)) +env.info() +end +return hmax +end +function WAREHOUSE:_GetFlightplan(asset,departure,destination) +local Vmax=asset.speedmax/3.6 +local Range=asset.range +local category=asset.category +local ceiling=asset.DCSdesc.Hmax +local Vymax=asset.DCSdesc.VyMax +local VxCruiseMax=0.90*Vmax +local VxCruiseMin=math.min(VxCruiseMax*0.70,166) +local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax) +local VxClimb=math.min(Vmax*0.90,200) +local VxDescent=math.min(Vmax*0.60,140) +local VxHolding=VxDescent*0.9 +local VxFinal=VxHolding*0.9 +local VyClimb=math.min(7.6,Vymax) +local AlphaClimb=math.rad(4) +local AlphaDescent=math.rad(4) +local FLcruise_expect=150*RAT.unit.FL2m +if category==Group.Category.HELICOPTER then +FLcruise_expect=1000 +end +local Pdeparture=departure:GetCoordinate() +local H_departure=Pdeparture.y +local Pdestination=destination:GetCoordinate() +local H_destination=Pdestination.y +local Rhmin=5000 +local Rhmax=10000 +if category==Group.Category.HELICOPTER then +Rhmin=500 +Rhmax=1000 +end +local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax,Rhmin) +local d_holding=Pholding:Get2DDistance(Pdestination) +local H_holding=Pholding.y +local heading=Pdeparture:HeadingTo(Pholding) +local d_total=Pdeparture:Get2DDistance(Pholding) +local h_holding=1200 +if category==Group.Category.HELICOPTER then +h_holding=150 +end +h_holding=UTILS.Randomize(h_holding,0.2) +local DeltaholdingMax=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,0) +if h_holding>DeltaholdingMax then +h_holding=math.abs(DeltaholdingMax) +end +local Hh_holding=H_holding+h_holding +local h_max=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,h_holding) +local FLmax=h_max+H_departure +local FLmin=math.max(H_departure,Hh_holding) +FLmax=math.min(FLmax,ceiling) +if FLmin>FLmax then +FLmin=FLmax +end +if FLcruise_expectFLmax then +FLcruise_expect=FLmax +end +local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax) +local h_climb=FLcruise-H_departure +local h_descent=FLcruise-Hh_holding +local d_climb=h_climb/math.tan(AlphaClimb) +local d_descent=h_descent/math.tan(AlphaDescent) +local d_cruise=d_total-d_climb-d_descent +local text=string.format("Flight plan:\n") +text=text..string.format("Vx max = %.2f km/h\n",Vmax*3.6) +text=text..string.format("Vx climb = %.2f km/h\n",VxClimb*3.6) +text=text..string.format("Vx cruise = %.2f km/h\n",VxCruise*3.6) +text=text..string.format("Vx descent = %.2f km/h\n",VxDescent*3.6) +text=text..string.format("Vx holding = %.2f km/h\n",VxHolding*3.6) +text=text..string.format("Vx final = %.2f km/h\n",VxFinal*3.6) +text=text..string.format("Vy max = %.2f m/s\n",Vymax) +text=text..string.format("Vy climb = %.2f m/s\n",VyClimb) +text=text..string.format("Alpha Climb = %.2f Deg\n",math.deg(AlphaClimb)) +text=text..string.format("Alpha Descent = %.2f Deg\n",math.deg(AlphaDescent)) +text=text..string.format("Dist climb = %.3f km\n",d_climb/1000) +text=text..string.format("Dist cruise = %.3f km\n",d_cruise/1000) +text=text..string.format("Dist descent = %.3f km\n",d_descent/1000) +text=text..string.format("Dist total = %.3f km\n",d_total/1000) +text=text..string.format("h_climb = %.3f km\n",h_climb/1000) +text=text..string.format("h_desc = %.3f km\n",h_descent/1000) +text=text..string.format("h_holding = %.3f km\n",h_holding/1000) +text=text..string.format("h_max = %.3f km\n",h_max/1000) +text=text..string.format("FL min = %.3f km\n",FLmin/1000) +text=text..string.format("FL expect = %.3f km\n",FLcruise_expect/1000) +text=text..string.format("FL cruise * = %.3f km\n",FLcruise/1000) +text=text..string.format("FL max = %.3f km\n",FLmax/1000) +text=text..string.format("Ceiling = %.3f km\n",ceiling/1000) +text=text..string.format("Max range = %.3f km\n",Range/1000) +self:T(self.lid..text) +if d_cruise<0 then +d_cruise=100 +end +local wp={} +local c={} +local _type=COORDINATE.WaypointType.TakeOffParking +local _action=COORDINATE.WaypointAction.FromParkingArea +if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then +_type=COORDINATE.WaypointType.TakeOffParkingHot +_action=COORDINATE.WaypointAction.FromParkingAreaHot +else +end +c[#c+1]=Pdeparture +wp[#wp+1]=Pdeparture:WaypointAir("RADIO",_type,_action,VxClimb*3.6,true,departure,nil,"Departure") +local Pcruise=Pdeparture:Translate(d_climb,heading) +Pcruise.y=FLcruise +c[#c+1]=Pcruise +wp[#wp+1]=Pcruise:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxCruise*3.6,true,nil,nil,"Cruise") +local Pdescent=Pcruise:Translate(d_cruise,heading) +Pdescent.y=FLcruise +c[#c+1]=Pdescent +wp[#wp+1]=Pdescent:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxDescent*3.6,true,nil,nil,"Descent") +Pholding.y=H_holding+h_holding +c[#c+1]=Pholding +wp[#wp+1]=Pholding:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxHolding*3.6,true,nil,nil,"Holding") +c[#c+1]=Pdestination +wp[#wp+1]=Pdestination:WaypointAir("RADIO",COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,VxFinal*3.6,true,destination,nil,"Final Destination") +if self.Debug then +for i,coord in pairs(c)do +local coord=coord +local dist=0 +if i>1 then +dist=coord:Get2DDistance(c[i-1]) +end +coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i,dist/1000)) +end +end +return wp,c +end +FOX={ +ClassName="FOX", +verbose=0, +Debug=false, +lid=nil, +menuadded={}, +menudisabled=nil, +destroy=nil, +launchalert=nil, +marklaunch=nil, +missiles={}, +players={}, +safezones={}, +launchzones={}, +protectedset=nil, +explosionpower=0.1, +explosiondist=200, +explosiondist2=500, +bigmissilemass=50, +dt50=5, +dt10=1, +dt05=0.5, +dt01=0.1, +dt00=0.01, +} +FOX.MenuF10={} +FOX.MenuF10Root=nil +FOX.version="0.8.0" +function FOX:New() +self.lid="FOX | " +local self=BASE:Inherit(self,FSM:New()) +self:SetDefaultMissileDestruction(true) +self:SetDefaultLaunchAlerts(true) +self:SetDefaultLaunchMarks(true) +self:SetExplosionDistance() +self:SetExplosionDistanceBigMissiles() +self:SetExplosionPower() +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","MissileLaunch","*") +self:AddTransition("*","MissileDestroyed","*") +self:AddTransition("*","EnterSafeZone","*") +self:AddTransition("*","ExitSafeZone","*") +self:AddTransition("Running","Stop","Stopped") +return self +end +function FOX:onafterStart(From,Event,To) +local text=string.format("Starting FOX Missile Trainer %s",FOX.version) +env.info(text) +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.Shot) +if self.Debug then +self:HandleEvent(EVENTS.Hit) +end +if self.Debug then +self:TraceClass(self.ClassName) +self:TraceLevel(2) +end +self:__Status(-20) +end +function FOX:onafterStop(From,Event,To) +local text=string.format("Stopping FOX Missile Trainer %s",FOX.version) +env.info(text) +self:UnHandleEvent(EVENTS.Birth) +self:UnHandleEvent(EVENTS.Shot) +if self.Debug then +self:UnhandleEvent(EVENTS.Hit) +end +end +function FOX:AddSafeZone(zone) +table.insert(self.safezones,zone) +return self +end +function FOX:AddLaunchZone(zone) +table.insert(self.launchzones,zone) +return self +end +function FOX:SetProtectedGroupSet(groupset) +self.protectedset=groupset +return self +end +function FOX:AddProtectedGroup(group) +if not self.protectedset then +self.protectedset=SET_GROUP:New() +end +self.protectedset:AddGroup(group) +return self +end +function FOX:SetExplosionPower(power) +self.explosionpower=power or 0.1 +return self +end +function FOX:SetExplosionDistance(distance) +self.explosiondist=distance or 200 +return self +end +function FOX:SetExplosionDistanceBigMissiles(distance,explosivemass) +self.explosiondist2=distance or 500 +self.bigmissilemass=explosivemass or 50 +return self +end +function FOX:SetDisableF10Menu() +self.menudisabled=true +return self +end +function FOX:SetEnableF10Menu() +self.menudisabled=false +return self +end +function FOX:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function FOX:SetDefaultMissileDestruction(switch) +if switch==nil then +self.destroy=false +else +self.destroy=switch +end +return self +end +function FOX:SetDefaultLaunchAlerts(switch) +if switch==nil then +self.launchalert=false +else +self.launchalert=switch +end +return self +end +function FOX:SetDefaultLaunchMarks(switch) +if switch==nil then +self.marklaunch=false +else +self.marklaunch=switch +end +return self +end +function FOX:SetDebugOnOff(switch) +if switch==nil then +self.Debug=false +else +self.Debug=switch +end +return self +end +function FOX:SetDebugOn() +self:SetDebugOnOff(true) +return self +end +function FOX:SetDebugOff() +self:SetDebugOff(false) +return self +end +function FOX:onafterStatus(From,Event,To) +local fsmstate=self:GetState() +local time=timer.getAbsTime() +local clock=UTILS.SecondsToClock(time) +if self.verbose>=1 then +self:I(self.lid..string.format("Missile trainer status %s: %s",clock,fsmstate)) +end +self:_CheckMissileStatus() +self:_CheckPlayers() +if fsmstate=="Running"then +self:__Status(-10) +end +end +function FOX:_CheckPlayers() +for playername,_playersettings in pairs(self.players)do +local playersettings=_playersettings +local unitname=playersettings.unitname +local unit=UNIT:FindByName(unitname) +if unit and unit:IsAlive()then +local coord=unit:GetCoordinate() +local issafe=self:_CheckCoordSafe(coord) +if issafe then +if not playersettings.inzone then +self:EnterSafeZone(playersettings) +playersettings.inzone=true +end +else +if playersettings.inzone==true then +self:ExitSafeZone(playersettings) +playersettings.inzone=false +end +end +end +end +end +function FOX:_RemoveMissile(missile) +if missile then +for i,_missile in pairs(self.missiles)do +local m=_missile +if missile.missileName==m.missileName then +table.remove(self.missiles,i) +return +end +end +end +end +function FOX:_CheckMissileStatus() +local text="Missiles:" +local inactive={} +for i,_missile in pairs(self.missiles)do +local missile=_missile +local targetname="unkown" +if missile.targetUnit then +targetname=missile.targetUnit:GetName() +end +local playername="none" +if missile.targetPlayer then +playername=missile.targetPlayer.name +end +local active=tostring(missile.active) +local mtype=missile.missileType +local dtype=missile.missileType +local range=UTILS.MetersToNM(missile.missileRange) +if not active then +table.insert(inactive,i) +end +local heading=self:_GetWeapongHeading(missile.weapon) +text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s",i,mtype,active,range,heading,targetname,playername,missile.missileName) +end +if#self.missiles==0 then +text=text.." none" +end +if self.verbose>=2 then +self:I(self.lid..text) +end +for i=#self.missiles,1,-1 do +local missile=self.missiles[i] +if missile and not missile.active then +table.remove(self.missiles,i) +end +end +end +function FOX:_IsProtected(targetunit) +if not self.protectedset then +return false +end +if targetunit and targetunit:IsAlive()then +local targetgroup=targetunit:GetGroup() +if targetgroup then +local targetname=targetgroup:GetName() +for _,_group in pairs(self.protectedset:GetSet())do +local group=_group +if group then +local groupname=group:GetName() +if targetname==groupname then +return true +end +end +end +end +end +return false +end +function FOX._FuncTrack(weapon,self,missile) +local missileCoord=missile.missileCoord:UpdateFromVec3(weapon.vec3) +local missileVelocity=weapon:GetSpeed() +self:GetMissileTarget(missile) +local target=nil +if missile.targetUnit then +if missile.targetPlayer then +if missile.targetPlayer.destroy==true then +target=missile.targetUnit +end +else +if self:_IsProtected(missile.targetUnit)then +target=missile.targetUnit +end +end +else +local function _GetTarget(_unit) +local unit=_unit +local playerCoord=unit:GetCoordinate() +local dist=missileCoord:Get3DDistance(playerCoord) +if dist<=self.explosiondist then +return unit +end +end +local mindist=nil +for _,_player in pairs(self.players)do +local player=_player +if player.unitname~=missile.shooterName then +local playerCoord=player.unit:GetCoordinate() +local dist=missileCoord:Get3DDistance(playerCoord) +local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord) +if(mindist==nil or dist=self.bigmissilemass +end +if destroymissile and self:_CheckCoordSafe(targetVec3)then +self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m", +missile.missileType,missile.missileName,missile.shooterName,target:GetName(),tostring(missile.targetPlayer~=nil),distance)) +weapon:Destroy() +missile.active=false +if self.Debug then +missileCoord:SmokeRed() +end +self:MissileDestroyed(missile) +if self.explosionpower>0 and distance>50 and(distShooter==nil or(distShooter and distShooter>50))then +missileCoord:Explosion(self.explosionpower) +end +if missile.targetPlayer then +local text=string.format("Destroying missile. %s",self:_DeadText()) +MESSAGE:New(text,10):ToGroup(target:GetGroup()) +missile.targetPlayer.dead=missile.targetPlayer.dead+1 +end +else +local dt=1.0 +if distance>50000 then +dt=self.dt50 +elseif distance>10000 then +dt=self.dt10 +elseif distance>5000 then +dt=self.dt05 +elseif distance>1000 then +dt=self.dt01 +else +dt=self.dt00 +end +weapon:SetTimeStepTrack(dt) +end +else +self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.",missile.missileType,missile.missileName,missile.shooterName)) +weapon:SetTimeStepTrack(0.1) +end +end +function FOX._FuncImpact(weapon,self,missile) +if missile.targetPlayer then +local player=missile.targetPlayer +if player and player.unit:IsAlive()then +local text=string.format("Missile defeated. Well done, %s!",player.name) +MESSAGE:New(text,10):ToClient(player.client) +player.defeated=player.defeated+1 +end +end +missile.active=false +self:T(FOX.lid..string.format("Terminating missile track timer.")) +weapon.tracking=false +end +function FOX:onafterMissileLaunch(From,Event,To,missile) +local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s",missile.missileType,missile.missileName,tostring(missile.targetName),missile.shooterName) +self:T(FOX.lid..text) +MESSAGE:New(text,10):ToAllIf(self.Debug) +for _,_player in pairs(self.players)do +local player=_player +local playerUnit=player.unit +if playerUnit and playerUnit:IsAlive()and player.coalition~=missile.shooterCoalition then +local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord) +local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord) +if player.launchalert then +if(missile.targetPlayer and player.unitname==missile.targetPlayer.unitname)or(distance Target=%s, fuse dist=%s, explosive=%s", +tostring(missile.shooterName),tostring(missile.missileType),tostring(missile.missileName),tostring(missile.targetName),tostring(missile.fuseDist),tostring(missile.explosive))) +if missile.targetPlayer or self:_IsProtected(missile.targetUnit)or missile.targetName=="unknown"then +table.insert(self.missiles,missile) +self:__MissileLaunch(0.1,missile) +end +end +end +function FOX:OnEventHit(EventData) +self:T({eventhit=EventData}) +if EventData.Weapon==nil then +return +end +if EventData.IniUnit==nil then +return +end +if EventData.TgtUnit==nil then +return +end +local weapon=EventData.Weapon +local weaponname=weapon:getName() +for i,_missile in pairs(self.missiles)do +local missile=_missile +if missile.missileName==weaponname then +self:I(self.lid..string.format("WARNING: Missile %s (%s) hit target %s. Missile trainer target was %s.",missile.missileType,missile.missileName,EventData.TgtUnitName,missile.targetName)) +self:I({missile=missile}) +return +end +end +end +function FOX:_AddF10Commands(_unitName) +self:F(_unitName) +local _unit,playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and playername then +local group=_unit:GetGroup() +local gid=group:GetID() +if group and gid then +if not self.menuadded[gid]then +self.menuadded[gid]=true +local _rootPath=nil +if FOX.MenuF10Root then +_rootPath=FOX.MenuF10Root +else +if FOX.MenuF10[gid]==nil then +FOX.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid,"FOX") +end +_rootPath=FOX.MenuF10[gid] +end +missionCommands.addCommandForGroup(gid,"Destroy Missiles On/Off",_rootPath,self._ToggleDestroyMissiles,self,_unitName) +missionCommands.addCommandForGroup(gid,"Launch Alerts On/Off",_rootPath,self._ToggleLaunchAlert,self,_unitName) +missionCommands.addCommandForGroup(gid,"Mark Launch On/Off",_rootPath,self._ToggleLaunchMark,self,_unitName) +missionCommands.addCommandForGroup(gid,"My Status",_rootPath,self._MyStatus,self,_unitName) +end +else +self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.",_unitName or"unknown")) +end +else +self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.",_unitName or"unknown")) +end +end +function FOX:_MyStatus(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +local m,mtext=self:_GetTargetMissiles(playerData.name) +local text=string.format("Status of player %s:\n",playerData.name) +local safe=self:_CheckCoordSafe(playerData.unit:GetCoordinate()) +text=text..string.format("Destroy missiles? %s\n",tostring(playerData.destroy)) +text=text..string.format("Launch alert? %s\n",tostring(playerData.launchalert)) +text=text..string.format("Launch marks? %s\n",tostring(playerData.marklaunch)) +text=text..string.format("Am I safe? %s\n",tostring(safe)) +text=text..string.format("Missiles defeated: %d\n",playerData.defeated) +text=text..string.format("Missiles destroyed: %d\n",playerData.dead) +text=text..string.format("Me target: %d\n%s",m,mtext) +MESSAGE:New(text,10,nil,true):ToClient(playerData.client) +end +end +end +function FOX:_GetTargetMissiles(playername) +local text="" +local n=0 +for _,_missile in pairs(self.missiles)do +local missile=_missile +if missile.targetPlayer and missile.targetPlayer.name==playername then +n=n+1 +text=text..string.format("Type %s: active %s\n",missile.missileType,tostring(missile.active)) +end +end +return n,text +end +function FOX:_ToggleLaunchAlert(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.launchalert=not playerData.launchalert +local text="" +if playerData.launchalert==true then +text=string.format("%s, missile launch alerts are now ENABLED.",playerData.name) +else +text=string.format("%s, missile launch alerts are now DISABLED.",playerData.name) +end +MESSAGE:New(text,5):ToClient(playerData.client) +end +end +end +function FOX:_ToggleLaunchMark(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.marklaunch=not playerData.marklaunch +local text="" +if playerData.marklaunch==true then +text=string.format("%s, missile launch marks are now ENABLED.",playerData.name) +else +text=string.format("%s, missile launch marks are now DISABLED.",playerData.name) +end +MESSAGE:New(text,5):ToClient(playerData.client) +end +end +end +function FOX:_ToggleDestroyMissiles(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.destroy=not playerData.destroy +local text="" +if playerData.destroy==true then +text=string.format("%s, incoming missiles will be DESTROYED.",playerData.name) +else +text=string.format("%s, incoming missiles will NOT be DESTROYED.",playerData.name) +end +MESSAGE:New(text,5):ToClient(playerData.client) +end +end +end +function FOX:_DeadText() +local texts={} +texts[1]="You're dead!" +texts[2]="Meet your maker!" +texts[3]="Time to meet your maker!" +texts[4]="Well, I guess that was it!" +texts[5]="Bye, bye!" +texts[6]="Cheers buddy, was nice knowing you!" +local r=math.random(#texts) +return texts[r] +end +function FOX:_CheckCoordSafe(coord) +if#self.safezones==0 then +return true +end +for _,_zone in pairs(self.safezones)do +local zone=_zone +local Vec2={x=coord.x,y=coord.z} +local inzone=zone:IsVec2InZone(Vec2) +if inzone then +return true +end +end +return false +end +function FOX:_CheckCoordLaunch(coord) +if#self.launchzones==0 then +return true +end +for _,_zone in pairs(self.launchzones)do +local zone=_zone +local Vec2={x=coord.x,y=coord.z} +local inzone=zone:IsVec2InZone(Vec2) +if inzone then +return true +end +end +return false +end +function FOX:_GetWeapongHeading(weapon) +if weapon and weapon:isExist()then +local wp=weapon:getPosition() +local wph=math.atan2(wp.x.z,wp.x.x) +if wph<0 then +wph=wph+2*math.pi +end +wph=math.deg(wph) +return wph +end +return-1 +end +function FOX:_SayNotchingHeadings(playerData,weapon) +if playerData and playerData.unit and playerData.unit:IsAlive()then +local nr,nl=self:_GetNotchingHeadings(weapon) +if nr and nl then +local text=string.format("Notching heading %03d° or %03d°",nr,nl) +MESSAGE:New(text,5,"FOX"):ToClient(playerData.client) +end +end +end +function FOX:_GetNotchingHeadings(weapon) +if weapon then +local hdg=self:_GetWeapongHeading(weapon) +local hdg1=hdg+90 +if hdg1>360 then +hdg1=hdg1-360 +end +local hdg2=hdg-90 +if hdg2<0 then +hdg2=hdg2+360 +end +return hdg1,hdg2 +end +return nil,nil +end +function FOX:_GetPlayerFromUnitname(unitName) +for _,_player in pairs(self.players)do +local player=_player +if player.unitname==unitName then +return player +end +end +return nil +end +function FOX:_GetPlayerFromUnit(unit) +if unit and unit:IsAlive()then +local unitname=unit:GetName() +for _,_player in pairs(self.players)do +local player=_player +if player.unitname==unitname then +return player +end +end +end +return nil +end +function FOX:_GetPlayerUnitAndName(_unitName) +self:F2(_unitName) +if _unitName~=nil then +local DCSunit=Unit.getByName(_unitName) +if DCSunit then +local playername=DCSunit:getPlayerName() +local unit=UNIT:Find(DCSunit) +self:T2({DCSunit=DCSunit,unit=unit,playername=playername}) +if DCSunit and unit and playername then +self:T(self.lid..string.format("Found DCS unit %s with player %s.",tostring(_unitName),tostring(playername))) +return unit,playername +end +end +end +return nil,nil +end +MANTIS={ +ClassName="MANTIS", +name="mymantis", +version="0.9.34", +SAM_Templates_Prefix="", +SAM_Group=nil, +EWR_Templates_Prefix="", +EWR_Group=nil, +Adv_EWR_Group=nil, +HQ_Template_CC="", +HQ_CC=nil, +SAM_Table={}, +SAM_Table_Long={}, +SAM_Table_Medium={}, +SAM_Table_Short={}, +SAM_Table_PointDef={}, +lid="", +Detection=nil, +AWACS_Detection=nil, +debug=false, +checkradius=25000, +grouping=5000, +acceptrange=80000, +detectinterval=30, +engagerange=95, +autorelocate=false, +advanced=false, +adv_ratio=100, +adv_state=0, +AWACS_Prefix="", +advAwacs=false, +verbose=false, +awacsrange=250000, +Shorad=nil, +ShoradLink=false, +ShoradTime=600, +ShoradActDistance=25000, +UseEmOnOff=false, +TimeStamp=0, +state2flag=false, +SamStateTracker={}, +DLink=false, +DLTimeStamp=0, +Padding=10, +SuppressedGroups={}, +automode=true, +autoshorad=true, +ShoradGroupSet=nil, +checkforfriendlies=false, +SmokeDecoy=false, +SmokeDecoyColor=SMOKECOLOR.White, +checkcounter=1, +DLinkCacheTime=120, +logsamstatus=false, +} +MANTIS.AdvancedState={ +GREEN=0, +AMBER=1, +RED=2, +} +MANTIS.SamType={ +SHORT="Short", +MEDIUM="Medium", +LONG="Long", +POINT="Point", +} +MANTIS.radiusscale={} +MANTIS.radiusscale[MANTIS.SamType.LONG]=1.1 +MANTIS.radiusscale[MANTIS.SamType.MEDIUM]=1.2 +MANTIS.radiusscale[MANTIS.SamType.SHORT]=1.75 +MANTIS.radiusscale[MANTIS.SamType.POINT]=3 +MANTIS.SamData={ +["Hawk"]={Range=35,Blindspot=0,Height=12,Type="Medium",Radar="Hawk"}, +["NASAMS"]={Range=14,Blindspot=0,Height=7,Type="Short",Radar="NSAMS"}, +["Patriot"]={Range=99,Blindspot=0,Height=25,Type="Long",Radar="Patriot str"}, +["Rapier"]={Range=10,Blindspot=0,Height=3,Type="Short",Radar="rapier"}, +["SA-2"]={Range=40,Blindspot=7,Height=25,Type="Medium",Radar="S_75M_Volhov"}, +["SA-3"]={Range=18,Blindspot=6,Height=18,Type="Short",Radar="5p73 s-125 ln"}, +["SA-5"]={Range=250,Blindspot=7,Height=40,Type="Long",Radar="5N62V"}, +["SA-6"]={Range=25,Blindspot=0,Height=8,Type="Medium",Radar="1S91"}, +["SA-10"]={Range=119,Blindspot=0,Height=18,Type="Long",Radar="S-300PS 4"}, +["SA-11"]={Range=35,Blindspot=0,Height=20,Type="Medium",Radar="SA-11"}, +["Roland"]={Range=6,Blindspot=0,Height=5,Type="Short",Radar="Roland"}, +["Gepard"]={Range=5,Blindspot=0,Height=4,Type="Point",Radar="Gepard"}, +["HQ-7"]={Range=12,Blindspot=0,Height=3,Type="Short",Radar="HQ-7"}, +["SA-9"]={Range=4,Blindspot=0,Height=3,Type="Point",Radar="Strela",Point="true"}, +["SA-8"]={Range=10,Blindspot=0,Height=5,Type="Short",Radar="Osa 9A33"}, +["SA-19"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Tunguska"}, +["SA-15"]={Range=11,Blindspot=0,Height=6,Type="Point",Radar="Tor 9A331",Point="true"}, +["SA-13"]={Range=5,Blindspot=0,Height=3,Type="Point",Radar="Strela",Point="true"}, +["Avenger"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Avenger"}, +["Chaparral"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Chaparral"}, +["Linebacker"]={Range=4,Blindspot=0,Height=3,Type="Point",Radar="Linebacker",Point="true"}, +["Silkworm"]={Range=90,Blindspot=1,Height=0.2,Type="Long",Radar="Silkworm"}, +["C-RAM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="HEMTT_C-RAM_Phalanx",Point="true"}, +["SA-10B"]={Range=75,Blindspot=0,Height=18,Type="Medium",Radar="SA-10B"}, +["SA-17"]={Range=50,Blindspot=3,Height=50,Type="Medium",Radar="SA-17"}, +["SA-20A"]={Range=150,Blindspot=5,Height=27,Type="Long",Radar="S-300PMU1"}, +["SA-20B"]={Range=200,Blindspot=4,Height=27,Type="Long",Radar="S-300PMU2"}, +["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, +["TAMIR IDFA"]={Range=20,Blindspot=0.6,Height=12.3,Type="Short",Radar="IRON_DOME_LN"}, +["STUNNER IDFA"]={Range=250,Blindspot=1,Height=45,Type="Long",Radar="DAVID_SLING_LN"}, +["NIKE"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, +["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"}, +["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, +["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, +["IRIS-T SLM"]={Range=40,Blindspot=0.5,Height=20,Type="Medium",Radar="CH_IRIST_SLM"}, +} +MANTIS.SamDataHDS={ +["SA-2 HDS"]={Range=56,Blindspot=7,Height=30,Type="Medium",Radar="V759"}, +["SA-3 HDS"]={Range=20,Blindspot=6,Height=30,Type="Short",Radar="V-601P"}, +["SA-10B HDS"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85CE ln"}, +["SA-10C HDS"]={Range=75,Blindspot=5,Height=25,Type="Long",Radar="5P85SE ln"}, +["SA-17 HDS"]={Range=50,Blindspot=3,Height=50,Type="Medium",Radar="SA-17 "}, +["SA-12 HDS 2"]={Range=100,Blindspot=13,Height=30,Type="Long",Radar="S-300V 9A82 l"}, +["SA-12 HDS 1"]={Range=75,Blindspot=6,Height=25,Type="Long",Radar="S-300V 9A83 l"}, +["SA-23 HDS 2"]={Range=200,Blindspot=5,Height=37,Type="Long",Radar="S-300VM 9A82ME"}, +["SA-23 HDS 1"]={Range=100,Blindspot=1,Height=50,Type="Long",Radar="S-300VM 9A83ME"}, +["HQ-2 HDS"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, +["SAMPT Block 1 HDS"]={Range=120,Blindspot=1,Height=20,Type="long",Radar="SAMPT_MLT_Blk1"}, +["SAMPT Block 1INT HDS"]={Range=150,Blindspot=1,Height=25,Type="long",Radar="SAMPT_MLT_Blk1NT"}, +["SAMPT Block 2 HDS"]={Range=200,Blindspot=10,Height=70,Type="long",Radar="SAMPT_MLT_Blk2"}, +} +MANTIS.SamDataSMA={ +["RBS98M SMA"]={Range=20,Blindspot=0.2,Height=8,Type="Short",Radar="RBS-98"}, +["RBS70 SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-70"}, +["RBS70M SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS70"}, +["RBS90 SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-90"}, +["RBS90M SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS90"}, +["RBS103A SMA"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_Rb103A"}, +["RBS103B SMA"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_Rb103B"}, +["RBS103AM SMA"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103A"}, +["RBS103BM SMA"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103B"}, +["Lvkv9040M SMA"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, +} +MANTIS.SamDataCH={ +["2S38 CHM"]={Range=6,Blindspot=0.1,Height=4.5,Type="Short",Radar="2S38"}, +["PantsirS1 CHM"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, +["PantsirS2 CHM"]={Range=30,Blindspot=1.2,Height=18,Type="Medium",Radar="PantsirS2"}, +["PGL-625 CHM"]={Range=10,Blindspot=1,Height=5,Type="Short",Radar="PGL_625"}, +["HQ-17A CHM"]={Range=15,Blindspot=1.5,Height=10,Type="Short",Radar="HQ17A"}, +["M903PAC2 CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="MIM104_M903_PAC2"}, +["M903PAC3 CHM"]={Range=160,Blindspot=1,Height=40,Type="Long",Radar="MIM104_M903_PAC3"}, +["TorM2 CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, +["TorM2K CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2K",Point="true"}, +["TorM2M CHM"]={Range=16,Blindspot=1,Height=10,Type="Point",Radar="TorM2M",Point="true"}, +["NASAMS3-AMRAAMER CHM"]={Range=50,Blindspot=2,Height=35.7,Type="Medium",Radar="CH_NASAMS3_LN_AMRAAM_ER"}, +["NASAMS3-AIM9X2 CHM"]={Range=20,Blindspot=0.2,Height=18,Type="Short",Radar="CH_NASAMS3_LN_AIM9X2"}, +["C-RAM CHM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="CH_Centurion_C_RAM",Point="true"}, +["PGZ-09 CHM"]={Range=4,Blindspot=0.5,Height=3,Type="Point",Radar="CH_PGZ09",Point="true"}, +["S350-9M100 CHM"]={Range=15,Blindspot=1,Height=8,Type="Short",Radar="CH_S350_50P6_9M100"}, +["S350-9M96D CHM"]={Range=150,Blindspot=2.5,Height=30,Type="Long",Radar="CH_S350_50P6_9M96D"}, +["LAV-AD CHM"]={Range=8,Blindspot=0.16,Height=4.8,Type="Short",Radar="CH_LAVAD"}, +["HQ-22 CHM"]={Range=170,Blindspot=5,Height=27,Type="Long",Radar="CH_HQ22_LN"}, +["PGZ-95 CHM"]={Range=2.5,Blindspot=0.5,Height=2,Type="Point",Radar="CH_PGZ95",Point="true"}, +["LD-3000 CHM"]={Range=2.5,Blindspot=0.1,Height=3,Type="Point",Radar="CH_LD3000_stationary",Point="true"}, +["LD-3000M CHM"]={Range=2.5,Blindspot=0.1,Height=3,Type="Point",Radar="CH_LD3000",Point="true"}, +["FlaRakRad CHM"]={Range=8,Blindspot=1.5,Height=6,Type="Short",Radar="CH_FlaRakRad"}, +["IRIS-T SLM CHM"]={Range=40,Blindspot=0.5,Height=20,Type="Medium",Radar="CH_IRIST_SLM"}, +["M903PAC2KAT1 CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="CH_MIM104_M903_PAC2_KAT1"}, +["Skynex CHM"]={Range=3.5,Blindspot=0.1,Height=3.5,Type="Point",Radar="CH_SkynexHX",Point="true"}, +["Skyshield CHM"]={Range=3.5,Blindspot=0.1,Height=3.5,Type="Point",Radar="CH_Skyshield_Gun",Point="true"}, +["WieselOzelot CHM"]={Range=8,Blindspot=0.16,Height=4.8,Type="Short",Radar="CH_Wiesel2Ozelot"}, +["BukM3-9M317M CHM"]={Range=70,Blindspot=0.25,Height=35,Type="Medium",Radar="CH_BukM3_9A317M"}, +["BukM3-9M317MA CHM"]={Range=70,Blindspot=0.25,Height=35,Type="Medium",Radar="CH_BukM3_9A317MA"}, +["SkySabre CHM"]={Range=30,Blindspot=0.5,Height=10,Type="Medium",Radar="CH_SkySabreLN"}, +["Stormer CHM"]={Range=7.5,Blindspot=0.3,Height=7,Type="Short",Radar="CH_StormerHVM"}, +["THAAD CHM"]={Range=200,Blindspot=40,Height=150,Type="Long",Radar="CH_THAAD_M1120"}, +["USInfantryFIM92K CHM"]={Range=8,Blindspot=0.16,Height=4.8,Type="Short",Radar="CH_USInfantry_FIM92"}, +["RBS98M CHM"]={Range=20,Blindspot=0.2,Height=8,Type="Short",Radar="RBS-98"}, +["RBS70 CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-70"}, +["RBS70M CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS70"}, +["RBS90 CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-90"}, +["RBS90M CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS90"}, +["RBS103A CHM"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_Rb103A"}, +["RBS103B CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_Rb103B"}, +["RBS103AM CHM"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103A"}, +["RBS103BM CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103B"}, +["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, +} +do +function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) +local self=BASE:Inherit(self,FSM:New()) +self.name=name or"mymantis" +self.SAM_Templates_Prefix=samprefix or"Red SAM" +self.EWR_Templates_Prefix=ewrprefix or"Red EWR" +self.HQ_Template_CC=hq or nil +self.Coalition=coalition or"red" +self.SAM_Table={} +self.SAM_Table_Long={} +self.SAM_Table_Medium={} +self.SAM_Table_Short={} +self.SAM_Table_PointDef={} +self.dynamic=dynamic or false +self.checkradius=25000 +self.grouping=5000 +self.acceptrange=80000 +self.detectinterval=30 +self.engagerange=95 +self.autorelocate=false +self.autorelocateunits={HQ=false,EWR=false} +self.advanced=false +self.adv_ratio=100 +self.adv_state=0 +self.verbose=false +self.Adv_EWR_Group=nil +self.AWACS_Prefix=awacs or nil +self.awacsrange=250000 +self.Shorad=nil +self.ShoradLink=false +self.ShoradTime=600 +self.ShoradActDistance=25000 +self.TimeStamp=timer.getAbsTime() +self.relointerval=math.random(1800,3600) +self.state2flag=false +self.SamStateTracker={} +self.DLink=false +self.Padding=Padding or 10 +self.SuppressedGroups={} +self.automode=true +self.usezones=false +self.AcceptZones={} +self.RejectZones={} +self.ConflictZones={} +self.maxlongrange=1 +self.maxmidrange=2 +self.maxshortrange=2 +self.maxpointdefrange=6 +self.maxclassic=6 +self.autoshorad=true +self.ShoradGroupSet=SET_GROUP:New() +self.FilterZones=Zones +self.SkateZones=nil +self.SkateNumber=3 +self.shootandscoot=false +self.SmokeDecoy=false +self.SmokeDecoyColor=SMOKECOLOR.White +self.UseEmOnOff=true +if EmOnOff==false then +self.UseEmOnOff=false +end +if type(awacs)=="string"then +self.advAwacs=true +else +self.advAwacs=false +end +self:SetDLinkCacheTime() +self.lid=string.format("MANTIS %s | ",self.name) +if self.debug then +BASE:TraceOnOff(true) +BASE:TraceClass(self.ClassName) +BASE:TraceLevel(1) +end +self.ewr_templates={} +if type(samprefix)~="table"then +self.SAM_Templates_Prefix={samprefix} +end +if type(ewrprefix)~="table"then +self.EWR_Templates_Prefix={ewrprefix} +end +for _,_group in pairs(self.SAM_Templates_Prefix)do +table.insert(self.ewr_templates,_group) +end +for _,_group in pairs(self.EWR_Templates_Prefix)do +table.insert(self.ewr_templates,_group) +end +if self.advAwacs then +table.insert(self.ewr_templates,awacs) +end +self.logsamstatus=false +self:T({self.ewr_templates}) +self.SAM_Group=SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition) +self.EWR_Group=SET_GROUP:New():FilterPrefixes(self.ewr_templates):FilterCoalitions(self.Coalition) +if self.FilterZones then +self.SAM_Group:FilterZones(self.FilterZones) +end +if self.dynamic then +self.SAM_Group:FilterStart() +self.EWR_Group:FilterStart() +else +self.SAM_Group:FilterOnce() +self.EWR_Group:FilterOnce() +end +if self.HQ_Template_CC then +self.HQ_CC=GROUP:FindByName(self.HQ_Template_CC) +end +self.checkcounter=1 +self:I(string.format("***** Starting MANTIS Version %s *****",self.version)) +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","Relocating","*") +self:AddTransition("*","GreenState","*") +self:AddTransition("*","RedState","*") +self:AddTransition("*","AdvStateChange","*") +self:AddTransition("*","ShoradActivated","*") +self:AddTransition("*","SeadSuppressionStart","*") +self:AddTransition("*","SeadSuppressionEnd","*") +self:AddTransition("*","SeadSuppressionPlanned","*") +self:AddTransition("*","Stop","Stopped") +return self +end +function MANTIS:_GetSAMTable() +self:T(self.lid.."GetSAMTable") +return self.SAM_Table +end +function MANTIS:_SetSAMTable(table) +self:T(self.lid.."SetSAMTable") +self.SAM_Table=table +return self +end +function MANTIS:SetEWRGrouping(radius) +self:T(self.lid.."SetEWRGrouping") +local radius=radius or 5000 +self.grouping=radius +return self +end +function MANTIS:AddScootZones(ZoneSet,Number,Random,Formation) +self:T(self.lid.." AddScootZones") +self.SkateZones=ZoneSet +self.SkateNumber=Number or 3 +self.shootandscoot=true +self.ScootRandom=Random +self.ScootFormation=Formation or"Cone" +return self +end +function MANTIS:AddZones(AcceptZones,RejectZones,ConflictZones) +self:T(self.lid.."AddZones") +self.AcceptZones=AcceptZones or{} +self.RejectZones=RejectZones or{} +self.ConflictZones=ConflictZones or{} +self.AcceptZonesNo=UTILS.TableLength(self.AcceptZones) +self.RejectZonesNo=UTILS.TableLength(self.RejectZones) +self.ConflictZonesNo=UTILS.TableLength(self.ConflictZones) +self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) +if self.AcceptZonesNo>0 or self.RejectZonesNo>0 or self.ConflictZonesNo>0 then +self.usezones=true +end +return self +end +function MANTIS:SetEWRRange(radius) +self:T(self.lid.."SetEWRRange") +return self +end +function MANTIS:SetSAMRadius(radius) +self:T(self.lid.."SetSAMRadius") +local radius=radius or 25000 +self.checkradius=radius +return self +end +function MANTIS:SetSAMRange(range) +self:T(self.lid.."SetSAMRange") +local range=range or 95 +if range<0 or range>100 then +range=95 +end +self.engagerange=range +return self +end +function MANTIS:SetSmokeDecoy(Onoff,Color) +self.SmokeDecoy=Onoff +self.SmokeDecoyColor=Color or SMOKECOLOR.White +return self +end +function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) +self:T(self.lid.."SetMaxActiveSAMs") +self.maxclassic=Classic or 6 +self.maxlongrange=Long or 1 +self.maxmidrange=Mid or 2 +self.maxshortrange=Short or 2 +self.maxpointdefrange=Point or 6 +return self +end +function MANTIS:SetNewSAMRangeWhileRunning(range) +self:T(self.lid.."SetNewSAMRangeWhileRunning") +local range=range or 95 +if range<0 or range>100 then +range=95 +end +self.engagerange=range +self:_RefreshSAMTable() +self.mysead.EngagementRange=range +return self +end +function MANTIS:Debug(onoff) +self:T(self.lid.."SetDebug") +local onoff=onoff or false +self.debug=onoff +if onoff then +BASE:TraceOn() +BASE:TraceClass("MANTIS") +BASE:TraceLevel(1) +else +BASE:TraceOff() +end +return self +end +function MANTIS:GetCommandCenter() +self:T(self.lid.."GetCommandCenter") +if self.HQ_CC then +return self.HQ_CC +else +return nil +end +end +function MANTIS:SetAwacs(prefix) +self:T(self.lid.."SetAwacs") +if prefix~=nil then +if type(prefix)=="string"then +self.AWACS_Prefix=prefix +self.advAwacs=true +end +end +return self +end +function MANTIS:SetAwacsRange(range) +self:T(self.lid.."SetAwacsRange") +local range=range or 250000 +self.awacsrange=range +return self +end +function MANTIS:SetCommandCenter(group) +self:T(self.lid.."SetCommandCenter") +local group=group or nil +if group~=nil then +if type(group)=="string"then +self.HQ_CC=GROUP:FindByName(group) +self.HQ_Template_CC=group +else +self.HQ_CC=group +self.HQ_Template_CC=group:GetName() +end +end +return self +end +function MANTIS:SetDLinkCacheTime(seconds) +self.DLinkCacheTime=math.abs(seconds or 120) +if self.DLinkCacheTime<5 then self.DLinkCacheTime=5 end +return self +end +function MANTIS:SetDetectInterval(interval) +self:T(self.lid.."SetDetectInterval") +local interval=interval or 30 +self.detectinterval=interval +return self +end +function MANTIS:SetAdvancedMode(onoff,ratio) +self:T(self.lid.."SetAdvancedMode") +local onoff=onoff or false +local ratio=ratio or 100 +if(type(self.HQ_Template_CC)=="string")and onoff and self.dynamic then +self.adv_ratio=ratio +self.advanced=true +self.adv_state=0 +self.Adv_EWR_Group=SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() +self:I(string.format("***** Starting Advanced Mode MANTIS Version %s *****",self.version)) +else +local text=self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both." +local m=MESSAGE:New(text,10,"MANTIS",true):ToAll() +self:E(text) +end +return self +end +function MANTIS:SetUsingEmOnOff(switch) +self:T(self.lid.."SetUsingEmOnOff") +self.UseEmOnOff=switch or false +return self +end +function MANTIS:SetUsingDLink(DLink) +self:T(self.lid.."SetUsingDLink") +self.DLink=true +self.Detection=DLink +self.DLTimeStamp=timer.getAbsTime() +return self +end +function MANTIS:_CheckHQState() +self:T(self.lid.."CheckHQState") +local text=self.lid.." Checking HQ State" +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then self:I(text)end +if self.advanced then +local hq=self.HQ_Template_CC +local hqgrp=GROUP:FindByName(hq) +if hqgrp then +if hqgrp:IsAlive()then +return true +else +return false +end +end +end +return self +end +function MANTIS:_CheckEWRState() +self:T(self.lid.."CheckEWRState") +local text=self.lid.." Checking EWR State" +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then self:I(text)end +if self.advanced then +local EWR_Group=self.Adv_EWR_Group +local nalive=EWR_Group:CountAlive() +if self.advAwacs then +local awacs=GROUP:FindByName(self.AWACS_Prefix) +if awacs~=nil then +if awacs:IsAlive()then +nalive=nalive+1 +end +end +end +if nalive>0 then +return true +else +return false +end +end +return self +end +function MANTIS:_CheckAnyEWRAlive() +self:T(self.lid.."_CheckAnyEWRAlive") +local alive=false +if self.EWR_Group:CountAlive()>0 then +alive=true +end +if not alive and self.AWACS_Prefix then +local awacs=GROUP:FindByName(self.AWACS_Prefix) +if awacs and awacs:IsAlive()then +alive=true +end +end +return alive +end +function MANTIS:_CalcAdvState() +self:T(self.lid.."CalcAdvState") +local m=MESSAGE:New(self.lid.." Calculating Advanced State",10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then self:I(self.lid.." Calculating Advanced State")end +local currstate=self.adv_state +local EWR_State=self:_CheckEWRState() +local HQ_State=self:_CheckHQState() +if EWR_State and HQ_State then +self.adv_state=0 +elseif EWR_State or HQ_State then +self.adv_state=1 +else +self.adv_state=2 +end +local interval=self.detectinterval +local ratio=self.adv_ratio/100 +ratio=ratio*self.adv_state +local newinterval=interval+(interval*ratio) +if self.debug or self.verbose then +local text=self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d",currstate,self.adv_state,newinterval) +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then self:I(text)end +end +return newinterval,currstate +end +function MANTIS:SetAutoRelocate(hq,ewr) +self:T(self.lid.."SetAutoRelocate") +local hqrel=hq or false +local ewrel=ewr or false +if hqrel or ewrel then +self.autorelocate=true +self.autorelocateunits={HQ=hqrel,EWR=ewrel} +end +return self +end +function MANTIS:_RelocateGroups() +self:T(self.lid.."RelocateGroups") +local text=self.lid.." Relocating Groups" +local m=MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug) +if self.verbose then self:I(text)end +if self.autorelocate then +local HQGroup=self.HQ_CC +if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive()then +local _hqgrp=self.HQ_CC +local text=self.lid.." Relocating HQ" +_hqgrp:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +if self.autorelocateunits.EWR then +local EWR_GRP=SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() +local EWR_Grps=EWR_GRP.Set +for _,_grp in pairs(EWR_Grps)do +if _grp:IsAlive()and _grp:IsGround()then +local text=self.lid.." Relocating EWR ".._grp:GetName() +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then self:I(text)end +_grp:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +end +end +end +return self +end +function MANTIS:_CheckCoordinateInZones(coord) +self:T(self.lid.."_CheckCoordinateInZones") +local inzone=false +self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) +if self.AcceptZonesNo>0 then +for _,_zone in pairs(self.AcceptZones)do +local zone=_zone +if zone:IsCoordinateInZone(coord)then +inzone=true +self:T(self.lid.."Target coord in Accept Zone!") +break +end +end +end +if self.RejectZonesNo>0 then +for _,_zone in pairs(self.RejectZones)do +local zone=_zone +if zone:IsCoordinateInZone(coord)then +inzone=false +self:T(self.lid.."Target coord in Reject Zone!") +break +end +end +end +if self.ConflictZonesNo>0 then +for _,_zone in pairs(self.ConflictZones)do +local zone=_zone +if zone:IsCoordinateInZone(coord)then +inzone=true +self:T(self.lid.."Target coord in Conflict Zone!") +break +end +end +end +return inzone +end +function MANTIS:_PreFilterHeight(height) +self:T(self.lid.."_PreFilterHeight") +local set={} +local dlink=self.Detection +local detectedgroups=dlink:GetContactTable() +for _,_contact in pairs(detectedgroups)do +local contact=_contact +local grp=contact.group +if grp:IsAlive()then +if grp:GetHeight(true)29)then +self.DLink=false +self.Detection=self:StartDetection() +self:I(self.lid.."Intel DLink not running - switching back to single detection!") +end +end +function MANTIS:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:T(self.lid.."Starting MANTIS") +self:SetSAMStartState() +if not INTEL then +self.Detection=self:StartDetection() +else +self.Detection=self:StartIntelDetection() +end +if self.autoshorad then +self.Shorad=SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff) +self.Shorad:SetDefenseLimits(80,95) +self.ShoradLink=true +self.Shorad.Groupset=self.ShoradGroupSet +self.Shorad.debug=self.debug +end +if self.shootandscoot and self.SkateZones and self.Shorad then +self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation) +end +self:__Status(-math.random(1,10)) +return self +end +function MANTIS:onbeforeStatus(From,Event,To) +self:T({From,Event,To}) +if not self.state2flag then +self:_Check(self.Detection,self.DLink,self.logsamstatus) +end +local EWRAlive=self:_CheckAnyEWRAlive() +local function FindSAMSRTR() +for i=1,1000 do +local randomsam=self.SAM_Group:GetRandom() +if randomsam and randomsam:IsAlive()then +if randomsam:IsSAM()then return randomsam end +end +end +end +if not EWRAlive then +local randomsam=FindSAMSRTR() +if randomsam and randomsam:IsAlive()then +if self.UseEmOnOff then +randomsam:EnableEmission(true) +else +randomsam:OptionAlarmStateRed() +end +local name=randomsam:GetName() +if self.SamStateTracker[name]~="RED"then +self:__RedState(1,randomsam) +self.SamStateTracker[name]="RED" +end +end +end +if self.autorelocate then +local relointerval=self.relointerval +local thistime=timer.getAbsTime() +local timepassed=thistime-self.TimeStamp +local halfintv=math.floor(timepassed/relointerval) +if halfintv>=1 then +self.TimeStamp=timer.getAbsTime() +self:_Relocate() +self:__Relocating(1) +end +end +if self.advanced then +self:_CheckAdvState() +end +if self.DLink then +self:_CheckDLinkState() +end +return self +end +function MANTIS:onafterStatus(From,Event,To) +self:T({From,Event,To}) +if self.debug and self.verbose then +self:I(self.lid.."Status Report") +for _name,_state in pairs(self.SamStateTracker)do +self:I(string.format("Site %s | Status %s",_name,_state)) +end +end +local interval=self.detectinterval*-1 +self:__Status(interval) +return self +end +function MANTIS:onafterStop(From,Event,To) +self:T({From,Event,To}) +return self +end +function MANTIS:onafterRelocating(From,Event,To) +self:T({From,Event,To}) +return self +end +function MANTIS:onafterGreenState(From,Event,To,Group) +self:T({From,Event,To,Group:GetName()}) +return self +end +function MANTIS:onafterRedState(From,Event,To,Group) +self:T({From,Event,To,Group:GetName()}) +return self +end +function MANTIS:onafterAdvStateChange(From,Event,To,Oldstate,Newstate,Interval) +self:T({From,Event,To,Oldstate,Newstate,Interval}) +return self +end +function MANTIS:onafterShoradActivated(From,Event,To,Name,Radius,Ontime) +self:T({From,Event,To,Name,Radius,Ontime}) +return self +end +function MANTIS:onafterSeadSuppressionStart(From,Event,To,Group,Name,Attacker) +self:T({From,Event,To,Name}) +self.SuppressedGroups[Name]=true +if self.ShoradLink then +local Shorad=self.Shorad +local radius=self.checkradius +local ontime=self.ShoradTime +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) +self:__ShoradActivated(1,Name,radius,ontime) +end +return self +end +function MANTIS:onafterSeadSuppressionEnd(From,Event,To,Group,Name) +self:T({From,Event,To,Name}) +self.SuppressedGroups[Name]=false +return self +end +function MANTIS:onafterSeadSuppressionPlanned(From,Event,To,Group,Name,SuppressionStartTime,SuppressionEndTime,Attacker) +self:T({From,Event,To,Name}) +return self +end +end +SHORAD={ +ClassName="SHORAD", +name="MyShorad", +debug=false, +Prefixes="", +Radius=20000, +Groupset=nil, +Samset=nil, +Coalition=nil, +ActiveTimer=600, +ActiveGroups={}, +lid="", +DefendHarms=true, +DefendMavs=true, +DefenseLowProb=70, +DefenseHighProb=90, +UseEmOnOff=true, +shootandscoot=false, +SkateNumber=3, +SkateZones=nil, +minscootdist=100, +maxscootdist=3000, +scootrandomcoord=false, +} +do +SHORAD.Harms={ +["AGM_88"]="AGM_88", +["AGM_122"]="AGM_122", +["AGM_84"]="AGM_84", +["AGM_45"]="AGM_45", +["ALARM"]="ALARM", +["LD-10"]="LD-10", +["X_58"]="X_58", +["X_28"]="X_28", +["X_25"]="X_25", +["X_31"]="X_31", +["Kh25"]="Kh25", +["HY-2"]="HY-2", +["ADM_141A"]="ADM_141A", +} +SHORAD.Mavs={ +["AGM"]="AGM", +["C-701"]="C-701", +["Kh25"]="Kh25", +["Kh29"]="Kh29", +["Kh31"]="Kh31", +["Kh66"]="Kh66", +} +function SHORAD:New(Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition,UseEmOnOff) +local self=BASE:Inherit(self,FSM:New()) +self:T({Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition}) +local GroupSet=SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() +self.name=Name or"MyShorad" +self.Prefixes=ShoradPrefix or"SAM SHORAD" +self.Radius=Radius or 20000 +self.Coalition=Coalition or"blue" +self.Samset=Samset or GroupSet +self.ActiveTimer=ActiveTimer or 600 +self.ActiveGroups={} +self.Groupset=GroupSet +self.DefendHarms=true +self.DefendMavs=true +self.DefenseLowProb=70 +self.DefenseHighProb=90 +self.UseEmOnOff=true +if UseEmOnOff==false then self.UseEmOnOff=UseEmOnOff end +self:I("*** SHORAD - Started Version 0.3.4") +self.lid=string.format("SHORAD %s | ",self.name) +self:_InitState() +self:HandleEvent(EVENTS.Shot,self.HandleEventShot) +self:SetStartState("Running") +self:AddTransition("*","WakeUpShorad","*") +self:AddTransition("*","CalculateHitZone","*") +self:AddTransition("*","ShootAndScoot","*") +return self +end +function SHORAD:_InitState() +self:T(self.lid.." _InitState") +local table={} +local set=self.Groupset +self:T({set=set}) +local aliveset=set:GetAliveSet() +for _,_group in pairs(aliveset)do +if self.UseEmOnOff then +_group:EnableEmission(false) +_group:OptionAlarmStateRed() +else +_group:OptionAlarmStateGreen() +end +_group:OptionDisperseOnAttack(30) +end +for i=1,100 do +math.random() +end +return self +end +function SHORAD:AddScootZones(ZoneSet,Number,Random,Formation) +self:T(self.lid.." AddScootZones") +self.SkateZones=ZoneSet +self.SkateNumber=Number or 3 +self.shootandscoot=true +self.scootrandomcoord=Random +self.scootformation=Formation or"Cone" +return self +end +function SHORAD:SwitchDebug(onoff) +self:T({onoff}) +if onoff then +self:SwitchDebugOn() +else +self:SwitchDebugOff() +end +return self +end +function SHORAD:SwitchDebugOn() +self.debug=true +BASE:TraceOn() +BASE:TraceClass("SHORAD") +return self +end +function SHORAD:SwitchDebugOff() +self.debug=false +BASE:TraceOff() +return self +end +function SHORAD:SwitchHARMDefense(onoff) +self:T({onoff}) +local onoff=onoff or true +self.DefendHarms=onoff +return self +end +function SHORAD:SwitchAGMDefense(onoff) +self:T({onoff}) +local onoff=onoff or true +self.DefendMavs=onoff +return self +end +function SHORAD:SetDefenseLimits(low,high) +self:T({low,high}) +local low=low or 70 +local high=high or 90 +if(low<0)or(low>100)or(low>high)then +low=70 +end +if(high<0)or(high>100)or(high%s",location) +end +text=Text..location.."!" +local ttstext=Text..location.."! Repeat! "..location +if _coalition==self.coalition then +if self.verbose then +MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) +end +if self.SRSRadio then +local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) +sound:SetPlayWithSRS(true) +self.SRS:PlaySoundFile(sound,2) +elseif self.DCSRadio then +self:DCSRadioBroadcast(Soundfile,Soundlength,text) +elseif self.SRSTTSRadio then +if self.SRSPilotVoice then +self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1) +else +self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1) +end +end +end +if _coalition==self.coalition and distancetofarp<=self.maxdistance then +self:T(self.lid.."Spawning new Pilot") +self.pilotindex=self.pilotindex+1 +local newpilot=SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template,self.pilotindex)) +newpilot:InitDelayOff() +newpilot:OnSpawnGroup( +function(grp) +self.pilotqueue[self.pilotindex]=grp +end +) +newpilot:SpawnFromCoordinate(_LandingPos) +self:__PilotDown(2,_LandingPos,true) +elseif _coalition==self.coalition and distancetofarp>self.maxdistance then +self:T(self.lid.."Pilot out of reach") +self:__PilotDown(2,_LandingPos,false) +end +return self +end +function AICSAR:_EventHandler(EventData,FromEject) +self:T(self.lid.."OnEventLandingAfterEjection ID="..EventData.id) +if self.autoonoff then +if self.playerset:CountAlive()>0 then +return self +end +end +if self.UseEventEject and(not FromEject)then return self end +local _event=EventData +local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p) +local _country=_event.initiator:getCountry() +local _coalition=coalition.getCountryCoalition(_country) +local distancetofarp=_LandingPos:Get2DDistance(self.farp:GetCoordinate()) +if self.UseRescueZone==true and self.RescueZone~=nil then +if self.RescueZone:IsCoordinateInZone(_LandingPos)then +distancetofarp=self.maxdistance-10 +else +distancetofarp=self.maxdistance+10 +end +end +local Text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTDOWN",self.locale) +local text="" +local setting={} +setting.MGRS_Accuracy=self.MGRS_Accuracy +local location=_LandingPos:ToStringMGRS(setting) +local msgtxt=Text..location.."!" +location=string.gsub(location,"MGRS ","") +location=string.gsub(location,"%s+","") +location=string.gsub(location,"([%a%d])","%1;") +location=string.gsub(location,"0","zero") +location=string.gsub(location,"9","niner") +location="MGRS;"..location +if self.SRSGoogle then +location=string.format("%s",location) +end +text=Text..location.."!" +local ttstext=Text..location.."! Repeat! "..location +if _coalition==self.coalition then +if self.verbose then +MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) +end +if self.SRSRadio then +local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) +sound:SetPlayWithSRS(true) +self.SRS:PlaySoundFile(sound,2) +elseif self.DCSRadio then +self:DCSRadioBroadcast(Soundfile,Soundlength,text) +elseif self.SRSTTSRadio then +if self.SRSPilotVoice then +self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1) +else +self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1) +end +end +end +if _coalition==self.coalition and distancetofarp<=self.maxdistance then +self:T(self.lid.."Spawning new Pilot") +self.pilotindex=self.pilotindex+1 +local newpilot=SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template,self.pilotindex)) +newpilot:InitDelayOff() +newpilot:OnSpawnGroup( +function(grp) +self.pilotqueue[self.pilotindex]=grp +end +) +newpilot:SpawnFromCoordinate(_LandingPos) +Unit.destroy(_event.initiator) +self:__PilotDown(2,_LandingPos,true) +elseif _coalition==self.coalition and distancetofarp>self.maxdistance then +self:T(self.lid.."Pilot out of reach") +self:__PilotDown(2,_LandingPos,false) +end +return self +end +function AICSAR:_GetFlight() +self:T(self.lid.."_GetFlight") +local newhelo=SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000)) +:InitDelayOff() +:InitUnControlled(true) +:OnSpawnGroup( +function(Group) +Group:OptionPreferVerticalLanding() +self:__HeloOnDuty(1,Group) +end +) +:Spawn() +local nhelo=FLIGHTGROUP:New(newhelo) +nhelo:SetHomebase(self.farp) +nhelo:Activate() +return nhelo +end +function AICSAR:_InitMission(Pilot,Index) +self:T(self.lid.."_InitMission") +local pickupzone=ZONE_GROUP:New(Pilot:GetName(),Pilot,self.rescuezoneradius) +local opstransport=OPSTRANSPORT:New(Pilot,pickupzone,self.farpzone) +local helo=self:_GetFlight() +helo.AICSARReserved=true +helo:SetDefaultAltitude(self.Altitude or 1500) +helo:SetDefaultSpeed(self.Speed or 100) +helo:AddOpsTransport(opstransport) +local function AICPickedUp(Helo,Cargo,Index) +self:__PilotPickedUp(2,Helo,Cargo,Index) +end +local function AICHeloDead(Helo,Index) +self:__HeloDown(2,Helo,Index) +end +local function AICHeloUnloaded(Helo,OpsGroup) +self:__PilotUnloaded(2,Helo,OpsGroup) +end +function helo:OnAfterLoading(From,Event,To) +AICPickedUp(helo,helo:GetCargoGroups(),Index) +helo:__LoadingDone(5) +end +function helo:OnAfterDead(From,Event,To) +AICHeloDead(helo,Index) +end +function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo) +AICHeloUnloaded(helo,OpsGroupCargo) +helo:__UnloadingDone(5) +end +function helo:OnAfterLandAtAirbase(From,Event,To,airbase) +helo:Despawn(2) +end +self.helos[Index]=helo +return self +end +function AICSAR:_CheckInMashZone(Pilot) +self:T(self.lid.."_CheckInMashZone") +if Pilot:IsInZone(self.farpzone)then +return true +else +return false +end +end +function AICSAR:SetDefaultSpeed(Knots) +self:T(self.lid.."SetDefaultSpeed") +self.Speed=Knots or 100 +return self +end +function AICSAR:SetDefaultAltitude(Feet) +self:T(self.lid.."SetDefaultAltitude") +self.Altitude=Feet or 1500 +return self +end +function AICSAR:_CheckHelos() +self:T(self.lid.."_CheckHelos") +for _index,_helo in pairs(self.helos)do +local helo=_helo +if helo and helo.ClassName=="FLIGHTGROUP"then +local state=helo:GetState() +local name=helo:GetName() +self:T("Helo group "..name.." in state "..state) +if state=="Arrived"then +helo.OnAfterDead=nil +helo:Despawn(35) +self.helos[_index]=nil +end +else +self.helos[_index]=nil +end +end +return self +end +function AICSAR:_CountHelos() +self:T(self.lid.."_CountHelos") +local count=0 +for _index,_helo in pairs(self.helos)do +count=count+1 +end +return count +end +function AICSAR:_CheckQueue(OpsGroup) +self:T(self.lid.."_CheckQueue") +for _index,_pilot in pairs(self.pilotqueue)do +local classname=_pilot.ClassName and _pilot.ClassName or"NONE" +local name=_pilot.GroupName and _pilot.GroupName or"NONE" +local playername="John Doe" +local helocount=self:_CountHelos() +if _pilot and _pilot.ClassName and _pilot.ClassName=="GROUP"then +local flightgroup=self.helos[_index] +if self:_CheckInMashZone(_pilot)then +self:T("Pilot".._pilot.GroupName.." rescued!") +if OpsGroup then +else +_pilot:Destroy(true,10) +end +self.pilotqueue[_index]=nil +self.rescued[_index]=true +if self.PilotStore:Count()>0 then +playername=self.PilotStore:Pull() +end +self:__PilotRescued(2,playername) +if flightgroup then +flightgroup.AICSARReserved=false +end +end +if not _pilot.AICSAR then +if self.limithelos and helocount>=self.helonumber then +break +end +_pilot.AICSAR={} +_pilot.AICSAR.Status="Initiated" +_pilot.AICSAR.Boarded=false +self:_InitMission(_pilot,_index) +break +else +if flightgroup then +local state=flightgroup:GetState() +_pilot.AICSAR.Status=state +end +end +end +end +return self +end +function AICSAR:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:__Status(3) +return self +end +function AICSAR:onafterStatus(From,Event,To) +self:T({From,Event,To}) +self:_CheckHelos() +self:__Status(30) +return self +end +function AICSAR:onafterStop(From,Event,To) +self:T({From,Event,To}) +self:UnHandleEvent(EVENTS.LandingAfterEjection) +if self.DCSRadioQueue then +self.DCSRadioQueue:Stop() +end +return self +end +function AICSAR:onafterPilotDown(From,Event,To,Coordinate,InReach) +self:T({From,Event,To}) +local CoordinateText=Coordinate:ToStringMGRS() +local inreach=tostring(InReach) +if InReach then +local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("INITIALOK",self.locale) +self:T(text) +if self.verbose then +MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) +end +if self.SRSRadio then +local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) +sound:SetPlayWithSRS(true) +self.SRS:PlaySoundFile(sound,2) +elseif self.DCSRadio then +self:DCSRadioBroadcast(Soundfile,Soundlength,text) +elseif self.SRSTTSRadio then +if self.SRSOperatorVoice then +self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) +else +self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) +end +end +else +local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("INITIALNOTOK",self.locale) +self:T(text) +if self.verbose then +MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) +end +if self.SRSRadio then +local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) +sound:SetPlayWithSRS(true) +self.SRS:PlaySoundFile(sound,2) +elseif self.DCSRadio then +self:DCSRadioBroadcast(Soundfile,Soundlength,text) +elseif self.SRSTTSRadio then +if self.SRSOperatorVoice then +self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) +else +self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) +end +end +end +self:_CheckQueue() +return self +end +function AICSAR:onafterPilotKIA(From,Event,To) +self:T({From,Event,To}) +local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTKIA",self.locale) +if self.verbose then +MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) +end +if self.SRSRadio then +local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) +sound:SetPlayWithSRS(true) +self.SRS:PlaySoundFile(sound,2) +elseif self.DCSRadio then +self:DCSRadioBroadcast(Soundfile,Soundlength,text) +elseif self.SRSTTSRadio then +self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) +end +return self +end +function AICSAR:onafterHeloDown(From,Event,To,Helo,Index) +self:T({From,Event,To}) +local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("HELODOWN",self.locale) +if self.verbose then +MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) +end +if self.SRSRadio then +local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) +sound:SetPlayWithSRS(true) +self.SRS:PlaySoundFile(sound,2) +elseif self.DCSRadio then +self:DCSRadioBroadcast(Soundfile,Soundlength,text) +elseif self.SRSTTSRadio then +if self.SRSOperatorVoice then +self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) +else +self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) +end +end +local findex=0 +local fhname=Helo:GetName() +if Index and Index>0 then +findex=Index +else +for _index,_helo in pairs(self.helos)do +local helo=_helo +local hname=helo:GetName() +if fhname==hname then +findex=_index +break +end +end +end +if findex>0 and not self.rescued[findex]then +local pilot=self.pilotqueue[findex] +self.helos[findex]=nil +if pilot.AICSAR.Boarded then +self:T("Helo Down: Found DEAD Pilot ID "..findex.." with name "..pilot:GetName()) +self:__PilotKIA(2) +self.pilotqueue[findex]=nil +else +self:T("Helo Down: Found ALIVE Pilot ID "..findex.." with name "..pilot:GetName()) +self:_InitMission(pilot,findex) +end +end +return self +end +function AICSAR:onafterPilotRescued(From,Event,To,PilotName) +self:T({From,Event,To}) +local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTRESCUED",self.locale) +if self.verbose then +MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) +end +if self.SRSRadio then +local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) +sound:SetPlayWithSRS(true) +self.SRS:PlaySoundFile(sound,2) +elseif self.DCSRadio then +self:DCSRadioBroadcast(Soundfile,Soundlength,text) +elseif self.SRSTTSRadio then +self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) +end +return self +end +function AICSAR:onafterPilotUnloaded(From,Event,To,Helo,OpsGroup) +self:T({From,Event,To}) +self:_CheckQueue(OpsGroup) +return self +end +function AICSAR:onafterPilotPickedUp(From,Event,To,Helo,CargoTable,Index) +self:T({From,Event,To}) +local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTINHELO",self.locale) +if self.verbose then +MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) +end +if self.SRSRadio then +local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) +sound:SetPlayWithSRS(true) +self.SRS:PlaySoundFile(sound,2) +elseif self.DCSRadio then +self:DCSRadioBroadcast(Soundfile,Soundlength,text) +elseif self.SRSTTSRadio then +self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) +end +local findex=0 +local fhname=Helo:GetName() +if Index and Index>0 then +findex=Index +else +for _index,_helo in pairs(self.helos)do +local helo=_helo +local hname=helo:GetName() +if fhname==hname then +findex=_index +break +end +end +end +if findex>0 then +local pilot=self.pilotqueue[findex] +self:T("Boarded: Found Pilot ID "..findex.." with name "..pilot:GetName()) +pilot.AICSAR.Boarded=true +end +return self +end +AMMOTRUCK={ +ClassName="AMMOTRUCK", +lid="", +version="0.0.12", +alias="", +debug=false, +trucklist={}, +targetlist={}, +coalition=nil, +truckset=nil, +targetset=nil, +remunitionqueue={}, +waitingtargets={}, +ammothreshold=5, +remunidist=20000, +monitor=-60, +unloadtime=600, +waitingtime=1800, +routeonroad=true, +reloads=5, +} +AMMOTRUCK.State={ +IDLE="idle", +DRIVING="driving", +ARRIVED="arrived", +UNLOADING="unloading", +RETURNING="returning", +WAITING="waiting", +RELOADING="reloading", +OUTOFAMMO="outofammo", +REQUESTED="requested", +} +function AMMOTRUCK:New(Truckset,Targetset,Coalition,Alias,Homezone) +local self=BASE:Inherit(self,FSM:New()) +self.truckset=Truckset +self.targetset=Targetset +self.coalition=Coalition +self.alias=Alias +self.debug=false +self.remunitionqueue={} +self.trucklist={} +self.targetlist={} +self.ammothreshold=5 +self.remunidist=20000 +self.homezone=Homezone +self.waitingtime=1800 +self.usearmygroup=false +self.hasarmygroup=false +self.lid=string.format("AMMOTRUCK %s | %s | ",self.version,self.alias) +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Monitor","*") +self:AddTransition("*","RouteTruck","*") +self:AddTransition("*","TruckArrived","*") +self:AddTransition("*","TruckUnloading","*") +self:AddTransition("*","TruckReturning","*") +self:AddTransition("*","TruckHome","*") +self:AddTransition("*","Stop","Stopped") +self:__Start(math.random(5,10)) +self:I(self.lid.."Started") +return self +end +function AMMOTRUCK:CheckDrivingTrucks(dataset) +self:T(self.lid.." CheckDrivingTrucks") +local data=dataset +for _,_data in pairs(data)do +local truck=_data +local coord=truck.group:GetCoordinate() +local tgtcoord=truck.targetcoordinate +local dist=coord:Get2DDistance(tgtcoord) +if dist<=150 then +truck.statusquo=AMMOTRUCK.State.ARRIVED +truck.timestamp=timer.getAbsTime() +truck.coordinate=coord +self:__TruckArrived(1,truck) +end +local Tnow=timer.getAbsTime() +if Tnow-truck.timestamp>30 then +local group=truck.group +if self.usearmygroup then +group=truck.group:GetGroup() +end +local currspeed=group:GetVelocityKMH() +if truck.lastspeed then +if truck.lastspeed==0 and currspeed==0 then +self:T(truck.group:GetName().." Is not moving!") +truck.timestamp=timer.getAbsTime() +if self.routeonroad then +group:RouteGroundOnRoad(truck.targetcoordinate,30,2,"Vee") +else +group:RouteGroundTo(truck.targetcoordinate,30,"Vee",2) +end +end +truck.lastspeed=currspeed +else +truck.lastspeed=currspeed +truck.timestamp=timer.getAbsTime() +end +self:I({truck=truck.group:GetName(),currspeed=currspeed,lastspeed=truck.lastspeed}) +end +end +return self +end +function AMMOTRUCK:GetAmmoStatus(Group) +local ammotot,shells,rockets,bombs,missiles,narti=Group:GetAmmunition() +return rockets+missiles+narti +end +function AMMOTRUCK:CheckWaitingTargets(dataset) +self:T(self.lid.." CheckWaitingTargets") +local data=dataset +for _,_data in pairs(data)do +local truck=_data +local Tnow=timer.getAbsTime() +local Tdiff=Tnow-truck.timestamp +if Tdiff>self.waitingtime then +local hasammo=self:GetAmmoStatus(truck.group) +if hasammo<=self.ammothreshold then +truck.statusquo=AMMOTRUCK.State.OUTOFAMMO +else +truck.statusquo=AMMOTRUCK.State.IDLE +end +end +end +return self +end +function AMMOTRUCK:CheckReturningTrucks(dataset) +self:T(self.lid.." CheckReturningTrucks") +local data=dataset +local tgtcoord=self.homezone:GetCoordinate() +local radius=self.homezone:GetRadius() +for _,_data in pairs(data)do +local truck=_data +local coord=truck.group:GetCoordinate() +local dist=coord:Get2DDistance(tgtcoord) +self:T({name=truck.name,radius=radius,distance=dist}) +if dist<=radius then +truck.statusquo=AMMOTRUCK.State.IDLE +truck.timestamp=timer.getAbsTime() +truck.coordinate=coord +truck.reloads=self.reloads or 5 +self:__TruckHome(1,truck) +end +end +return self +end +function AMMOTRUCK:FindTarget(name) +self:T(self.lid.." FindTarget") +local data=nil +local dataset=self.targetlist +for _,_entry in pairs(dataset)do +local entry=_entry +if entry.name==name then +data=entry +break +end +end +return data +end +function AMMOTRUCK:FindTruck(name) +self:T(self.lid.." FindTruck") +local data=nil +local dataset=self.trucklist +for _,_entry in pairs(dataset)do +local entry=_entry +if entry.name==name then +data=entry +break +end +end +return data +end +function AMMOTRUCK:CheckArrivedTrucks(dataset) +self:T(self.lid.." CheckArrivedTrucks") +local data=dataset +for _,_data in pairs(data)do +local truck=_data +truck.statusquo=AMMOTRUCK.State.UNLOADING +truck.timestamp=timer.getAbsTime() +self:__TruckUnloading(2,truck) +local aridata=self:FindTarget(truck.targetname) +if aridata then +aridata.statusquo=AMMOTRUCK.State.RELOADING +aridata.timestamp=timer.getAbsTime() +end +end +return self +end +function AMMOTRUCK:CheckUnloadingTrucks(dataset) +self:T(self.lid.." CheckUnloadingTrucks") +local data=dataset +for _,_data in pairs(data)do +local truck=_data +local Tnow=timer.getAbsTime() +local Tpassed=Tnow-truck.timestamp +local hasammo=self:GetAmmoStatus(truck.targetgroup) +if Tpassed>self.unloadtime and hasammo>self.ammothreshold then +truck.statusquo=AMMOTRUCK.State.RETURNING +truck.timestamp=timer.getAbsTime() +self:__TruckReturning(2,truck) +local aridata=self:FindTarget(truck.targetname) +if aridata then +aridata.statusquo=AMMOTRUCK.State.IDLE +aridata.timestamp=timer.getAbsTime() +end +end +end +return self +end +function AMMOTRUCK:CheckTargetsAlive() +self:T(self.lid.." CheckTargetsAlive") +local arilist=self.targetlist +for _,_ari in pairs(arilist)do +local ari=_ari +if ari.group and ari.group:IsAlive()then +else +self.targetlist[ari.name]=nil +end +end +local aritable=self.targetset:GetSetObjects() +for _,_ari in pairs(aritable)do +local ari=_ari +if ari and ari:IsAlive()and not self.targetlist[ari:GetName()]then +local name=ari:GetName() +local newari={} +newari.name=name +newari.group=ari +newari.statusquo=AMMOTRUCK.State.IDLE +newari.timestamp=timer.getAbsTime() +newari.coordinate=ari:GetCoordinate() +local hasammo=self:GetAmmoStatus(ari) +newari.ammo=hasammo +self.targetlist[name]=newari +end +end +return self +end +function AMMOTRUCK:CheckTrucksAlive() +self:T(self.lid.." CheckTrucksAlive") +local trucklist=self.trucklist +for _,_truck in pairs(trucklist)do +local truck=_truck +if truck.group and truck.group:IsAlive()then +else +local tgtname=truck.targetname +local targetdata=self:FindTarget(tgtname) +if targetdata then +if targetdata.statusquo~=AMMOTRUCK.State.IDLE then +targetdata.statusquo=AMMOTRUCK.State.IDLE +end +end +self.trucklist[truck.name]=nil +end +end +local trucktable=self.truckset:GetSetObjects() +for _,_truck in pairs(trucktable)do +local truck=_truck +if truck and truck:IsAlive()and not self.trucklist[truck:GetName()]then +local name=truck:GetName() +local newtruck={} +newtruck.name=name +newtruck.group=truck +if self.hasarmygroup then +if truck.ClassName and truck.ClassName=="GROUP"then +local trucker=ARMYGROUP:New(truck) +trucker:Activate() +newtruck.group=trucker +end +end +newtruck.statusquo=AMMOTRUCK.State.IDLE +newtruck.timestamp=timer.getAbsTime() +newtruck.coordinate=truck:GetCoordinate() +newtruck.reloads=self.reloads or 5 +self.trucklist[name]=newtruck +end +end +return self +end +function AMMOTRUCK:onafterStart(From,Event,To) +self:T({From,Event,To}) +if ARMYGROUP and self.usearmygroup then +self.hasarmygroup=true +else +self.hasarmygroup=false +end +if self.debug then +BASE:TraceOn() +BASE:TraceClass("AMMOTRUCK") +end +self:CheckTargetsAlive() +self:CheckTrucksAlive() +self:__Monitor(-30) +return self +end +function AMMOTRUCK:onafterMonitor(From,Event,To) +self:T({From,Event,To}) +self:CheckTargetsAlive() +self:CheckTrucksAlive() +local remunition=false +local remunitionqueue={} +local waitingtargets={} +for _,_ari in pairs(self.targetlist)do +local data=_ari +if data.group and data.group:IsAlive()then +data.ammo=self:GetAmmoStatus(data.group) +data.timestamp=timer.getAbsTime() +local text=string.format("Ari %s | Ammo %d | State %s",data.name,data.ammo,data.statusquo) +self:T(text) +if data.ammo<=self.ammothreshold and(data.statusquo==AMMOTRUCK.State.IDLE or data.statusquo==AMMOTRUCK.State.OUTOFAMMO)then +data.statusquo=AMMOTRUCK.State.OUTOFAMMO +remunitionqueue[#remunitionqueue+1]=data +remunition=true +elseif data.statusquo==AMMOTRUCK.State.WAITING then +waitingtargets[#waitingtargets+1]=data +end +else +self.targetlist[data.name]=nil +end +end +local idletrucks={} +local drivingtrucks={} +local unloadingtrucks={} +local arrivedtrucks={} +local returningtrucks={} +local found=false +for _,_truckdata in pairs(self.trucklist)do +local data=_truckdata +if data.group and data.group:IsAlive()then +local text=string.format("Truck %s | State %s",data.name,data.statusquo) +self:T(text) +if data.statusquo==AMMOTRUCK.State.IDLE then +idletrucks[#idletrucks+1]=data +found=true +elseif data.statusquo==AMMOTRUCK.State.DRIVING then +drivingtrucks[#drivingtrucks+1]=data +elseif data.statusquo==AMMOTRUCK.State.ARRIVED then +arrivedtrucks[#arrivedtrucks+1]=data +elseif data.statusquo==AMMOTRUCK.State.UNLOADING then +unloadingtrucks[#unloadingtrucks+1]=data +elseif data.statusquo==AMMOTRUCK.State.RETURNING then +returningtrucks[#returningtrucks+1]=data +if data.reloads>0 or data.reloads==-1 then +idletrucks[#idletrucks+1]=data +found=true +end +end +else +self.truckset[data.name]=nil +end +end +local n=0 +if found and remunition then +for _,_truckdata in pairs(idletrucks)do +local truckdata=_truckdata +local truckcoord=truckdata.group:GetCoordinate() +for _,_aridata in pairs(remunitionqueue)do +local aridata=_aridata +local aricoord=aridata.coordinate +local distance=truckcoord:Get2DDistance(aricoord) +if distance<=self.remunidist and aridata.statusquo==AMMOTRUCK.State.OUTOFAMMO and n<=#idletrucks then +n=n+1 +aridata.statusquo=AMMOTRUCK.State.REQUESTED +self:__RouteTruck(n*5,truckdata,aridata) +break +end +end +end +end +if#drivingtrucks>0 then +self:CheckDrivingTrucks(drivingtrucks) +end +if#arrivedtrucks>0 then +self:CheckArrivedTrucks(arrivedtrucks) +end +if#unloadingtrucks>0 then +self:CheckUnloadingTrucks(unloadingtrucks) +end +if#returningtrucks>0 then +self:CheckReturningTrucks(returningtrucks) +end +if#waitingtargets>0 then +self:CheckWaitingTargets(waitingtargets) +end +self:__Monitor(self.monitor) +return self +end +function AMMOTRUCK:onafterRouteTruck(From,Event,To,Truckdata,Aridata) +self:T({From,Event,To,Truckdata.name,Aridata.name}) +local truckdata=Truckdata +local aridata=Aridata +local tgtgrp=aridata.group +local tgtzone=ZONE_GROUP:New(aridata.name,tgtgrp,30) +local tgtcoord=tgtzone:GetRandomCoordinate(15) +if self.hasarmygroup then +local mission=AUFTRAG:NewONGUARD(tgtcoord) +local oldmission=truckdata.group:GetMissionCurrent() +if oldmission then oldmission:Cancel()end +mission:SetTime(5) +mission:SetTeleport(false) +truckdata.group:AddMission(mission) +elseif self.routeonroad then +truckdata.group:RouteGroundOnRoad(tgtcoord,30) +else +truckdata.group:RouteGroundTo(tgtcoord,30) +end +truckdata.statusquo=AMMOTRUCK.State.DRIVING +truckdata.targetgroup=tgtgrp +truckdata.targetname=aridata.name +truckdata.targetcoordinate=tgtcoord +aridata.statusquo=AMMOTRUCK.State.WAITING +aridata.timestamp=timer.getAbsTime() +return self +end +function AMMOTRUCK:onafterTruckUnloading(From,Event,To,Truckdata) +local m=MESSAGE:New("Truck "..Truckdata.name.." unloading!",15,"AmmoTruck"):ToCoalitionIf(self.coalition,self.debug) +local truck=Truckdata +local coord=truck.group:GetCoordinate() +local heading=truck.group:GetHeading() +heading=heading<180 and(360-heading)or(heading-180) +local cid=self.coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA +cid=self.coalition==coalition.side.NEUTRAL and country.id.UN_PEACEKEEPERS or cid +local ammo={} +for i=1,5 do +ammo[i]=SPAWNSTATIC:NewFromType("ammo_cargo","Cargos",cid) +:InitCoordinate(coord:Translate((15+((i-1)*4)),heading)) +:Spawn(0,"AmmoCrate-"..math.random(1,10000)) +end +local function destroyammo(ammo) +for _,_crate in pairs(ammo)do +_crate:Destroy(false) +end +end +local scheduler=SCHEDULER:New(nil,destroyammo,{ammo},self.waitingtime) +if truck.reloads~=-1 then +truck.reloads=truck.reloads-1 +end +return self +end +function AMMOTRUCK:onafterTruckReturning(From,Event,To,Truck) +self:T({From,Event,To,Truck.name}) +local truckdata=Truck +local tgtzone=self.homezone +local tgtcoord=tgtzone:GetRandomCoordinate() +if self.hasarmygroup then +local mission=AUFTRAG:NewONGUARD(tgtcoord) +local oldmission=truckdata.group:GetMissionCurrent() +if oldmission then oldmission:Cancel()end +mission:SetTime(5) +mission:SetTeleport(false) +truckdata.group:AddMission(mission) +elseif self.routeonroad then +truckdata.group:RouteGroundOnRoad(tgtcoord,30,1,"Cone") +else +truckdata.group:RouteGroundTo(tgtcoord,30,"Cone",1) +end +return self +end +function AMMOTRUCK:onafterStop(From,Event,To) +self:T({From,Event,To}) +return self +end +AUTOLASE={ +ClassName="AUTOLASE", +lid="", +verbose=0, +alias="", +debug=false, +smokemenu=true, +RoundingPrecision=0, +increasegroundawareness=false, +MonitorFrequency=30, +} +AUTOLASE.version="0.1.31" +function AUTOLASE:New(RecceSet,Coalition,Alias,PilotSet) +BASE:T({RecceSet,Coalition,Alias,PilotSet}) +local self=BASE:Inherit(self,BASE:New()) +if Coalition and type(Coalition)=="string"then +if Coalition=="blue"then +self.coalition=coalition.side.BLUE +elseif Coalition=="red"then +self.coalition=coalition.side.RED +elseif Coalition=="neutral"then +self.coalition=coalition.side.NEUTRAL +else +self:E("ERROR: Unknown coalition in AUTOLASE!") +end +end +if Alias then +self.alias=tostring(Alias) +else +self.alias="Lion" +if self.coalition then +if self.coalition==coalition.side.RED then +self.alias="Wolf" +elseif self.coalition==coalition.side.BLUE then +self.alias="Fox" +end +end +end +local self=BASE:Inherit(self,INTEL:New(RecceSet,Coalition,Alias)) +self.RecceSet=RecceSet +self.DetectVisual=true +self.DetectOptical=true +self.DetectRadar=true +self.DetectIRST=true +self.DetectRWR=true +self.DetectDLINK=true +self.LaserCodes=UTILS.GenerateLaserCodes() +self.LaseDistance=5000 +self.LaseDuration=300 +self.GroupsByThreat={} +self.UnitsByThreat={} +self.RecceNames={} +self.RecceLaserCode={} +self.RecceSmokeColor={} +self.RecceUnitNames={} +self.maxlasing=4 +self.CurrentLasing={} +self.lasingindex=0 +self.deadunitnotes={} +self.usepilotset=false +self.reporttimeshort=10 +self.reporttimelong=30 +self.smoketargets=false +self.smokecolor=SMOKECOLOR.Red +self.smokeoffset=nil +self.notifypilots=true +self.targetsperrecce={} +self.RecceUnits={} +self.forcecooldown=true +self.cooldowntime=60 +self.useSRS=false +self.SRSPath="" +self.SRSFreq=251 +self.SRSMod=radio.modulation.AM +self.NoMenus=false +self.minthreatlevel=0 +self.blacklistattributes={} +self:SetLaserCodes({1688,1130,4785,6547,1465,4578}) +self.playermenus={} +self.smokemenu=true +self.threatmenu=true +self.RoundingPrecision=0 +self.increasegroundawareness=false +self.MonitorFrequency=30 +self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) +self.lid=string.format("AUTOLASE %s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") +self:AddTransition("*","Monitor","*") +self:AddTransition("*","Lasing","*") +self:AddTransition("*","TargetLost","*") +self:AddTransition("*","TargetDestroyed","*") +self:AddTransition("*","RecceKIA","*") +self:AddTransition("*","LaserTimeout","*") +self:AddTransition("*","Cancel","*") +if PilotSet then +self.usepilotset=true +self.pilotset=PilotSet +self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) +end +self:SetClusterAnalysis(false,false) +self:__Start(2) +self:__Monitor(math.random(5,10)) +return self +end +function AUTOLASE:SetMonitorFrequency(Seconds) +self.MonitorFrequency=Seconds or 30 +return self +end +function AUTOLASE:SetLaserCodes(LaserCodes) +self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes} +return self +end +function AUTOLASE:EnableImproveGroundUnitsDetection() +self.increasegroundawareness=true +return self +end +function AUTOLASE:DisableImproveGroundUnitsDetection() +self.increasegroundawareness=false +return self +end +function AUTOLASE:SetPilotMenu() +if self.usepilotset then +local pilottable=self.pilotset:GetSetObjects()or{} +local grouptable={} +for _,_unit in pairs(pilottable)do +local Unit=_unit +if Unit and Unit:IsAlive()then +local Group=Unit:GetGroup() +local GroupName=Group:GetName()or"none" +local unitname=Unit:GetName() +if not grouptable[GroupName]==true then +if self.playermenus[unitname]then self.playermenus[unitname]:Remove()end +local lasetopm=MENU_GROUP:New(Group,"Autolase",nil) +self.playermenus[unitname]=lasetopm +local lasemenu=MENU_GROUP_COMMAND:New(Group,"Status",lasetopm,self.ShowStatus,self,Group,Unit) +if self.smokemenu then +local smoke=(self.smoketargets==true)and"off"or"on" +local smoketext=string.format("Switch smoke targets to %s",smoke) +local smokemenu=MENU_GROUP_COMMAND:New(Group,smoketext,lasetopm,self.SetSmokeTargets,self,(not self.smoketargets)) +end +if self.threatmenu then +local threatmenutop=MENU_GROUP:New(Group,"Set min lasing threat",lasetopm) +for i=0,10,2 do +local text="Threatlevel "..tostring(i) +local threatmenu=MENU_GROUP_COMMAND:New(Group,text,threatmenutop,self.SetMinThreatLevel,self,i) +end +end +for _,_grp in pairs(self.RecceSet.Set)do +local grp=_grp +local unit=grp:GetUnit(1) +if unit and unit:IsAlive()then +local name=unit:GetName() +local mname=string.gsub(name,".%d+.%d+$","") +local code=self:GetLaserCode(name) +local unittop=MENU_GROUP:New(Group,"Change laser code for "..mname,lasetopm) +for _,_code in pairs(self.LaserCodes)do +local text=tostring(_code) +if _code==code then text=text.."(*)"end +local changemenu=MENU_GROUP_COMMAND:New(Group,text,unittop,self.SetRecceLaserCode,self,name,_code,true) +end +end +end +grouptable[GroupName]=true +end +end +end +else +if not self.NoMenus then +self.Menu=MENU_COALITION_COMMAND:New(self.coalition,"Autolase",nil,self.ShowStatus,self) +end +end +return self +end +function AUTOLASE:_EventHandler(EventData) +self:SetPilotMenu() +return self +end +function AUTOLASE:SetMinThreatLevel(Level) +local level=Level or 0 +if level<0 or level>10 then level=0 end +self.minthreatlevel=level +return self +end +function AUTOLASE:AddBlackListAttributes(Attributes) +local attributes=Attributes +if type(attributes)~="table"then +attributes={attributes} +end +for _,_attr in pairs(attributes)do +table.insert(self.blacklistattributes,_attr) +end +return self +end +function AUTOLASE:GetLaserCode(RecceName) +local code=1688 +if self.RecceLaserCode[RecceName]==nil then +code=self.LaserCodes[math.random(#self.LaserCodes)] +self.RecceLaserCode[RecceName]=code +else +code=self.RecceLaserCode[RecceName] +end +return code +end +function AUTOLASE:GetSmokeColor(RecceName) +local color=self.smokecolor +if self.RecceSmokeColor[RecceName]==nil then +self.RecceSmokeColor[RecceName]=color +else +color=self.RecceSmokeColor[RecceName] +end +return color +end +function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) +if OnOff then +self.useSRS=true +self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +self.SRSFreq=Frequency or 271 +self.SRSMod=Modulation or radio.modulation.AM +self.Gender=Gender or MSRS.gender or"male" +self.Culture=Culture or MSRS.culture or"en-US" +self.Port=Port or MSRS.port or 5002 +self.Voice=Voice +self.PathToGoogleKey=PathToGoogleKey +self.Volume=Volume or 1.0 +self.Label=Label +self.SRS=MSRS:New(self.SRSPath,self.SRSFreq,self.SRSMod) +self.SRS:SetCoalition(self.coalition) +self.SRS:SetLabel(self.MenuName or self.Name) +self.SRS:SetGender(self.Gender) +self.SRS:SetCulture(self.Culture) +self.SRS:SetPort(self.Port) +self.SRS:SetVoice(self.Voice) +self.SRS:SetCoalition(self.coalition) +self.SRS:SetVolume(self.Volume) +if self.PathToGoogleKey then +self.SRS:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) +self.SRS:SetProvider(MSRS.Provider.GOOGLE) +end +self.SRSQueue=MSRSQUEUE:New(self.alias) +else +self.useSRS=false +self.SRS=nil +self.SRSQueue=nil +end +return self +end +function AUTOLASE:SetMaxLasingTargets(Number) +self.maxlasing=Number or 4 +return self +end +function AUTOLASE:SetNotifyPilots(OnOff) +self.notifypilots=OnOff and true +return self +end +function AUTOLASE:SetRecceLaserCode(RecceName,Code,Refresh) +local code=Code or 1688 +self.RecceLaserCode[RecceName]=code +if Refresh then +self:SetPilotMenu() +if self.notifypilots then +if string.find(RecceName,"#")then +RecceName=string.match(RecceName,"^(.*)#") +end +self:NotifyPilots(string.format("Code for %s set to: %d",RecceName,Code),15) +end +end +return self +end +function AUTOLASE:SetRecceSmokeColor(RecceName,Color) +local color=Color or self.smokecolor +self.RecceSmokeColor[RecceName]=color +return self +end +function AUTOLASE:SetLaserCoolDown(OnOff,Seconds) +self.forcecooldown=OnOff and true +self.cooldowntime=Seconds or 60 +return self +end +function AUTOLASE:SetReportingTimes(long,short) +self.reporttimeshort=short or 10 +self.reporttimelong=long or 30 +return self +end +function AUTOLASE:SetLasingParameters(Distance,Duration) +self.LaseDistance=Distance or 5000 +self.LaseDuration=Duration or 300 +return self +end +function AUTOLASE:SetSmokeTargets(OnOff,Color) +self.smoketargets=OnOff +self.smokecolor=Color or SMOKECOLOR.Red +local smktxt=OnOff==true and"on"or"off" +local Message="Smoking targets is now "..smktxt.."!" +self:NotifyPilots(Message,10) +return self +end +function AUTOLASE:SetRoundingPrecsion(IDP) +self.RoundingPrecision=IDP or 0 +return self +end +function AUTOLASE:EnableSmokeMenu(Offset) +self.smokemenu=true +if Offset then +self.smokeoffset={} +self.smokeoffset.Distance=Offset.Distance or math.random(10,20) +self.smokeoffset.Angle=Offset.Angle or math.random(0,359) +end +return self +end +function AUTOLASE:DisableSmokeMenu() +self.smokemenu=false +self.smokeoffset=nil +return self +end +function AUTOLASE:EnableThreatLevelMenu() +self.threatmenu=true +return self +end +function AUTOLASE:DisableThreatLevelMenu() +self.threatmenu=false +return self +end +function AUTOLASE:GetLosFromUnit(Unit) +local lasedistance=self.LaseDistance +local unitheight=Unit:GetHeight() +local coord=Unit:GetCoordinate() +local landheight=coord:GetLandHeight() +local asl=unitheight-landheight +if asl>100 then +local absquare=lasedistance^2+asl^2 +lasedistance=math.sqrt(absquare) +end +return lasedistance +end +function AUTOLASE:CleanCurrentLasing() +local lasingtable=self.CurrentLasing +local newtable={} +local newreccecount={} +local lasing=0 +for _ind,_entry in pairs(lasingtable)do +local entry=_entry +if not newreccecount[entry.reccename]then +newreccecount[entry.reccename]=0 +end +end +for _,_recce in pairs(self.RecceSet:GetSetObjects())do +local recce=_recce +if recce and recce:IsAlive()then +local unit=recce:GetUnit(1) +local name=unit:GetName() +if not self.RecceUnits[name]then +local isground=(unit and unit.IsGround)and unit:IsGround()or false +self.RecceUnits[name]={name=name,unit=unit,cooldown=false,timestamp=timer.getAbsTime(),isground=isground} +end +end +end +for _ind,_entry in pairs(lasingtable)do +local entry=_entry +local valid=0 +local reccedead=false +local unitdead=false +local lostsight=false +local timeout=false +local Tnow=timer.getAbsTime() +local recce=entry.lasingunit +if recce and recce:IsAlive()then +valid=valid+1 +else +reccedead=true +self:__RecceKIA(2,entry.reccename) +end +local unit=entry.lasedunit +if unit and unit:IsAlive()==true then +valid=valid+1 +else +unitdead=true +if not self.deadunitnotes[entry.unitname]then +self.deadunitnotes[entry.unitname]=true +self:__TargetDestroyed(2,entry.unitname,entry.reccename) +end +end +if not reccedead and not unitdead then +if self:CanLase(recce,unit)then +valid=valid+1 +else +lostsight=true +entry.laserspot:LaseOff() +self:__TargetLost(2,entry.unitname,entry.reccename) +end +end +local timestamp=entry.timestamp +if Tnow-timestamp10 or needsinit==true or TNow-_data.timestamp>29 then +local hasunits,hasstatics,_,Units,Statics=position:ScanObjects(self.LaseDistance,true,true,false) +if hasunits then +self:T(self.lid.."Checking possibly visible UNITs for Recce "..unit:GetName()) +for _,_target in pairs(Units)do +local target=_target +if target and target:GetCoalition()~=self.coalition then +if unit:IsLOS(target)and(not target:IsUnitDetected(unit))then +unit:KnowUnit(target,true,true) +end +end +end +end +if hasstatics then +self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) +for _,_static in pairs(Statics)do +local static=STATIC:Find(_static) +if static and static:GetCoalition()~=self.coalition and static:GetCoordinate()then +local IsLOS=position:IsLOS(static:GetCoordinate()) +if IsLOS then +unit:KnowUnit(static,true,true) +end +end +end +end +end +end +end +end +return self +end +function AUTOLASE:onbeforeMonitor(From,Event,To) +self:T({From,Event,To}) +if self.increasegroundawareness then +self:_Prescient() +end +self:UpdateIntel() +return self +end +function AUTOLASE:onafterMonitor(From,Event,To) +self:T({From,Event,To}) +local countlases=self:CleanCurrentLasing() +self:SetPilotMenu() +local detecteditems=self.Contacts or{} +local groupsbythreat={} +local report=REPORT:New("Detections") +local lines=0 +for _,_contact in pairs(detecteditems)do +local contact=_contact +local grp=contact.group +local coord=contact.position +local reccename=contact.recce or"none" +local threat=contact.threatlevel or 0 +local reccegrp=UNIT:FindByName(reccename) +if reccegrp then +local reccecoord=reccegrp:GetCoordinate() +local distance=math.floor(reccecoord:Get3DDistance(coord)) +local text=string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute,contact.groupname,math.floor(distance/1000),contact.threatlevel) +report:Add(text) +self:T(text) +if self.debug then self:I(text)end +lines=lines+1 +local lasedistance=self:GetLosFromUnit(reccegrp) +if grp:IsGround()and lasedistance>=distance and threat>=self.minthreatlevel then +table.insert(groupsbythreat,{contact.group,contact.threatlevel}) +self.RecceNames[contact.groupname]=contact.recce +end +end +end +self.GroupsByThreat=groupsbythreat +if self.verbose>2 and lines>0 then +local m=MESSAGE:New(report:Text(),self.reporttimeshort,"Autolase"):ToAll() +end +table.sort(self.GroupsByThreat,function(a,b) +local aNum=a[2] +local bNum=b[2] +return aNum>bNum +end) +local unitsbythreat={} +for _,_entry in pairs(self.GroupsByThreat)do +local group=_entry[1] +if group and group:IsAlive()then +local units=group:GetUnits() +local reccename=self.RecceNames[group:GetName()] +for _,_unit in pairs(units)do +local unit=_unit +if unit and unit:IsAlive()then +local threat=unit:GetThreatLevel() +local coord=unit:GetCoordinate() +if threat>=self.minthreatlevel then +local unitname=unit:GetName() +if unit:HasAttribute("RADAR_BAND1_FOR_ARM")or unit:HasAttribute("RADAR_BAND2_FOR_ARM")or unit:HasAttribute("Optical Tracker")then +threat=11 +end +table.insert(unitsbythreat,{unit,threat}) +self.RecceUnitNames[unitname]=reccename +end +end +end +end +end +self.UnitsByThreat=unitsbythreat +table.sort(self.UnitsByThreat,function(a,b) +local aNum=a[2] +local bNum=b[2] +return aNum>bNum +end) +local unitreport=REPORT:New("Detected Units") +local lines=0 +for _,_entry in pairs(self.UnitsByThreat)do +local threat=_entry[2] +local unit=_entry[1] +local unitname=unit:GetName() +local text=string.format("Unit %s | Threatlevel %d | Detected by %s",unitname,threat,self.RecceUnitNames[unitname]) +unitreport:Add(text) +lines=lines+1 +self:T(text) +if self.debug then self:I(text)end +end +if self.verbose>2 and lines>0 then +local m=MESSAGE:New(unitreport:Text(),self.reporttimeshort,"Autolase"):ToAll() +end +for _,_detectingunit in pairs(self.RecceUnits)do +local reccename=_detectingunit.name +local recce=_detectingunit.unit +local reccecount=self.targetsperrecce[reccename]or 0 +local targets=0 +for _,_entry in pairs(self.UnitsByThreat)do +local unit=_entry[1] +local unitname=unit:GetName() +local canlase=self:CanLase(recce,unit) +if targets+reccecount0 then +local SwitchAAA=self.SwitchAAA +local group_coalition=group:GetCoalition() +ground:ForEachGroupAlive( +function(grp) +local tiresias_data=grp.Tiresias +if grp:GetCoalition()~=group_coalition +and tiresias_data +and tiresias_data.type +and not tiresias_data.exception==true then +if tiresias_data.invisible==true then +grp:SetCommandInvisible(false) +tiresias_data.invisible=false +end +local grp_type=tiresias_data.type +if grp_type=="Vehicle"and tiresias_data.AIOff==true then +grp:SetAIOn() +tiresias_data.AIOff=false +elseif SwitchAAA==true and grp_type=="AAA"and tiresias_data.AIOff==true then +grp:SetAIOn() +grp:EnableEmission(true) +tiresias_data.AIOff=false +end +else +BASE:T("TIRESIAS - This group "..tostring(grp:GetName()).." has not been initialized or is an exception!") +end +end +) +end +return self +end +function TIRESIAS:onafterStart(From,Event,To) +self:T({From,Event,To}) +local VehicleSet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart() +local AAASet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart() +local SAMSet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart() +local OpsGroupSet=SET_OPSGROUP:New():FilterActive(true):FilterStart() +self.FlightSet=SET_GROUP:New():FilterCategories({" plane"," helicopter"}):FilterStart() +local EngageRange=self.AAARange +local SwitchAAA=self.SwitchAAA +local ExceptionSet=self.ExceptionSet +local exception_data={ +type=" Exception", +exception=true, +} +local vehicle_data={ +type=" Vehicle", +invisible=true, +AIOff=true, +exception=false, +} +local aaa_data={ +type=" AAA", +invisible=true, +range=EngageRange, +exception=false, +AIOff=SwitchAAA, +} +local sam_data={ +type=" SAM", +invisible=true, +exception=false, +} +if ExceptionSet then +function ExceptionSet:OnAfterAdded(From,Event,To,ObjectName,Object) +BASE:I(" TIRESIAS: EXCEPTION Object Added: "..Object:GetName()) +if Object and Object:IsAlive()then +Object.Tiresias=exception_data +Object:SetAIOn() +Object:SetCommandInvisible(false) +Object:EnableEmission(true) +end +end +local OGS=OpsGroupSet:GetAliveSet() +for _,_OG in pairs(OGS or{})do +local OG=_OG +local grp=OG:GetGroup() +ExceptionSet:AddGroup(grp,true) +end +function OpsGroupSet:OnAfterAdded(From,Event,To,ObjectName,Object) +local grp=Object:GetGroup() +ExceptionSet:AddGroup(grp,true) +end +end +function VehicleSet:OnAfterAdded(From,Event,To,ObjectName,Object) +BASE:T(" TIRESIAS: VEHICLE Object Added: "..Object:GetName()) +if Object and Object:IsAlive()then +Object:SetAIOff() +Object:SetCommandInvisible(true) +Object.Tiresias=vehicle_data +end +end +function AAASet:OnAfterAdded(From,Event,To,ObjectName,Object) +if Object and Object:IsAlive()then +BASE:I(" TIRESIAS: AAA Object Added: "..Object:GetName()) +Object:OptionEngageRange(EngageRange) +Object:SetCommandInvisible(true) +if SwitchAAA then +Object:SetAIOff() +Object:EnableEmission(false) +end +Object.Tiresias=aaa_data +end +end +function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object) +if Object and Object:IsAlive()then +BASE:T(" TIRESIAS: SAM Object Added: "..Object:GetName()) +Object:SetCommandInvisible(true) +Object.Tiresias=sam_data +end +end +self.VehicleSet=VehicleSet +self.AAASet=AAASet +self.SAMSet=SAMSet +self.OpsGroupSet=OpsGroupSet +self:_InitGroups() +self:__Status(1) +return self +end +function TIRESIAS:onbeforeStatus(From,Event,To) +self:T({From,Event,To}) +return self:GetState()~=" Stopped" +end +function TIRESIAS:onafterStatus(From,Event,To) +self:T({From,Event,To}) +if self.debug then +local count=self.VehicleSet:CountAlive() +local AAAcount=self.AAASet:CountAlive() +local SAMcount=self.SAMSet:CountAlive() +self:I(string.format(" Overall: %d | Vehicles: %d | AAA: %d | SAM: %d", +count+AAAcount+SAMcount,count,AAAcount,SAMcount)) +end +self:_InitGroups() +local flight_count=self.FlightSet:CountAlive() +if flight_count>0 then +local Set=self.FlightSet:GetAliveSet() +local helo_range=self.HeloSwitchRange +local plane_range=self.PlaneSwitchRange +for _,_plane in pairs(Set or{})do +local plane=_plane +local radius=plane:IsHelicopter()and helo_range or plane_range +self:_SwitchOnGroups(plane,radius) +end +end +if self:GetState()~=" Stopped"then +self:__Status(self.Interval) +end +return self +end +function TIRESIAS:onafterStop(From,Event,To) +self:T({From,Event,To}) +self:UnHandleEvent(EVENTS.PlayerEnterAircraft) +self._cached_zones={} +return self +end +STRATEGO={ +ClassName="STRATEGO", +debug=false, +drawzone=false, +markzone=false, +version="0.3.1", +portweight=3, +POIweight=1, +maxrunways=3, +coalition=nil, +colors=nil, +airbasetable={}, +nonconnectedab={}, +easynames={}, +maxdist=150, +disttable={}, +routexists={}, +routefactor=5, +OpsZones={}, +NeutralBenefit=100, +Budget=0, +usebudget=false, +CaptureUnits=3, +CaptureThreatlevel=1, +CaptureObjectCategories={Object.Category.UNIT}, +ExcludeShips=true, +} +STRATEGO.Type={ +AIRBASE="AIRBASE", +PORT="PORT", +POI="POI", +FARP="FARP", +SHIP="SHIP", +} +function STRATEGO:New(Name,Coalition,MaxDist) +local self=BASE:Inherit(self,FSM:New()) +self.coalition=Coalition +self.coalitiontext=UTILS.GetCoalitionName(Coalition) +self.name=Name or"Hannibal" +self.maxdist=MaxDist or 150 +self.disttable={} +self.routexists={} +self.ExcludeShips=true +self.lid=string.format("STRATEGO %s %s | ",self.name,self.version) +self.bases=SET_AIRBASE:New():FilterOnce() +self.ports=SET_ZONE:New():FilterPrefixes("Port"):FilterOnce() +self.POIs=SET_ZONE:New():FilterPrefixes("POI"):FilterOnce() +self.colors={ +[1]={0,1,0}, +[2]={1,0,0}, +[3]={0,0,1}, +[4]={1,0.65,0}, +} +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Update","*") +self:AddTransition("*","NodeEvent","*") +self:AddTransition("Running","Stop","Stopped") +return self +end +function STRATEGO:onafterStart(From,Event,To) +self:T(self.lid.."Start") +self:AnalyseBases() +self:AnalysePOIs(self.ports,self.portweight,"PORT") +self:AnalysePOIs(self.POIs,self.POIweight,"POI") +for i=self.maxrunways,1,-1 do +self:AnalyseRoutes(i,i*self.routefactor,self.colors[(i%3)+1],i) +end +self:AnalyseUnconnected(self.colors[4]) +self:I(self.lid.."Advisory ready.") +self:__Update(180) +return self +end +function STRATEGO:onafterUpdate(From,Event,To) +self:T(self.lid.."Update") +self:UpdateNodeCoalitions() +if self:GetState()=="Running"then +self:__Update(180) +end +return self +end +function STRATEGO:SetUsingBudget(Usebudget,StartBudget) +self:T(self.lid.."SetUsingBudget") +self.usebudget=Usebudget +self.Budget=StartBudget +return self +end +function STRATEGO:SetDebug(Debug,DrawZones,MarkZones) +self:T(self.lid.."SetDebug") +self.debug=Debug +self.drawzone=DrawZones +self.markzone=MarkZones +return self +end +function STRATEGO:SetStrategoZone(Zone) +self.StrategoZone=Zone +return self +end +function STRATEGO:SetWeights(MaxRunways,PortWeight,POIWeight,RouteFactor) +self:T(self.lid.."SetWeights") +self.portweight=PortWeight or 3 +self.POIweight=POIWeight or 1 +self.maxrunways=MaxRunways or 3 +self.routefactor=RouteFactor or 5 +return self +end +function STRATEGO:SetNeutralBenefit(NeutralBenefit) +self:T(self.lid.."SetNeutralBenefit") +self.NeutralBenefit=NeutralBenefit or 100 +return self +end +function STRATEGO:SetCaptureOptions(CaptureUnits,CaptureThreatlevel,CaptureCategories) +self:T(self.lid.."SetCaptureOptions") +self.CaptureUnits=CaptureUnits or 3 +self.CaptureThreatlevel=CaptureThreatlevel or 1 +self.CaptureObjectCategories=CaptureCategories or{Object.Category.UNIT} +return self +end +function STRATEGO:AnalyseBases() +self:T(self.lid.."AnalyseBases") +local colors=self.colors +local debug=self.debug +local airbasetable=self.airbasetable +local nonconnectedab=self.nonconnectedab +local easynames=self.easynames +local zone=self.StrategoZone +self.bases:ForEach( +function(afb) +local ab=afb +local abvec2=ab:GetVec2() +if self.ExcludeShips and ab:IsShip()then return end +if zone~=nil then +if not zone:IsVec2InZone(abvec2)then +return +end +end +local abname=ab:GetName() +local runways=ab:GetRunways() +local numrwys=#runways +if numrwys>=1 then numrwys=numrwys*0.5 end +local abzone=ab:GetZone() +if not abzone then +abzone=ZONE_RADIUS:New(abname,ab:GetVec2(),500) +end +local coa=ab:GetCoalition() +if coa==nil then return end +coa=coa+1 +local abtype=STRATEGO.Type.AIRBASE +if ab:IsShip()then +numrwys=1 +abtype=STRATEGO.Type.SHIP +end +if ab:IsHelipad()then +numrwys=1 +abtype=STRATEGO.Type.FARP +end +local coord=ab:GetCoordinate() +if debug then +abzone:DrawZone(-1,colors[coa],1,colors[coa],0.3,1) +coord:TextToAll(tostring(numrwys),-1,{0,0,0},1,colors[coa],0.3,20) +end +local opszone=self:GetNewOpsZone(abname,coa-1) +local tbl={ +name=abname, +baseweight=numrwys, +weight=0, +coalition=coa-1, +port=false, +zone=abzone, +coord=coord, +type=abtype, +opszone=opszone, +connections=0, +} +airbasetable[abname]=tbl +nonconnectedab[abname]=true +local name=string.gsub(abname,"[%p%s]",".") +easynames[name]=abname +end +) +return self +end +function STRATEGO:UpdateNodeCoalitions() +self:T(self.lid.."UpdateNodeCoalitions") +local newtable={} +for _id,_data in pairs(self.airbasetable)do +local data=_data +if data.type==STRATEGO.Type.AIRBASE or data.type==STRATEGO.Type.FARP or data.type==STRATEGO.Type.SHIP then +data.coalition=AIRBASE:FindByName(data.name):GetCoalition()or 0 +else +data.coalition=data.opszone:GetOwner()or 0 +end +newtable[_id]=_data +end +self.airbasetable=nil +self.airbasetable=newtable +return self +end +function STRATEGO:GetNewOpsZone(Zone,Coalition) +self:T(self.lid.."GetNewOpsZone") +local opszone=OPSZONE:New(Zone,Coalition or 0) +opszone:SetCaptureNunits(self.CaptureUnits) +opszone:SetCaptureThreatlevel(self.CaptureThreatlevel) +opszone:SetObjectCategories(self.CaptureObjectCategories) +opszone:SetDrawZone(self.drawzone) +opszone:SetMarkZone(self.markzone) +opszone:Start() +local function Captured(opszone,coalition) +self:__NodeEvent(1,opszone,coalition) +end +function opszone:OnBeforeCaptured(From,Event,To,Coalition) +Captured(opszone,Coalition) +end +return opszone +end +function STRATEGO:AnalysePOIs(Set,Weight,Key) +self:T(self.lid.."AnalysePOIs") +local colors=self.colors +local debug=self.debug +local airbasetable=self.airbasetable +local nonconnectedab=self.nonconnectedab +local easynames=self.easynames +Set:ForEach( +function(port) +local zone=port +local zname=zone:GetName() +local coord=zone:GetCoordinate() +if debug then +zone:DrawZone(-1,colors[1],1,colors[1],0.3,1) +coord:TextToAll(tostring(Weight),-1,{0,0,0},1,colors[1],0.3,20) +end +local opszone=self:GetNewOpsZone(zone) +local tbl={ +name=zname, +baseweight=Weight, +weight=0, +coalition=coalition.side.NEUTRAL, +port=true, +zone=zone, +coord=coord, +type=Key, +opszone=opszone, +connections=0, +} +airbasetable[zname]=tbl +nonconnectedab[zname]=true +local name=string.gsub(zname,"[%p%s]",".") +easynames[name]=zname +end +) +return self +end +function STRATEGO:GetToFrom(StartPoint,EndPoint) +self:T(self.lid.."GetToFrom "..tostring(StartPoint).." "..tostring(EndPoint)) +local pstart=string.gsub(StartPoint,"[%p%s]",".") +local pend=string.gsub(EndPoint,"[%p%s]",".") +local fromto=pstart..";"..pend +local tofrom=pend..";"..pstart +return fromto,tofrom +end +function STRATEGO:GetRoutesFromNode(StartPoint) +self:T(self.lid.."GetRoutesFromNode") +local pstart=string.gsub(StartPoint,"[%p%s]",".") +local found=false +pstart=pstart..";" +local routes={} +local listed={} +for _,_data in pairs(self.routexists)do +if string.find(_data,pstart,1,true)and not listed[_data]then +local target=string.gsub(_data,pstart,"") +local fname=self.easynames[target] +table.insert(routes,fname) +found=true +listed[_data]=true +end +end +return found,routes +end +function STRATEGO:AddRoutesManually(Startpoint,Endpoint,Color,Linetype,Draw) +self:T(self.lid.."AddRoutesManually") +local fromto,tofrom=self:GetToFrom(Startpoint,Endpoint) +local startcoordinate=self.airbasetable[Startpoint].coord +local targetcoordinate=self.airbasetable[Endpoint].coord +local dist=UTILS.Round(targetcoordinate:Get2DDistance(startcoordinate),-2)/1000 +local color=Color or{136/255,0,1} +local linetype=Linetype or 5 +local data={ +start=Startpoint, +target=Endpoint, +dist=dist, +} +self.disttable[fromto]=data +self.disttable[tofrom]=data +table.insert(self.routexists,fromto) +table.insert(self.routexists,tofrom) +self.nonconnectedab[Endpoint]=false +self.nonconnectedab[Startpoint]=false +local factor=self.airbasetable[Startpoint].baseweight*self.routefactor +self.airbasetable[Startpoint].weight=self.airbasetable[Startpoint].weight+factor +self.airbasetable[Endpoint].weight=self.airbasetable[Endpoint].weight+factor +self.airbasetable[Endpoint].connections=self.airbasetable[Endpoint].connections+2 +self.airbasetable[Startpoint].connections=self.airbasetable[Startpoint].connections+2 +if self.debug or Draw then +startcoordinate:LineToAll(targetcoordinate,-1,color,1,linetype,nil,string.format("%dkm",dist)) +end +return self +end +function STRATEGO:AnalyseRoutes(tgtrwys,factor,color,linetype) +self:T(self.lid.."AnalyseRoutes") +for _,_ab in pairs(self.airbasetable)do +if _ab.baseweight>=1 then +local startpoint=_ab.name +local startcoord=_ab.coord +for _,_data in pairs(self.airbasetable)do +local fromto,tofrom=self:GetToFrom(startpoint,_data.name) +if _data.name==startpoint then +elseif _data.baseweight==tgtrwys and not(self.routexists[fromto]or self.routexists[tofrom])then +local tgtc=_data.coord +local dist=UTILS.Round(tgtc:Get2DDistance(startcoord),-2)/1000 +if dist<=self.maxdist then +local data={ +start=startpoint, +target=_data.name, +dist=dist, +} +self.disttable[fromto]=data +self.disttable[tofrom]=data +table.insert(self.routexists,fromto) +table.insert(self.routexists,tofrom) +self.nonconnectedab[_data.name]=false +self.nonconnectedab[startpoint]=false +self.airbasetable[startpoint].weight=self.airbasetable[startpoint].weight+factor +self.airbasetable[_data.name].weight=self.airbasetable[_data.name].weight+factor +self.airbasetable[startpoint].connections=self.airbasetable[startpoint].connections+1 +self.airbasetable[_data.name].connections=self.airbasetable[_data.name].connections+1 +if self.debug then +startcoord:LineToAll(tgtc,-1,color,1,linetype,nil,string.format("%dkm",dist)) +end +end +end +end +end +end +return self +end +function STRATEGO:AnalyseUnconnected(Color) +self:T(self.lid.."AnalyseUnconnected") +for _name,_noconnect in pairs(self.nonconnectedab)do +if _noconnect then +local startpoint=_name +local startcoord=self.airbasetable[_name].coord +local shortest=1000*1000 +local closest=nil +local closestcoord=nil +for _,_data in pairs(self.airbasetable)do +if _name~=_data.name then +local tgtc=_data.coord +local dist=UTILS.Round(tgtc:Get2DDistance(startcoord),-2)/1000 +if dist=weight and okay then +weight=_data.weight +if not airbases[weight]then airbases[weight]={}end +table.insert(airbases[weight],_name) +end +if _data.weight>highest and okay then +highest=_data.weight +highname=_name +end +end +return airbases[weight],weight,highest,highname +end +function STRATEGO:GetNextHighestWeightNodes(Weight,Coalition) +self:T(self.lid.."GetNextHighestWeightNodes") +local weight=0 +local airbases={} +for _name,_data in pairs(self.airbasetable)do +local okay=true +if Coalition then +if _data.coalition~=Coalition then +okay=false +end +end +if _data.weight>=weight and _data.weight=targetweight)then +self:T("Found Consolidation Target: "..cname) +shortest=dist +target=cname +weight=self.airbasetable[cname].weight +coa=coa +end +end +end +return shortest,target,weight,coa +end +function STRATEGO:FindClosestStrategicTarget(Startpoint,Weight) +self:T(self.lid.."FindClosestStrategicTarget for "..Startpoint.." Weight "..Weight or 0) +local shortest=1000*1000 +local target=nil +local weight=0 +local coa=nil +if not Weight then Weight=self.maxrunways end +local startpoint=string.gsub(Startpoint,"[%p%s]",".") +for _,_route in pairs(self.routexists)do +if string.find(_route,startpoint,1,true)then +local dist=self.disttable[_route].dist +local tname=string.gsub(_route,startpoint,"") +local tname=string.gsub(tname,";","") +local cname=self.easynames[tname] +local coa=self.airbasetable[cname].coalition +local tweight=self.airbasetable[cname].baseweight +local ttweight=self.airbasetable[cname].weight +if(dist=Weight)then +self:T("Found Strategic Target: "..cname) +shortest=dist +target=cname +weight=self.airbasetable[cname].weight +coa=self.airbasetable[cname].coalition +end +end +end +return shortest,target,weight,coa +end +function STRATEGO:FindStrategicTargets() +self:T(self.lid.."FindStrategicTargets") +local targets={} +for _,_data in pairs(self.airbasetable)do +local data=_data +if data.coalition==self.coalition then +local dist,name,points,coa=self:FindClosestStrategicTarget(data.name,data.weight) +if points>0 then +self:T({dist=dist,name=name,points=points,coa=coa}) +end +if points~=0 then +local enemycoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Enemycoa = "..enemycoa) +if coa==coalition.side.NEUTRAL then +local tdata={} +tdata.name=name +tdata.dist=dist +tdata.points=points+self.NeutralBenefit +tdata.coalition=coa +tdata.coalitionname=UTILS.GetCoalitionName(coa) +tdata.coordinate=self.airbasetable[name].coord +table.insert(targets,tdata) +else +local tdata={} +tdata.name=name +tdata.dist=dist +tdata.points=points +tdata.coalition=coa +tdata.coalitionname=UTILS.GetCoalitionName(coa) +tdata.coordinate=self.airbasetable[name].coord +table.insert(targets,tdata) +end +end +end +end +return targets +end +function STRATEGO:FindConsolidationTargets() +self:T(self.lid.."FindConsolidationTargets") +local targets={} +for _,_data in pairs(self.airbasetable)do +local data=_data +if data.coalition==self.coalition then +local dist,name,points,coa=self:FindClosestConsolidationTarget(data.name,self.maxrunways-1) +if points>0 then +self:T({dist=dist,name=name,points=points,coa=coa}) +end +if points~=0 then +local enemycoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Enemycoa = "..enemycoa) +if coa==coalition.side.NEUTRAL then +local tdata={} +tdata.name=name +tdata.dist=dist +tdata.points=points+self.NeutralBenefit +tdata.coalition=coa +tdata.coalitionname=UTILS.GetCoalitionName(coa) +tdata.coordinate=self.airbasetable[name].coord +table.insert(targets,tdata) +else +local tdata={} +tdata.name=name +tdata.dist=dist +tdata.points=points +tdata.coalition=coa +tdata.coalitionname=UTILS.GetCoalitionName(coa) +tdata.coordinate=self.airbasetable[name].coord +table.insert(targets,tdata) +end +end +end +end +return targets +end +function STRATEGO:FindNeighborNodes(Name,Enemies,Friends) +self:T(self.lid.."FindNeighborNodes") +local neighbors={} +local name=string.gsub(Name,"[%p%s]",".") +local shortestdist=1000*1000 +local nearest=nil +for _route,_data in pairs(self.disttable)do +if string.find(_route,name,1,true)then +local dist=self.disttable[_route] +local tname=string.gsub(_route,name,"") +local tname=string.gsub(tname,";","") +local cname=self.easynames[tname] +if cname then +local encoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +if Enemies==true then +if self.airbasetable[cname].coalition==encoa then +neighbors[cname]=dist +end +elseif Friends==true then +if self.airbasetable[cname].coalition~=encoa then +neighbors[cname]=dist +end +else +neighbors[cname]=dist +end +if neighbors[cname]and dist.dist2 and true or false +if _name==End then nnodes=true end +if kcoord~=nil and ecoord~=nil and nnodes==true and InRoute[_name]~=true then +local dist=math.floor((kcoord:Get2DDistance(ecoord)/1000)+0.5) +if(distlargestcut then +largestcut=j-i +cut={i=i,j=j} +foundcut=true +end +end +end +end +end +if foundcut then +local newroute={} +for i=1,#Route do +if i<=cut.i or i>=cut.j then +table.insert(newroute,Route[i]) +end +end +return newroute +end +return Route,foundcut +end +if routecomplete==true and NoOptimize~=true then +local foundcut=true +while foundcut~=false do +Route,foundcut=OptimizeRoute(Route) +end +else +Route,routecomplete=self:FindRoute(End,Start,Hops,Draw,Color,LineType) +reverse=true +end +if(self.debug or Draw)then DrawRoute(Route)end +return Route,routecomplete,reverse +end +function STRATEGO:AddBudget(Number) +self:T(self.lid.."AddBudget") +self.Budget=self.Budget+Number +return self +end +function STRATEGO:SubtractBudget(Number) +self:T(self.lid.."SubtractBudget") +self.Budget=self.Budget-Number +return self +end +function STRATEGO:GetBudget() +self:T(self.lid.."GetBudget") +return self.Budget +end +function STRATEGO:FindAffordableStrategicTarget() +self:T(self.lid.."FindAffordableStrategicTarget") +local Stargets=self:FindStrategicTargets() +local budget=self.Budget +local ftarget=nil +local Targets={} +for _,_data in pairs(Stargets)do +local data=_data +self:T("Considering Strategic Target "..data.name) +if data.points<=budget then +table.insert(Targets,data) +self:T(self.lid.."Affordable strategic target: "..data.name) +end +end +if#Targets==0 then +self:T(self.lid.."No suitable target found!") +return nil +end +if#Targets>1 then +ftarget=Targets[math.random(1,#Targets)] +else +ftarget=Targets[1] +end +if ftarget then +self:T(self.lid.."Final affordable strategic target: "..ftarget.name) +return ftarget +else +return nil +end +end +function STRATEGO:FindAffordableConsolidationTarget() +self:T(self.lid.."FindAffordableConsolidationTarget") +local Ctargets=self:FindConsolidationTargets() +local budget=self.Budget +local ftarget=nil +local Targets={} +for _,_data in pairs(Ctargets)do +local data=_data +self:T("Considering Consolidation Target "..data.name) +if data.points<=budget then +table.insert(Targets,data) +self:T(self.lid.."Affordable consolidation target: "..data.name) +end +end +if#Targets==0 then +self:T(self.lid.."No suitable target found!") +return nil +end +if#Targets>1 then +ftarget=Targets[math.random(1,#Targets)] +else +ftarget=Targets[1] +end +if ftarget then +self:T(self.lid.."Final affordable consolidation target: "..ftarget.name) +return ftarget +else +return nil +end +end +function STRATEGO:_FloodNext(next,filled,unfilled) +local start=self:FindNeighborNodes(next) +for _name,_ in pairs(start)do +if filled[_name]~=true then +self:T("Flooding ".._name) +filled[_name]=true +unfilled[_name]=nil +self:_FloodNext(_name,filled,unfilled) +end +end +return self +end +function STRATEGO:_FloodFill(Start,ABTable) +self:T("Start = "..tostring(Start)) +if Start==nil then return end +local filled={} +local unfilled={} +if ABTable then +unfilled=ABTable +else +for _name,_ in pairs(self.airbasetable)do +unfilled[_name]=true +end +end +filled[Start]=true +unfilled[Start]=nil +local start=self:FindNeighborNodes(Start) +for _name,_ in pairs(start)do +if filled[_name]~=true then +self:T("Flooding ".._name) +filled[_name]=true +unfilled[_name]=nil +self:_FloodNext(_name,filled,unfilled) +end +end +return filled,unfilled +end +function STRATEGO:_FloodTest(connect,draw) +local function GetElastic(bases) +local vec2table={} +for _name,_ in pairs(bases)do +local coord=self.airbasetable[_name].coord +local vec2=coord:GetVec2() +table.insert(vec2table,vec2) +end +local zone=ZONE_ELASTIC:New("STRATEGO-Floodtest-"..math.random(1,10000),vec2table) +return zone +end +local function DrawElastic(filled,drawit) +local zone=GetElastic(filled) +if drawit then +zone:SetColor({1,1,1},1) +zone:SetDrawCoalition(-1) +zone:Update(1,true) +end +return zone +end +local _,_,weight,name=self:GetHighestWeightNodes() +local filled,unfilled=self:_FloodFill(name) +local allin=true +if table.length(unfilled)>0 then +MESSAGE:New("There is at least one node island!",15,"STRATEGO"):ToAllIf(self.debug):ToLog() +allin=false +if self.debug==true then +local zone1=DrawElastic(filled,draw) +local zone2=DrawElastic(unfilled,draw) +local vertices1=zone1:GetVerticiesVec2() +local vertices2=zone2:GetVerticiesVec2() +local corner1=nil +local corner2=nil +local mindist=math.huge +local found=false +for _,_edge in pairs(vertices1)do +for _,_edge2 in pairs(vertices2)do +local dist=UTILS.VecDist2D(_edge,_edge2) +if distTstop then +self:E(string.format("ERROR: Recovery stop time %s lies before recovery start time %s! Recovery window rejected.",UTILS.SecondsToClock(Tstart),UTILS.SecondsToClock(Tstop))) +return self +end +if Tstop<=Tnow then +string.format("WARNING: Recovery stop time %s already over. Tnow=%s! Recovery window rejected.",UTILS.SecondsToClock(Tstop),UTILS.SecondsToClock(Tnow)) +return self +end +case=case or self.defaultcase +holdingoffset=holdingoffset or self.defaultoffset +if case==1 then +holdingoffset=0 +end +self.windowcount=self.windowcount+1 +local recovery={} +recovery.START=Tstart +recovery.STOP=Tstop +recovery.CASE=case +recovery.OFFSET=holdingoffset +recovery.OPEN=false +recovery.OVER=false +recovery.WIND=turnintowind +recovery.SPEED=speed or 20 +recovery.ID=self.windowcount +if uturn==nil or uturn==true then +recovery.UTURN=true +else +recovery.UTURN=false +end +table.insert(self.recoverytimes,recovery) +return recovery +end +function AIRBOSS:SetSquadronAI(SetGroup) +self.squadsetAI=SetGroup +return self +end +function AIRBOSS:SetExcludeAI(SetGroup) +self.excludesetAI=SetGroup +return self +end +function AIRBOSS:AddExcludeAI(Group) +self.excludesetAI=self.excludesetAI or SET_GROUP:New() +self.excludesetAI:AddGroup(Group) +return self +end +function AIRBOSS:CloseCurrentRecoveryWindow(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,self.CloseCurrentRecoveryWindow,self) +else +if self:IsRecovering()and self.recoverywindow and self.recoverywindow.OPEN then +self:RecoveryStop() +self.recoverywindow.OPEN=false +self.recoverywindow.OVER=true +self:DeleteRecoveryWindow(self.recoverywindow) +end +end +end +function AIRBOSS:DeleteAllRecoveryWindows(Delay) +for _,recovery in pairs(self.recoverytimes)do +self:I(self.lid..string.format("Deleting recovery window ID %s",tostring(recovery.ID))) +self:DeleteRecoveryWindow(recovery,Delay) +end +return self +end +function AIRBOSS:GetRecoveryWindowByID(id) +if id then +for _,_window in pairs(self.recoverytimes)do +local window=_window +if window and window.ID==id then +return window +end +end +end +return nil +end +function AIRBOSS:DeleteRecoveryWindow(Window,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,self.DeleteRecoveryWindow,self,Window) +else +for i,_recovery in pairs(self.recoverytimes)do +local recovery=_recovery +if Window and Window.ID==recovery.ID then +if Window.OPEN then +self:RecoveryStop() +else +table.remove(self.recoverytimes,i) +end +end +end +end +end +function AIRBOSS:SetRecoveryTurnTime(Interval) +self.dTturn=Interval or 300 +return self +end +function AIRBOSS:SetMPWireCorrection(Dcorr) +self.mpWireCorrection=Dcorr or 12 +return self +end +function AIRBOSS:SetQueueUpdateTime(TimeInterval) +self.dTqueue=TimeInterval or 30 +return self +end +function AIRBOSS:SetLSOCallInterval(TimeInterval) +self.LSOdT=TimeInterval or 4 +return self +end +function AIRBOSS:SetIntoWindLegacy(SwitchOn) +if SwitchOn==nil then +SwitchOn=true +end +self.intowindold=SwitchOn +return self +end +function AIRBOSS:SetAirbossNiceGuy(Switch) +if Switch==true or Switch==nil then +self.airbossnice=true +else +self.airbossnice=false +end +return self +end +function AIRBOSS:SetEmergencyLandings(Switch) +if Switch==true or Switch==nil then +self.emergency=true +else +self.emergency=false +end +return self +end +function AIRBOSS:SetDespawnOnEngineShutdown(Switch) +if Switch==true or Switch==nil then +self.despawnshutdown=true +else +self.despawnshutdown=false +end +return self +end +function AIRBOSS:SetRespawnAI(Switch) +if Switch==true or Switch==nil then +self.respawnAI=true +else +self.respawnAI=false +end +return self +end +function AIRBOSS:SetRefuelAI(LowFuelThreshold) +self.lowfuelAI=LowFuelThreshold or 10 +return self +end +function AIRBOSS:SetInitialMaxAlt(MaxAltitude) +self.initialmaxalt=UTILS.FeetToMeters(MaxAltitude or 1300) +return self +end +function AIRBOSS:SetSoundfilesFolder(FolderPath) +if FolderPath then +local lastchar=string.sub(FolderPath,-1) +if lastchar~="/"then +FolderPath=FolderPath.."/" +end +end +self.soundfolder=FolderPath +self:I(self.lid..string.format("Setting sound files folder to: %s",self.soundfolder)) +return self +end +function AIRBOSS:SetStatusUpdateTime(TimeInterval) +self.dTstatus=TimeInterval or 0.5 +return self +end +function AIRBOSS:SetDefaultMessageDuration(Duration) +self.Tmessage=Duration or 10 +return self +end +function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min,High,HIGH,Low,LOW) +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or +self.carriertype==AIRBOSS.CarrierType.HERMES or +self.carriertype==AIRBOSS.CarrierType.TARAWA or +self.carriertype==AIRBOSS.CarrierType.AMERICA or +self.carriertype==AIRBOSS.CarrierType.JCARLOS or +self.carriertype==AIRBOSS.CarrierType.CANBERRA then +self.gle._max=_max or 0.7 +self.gle.High=High or 1.4 +self.gle.HIGH=HIGH or 1.9 +self.gle._min=_min or-0.5 +self.gle.Low=Low or-1.2 +self.gle.LOW=LOW or-1.5 +else +self.gle._max=_max or 0.4 +self.gle.High=High or 0.8 +self.gle.HIGH=HIGH or 1.5 +self.gle._min=_min or-0.3 +self.gle.Low=Low or-0.6 +self.gle.LOW=LOW or-0.9 +end +return self +end +function AIRBOSS:SetLineupErrorThresholds(_max,_min,Left,LeftMed,LEFT,Right,RightMed,RIGHT) +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +self.lue._max=_max or 1.8 +self.lue._min=_min or-1.8 +self.lue.Left=Left or-2.8 +self.lue.LeftMed=LeftMed or-3.8 +self.lue.LEFT=LEFT or-4.5 +self.lue.Right=Right or 2.8 +self.lue.RightMed=RightMed or 3.8 +self.lue.RIGHT=RIGHT or 4.5 +else +self.lue._max=_max or 0.5 +self.lue._min=_min or-0.5 +self.lue.Left=Left or-1.0 +self.lue.LeftMed=LeftMed or-2.0 +self.lue.LEFT=LEFT or-3.0 +self.lue.Right=Right or 1.0 +self.lue.RightMed=RightMed or 2.0 +self.lue.RIGHT=RIGHT or 3.0 +end +return self +end +function AIRBOSS:SetMarshalRadius(Radius) +self.marshalradius=UTILS.NMToMeters(Radius or 2.8) +return self +end +function AIRBOSS:SetMenuSingleCarrier(Switch) +if Switch==true or Switch==nil then +self.menusingle=true +else +self.menusingle=false +end +return self +end +function AIRBOSS:SetMenuMarkZones(Switch) +if Switch==nil or Switch==true then +self.menumarkzones=true +else +self.menumarkzones=false +end +return self +end +function AIRBOSS:SetMenuSmokeZones(Switch) +if Switch==nil or Switch==true then +self.menusmokezones=true +else +self.menusmokezones=false +end +return self +end +function AIRBOSS:SetTrapSheet(Path,Prefix) +if io then +self.trapsheet=true +self.trappath=Path +self.trapprefix=Prefix +else +self:E(self.lid.."ERROR: io is not desanitized. Cannot save trap sheet.") +end +return self +end +function AIRBOSS:SetStaticWeather(Switch) +if Switch==nil or Switch==true then +self.staticweather=true +else +self.staticweather=false +end +return self +end +function AIRBOSS:SetTACANoff() +self.TACANon=false +return self +end +function AIRBOSS:SetTACAN(Channel,Mode,MorseCode) +self.TACANchannel=Channel or 74 +self.TACANmode=Mode or"X" +self.TACANmorse=MorseCode or"STN" +self.TACANon=true +return self +end +function AIRBOSS:SetICLSoff() +self.ICLSon=false +return self +end +function AIRBOSS:SetICLS(Channel,MorseCode) +self.ICLSchannel=Channel or 1 +self.ICLSmorse=MorseCode or"STN" +self.ICLSon=true +return self +end +function AIRBOSS:SetBeaconRefresh(TimeInterval) +self.dTbeacon=TimeInterval or(20*60) +return self +end +function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volume,AltBackend) +local Frequency=self.AirbossRadio.frequency +local Modulation=self.AirbossRadio.modulation +self.SRS=MSRS:New(PathToSRS,Frequency,Modulation,AltBackend) +self.SRS:SetCoalition(self:GetCoalition()) +self.SRS:SetCoordinate(self:GetCoordinate()) +self.SRS:SetCulture(Culture or"en-US") +self.SRS:SetGender(Gender or"male") +self.SRS:SetPort(Port or MSRS.port or 5002) +self.SRS:SetLabel(self.AirbossRadio.alias or"AIRBOSS") +self.SRS:SetCoordinate(self.carrier:GetCoordinate()) +self.SRS:SetVolume(Volume or 1) +if GoogleCreds then +self.SRS:SetGoogle(GoogleCreds) +end +if Voice then +self.SRS:SetVoice(Voice) +end +if(not Voice)and self.SRS and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then +self.SRS.voice=MSRS.poptions["gcloud"].voice or MSRS.Voices.Google.Standard.en_US_Standard_B +end +self.SRSQ=MSRSQUEUE:New("AIRBOSS") +self.SRSQ:SetTransmitOnlyWithPlayers(true) +if not self.PilotRadio then +self:SetSRSPilotVoice() +end +return self +end +function AIRBOSS:SetLSORadio(Frequency,Modulation,Voice,Gender,Culture) +self.LSOFreq=(Frequency or 264) +Modulation=Modulation or"AM" +if Modulation=="FM"then +self.LSOModu=radio.modulation.FM +else +self.LSOModu=radio.modulation.AM +end +self.LSORadio={} +self.LSORadio.frequency=self.LSOFreq +self.LSORadio.modulation=self.LSOModu +self.LSORadio.alias="LSO" +self.LSORadio.voice=Voice +self.LSORadio.gender=Gender or"male" +self.LSORadio.culture=Culture or"en-US" +return self +end +function AIRBOSS:SetAirbossRadio(Frequency,Modulation,Voice,Gender,Culture) +self.AirbossFreq=Frequency or self:_GetTowerFrequency()or 127.5 +Modulation=Modulation or"AM" +if type(Modulation)=="table"then +self.AirbossModu=Modulation +else +if Modulation=="FM"then +self.AirbossModu=radio.modulation.FM +else +self.AirbossModu=radio.modulation.AM +end +end +self.AirbossRadio={} +self.AirbossRadio.frequency=self.AirbossFreq +self.AirbossRadio.modulation=self.AirbossModu +self.AirbossRadio.alias="AIRBOSS" +self.AirbossRadio.voice=Voice +self.AirbossRadio.gender=Gender or"male" +self.AirbossRadio.culture=Culture or"en-US" +return self +end +function AIRBOSS:SetMarshalRadio(Frequency,Modulation,Voice,Gender,Culture) +self.MarshalFreq=Frequency or 305 +Modulation=Modulation or"AM" +if Modulation=="FM"then +self.MarshalModu=radio.modulation.FM +else +self.MarshalModu=radio.modulation.AM +end +self.MarshalRadio={} +self.MarshalRadio.frequency=self.MarshalFreq +self.MarshalRadio.modulation=self.MarshalModu +self.MarshalRadio.alias="MARSHAL" +self.MarshalRadio.voice=Voice +self.MarshalRadio.gender=Gender or"male" +self.MarshalRadio.culture=Culture or"en-US" +return self +end +function AIRBOSS:SetRadioUnitName(unitname) +self.senderac=unitname +return self +end +function AIRBOSS:SetRadioRelayLSO(unitname) +self.radiorelayLSO=unitname +return self +end +function AIRBOSS:SetRadioRelayMarshal(unitname) +self.radiorelayMSH=unitname +return self +end +function AIRBOSS:SetUserSoundRadio() +self.usersoundradio=true +return self +end +function AIRBOSS:SoundCheckLSO(delay) +if delay and delay>0 then +self:ScheduleOnce(delay,AIRBOSS.SoundCheckLSO,self) +else +local text="Playing LSO sound files:" +for _name,_call in pairs(self.LSOCall)do +local call=_call +text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".",call.file,call.suffix,call.duration,tostring(call.loud),call.subtitle) +self:RadioTransmission(self.LSORadio,call,false) +if call.loud then +self:RadioTransmission(self.LSORadio,call,true) +end +end +self:T(self.lid..text) +end +end +function AIRBOSS:SoundCheckMarshal(delay) +if delay and delay>0 then +self:ScheduleOnce(delay,AIRBOSS.SoundCheckMarshal,self) +else +local text="Playing Marshal sound files:" +for _name,_call in pairs(self.MarshalCall)do +local call=_call +text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".",call.file,call.suffix,call.duration,tostring(call.loud),call.subtitle) +self:RadioTransmission(self.MarshalRadio,call,false) +if call.loud then +self:RadioTransmission(self.MarshalRadio,call,true) +end +end +self:T(self.lid..text) +end +end +function AIRBOSS:SetMaxLandingPattern(nmax) +nmax=nmax or 4 +nmax=math.max(nmax,1) +nmax=math.min(nmax,6) +self.Nmaxpattern=nmax +return self +end +function AIRBOSS:SetMaxMarshalStacks(nmax) +self.Nmaxmarshal=nmax or 3 +self.Nmaxmarshal=math.max(self.Nmaxmarshal,1) +return self +end +function AIRBOSS:SetMaxSectionSize(nmax) +nmax=nmax or 2 +nmax=math.max(nmax,1) +nmax=math.min(nmax,4) +self.NmaxSection=nmax-1 +return self +end +function AIRBOSS:SetMaxSectionDistance(dmax) +if dmax then +if dmax<10 then +dmax=10 +elseif dmax>5000 then +dmax=5000 +end +end +self.maxsectiondistance=dmax or 100 +return self +end +function AIRBOSS:SetMaxFlightsPerStack(nmax) +nmax=nmax or 2 +nmax=math.max(nmax,1) +nmax=math.min(nmax,4) +self.NmaxStack=nmax +return self +end +function AIRBOSS:SetHandleAION() +self.handleai=true +return self +end +function AIRBOSS:SetExtraVoiceOvers(status) +self.xtVoiceOvers=status +return self +end +function AIRBOSS:SetExtraVoiceOversAI(status) +self.xtVoiceOversAI=status +return self +end +function AIRBOSS:SetHandleAIOFF() +self.handleai=false +return self +end +function AIRBOSS:SetRecoveryTanker(recoverytanker) +self.tanker=recoverytanker +return self +end +function AIRBOSS:SetAWACS(awacs) +self.awacs=awacs +return self +end +function AIRBOSS:SetDefaultPlayerSkill(skill) +self.defaultskill=skill or AIRBOSS.Difficulty.NORMAL +local gotit=false +for _,_skill in pairs(AIRBOSS.Difficulty)do +if _skill==self.defaultskill then +gotit=true +end +end +if not gotit then +self.defaultskill=AIRBOSS.Difficulty.NORMAL +self:E(self.lid..string.format("ERROR: Invalid default skill = %s. Resetting to Naval Aviator.",tostring(skill))) +end +return self +end +function AIRBOSS:SetAutoSave(path,filename) +self.autosave=true +self.autosavepath=path +self.autosavefile=filename +return self +end +function AIRBOSS:SetDebugModeON() +self.Debug=true +return self +end +function AIRBOSS:SetPatrolAdInfinitum(switch) +if switch==false then +self.adinfinitum=false +else +self.adinfinitum=true +end +return self +end +function AIRBOSS:SetMagneticDeclination(declination) +self.magvar=declination or UTILS.GetMagneticDeclination() +return self +end +function AIRBOSS:SetDebugModeOFF() +self.Debug=false +return self +end +function AIRBOSS:SetFunkManOn(Port,Host) +self.funkmanSocket=SOCKET:New(Port,Host) +return self +end +function AIRBOSS:GetNextRecoveryTime(InSeconds) +if self.recoverywindow then +if InSeconds then +return self.recoverywindow.START,self.recoverywindow.STOP +else +return UTILS.SecondsToClock(self.recoverywindow.START),UTILS.SecondsToClock(self.recoverywindow.STOP) +end +else +if InSeconds then +return-1,-1 +else +return"?","?" +end +end +end +function AIRBOSS:IsRecovering() +return self:is("Recovering") +end +function AIRBOSS:IsIdle() +return self:is("Idle") +end +function AIRBOSS:IsPaused() +return self:is("Paused") +end +function AIRBOSS:_ActivateBeacons() +self:T(self.lid..string.format("Activating Beacons (TACAN=%s, ICLS=%s)",tostring(self.TACANon),tostring(self.ICLSon))) +if self.TACANon then +self:I(self.lid..string.format("Activating TACAN Channel %d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse)) +self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true) +end +if self.ICLSon then +self:I(self.lid..string.format("Activating ICLS Channel %d (%s)",self.ICLSchannel,self.ICLSmorse)) +self.beacon:ActivateICLS(self.ICLSchannel,self.ICLSmorse) +end +self.Tbeacon=timer.getTime() +end +function AIRBOSS:onafterStart(From,Event,To) +self:I(self.lid..string.format("Starting AIRBOSS v%s for carrier unit %s of type %s on map %s",AIRBOSS.version,self.carrier:GetName(),self.carriertype,self.theatre)) +self:_ActivateBeacons() +self.Cposition=self:GetCoordinate() +self.Corientation=self.carrier:GetOrientationX() +self.Corientlast=self.Corientation +self.Tpupdate=timer.getTime() +if#self.recoverytimes==0 and false then +local Topen=timer.getAbsTime()+15*60 +local Tclose=Topen+3*60*60 +self:AddRecoveryWindow(UTILS.SecondsToClock(Topen),UTILS.SecondsToClock(Tclose)) +end +self:_CheckRecoveryTimes() +self.Tqueue=timer.getTime()-60 +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.RunwayTouch) +self:HandleEvent(EVENTS.EngineShutdown) +self:HandleEvent(EVENTS.Takeoff) +self:HandleEvent(EVENTS.Crash) +self:HandleEvent(EVENTS.Ejection) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft) +self:HandleEvent(EVENTS.MissionEnd) +self:HandleEvent(EVENTS.RemoveUnit) +self.StatusTimer=TIMER:New(self._Status,self):Start(2,0.5) +self:__Status(1) +end +function AIRBOSS:onafterStatus(From,Event,To) +local time=timer.getTime() +if time-self.Tqueue>self.dTqueue then +local clock=UTILS.SecondsToClock(timer.getAbsTime()) +local eta=UTILS.SecondsToClock(self:_GetETAatNextWP()) +local hdg=self:GetHeading() +local pos=self:GetCoordinate() +local speed=self.carrier:GetVelocityKNOTS() +if require then +self.magvar=pos:GetMagneticDeclination() +end +local collision=false +local holdtime=0 +if self.holdtimestamp then +holdtime=timer.getTime()-self.holdtimestamp +end +local NextWP=self:_GetNextWaypoint() +local ExpectedSpeed=UTILS.MpsToKnots(NextWP:GetVelocity()) +if speed<0.5 and ExpectedSpeed>0 and not(self.detour or self.turnintowind)then +if not self.holdtimestamp then +self:E(self.lid..string.format("Carrier came to an unexpected standstill. Trying to re-route in 3 min. Speed=%.1f knots, expected=%.1f knots",speed,ExpectedSpeed)) +self.holdtimestamp=timer.getTime() +else +if holdtime>3*60 then +local coord=self:GetCoordinate():Translate(500,hdg+10) +self:CarrierResumeRoute(coord) +self.holdtimestamp=nil +end +end +end +local text=string.format("Time %s - Status %s (case=%d) - Speed=%.1f kts - Heading=%d - WP=%d - ETA=%s - Turning=%s - Collision Warning=%s - Detour=%s - Turn Into Wind=%s - Holdtime=%d sec",clock,self:GetState(),self.case,speed,hdg,self.currentwp,eta,tostring(self.turning),tostring(collision),tostring(self.detour),tostring(self.turnintowind),holdtime) +self:T(self.lid..text) +text="Players:" +local i=0 +for _name,_player in pairs(self.players)do +i=i+1 +local player=_player +text=text..string.format("\n%d.) %s: Step=%s, Unit=%s, Airframe=%s",i,tostring(player.name),tostring(player.step),tostring(player.unitname),tostring(player.actype)) +end +if i==0 then +text=text.." none" +end +self:T(self.lid..text) +if collision then +if self.turnintowind then +self:CarrierResumeRoute(self.Creturnto) +if self:IsRecovering()and self.recoverywindow and self.recoverywindow.WIND then +self.recoverywindow.WIND=false +end +end +end +self:_CheckRecoveryTimes() +self:_ScanCarrierZone() +self:_CheckQueue() +self:_CheckCarrierTurning() +self:_CheckPatternUpdate() +self.Tqueue=time +end +if time-self.Tbeacon>self.dTbeacon then +self:_ActivateBeacons() +end +self:__Status(-30) +end +function AIRBOSS:_Status() +self:_CheckPlayerStatus() +self:_CheckAIStatus() +end +function AIRBOSS:_CheckAIStatus() +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +if flight.ai then +local fuel=flight.group:GetFuelMin()*100 +local text=string.format("Group %s fuel=%.1f %%",flight.groupname,fuel) +self:T3(self.lid..text) +if self.lowfuelAI and fuel=recovery.START then +if time0 then +local extmin=5*npattern +recovery.STOP=recovery.STOP+extmin*60 +local text=string.format("We still got flights in the pattern.\nRecovery time prolonged by %d minutes.\nNow get your act together and no more bolters!",extmin) +self:MessageToPattern(text,"AIRBOSS","99",10,false,nil) +else +self:RecoveryStop() +state="closing now" +recovery.OPEN=false +recovery.OVER=true +end +else +state="closed" +end +end +else +state="in the future" +if nextwindow==nil then +nextwindow=recovery +state="next in line" +end +end +text=text..string.format("\n- Start=%s Stop=%s Case=%d Offset=%d Open=%s Closed=%s Status=\"%s\"",Cstart,Cstop,recovery.CASE,recovery.OFFSET,tostring(recovery.OPEN),tostring(recovery.OVER),state) +end +self:T(self.lid..text) +self.recoverywindow=nil +if self:IsIdle()then +if nextwindow then +self:RecoveryCase(nextwindow.CASE,nextwindow.OFFSET) +if nextwindow.WIND and nextwindow.START-time5 +local _,vwind=self:GetWind() +if vwind<0.1 then +uturn=false +end +if not nextwindow.UTURN then +uturn=false +end +self:T(self.lid..string.format("Heading=%03d°, Wind=%03d° %.1f kts, Delta=%03d° ==> U-turn=%s",hdg,wind,UTILS.MpsToKnots(vwind),delta,tostring(uturn))) +local t=math.max(nextwindow.STOP-nextwindow.START+self.dTturn,60*60*24) +local v=UTILS.KnotsToMps(nextwindow.SPEED) +local vmax=self.carrier:GetSpeedMax()/3.6 +v=math.min(v,vmax) +self:CarrierTurnIntoWind(t,v,uturn) +end +self.recoverywindow=nextwindow +else +self:RecoveryCase() +end +else +if currwindow then +self.recoverywindow=currwindow +else +self.recoverywindow=nextwindow +end +end +self:T2({"FF",recoverywindow=self.recoverywindow}) +end +function AIRBOSS:_GetFlightLead(flight) +if flight.name~=flight.seclead then +local lead=self.players[flight.seclead] +return lead,false +else +return flight,true +end +end +function AIRBOSS:onbeforeRecoveryCase(From,Event,To,Case,Offset) +Case=Case or self.defaultcase +Offset=Offset or self.defaultoffset +if Case==self.case and Offset==self.holdingoffset then +return false +end +return true +end +function AIRBOSS:onafterRecoveryCase(From,Event,To,Case,Offset) +Case=Case or self.defaultcase +Offset=Offset or self.defaultoffset +local text=string.format("Switching recovery case %d ==> %d",self.case,Case) +if Case>1 then +text=text..string.format(" Holding offset angle %d degrees.",Offset) +end +MESSAGE:New(text,20,self.alias):ToAllIf(self.Debug) +self:T(self.lid..text) +self.case=Case +self.holdingoffset=Offset +for _,_flight in pairs(self.flights)do +local flight=_flight +if not(self:_InQueue(self.Qmarshal,flight.group)or self:_InQueue(self.Qpattern,flight.group))then +if flight.name~=flight.seclead then +local lead=self.players[flight.seclead] +if lead and not(self:_InQueue(self.Qmarshal,lead.group)or self:_InQueue(self.Qpattern,lead.group))then +flight.case=self.case +end +else +flight.case=self.case +end +end +end +end +function AIRBOSS:onafterRecoveryStart(From,Event,To,Case,Offset) +Case=Case or self.defaultcase +Offset=Offset or self.defaultoffset +self:_MarshalCallRecoveryStart(Case) +self:RecoveryCase(Case,Offset) +end +function AIRBOSS:onafterRecoveryStop(From,Event,To) +self:T(self.lid..string.format("Stopping aircraft recovery.")) +self:_MarshalCallRecoveryStopped(self.case) +if self.turnintowind then +local coord=self.Creturnto +if self.recoverywindow and self.recoverywindow.UTURN==false then +coord=nil +end +self:CarrierResumeRoute(coord) +end +if self.recoverywindow and self.recoverywindow.OPEN==true then +self.recoverywindow.OPEN=false +self.recoverywindow.OVER=true +self:DeleteRecoveryWindow(self.recoverywindow) +end +self:_CheckRecoveryTimes() +end +function AIRBOSS:onafterRecoveryPause(From,Event,To,duration) +self:T(self.lid..string.format("Pausing aircraft recovery.")) +if duration then +self:__RecoveryUnpause(duration) +local clock=UTILS.SecondsToClock(timer.getAbsTime()+duration) +self:_MarshalCallRecoveryPausedResumedAt(clock) +else +local text=string.format("aircraft recovery is paused until further notice.") +self:_MarshalCallRecoveryPausedNotice() +end +end +function AIRBOSS:onafterRecoveryUnpause(From,Event,To) +self:T(self.lid..string.format("Unpausing aircraft recovery.")) +self:_MarshalCallResumeRecovery() +end +function AIRBOSS:onafterPassingWaypoint(From,Event,To,n) +self:I(self.lid..string.format("Carrier passed waypoint %d.",n)) +end +function AIRBOSS:onafterIdle(From,Event,To) +self:T(self.lid..string.format("Carrier goes to idle.")) +end +function AIRBOSS:onafterStop(From,Event,To) +self:I(self.lid..string.format("Stopping airboss script.")) +self:UnHandleEvent(EVENTS.Birth) +self:UnHandleEvent(EVENTS.RunwayTouch) +self:UnHandleEvent(EVENTS.EngineShutdown) +self:UnHandleEvent(EVENTS.Takeoff) +self:UnHandleEvent(EVENTS.Crash) +self:UnHandleEvent(EVENTS.Ejection) +self:UnHandleEvent(EVENTS.PlayerLeaveUnit) +self:UnHandleEvent(EVENTS.MissionEnd) +self.CallScheduler:Clear() +end +function AIRBOSS:_InitStennis() +self.carrierparam.sterndist=-153 +self.carrierparam.deckheight=18.30 +self.carrierparam.totlength=310 +self.carrierparam.totwidthport=40 +self.carrierparam.totwidthstarboard=30 +self.carrierparam.rwyangle=-9.1359 +self.carrierparam.rwylength=225 +self.carrierparam.rwywidth=20 +self.carrierparam.wire1=46 +self.carrierparam.wire2=46+12 +self.carrierparam.wire3=46+24 +self.carrierparam.wire4=46+35 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 +self.Platform.name="Platform 5k" +self.Platform.Xmin=-UTILS.NMToMeters(22) +self.Platform.Xmax=nil +self.Platform.Zmin=-UTILS.NMToMeters(30) +self.Platform.Zmax=UTILS.NMToMeters(30) +self.Platform.LimitXmin=nil +self.Platform.LimitXmax=nil +self.Platform.LimitZmin=nil +self.Platform.LimitZmax=nil +self.DirtyUp.name="Dirty Up" +self.DirtyUp.Xmin=-UTILS.NMToMeters(21) +self.DirtyUp.Xmax=nil +self.DirtyUp.Zmin=-UTILS.NMToMeters(30) +self.DirtyUp.Zmax=UTILS.NMToMeters(30) +self.DirtyUp.LimitXmin=nil +self.DirtyUp.LimitXmax=nil +self.DirtyUp.LimitZmin=nil +self.DirtyUp.LimitZmax=nil +self.Bullseye.name="Bullseye" +self.Bullseye.Xmin=-UTILS.NMToMeters(11) +self.Bullseye.Xmax=nil +self.Bullseye.Zmin=-UTILS.NMToMeters(30) +self.Bullseye.Zmax=UTILS.NMToMeters(30) +self.Bullseye.LimitXmin=nil +self.Bullseye.LimitXmax=nil +self.Bullseye.LimitZmin=nil +self.Bullseye.LimitZmax=nil +self.BreakEntry.name="Break Entry" +self.BreakEntry.Xmin=-UTILS.NMToMeters(4) +self.BreakEntry.Xmax=nil +self.BreakEntry.Zmin=-UTILS.NMToMeters(0.5) +self.BreakEntry.Zmax=UTILS.NMToMeters(1.5) +self.BreakEntry.LimitXmin=0 +self.BreakEntry.LimitXmax=nil +self.BreakEntry.LimitZmin=nil +self.BreakEntry.LimitZmax=nil +self.BreakEarly.name="Early Break" +self.BreakEarly.Xmin=-UTILS.NMToMeters(1) +self.BreakEarly.Xmax=UTILS.NMToMeters(7) +self.BreakEarly.Zmin=-UTILS.NMToMeters(2) +self.BreakEarly.Zmax=UTILS.NMToMeters(1) +self.BreakEarly.LimitXmin=0 +self.BreakEarly.LimitXmax=nil +self.BreakEarly.LimitZmin=-UTILS.NMToMeters(0.2) +self.BreakEarly.LimitZmax=nil +self.BreakLate.name="Late Break" +self.BreakLate.Xmin=-UTILS.NMToMeters(1) +self.BreakLate.Xmax=UTILS.NMToMeters(7) +self.BreakLate.Zmin=-UTILS.NMToMeters(2) +self.BreakLate.Zmax=UTILS.NMToMeters(1) +self.BreakLate.LimitXmin=0 +self.BreakLate.LimitXmax=nil +self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.8) +self.BreakLate.LimitZmax=nil +self.Abeam.name="Abeam Position" +self.Abeam.Xmin=-UTILS.NMToMeters(5) +self.Abeam.Xmax=UTILS.NMToMeters(7) +self.Abeam.Zmin=-UTILS.NMToMeters(2) +self.Abeam.Zmax=500 +self.Abeam.LimitXmin=-200 +self.Abeam.LimitXmax=nil +self.Abeam.LimitZmin=nil +self.Abeam.LimitZmax=nil +self.Ninety.name="Ninety" +self.Ninety.Xmin=-UTILS.NMToMeters(4) +self.Ninety.Xmax=0 +self.Ninety.Zmin=-UTILS.NMToMeters(2) +self.Ninety.Zmax=nil +self.Ninety.LimitXmin=nil +self.Ninety.LimitXmax=nil +self.Ninety.LimitZmin=nil +self.Ninety.LimitZmax=-UTILS.NMToMeters(0.6) +self.Wake.name="Wake" +self.Wake.Xmin=-UTILS.NMToMeters(4) +self.Wake.Xmax=0 +self.Wake.Zmin=-2000 +self.Wake.Zmax=nil +self.Wake.LimitXmin=nil +self.Wake.LimitXmax=nil +self.Wake.LimitZmin=0 +self.Wake.LimitZmax=nil +self.Final.name="Final" +self.Final.Xmin=-UTILS.NMToMeters(4) +self.Final.Xmax=0 +self.Final.Zmin=-2000 +self.Final.Zmax=nil +self.Final.LimitXmin=nil +self.Final.LimitXmax=nil +self.Final.LimitZmin=nil +self.Final.LimitZmax=nil +self.Groove.name="Groove" +self.Groove.Xmin=-UTILS.NMToMeters(4) +self.Groove.Xmax=nil +self.Groove.Zmin=-UTILS.NMToMeters(2) +self.Groove.Zmax=UTILS.NMToMeters(2) +self.Groove.LimitXmin=nil +self.Groove.LimitXmax=nil +self.Groove.LimitZmin=nil +self.Groove.LimitZmax=nil +end +function AIRBOSS:_InitNimitz() +self:_InitStennis() +self.carrierparam.sterndist=-164 +self.carrierparam.deckheight=20.1494 +self.carrierparam.totlength=332.8 +self.carrierparam.totwidthport=45 +self.carrierparam.totwidthstarboard=35 +self.carrierparam.rwyangle=-9.1359 +self.carrierparam.rwylength=250 +self.carrierparam.rwywidth=25 +self.carrierparam.wire1=55 +self.carrierparam.wire2=67 +self.carrierparam.wire3=79 +self.carrierparam.wire4=96 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 +end +function AIRBOSS:_InitForrestal() +self:_InitNimitz() +self.carrierparam.sterndist=-135.5 +self.carrierparam.deckheight=20 +self.carrierparam.totlength=315 +self.carrierparam.totwidthport=45 +self.carrierparam.totwidthstarboard=35 +self.carrierparam.rwyangle=-9.1359 +self.carrierparam.rwylength=212 +self.carrierparam.rwywidth=25 +self.carrierparam.wire1=44 +self.carrierparam.wire2=54 +self.carrierparam.wire3=64 +self.carrierparam.wire4=74 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 +end +function AIRBOSS:_InitEnterprise() +self:_InitForrestal() +self.carrierparam.sterndist=-164.30 +self.carrierparam.deckheight=19.52 +self.carrierparam.totlength=335 +self.carrierparam.rwylength=223 +self.carrierparam.wire1=57.7 +self.carrierparam.wire2=69.6 +self.carrierparam.wire3=79.5 +self.carrierparam.wire4=90.0 +end +function AIRBOSS:_InitEssex() +self:_InitNimitz() +self.carrierparam.sterndist=-126 +self.carrierparam.deckheight=19.27 +self.carrierparam.totlength=268 +self.carrierparam.totwidthport=23 +self.carrierparam.totwidthstarboard=23 +self.carrierparam.rwyangle=0.0 +self.carrierparam.rwylength=265 +self.carrierparam.rwywidth=20 +self.carrierparam.wire1=21.9 +self.carrierparam.wire2=28.3 +self.carrierparam.wire3=34.7 +self.carrierparam.wire4=41.1 +self.carrierparam.wire5=47.4 +self.carrierparam.wire6=53.7 +self.carrierparam.wire7=59.0 +self.carrierparam.wire8=64.1 +self.carrierparam.wire9=72.7 +self.carrierparam.wire10=78.0 +self.carrierparam.wire11=85.5 +self.carrierparam.wire12=105.9 +self.carrierparam.wire13=113.3 +self.carrierparam.wire14=121.0 +self.carrierparam.wire15=128.5 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 +end +function AIRBOSS:_InitBonHommeRichard() +self:_InitEssex() +self.carrierparam.deckheight=16.95 +self.carrierparam.rwyangle=-11.4 +self.carrierparam.rwylength=97 +self.carrierparam.rwywidth=20 +self.carrierparam.wire1=40.4 +self.carrierparam.wire2=45 +self.carrierparam.wire3=51 +self.carrierparam.wire4=58.1 +end +function AIRBOSS:_InitEssexSCB125() +self:_InitBonHommeRichard() +end +function AIRBOSS:_InitHermes() +self:_InitStennis() +self.carrierparam.sterndist=-105 +self.carrierparam.deckheight=12 +self.carrierparam.totlength=228.19 +self.carrierparam.totwidthport=20.5 +self.carrierparam.totwidthstarboard=24.5 +self.carrierparam.rwyangle=0 +self.carrierparam.rwylength=215 +self.carrierparam.rwywidth=13 +self.carrierparam.wire1=nil +self.carrierparam.wire2=nil +self.carrierparam.wire3=nil +self.carrierparam.wire4=nil +self.carrierparam.landingspot=69 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot +self.BreakLate.name="Late Break" +self.BreakLate.Xmin=-UTILS.NMToMeters(1) +self.BreakLate.Xmax=UTILS.NMToMeters(5) +self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) +self.BreakLate.Zmax=UTILS.NMToMeters(1) +self.BreakLate.LimitXmin=0 +self.BreakLate.LimitXmax=nil +self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) +self.BreakLate.LimitZmax=nil +end +function AIRBOSS:_InitInvincible() +self:_InitStennis() +self.carrierparam.sterndist=-105 +self.carrierparam.deckheight=12 +self.carrierparam.totlength=228.19 +self.carrierparam.totwidthport=20.5 +self.carrierparam.totwidthstarboard=24.5 +self.carrierparam.rwyangle=0 +self.carrierparam.rwylength=215 +self.carrierparam.rwywidth=13 +self.carrierparam.wire1=nil +self.carrierparam.wire2=nil +self.carrierparam.wire3=nil +self.carrierparam.wire4=nil +self.carrierparam.landingspot=69 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot +self.BreakLate.name="Late Break" +self.BreakLate.Xmin=-UTILS.NMToMeters(1) +self.BreakLate.Xmax=UTILS.NMToMeters(5) +self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) +self.BreakLate.Zmax=UTILS.NMToMeters(1) +self.BreakLate.LimitXmin=0 +self.BreakLate.LimitXmax=nil +self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) +self.BreakLate.LimitZmax=nil +end +function AIRBOSS:_InitTarawa() +self:_InitStennis() +self.carrierparam.sterndist=-125 +self.carrierparam.deckheight=21 +self.carrierparam.totlength=245 +self.carrierparam.totwidthport=10 +self.carrierparam.totwidthstarboard=25 +self.carrierparam.rwyangle=0 +self.carrierparam.rwylength=225 +self.carrierparam.rwywidth=15 +self.carrierparam.wire1=nil +self.carrierparam.wire2=nil +self.carrierparam.wire3=nil +self.carrierparam.wire4=nil +self.carrierparam.landingspot=57 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot +self.BreakLate.name="Late Break" +self.BreakLate.Xmin=-UTILS.NMToMeters(1) +self.BreakLate.Xmax=UTILS.NMToMeters(5) +self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) +self.BreakLate.Zmax=UTILS.NMToMeters(1) +self.BreakLate.LimitXmin=0 +self.BreakLate.LimitXmax=nil +self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) +self.BreakLate.LimitZmax=nil +end +function AIRBOSS:_InitAmerica() +self:_InitStennis() +self.carrierparam.sterndist=-125 +self.carrierparam.deckheight=20 +self.carrierparam.totlength=257 +self.carrierparam.totwidthport=11 +self.carrierparam.totwidthstarboard=25 +self.carrierparam.rwyangle=0 +self.carrierparam.rwylength=240 +self.carrierparam.rwywidth=15 +self.carrierparam.wire1=nil +self.carrierparam.wire2=nil +self.carrierparam.wire3=nil +self.carrierparam.wire4=nil +self.carrierparam.landingspot=59 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot +self.BreakLate.name="Late Break" +self.BreakLate.Xmin=-UTILS.NMToMeters(1) +self.BreakLate.Xmax=UTILS.NMToMeters(5) +self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) +self.BreakLate.Zmax=UTILS.NMToMeters(1) +self.BreakLate.LimitXmin=0 +self.BreakLate.LimitXmax=nil +self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) +self.BreakLate.LimitZmax=nil +end +function AIRBOSS:_InitJcarlos() +self:_InitStennis() +self.carrierparam.sterndist=-125 +self.carrierparam.deckheight=20 +self.carrierparam.totlength=231 +self.carrierparam.totwidthport=10 +self.carrierparam.totwidthstarboard=22 +self.carrierparam.rwyangle=0 +self.carrierparam.rwylength=202 +self.carrierparam.rwywidth=14 +self.carrierparam.wire1=nil +self.carrierparam.wire2=nil +self.carrierparam.wire3=nil +self.carrierparam.wire4=nil +self.carrierparam.landingspot=89 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot +self.BreakLate.name="Late Break" +self.BreakLate.Xmin=-UTILS.NMToMeters(1) +self.BreakLate.Xmax=UTILS.NMToMeters(5) +self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) +self.BreakLate.Zmax=UTILS.NMToMeters(1) +self.BreakLate.LimitXmin=0 +self.BreakLate.LimitXmax=nil +self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) +self.BreakLate.LimitZmax=nil +end +function AIRBOSS:_InitCanberra() +self:_InitJcarlos() +end +function AIRBOSS:SetVoiceOversMarshalByGabriella(mizfolder) +if mizfolder then +local lastchar=string.sub(mizfolder,-1) +if lastchar~="/"then +mizfolder=mizfolder.."/" +end +self.soundfolderMSH=mizfolder +else +self.soundfolderMSH=self.soundfolder +end +self:I(self.lid..string.format("Marshal Gabriella reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH))) +self.MarshalCall.AFFIRMATIVE.duration=0.65 +self.MarshalCall.ALTIMETER.duration=0.60 +self.MarshalCall.BRC.duration=0.67 +self.MarshalCall.CARRIERTURNTOHEADING.duration=1.62 +self.MarshalCall.CASE.duration=0.30 +self.MarshalCall.CHARLIETIME.duration=0.77 +self.MarshalCall.CLEAREDFORRECOVERY.duration=0.93 +self.MarshalCall.DECKCLOSED.duration=0.73 +self.MarshalCall.DEGREES.duration=0.48 +self.MarshalCall.EXPECTED.duration=0.50 +self.MarshalCall.FLYNEEDLES.duration=0.89 +self.MarshalCall.HOLDATANGELS.duration=0.81 +self.MarshalCall.HOURS.duration=0.41 +self.MarshalCall.MARSHALRADIAL.duration=0.95 +self.MarshalCall.N0.duration=0.41 +self.MarshalCall.N1.duration=0.30 +self.MarshalCall.N2.duration=0.34 +self.MarshalCall.N3.duration=0.31 +self.MarshalCall.N4.duration=0.34 +self.MarshalCall.N5.duration=0.30 +self.MarshalCall.N6.duration=0.33 +self.MarshalCall.N7.duration=0.38 +self.MarshalCall.N8.duration=0.35 +self.MarshalCall.N9.duration=0.35 +self.MarshalCall.NEGATIVE.duration=0.60 +self.MarshalCall.NEWFB.duration=0.95 +self.MarshalCall.OPS.duration=0.23 +self.MarshalCall.POINT.duration=0.38 +self.MarshalCall.RADIOCHECK.duration=1.27 +self.MarshalCall.RECOVERY.duration=0.60 +self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.25 +self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.55 +self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=2.55 +self.MarshalCall.REPORTSEEME.duration=0.87 +self.MarshalCall.RESUMERECOVERY.duration=1.55 +self.MarshalCall.ROGER.duration=0.50 +self.MarshalCall.SAYNEEDLES.duration=0.82 +self.MarshalCall.STACKFULL.duration=5.70 +self.MarshalCall.STARTINGRECOVERY.duration=1.61 +end +function AIRBOSS:SetVoiceOversMarshalByRaynor(mizfolder) +if mizfolder then +local lastchar=string.sub(mizfolder,-1) +if lastchar~="/"then +mizfolder=mizfolder.."/" +end +self.soundfolderMSH=mizfolder +else +self.soundfolderMSH=self.soundfolder +end +self:I(self.lid..string.format("Marshal Raynor reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH))) +self.MarshalCall.AFFIRMATIVE.duration=0.70 +self.MarshalCall.ALTIMETER.duration=0.60 +self.MarshalCall.BRC.duration=0.60 +self.MarshalCall.CARRIERTURNTOHEADING.duration=1.87 +self.MarshalCall.CASE.duration=0.60 +self.MarshalCall.CHARLIETIME.duration=0.81 +self.MarshalCall.CLEAREDFORRECOVERY.duration=1.21 +self.MarshalCall.DECKCLOSED.duration=0.86 +self.MarshalCall.DEGREES.duration=0.55 +self.MarshalCall.EXPECTED.duration=0.61 +self.MarshalCall.FLYNEEDLES.duration=0.90 +self.MarshalCall.HOLDATANGELS.duration=0.91 +self.MarshalCall.HOURS.duration=0.54 +self.MarshalCall.MARSHALRADIAL.duration=0.80 +self.MarshalCall.N0.duration=0.38 +self.MarshalCall.N1.duration=0.30 +self.MarshalCall.N2.duration=0.30 +self.MarshalCall.N3.duration=0.30 +self.MarshalCall.N4.duration=0.32 +self.MarshalCall.N5.duration=0.41 +self.MarshalCall.N6.duration=0.48 +self.MarshalCall.N7.duration=0.51 +self.MarshalCall.N8.duration=0.38 +self.MarshalCall.N9.duration=0.34 +self.MarshalCall.NEGATIVE.duration=0.60 +self.MarshalCall.NEWFB.duration=1.10 +self.MarshalCall.OPS.duration=0.46 +self.MarshalCall.POINT.duration=0.21 +self.MarshalCall.RADIOCHECK.duration=0.95 +self.MarshalCall.RECOVERY.duration=0.63 +self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.36 +self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.8 +self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=2.75 +self.MarshalCall.REPORTSEEME.duration=1.06 +self.MarshalCall.RESUMERECOVERY.duration=1.41 +self.MarshalCall.ROGER.duration=0.41 +self.MarshalCall.SAYNEEDLES.duration=0.79 +self.MarshalCall.STACKFULL.duration=4.70 +self.MarshalCall.STARTINGRECOVERY.duration=2.06 +end +function AIRBOSS:SetVoiceOversLSOByRaynor(mizfolder) +if mizfolder then +local lastchar=string.sub(mizfolder,-1) +if lastchar~="/"then +mizfolder=mizfolder.."/" +end +self.soundfolderLSO=mizfolder +else +self.soundfolderLSO=self.soundfolder +end +self:I(self.lid..string.format("LSO Raynor reporting for duty! Soundfolder=%s",tostring(self.soundfolderLSO))) +self.LSOCall.BOLTER.duration=0.75 +self.LSOCall.CALLTHEBALL.duration=0.625 +self.LSOCall.CHECK.duration=0.40 +self.LSOCall.CLEAREDTOLAND.duration=0.85 +self.LSOCall.COMELEFT.duration=0.60 +self.LSOCall.DEPARTANDREENTER.duration=1.10 +self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.30 +self.LSOCall.EXPECTSPOT75.duration=1.85 +self.LSOCall.EXPECTSPOT5.duration=1.3 +self.LSOCall.FAST.duration=0.75 +self.LSOCall.FOULDECK.duration=0.75 +self.LSOCall.HIGH.duration=0.65 +self.LSOCall.IDLE.duration=0.40 +self.LSOCall.LONGINGROOVE.duration=1.25 +self.LSOCall.LOW.duration=0.60 +self.LSOCall.N0.duration=0.38 +self.LSOCall.N1.duration=0.30 +self.LSOCall.N2.duration=0.30 +self.LSOCall.N3.duration=0.30 +self.LSOCall.N4.duration=0.32 +self.LSOCall.N5.duration=0.41 +self.LSOCall.N6.duration=0.48 +self.LSOCall.N7.duration=0.51 +self.LSOCall.N8.duration=0.38 +self.LSOCall.N9.duration=0.34 +self.LSOCall.PADDLESCONTACT.duration=0.91 +self.LSOCall.POWERsoft.duration=0.9 +self.LSOCall.POWER.duration=0.45 +self.LSOCall.RADIOCHECK.duration=0.90 +self.LSOCall.RIGHTFORLINEUP.duration=0.70 +self.LSOCall.ROGERBALL.duration=0.72 +self.LSOCall.SLOW.duration=0.63 +self.LSOCall.STABILIZED.duration=0.75 +self.LSOCall.WAVEOFF.duration=0.55 +self.LSOCall.WELCOMEABOARD.duration=0.80 +end +function AIRBOSS:SetVoiceOversLSOByFF(mizfolder) +if mizfolder then +local lastchar=string.sub(mizfolder,-1) +if lastchar~="/"then +mizfolder=mizfolder.."/" +end +self.soundfolderLSO=mizfolder +else +self.soundfolderLSO=self.soundfolder +end +self:I(self.lid..string.format("LSO FF reporting for duty! Soundfolder=%s",tostring(self.soundfolderLSO))) +self.LSOCall.BOLTER.duration=0.75 +self.LSOCall.CALLTHEBALL.duration=0.60 +self.LSOCall.CHECK.duration=0.45 +self.LSOCall.CLEAREDTOLAND.duration=1.00 +self.LSOCall.COMELEFT.duration=0.60 +self.LSOCall.DEPARTANDREENTER.duration=1.10 +self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.20 +self.LSOCall.EXPECTSPOT75.duration=2.00 +self.LSOCall.EXPECTSPOT5.duration=1.3 +self.LSOCall.FAST.duration=0.70 +self.LSOCall.FOULDECK.duration=0.62 +self.LSOCall.HIGH.duration=0.65 +self.LSOCall.IDLE.duration=0.45 +self.LSOCall.LONGINGROOVE.duration=1.20 +self.LSOCall.LOW.duration=0.50 +self.LSOCall.N0.duration=0.40 +self.LSOCall.N1.duration=0.25 +self.LSOCall.N2.duration=0.37 +self.LSOCall.N3.duration=0.37 +self.LSOCall.N4.duration=0.39 +self.LSOCall.N5.duration=0.39 +self.LSOCall.N6.duration=0.40 +self.LSOCall.N7.duration=0.40 +self.LSOCall.N8.duration=0.37 +self.LSOCall.N9.duration=0.40 +self.LSOCall.PADDLESCONTACT.duration=1.00 +self.LSOCall.POWER.duration=0.50 +self.LSOCall.POWERsoft.duration=0.9 +self.LSOCall.RADIOCHECK.duration=1.10 +self.LSOCall.RIGHTFORLINEUP.duration=0.80 +self.LSOCall.ROGERBALL.duration=1.00 +self.LSOCall.SLOW.duration=0.65 +self.LSOCall.SLOW.duration=0.59 +self.LSOCall.STABILIZED.duration=0.90 +self.LSOCall.WAVEOFF.duration=0.60 +self.LSOCall.WELCOMEABOARD.duration=1.00 +end +function AIRBOSS:SetVoiceOversMarshalByFF(mizfolder) +if mizfolder then +local lastchar=string.sub(mizfolder,-1) +if lastchar~="/"then +mizfolder=mizfolder.."/" +end +self.soundfolderMSH=mizfolder +else +self.soundfolderMSH=self.soundfolder +end +self:I(self.lid..string.format("Marshal FF reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH))) +self.MarshalCall.AFFIRMATIVE.duration=0.90 +self.MarshalCall.ALTIMETER.duration=0.85 +self.MarshalCall.BRC.duration=0.80 +self.MarshalCall.CARRIERTURNTOHEADING.duration=2.48 +self.MarshalCall.CASE.duration=0.40 +self.MarshalCall.CHARLIETIME.duration=0.90 +self.MarshalCall.CLEAREDFORRECOVERY.duration=1.25 +self.MarshalCall.DECKCLOSED.duration=1.10 +self.MarshalCall.DEGREES.duration=0.60 +self.MarshalCall.EXPECTED.duration=0.55 +self.MarshalCall.FLYNEEDLES.duration=0.90 +self.MarshalCall.HOLDATANGELS.duration=1.10 +self.MarshalCall.HOURS.duration=0.60 +self.MarshalCall.MARSHALRADIAL.duration=1.10 +self.MarshalCall.N0.duration=0.40 +self.MarshalCall.N1.duration=0.25 +self.MarshalCall.N2.duration=0.37 +self.MarshalCall.N3.duration=0.37 +self.MarshalCall.N4.duration=0.39 +self.MarshalCall.N5.duration=0.39 +self.MarshalCall.N6.duration=0.40 +self.MarshalCall.N7.duration=0.40 +self.MarshalCall.N8.duration=0.37 +self.MarshalCall.N9.duration=0.40 +self.MarshalCall.NEGATIVE.duration=0.80 +self.MarshalCall.NEWFB.duration=1.35 +self.MarshalCall.OPS.duration=0.48 +self.MarshalCall.POINT.duration=0.33 +self.MarshalCall.RADIOCHECK.duration=1.20 +self.MarshalCall.RECOVERY.duration=0.70 +self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.65 +self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.9 +self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=3.40 +self.MarshalCall.REPORTSEEME.duration=0.95 +self.MarshalCall.RESUMERECOVERY.duration=1.75 +self.MarshalCall.ROGER.duration=0.53 +self.MarshalCall.SAYNEEDLES.duration=0.90 +self.MarshalCall.STACKFULL.duration=6.35 +self.MarshalCall.STARTINGRECOVERY.duration=2.65 +end +function AIRBOSS:_InitVoiceOvers() +self.LSOCall={ +BOLTER={file="LSO-BolterBolter",suffix="ogg",loud=false,subtitle="Bolter, Bolter",duration=0.75,subduration=5}, +CALLTHEBALL={file="LSO-CallTheBall",suffix="ogg",loud=false,subtitle="Call the ball",duration=0.6,subduration=2}, +CHECK={file="LSO-Check",suffix="ogg",loud=false,subtitle="Check",duration=0.45,subduration=2.5}, +CLEAREDTOLAND={file="LSO-ClearedToLand",suffix="ogg",loud=false,subtitle="Cleared to land",duration=1.0,subduration=5}, +COMELEFT={file="LSO-ComeLeft",suffix="ogg",loud=true,subtitle="Come left",duration=0.60,subduration=1}, +RADIOCHECK={file="LSO-RadioCheck",suffix="ogg",loud=false,subtitle="Paddles, radio check",duration=1.1,subduration=5}, +RIGHTFORLINEUP={file="LSO-RightForLineup",suffix="ogg",loud=true,subtitle="Right for line up",duration=0.80,subduration=1}, +HIGH={file="LSO-High",suffix="ogg",loud=true,subtitle="You're high",duration=0.65,subduration=1}, +LOW={file="LSO-Low",suffix="ogg",loud=true,subtitle="You're low",duration=0.50,subduration=1}, +POWER={file="LSO-Power",suffix="ogg",loud=true,subtitle="Power",duration=0.50,subduration=1}, +POWERsoft={file="LSO-Power-soft",suffix="ogg",loud=false,subtitle="Power-soft",duration=0.90,subduration=1}, +SLOW={file="LSO-Slow",suffix="ogg",loud=true,subtitle="You're slow",duration=0.65,subduration=1}, +FAST={file="LSO-Fast",suffix="ogg",loud=true,subtitle="You're fast",duration=0.70,subduration=1}, +ROGERBALL={file="LSO-RogerBall",suffix="ogg",loud=false,subtitle="Roger ball",duration=1.00,subduration=2}, +WAVEOFF={file="LSO-WaveOff",suffix="ogg",loud=false,subtitle="Wave off",duration=0.6,subduration=5}, +LONGINGROOVE={file="LSO-LongInTheGroove",suffix="ogg",loud=false,subtitle="You're long in the groove",duration=1.2,subduration=5}, +FOULDECK={file="LSO-FoulDeck",suffix="ogg",loud=false,subtitle="Foul deck",duration=0.62,subduration=5}, +DEPARTANDREENTER={file="LSO-DepartAndReenter",suffix="ogg",loud=false,subtitle="Depart and re-enter",duration=1.1,subduration=5}, +PADDLESCONTACT={file="LSO-PaddlesContact",suffix="ogg",loud=false,subtitle="Paddles, contact",duration=1.0,subduration=5}, +WELCOMEABOARD={file="LSO-WelcomeAboard",suffix="ogg",loud=false,subtitle="Welcome aboard",duration=1.0,subduration=5}, +EXPECTHEAVYWAVEOFF={file="LSO-ExpectHeavyWaveoff",suffix="ogg",loud=false,subtitle="Expect heavy waveoff",duration=1.2,subduration=5}, +EXPECTSPOT75={file="LSO-ExpectSpot75",suffix="ogg",loud=false,subtitle="Expect spot 7.5",duration=2.0,subduration=5}, +EXPECTSPOT5={file="LSO-ExpectSpot5",suffix="ogg",loud=false,subtitle="Expect spot 5",duration=1.3,subduration=5}, +STABILIZED={file="LSO-Stabilized",suffix="ogg",loud=false,subtitle="Stabilized",duration=0.9,subduration=5}, +IDLE={file="LSO-Idle",suffix="ogg",loud=false,subtitle="Idle",duration=0.45,subduration=5}, +N0={file="LSO-N0",suffix="ogg",loud=false,subtitle="",duration=0.40}, +N1={file="LSO-N1",suffix="ogg",loud=false,subtitle="",duration=0.25}, +N2={file="LSO-N2",suffix="ogg",loud=false,subtitle="",duration=0.37}, +N3={file="LSO-N3",suffix="ogg",loud=false,subtitle="",duration=0.37}, +N4={file="LSO-N4",suffix="ogg",loud=false,subtitle="",duration=0.39}, +N5={file="LSO-N5",suffix="ogg",loud=false,subtitle="",duration=0.39}, +N6={file="LSO-N6",suffix="ogg",loud=false,subtitle="",duration=0.40}, +N7={file="LSO-N7",suffix="ogg",loud=false,subtitle="",duration=0.40}, +N8={file="LSO-N8",suffix="ogg",loud=false,subtitle="",duration=0.37}, +N9={file="LSO-N9",suffix="ogg",loud=false,subtitle="",duration=0.40}, +CLICK={file="AIRBOSS-RadioClick",suffix="ogg",loud=false,subtitle="",duration=0.35}, +NOISE={file="AIRBOSS-Noise",suffix="ogg",loud=false,subtitle="",duration=3.6}, +SPINIT={file="AIRBOSS-SpinIt",suffix="ogg",loud=false,subtitle="",duration=0.73,subduration=5}, +} +self.PilotCall={ +N0={file="PILOT-N0",suffix="ogg",loud=false,subtitle="",duration=0.40}, +N1={file="PILOT-N1",suffix="ogg",loud=false,subtitle="",duration=0.25}, +N2={file="PILOT-N2",suffix="ogg",loud=false,subtitle="",duration=0.37}, +N3={file="PILOT-N3",suffix="ogg",loud=false,subtitle="",duration=0.37}, +N4={file="PILOT-N4",suffix="ogg",loud=false,subtitle="",duration=0.39}, +N5={file="PILOT-N5",suffix="ogg",loud=false,subtitle="",duration=0.39}, +N6={file="PILOT-N6",suffix="ogg",loud=false,subtitle="",duration=0.40}, +N7={file="PILOT-N7",suffix="ogg",loud=false,subtitle="",duration=0.40}, +N8={file="PILOT-N8",suffix="ogg",loud=false,subtitle="",duration=0.37}, +N9={file="PILOT-N9",suffix="ogg",loud=false,subtitle="",duration=0.40}, +POINT={file="PILOT-Point",suffix="ogg",loud=false,subtitle="",duration=0.33}, +SKYHAWK={file="PILOT-Skyhawk",suffix="ogg",loud=false,subtitle="",duration=0.95,subduration=5}, +HARRIER={file="PILOT-Harrier",suffix="ogg",loud=false,subtitle="",duration=0.58,subduration=5}, +HAWKEYE={file="PILOT-Hawkeye",suffix="ogg",loud=false,subtitle="",duration=0.63,subduration=5}, +TOMCAT={file="PILOT-Tomcat",suffix="ogg",loud=false,subtitle="",duration=0.66,subduration=5}, +HORNET={file="PILOT-Hornet",suffix="ogg",loud=false,subtitle="",duration=0.56,subduration=5}, +VIKING={file="PILOT-Viking",suffix="ogg",loud=false,subtitle="",duration=0.61,subduration=5}, +GREYHOUND={file="PILOT-Greyhound",suffix="ogg",loud=false,subtitle="",duration=0.61,subduration=5}, +BALL={file="PILOT-Ball",suffix="ogg",loud=false,subtitle="",duration=0.50,subduration=5}, +BINGOFUEL={file="PILOT-BingoFuel",suffix="ogg",loud=false,subtitle="",duration=0.80}, +GASATDIVERT={file="PILOT-GasAtDivert",suffix="ogg",loud=false,subtitle="",duration=1.80}, +GASATTANKER={file="PILOT-GasAtTanker",suffix="ogg",loud=false,subtitle="",duration=1.95}, +} +self.MarshalCall={ +AFFIRMATIVE={file="MARSHAL-Affirmative",suffix="ogg",loud=false,subtitle="",duration=0.90}, +ALTIMETER={file="MARSHAL-Altimeter",suffix="ogg",loud=false,subtitle="",duration=0.85}, +BRC={file="MARSHAL-BRC",suffix="ogg",loud=false,subtitle="",duration=0.80}, +CARRIERTURNTOHEADING={file="MARSHAL-CarrierTurnToHeading",suffix="ogg",loud=false,subtitle="",duration=2.48,subduration=5}, +CASE={file="MARSHAL-Case",suffix="ogg",loud=false,subtitle="",duration=0.40}, +CHARLIETIME={file="MARSHAL-CharlieTime",suffix="ogg",loud=false,subtitle="",duration=0.90}, +CLEAREDFORRECOVERY={file="MARSHAL-ClearedForRecovery",suffix="ogg",loud=false,subtitle="",duration=1.25}, +DECKCLOSED={file="MARSHAL-DeckClosed",suffix="ogg",loud=false,subtitle="",duration=1.10,subduration=5}, +DEGREES={file="MARSHAL-Degrees",suffix="ogg",loud=false,subtitle="",duration=0.60}, +EXPECTED={file="MARSHAL-Expected",suffix="ogg",loud=false,subtitle="",duration=0.55}, +FLYNEEDLES={file="MARSHAL-FlyYourNeedles",suffix="ogg",loud=false,subtitle="Fly your needles",duration=0.9,subduration=5}, +HOLDATANGELS={file="MARSHAL-HoldAtAngels",suffix="ogg",loud=false,subtitle="",duration=1.10}, +HOURS={file="MARSHAL-Hours",suffix="ogg",loud=false,subtitle="",duration=0.60,subduration=5}, +MARSHALRADIAL={file="MARSHAL-MarshalRadial",suffix="ogg",loud=false,subtitle="",duration=1.10}, +N0={file="MARSHAL-N0",suffix="ogg",loud=false,subtitle="",duration=0.40}, +N1={file="MARSHAL-N1",suffix="ogg",loud=false,subtitle="",duration=0.25}, +N2={file="MARSHAL-N2",suffix="ogg",loud=false,subtitle="",duration=0.37}, +N3={file="MARSHAL-N3",suffix="ogg",loud=false,subtitle="",duration=0.37}, +N4={file="MARSHAL-N4",suffix="ogg",loud=false,subtitle="",duration=0.39}, +N5={file="MARSHAL-N5",suffix="ogg",loud=false,subtitle="",duration=0.39}, +N6={file="MARSHAL-N6",suffix="ogg",loud=false,subtitle="",duration=0.40}, +N7={file="MARSHAL-N7",suffix="ogg",loud=false,subtitle="",duration=0.40}, +N8={file="MARSHAL-N8",suffix="ogg",loud=false,subtitle="",duration=0.37}, +N9={file="MARSHAL-N9",suffix="ogg",loud=false,subtitle="",duration=0.40}, +NEGATIVE={file="MARSHAL-Negative",suffix="ogg",loud=false,subtitle="",duration=0.80,subduration=5}, +NEWFB={file="MARSHAL-NewFB",suffix="ogg",loud=false,subtitle="",duration=1.35}, +OPS={file="MARSHAL-Ops",suffix="ogg",loud=false,subtitle="",duration=0.48}, +POINT={file="MARSHAL-Point",suffix="ogg",loud=false,subtitle="",duration=0.33}, +RADIOCHECK={file="MARSHAL-RadioCheck",suffix="ogg",loud=false,subtitle="Radio check",duration=1.20,subduration=5}, +RECOVERY={file="MARSHAL-Recovery",suffix="ogg",loud=false,subtitle="",duration=0.70,subduration=5}, +RECOVERYOPSSTOPPED={file="MARSHAL-RecoveryOpsStopped",suffix="ogg",loud=false,subtitle="",duration=1.65,subduration=5}, +RECOVERYPAUSEDNOTICE={file="MARSHAL-RecoveryPausedNotice",suffix="ogg",loud=false,subtitle="aircraft recovery paused until further notice",duration=2.90,subduration=5}, +RECOVERYPAUSEDRESUMED={file="MARSHAL-RecoveryPausedResumed",suffix="ogg",loud=false,subtitle="",duration=3.40,subduration=5}, +REPORTSEEME={file="MARSHAL-ReportSeeMe",suffix="ogg",loud=false,subtitle="",duration=0.95}, +RESUMERECOVERY={file="MARSHAL-ResumeRecovery",suffix="ogg",loud=false,subtitle="resuming aircraft recovery",duration=1.75,subduraction=5}, +ROGER={file="MARSHAL-Roger",suffix="ogg",loud=false,subtitle="",duration=0.53,subduration=5}, +SAYNEEDLES={file="MARSHAL-SayNeedles",suffix="ogg",loud=false,subtitle="Say needles",duration=0.90,subduration=5}, +STACKFULL={file="MARSHAL-StackFull",suffix="ogg",loud=false,subtitle="Marshal Stack is currently full. Hold outside 10 NM zone and wait for further instructions",duration=6.35,subduration=10}, +STARTINGRECOVERY={file="MARSHAL-StartingRecovery",suffix="ogg",loud=false,subtitle="",duration=2.65,subduration=5}, +CLICK={file="AIRBOSS-RadioClick",suffix="ogg",loud=false,subtitle="",duration=0.35}, +NOISE={file="AIRBOSS-Noise",suffix="ogg",loud=false,subtitle="",duration=3.6}, +} +self:SetVoiceOversLSOByRaynor() +self:SetVoiceOversMarshalByRaynor() +end +function AIRBOSS:SetVoiceOver(radiocall,duration,subtitle,subduration,filename,suffix) +radiocall.duration=duration +radiocall.subtitle=subtitle or radiocall.subtitle +radiocall.file=filename +radiocall.suffix=suffix or".ogg" +end +function AIRBOSS:_GetAircraftAoA(playerData) +local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF +or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER +local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C +local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC +local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW +local aoa={} +if hornet then +aoa.SLOW=9.8 +aoa.Slow=9.3 +aoa.OnSpeedMax=8.8 +aoa.OnSpeed=8.1 +aoa.OnSpeedMin=7.4 +aoa.Fast=6.9 +aoa.FAST=6.3 +elseif tomcat then +aoa.SLOW=self:_AoAUnit2Deg(playerData,17.5) +aoa.Slow=self:_AoAUnit2Deg(playerData,16.5) +aoa.OnSpeedMax=self:_AoAUnit2Deg(playerData,16.0) +aoa.OnSpeed=self:_AoAUnit2Deg(playerData,15.0) +aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) +aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) +aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) +elseif goshawk then +aoa.SLOW=8.00 +aoa.Slow=7.75 +aoa.OnSpeedMax=7.25 +aoa.OnSpeed=7.00 +aoa.OnSpeedMin=6.75 +aoa.Fast=6.25 +aoa.FAST=6.00 +elseif skyhawk then +aoa.SLOW=10.50 +aoa.Slow=9.50 +aoa.OnSpeedMax=9.25 +aoa.OnSpeed=8.75 +aoa.OnSpeedMin=8.25 +aoa.Fast=8.00 +aoa.FAST=7.00 +elseif harrier then +aoa.SLOW=16.0 +aoa.Slow=13.5 +aoa.OnSpeedMax=12.5 +aoa.OnSpeed=10.0 +aoa.OnSpeedMin=9.5 +aoa.Fast=8.0 +aoa.FAST=7.5 +elseif corsair then +aoa.SLOW=16.0 +aoa.Slow=13.5 +aoa.OnSpeedMax=12.5 +aoa.OnSpeed=10.0 +aoa.OnSpeedMin=9.5 +aoa.Fast=8.0 +aoa.FAST=7.5 +end +return aoa +end +function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits) +local degrees=aoaunits +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +degrees=-10+50/30*aoaunits +degrees=0.918*aoaunits-3.411 +elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then +degrees=0.5*aoaunits +end +return degrees +end +function AIRBOSS:_AoADeg2Units(playerData,degrees) +local aoaunits=degrees +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +aoaunits=(degrees+10)*30/50 +aoaunits=1.089*degrees+3.715 +elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then +aoaunits=2*degrees +end +return aoaunits +end +function AIRBOSS:_GetAircraftParameters(playerData,step) +step=step or playerData.step +local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF +or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER +local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B +local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C +local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW +local alt +local aoa +local dist +local speed +local aoaac=self:_GetAircraftAoA(playerData) +if step==AIRBOSS.PatternStep.PLATFORM then +alt=UTILS.FeetToMeters(5000) +speed=UTILS.KnotsToMps(250) +elseif step==AIRBOSS.PatternStep.ARCIN then +if tomcat then +speed=UTILS.KnotsToMps(150) +else +speed=UTILS.KnotsToMps(250) +end +elseif step==AIRBOSS.PatternStep.ARCOUT then +if tomcat then +speed=UTILS.KnotsToMps(150) +else +speed=UTILS.KnotsToMps(250) +end +elseif step==AIRBOSS.PatternStep.DIRTYUP then +alt=UTILS.FeetToMeters(1200) +elseif step==AIRBOSS.PatternStep.BULLSEYE then +alt=UTILS.FeetToMeters(1200) +dist=-UTILS.NMToMeters(3) +aoa=aoaac.OnSpeed +elseif step==AIRBOSS.PatternStep.INITIAL then +if hornet or tomcat or harrier then +alt=UTILS.FeetToMeters(800) +speed=UTILS.KnotsToMps(350) +elseif skyhawk then +alt=UTILS.FeetToMeters(600) +speed=UTILS.KnotsToMps(250) +elseif goshawk then +alt=UTILS.FeetToMeters(800) +speed=UTILS.KnotsToMps(300) +elseif corsair then +alt=UTILS.FeetToMeters(300) +speed=UTILS.KnotsToMps(120) +end +elseif step==AIRBOSS.PatternStep.BREAKENTRY then +if hornet or tomcat or harrier then +alt=UTILS.FeetToMeters(800) +speed=UTILS.KnotsToMps(350) +elseif skyhawk then +alt=UTILS.FeetToMeters(600) +speed=UTILS.KnotsToMps(250) +elseif goshawk then +alt=UTILS.FeetToMeters(800) +speed=UTILS.KnotsToMps(300) +elseif corsair then +alt=UTILS.FeetToMeters(200) +speed=UTILS.KnotsToMps(110) +end +elseif step==AIRBOSS.PatternStep.EARLYBREAK then +if hornet or tomcat or harrier or goshawk then +alt=UTILS.FeetToMeters(800) +elseif skyhawk then +alt=UTILS.FeetToMeters(600) +elseif corsair then +alt=UTILS.FeetToMeters(200) +speed=UTILS.KnotsToMps(100) +end +elseif step==AIRBOSS.PatternStep.LATEBREAK then +if hornet or tomcat or harrier or goshawk then +alt=UTILS.FeetToMeters(800) +elseif skyhawk then +alt=UTILS.FeetToMeters(600) +elseif corsair then +alt=UTILS.FeetToMeters(150) +speed=UTILS.KnotsToMps(100) +end +elseif step==AIRBOSS.PatternStep.ABEAM then +if hornet or tomcat or harrier or goshawk then +alt=UTILS.FeetToMeters(600) +elseif skyhawk then +alt=UTILS.FeetToMeters(500) +elseif corsair then +alt=UTILS.FeetToMeters(150) +speed=UTILS.KnotsToMps(90) +end +aoa=aoaac.OnSpeed +if goshawk then +dist=UTILS.NMToMeters(0.9) +elseif harrier then +dist=UTILS.NMToMeters(0.9) +else +dist=UTILS.NMToMeters(1.1) +end +elseif step==AIRBOSS.PatternStep.NINETY then +if hornet or tomcat then +alt=UTILS.FeetToMeters(500) +elseif goshawk then +alt=UTILS.FeetToMeters(450) +elseif skyhawk then +alt=UTILS.FeetToMeters(500) +elseif harrier then +alt=UTILS.FeetToMeters(425) +elseif corsair then +alt=UTILS.FeetToMeters(90) +speed=UTILS.KnotsToMps(90) +end +aoa=aoaac.OnSpeed +elseif step==AIRBOSS.PatternStep.WAKE then +if hornet or goshawk then +alt=UTILS.FeetToMeters(370) +elseif tomcat then +alt=UTILS.FeetToMeters(430) +elseif skyhawk then +alt=UTILS.FeetToMeters(370) +elseif corsair then +alt=UTILS.FeetToMeters(80) +end +aoa=aoaac.OnSpeed +elseif step==AIRBOSS.PatternStep.FINAL then +if hornet or goshawk then +alt=UTILS.FeetToMeters(300) +elseif tomcat then +alt=UTILS.FeetToMeters(360) +elseif skyhawk then +alt=UTILS.FeetToMeters(300) +elseif harrier then +alt=UTILS.FeetToMeters(312) +elseif corsair then +alt=UTILS.FeetToMeters(80) +end +aoa=aoaac.OnSpeed +end +return alt,aoa,dist,speed +end +function AIRBOSS:_GetNextMarshalFight() +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +local stack=flight.flag +local Tmarshal=timer.getAbsTime()-flight.time +local TmarshalMin=2*60 +if flight.ai then +TmarshalMin=3*60 +end +if flight.holding~=nil and Tmarshal>=TmarshalMin then +if flight.case==1 and stack==1 or flight.case>1 then +if flight.ai then +return flight +else +if flight.step~=AIRBOSS.PatternStep.COMMENCING then +return flight +end +end +end +end +end +return nil +end +function AIRBOSS:_CheckQueue() +if self.Debug then +self:_PrintQueue(self.flights,"All Flights") +end +self:_PrintQueue(self.Qmarshal,"Marshal") +self:_PrintQueue(self.Qpattern,"Pattern") +self:_PrintQueue(self.Qwaiting,"Waiting") +self:_PrintQueue(self.Qspinning,"Spinning") +if self.case>1 then +for _,_flight in pairs(self.Qwaiting)do +local flight=_flight +local removed=self:_RemoveFlightFromQueue(self.Qwaiting,flight) +if removed then +local stack=self:_GetFreeStack(flight.ai) +self:T(self.lid..string.format("Moving flight %s onboard %s from Waiting queue to Case %d Marshal stack %d",flight.groupname,flight.onboard,self.case,stack)) +if flight.ai then +self:_MarshalAI(flight,stack) +else +self:_MarshalPlayer(flight,stack) +end +break +end +end +end +if not self:IsRecovering()then +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +if(flight.case==1 and self.case>1)or(flight.case>1 and self.case==1)then +local removed=self:_RemoveFlightFromQueue(self.Qmarshal,flight) +if removed then +local stack=self:_GetFreeStack(flight.ai) +self:T(self.lid..string.format("Moving flight %s onboard %s from Marshal Case %d ==> %d Marshal stack %d",flight.groupname,flight.onboard,flight.case,self.case,stack)) +if flight.ai then +self:_MarshalAI(flight,stack) +else +self:_MarshalPlayer(flight,stack) +end +break +elseif flight.case~=self.case then +flight.case=self.case +end +end +end +return +end +local _,npattern=self:_GetQueueInfo(self.Qpattern) +local _,nspinning=self:_GetQueueInfo(self.Qspinning) +local marshalflight=self:_GetNextMarshalFight() +if marshalflight and npattern0 then +local patternflight=self.Qpattern[#self.Qpattern] +pcase=patternflight.case +local npunits=self:_GetFlightUnits(patternflight,false) +Tpattern=timer.getAbsTime()-patternflight.time +self:T(self.lid..string.format("Pattern time of last group %s = %d seconds. # of units=%d.",patternflight.groupname,Tpattern,npunits)) +end +local TpatternMin +if pcase==1 then +TpatternMin=2*60*npunits +else +TpatternMin=2*60*npunits +end +if Tpattern>TpatternMin then +self:T(self.lid..string.format("Sending marshal flight %s to pattern.",marshalflight.groupname)) +self:_ClearForLanding(marshalflight) +end +end +end +function AIRBOSS:_ClearForLanding(flight) +if flight.ai then +self:_RemoveFlightFromMarshalQueue(flight,false) +self:_LandAI(flight) +self:_MarshalCallClearedForRecovery(flight.onboard,flight.case) +if self.xtVoiceOversAI then +local leader=flight.group:GetUnits()[1] +self:_CommencingCall(leader,flight.onboard) +end +else +if flight.step~=AIRBOSS.PatternStep.COMMENCING then +self:_MarshalCallClearedForRecovery(flight.onboard,flight.case) +flight.time=timer.getAbsTime() +end +self:_SetPlayerStep(flight,AIRBOSS.PatternStep.COMMENCING,3) +end +end +function AIRBOSS:_SetPlayerStep(playerData,step,delay) +if delay and delay>0 then +self:ScheduleOnce(delay,self._SetPlayerStep,self,playerData,step) +else +if playerData then +playerData.step=step +playerData.warning=nil +self:_StepHint(playerData) +end +end +end +function AIRBOSS:_ScanCarrierZone() +local coord=self:GetCoordinate() +local RCCZ=self.zoneCCA:GetRadius() +self:T(self.lid..string.format("Scanning Carrier Controlled Area. Radius=%.1f NM.",UTILS.MetersToNM(RCCZ))) +local _,_,_,unitscan=coord:ScanObjects(RCCZ,true,false,false) +local insideCCA={} +for _,_unit in pairs(unitscan)do +local unit=_unit +local airborne=unit:IsAir() +local inzone=unit:IsInZone(self.zoneCCA) +local friendly=self:GetCoalition()==unit:GetCoalition() +local carrierac=self:_IsCarrierAircraft(unit) +if airborne and inzone and friendly and carrierac then +local group=unit:GetGroup() +local groupname=group:GetName() +if insideCCA[groupname]==nil then +insideCCA[groupname]=group +end +end +end +for groupname,_group in pairs(insideCCA)do +local group=_group +local knownflight=self:_GetFlightFromGroupInQueue(group,self.flights) +local actype=group:GetTypeName() +if knownflight then +self:T2(self.lid..string.format("Known flight group %s of type %s in CCA.",groupname,actype)) +if knownflight.ai and knownflight.flag==-100 and self.handleai then +local putintomarshal=false +local flight=_DATABASE:GetOpsGroup(groupname) +if flight and flight:IsInbound()and flight.destbase:GetName()==self.carrier:GetName()then +if flight.ishelo then +else +putintomarshal=true +end +flight.airboss=self +end +if putintomarshal then +local stack=self:_GetFreeStack(knownflight.ai) +local respawn=self.respawnAI +if stack then +self:_MarshalAI(knownflight,stack,respawn) +else +if not self:_InQueue(self.Qwaiting,knownflight.group)then +self:_WaitAI(knownflight,respawn) +end +end +break +end +end +else +if not self:_IsHuman(group)then +self:_CreateFlightGroup(group) +end +end +end +local remove={} +for _,_flight in pairs(self.flights)do +local flight=_flight +if insideCCA[flight.groupname]==nil then +if flight.ai and not(self:_InQueue(self.Qmarshal,flight.group)or self:_InQueue(self.Qpattern,flight.group))then +table.insert(remove,flight) +end +end +end +for _,flight in pairs(remove)do +self:_RemoveFlightFromQueue(self.flights,flight) +end +end +function AIRBOSS:_WaitPlayer(playerData) +if playerData then +local nwaiting=#self.Qwaiting +self:_MarshalCallStackFull(playerData.onboard,nwaiting) +table.insert(self.Qwaiting,playerData) +playerData.time=timer.getAbsTime() +playerData.step=AIRBOSS.PatternStep.WAITING +playerData.warning=nil +for _,_flight in pairs(playerData.section)do +local flight=_flight +flight.step=AIRBOSS.PatternStep.WAITING +flight.time=timer.getAbsTime() +flight.warning=nil +end +end +end +function AIRBOSS:_MarshalPlayer(playerData,stack) +if playerData then +self:_AddMarshalGroup(playerData,stack) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.HOLDING) +playerData.holding=nil +for _,_flight in pairs(playerData.section)do +local flight=_flight +self:_SetPlayerStep(flight,AIRBOSS.PatternStep.HOLDING) +flight.holding=nil +flight.case=playerData.case +flight.flag=stack +self:Marshal(flight) +end +else +self:E(self.lid.."ERROR: Could not add player to Marshal stack! playerData=nil") +end +end +function AIRBOSS:_WaitAI(flight,respawn) +flight.flag=-99 +table.insert(self.Qwaiting,flight) +local group=flight.group +local groupname=flight.groupname +local speedOrbitMps=UTILS.KnotsToMps(274) +local speedOrbitKmh=UTILS.KnotsToKmph(274) +local speedTransit=UTILS.KnotsToKmph(370) +local cv=self:GetCoordinate() +local fc=group:GetCoordinate() +local hdg=self:GetHeading(false) +local hdgto=cv:HeadingTo(fc) +local angels=math.random(6,10) +local altitude=UTILS.FeetToMeters(angels*1000) +local p0=cv:Translate(UTILS.NMToMeters(11),hdgto):Translate(UTILS.NMToMeters(5),hdg):SetAltitude(altitude) +local wp={} +wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedTransit,{},"Current Position") +local taskorbit=group:TaskOrbit(p0,altitude,speedOrbitMps) +wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedOrbitKmh,{taskorbit},string.format("Waiting Orbit at Angels %d",angels)) +if self.Debug then +p0:MarkToAll(string.format("Waiting Orbit of flight %s at Angels %s",groupname,angels)) +end +if respawn then +local Template=group:GetTemplate() +Template.route.points=wp +group=group:Respawn(Template,true) +end +group:WayPointInitialize(wp) +group:Route(wp,1) +end +function AIRBOSS:_MarshalAI(flight,nstack,respawn) +self:F2({flight=flight,nstack=nstack,respawn=respawn}) +if flight==nil or flight.group==nil then +self:E(self.lid.."ERROR: flight or flight.group is nil.") +return +end +if flight.group:GetCoordinate()==nil then +self:E(self.lid.."ERROR: cannot get coordinate of flight group.") +return +end +if not self:_InQueue(self.Qmarshal,flight.group)then +if self.xtVoiceOversAI then +local leader=flight.group:GetUnits()[1] +self:_MarshallInboundCall(leader,flight.onboard) +end +self:_AddMarshalGroup(flight,nstack) +end +local case=flight.case +local ostack=flight.flag +local group=flight.group +local groupname=flight.groupname +flight.flag=nstack +local Carrier=self:GetCoordinate() +local hdg=self:GetHeading() +local speedOrbitMps=UTILS.KnotsToMps(274) +local speedOrbitKmh=UTILS.KnotsToKmph(274) +local speedTransit=UTILS.KnotsToKmph(370) +local altitude +local p0 +local p1 +local p2 +altitude,p1,p2=self:_GetMarshalAltitude(nstack,case) +local wp={} +if not flight.holding then +self:T(self.lid..string.format("Guiding AI flight %s to marshal stack %d-->%d.",groupname,ostack,nstack)) +wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedTransit,{},"Current Position") +local TaskArrivedHolding=flight.group:TaskFunction("AIRBOSS._ReachedHoldingZone",self,flight) +if case==1 then +local pE=Carrier:Translate(UTILS.NMToMeters(7),hdg-30):SetAltitude(altitude) +p0=Carrier:Translate(UTILS.NMToMeters(5),hdg-135):SetAltitude(altitude) +wp[#wp+1]=pE:WaypointAirTurningPoint(nil,speedTransit,{TaskArrivedHolding},"Entering Case I Marshal Pattern") +else +local radial=self:GetRadial(case,false,true) +p0=p2:Translate(UTILS.NMToMeters(5),radial+90,true):Translate(UTILS.NMToMeters(5),radial,true) +wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedTransit,{TaskArrivedHolding},"Entering Case II/III Marshal Pattern") +end +else +self:T(self.lid..string.format("Updating AI flight %s at marshal stack %d-->%d.",groupname,ostack,nstack)) +wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedOrbitKmh,{},"Current Position") +p0=group:GetCoordinate():Translate(UTILS.NMToMeters(0.2),group:GetHeading(),true) +end +local taskorbit=group:TaskOrbit(p1,altitude,speedOrbitMps,p2) +wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedOrbitKmh,{taskorbit},string.format("Marshal Orbit Stack %d",nstack)) +if self.Debug then +p0:MarkToAll("WP P0 "..groupname) +p1:MarkToAll("RT P1 "..groupname) +p2:MarkToAll("RT P2 "..groupname) +end +if respawn then +local Template=group:GetTemplate() +Template.route.points=wp +flight.group=group:Respawn(Template,true) +end +flight.group:WayPointInitialize(wp) +flight.group:Route(wp,1) +self:Marshal(flight) +end +function AIRBOSS:_RefuelAI(flight) +local wp={} +local CurrentSpeed=flight.group:GetVelocityKMH() +wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil,CurrentSpeed,{},"Current position") +local refuelac=false +local actype=flight.group:GetTypeName() +if actype==AIRBOSS.AircraftCarrier.AV8B or +actype==AIRBOSS.AircraftCarrier.F14A or +actype==AIRBOSS.AircraftCarrier.F14B or +actype==AIRBOSS.AircraftCarrier.F14A_AI or +actype==AIRBOSS.AircraftCarrier.HORNET or +actype==AIRBOSS.AircraftCarrier.RHINOE or +actype==AIRBOSS.AircraftCarrier.RHINOF or +actype==AIRBOSS.AircraftCarrier.GROWLER or +actype==AIRBOSS.AircraftCarrier.FA18C or +actype==AIRBOSS.AircraftCarrier.S3B or +actype==AIRBOSS.AircraftCarrier.S3BTANKER then +refuelac=true +end +local text="" +if self.tanker and refuelac then +local tankerpos=self.tanker.tanker:GetCoordinate() +local TaskRefuel=flight.group:TaskRefueling() +local TaskMarshal=flight.group:TaskFunction("AIRBOSS._TaskFunctionMarshalAI",self,flight) +wp[#wp+1]=tankerpos:WaypointAirTurningPoint(nil,CurrentSpeed,{TaskRefuel,TaskMarshal},"Refueling") +self:_MarshalCallGasAtTanker(flight.onboard) +else +local divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME,self:GetCoalition()) +if divertfield==nil then +divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME,0) +end +if divertfield then +local divertcoord=divertfield:GetCoordinate() +wp[#wp+1]=divertcoord:WaypointAirLanding(UTILS.KnotsToKmph(300),divertfield,{},"Divert Field") +self:_MarshalCallGasAtDivert(flight.onboard,divertfield:GetName()) +local Template=flight.group:GetTemplate() +Template.route.points=wp +flight.group=flight.group:Respawn(Template,true) +else +self:E(self.lid..string.format("WARNING: No recovery tanker or divert field available for group %s.",flight.groupname)) +flight.refueling=true +return +end +end +flight.group:WayPointInitialize(wp) +flight.group:Route(wp,1) +flight.refueling=true +end +function AIRBOSS:_LandAI(flight) +self:T(self.lid..string.format("Landing AI flight %s.",flight.groupname)) +local Speed=UTILS.KnotsToKmph(200) +if flight.actype==AIRBOSS.AircraftCarrier.HORNET +or flight.actype==AIRBOSS.AircraftCarrier.FA18C +or flight.actype==AIRBOSS.AircraftCarrier.RHINOE +or flight.actype==AIRBOSS.AircraftCarrier.RHINOF +or flight.actype==AIRBOSS.AircraftCarrier.GROWLER then +Speed=UTILS.KnotsToKmph(200) +elseif flight.actype==AIRBOSS.AircraftCarrier.E2D or flight.actype==AIRBOSS.AircraftCarrier.C2A then +Speed=UTILS.KnotsToKmph(150) +elseif flight.actype==AIRBOSS.AircraftCarrier.F14A_AI or flight.actype==AIRBOSS.AircraftCarrier.F14A or flight.actype==AIRBOSS.AircraftCarrier.F14B then +Speed=UTILS.KnotsToKmph(175) +elseif flight.actype==AIRBOSS.AircraftCarrier.S3B or flight.actype==AIRBOSS.AircraftCarrier.S3BTANKER then +Speed=UTILS.KnotsToKmph(140) +elseif flight.actype==AIRBOSS.AircraftCarrier.CORSAIR or flight.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW then +Speed=UTILS.KnotsToKmph(100) +end +local Carrier=self:GetCoordinate() +local hdg=self:GetHeading() +local wp={} +local CurrentSpeed=flight.group:GetVelocityKMH() +wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil,CurrentSpeed,{},"Current position") +local alt=UTILS.FeetToMeters(800) +wp[#wp+1]=Carrier:Translate(UTILS.NMToMeters(4),hdg-160):SetAltitude(alt):WaypointAirLanding(Speed,self.airbase,nil,"Landing") +flight.group:WayPointInitialize(wp) +flight.group:Route(wp,0) +end +function AIRBOSS:_GetMarshalAltitude(stack,case) +if stack<=0 then +return 0,nil,nil +end +case=case or self.case +local Carrier=self:GetCoordinate() +local angels0 +local Dist +local p1=nil +local p2=nil +local nstack=stack-1 +if case==1 then +angels0=2 +local hdg=self.carrier:GetHeading() +p1=Carrier +p2=Carrier:Translate(UTILS.NMToMeters(1.5),hdg) +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +p1=Carrier:Translate(UTILS.NMToMeters(1.0),hdg+90) +p2=p1:Translate(2.5,hdg) +end +else +angels0=6 +Dist=UTILS.NMToMeters(nstack+angels0+15) +local radial=self:GetRadial(case,false,true) +local l=UTILS.NMToMeters(10) +p1=Carrier:Translate(Dist+l,radial) +p2=Carrier:Translate(Dist,radial) +end +local altitude=UTILS.FeetToMeters((nstack+angels0)*1000) +p1:SetAltitude(altitude,true) +p2:SetAltitude(altitude,true) +return altitude,p1,p2 +end +function AIRBOSS:_GetCharlieTime(flightgroup) +local stack=flightgroup.flag +if stack<=0 then +return nil +end +local Tnow=timer.getAbsTime() +local Tcharlie=0 +local Trecovery=0 +if self.recoverywindow then +Trecovery=math.max(self.recoverywindow.START-Tnow,0) +else +Trecovery=7*60 +end +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +local mstack=flight.flag +local Tarrive=0 +local Tholding=3*60 +if stack>0 and mstack>0 and mstack<=stack then +if flight.holding==nil then +local holdingzone=self:_GetZoneHolding(flight.case,1):GetCoordinate() +local d0=holdingzone:Get2DDistance(flight.group:GetCoordinate()) +local v0=flight.group:GetVelocityMPS() +Tarrive=d0/v0 +self:T3(self.lid..string.format("Tarrive=%.1f seconds, Clock %s",Tarrive,UTILS.SecondsToClock(Tnow+Tarrive))) +else +if mstack==1 then +local tholding=timer.getAbsTime()-flight.time +Tholding=math.max(3*60-tholding,0) +end +end +local Tmin=math.max(Tarrive,Trecovery) +Tcharlie=math.max(Tmin,Tcharlie)+Tholding +end +end +Tcharlie=Tcharlie+Tnow +local text=string.format("Charlie time for flight %s (%s) %s",flightgroup.onboard,flightgroup.groupname,UTILS.SecondsToClock(Tcharlie)) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +return Tcharlie +end +function AIRBOSS:_AddMarshalGroup(flight,stack) +flight.flag=stack +flight.case=self.case +table.insert(self.Qmarshal,flight) +local P=UTILS.hPa2inHg(self:GetCoordinate():GetPressure()) +local alt=self:_GetMarshalAltitude(stack,flight.case) +local brc=self:GetBRC() +if self.recoverywindow and self.recoverywindow.WIND then +brc=self:GetBRCintoWind(self.recoverywindow.SPEED) +end +flight.Tcharlie=self:_GetCharlieTime(flight) +local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 +self:_MarshalCallArrived(flight.onboard,flight.case,brc,alt,Ccharlie,P) +if self.TACANon and(not flight.ai)and flight.difficulty==AIRBOSS.Difficulty.EASY then +local radial=self:GetRadial(flight.case,true,true,true) +if flight.case==1 then +radial=self:GetBRC() +end +local text=string.format("Select TACAN %03d°, channel %d%s (%s)",radial,self.TACANchannel,self.TACANmode,self.TACANmorse) +self:MessageToPlayer(flight,text,nil,"") +end +end +function AIRBOSS:_CollapseMarshalStack(flight,nopattern) +self:F2({flight=flight,nopattern=nopattern}) +local case=flight.case +local stack=flight.flag +if stack<=0 then +self:E(self.lid..string.format("ERROR: Flight %s is has stack value %d<0. Cannot collapse stack!",flight.groupname,stack)) +return +end +self.Tcollapse=timer.getTime() +for _,_flight in pairs(self.Qmarshal)do +local mflight=_flight +if(case==1 and mflight.case==1)then +local mstack=mflight.flag +if mstack>stack then +local newstack=self:_GetFreeStack(mflight.ai,mflight.case,true) +if newstack and newstack %d.",mflight.groupname,mflight.case,mstack,newstack)) +if mflight.ai then +self:_MarshalAI(mflight,newstack) +else +mflight.flag=newstack +local angels=self:_GetAngels(self:_GetMarshalAltitude(newstack,case)) +if mflight.difficulty~=AIRBOSS.Difficulty.HARD then +local text=string.format("descent to stack at Angels %d.",angels) +self:MessageToPlayer(mflight,text,"MARSHAL") +end +mflight.time=timer.getAbsTime() +for _,_sec in pairs(mflight.section)do +local sec=_sec +sec.flag=newstack +sec.time=timer.getAbsTime() +if sec.difficulty~=AIRBOSS.Difficulty.HARD then +local text=string.format("descent to stack at Angels %d.",angels) +self:MessageToPlayer(sec,text,"MARSHAL") +end +end +end +end +end +end +end +if nopattern then +self:T(self.lid..string.format("Flight %s is leaving stack but not going to pattern.",flight.groupname)) +else +local Tmarshal=UTILS.SecondsToClock(timer.getAbsTime()-flight.time) +self:T(self.lid..string.format("Flight %s is leaving marshal after %s and going pattern.",flight.groupname,Tmarshal)) +self:_AddFlightToPatternQueue(flight) +end +flight.flag=-1 +flight.time=timer.getAbsTime() +end +function AIRBOSS:_GetFreeStack(ai,case,empty) +case=case or self.case +if case==1 then +return self:_GetFreeStack_Old(ai,case,empty) +end +local nmaxstacks=100 +if case==1 then +nmaxstacks=self.Nmaxmarshal +end +local stack={} +for i=1,nmaxstacks do +stack[i]=self.NmaxStack +end +local nmax=1 +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +if flight.case==case then +local n=flight.flag +if n>nmax then +nmax=n +end +if n>0 then +if flight.ai or flight.case>1 then +stack[n]=0 +else +stack[n]=stack[n]-1 +end +else +self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.",flight.groupname,n)) +end +end +end +local nfree=nil +if stack[nmax]==0 then +if case==1 then +if nmax>=nmaxstacks then +nfree=nil +else +nfree=nmax+1 +end +else +nfree=nmax+1 +end +elseif stack[nmax]==self.NmaxStack then +self:E(self.lid..string.format("ERROR: Max occupied stack is empty. Should not happen! Nmax=%d, stack[nmax]=%d",nmax,stack[nmax])) +nfree=nmax +else +if ai or empty or case>1 then +nfree=nmax+1 +else +nfree=nmax +end +end +self:T(self.lid..string.format("Returning free stack %s",tostring(nfree))) +return nfree +end +function AIRBOSS:_GetFreeStack_Old(ai,case,empty) +case=case or self.case +local nmaxstacks=100 +if case==1 then +nmaxstacks=self.Nmaxmarshal +end +local stack={} +for i=1,nmaxstacks do +stack[i]=self.NmaxStack +end +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +if flight.case==case then +local n=flight.flag +if n>0 then +if flight.ai or flight.case>1 then +stack[n]=0 +else +stack[n]=stack[n]-1 +end +else +self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.",flight.groupname,n)) +end +end +end +local nfree=nil +for i=1,nmaxstacks do +self:T2(self.lid..string.format("FF Stack[%d]=%d",i,stack[i])) +if ai or empty or case>1 then +if stack[i]==self.NmaxStack then +nfree=i +return i +end +else +if stack[i]>0 then +nfree=i +return i +end +end +end +return nfree +end +function AIRBOSS:_GetFlightUnits(flight,onground) +local inair=true +if onground==true then +inair=false +end +local function countunits(_group,inair) +local group=_group +local units=group:GetUnits() +local n=0 +if units then +for _,_unit in pairs(units)do +local unit=_unit +if unit and unit:IsAlive()then +if inair then +if unit:InAir()then +self:T2(self.lid..string.format("Unit %s is in AIR",unit:GetName())) +n=n+1 +end +else +n=n+1 +end +end +end +end +return n +end +local nunits=countunits(flight.group,inair) +local nsection=0 +for _,sec in pairs(flight.section)do +local secflight=sec +nsection=nsection+countunits(secflight.group,inair) +end +return nunits+nsection,nunits,nsection +end +function AIRBOSS:_GetQueueInfo(queue,case) +local ngroup=0 +local Nunits=0 +for _,_flight in pairs(queue)do +local flight=_flight +if case then +if(flight.case==case)or(case==23 and(flight.case==2 or flight.case==3))then +local ntot,nunits,nsection=self:_GetFlightUnits(flight) +Nunits=Nunits+ntot +if ntot>0 then +ngroup=ngroup+1 +end +end +else +local ntot,nunits,nsection=self:_GetFlightUnits(flight) +Nunits=Nunits+ntot +if ntot>0 then +ngroup=ngroup+1 +end +end +end +return ngroup,Nunits +end +function AIRBOSS:_PrintQueue(queue,name) +local Nqueue,nqueue=self:_GetQueueInfo(queue) +local text=string.format("%s Queue N=%d (#%d), n=%d:",name,Nqueue,#queue,nqueue) +if#queue==0 then +text=text.." empty." +else +for i,_flight in pairs(queue)do +local flight=_flight +local clock=UTILS.SecondsToClock(timer.getAbsTime()-flight.time) +local case=flight.case +local stack=flight.flag +local fuel=flight.group:GetFuelMin()*100 +local ai=tostring(flight.ai) +local lead=flight.seclead +local Nsec=#flight.section +local actype=self:_GetACNickname(flight.actype) +local onboard=flight.onboard +local holding=tostring(flight.holding) +local _,nunits,nsec=self:_GetFlightUnits(flight,false) +text=text..string.format("\n[%d] %s*%d (%s): lead=%s (%d/%d), onboard=%s, flag=%d, case=%d, time=%s, fuel=%d, ai=%s, holding=%s",i,flight.groupname,nunits,actype,lead,nsec,Nsec,onboard,stack,case,clock,fuel,ai,holding) +if stack>0 then +local alt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack,case)) +text=text..string.format(" stackalt=%d ft",alt) +end +for j,_element in pairs(flight.elements)do +local element=_element +text=text..string.format("\n (%d) %s (%s): ai=%s, ballcall=%s, recovered=%s",j,element.onboard,element.unitname,tostring(element.ai),tostring(element.ballcall),tostring(element.recovered)) +end +end +end +self:T(self.lid..text) +end +function AIRBOSS:_CreateFlightGroup(group) +self:T(self.lid..string.format("Creating new flight for group %s of aircraft type %s.",group:GetName(),group:GetTypeName())) +local flight={} +if not self:_InQueue(self.flights,group)then +local groupname=group:GetName() +local human,playername=self:_IsHuman(group) +flight.group=group +flight.groupname=group:GetName() +flight.nunits=#group:GetUnits() +flight.time=timer.getAbsTime() +flight.dist0=group:GetCoordinate():Get2DDistance(self:GetCoordinate()) +flight.flag=-100 +flight.ai=not human +flight.actype=group:GetTypeName() +flight.onboardnumbers=self:_GetOnboardNumbers(group) +flight.seclead=flight.group:GetUnit(1):GetName() +flight.section={} +flight.ballcall=false +flight.refueling=false +flight.holding=nil +flight.name=flight.group:GetUnit(1):GetName() +flight.case=self.case +local text=string.format("Flight elements of group %s:",flight.groupname) +flight.elements={} +local units=group:GetUnits() +for i,_unit in pairs(units)do +local unit=_unit +local element={} +element.unit=unit +element.unitname=unit:GetName() +element.onboard=flight.onboardnumbers[element.unitname] +element.ballcall=false +element.ai=not self:_IsHumanUnit(unit) +element.recovered=nil +text=text..string.format("\n[%d] %s onboard #%s, AI=%s",i,element.unitname,tostring(element.onboard),tostring(element.ai)) +table.insert(flight.elements,element) +end +self:T(self.lid..text) +if flight.ai then +local onboard=flight.onboardnumbers[flight.seclead] +flight.onboard=onboard +else +flight.onboard=self:_GetOnboardNumberPlayer(group) +end +table.insert(self.flights,flight) +else +self:E(self.lid..string.format("ERROR: Flight group %s already exists in self.flights!",group:GetName())) +return nil +end +return flight +end +function AIRBOSS:_NewPlayer(unitname) +local playerunit,playername=self:_GetPlayerUnitAndName(unitname) +if playerunit and playername then +local group=playerunit:GetGroup() +local playerData +playerData=self:_CreateFlightGroup(group) +if playerData then +playerData.unit=playerunit +playerData.unitname=unitname +playerData.name=playername +playerData.callsign=playerData.unit:GetCallsign() +playerData.client=CLIENT:FindByName(unitname,nil,true) +playerData.seclead=playername +playerData.passes=0 +playerData.messages={} +playerData.lastdebrief=playerData.lastdebrief or{} +playerData.attitudemonitor=false +if playerData.trapon==nil then +playerData.trapon=self.trapsheet +end +playerData.difficulty=playerData.difficulty or self.defaultskill +if playerData.subtitles==nil then +playerData.subtitles=true +end +if playerData.showhints==nil then +if playerData.difficulty==AIRBOSS.Difficulty.HARD then +playerData.showhints=false +else +playerData.showhints=true +end +end +playerData.points={} +playerData=self:_InitPlayer(playerData) +self.players[playername]=playerData +self.playerscores[playername]=self.playerscores[playername]or{} +if self.welcome then +self:MessageToPlayer(playerData,string.format("Welcome, %s %s!",playerData.difficulty,playerData.name),string.format("AIRBOSS %s",self.alias),"",5) +end +end +return playerData +end +return nil +end +function AIRBOSS:_InitPlayer(playerData,step) +self:T(self.lid..string.format("Initializing player data for %s callsign %s.",playerData.name,playerData.callsign)) +playerData.step=step or AIRBOSS.PatternStep.UNDEFINED +playerData.groove={} +playerData.debrief={} +playerData.trapsheet={} +playerData.warning=nil +playerData.holding=nil +playerData.refueling=false +playerData.valid=false +playerData.lig=false +playerData.wop=false +playerData.waveoff=false +playerData.wofd=false +playerData.owo=false +playerData.boltered=false +playerData.hover=false +playerData.stable=false +playerData.landed=false +playerData.Tlso=timer.getTime() +playerData.Tgroove=nil +playerData.TIG0=0 +playerData.wire=nil +playerData.flag=-100 +playerData.debriefschedulerID=nil +if playerData.group:GetName():match("Groove")and playerData.passes==0 then +self:MessageToPlayer(playerData,"Group name contains \"Groove\". Happy groove testing.") +playerData.attitudemonitor=true +playerData.step=AIRBOSS.PatternStep.FINAL +self:_AddFlightToPatternQueue(playerData) +self.dTstatus=0.1 +end +return playerData +end +function AIRBOSS:_GetFlightFromGroupInQueue(group,queue) +if group then +local name=group:GetName() +for i,_flight in pairs(queue)do +local flight=_flight +if flight.groupname==name then +return flight,i +end +end +self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.",name)) +end +self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!")) +return nil,nil +end +function AIRBOSS:_GetFlightElement(unitname) +local unit=UNIT:FindByName(unitname) +if unit then +local flight=self:_GetFlightFromGroupInQueue(unit:GetGroup(),self.flights) +if flight then +for i,_element in pairs(flight.elements)do +local element=_element +if element.unit:GetName()==unitname then +return element,i,flight +end +end +self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.",unitname,flight.groupname)) +end +end +return nil,nil,nil +end +function AIRBOSS:_RemoveFlightElement(unitname) +local element,idx,flight=self:_GetFlightElement(unitname) +if idx then +table.remove(flight.elements,idx) +return true +else +self:T("WARNING: Flight element could not be removed from flight group. Index=nil!") +return nil +end +end +function AIRBOSS:_InQueue(queue,group) +local name=group:GetName() +for _,_flight in pairs(queue)do +local flight=_flight +if name==flight.groupname then +return true +end +end +return false +end +function AIRBOSS:_RemoveDeadFlightGroups() +for i=#self.flight,1,-1 do +local flight=self.flights[i] +if not flight.group:IsAlive()then +self:T(string.format("Removing dead flight group %s from ALL flights table.",flight.groupname)) +table.remove(self.flights,i) +end +end +for i=#self.Qmarshal,1,-1 do +local flight=self.Qmarshal[i] +if not flight.group:IsAlive()then +self:T(string.format("Removing dead flight group %s from Marshal Queue table.",flight.groupname)) +table.remove(self.Qmarshal,i) +end +end +for i=#self.Qpattern,1,-1 do +local flight=self.Qpattern[i] +if not flight.group:IsAlive()then +self:T(string.format("Removing dead flight group %s from Pattern Queue table.",flight.groupname)) +table.remove(self.Qpattern,i) +end +end +end +function AIRBOSS:_GetLeadFlight(flight) +local lead=flight +if flight.name~=flight.seclead then +lead=self.players[flight.seclead] +end +return lead +end +function AIRBOSS:_CheckSectionRecovered(flight) +if flight==nil then +return true +end +local lead=self:_GetLeadFlight(flight) +for _,_element in pairs(lead.elements)do +local element=_element +if not element.recovered then +return false +end +end +for _,_section in pairs(lead.section)do +local sectionmember=_section +for _,_element in pairs(sectionmember.elements)do +local element=_element +if not element.recovered then +return false +end +end +end +self:_RemoveFlightFromQueue(self.Qpattern,lead) +if self:_InQueue(self.Qmarshal,lead.group)then +self:E(self.lid..string.format("ERROR: lead flight group %s should not be in marshal queue",lead.groupname)) +self:_RemoveFlightFromMarshalQueue(lead,true) +end +if self:_InQueue(self.Qwaiting,lead.group)then +self:E(self.lid..string.format("ERROR: lead flight group %s should not be in pattern queue",lead.groupname)) +self:_RemoveFlightFromQueue(self.Qwaiting,lead) +end +return true +end +function AIRBOSS:_AddFlightToPatternQueue(flight) +table.insert(self.Qpattern,flight) +flight.flag=-1 +flight.time=timer.getAbsTime() +flight.recovered=false +for _,elem in pairs(flight.elements)do +elem.recoverd=false +end +for _,sec in pairs(flight.section)do +sec.flag=-1 +sec.time=timer.getAbsTime() +for _,elem in pairs(sec.elements)do +elem.recoverd=false +end +end +end +function AIRBOSS:_RecoveredElement(unit) +local element,idx,flight=self:_GetFlightElement(unit:GetName()) +if element then +element.recovered=true +end +return flight +end +function AIRBOSS:_RemoveFlightFromMarshalQueue(flight,nopattern) +local removed,idx=self:_RemoveFlightFromQueue(self.Qmarshal,flight) +if removed then +flight.holding=nil +self:_CollapseMarshalStack(flight,nopattern) +if flight.case==1 and#self.Qwaiting>0 then +local nextflight=self.Qwaiting[1] +local freestack=self:_GetFreeStack(nextflight.ai) +if nextflight.ai then +self:_MarshalAI(nextflight,freestack) +else +self:_MarshalPlayer(nextflight,freestack) +end +self:_RemoveFlightFromQueue(self.Qwaiting,nextflight) +end +end +return removed,idx +end +function AIRBOSS:_RemoveFlightFromQueue(queue,flight) +for i,_flight in pairs(queue)do +local qflight=_flight +if qflight.groupname==flight.groupname then +self:T(self.lid..string.format("Removing flight group %s from queue.",flight.groupname)) +table.remove(queue,i) +return true,i +end +end +return false,nil +end +function AIRBOSS:_RemoveUnitFromFlight(unit) +if unit and unit:IsInstanceOf("UNIT")then +local group=unit:GetGroup() +if group then +local flight=self:_GetFlightFromGroupInQueue(group,self.flights) +if flight then +local removed=self:_RemoveFlightElement(unit:GetName()) +if removed then +local _,nunits=self:_GetFlightUnits(flight,not flight.ai) +local nelements=#flight.elements +self:T(self.lid..string.format("Removed unit %s: nunits=%d, nelements=%d",unit:GetName(),nunits,nelements)) +if nunits==0 or nelements==0 then +self:_RemoveFlight(flight) +end +end +end +end +end +end +function AIRBOSS:_RemoveFlightFromSection(flight) +if flight.name~=flight.seclead then +local lead=self.players[flight.seclead] +if lead then +for i,sec in pairs(lead.section)do +local sectionmember=sec +if sectionmember.name==flight.name then +table.remove(lead.section,i) +break +end +end +end +end +end +function AIRBOSS:_UpdateFlightSection(flight) +if flight.seclead==flight.name then +if#flight.section>=1 then +local newlead=flight.section[1] +newlead.seclead=newlead.name +for i=2,#flight.section do +local member=flight.section[i] +table.insert(newlead.section,member) +member.seclead=newlead.name +end +end +flight.section={} +else +self:_RemoveFlightFromSection(flight) +end +end +function AIRBOSS:_RemoveFlight(flight,completely) +self:F(self.lid..string.format("Removing flight %s, ai=%s completely=%s.",tostring(flight.groupname),tostring(flight.ai),tostring(completely))) +self:_RemoveFlightFromMarshalQueue(flight,true) +self:_RemoveFlightFromQueue(self.Qpattern,flight) +self:_RemoveFlightFromQueue(self.Qwaiting,flight) +self:_RemoveFlightFromQueue(self.Qspinning,flight) +if flight.ai then +self:_RemoveFlightFromQueue(self.flights,flight) +else +local grades=self.playerscores[flight.name] +if grades and#grades>0 then +while#grades>0 and grades[#grades].finalscore==nil do +table.remove(grades,#grades) +end +end +if completely then +self:_UpdateFlightSection(flight) +self:_RemoveFlightFromQueue(self.flights,flight) +local playerdata=self.players[flight.name] +if playerdata then +self:T(self.lid..string.format("Removing player %s completely.",flight.name)) +self.players[flight.name]=nil +end +flight=nil +else +self:_SetPlayerStep(flight,AIRBOSS.PatternStep.UNDEFINED) +for _,sectionmember in pairs(flight.section)do +self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.UNDEFINED) +self:_RemoveFlightFromQueue(self.Qspinning,sectionmember) +end +self:_RemoveFlightFromSection(flight) +end +end +end +function AIRBOSS:_CheckPlayerStatus() +for _playerName,_playerData in pairs(self.players)do +local playerData=_playerData +if playerData then +local unit=playerData.unit +if unit and unit:IsAlive()then +if unit:IsInZone(self.zoneCCA)then +if playerData.step==AIRBOSS.PatternStep.WAKE then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<20 and math.abs(playerData.unit:GetRoll())>=10 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<10 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end +if playerData.attitudemonitor then +self:_AttitudeMonitor(playerData) +end +self:_CheckPlayerPatternDistance(playerData) +self:_CheckFoulDeck(playerData) +if playerData.step==AIRBOSS.PatternStep.UNDEFINED then +elseif playerData.step==AIRBOSS.PatternStep.REFUELING then +elseif playerData.step==AIRBOSS.PatternStep.SPINNING then +self:_Spinning(playerData) +elseif playerData.step==AIRBOSS.PatternStep.HOLDING then +self:_Holding(playerData) +elseif playerData.step==AIRBOSS.PatternStep.WAITING then +self:_Waiting(playerData) +elseif playerData.step==AIRBOSS.PatternStep.COMMENCING then +self:_Commencing(playerData,true) +elseif playerData.step==AIRBOSS.PatternStep.BOLTER then +self:_BolterPattern(playerData) +elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then +self:_Platform(playerData) +elseif playerData.step==AIRBOSS.PatternStep.ARCIN then +self:_ArcInTurn(playerData) +elseif playerData.step==AIRBOSS.PatternStep.ARCOUT then +self:_ArcOutTurn(playerData) +elseif playerData.step==AIRBOSS.PatternStep.DIRTYUP then +self:_DirtyUp(playerData) +elseif playerData.step==AIRBOSS.PatternStep.BULLSEYE then +self:_Bullseye(playerData) +elseif playerData.step==AIRBOSS.PatternStep.INITIAL then +self:_Initial(playerData) +elseif playerData.step==AIRBOSS.PatternStep.BREAKENTRY then +self:_BreakEntry(playerData) +elseif playerData.step==AIRBOSS.PatternStep.EARLYBREAK then +self:_Break(playerData,AIRBOSS.PatternStep.EARLYBREAK) +elseif playerData.step==AIRBOSS.PatternStep.LATEBREAK then +self:_Break(playerData,AIRBOSS.PatternStep.LATEBREAK) +elseif playerData.step==AIRBOSS.PatternStep.ABEAM then +self:_Abeam(playerData) +elseif playerData.step==AIRBOSS.PatternStep.NINETY then +self:_CheckForLongDownwind(playerData) +self:_Ninety(playerData) +elseif playerData.step==AIRBOSS.PatternStep.WAKE then +self:_Wake(playerData) +elseif playerData.step==AIRBOSS.PatternStep.EMERGENCY then +self:_Final(playerData,true) +elseif playerData.step==AIRBOSS.PatternStep.FINAL then +self:_Final(playerData) +elseif playerData.step==AIRBOSS.PatternStep.GROOVE_XX or +playerData.step==AIRBOSS.PatternStep.GROOVE_IM or +playerData.step==AIRBOSS.PatternStep.GROOVE_IC or +playerData.step==AIRBOSS.PatternStep.GROOVE_AR or +playerData.step==AIRBOSS.PatternStep.GROOVE_AL or +playerData.step==AIRBOSS.PatternStep.GROOVE_LC or +playerData.step==AIRBOSS.PatternStep.GROOVE_IW then +self:_Groove(playerData) +elseif playerData.step==AIRBOSS.PatternStep.DEBRIEF then +playerData.debriefschedulerID=self:ScheduleOnce(5,self._Debrief,self,playerData) +playerData.step=AIRBOSS.PatternStep.UNDEFINED +else +self:E(self.lid..string.format("ERROR: Unknown player step %s. Please report!",tostring(playerData.step))) +end +self:_CheckMissedStepOnEntry(playerData) +else +self:T2(self.lid.."WARNING: Player unit not inside the CCA!") +end +else +self:T(self.lid.."WARNING: Player unit is not alive!") +end +end +end +end +function AIRBOSS:_CheckMissedStepOnEntry(playerData) +local rightcase=playerData.case>1 +local rightqueue=self:_InQueue(self.Qpattern,playerData.group) +local rightflag=playerData.flag~=-42 +local step=playerData.step +local missedstep=step==AIRBOSS.PatternStep.PLATFORM or step==AIRBOSS.PatternStep.ARCIN or step==AIRBOSS.PatternStep.ARCOUT or step==AIRBOSS.PatternStep.DIRTYUP +if rightcase and rightqueue and rightflag then +local zone=nil +if playerData.case==2 and missedstep then +zone=self:_GetZoneInitial(playerData.case) +elseif playerData.case==3 and missedstep then +zone=self:_GetZoneBullseye(playerData.case) +end +if zone then +local inzone=playerData.unit:IsInZone(zone) +local relheading=self:_GetRelativeHeading(playerData.unit,false) +if inzone and math.abs(relheading)<60 then +local text=string.format("you missed an important step in the pattern!\nYour next step would have been %s.",playerData.step) +self:MessageToPlayer(playerData,text,"AIRBOSS",nil,5) +if playerData.case==2 then +playerData.step=AIRBOSS.PatternStep.INITIAL +elseif playerData.case==3 then +playerData.step=AIRBOSS.PatternStep.BULLSEYE +end +playerData.flag=-42 +end +end +end +end +function AIRBOSS:_SetTimeInGroove(playerData) +if playerData.TIG0 then +playerData.Tgroove=timer.getTime()-playerData.TIG0-1.5 +else +playerData.Tgroove=999 +end +end +function AIRBOSS:_GetTimeInGroove(playerData) +local Tgroove=999 +if playerData.TIG0 then +Tgroove=timer.getTime()-playerData.TIG0 +end +return Tgroove +end +function AIRBOSS:OnEventBirth(EventData) +self:F3({eventbirth=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event BIRTH!") +self:E(EventData) +return +end +if EventData.IniUnit==nil and(not EventData.IniObjectCategory==Object.Category.STATIC)then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!") +self:E(EventData) +return +end +if EventData.IniObjectCategory~=Object.Category.UNIT then return end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName)) +self:T(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName)) +self:T(self.lid.."BIRTH: player = "..tostring(_playername)) +if _unit and _playername then +local _uid=_unit:GetID() +local _group=_unit:GetGroup() +local _callsign=_unit:GetCallsign() +local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.",_playername,_callsign,_unitName,_group:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,5):ToAllIf(self.Debug) +local rightaircraft=self:_IsCarrierAircraft(_unit) +if rightaircraft==false then +local text=string.format("Player aircraft type %s not supported by AIRBOSS class.",_unit:GetTypeName()) +MESSAGE:New(text,30):ToAllIf(self.Debug) +self:T2(self.lid..text) +return +end +if self:GetCoalition()~=_unit:GetCoalition()then +local text=string.format("Player entered aircraft of other coalition.") +MESSAGE:New(text,30):ToAllIf(self.Debug) +self:T(self.lid..text) +return +end +self:_AddF10Commands(_unitName) +self:ScheduleOnce(1,self._NewPlayer,self,_unitName) +end +end +function AIRBOSS:OnEventRunwayTouch(EventData) +self:F3({eventland=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event LAND!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event LAND!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T(self.lid.."LAND: unit = "..tostring(EventData.IniUnitName)) +self:T(self.lid.."LAND: group = "..tostring(EventData.IniGroupName)) +self:T(self.lid.."LAND: player = "..tostring(_playername)) +local airbase=EventData.Place +if airbase==nil then +return +end +local airbasename=tostring(airbase:GetName()) +if airbasename==self.airbase:GetName()then +local stern=self:_GetSternCoord() +local zoneCarrier=self:_GetZoneCarrierBox() +if _unit and _playername then +local _uid=_unit:GetID() +local _group=_unit:GetGroup() +local _callsign=_unit:GetCallsign() +local text=string.format("Player %s, callsign %s unit %s (ID=%d) of group %s landed at airbase %s",_playername,_callsign,_unitName,_uid,_group:GetName(),airbasename) +self:T(self.lid..text) +MESSAGE:New(text,5,"DEBUG"):ToAllIf(self.Debug) +local playerData=self.players[_playername] +if playerData==nil then +self:E(self.lid..string.format("ERROR: playerData nil in landing event. unit=%s player=%s",tostring(_unitName),tostring(_playername))) +return +end +if _unit:IsInZone(zoneCarrier)then +if not playerData.valid then +local text=string.format("you missed at least one important step in the pattern!\nYour next step would have been %s.\nThis pass is INVALID.",playerData.step) +self:MessageToPlayer(playerData,text,"AIRBOSS",nil,30,true,5) +self:_RemoveFlightFromMarshalQueue(playerData,true) +self:_RemoveFlightFromQueue(self.Qpattern,playerData) +self:_RemoveFlightFromQueue(self.Qwaiting,playerData) +self:_RemoveFlightFromQueue(self.Qspinning,playerData) +self:_InitPlayer(playerData) +return +end +if playerData.landed then +self:E(self.lid..string.format("Player %s just landed a second time.",_playername)) +else +playerData.landed=true +playerData.attitudemonitor=false +local coord=playerData.unit:GetCoordinate() +local X,Z,rho,phi=self:_GetDistances(_unit) +local dist=coord:Get2DDistance(stern) +if self.Debug and false then +local lp=coord:MarkToAll("Landing coord.") +coord:SmokeGreen() +end +self:_SetTimeInGroove(playerData) +local text=string.format("Player %s AC type %s landed at dist=%.1f m. Tgroove=%.1f sec.",playerData.name,playerData.actype,dist,self:_GetTimeInGroove(playerData)) +text=text..string.format(" X=%.1f m, Z=%.1f m, rho=%.1f m.",X,Z,rho) +self:T(self.lid..text) +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +self:RadioTransmission(self.LSORadio,self.LSOCall.IDLE,false,1,nil,true) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF) +else +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.UNDEFINED) +self:ScheduleOnce(1,self._Trapped,self,playerData) +end +end +else +if playerData then +self:E(self.lid..string.format("Player %s did not land in carrier box zone. Maybe in the water near the carrier?",playerData.name)) +end +end +else +if self.carriertype~=AIRBOSS.CarrierType.INVINCIBLE or self.carriertype~=AIRBOSS.CarrierType.HERMES or self.carriertype~=AIRBOSS.CarrierType.TARAWA or self.carriertype~=AIRBOSS.CarrierType.AMERICA or self.carriertype~=AIRBOSS.CarrierType.JCARLOS or self.carriertype~=AIRBOSS.CarrierType.CANBERRA then +local coord=EventData.IniUnit:GetCoordinate() +local dist=coord:Get2DDistance(self:GetCoordinate()) +local wire=self:_GetWire(coord,0) +local _type=EventData.IniUnit:GetTypeName() +local text=string.format("AI unit %s of type %s landed at dist=%.1f m. Trapped wire=%d.",_unitName,_type,dist,wire) +self:T(self.lid..text) +end +local flight=self:_RecoveredElement(EventData.IniUnit) +self:_CheckSectionRecovered(flight) +end +end +end +function AIRBOSS:OnEventEngineShutdown(EventData) +self:F3({eventengineshutdown=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event ENGINESHUTDOWN!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event ENGINESHUTDOWN!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."ENGINESHUTDOWN: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."ENGINESHUTDOWN: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."ENGINESHUTDOWN: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s shut down its engines!",_playername)) +else +self:T(self.lid..string.format("AI unit %s shut down its engines!",_unitName)) +local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) +if flight and flight.ai then +local recovered=self:_CheckSectionRecovered(flight) +if recovered then +self:T(self.lid..string.format("AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.",tostring(EventData.IniGroupName))) +self:_RemoveFlight(flight) +local istanker=self.tanker and self.tanker.tanker:GetName()==EventData.IniGroupName +local isawacs=self.awacs and self.awacs.tanker:GetName()==EventData.IniGroupName +if self.despawnshutdown and not(istanker or isawacs)then +EventData.IniGroup:Destroy(nil,5) +end +end +end +end +end +function AIRBOSS:OnEventTakeoff(EventData) +self:F3({eventtakeoff=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event TAKEOFF!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event TAKEOFF!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."TAKEOFF: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."TAKEOFF: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."TAKEOFF: player = "..tostring(_playername)) +local airbase=EventData.Place +local airbasename="unknown" +if airbase then +airbasename=airbase:GetName() +end +if airbasename==self.airbase:GetName()then +if _unit and _playername then +self:T(self.lid..string.format("Player %s took off at %s!",_playername,airbasename)) +else +self:T2(self.lid..string.format("AI unit %s took off at %s!",_unitName,airbasename)) +local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) +if flight then +for _,elem in pairs(flight.elements)do +local element=elem +element.ballcall=false +element.recovered=nil +end +end +end +end +end +function AIRBOSS:OnEventCrash(EventData) +self:F3({eventcrash=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event CRASH!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event CRASH!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."CRASH: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."CRASH: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."CARSH: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s crashed!",_playername)) +local flight=self.players[_playername] +if flight then +self:_RemoveFlight(flight,true) +end +else +self:T2(self.lid..string.format("AI unit %s crashed!",EventData.IniUnitName)) +self:_RemoveUnitFromFlight(EventData.IniUnit) +end +end +function AIRBOSS:OnEventEjection(EventData) +self:F3({eventland=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event EJECTION!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event EJECTION!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."EJECT: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s ejected!",_playername)) +local flight=self.players[_playername] +if flight then +self:_RemoveFlight(flight,true) +end +else +self:T(self.lid..string.format("AI unit %s ejected!",EventData.IniUnitName)) +self:_RemoveUnitFromFlight(EventData.IniUnit) +local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) +self:_CheckSectionRecovered(flight) +end +end +function AIRBOSS:OnEventRemoveUnit(EventData) +self:F3({eventland=EventData}) +if EventData==nil then +self:T(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!") +self:T(EventData) +return +end +if EventData.IniUnit==nil then +self:T(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!") +self:T(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."EJECT: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s removed!",_playername)) +local flight=self.players[_playername] +if flight then +self:_RemoveFlight(flight,true) +end +else +self:T(self.lid..string.format("AI unit %s removed!",EventData.IniUnitName)) +self:_RemoveUnitFromFlight(EventData.IniUnit) +local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) +self:_CheckSectionRecovered(flight) +end +end +function AIRBOSS:_PlayerLeft(EventData) +self:F3({eventleave=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event PLAYERLEFTUNIT!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event PLAYERLEFTUNIT!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."PLAYERLEAVEUNIT: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."PLAYERLEAVEUNIT: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."PLAYERLEAVEUNIT: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s left unit %s!",_playername,_unitName)) +local flight=self.players[_playername] +if flight then +self:_RemoveFlight(flight,true) +end +end +end +function AIRBOSS:OnEventMissionEnd(EventData) +self:T3(self.lid.."Mission Ended") +end +function AIRBOSS:_Spinning(playerData) +local SpinIt={} +SpinIt.name="Spinning" +SpinIt.Xmin=-UTILS.NMToMeters(6) +SpinIt.Xmax=UTILS.NMToMeters(5) +SpinIt.Zmin=-UTILS.NMToMeters(6) +SpinIt.Zmax=UTILS.NMToMeters(2) +SpinIt.LimitXmin=-100 +SpinIt.LimitXmax=nil +SpinIt.LimitZmin=-UTILS.NMToMeters(1) +SpinIt.LimitZmax=nil +local X,Z,rho,phi=self:_GetDistances(playerData.unit) +if self:_CheckLimits(X,Z,SpinIt)then +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.INITIAL) +self:_RemoveFlightFromQueue(self.Qspinning,playerData) +end +end +function AIRBOSS:_Waiting(playerData) +local radius=UTILS.NMToMeters(10) +local zone=ZONE_RADIUS:New("Carrier 10 NM Zone",self.carrier:GetVec2(),radius) +local inzone=playerData.unit:IsInZone(zone) +local Twaiting=timer.getAbsTime()-playerData.time +if inzone and Twaiting>3*60 and not playerData.warning then +local text=string.format("You are supposed to wait outside the 10 NM zone.") +self:MessageToPlayer(playerData,text,"AIRBOSS") +playerData.warning=true +end +if inzone==false and playerData.warning==true then +playerData.warning=nil +end +end +function AIRBOSS:_Holding(playerData) +local unit=playerData.unit +local stack=playerData.flag +if stack<=0 then +local text=string.format("ERROR: player %s in step %s is holding but has stack=%s (<=0)",playerData.name,playerData.step,tostring(stack)) +self:E(self.lid..text) +end +local patternalt=self:_GetMarshalAltitude(stack,playerData.case) +local playeralt=unit:GetAltitude() +local zoneHolding=self:_GetZoneHolding(playerData.case,stack) +if zoneHolding==nil then +self:E(self.lid.."ERROR: zoneHolding is nil!") +self:E({playerData=playerData}) +return +end +local inholdingzone=unit:IsInZone(zoneHolding) +local altdiff=playeralt-patternalt +local altgood=UTILS.FeetToMeters(500) +if playerData.difficulty==AIRBOSS.Difficulty.HARD then +altgood=UTILS.FeetToMeters(200) +elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then +altgood=UTILS.FeetToMeters(350) +elseif playerData.difficulty==AIRBOSS.Difficulty.EASY then +altgood=UTILS.FeetToMeters(500) +end +local altback=altgood*0.5 +local justcollapsed=false +if self.Tcollapse then +local dT=timer.getTime()-self.Tcollapse +if dT<=90 then +justcollapsed=true +end +end +local goodalt=math.abs(altdiff)altgood then +if not playerData.warning then +text=text..string.format("You left your assigned altitude. Descent to angels %d.",angels) +playerData.warning=true +end +elseif altdiff<-altgood then +if not playerData.warning then +text=text..string.format("You left your assigned altitude. Climb to angels %d.",angels) +playerData.warning=true +end +end +end +if playerData.warning and math.abs(altdiff)<=altback then +text=text..string.format("Altitude is looking good again.") +playerData.warning=nil +end +elseif playerData.holding==false then +if inholdingzone then +text=text..string.format("You are back in the holding zone. Now stay there!") +playerData.holding=true +else +self:T3("Player still outside the holding zone. What are you doing man?!") +end +elseif playerData.holding==nil then +if inholdingzone then +playerData.holding=true +text=text..string.format("You arrived at the holding zone.") +if goodalt then +text=text..string.format(" Altitude is good.") +else +if altdiff<0 then +text=text..string.format(" But you're too low.") +else +text=text..string.format(" But you're too high.") +end +text=text..string.format("\nCurrently assigned altitude is %d ft.",UTILS.MetersToFeet(patternalt)) +playerData.warning=true +end +else +self:T3("Waiting for player to arrive in the holding zone.") +end +end +if playerData.showhints then +self:MessageToPlayer(playerData,text,"MARSHAL") +end +end +function AIRBOSS:_Commencing(playerData,zonecheck) +if zonecheck then +local zoneCommence=self:_GetZoneCommence(playerData.case,playerData.flag) +local inzone=playerData.unit:IsInZone(zoneCommence) +if not inzone then +if timer.getAbsTime()-playerData.time>180 then +self:_MarshalCallClearedForRecovery(playerData.onboard,playerData.case) +playerData.time=timer.getAbsTime() +end +return +end +end +self:_RemoveFlightFromMarshalQueue(playerData) +self:_InitPlayer(playerData) +if playerData.difficulty~=AIRBOSS.Difficulty.HARD then +local text="" +if playerData.case==1 then +text=text.."Proceed to initial." +else +text=text.."Descent to platform." +if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then +text=text.." VSI 4000 ft/min until you reach 5000 ft." +end +end +self:MessageToPlayer(playerData,text,"MARSHAL") +end +local nextstep +if playerData.case==1 then +nextstep=AIRBOSS.PatternStep.INITIAL +else +nextstep=AIRBOSS.PatternStep.PLATFORM +end +self:_SetPlayerStep(playerData,nextstep) +for i,_flight in pairs(playerData.section)do +local flight=_flight +self:_Commencing(flight,false) +end +end +function AIRBOSS:_Initial(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZoneInitial(playerData.case)) +local relheading=self:_GetRelativeHeading(playerData.unit,false) +local altitude=playerData.unit:GetAltitude() +if inzone and math.abs(relheading)<60 and altitude<=self.initialmaxalt then +if playerData.showhints then +local hint=string.format("Initial") +if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +hint=hint.." - Hook down, SAS on, Wing Sweep 68°!" +else +hint=hint.." - Hook down!" +end +end +self:MessageToPlayer(playerData,hint,"MARSHAL") +end +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.BREAKENTRY) +return true +end +return false +end +function AIRBOSS:_CheckCorridor(playerData) +local validzone=self:_GetZoneCorridor(playerData.case) +local invalid=playerData.unit:IsNotInZone(validzone) +if invalid and(not playerData.warning)then +self:MessageToPlayer(playerData,"you left the approach corridor!","AIRBOSS") +playerData.warning=true +end +if(not invalid)and playerData.warning then +self:MessageToPlayer(playerData,"you're back in the approach corridor.","AIRBOSS") +playerData.warning=false +end +end +function AIRBOSS:_Platform(playerData) +self:_CheckCorridor(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZonePlatform(playerData.case)) +if inzone then +self:_PlayerHint(playerData) +local nextstep +if math.abs(self.holdingoffset)>0 and playerData.case>1 then +nextstep=AIRBOSS.PatternStep.ARCIN +else +if playerData.case==2 then +nextstep=AIRBOSS.PatternStep.INITIAL +elseif playerData.case==3 then +nextstep=AIRBOSS.PatternStep.DIRTYUP +end +end +self:_SetPlayerStep(playerData,nextstep) +end +end +function AIRBOSS:_ArcInTurn(playerData) +self:_CheckCorridor(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZoneArcIn(playerData.case)) +if inzone then +self:_PlayerHint(playerData) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.ARCOUT) +end +end +function AIRBOSS:_ArcOutTurn(playerData) +self:_CheckCorridor(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZoneArcOut(playerData.case)) +if inzone then +self:_PlayerHint(playerData) +local nextstep +if playerData.case==3 then +nextstep=AIRBOSS.PatternStep.DIRTYUP +else +nextstep=AIRBOSS.PatternStep.INITIAL +end +self:_SetPlayerStep(playerData,nextstep) +end +end +function AIRBOSS:_DirtyUp(playerData) +self:_CheckCorridor(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case)) +if inzone then +playerData.Tgroove=timer.getTime()-playerData.TIG0-1.5 +playerData.wrappedUpAtWakeLittle=false +playerData.wrappedUpAtWakeFull=false +playerData.wrappedUpAtWakeUnderline=false +playerData.wrappedUpAtStartLittle=false +playerData.wrappedUpAtStartFull=false +playerData.wrappedUpAtStartUnderline=false +playerData.AAatWakeLittle=false +playerData.AAatWakeFull=false +playerData.AAatWakeUnderline=false +playerData.AFU=false +if playerData.actype==AIRBOSS.AircraftCarrier.HORNET +or playerData.actype==AIRBOSS.AircraftCarrier.F14A +or playerData.actype==AIRBOSS.AircraftCarrier.F14B +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF +or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER +then +local callsay=self:_NewRadioCall(self.MarshalCall.SAYNEEDLES,nil,nil,5,playerData.onboard) +local callfly=self:_NewRadioCall(self.MarshalCall.FLYNEEDLES,nil,nil,5,playerData.onboard) +self:RadioTransmission(self.MarshalRadio,callsay,false,55,nil,true) +self:RadioTransmission(self.MarshalRadio,callfly,false,60,nil,true) +end +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.BULLSEYE) +end +end +function AIRBOSS:_Bullseye(playerData) +self:_CheckCorridor(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZoneBullseye(playerData.case)) +local relheading=self:_GetRelativeHeading(playerData.unit,true) +if inzone and math.abs(relheading)<60 then +self:_PlayerHint(playerData) +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then +self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,nil,nil,nil,true) +elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.CANBERRA then +self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,nil,nil,nil,true) +elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT75,nil,nil,nil,true) +end +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_XX) +end +end +function AIRBOSS:_BolterPattern(playerData) +local X,Z,rho,phi=self:_GetDistances(playerData.unit) +local Bolter={} +Bolter.name="Bolter Pattern" +Bolter.Xmin=-UTILS.NMToMeters(5) +Bolter.Xmax=UTILS.NMToMeters(3) +Bolter.Zmin=-UTILS.NMToMeters(5) +Bolter.Zmax=UTILS.NMToMeters(1) +Bolter.LimitXmin=100 +Bolter.LimitXmax=nil +Bolter.LimitZmin=nil +Bolter.LimitZmax=nil +if self:_CheckLimits(X,Z,Bolter)then +local nextstep +if playerData.case<3 then +nextstep=AIRBOSS.PatternStep.ABEAM +else +nextstep=AIRBOSS.PatternStep.BULLSEYE +end +self:_SetPlayerStep(playerData,nextstep) +end +end +function AIRBOSS:_BreakEntry(playerData) +local X,Z=self:_GetDistances(playerData.unit) +if self:_CheckAbort(X,Z,self.BreakEntry)then +self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) +return +end +if self:_CheckLimits(X,Z,self.BreakEntry)then +self:_PlayerHint(playerData) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) +end +end +function AIRBOSS:_Break(playerData,part) +local X,Z=self:_GetDistances(playerData.unit) +local breakpoint=self.BreakEarly +if part==AIRBOSS.PatternStep.LATEBREAK then +breakpoint=self.BreakLate +end +if self:_CheckAbort(X,Z,breakpoint)then +self:_AbortPattern(playerData,X,Z,breakpoint,true) +return +end +local tooclose=false +if part==AIRBOSS.PatternStep.LATEBREAK then +local close=0.8 +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +close=0.5 +end +if X<0 and Z90 and self:_CheckLimits(X,Z,self.Wake)then +self:MessageToPlayer(playerData,"you are already at the wake and have not passed the 90. Turn faster next time!","LSO") +self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,nil,nil,nil,true) +playerData.wop=true +self:_AddToDebrief(playerData,"Overshoot at wake - Pattern Waveoff!") +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF) +end +end +function AIRBOSS:_Wake(playerData) +local X,Z=self:_GetDistances(playerData.unit) +if self:_CheckAbort(X,Z,self.Wake)then +self:_AbortPattern(playerData,X,Z,self.Wake,true) +return +end +if self:_CheckLimits(X,Z,self.Wake)then +self:_PlayerHint(playerData) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.FINAL) +end +end +function AIRBOSS:_GetGrooveData(playerData) +local X,Z=self:_GetDistances(playerData.unit) +local stern=self:_GetSternCoord() +local rho=stern:Get2DDistance(playerData.unit:GetCoordinate()) +local astern=X=RAR and rho<=RIC and not playerData.waveoff then +local waveoff=self:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData) +if waveoff then +self:T3(self.lid..string.format("Waveoff distance rho=%.1f m",rho)) +self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,nil,nil,nil,true) +playerData.Tlso=timer.getTime() +playerData.waveoff=true +return +end +end +groovedata.Step=playerData.step +if rho>=RAR and rho=RAR and rho<=RIM then +if gd.LUE>0.22 and lineupError<-0.22 then +env.info" Drift Right across centre ==> DR-" +gd.Drift="DR" +self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) +elseif gd.LUE<-0.22 and lineupError>0.22 then +env.info" Drift Left ==> DL-" +gd.Drift="DL" +self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) +elseif gd.LUE>0.13 and lineupError<-0.14 then +env.info" Little Drift Right across centre ==> (DR-)" +gd.Drift="(DR)" +self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) +elseif gd.LUE<-0.13 and lineupError>0.14 then +env.info" Little Drift Left across centre ==> (DL-)" +gd.Drift="(DL)" +self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) +end +end +if math.abs(lineupError)>math.abs(gd.LUE)then +self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f",gs,d,lineupError,gd.LUE)) +gd.LUE=lineupError +end +if gd.GSE>0.4 and glideslopeError<-0.3 then +gd.FlyThrough="\\" +self:T(self.lid..string.format("Got Fly through DOWN at step %s, d=%.3f: Max GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError)) +elseif gd.GSE<-0.3 and glideslopeError>0.4 then +gd.FlyThrough="/" +self:E(self.lid..string.format("Got Fly through UP at step %s, d=%.3f: Min GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError)) +end +if math.abs(glideslopeError)>math.abs(gd.GSE)then +self:T(self.lid..string.format("Got bigger GSE at step %s, d=%.3f: GSE |%.3f|>|%.3f|",gs,d,glideslopeError,gd.GSE)) +gd.GSE=glideslopeError +end +local aircraftaoa=self:_GetAircraftAoA(playerData) +local aoaopt=aircraftaoa.OnSpeed +if math.abs(AoA-aoaopt)>math.abs(gd.AoA-aoaopt)then +self:T(self.lid..string.format("Got bigger AoA error at step %s, d=%.3f: AoA %.3f>%.3f.",gs,d,AoA,gd.AoA)) +gd.AoA=AoA +end +end +local deltaT=timer.getTime()-playerData.Tlso +local _advice=true +if playerData.TIG0==nil and playerData.difficulty~=AIRBOSS.Difficulty.EASY then +_advice=false +end +if deltaT>=self.LSOdT and _advice then +self:_LSOadvice(playerData,glideslopeError,lineupError) +end +end +if X>self.carrierparam.totlength+self.carrierparam.sterndist then +if playerData.waveoff then +if playerData.landed then +self:_AddToDebrief(playerData,"You were waved off but landed anyway. Airboss wants to talk to you!") +else +self:_AddToDebrief(playerData,"You were waved off.") +end +elseif playerData.boltered then +self:_AddToDebrief(playerData,"You boltered.") +else +self:T("Player was not waved off but flew past the carrier without landing ==> Own wave off!") +self:_AddToDebrief(playerData,"Own waveoff.") +playerData.owo=true +end +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF) +end +end +function AIRBOSS:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData) +local waveoff=false +local glMax=1.8 +local glMin=-1.2 +local luAbs=3.0 +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +glMax=2.6 +glMin=-2.2 +luAbs=4.1 +end +if glideslopeError>glMax then +local text=string.format("\n- Waveoff due to glideslope error %.2f > %.1f degrees!",glideslopeError,glMax) +self:T(self.lid..string.format("%s: %s",playerData.name,text)) +self:_AddToDebrief(playerData,text) +waveoff=true +elseif glideslopeErrorluAbs then +local text=string.format("\n- Waveoff due to line up error |%.1f| > %.1f degrees!",lineupError,luAbs) +self:T(self.lid..string.format("%s: %s",playerData.name,text)) +self:_AddToDebrief(playerData,text) +waveoff=true +end +if playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then +local aoaac=self:_GetAircraftAoA(playerData) +if AoAaoaac.SLOW then +local text=string.format("\n- Waveoff due to AoA %.1f > %.1f!",AoA,aoaac.SLOW) +self:T(self.lid..string.format("%s: %s",playerData.name,text)) +self:_AddToDebrief(playerData,text) +waveoff=true +end +end +return waveoff +end +function AIRBOSS:_CheckFoulDeck(playerData) +local check=false +if playerData.step==AIRBOSS.PatternStep.GROOVE_IM or playerData.step==AIRBOSS.PatternStep.GROOVE_IC then +check=true +end +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +if playerData.step==AIRBOSS.PatternStep.GROOVE_AR or playerData.step==AIRBOSS.PatternStep.GROOVE_AL then +check=true +end +end +if playerData.wofd==true or check==false then +return +end +local runway=self:_GetZoneRunwayBox() +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +runway=self:_GetZoneLandingSpot() +end +local R=250 +self:T(self.lid..string.format("Foul deck check: Scanning Carrier Runway Area. Radius=%.1f m.",R)) +local _,_,_,unitscan=self:GetCoordinate():ScanObjects(R,true,false,false) +local fouldeck=false +local foulunit=nil +for _,_unit in pairs(unitscan)do +local unit=_unit +local inzone=unit:IsInZone(runway) +local isaircraft=unit:IsAir() +local isairborn=unit:InAir() +if inzone and isaircraft and not isairborn then +local text=string.format("Unit %s on landing runway ==> Foul deck!",unit:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToAllIf(self.Debug) +if self.Debug then +runway:FlareZone(FLARECOLOR.Red,30) +end +fouldeck=true +foulunit=unit +end +end +if playerData and fouldeck then +local text=string.format("Foul deck waveoff due to aircraft %s!",foulunit:GetName()) +self:T(self.lid..string.format("%s: %s",playerData.name,text)) +self:_AddToDebrief(playerData,text) +self:RadioTransmission(self.LSORadio,self.LSOCall.FOULDECK,false,1) +self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,false,1.2,nil,true) +if playerData.showhints then +local text=string.format("overfly landing area and enter bolter pattern.") +self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3) +end +playerData.wofd=true +playerData.step=AIRBOSS.PatternStep.DEBRIEF +playerData.warning=nil +playerData.valid=false +if foulunit then +local foulflight=self:_GetFlightFromGroupInQueue(foulunit:GetGroup(),self.flights) +if foulflight and not foulflight.ai then +self:MessageToPlayer(foulflight,"move your ass from my runway. NOW!","AIRBOSS") +end +end +end +return fouldeck +end +function AIRBOSS:_GetSternCoord() +local hdg=self.carrier:GetHeading() +local FB=self:GetFinalBearing() +local case=self.case +self.sterncoord:UpdateFromCoordinate(self:GetCoordinate()) +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +if case==3 then +self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(8,FB-90,true,true) +elseif case==2 or case==1 then +self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(8,FB-90,true,true) +end +elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then +self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7,FB+90,true,true) +elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then +self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7.5,FB+90,true,true) +elseif self.carriertype==AIRBOSS.CarrierType.ESSEX then +self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(-1,FB+90,true,true) +else +self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(9.5,FB+90,true,true) +end +self.sterncoord:SetAltitude(self.carrierparam.deckheight) +return self.sterncoord +end +function AIRBOSS:_GetWireFromDrawArg() +local wireArgs={} +wireArgs[1]=141 +wireArgs[2]=142 +wireArgs[3]=143 +wireArgs[4]=144 +for wire,drawArg in pairs(wireArgs)do +local value=self.carrier:GetDrawArgumentValue(drawArg) +if math.abs(value)>0.001 then +return wire +end +end +return 99 +end +function AIRBOSS:_GetWire(Lcoord,dc) +local FB=self:GetFinalBearing() +local Scoord=self:_GetSternCoord() +local Ldist=Lcoord:Get2DDistance(Scoord) +dc=dc or 65 +local d=Ldist-dc +if self.mpWireCorrection then +d=d-self.mpWireCorrection +end +local w1=self.carrierparam.wire1 +local w2=self.carrierparam.wire2 +local w3=self.carrierparam.wire3 +local w4=self.carrierparam.wire4 +local wire +if d wire=%d (dc=%.1f)",Ldist,Ldist-dc,wire,dc)) +return wire +end +function AIRBOSS:_Trapped(playerData) +if playerData.unit:InAir()==false then +local unit=playerData.unit +local coord=unit:GetCoordinate() +local v=unit:GetVelocityKMH()-self.carrier:GetVelocityKMH() +local stern=self:_GetSternCoord() +local s=stern:Get2DDistance(coord) +local dcorr=100 +if playerData.actype==AIRBOSS.AircraftCarrier.HORNET +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF +or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER then +dcorr=100 +elseif playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +dcorr=100 +elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then +dcorr=56 +elseif playerData.actype==AIRBOSS.AircraftCarrier.T45C then +dcorr=56 +end +local wire=self:_GetWire(coord,dcorr) +local text=string.format("Player %s _Trapped: v=%.1f km/h, s-dcorr=%.1f m ==> wire=%d (dcorr=%d)",playerData.name,v,s-dcorr,wire,dcorr) +self:T(self.lid..text) +if v>5 then +if wire>4 and v>10 and not playerData.warning then +self:RadioTransmission(self.LSORadio,self.LSOCall.BOLTER,nil,nil,nil,true) +playerData.warning=true +end +self:ScheduleOnce(0.1,self._Trapped,self,playerData) +return +end +if self.Debug then +coord:SmokeBlue() +coord:MarkToAll(text) +stern:MarkToAll("Stern") +end +playerData.wire=wire +local text=string.format("Trapped %d-wire.",wire) +if wire==3 then +text=text.." " +elseif wire==2 then +text=text.." " +elseif wire==4 then +text=text.." " +elseif wire==1 then +text=text.." " +end +self:MessageToPlayer(playerData,text,"LSO","") +local hint=string.format("Trapped %d-wire.",wire) +self:_AddToDebrief(playerData,hint,"Groove: IW") +else +local text=string.format("Player %s boltered in trapped function.",playerData.name) +self:T(self.lid..text) +MESSAGE:New(text,5,"DEBUG"):ToAllIf(self.debug) +playerData.boltered=true +end +playerData.step=AIRBOSS.PatternStep.DEBRIEF +playerData.warning=nil +end +function AIRBOSS:_GetZoneInitial(case) +self.zoneInitial=self.zoneInitial or ZONE_POLYGON_BASE:New("Zone CASE I/II Initial") +local radial=self:GetRadial(2,false,false) +local cv=self:GetCoordinate() +local vec2={} +if case==1 then +local c1=cv:Translate(UTILS.NMToMeters(0.5),radial-90) +local c2=cv:Translate(UTILS.NMToMeters(1.3),radial-90):Translate(UTILS.NMToMeters(3),radial) +local c3=cv:Translate(UTILS.NMToMeters(0.4),radial+90):Translate(UTILS.NMToMeters(3),radial) +local c4=cv:Translate(UTILS.NMToMeters(1.0),radial) +local c5=cv +vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2()} +else +local c1=cv:Translate(UTILS.NMToMeters(0.5),radial-90) +local c2=c1:Translate(UTILS.NMToMeters(0.5),radial) +local c3=cv:Translate(UTILS.NMToMeters(1.2),radial-90):Translate(UTILS.NMToMeters(3),radial) +local c4=cv:Translate(UTILS.NMToMeters(1.2),radial+90):Translate(UTILS.NMToMeters(3),radial) +local c5=cv:Translate(UTILS.NMToMeters(0.5),radial) +local c6=cv +vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2(),c6:GetVec2()} +end +self.zoneInitial:UpdateFromVec2(vec2) +return self.zoneInitial +end +function AIRBOSS:_GetZoneLineup() +self.zoneLineup=self.zoneLineup or ZONE_POLYGON_BASE:New("Zone Lineup") +local fbi=self:GetRadial(1,false,false) +local st=self:_GetOptLandingCoordinate() +local c1=st +local c2=st:Translate(UTILS.NMToMeters(0.50),fbi+15) +local c3=st:Translate(UTILS.NMToMeters(0.50),fbi+self.lue._max-0.05) +local c4=st:Translate(UTILS.NMToMeters(0.77),fbi+self.lue._max-0.05) +local c5=c4:Translate(UTILS.NMToMeters(0.25),fbi-90) +local vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2()} +self.zoneLineup:UpdateFromVec2(vec2) +return self.zoneLineup +end +function AIRBOSS:_GetZoneGroove(l,w,b) +self.zoneGroove=self.zoneGroove or ZONE_POLYGON_BASE:New("Zone Groove") +l=l or 1.50 +w=w or 0.25 +b=b or 0.10 +local fbi=self:GetRadial(1,false,false) +local st=self:_GetSternCoord() +local c1=st:Translate(self.carrierparam.totwidthstarboard,fbi-90) +local c2=st:Translate(UTILS.NMToMeters(0.10),fbi-90):Translate(UTILS.NMToMeters(0.3),fbi) +local c3=st:Translate(UTILS.NMToMeters(0.25),fbi-90):Translate(UTILS.NMToMeters(l),fbi) +local c4=st:Translate(UTILS.NMToMeters(w/2),fbi+90):Translate(UTILS.NMToMeters(l),fbi) +local c5=st:Translate(UTILS.NMToMeters(b),fbi+90):Translate(UTILS.NMToMeters(0.3),fbi) +local c6=st:Translate(self.carrierparam.totwidthport,fbi+90) +local vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2(),c6:GetVec2()} +self.zoneGroove:UpdateFromVec2(vec2) +return self.zoneGroove +end +function AIRBOSS:_GetZoneBullseye(case) +local radius=UTILS.NMToMeters(1) +local distance=UTILS.NMToMeters(3) +local radial=self:GetRadial(case,false,false) +local coord=self:GetCoordinate():Translate(distance,radial) +local vec2=coord:GetVec2() +local zone=ZONE_RADIUS:New("Zone Bullseye",vec2,radius) +return zone +end +function AIRBOSS:_GetZoneDirtyUp(case) +local radius=UTILS.NMToMeters(1) +local distance=UTILS.NMToMeters(9) +local radial=self:GetRadial(case,false,false) +local coord=self:GetCoordinate():Translate(distance,radial) +local vec2=coord:GetVec2() +local zone=ZONE_RADIUS:New("Zone Dirty Up",vec2,radius) +return zone +end +function AIRBOSS:_GetZoneArcOut(case) +local radius=UTILS.NMToMeters(1.25) +local distance=UTILS.NMToMeters(11.75) +local radial=self:GetRadial(case,false,false) +local coord=self:GetCoordinate():Translate(distance,radial) +local zone=ZONE_RADIUS:New("Zone Arc Out",coord:GetVec2(),radius) +return zone +end +function AIRBOSS:_GetZoneArcIn(case) +local radius=UTILS.NMToMeters(1.25) +local radial=self:GetRadial(case,false,true) +local alpha=math.rad(self.holdingoffset) +local x=14 +local distance=UTILS.NMToMeters(x) +local coord=self:GetCoordinate():Translate(distance,radial) +local zone=ZONE_RADIUS:New("Zone Arc In",coord:GetVec2(),radius) +return zone +end +function AIRBOSS:_GetZonePlatform(case) +local radius=UTILS.NMToMeters(1) +local radial=self:GetRadial(case,false,true) +local alpha=math.rad(self.holdingoffset) +local distance=UTILS.NMToMeters(19) +local coord=self:GetCoordinate():Translate(distance,radial) +local zone=ZONE_RADIUS:New("Zone Platform",coord:GetVec2(),radius) +return zone +end +function AIRBOSS:_GetZoneCorridor(case,l) +l=l or 31 +local radial=self:GetRadial(case,false,false) +local offset=self:GetRadial(case,false,true) +local dx=5 +local w=2 +local w2=w/2 +local d=12 +local cv=self:GetCoordinate() +local c={} +c[1]=cv:Translate(-UTILS.NMToMeters(dx),radial) +if math.abs(self.holdingoffset)>=5 then +c[2]=c[1]:Translate(UTILS.NMToMeters(w2),radial-90) +c[3]=c[2]:Translate(UTILS.NMToMeters(d+dx+w2),radial) +c[4]=cv:Translate(UTILS.NMToMeters(15),offset):Translate(UTILS.NMToMeters(1),offset-90) +c[5]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset-90) +c[6]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset+90) +c[7]=cv:Translate(UTILS.NMToMeters(13),offset):Translate(UTILS.NMToMeters(1),offset+90) +c[8]=cv:Translate(UTILS.NMToMeters(11),radial):Translate(UTILS.NMToMeters(1),radial+90) +c[9]=c[1]:Translate(UTILS.NMToMeters(w2),radial+90) +else +c[2]=c[1]:Translate(UTILS.NMToMeters(w2),radial-90) +c[3]=c[2]:Translate(UTILS.NMToMeters(dx+l),radial) +c[4]=c[3]:Translate(UTILS.NMToMeters(w),radial+90) +c[5]=c[1]:Translate(UTILS.NMToMeters(w2),radial+90) +end +local p={} +for _i,_c in ipairs(c)do +if self.Debug then +end +p[_i]=_c:GetVec2() +end +local zone=ZONE_POLYGON_BASE:New("CASE II/III Approach Corridor",p) +return zone +end +function AIRBOSS:_GetZoneCarrierBox() +self.zoneCarrierbox=self.zoneCarrierbox or ZONE_POLYGON_BASE:New("Carrier Box Zone") +local S=self:_GetSternCoord() +local hdg=self:GetHeading(false) +local p={} +p[1]=S:Translate(self.carrierparam.totwidthstarboard,hdg+90) +p[2]=p[1]:Translate(self.carrierparam.totlength,hdg) +p[3]=p[2]:Translate(self.carrierparam.totwidthstarboard+self.carrierparam.totwidthport,hdg-90) +p[4]=p[3]:Translate(self.carrierparam.totlength,hdg-180) +local vec2={} +for _,coord in ipairs(p)do +table.insert(vec2,coord:GetVec2()) +end +self.zoneCarrierbox:UpdateFromVec2(vec2) +return self.zoneCarrierbox +end +function AIRBOSS:_GetZoneRunwayBox() +self.zoneRunwaybox=self.zoneRunwaybox or ZONE_POLYGON_BASE:New("Landing Runway Zone") +local S=self:_GetSternCoord() +local FB=self:GetFinalBearing(false) +local p={} +p[1]=S:Translate(self.carrierparam.rwywidth*0.5,FB+90) +p[2]=p[1]:Translate(self.carrierparam.rwylength,FB) +p[3]=p[2]:Translate(self.carrierparam.rwywidth,FB-90) +p[4]=p[3]:Translate(self.carrierparam.rwylength,FB-180) +local vec2={} +for _,coord in ipairs(p)do +table.insert(vec2,coord:GetVec2()) +end +self.zoneRunwaybox:UpdateFromVec2(vec2) +return self.zoneRunwaybox +end +function AIRBOSS:_GetZoneAbeamLandingSpot() +local S=self:_GetOptLandingCoordinate() +local FB=self:GetFinalBearing(false) +local p={} +p[1]=S:Translate(15,FB):Translate(15,FB+90) +p[2]=S:Translate(-45,FB):Translate(15,FB+90) +p[3]=S:Translate(-45,FB):Translate(15,FB-90) +p[4]=S:Translate(15,FB):Translate(15,FB-90) +local vec2={} +for _,coord in ipairs(p)do +table.insert(vec2,coord:GetVec2()) +end +local zone=ZONE_POLYGON_BASE:New("Abeam Landing Spot Zone",vec2) +return zone +end +function AIRBOSS:_GetZoneLandingSpot() +local S=self:_GetLandingSpotCoordinate() +local FB=self:GetFinalBearing(false) +local p={} +p[1]=S:Translate(10,FB):Translate(10,FB+90) +p[2]=S:Translate(-10,FB):Translate(10,FB+90) +p[3]=S:Translate(-10,FB):Translate(10,FB-90) +p[4]=S:Translate(10,FB):Translate(10,FB-90) +local vec2={} +for _,coord in ipairs(p)do +table.insert(vec2,coord:GetVec2()) +end +local zone=ZONE_POLYGON_BASE:New("Landing Spot Zone",vec2) +return zone +end +function AIRBOSS:_GetZoneHolding(case,stack) +local zoneHolding=nil +if stack<=0 then +self:E(self.lid.."ERROR: Stack <= 0 in _GetZoneHolding!") +self:E({case=case,stack=stack}) +return nil +end +local patternalt,c1,c2=self:_GetMarshalAltitude(stack,case) +if case==1 then +local hdg=self:GetHeading() +local D=UTILS.NMToMeters(2.5) +local Post=self:GetCoordinate():Translate(D,hdg+270) +self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone",Post:GetVec2(),self.marshalradius) +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone",self.carrier:GetVec2(),UTILS.NMToMeters(5)) +end +else +local radial=self:GetRadial(case,false,true) +local p={} +p[1]=c2:Translate(UTILS.NMToMeters(1),radial-90):GetVec2() +p[2]=c1:Translate(UTILS.NMToMeters(1),radial-90):GetVec2() +p[3]=c1:Translate(UTILS.NMToMeters(7),radial+90):GetVec2() +p[4]=c2:Translate(UTILS.NMToMeters(7),radial+90):GetVec2() +self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone") +self.zoneHolding:UpdateFromVec2(p) +end +return self.zoneHolding +end +function AIRBOSS:_GetZoneCommence(case,stack) +local zone +if case==1 then +local hdg=self:GetHeading() +local D=UTILS.NMToMeters(4.75) +local R=UTILS.NMToMeters(1) +local Three=self:GetCoordinate():Translate(D,hdg+275) +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +local Dx=UTILS.NMToMeters(2.25) +local Dz=UTILS.NMToMeters(2.25) +R=UTILS.NMToMeters(1) +Three=self:GetCoordinate():Translate(Dz,hdg-90):Translate(Dx,hdg-180) +end +self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone") +self.zoneCommence:UpdateFromVec2(Three:GetVec2(),R) +else +stack=stack or 1 +local l=20+stack +local offset=self:GetRadial(case,false,true) +local cv=self:GetCoordinate() +local c={} +c[1]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset-90) +c[2]=cv:Translate(UTILS.NMToMeters(l+2.5),offset):Translate(UTILS.NMToMeters(1),offset-90) +c[3]=cv:Translate(UTILS.NMToMeters(l+2.5),offset):Translate(UTILS.NMToMeters(1),offset+90) +c[4]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset+90) +local p={} +for _i,_c in ipairs(c)do +p[_i]=_c:GetVec2() +end +self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone") +self.zoneCommence:UpdateFromVec2(p) +end +return self.zoneCommence +end +function AIRBOSS:_AttitudeMonitor(playerData) +local unit=playerData.unit +local aoa=unit:GetAoA() +local yaw=unit:GetYaw() +local roll=unit:GetRoll() +local pitch=unit:GetPitch() +local dist=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate()) +local dx,dz,rho,phi=self:_GetDistances(unit) +local wind=unit:GetCoordinate():GetWindWithTurbulenceVec3() +local velo=unit:GetVelocityVec3() +local vabs=UTILS.VecNorm(velo) +local rwy=false +local step=playerData.step +if playerData.step==AIRBOSS.PatternStep.FINAL or +playerData.step==AIRBOSS.PatternStep.GROOVE_XX or +playerData.step==AIRBOSS.PatternStep.GROOVE_IM or +playerData.step==AIRBOSS.PatternStep.GROOVE_IC or +playerData.step==AIRBOSS.PatternStep.GROOVE_AR or +playerData.step==AIRBOSS.PatternStep.GROOVE_AL or +playerData.step==AIRBOSS.PatternStep.GROOVE_LC or +playerData.step==AIRBOSS.PatternStep.GROOVE_IW then +step=self:_GS(step,-1) +rwy=true +end +local relhead=self:_GetRelativeHeading(playerData.unit,rwy) +local text=string.format("Pattern step: %s",step) +text=text..string.format("\nAoA=%.1f° = %.1f Units | |V|=%.1f knots",aoa,self:_AoADeg2Units(playerData,aoa),UTILS.MpsToKnots(vabs)) +if self.Debug then +text=text..string.format("\nVx=%.1f Vy=%.1f Vz=%.1f m/s",velo.x,velo.y,velo.z) +text=text..string.format("\nWind Vx=%.1f Vy=%.1f Vz=%.1f m/s",wind.x,wind.y,wind.z) +end +text=text..string.format("\nPitch=%.1f° | Roll=%.1f° | Yaw=%.1f°",pitch,roll,yaw) +text=text..string.format("\nClimb Angle=%.1f° | Rate=%d ft/min",unit:GetClimbAngle(),velo.y*196.85) +local dist=self:_GetOptLandingCoordinate():Get3DDistance(playerData.unit:GetVec3()) +local vplayer=playerData.unit:GetVelocityKMH() +local vcarrier=self.carrier:GetVelocityKMH() +local dv=math.abs(vplayer-vcarrier) +local alt=self:_GetAltCarrier(playerData.unit) +text=text..string.format("\nDist=%.1f m Alt=%.1f m delta|V|=%.1f km/h",dist,alt,dv) +if playerData.step==AIRBOSS.PatternStep.FINAL or +playerData.step==AIRBOSS.PatternStep.GROOVE_XX or +playerData.step==AIRBOSS.PatternStep.GROOVE_IM or +playerData.step==AIRBOSS.PatternStep.GROOVE_IC or +playerData.step==AIRBOSS.PatternStep.GROOVE_AR or +playerData.step==AIRBOSS.PatternStep.GROOVE_AL or +playerData.step==AIRBOSS.PatternStep.GROOVE_LC or +playerData.step==AIRBOSS.PatternStep.GROOVE_IW then +local lue=self:_Lineup(playerData.unit,true) +local gle=self:_Glideslope(playerData.unit) +text=text..string.format("\nGamma=%.1f° | Rho=%.1f°",relhead,phi) +text=text..string.format("\nLineUp=%.2f° | GlideSlope=%.2f° | AoA=%.1f Units",lue,gle,self:_AoADeg2Units(playerData,aoa)) +local grade,points,analysis=self:_LSOgrade(playerData) +text=text..string.format("\nTgroove=%.1f sec",self:_GetTimeInGroove(playerData)) +text=text..string.format("\nGrade: %s %.1f PT - %s",grade,points,analysis) +else +text=text..string.format("\nR=%.2f NM | X=%d Z=%d m",UTILS.MetersToNM(rho),dx,dz) +text=text..string.format("\nGamma=%.1f° | Rho=%.1f°",relhead,phi) +end +local lueWire=self:_LineupWIRE(playerData.unit,true) +text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0) +local unitClient=Unit.getByName(unit:GetName()) +local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if hornet then +local nozzlePosL=0 +local burnerPosL=unitClient:getDrawArgumentValue(28) +if burnerPosL<0.2 then +nozzlePosL=unitClient:getDrawArgumentValue(89) +else +nozzlePosL=0 +end +local nozzlePosR=0 +local burnerPosR=unitClient:getDrawArgumentValue(29) +if burnerPosR<0.2 then +nozzlePosR=unitClient:getDrawArgumentValue(90) +else +nozzlePosR=0 +end +text=text..string.format("\n Left Nozzle position=%.2f | Right Nozzle position=%.2f ",nozzlePosL,nozzlePosR) +end +if tomcat then +local nozzlePosL=unitClient:getDrawArgumentValue(434) +local nozzlePosR=unitClient:getDrawArgumentValue(433) +text=text..string.format("\n Left Nozzle position=%.2f | Right Nozzle position=%.2f ",nozzlePosL,nozzlePosR) +end +MESSAGE:New(text,1,nil,true):ToClient(playerData.client) +end +function AIRBOSS:_Glideslope(unit,optangle) +if optangle==nil then +if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then +optangle=3.0 +else +optangle=3.5 +end +end +local landingcoord=self:_GetOptLandingCoordinate() +local x=unit:GetCoordinate():Get2DDistance(landingcoord) +local h=self:_GetAltCarrier(unit) +if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then +h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2) +end +local glideslope=math.atan(h/x) +local gs=math.deg(glideslope)-optangle +return gs +end +function AIRBOSS:_Glideslope2(unit,optangle) +if optangle==nil then +if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then +optangle=3.0 +else +optangle=3.5 +end +end +local landingcoord=self:_GetOptLandingCoordinate() +local x=unit:GetCoordinate():Get3DDistance(landingcoord) +local h=self:_GetAltCarrier(unit) +if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then +h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2) +end +local glideslope=math.asin(h/x) +local gs=math.deg(glideslope)-optangle +self:T3(self.lid..string.format("Glide slope error = %.1f, x=%.1f h=%.1f",gs,x,h)) +return gs +end +function AIRBOSS:_Lineup(unit,runway) +local landingcoord=self:_GetOptLandingCoordinate() +local A=landingcoord:GetVec3() +local B=unit:GetVec3() +local C=UTILS.VecSubstract(A,B) +C.y=0.0 +local X=self.carrier:GetOrientationX() +X.y=0.0 +if runway then +X=UTILS.Rotate2D(X,-self.carrierparam.rwyangle) +end +local x=UTILS.VecDot(X,C) +local Z=self.carrier:GetOrientationZ() +Z.y=0.0 +if runway then +Z=UTILS.Rotate2D(Z,-self.carrierparam.rwyangle) +end +local z=UTILS.VecDot(Z,C) +local lineup=math.deg(math.atan2(z,x)) +return lineup +end +function AIRBOSS:_LineupWIRE(unit,runway) +local landingcoord=self:_GetOptLandingCoordinateWIRE() +local A=landingcoord:GetVec3() +local B=unit:GetVec3() +local C=UTILS.VecSubstract(A,B) +C.y=0.0 +local X=self.carrier:GetOrientationX() +X.y=0.0 +if runway then +X=UTILS.Rotate2D(X,-self.carrierparam.rwyangle) +end +local x=UTILS.VecDot(X,C) +local Z=self.carrier:GetOrientationZ() +Z.y=0.0 +if runway then +Z=UTILS.Rotate2D(Z,-self.carrierparam.rwyangle) +end +local z=UTILS.VecDot(Z,C) +local lineup=math.deg(math.atan2(z,x)) +return lineup +end +function AIRBOSS:_NozzleArgumentLeft(unit) +local unitClient=Unit.getByName(unit:GetName()) +local typeName=unit:GetTypeName() +local nozzlePosL=0 +local burnerPosL=0 +if typeName=="FA-18C_hornet"then +burnerPosL=unitClient:getDrawArgumentValue(28) +if burnerPosL<0.2 then +nozzlePosL=unitClient:getDrawArgumentValue(89) +else +nozzlePosL=0 +end +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +nozzlePosL=unitClient:getDrawArgumentValue(434) +end +return nozzlePosL +end +function AIRBOSS:_NozzleArgumentRight(unit) +local unitClient=Unit.getByName(unit:GetName()) +local typeName=unit:GetTypeName() +local nozzlePosR=0 +local burnerPosR=0 +if typeName=="FA-18C_hornet"then +burnerPosR=unitClient:getDrawArgumentValue(29) +if burnerPosR<0.2 then +nozzlePosR=unitClient:getDrawArgumentValue(90) +else +nozzlePosR=0 +end +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +nozzlePosR=unitClient:getDrawArgumentValue(433) +end +return nozzlePosR +end +function AIRBOSS:_GetAltCarrier(unit) +local h=unit:GetAltitude()-self.carrierparam.deckheight-2 +return h +end +function AIRBOSS:_GetOptLandingCoordinate() +self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord()) +local FB=self:GetFinalBearing(false) +local case=self.case +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +if case==3 then +self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()) +self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) +elseif case==2 or case==1 then +self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35,FB-90,true,true) +self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) +end +else +if self.carrierparam.wire3 then +self.landingcoord:Translate(self.carrierparam.wire3,FB,true,true) +end +self.landingcoord.y=self.landingcoord.y+2 +end +return self.landingcoord +end +function AIRBOSS:_GetOptLandingCoordinateWIRE() +self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord()) +local FB=self:GetFinalBearing(false) +local case=self.case +if self.carrierparam.wire3 then +self.landingcoord:Translate(self.carrierparam.wire3+500,FB,true,true) +end +self.landingcoord.y=self.landingcoord.y+2 +return self.landingcoord +end +function AIRBOSS:_GetLandingSpotCoordinate() +self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord()) +local hdg=self:GetHeading() +self.landingspotcoord:Translate(self.carrierparam.landingspot,hdg,true,true):SetAltitude(self.carrierparam.deckheight) +return self.landingspotcoord +end +function AIRBOSS:GetHeading(magnetic) +self:F3({magnetic=magnetic}) +local hdg=self.carrier:GetHeading() +if magnetic then +hdg=hdg-self.magvar +end +hdg=hdg%360 +return hdg +end +function AIRBOSS:GetBRC() +return self:GetHeading(true) +end +function AIRBOSS:GetWind(alt,magnetic,coord) +local cv=coord or self:GetCoordinate() +local Wdir,Wspeed=cv:GetWind(alt or 18) +if magnetic then +Wdir=Wdir-self.magvar +if Wdir<0 then +Wdir=Wdir+360 +end +end +return Wdir,Wspeed +end +function AIRBOSS:GetWindOnDeck(alt) +local cv=self:GetCoordinate() +local vc=self.carrier:GetVelocityVec3() +local xc=self.carrier:GetOrientationX() +local zc=self.carrier:GetOrientationZ() +xc=UTILS.Rotate2D(xc,-self.carrierparam.rwyangle) +zc=UTILS.Rotate2D(zc,-self.carrierparam.rwyangle) +local vw=cv:GetWindWithTurbulenceVec3(alt or 18) +local vT=UTILS.VecSubstract(vw,vc) +local vpa=UTILS.VecDot(vT,xc) +local vpp=UTILS.VecDot(vT,zc) +local vabs=UTILS.VecNorm(vT) +return-vpa,vpp,vabs +end +function AIRBOSS:GetHeadingIntoWind(vdeck,magnetic,coord) +if self.intowindold then +return self:GetHeadingIntoWind_old(vdeck,magnetic,coord) +else +return self:GetHeadingIntoWind_new(vdeck,magnetic,coord) +end +end +function AIRBOSS:GetHeadingIntoWind_old(vdeck,magnetic,coord) +local function adjustDegreesForWindSpeed(windSpeed) +local degreesAdjustment=0 +if windSpeed>0 and windSpeed<3 then +degreesAdjustment=30 +elseif windSpeed>=3 and windSpeed<5 then +degreesAdjustment=20 +elseif windSpeed>=5 and windSpeed<8 then +degreesAdjustment=8 +elseif windSpeed>=8 and windSpeed<13 then +degreesAdjustment=4 +elseif windSpeed>=13 then +degreesAdjustment=0 +end +return degreesAdjustment +end +local windfrom,vwind=self:GetWind(nil,nil,coord) +local intowind=windfrom-self.carrierparam.rwyangle+adjustDegreesForWindSpeed(vwind) +if vwind<0.1 then +intowind=self:GetHeading() +end +if magnetic then +intowind=intowind-self.magvar +end +if intowind<0 then +intowind=intowind+360 +end +local vtot=math.max(vdeck-UTILS.MpsToKnots(vwind),4) +return intowind,vtot +end +function AIRBOSS:GetHeadingIntoWind_new(vdeck,magnetic,coord) +local Offset=self.carrierparam.rwyangle or 0 +local windfrom,vwind=self:GetWind(18,nil,coord) +local Vmin=4 +local Vmax=UTILS.KmphToKnots(self.carrier:GetSpeedMax()) +if vwind<0.1 then +local h=self:GetHeading(magnetic) +return h,math.min(vdeck,Vmax) +end +vwind=UTILS.MpsToKnots(vwind) +local windto=(windfrom+180)%360 +local alpha=math.rad(-Offset) +local C=math.sqrt(math.cos(alpha)^2/math.sin(alpha)^2+1) +local vdeckMax=vwind+math.cos(alpha)*Vmax +local vdeckMin=vwind+math.cos(alpha)*Vmin +local v=0 +local theta=0 +if vdeck>vdeckMax then +v=Vmax +theta=math.asin(v/(vwind*C))-math.asin(-1/C) +elseif vdeckvwind then +theta=math.pi/2 +v=math.sqrt(vdeck^2-vwind^2) +else +theta=math.asin(vdeck*math.sin(alpha)/vwind) +v=vdeck*math.cos(alpha)-vwind*math.cos(theta) +end +local magvar=magnetic and self.magvar or 0 +local intowind=(540+(windto-magvar+math.deg(theta)))%360 +return intowind,v +end +function AIRBOSS:GetBRCintoWind(vdeck) +return self:GetHeadingIntoWind(vdeck,true) +end +function AIRBOSS:GetFinalBearing(magnetic) +local fb=self:GetHeading(magnetic) +fb=fb+self.carrierparam.rwyangle +if fb<0 then +fb=fb+360 +end +return fb +end +function AIRBOSS:GetRadial(case,magnetic,offset,inverse) +case=case or self.case +local radial +if case==1 then +radial=self:GetFinalBearing(magnetic)-180 +elseif case==2 then +radial=self:GetHeading(magnetic)-180 +if offset then +radial=radial+self.holdingoffset +end +elseif case==3 then +radial=self:GetFinalBearing(magnetic)-180 +if offset then +radial=radial+self.holdingoffset +end +end +if radial<0 then +radial=radial+360 +end +if inverse then +radial=radial-180 +if radial<0 then +radial=radial+360 +end +end +return radial +end +function AIRBOSS:_GetDeltaHeading(hdg1,hdg2) +local V={} +V.x=math.cos(math.rad(hdg1)) +V.y=0 +V.z=math.sin(math.rad(hdg1)) +local W={} +W.x=math.cos(math.rad(hdg2)) +W.y=0 +W.z=math.sin(math.rad(hdg2)) +local alpha=UTILS.VecAngle(V,W) +return alpha +end +function AIRBOSS:_GetRelativeHeading(unit,runway) +local vC=self.carrier:GetOrientationX() +if runway then +vC=UTILS.Rotate2D(vC,-self.carrierparam.rwyangle) +end +local vP=unit:GetOrientationX() +vC.y=0; +vP.y=0 +local rhdg=UTILS.VecAngle(vC,vP) +return rhdg +end +function AIRBOSS:_GetRelativeVelocity(unit) +local vC=self.carrier:GetVelocityVec3() +local vP=unit:GetVelocityVec3() +vC.y=0; +vP.y=0 +local v=UTILS.VecSubstract(vP,vC) +return UTILS.VecNorm(v),v +end +function AIRBOSS:_GetDistances(unit) +local a=self.carrier:GetVec3() +local b=unit:GetVec3() +local c={x=b.x-a.x,y=0,z=b.z-a.z} +local x=self.carrier:GetOrientationX() +local dx=UTILS.VecDot(x,c) +local z=self.carrier:GetOrientationZ() +local dz=UTILS.VecDot(z,c) +local rho=math.sqrt(dx*dx+dz*dz) +local phi=math.deg(math.atan2(dz,dx)) +if phi<0 then +phi=phi+360 +end +return dx,dz,rho,phi +end +function AIRBOSS:_CheckLimits(X,Z,check) +local nextXmin=check.LimitXmin==nil or(check.LimitXmin and(check.LimitXmin<0 and X<=check.LimitXmin or check.LimitXmin>=0 and X>=check.LimitXmin)) +local nextXmax=check.LimitXmax==nil or(check.LimitXmax and(check.LimitXmax<0 and X>=check.LimitXmax or check.LimitXmax>=0 and X<=check.LimitXmax)) +local nextZmin=check.LimitZmin==nil or(check.LimitZmin and(check.LimitZmin<0 and Z<=check.LimitZmin or check.LimitZmin>=0 and Z>=check.LimitZmin)) +local nextZmax=check.LimitZmax==nil or(check.LimitZmax and(check.LimitZmax<0 and Z>=check.LimitZmax or check.LimitZmax>=0 and Z<=check.LimitZmax)) +local next=nextXmin and nextXmax and nextZmin and nextZmax +local text=string.format("step=%s: next=%s: X=%d Xmin=%s Xmax=%s | Z=%d Zmin=%s Zmax=%s",check.name,tostring(next),X,tostring(check.LimitXmin),tostring(check.LimitXmax),Z,tostring(check.LimitZmin),tostring(check.LimitZmax)) +self:T3(self.lid..text) +return next +end +function AIRBOSS:_LSOadvice(playerData,glideslopeError,lineupError) +local advice=0 +if glideslopeError>self.gle.HIGH then +self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,true,nil,nil,true) +advice=advice+self.LSOCall.HIGH.duration +elseif glideslopeError>self.gle.High then +self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,false,nil,nil,true) +advice=advice+self.LSOCall.HIGH.duration +elseif glideslopeErrorself.lue.RIGHT then +self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,true,nil,nil,true) +advice=advice+self.LSOCall.RIGHTFORLINEUP.duration +elseif lineupError>self.lue.Right then +self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,false,nil,nil,true) +advice=advice+self.LSOCall.RIGHTFORLINEUP.duration +else +end +local AOA=playerData.unit:GetAoA() +local acaoa=self:_GetAircraftAoA(playerData) +if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then +if AOA>acaoa.SLOW then +self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,true,nil,nil,true) +advice=advice+self.LSOCall.SLOW.duration +elseif AOA>acaoa.Slow then +self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,false,nil,nil,true) +advice=advice+self.LSOCall.SLOW.duration +elseif AOA>acaoa.OnSpeedMax then +elseif AOA=25 then +grade="_LIG_" +elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<55 then +grade="FAST V/STOL Groove" +elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<75 then +grade="OK V/STOL Groove" +elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t>=76 then +grade="SLOW V/STOL Groove" +else +grade="LIG" +end +if t>=16.49 and t<=16.6 then +grade="_OK_" +end +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and(t>=60.0 and t<=65.0)then +grade="_OK_ V/STOL" +end +return grade +end +function AIRBOSS:_LSOgrade(playerData) +local function count(base,pattern) +return select(2,string.gsub(base,pattern,"")) +end +local TIG="" +if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then +TIG=self:_EvalGrooveTime(playerData)or"N/A" +end +local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) +local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) +local GIC,nIC=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IC) +local GAR,nAR=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.AR) +local GIW,nIW=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IW) +local vtol=playerData.actype==AIRBOSS.AircraftCarrier.AV8B +local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR.." "..GIW.." "..TIG +local gradeWithDeviations=GXX.."["..nXX.."] "..GIM.."["..nIM.."] "..GIC.."["..nIC.."] "..GAR.."["..nAR.."] "..GIW.."["..nIW.."]" +env.info("LSO Grade [with deviation count]: "..gradeWithDeviations) +local N=nXX+nIM+nIC+nAR+nIW +local nL=count(G,'_')/2 +local nS=count(G,'%(') +local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end +local Tgroove=playerData.Tgroove +local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false +local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false +local grade +local points +if N==0 and TgrooveVstolUnicorn then +grade="_OK_" +points=5.0 +G="Unicorn" +end +if N==0 and TgrooveUnicorn then +if playerData.wire==3 then +grade="_OK_" +points=5.0 +G="Unicorn" +else +grade="OK" +points=4.0 +end +else +if vtol then +local Gb=GXX.." "..GIM +local N=nXX+nIM +local nL=count(Gb,'_')/2 +local nS=count(Gb,'%(') +local nN=N-nS-nL +local Gv=GIC.." "..GAR +local Nv=nIC+nAR +local nLv=count(Gv,'_')/2 +local nSv=count(Gv,'%(') +local nNv=Nv-nSv-nLv +if nL>0 or nLv>1 then +grade="--" +points=2.0 +elseif nN>0 or nNv>1 or nLv==1 then +grade="(OK)" +points=3.0 +else +grade="OK" +points=4.0 +end +else +if nL>0 then +grade="--" +points=2.0 +elseif nN>0 then +grade="(OK)" +points=3.0 +else +grade="OK" +points=4.0 +end +end +end +G=G:gsub("%)%(","") +G=G:gsub("__","") +local text="LSO grade:\n" +text=text..G.."\n" +text=text.."Grade = "..grade.." points = "..points.."\n" +text=text.."# of total deviations = "..N.."\n" +text=text.."# of large deviations _ = "..nL.."\n" +text=text.."# of normal deviations = "..nN.."\n" +text=text.."# of small deviations ( = "..nS.."\n" +self:T2(self.lid..text) +env.info(text) +if playerData.wop then +if playerData.lig then +grade="WO" +points=1.0 +G="LIG" +else +grade="WOP" +points=2.0 +G="n/a" +end +elseif playerData.wofd then +if playerData.landed then +grade="CUT" +points=0.0 +else +grade="WOFD" +points=-1.0 +end +G="n/a" +elseif playerData.owo then +grade="OWO" +points=2.0 +if N==0 then +G="n/a" +end +elseif playerData.waveoff then +if playerData.landed then +grade="CUT" +points=0.0 +else +grade="WO" +points=1.0 +end +elseif playerData.boltered then +grade="-- (BOLTER)" +points=2.5 +elseif not playerData.hover and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +if playerData.landed then +grade="CUT" +points=0.0 +end +end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 +end +env.info("Returning: "..grade.." "..points.." "..G) +return grade,points,G +end +function AIRBOSS:_Flightdata2Text(playerData,groovestep) +local function little(text) +return string.format("(%s)",text) +end +local function underline(text) +return string.format("_%s_",text) +end +local fdata=playerData.groove[groovestep] +if fdata==nil then +self:T3(self.lid.."Flight data is nil.") +return"",0 +end +local step=fdata.Step +local AOA=fdata.AoA +local GSE=fdata.GSE +local LUE=fdata.LUE +local LUEwire=fdata.LUEwire +local Lnoz=fdata.LeftNozzle +local Rnoz=fdata.RightNozzle +local ROL=fdata.Roll +local GT=fdata.GT +local acaoa=self:_GetAircraftAoA(playerData) +local P=nil +if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=3.5 and playerData.case<3 then +if LUE>3.2 then +P=underline("LUL") +elseif LUE>2.2 then +P="LUL" +elseif LUE>1.2 then +P=little("LUL") +end +end +local O=nil +if step==AIRBOSS.PatternStep.GROOVE_XX and playerData.case<3 then +if LUEacaoa.SLOW then +S=underline("SLO") +elseif AOA>acaoa.Slow then +S="SLO" +elseif AOA>acaoa.OnSpeedMax then +S=little("SLO") +elseif AOAself.gle.HIGH then +A=underline("H") +elseif GSE>self.gle.High then +A="H" +elseif GSE>self.gle._max then +A=little("H") +elseif GSEself.lue.RIGHT and step~=AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then +D=underline("LUL") +elseif LUE>self.lue.Right and step~=AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then +D="LUL" +elseif LUE>self.lue._max and step~=AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then +D=little("LUL") +elseif LUE1.2 then +DW=underline("LL") +elseif LUEwire>0.4 then +DW="LL" +elseif LUEwire>0.25 then +DW=little("LL") +elseif LUEwire<-1.17 then +DW=underline("LR") +elseif LUEwire<-0.46 then +DW="LR" +elseif LUEwire<-0.25 then +DW=little("LR") +else +end +if ROL>5 and ROL<=10 then +Rol=little("LRWD") +elseif ROL>10 and ROL<=15 then +Rol=("LRWD") +elseif ROL>15 then +Rol=underline("LRWD") +elseif ROL<-5 and ROL>=-10 then +Rol=little("LLWD") +elseif ROL<-10 and ROL>=-15 then +Rol=("LLWD") +elseif ROL<-15 then +Rol=underline("LLWD") +else +end +local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if hornet then +if Lnoz>0.6 and Rnoz>0.6 then +Noz=underline("EG") +else +end +end +if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then +local grooveTime=playerData.Tgroove +if grooveTime>19 or grooveTime<15 then +GT="" +end +end +end +local G="" +local n=0 +if stepMod=="XX"then +if playerData.case<3 then +if fdata.WrappedUp then +env.info("Adding WrappedUp deviation.") +G=G..fdata.WrappedUp +n=n+1 +end +if fdata.AngledApch then +env.info("Adding AngledApch deviation.") +G=G..fdata.AngledApch +n=n+1 +end +if fdata.AFU then +env.info("Adding AFU deviation.") +G=G..fdata.AFU +n=n+1 +end +end +end +if fdata.FlyThrough then +G=G..fdata.FlyThrough +end +if S then +env.info("Adding speed deviation.") +G=G..S +n=n+1 +end +if A then +env.info("Adding altitude deviation.") +G=G..A +n=n+1 +end +if D then +env.info("Adding line up deviation.") +G=G..D +n=n+1 +end +if fdata.Drift then +env.info("Adding drift deviation.") +G=G..fdata.Drift +n=n +end +if O then +env.info("Adding overshoot deviation.") +G=G..O +n=n+1 +end +if DW then +env.info("Adding landed L/R deviation.") +G=G..DW +n=n+1 +end +if Rol then +env.info("Adding landed rol deviation.") +G=G..Rol +n=n+1 +end +if Noz then +env.info("Adding eased guns deviation.") +G=G..Noz +n=n+1 +end +if GT then +G=G..GT +n=n+1 +end +local step=self:_GS(step) +step=step:gsub("XX","X") +if G~=""then +G=G..step +end +local text=string.format("LSO Grade at %s:\n",step) +text=text..string.format("AOA=%.1f\n",AOA) +text=text..string.format("GSE=%.1f\n",GSE) +text=text..string.format("LUE=%.1f\n",LUE) +text=text..string.format("ROL=%.1f\n",ROL) +text=text..G +self:T3(self.lid..text) +return G,n +end +function AIRBOSS:_GS(step,n) +local gp +n=n or 0 +if step==AIRBOSS.PatternStep.FINAL then +gp=AIRBOSS.GroovePos.X0 +if n==-1 then +gp=AIRBOSS.GroovePos.X0 +elseif n==1 then +gp=AIRBOSS.GroovePos.XX +end +elseif step==AIRBOSS.PatternStep.GROOVE_XX then +gp=AIRBOSS.GroovePos.XX +if n==-1 then +gp=AIRBOSS.GroovePos.X0 +elseif n==1 then +gp=AIRBOSS.GroovePos.IM +end +elseif step==AIRBOSS.PatternStep.GROOVE_IM then +gp=AIRBOSS.GroovePos.IM +if n==-1 then +gp=AIRBOSS.GroovePos.XX +elseif n==1 then +gp=AIRBOSS.GroovePos.IC +end +elseif step==AIRBOSS.PatternStep.GROOVE_IC then +gp=AIRBOSS.GroovePos.IC +if n==-1 then +gp=AIRBOSS.GroovePos.IM +elseif n==1 then +gp=AIRBOSS.GroovePos.AR +end +elseif step==AIRBOSS.PatternStep.GROOVE_AR then +gp=AIRBOSS.GroovePos.AR +if n==-1 then +gp=AIRBOSS.GroovePos.IC +elseif n==1 then +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +gp=AIRBOSS.GroovePos.AL +else +gp=AIRBOSS.GroovePos.IW +end +end +elseif step==AIRBOSS.PatternStep.GROOVE_AL then +gp=AIRBOSS.GroovePos.AL +if n==-1 then +gp=AIRBOSS.GroovePos.AR +elseif n==1 then +gp=AIRBOSS.GroovePos.LC +end +elseif step==AIRBOSS.PatternStep.GROOVE_LC then +gp=AIRBOSS.GroovePos.LC +if n==-1 then +gp=AIRBOSS.GroovePos.AL +elseif n==1 then +gp=AIRBOSS.GroovePos.LC +end +elseif step==AIRBOSS.PatternStep.GROOVE_IW then +gp=AIRBOSS.GroovePos.IW +if n==-1 then +gp=AIRBOSS.GroovePos.AR +elseif n==1 then +gp=AIRBOSS.GroovePos.IW +end +end +return gp +end +function AIRBOSS:_CheckAbort(X,Z,pos) +local abort=false +if pos.Xmin and Xpos.Xmax then +self:T(string.format("Xmax: X=%d > %d=Xmax",X,pos.Xmax)) +abort=true +elseif pos.Zmin and Zpos.Zmax then +self:T(string.format("Zmax: Z=%d > %d=Zmax",Z,pos.Zmax)) +abort=true +end +return abort +end +function AIRBOSS:_TooFarOutText(X,Z,posData) +local text="you are too " +local xtext=nil +if posData.Xmin and XposData.Xmax then +if posData.Xmax>=0 then +xtext="far ahead of " +else +xtext="close to " +end +end +local ztext=nil +if posData.Zmin and ZposData.Zmax then +if posData.Zmax>=0 then +ztext="far starboard of " +else +ztext="too close to " +end +end +if xtext and ztext then +text=text..xtext.." and "..ztext +elseif xtext then +text=text..xtext +elseif ztext then +text=text..ztext +end +text=text.."the carrier." +if xtext==nil and ztext==nil then +text="you are too far from where you should be!" +end +return text +end +function AIRBOSS:_AbortPattern(playerData,X,Z,posData,patternwo) +local text=self:_TooFarOutText(X,Z,posData) +local dtext=string.format("Abort: X=%d Xmin=%s, Xmax=%s | Z=%d Zmin=%s Zmax=%s",X,tostring(posData.Xmin),tostring(posData.Xmax),Z,tostring(posData.Zmin),tostring(posData.Zmax)) +self:T(self.lid..dtext) +self:MessageToPlayer(playerData,text,"LSO") +if patternwo then +playerData.wop=true +self:_AddToDebrief(playerData,string.format("Pattern wave off: %s",text)) +self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,false,3,nil,nil,true) +playerData.step=AIRBOSS.PatternStep.DEBRIEF +playerData.warning=nil +end +end +function AIRBOSS:_PlayerHint(playerData,delay,soundoff) +if not playerData.showhints then +return +end +local alt,aoa,dist,speed=self:_GetAircraftParameters(playerData) +local hintAlt,debriefAlt,callAlt=self:_AltitudeCheck(playerData,alt) +local hintSpeed,debriefSpeed,callSpeed=self:_SpeedCheck(playerData,speed) +local hintAoA,debriefAoA,callAoA=self:_AoACheck(playerData,aoa) +local hintDist,debriefDist,callDist=self:_DistanceCheck(playerData,dist) +local hint="" +if hintAlt and hintAlt~=""then +hint=hint.."\n"..hintAlt +end +if hintSpeed and hintSpeed~=""then +hint=hint.."\n"..hintSpeed +end +if hintAoA and hintAoA~=""then +hint=hint.."\n"..hintAoA +end +if hintDist and hintDist~=""then +hint=hint.."\n"..hintDist +end +local debrief="" +if debriefAlt and debriefAlt~=""then +debrief=debrief.."\n- "..debriefAlt +end +if debriefSpeed and debriefSpeed~=""then +debrief=debrief.."\n- "..debriefSpeed +end +if debriefAoA and debriefAoA~=""then +debrief=debrief.."\n- "..debriefAoA +end +if debriefDist and debriefDist~=""then +debrief=debrief.."\n- "..debriefDist +end +if debrief~=""then +self:_AddToDebrief(playerData,debrief) +end +delay=delay or 0 +if not soundoff then +if callAlt then +self:Sound2Player(playerData,self.LSORadio,callAlt,false,delay) +delay=delay+callAlt.duration+0.5 +end +if callSpeed then +self:Sound2Player(playerData,self.LSORadio,callSpeed,false,delay) +delay=delay+callSpeed.duration+0.5 +end +if callAoA then +self:Sound2Player(playerData,self.LSORadio,callAoA,false,delay) +delay=delay+callAoA.duration+0.5 +end +if callDist then +self:Sound2Player(playerData,self.LSORadio,callDist,false,delay) +delay=delay+callDist.duration+0.5 +end +end +if playerData.step==AIRBOSS.PatternStep.ARCIN then +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +local radial=self:GetRadial(playerData.case,true,false,true) +local turn="right" +if self.holdingoffset<0 then +turn="left" +end +hint=hint..string.format("\nTurn %s and select TACAN %03d°.",turn,radial) +end +end +if playerData.step==AIRBOSS.PatternStep.DIRTYUP then +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +hint=hint.."\nFAF! Checks completed. Nozzles 50°." +else +hint=hint.."\nDirty up! Hook, gear and flaps down." +end +end +end +if playerData.step==AIRBOSS.PatternStep.BULLSEYE then +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +if playerData.actype==AIRBOSS.AircraftCarrier.HORNET +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE +or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF +or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER then +hint=hint..string.format("\nIntercept glideslope and follow the needles.") +else +hint=hint..string.format("\nIntercept glideslope.") +end +end +end +if hint~=""then +local text=string.format("%s%s",playerData.step,hint) +self:MessageToPlayer(playerData,hint,"AIRBOSS","") +end +end +function AIRBOSS:_StepHint(playerData,step) +step=step or playerData.step +if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then +local alt,aoa,dist,speed=self:_GetAircraftParameters(playerData,step) +local hint="" +if alt then +hint=hint..string.format("\nAltitude %d ft",UTILS.MetersToFeet(alt)) +end +if aoa then +hint=hint..string.format("\nAoA %.1f",self:_AoADeg2Units(playerData,aoa)) +end +if speed then +hint=hint..string.format("\nSpeed %d knots",UTILS.MpsToKnots(speed)) +end +if dist then +hint=hint..string.format("\nDistance to the boat %.1f NM",UTILS.MetersToNM(dist)) +end +if step==AIRBOSS.PatternStep.LATEBREAK then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +hint=hint.."\nWing Sweep 20°, Gear DOWN < 280 KIAS." +end +end +if step==AIRBOSS.PatternStep.ABEAM then +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +hint=hint.."\nNozzles 50°-60°. Antiskid OFF. Lights OFF." +elseif playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +hint=hint.."\nSlats/Flaps EXTENDED < 225 KIAS. DLC SELECTED. Auto Throttle IF DESIRED." +else +hint=hint.."\nDirty up! Gear DOWN, flaps DOWN. Check hook down." +end +end +if hint~=""then +local text=string.format("Optimal setup at next step %s:%s",step,hint) +self:MessageToPlayer(playerData,text,"AIRBOSS","",nil,false,1) +end +end +end +function AIRBOSS:_AltitudeCheck(playerData,altopt) +if altopt==nil then +return nil,nil +end +local altitude=playerData.unit:GetAltitude() +local lowscore,badscore=self:_GetGoodBadScore(playerData) +local _error=(altitude-altopt)/altopt*100 +local radiocall=nil +local hint="" +if _error>badscore then +radiocall=self:_NewRadioCall(self.LSOCall.HIGH,"Paddles","") +elseif _error>lowscore then +radiocall=self:_NewRadioCall(self.LSOCall.HIGH,"Paddles","") +elseif _error<-badscore then +radiocall=self:_NewRadioCall(self.LSOCall.LOW,"Paddles","") +elseif _error<-lowscore then +radiocall=self:_NewRadioCall(self.LSOCall.LOW,"Paddles","") +else +hint=string.format("Good altitude. ") +end +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +hint=hint..string.format("Optimal altitude is %d ft.",UTILS.MetersToFeet(altopt)) +elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then +hint="" +elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then +hint="" +end +local debrief=string.format("Altitude %d ft = %d%% deviation from %d ft.",UTILS.MetersToFeet(altitude),_error,UTILS.MetersToFeet(altopt)) +return hint,debrief,radiocall +end +function AIRBOSS:_AoACheck(playerData,optaoa) +if optaoa==nil then +return nil,nil +end +local lowscore,badscore=self:_GetGoodBadScore(playerData) +local aoa=playerData.unit:GetAoA() +local _error=(aoa-optaoa)/optaoa*100 +local aircraftaoa=self:_GetAircraftAoA(playerData) +local radiocall=nil +local hint="" +if aoa>=aircraftaoa.SLOW then +radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"Paddles","") +elseif aoa>=aircraftaoa.Slow then +radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"Paddles","") +elseif aoa>=aircraftaoa.OnSpeedMax then +hint="Your're a little slow. " +elseif aoa>=aircraftaoa.OnSpeedMin then +hint="You're on speed. " +elseif aoa>=aircraftaoa.Fast then +hint="You're a little fast. " +elseif aoa>=aircraftaoa.FAST then +radiocall=self:_NewRadioCall(self.LSOCall.FAST,"Paddles","") +else +radiocall=self:_NewRadioCall(self.LSOCall.FAST,"Paddles","") +end +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +hint=hint..string.format("Optimal AoA is %.1f.",self:_AoADeg2Units(playerData,optaoa)) +elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then +hint="" +elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then +hint="" +end +local debrief=string.format("AoA %.1f = %d%% deviation from %.1f.",self:_AoADeg2Units(playerData,aoa),_error,self:_AoADeg2Units(playerData,optaoa)) +return hint,debrief,radiocall +end +function AIRBOSS:_SpeedCheck(playerData,speedopt) +if speedopt==nil then +return nil,nil +end +local speed=playerData.unit:GetVelocityMPS() +local lowscore,badscore=self:_GetGoodBadScore(playerData) +local _error=(speed-speedopt)/speedopt*100 +local radiocall=nil +local hint="" +if _error>badscore then +radiocall=self:_NewRadioCall(self.LSOCall.FAST,"AIRBOSS","") +elseif _error>lowscore then +radiocall=self:_NewRadioCall(self.LSOCall.FAST,"AIRBOSS","") +elseif _error<-badscore then +radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"AIRBOSS","") +elseif _error<-lowscore then +radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"AIRBOSS","") +else +hint=string.format("Good speed. ") +end +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +hint=hint..string.format("Optimal speed is %d knots.",UTILS.MpsToKnots(speedopt)) +elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then +hint="" +elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then +hint="" +end +local debrief=string.format("Speed %d knots = %d%% deviation from %d knots.",UTILS.MpsToKnots(speed),_error,UTILS.MpsToKnots(speedopt)) +return hint,debrief,radiocall +end +function AIRBOSS:_DistanceCheck(playerData,optdist) +if optdist==nil then +return nil,nil +end +local distance=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate()) +local lowscore,badscore=self:_GetGoodBadScore(playerData) +local _error=(distance-optdist)/optdist*100 +local hint +if _error>badscore then +hint=string.format("You're too far from the boat!") +elseif _error>lowscore then +hint=string.format("You're slightly too far from the boat.") +elseif _error<-badscore then +hint=string.format("You're too close to the boat!") +elseif _error<-lowscore then +hint=string.format("You're slightly too far from the boat.") +else +hint=string.format("Good distance to the boat.") +end +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +hint=hint..string.format(" Optimal distance is %.1f NM.",UTILS.MetersToNM(optdist)) +elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then +hint="" +elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then +hint="" +end +local debrief=string.format("Distance %.1f NM = %d%% deviation from %.1f NM.",UTILS.MetersToNM(distance),_error,UTILS.MetersToNM(optdist)) +return hint,debrief,nil +end +function AIRBOSS:_AddToDebrief(playerData,hint,step) +step=step or playerData.step +table.insert(playerData.debrief,{step=step,hint=hint}) +end +function AIRBOSS:_Debrief(playerData) +self:F(self.lid..string.format("Debriefing of player %s.",playerData.name)) +playerData.debriefschedulerID=nil +playerData.attitudemonitor=false +local grade,points,analysis=self:_LSOgrade(playerData) +if points and points>=0 then +table.insert(playerData.points,points) +end +local Points=0 +if playerData.landed and not playerData.unit:InAir()then +for _,_points in pairs(playerData.points)do +Points=Points+_points +end +Points=Points/#playerData.points +playerData.points={} +else +Points=points +end +local mygrade={} +mygrade.grade=grade +mygrade.points=points +mygrade.details=analysis +mygrade.wire=playerData.wire +mygrade.Tgroove=playerData.Tgroove +if playerData.landed and not playerData.unit:InAir()then +mygrade.finalscore=Points +end +mygrade.case=playerData.case +local windondeck=self:GetWindOnDeck() +mygrade.wind=UTILS.Round(UTILS.MpsToKnots(windondeck),1) +mygrade.modex=playerData.onboard +mygrade.airframe=playerData.actype +mygrade.carriertype=self.carriertype +mygrade.carriername=self.alias +mygrade.carrierrwy=self.carrierparam.rwyangle +mygrade.theatre=self.theatre +mygrade.mitime=UTILS.SecondsToClock(timer.getAbsTime(),true) +mygrade.midate=UTILS.GetDCSMissionDate() +mygrade.osdate="n/a" +if os then +mygrade.osdate=os.date() +end +playerData.grade=mygrade +if playerData.trapon and self.trapsheet then +self:_SaveTrapSheet(playerData,mygrade) +end +table.insert(self.playerscores[playerData.name],mygrade) +self:LSOGrade(playerData,mygrade) +local text=string.format("%s %.1f PT - %s",grade,Points,analysis) +if Points==-1 then +text=string.format("%s n/a PT - Foul deck",grade,Points,analysis) +end +if not(playerData.wop or playerData.wofd)then +if playerData.wire and playerData.wire<=4 then +text=text..string.format(" %d-wire",playerData.wire) +end +if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then +text=text..string.format("\nTime in the groove %.1f seconds.",playerData.Tgroove) +end +end +playerData.lastdebrief=UTILS.DeepCopy(playerData.debrief) +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +text=text..string.format("\nYour detailed debriefing can be found via the F10 radio menu.") +end +self:MessageToPlayer(playerData,text,"LSO","",30,true) +playerData.step=AIRBOSS.PatternStep.UNDEFINED +if playerData.wop then +if playerData.unit:IsAlive()then +local heading,distance +if playerData.case==1 or playerData.case==2 then +playerData.step=AIRBOSS.PatternStep.INITIAL +local initial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5),self:GetRadial(2,false,false,false)) +heading=playerData.unit:GetCoordinate():HeadingTo(initial) +distance=playerData.unit:GetCoordinate():Get2DDistance(initial) +elseif playerData.case==3 then +playerData.step=AIRBOSS.PatternStep.BULLSEYE +local zone=self:_GetZoneBullseye(playerData.case) +heading=playerData.unit:GetCoordinate():HeadingTo(zone:GetCoordinate()) +distance=playerData.unit:GetCoordinate():Get2DDistance(zone:GetCoordinate()) +end +local text=string.format("fly heading %03d° for %d NM to re-enter the pattern.",heading,UTILS.MetersToNM(distance)) +self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,5) +else +self:E(self.lid..string.format("ERROR: Player unit not alive!")) +end +elseif playerData.wofd then +if playerData.unit:InAir()then +playerData.step=AIRBOSS.PatternStep.BOLTER +else +self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD) +local text=string.format("deck was fouled but you landed anyway. Airboss wants to talk to you!") +self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3) +end +elseif playerData.owo then +if playerData.unit:InAir()then +playerData.step=AIRBOSS.PatternStep.BOLTER +else +self:E(self.lid.."ERROR: player landed when OWO was issues. This should not happen. Please report!") +self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD) +end +elseif playerData.waveoff then +if playerData.unit:InAir()then +playerData.step=AIRBOSS.PatternStep.BOLTER +else +self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD) +local text=string.format("you were waved off but landed anyway. Airboss wants to talk to you!") +self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3) +end +elseif playerData.boltered then +if playerData.unit:InAir()then +playerData.step=AIRBOSS.PatternStep.BOLTER +end +elseif playerData.landed then +if not playerData.unit:InAir()then +self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD) +end +else +self:MessageToPlayer(playerData,"Undefined state after landing! Please report.","ERROR",nil,20) +playerData.step=AIRBOSS.PatternStep.UNDEFINED +end +if playerData.landed and not playerData.unit:InAir()then +self:_RecoveredElement(playerData.unit) +self:_CheckSectionRecovered(playerData) +end +playerData.passes=playerData.passes+1 +self:_StepHint(playerData) +self:_InitPlayer(playerData,playerData.step) +MESSAGE:New(string.format("Player step %s.",playerData.step),5,"DEBUG"):ToAllIf(self.Debug) +if self.autosave and mygrade.finalscore then +self:Save(self.autosavepath,self.autosavefile) +end +end +function AIRBOSS:_CheckCollisionCoord(coordto,coordfrom) +local dx=100 +local d=0 +if coordfrom then +d=0 +else +d=250 +coordfrom=self:GetCoordinate():Translate(d,self:GetHeading()) +end +local dmax=coordfrom:Get2DDistance(coordto) +local direction=coordfrom:HeadingTo(coordto) +local clear=true +while d<=dmax do +local cp=coordfrom:Translate(d,direction) +if not cp:IsSurfaceTypeWater()then +if self.Debug then +local st=cp:GetSurfaceType() +cp:MarkToAll(string.format("Collision check surface type %d",st)) +end +clear=false +break +end +d=d+dx +end +local text="" +if clear then +text=string.format("Path into direction %03d° is clear for the next %.1f NM.",direction,UTILS.MetersToNM(d)) +else +text=string.format("Detected obstacle at distance %.1f NM into direction %03d°.",UTILS.MetersToNM(d),direction) +end +self:T2(self.lid..text) +return not clear,d +end +function AIRBOSS:_CheckFreePathToNextWP(fromcoord) +fromcoord=fromcoord or self:GetCoordinate():Translate(250,self:GetHeading()) +local Nnextwp=math.min(self.currentwp+1,#self.waypoints) +local nextwp=self.waypoints[Nnextwp] +local collision=self:_CheckCollisionCoord(nextwp,fromcoord) +return collision +end +function AIRBOSS:_Pathfinder() +local hdg=self:GetHeading() +local cv=self:GetCoordinate() +local directions={-20,20,-30,30,-40,40,-50,50,-60,60,-70,70,-80,80,-90,90,-100,100} +for _,_direction in pairs(directions)do +local direction=hdg+_direction +local _,dfree=self:_CheckCollisionCoord(cv:Translate(UTILS.NMToMeters(20),direction),cv) +local distance=500 +while distance<=dfree do +local fromcoord=cv:Translate(distance,direction) +local collision=self:_CheckFreePathToNextWP(fromcoord) +self:T2(self.lid..string.format("Pathfinder d=%.1f m, direction=%03d°, collision=%s",distance,direction,tostring(collision))) +if not collision then +self:CarrierDetour(fromcoord) +return +end +distance=distance+500 +end +end +end +function AIRBOSS:CarrierResumeRoute(gotocoord) +AIRBOSS._ResumeRoute(self.carrier:GetGroup(),self,gotocoord) +return self +end +function AIRBOSS:CarrierDetour(coord,speed,uturn,uspeed,tcoord) +local pos0=self:GetCoordinate() +local vel0=self.carrier:GetVelocityKNOTS() +speed=speed or math.max(vel0,5) +local speedkmh=math.max(UTILS.KnotsToKmph(speed),UTILS.KnotsToKmph(2)) +local cspeedkmh=math.max(self.carrier:GetVelocityKMH(),UTILS.KnotsToKmph(10)) +local uspeedkmh=UTILS.KnotsToKmph(uspeed or speed) +local wp={} +table.insert(wp,pos0:WaypointGround(cspeedkmh)) +if tcoord then +table.insert(wp,tcoord:WaypointGround(cspeedkmh)) +end +table.insert(wp,coord:WaypointGround(speedkmh)) +if uturn then +table.insert(wp,pos0:WaypointGround(uspeedkmh)) +end +local group=self.carrier:GetGroup() +local TaskResumeRoute=group:TaskFunction("AIRBOSS._ResumeRoute",self) +group:SetTaskWaypoint(wp[#wp],TaskResumeRoute) +if self.Debug then +if tcoord then +tcoord:MarkToAll(string.format("Detour Turn Help WP. Speed %.1f knots",UTILS.KmphToKnots(cspeedkmh))) +end +coord:MarkToAll(string.format("Detour Waypoint. Speed %.1f knots",UTILS.KmphToKnots(speedkmh))) +if uturn then +pos0:MarkToAll(string.format("Detour U-turn WP. Speed %.1f knots",UTILS.KmphToKnots(uspeedkmh))) +end +end +self.detour=true +self.carrier:Route(wp) +end +function AIRBOSS:CarrierTurnIntoWind(time,vdeck,uturn) +local _,vwind=self:GetWind() +local vdeck=UTILS.MpsToKnots(vdeck) +local hiw,speedknots=self:GetHeadingIntoWind(vdeck) +local vtot=UTILS.KnotsToMps(speedknots) +local dist=vtot*time +local distNM=UTILS.MetersToNM(dist) +local hdg=self:GetHeading() +local deltaH=self:_GetDeltaHeading(hdg,hiw) +self:I(self.lid..string.format("Carrier steaming into the wind (%.1f kts). Heading=%03d-->%03d (Delta=%.1f), Speed=%.1f knots, Distance=%.1f NM, Time=%d sec", +UTILS.MpsToKnots(vwind),hdg,hiw,deltaH,speedknots,distNM,speedknots,time)) +local Cv=self:GetCoordinate() +local Ctiw=nil +local Csoo=nil +if deltaH<45 then +Csoo=Cv:Translate(750,hdg):Translate(750,hiw) +local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo) +Ctiw=Csoo:Translate(dist,hsw) +elseif deltaH<90 then +Csoo=Cv:Translate(900,hdg):Translate(900,hiw) +local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo) +Ctiw=Csoo:Translate(dist,hsw) +elseif deltaH<135 then +Csoo=Cv:Translate(1100,hdg-90):Translate(1000,hiw) +local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo) +Ctiw=Csoo:Translate(dist,hsw) +else +Csoo=Cv:Translate(1200,hdg-90):Translate(1000,hiw) +local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo) +Ctiw=Csoo:Translate(dist,hsw) +end +self.Creturnto=self:GetCoordinate() +local nextwp=self:_GetNextWaypoint() +local vdownwind=UTILS.MpsToKnots(nextwp:GetVelocity()) +if vdownwind<1 then +vdownwind=10 +end +self:CarrierDetour(Ctiw,speedknots,uturn,vdownwind,Csoo) +self.turnintowind=true +return self +end +function AIRBOSS:_GetNextWaypoint() +local Nextwp=nil +if self.currentwp==#self.waypoints then +Nextwp=1 +else +Nextwp=self.currentwp+1 +end +local text=string.format("Current WP=%d/%d, next WP=%d",self.currentwp,#self.waypoints,Nextwp) +self:T2(self.lid..text) +local nextwp=self.waypoints[Nextwp] +return nextwp,Nextwp +end +function AIRBOSS:_InitWaypoints() +local Waypoints=self.carrier:GetGroup():GetTemplateRoutePoints() +self.waypoints={} +for i,point in ipairs(Waypoints)do +local coord=COORDINATE:New(point.x,point.alt,point.y) +coord:SetVelocity(point.speed) +table.insert(self.waypoints,coord) +if self.Debug then +coord:MarkToAll(string.format("Carrier Waypoint %d, Speed=%.1f knots",i,UTILS.MpsToKnots(point.speed))) +end +end +return self +end +function AIRBOSS:_PatrolRoute(n) +local nextWP,N=self:_GetNextWaypoint() +n=n or N +local CarrierGroup=self.carrier:GetGroup() +local Waypoints={} +local wp=self:GetCoordinate():WaypointGround(CarrierGroup:GetVelocityKMH()) +table.insert(Waypoints,wp) +for i=n,#self.waypoints do +local coord=self.waypoints[i] +local wp=coord:WaypointGround(UTILS.MpsToKmph(coord.Velocity)) +local TaskPassingWP=CarrierGroup:TaskFunction("AIRBOSS._PassingWaypoint",self,i,#self.waypoints) +CarrierGroup:SetTaskWaypoint(wp,TaskPassingWP) +table.insert(Waypoints,wp) +end +CarrierGroup:Route(Waypoints) +return self +end +function AIRBOSS:_GetETAatNextWP() +local cwp=self.currentwp +local tnow=timer.getAbsTime() +local p=self:GetCoordinate() +local v=self.carrier:GetVelocityMPS() +local nextWP=self:_GetNextWaypoint() +local s=p:Get2DDistance(nextWP) +local t=s/v +local eta=t+tnow +return eta +end +function AIRBOSS:_CheckCarrierTurning() +local vNew=self.carrier:GetOrientationX() +local vLast=self.Corientlast +vNew.y=0; +vLast.y=0 +local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) +self.Corientlast=vNew +local turning=math.abs(deltaLast)>=1 +if self.turning and not turning then +local FB=self:GetFinalBearing(true) +self:_MarshalCallNewFinalBearing(FB) +end +if turning and not self.turning then +local hdg +if self.turnintowind then +local vdeck=self.recoverywindow and self.recoverywindow.SPEED or 20 +hdg=self:GetHeadingIntoWind(vdeck,false) +else +hdg=self:GetCoordinate():HeadingTo(self:_GetNextWaypoint()) +end +hdg=hdg-self.magvar +if hdg<0 then +hdg=360+hdg +end +self:_MarshalCallCarrierTurnTo(hdg) +end +self.turning=turning +end +function AIRBOSS:_CheckPatternUpdate() +local dTPupdate=10*60 +local Dupdate=UTILS.NMToMeters(2.5) +local Hupdate=5 +local dt=timer.getTime()-self.Tpupdate +if dt=Hupdate then +self:T(self.lid..string.format("Carrier heading changed by %d°.",deltaHeading)) +Hchange=true +end +local pos=self:GetCoordinate() +local dist=pos:Get2DDistance(self.Cposition) +local Dchange=false +if dist>=Dupdate then +self:T(self.lid..string.format("Carrier position changed by %.1f NM.",UTILS.MetersToNM(dist))) +Dchange=true +end +if Hchange or Dchange then +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +if flight.ai then +self:_MarshalAI(flight,flight.flag) +end +end +self.Corientation=vNew +self.Cposition=pos +self.Tpupdate=timer.getTime() +end +end +function AIRBOSS._PassingWaypoint(group,airboss,i,final) +local text=string.format("Group %s passing waypoint %d of %d.",group:GetName(),i,final) +if airboss.Debug and false then +local pos=group:GetCoordinate() +pos:SmokeRed() +local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d",group:GetName(),i)) +end +MESSAGE:New(text,10):ToAllIf(airboss.Debug) +airboss:T(airboss.lid..text) +airboss.currentwp=i +airboss:PassingWaypoint(i) +if i==final and final>1 and airboss.adinfinitum then +airboss:_PatrolRoute() +end +end +function AIRBOSS._ResumeRoute(group,airboss,gotocoord) +local nextwp,Nextwp=airboss:_GetNextWaypoint() +local speedkmh=nextwp.Velocity*3.6 +if speedkmh<1 then +speedkmh=UTILS.KnotsToKmph(10) +end +local waypoints={} +local c0=group:GetCoordinate() +local wp0=c0:WaypointGround(speedkmh) +table.insert(waypoints,wp0) +if gotocoord then +local headingto=c0:HeadingTo(gotocoord) +local hdg1=airboss:GetHeading() +local hdg2=c0:HeadingTo(gotocoord) +local delta=airboss:_GetDeltaHeading(hdg1,hdg2) +if delta>90 then +local turnradius=UTILS.NMToMeters(3) +local gotocoordh=c0:Translate(turnradius,hdg1+45) +local wp=gotocoordh:WaypointGround(speedkmh) +table.insert(waypoints,wp) +gotocoordh=c0:Translate(turnradius,hdg1+90) +wp=gotocoordh:WaypointGround(speedkmh) +table.insert(waypoints,wp) +end +local wp1=gotocoord:WaypointGround(speedkmh) +table.insert(waypoints,wp1) +end +local text=string.format("Carrier is resuming route. Next waypoint %d, Speed=%.1f knots.",Nextwp,UTILS.KmphToKnots(speedkmh)) +MESSAGE:New(text,10):ToAllIf(airboss.Debug) +airboss:I(airboss.lid..text) +for i=Nextwp,#airboss.waypoints do +local coord=airboss.waypoints[i] +local speed=coord.Velocity*3.6 +if speed<1 then +speed=UTILS.KnotsToKmph(10) +end +local wp=coord:WaypointGround(speed) +local TaskPassingWP=group:TaskFunction("AIRBOSS._PassingWaypoint",airboss,i,#airboss.waypoints) +group:SetTaskWaypoint(wp,TaskPassingWP) +table.insert(waypoints,wp) +end +airboss.turnintowind=false +airboss.detour=false +group:Route(waypoints) +end +function AIRBOSS._ReachedHoldingZone(group,airboss,flight) +local text=string.format("Flight %s reached holding zone.",group:GetName()) +MESSAGE:New(text,10):ToAllIf(airboss.Debug) +airboss:T(airboss.lid..text) +if airboss.Debug then +group:GetCoordinate():MarkToAll(text) +end +if flight then +flight.holding=true +flight.time=timer.getAbsTime() +end +end +function AIRBOSS._TaskFunctionMarshalAI(group,airboss,flight) +local text=string.format("Flight %s is send to marshal.",group:GetName()) +MESSAGE:New(text,10):ToAllIf(airboss.Debug) +airboss:T(airboss.lid..text) +local stack=airboss:_GetFreeStack(flight.ai) +if stack then +airboss:_MarshalAI(flight,stack) +else +if not airboss:_InQueue(airboss.Qwaiting,flight.group)then +airboss:_WaitAI(flight) +end +end +if flight.refueling==true then +airboss:I(airboss.lid..string.format("Flight group %s finished refueling task.",flight.groupname)) +end +flight.refueling=false +end +function AIRBOSS:_GetACNickname(actype) +local nickname="unknown" +if actype==AIRBOSS.AircraftCarrier.A4EC then +nickname="Skyhawk" +elseif actype==AIRBOSS.AircraftCarrier.T45C then +nickname="Goshawk" +elseif actype==AIRBOSS.AircraftCarrier.AV8B then +nickname="Harrier" +elseif actype==AIRBOSS.AircraftCarrier.E2D then +nickname="Hawkeye" +elseif actype==AIRBOSS.AircraftCarrier.C2A then +nickname="Greyhound" +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B then +nickname="Tomcat" +elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then +nickname="Hornet" +elseif actype==AIRBOSS.AircraftCarrier.RHINOE or actype==AIRBOSS.AircraftCarrier.RHINOF then +nickname="Rhino" +elseif actype==AIRBOSS.AircraftCarrier.GROWLER then +nickname="Growler" +elseif actype==AIRBOSS.AircraftCarrier.S3B or actype==AIRBOSS.AircraftCarrier.S3BTANKER then +nickname="Viking" +end +return nickname +end +function AIRBOSS:_GetOnboardNumberPlayer(group) +return self:_GetOnboardNumbers(group,true) +end +function AIRBOSS:_GetOnboardNumbers(group,playeronly) +local groupname=group:GetName() +local text=string.format("Onboard numbers of group %s:",groupname) +local template=group:GetTemplate() +local numbers={} +if template then +local units=template.units +for _,unit in pairs(units)do +local n=tostring(unit.onboard_num) +local name=unit.name +local skill=unit.skill or"Unknown" +text=text..string.format("\n- unit %s: onboard #=%s skill=%s",name,n,tostring(skill)) +if playeronly and skill=="Client"or skill=="Player"then +return n +end +numbers[name]=n +end +self:T2(self.lid..text) +else +if playeronly then +return 101 +else +local units=group:GetUnits() +for i,_unit in pairs(units)do +local name=_unit:GetName() +numbers[name]=100+i +end +end +end +return numbers +end +function AIRBOSS:_GetTowerFrequency() +self.TowerFreq=0 +local striketemplate=self.carrier:GetGroup():GetTemplate() +for _,unit in pairs(striketemplate.units)do +if self.carrier:GetName()==unit.name then +self.TowerFreq=unit.frequency/1000000 +return +end +end +end +function AIRBOSS:_GetGoodBadScore(playerData) +local lowscore +local badscore +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +lowscore=10 +badscore=20 +elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then +lowscore=5 +badscore=10 +elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then +lowscore=2.5 +badscore=5 +end +return lowscore,badscore +end +function AIRBOSS:_IsCarrierAircraft(unit) +local aircrafttype=unit:GetTypeName() +if aircrafttype==AIRBOSS.AircraftCarrier.AV8B then +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +return true +else +return false +end +end +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then +return false +end +end +for _,actype in pairs(AIRBOSS.AircraftCarrier)do +if actype==aircrafttype then +return true +end +end +return false +end +function AIRBOSS:_IsHumanUnit(unit) +local playerunit=self:_GetPlayerUnitAndName(unit:GetName()) +if playerunit then +return true +else +return false +end +end +function AIRBOSS:_IsHuman(group) +local units=group:GetUnits() +for _,_unit in pairs(units)do +local human=self:_IsHumanUnit(_unit) +if human then +return true +end +end +return false +end +function AIRBOSS:_GetFuelState(unit) +local fuel=unit:GetFuel() +local maxfuel=self:_GetUnitMasses(unit) +local fuelstate=fuel*maxfuel +self:T2(self.lid..string.format("Unit %s fuel state = %.1f kg = %.1f lbs",unit:GetName(),fuelstate,UTILS.kg2lbs(fuelstate))) +return UTILS.kg2lbs(fuelstate) +end +function AIRBOSS:_GetAngels(alt) +if alt then +local angels=UTILS.Round(UTILS.MetersToFeet(alt)/1000,0) +return angels +else +return 0 +end +end +function AIRBOSS:_GetUnitMasses(unit) +local Desc=unit:GetDesc() +local massfuel=Desc.fuelMassMax or 0 +local massempty=Desc.massEmpty or 0 +local massmax=Desc.massMax or 0 +local masscargo=massmax-massfuel-massempty +self:T2(self.lid..string.format("Unit %s mass fuel=%.1f kg, empty=%.1f kg, max=%.1f kg, cargo=%.1f kg",unit:GetName(),massfuel,massempty,massmax,masscargo)) +return massfuel,massempty,massmax,masscargo +end +function AIRBOSS:_GetPlayerDataUnit(unit) +if unit:IsAlive()then +local unitname=unit:GetName() +local playerunit,playername=self:_GetPlayerUnitAndName(unitname) +if playerunit and playername then +return self.players[playername] +end +end +return nil +end +function AIRBOSS:_GetPlayerDataGroup(group) +local units=group:GetUnits() +for _,unit in pairs(units)do +local playerdata=self:_GetPlayerDataUnit(unit) +if playerdata then +return playerdata +end +end +return nil +end +function AIRBOSS:_GetPlayerUnit(_unitName) +for _,_player in pairs(self.players)do +local player=_player +if player.unit and player.unit:GetName()==_unitName then +self:T(self.lid..string.format("Found player=%s unit=%s in players table.",tostring(player.name),tostring(_unitName))) +return player.unit,player.name +end +end +return nil,nil +end +function AIRBOSS:_GetPlayerUnitAndName(_unitName) +self:F2(_unitName) +if _unitName~=nil then +local u,pn=self:_GetPlayerUnit(_unitName) +if u and pn then +return u,pn +end +local DCSunit=Unit.getByName(_unitName) +if DCSunit and DCSunit.getPlayerName then +local playername=DCSunit:getPlayerName() +local unit=UNIT:Find(DCSunit) +self:T2({DCSunit=DCSunit,unit=unit,playername=playername}) +if DCSunit and unit and playername then +self:T(self.lid..string.format("Found DCS unit %s with player %s.",tostring(_unitName),tostring(playername))) +return unit,playername +end +end +end +return nil,nil +end +function AIRBOSS:GetCoalition() +return self.carrier:GetCoalition() +end +function AIRBOSS:GetCoordinate() +return self.carrier:GetCoord() +end +function AIRBOSS:GetCoord() +return self.carrier:GetCoord() +end +function AIRBOSS:_GetStaticWeather() +local weather=env.mission.weather +local clouds=weather.clouds +local visibility=weather.visibility.distance +local dust=nil +if weather.enable_dust==true then +dust=weather.dust_density +end +local fog=nil +if weather.enable_fog==true then +fog=weather.fog +end +return clouds,visibility,fog,dust +end +function AIRBOSS._CheckRadioQueueT(param,time) +AIRBOSS._CheckRadioQueue(param.airboss,param.radioqueue,param.name) +return time+0.05 +end +function AIRBOSS:_CheckRadioQueue(radioqueue,name) +if#radioqueue==0 then +if name=="LSO"then +self:T(self.lid..string.format("Stopping LSO radio queue.")) +self.radiotimer:Stop(self.RQLid) +self.RQLid=nil +elseif name=="MARSHAL"then +self:T(self.lid..string.format("Stopping Marshal radio queue.")) +self.radiotimer:Stop(self.RQMid) +self.RQMid=nil +end +return +end +local _time=timer.getAbsTime() +local playing=false +local next=nil +local _remove=nil +for i,_transmission in ipairs(radioqueue)do +local transmission=_transmission +if _time>=transmission.Tplay then +if transmission.isplaying then +if _time>=transmission.Tstarted+transmission.call.duration then +transmission.isplaying=false +_remove=i +if transmission.radio.alias=="LSO"then +self.TQLSO=_time +elseif transmission.radio.alias=="MARSHAL"then +self.TQMarshal=_time +end +else +playing=true +end +else +local Tlast=nil +if transmission.interval then +if transmission.radio.alias=="LSO"then +Tlast=self.TQLSO +elseif transmission.radio.alias=="MARSHAL"then +Tlast=self.TQMarshal +end +end +if transmission.interval==nil then +if next==nil then +next=transmission +end +else +if _time-Tlast>=transmission.interval then +next=transmission +else +end +end +if next or Tlast then +break +end +end +else +end +end +if next~=nil and not playing then +self:Broadcast(next.radio,next.call,next.loud) +next.isplaying=true +next.Tstarted=_time +end +if _remove then +table.remove(radioqueue,_remove) +end +return +end +function AIRBOSS:RadioTransmission(radio,call,loud,delay,interval,click,pilotcall) +self:F2({radio=radio,call=call,loud=loud,delay=delay,interval=interval,click=click}) +if radio==nil or call==nil then +return +end +if not self.SRS then +local transmission={} +transmission.radio=radio +transmission.call=call +transmission.Tplay=timer.getAbsTime()+(delay or 0) +transmission.interval=interval +transmission.isplaying=false +transmission.Tstarted=nil +transmission.loud=loud and call.loud +if self:_IsOnboard(call.modexsender)then +self:_Number2Radio(radio,call.modexsender,delay,0.3,pilotcall) +end +if self:_IsOnboard(call.modexreceiver)then +self:_Number2Radio(radio,call.modexreceiver,delay,0.3,pilotcall) +end +local caller="" +if radio.alias=="LSO"then +table.insert(self.RQLSO,transmission) +caller="LSOCall" +if not self.RQLid then +self:T(self.lid..string.format("Starting LSO radio queue.")) +self.RQLid=self.radiotimer:Schedule(nil,AIRBOSS._CheckRadioQueue,{self,self.RQLSO,"LSO"},0.02,0.05) +end +elseif radio.alias=="MARSHAL"then +table.insert(self.RQMarshal,transmission) +caller="MarshalCall" +if not self.RQMid then +self:T(self.lid..string.format("Starting Marhal radio queue.")) +self.RQMid=self.radiotimer:Schedule(nil,AIRBOSS._CheckRadioQueue,{self,self.RQMarshal,"MARSHAL"},0.02,0.05) +end +end +if click then +self:RadioTransmission(radio,self[caller].CLICK,false,delay) +end +else +if call.subtitle~=nil and string.len(call.subtitle)>1 then +local frequency=self.MarshalRadio.frequency +local modulation=self.MarshalRadio.modulation +local voice=nil +local gender=nil +local culture=nil +if radio.alias=="AIRBOSS"then +frequency=self.AirbossRadio.frequency +modulation=self.AirbossRadio.modulation +voice=self.AirbossRadio.voice +gender=self.AirbossRadio.gender +culture=self.AirbossRadio.culture +end +if radio.alias=="MARSHAL"then +voice=self.MarshalRadio.voice +gender=self.MarshalRadio.gender +culture=self.MarshalRadio.culture +end +if radio.alias=="LSO"then +frequency=self.LSORadio.frequency +modulation=self.LSORadio.modulation +voice=self.LSORadio.voice +gender=self.LSORadio.gender +culture=self.LSORadio.culture +end +if pilotcall then +voice=self.PilotRadio.voice +gender=self.PilotRadio.gender +culture=self.PilotRadio.culture +radio.alias="PILOT" +end +if not radio.alias then +frequency=self.AirbossRadio.frequency +modulation=self.AirbossRadio.modulation +radio.alias="AIRBOSS" +end +local volume=nil +if loud then +volume=1.0 +end +local text=call.subtitle +self:T(self.lid..text) +local srstext=self:_GetNiceSRSText(text) +self.SRSQ:NewTransmission(srstext,call.duration,self.SRS,nil,0.1,nil,call.subtitle,call.subduration,frequency,modulation,gender,culture,voice,volume,radio.alias) +end +end +end +function AIRBOSS:SetSRSPilotVoice(Voice,Gender,Culture) +self.PilotRadio={} +self.PilotRadio.alias="PILOT" +self.PilotRadio.voice=Voice or MSRS.Voices.Microsoft.David +self.PilotRadio.gender=Gender or"male" +self.PilotRadio.culture=Culture or"en-US" +if(not Voice)and self.SRS and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then +self.PilotRadio.voice=MSRS.Voices.Google.Standard.en_US_Standard_J +end +return self +end +function AIRBOSS:_NeedsSubtitle(call) +if call.file==self.MarshalCall.NOISE.file or call.file==self.LSOCall.NOISE.file then +return true +else +return false +end +end +function AIRBOSS:Broadcast(radio,call,loud) +self:F(call) +if not self.usersoundradio then +local sender=self:_GetRadioSender(radio) +local filename=self:_RadioFilename(call,loud,radio.alias) +local subtitle=self:_RadioSubtitle(radio,call,loud) +self:T({filename=filename,subtitle=subtitle}) +if sender then +self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName())) +local commandFrequency={ +id="SetFrequency", +params={ +frequency=radio.frequency*1000000, +modulation=radio.modulation, +}, +} +local commandTransmit={ +id="TransmitMessage", +params={ +file=filename, +duration=call.subduration or 5, +subtitle=subtitle, +loop=false, +}, +} +sender:SetCommand(commandFrequency) +sender:SetCommand(commandTransmit) +else +self:T(self.lid..string.format("Broadcasting from carrier via trigger.action.radioTransmission().")) +local vec3=self.carrier:GetPositionVec3() +trigger.action.radioTransmission(filename,vec3,radio.modulation,false,radio.frequency*1000000,100) +for _,_player in pairs(self.players)do +local playerData=_player +if playerData.unit:IsInZone(self.zoneCCA)and playerData.actype~=AIRBOSS.AircraftCarrier.A4EC then +if playerData.subtitles or self:_NeedsSubtitle(call)then +if radio.alias=="MARSHAL"or(radio.alias=="LSO"and self:_InQueue(self.Qpattern,playerData.group))then +self:MessageToPlayer(playerData,subtitle,nil,"",call.subduration or 5) +end +end +end +end +end +end +for _,_player in pairs(self.players)do +local playerData=_player +if self.usersoundradio or playerData.actype==AIRBOSS.AircraftCarrier.A4EC then +if radio.alias=="MARSHAL"or(radio.alias=="LSO"and self:_InQueue(self.Qpattern,playerData.group))then +self:Sound2Player(playerData,radio,call,loud) +end +end +end +end +function AIRBOSS:Sound2Player(playerData,radio,call,loud,delay) +if playerData.unit:IsInZone(self.zoneCCA)and call then +local filename=self:_RadioFilename(call,loud,radio.alias) +local subtitle=self:_RadioSubtitle(radio,call,loud) +USERSOUND:New(filename):ToGroup(playerData.group,delay) +if playerData.subtitles or self:_NeedsSubtitle(call)then +self:MessageToPlayer(playerData,subtitle,nil,"",call.subduration,false,delay) +end +end +end +function AIRBOSS:_RadioSubtitle(radio,call,loud) +if call==nil or call.subtitle==nil or call.subtitle==""then +return"" +end +local sender=call.sender or radio.alias +if call.modexsender then +sender=call.modexsender +end +local receiver=call.modexreceiver or"" +local subtitle=string.format("%s: %s",sender,call.subtitle) +if receiver and receiver~=""then +subtitle=string.format("%s: %s, %s",sender,receiver,call.subtitle) +end +local lastchar=string.sub(subtitle,-1) +if loud then +if lastchar=="."or lastchar=="!"then +subtitle=string.sub(subtitle,1,-1) +end +subtitle=subtitle.."!" +else +if lastchar=="!"then +elseif lastchar=="."then +else +subtitle=subtitle.."." +end +end +return subtitle +end +function AIRBOSS:_RadioFilename(call,loud,channel) +local prefix=call.file or"" +local suffix=call.suffix or"ogg" +local path=self.soundfolder or"l10n/DEFAULT/" +if string.find(call.file,"LSO-")and channel and(channel=="LSO"or channel=="LSOCall")then +path=self.soundfolderLSO or path +end +if string.find(call.file,"MARSHAL-")and channel and(channel=="MARSHAL"or channel=="MarshalCall")then +path=self.soundfolderMSH or path +end +if loud then +prefix=prefix.."_Loud" +end +local filename=string.format("%s%s.%s",path,prefix,suffix) +return filename +end +function AIRBOSS:_GetNiceSRSText(text) +text=string.gsub(text,"================================\n","") +text=string.gsub(text,"||","parallel") +text=string.gsub(text,"==","perpendicular") +text=string.gsub(text,"BRC","Base recovery") +text=string.gsub(text,"%((%a+)%)","Morse %1") +text=string.gsub(text,"°C","° Celsius") +text=string.gsub(text,"°"," degrees") +text=string.gsub(text," FB "," Final bearing ") +text=string.gsub(text," ops"," operations ") +text=string.gsub(text," kts"," knots") +text=string.gsub(text,"TACAN","Tackan") +text=string.gsub(text,"ICLS","I.C.L.S.") +text=string.gsub(text,"LSO","L.S.O.") +text=string.gsub(text,"inHg","inches of Mercury") +text=string.gsub(text,"QFE","Q.F.E.") +text=string.gsub(text,"hPa","hecto pascal") +text=string.gsub(text," NM"," nautical miles") +text=string.gsub(text," ft"," feet") +text=string.gsub(text,"A/C","aircraft") +text=string.gsub(text,"(#[%a%d%p%s]+)\n","") +text=string.gsub(text,"%.000"," dot zero") +text=string.gsub(text,"00"," double zero") +text=string.gsub(text," 0 "," zero ") +text=string.gsub(text,"\n","; ") +return text +end +function AIRBOSS:MessageToPlayer(playerData,message,sender,receiver,duration,clear,delay) +self:T({sender,receiver,message}) +if playerData and message and message~=""then +duration=duration or self.Tmessage +local text +if receiver and receiver==""then +text=string.format("%s",message) +else +receiver=receiver or playerData.onboard +text=string.format("%s, %s",receiver,message) +end +self:T(self.lid..text) +if delay and delay>0 then +self:ScheduleOnce(delay,self.MessageToPlayer,self,playerData,message,sender,receiver,duration,clear) +else +if not self.SRS then +local wait=0 +if receiver==playerData.onboard then +if sender and(sender=="LSO"or sender=="MARSHAL"or sender=="AIRBOSS")then +wait=wait+self:_Number2Sound(playerData,sender,receiver) +end +end +if string.find(text:lower(),"negative")then +local filename=self:_RadioFilename(self.MarshalCall.NEGATIVE,false,"MARSHAL") +USERSOUND:New(filename):ToGroup(playerData.group,wait) +wait=wait+self.MarshalCall.NEGATIVE.duration +end +if string.find(text:lower(),"affirm")then +local filename=self:_RadioFilename(self.MarshalCall.AFFIRMATIVE,false,"MARSHAL") +USERSOUND:New(filename):ToGroup(playerData.group,wait) +wait=wait+self.MarshalCall.AFFIRMATIVE.duration +end +if string.find(text:lower(),"roger")then +local filename=self:_RadioFilename(self.MarshalCall.ROGER,false,"MARSHAL") +USERSOUND:New(filename):ToGroup(playerData.group,wait) +wait=wait+self.MarshalCall.ROGER.duration +end +if wait>0 then +local filename=self:_RadioFilename(self.MarshalCall.CLICK) +USERSOUND:New(filename):ToGroup(playerData.group,wait) +end +else +local frequency=self.MarshalRadio.frequency +local modulation=self.MarshalRadio.modulation +local voice=self.MarshalRadio.voice +local gender=self.MarshalRadio.gender +local culture=self.MarshalRadio.culture +if not sender then sender="AIRBOSS"end +if string.find(sender,"AIRBOSS")then +frequency=self.AirbossRadio.frequency +modulation=self.AirbossRadio.modulation +voice=self.AirbossRadio.voice +gender=self.AirbossRadio.gender +culture=self.AirbossRadio.culture +end +if sender=="LSO"then +frequency=self.LSORadio.frequency +modulation=self.LSORadio.modulation +voice=self.LSORadio.voice +gender=self.LSORadio.gender +culture=self.LSORadio.culture +end +self:T(self.lid..text) +self:T({sender,frequency,modulation,voice}) +local srstext=self:_GetNiceSRSText(text) +self.SRSQ:NewTransmission(srstext,duration,self.SRS,nil,0.1,nil,nil,nil,frequency,modulation,gender,culture,voice,nil,sender) +end +if playerData.client then +MESSAGE:New(text,duration,sender,clear):ToClient(playerData.client) +end +end +end +end +function AIRBOSS:MessageToPattern(message,sender,receiver,duration,clear,delay) +local call=self:_NewRadioCall(self.LSOCall.NOISE,sender or"LSO",message,duration,receiver,sender) +self:RadioTransmission(self.LSORadio,call,false,delay,nil,true) +end +function AIRBOSS:MessageToMarshal(message,sender,receiver,duration,clear,delay) +local call=self:_NewRadioCall(self.MarshalCall.NOISE,sender or"MARSHAL",message,duration,receiver,sender) +self:RadioTransmission(self.MarshalRadio,call,false,delay,nil,true) +end +function AIRBOSS:_NewRadioCall(call,sender,subtitle,subduration,modexreceiver,modexsender) +local newcall=UTILS.DeepCopy(call) +newcall.sender=sender +newcall.subtitle=subtitle or call.subtitle +newcall.subduration=subduration or self.Tmessage +if self:_IsOnboard(modexreceiver)then +newcall.modexreceiver=modexreceiver +end +if self:_IsOnboard(modexsender)then +newcall.modexsender=modexsender +end +return newcall +end +function AIRBOSS:_GetRadioSender(radio) +local sender=nil +if self.senderac then +sender=UNIT:FindByName(self.senderac) +end +if radio.alias=="MARSHAL"then +if self.radiorelayMSH then +sender=UNIT:FindByName(self.radiorelayMSH) +end +end +if radio.alias=="LSO"then +if self.radiorelayLSO then +sender=UNIT:FindByName(self.radiorelayLSO) +end +end +if sender and sender:IsAlive()and sender:IsAir()then +return sender +end +return nil +end +function AIRBOSS:_IsOnboard(text) +if text==nil then +return false +end +if text=="99"then +return true +end +for _,_flight in pairs(self.flights)do +local flight=_flight +for _,onboard in pairs(flight.onboardnumbers)do +if text==onboard then +return true +end +end +end +return false +end +function AIRBOSS:_Number2Sound(playerData,sender,number,delay) +delay=delay or 0 +local function _split(str) +local chars={} +for i=1,#str do +local c=str:sub(i,i) +table.insert(chars,c) +end +return chars +end +local Sender +if sender=="LSO"then +Sender="LSOCall" +elseif sender=="MARSHAL"or sender=="AIRBOSS"then +Sender="MarshalCall" +else +self:E(self.lid..string.format("ERROR: Unknown radio sender %s!",tostring(sender))) +return +end +local numbers=_split(tostring(number)) +local wait=0 +for i=1,#numbers do +local n=numbers[i] +local N=string.format("N%s",n) +local call=self[Sender][N] +local filename=self:_RadioFilename(call,false,Sender) +USERSOUND:New(filename):ToGroup(playerData.group,delay+wait) +wait=wait+call.duration +end +return wait +end +function AIRBOSS:_Number2Radio(radio,number,delay,interval,pilotcall) +local function _split(str) +local chars={} +for i=1,#str do +local c=str:sub(i,i) +table.insert(chars,c) +end +return chars +end +local Sender="" +if radio.alias=="LSO"then +Sender="LSOCall" +elseif radio.alias=="MARSHAL"then +Sender="MarshalCall" +else +self:E(self.lid..string.format("ERROR: Unknown radio alias %s!",tostring(radio.alias))) +end +if pilotcall then +Sender="PilotCall" +end +if Sender==""then +self:E(self.lid..string.format("ERROR: Sender unknown!")) +return +end +local numbers=_split(tostring(number)) +local wait=0 +for i=1,#numbers do +local n=numbers[i] +local N=string.format("N%s",n) +local call=self[Sender][N] +if interval and i==1 then +self:RadioTransmission(radio,call,false,delay,interval) +else +self:RadioTransmission(radio,call,false,delay) +end +wait=wait+call.duration +end +return wait +end +function AIRBOSS:_MarshallInboundCall(unit,modex) +local vectorCarrier=self:GetCoordinate():GetDirectionVec3(unit:GetCoordinate()) +local bearing=UTILS.Round(unit:GetCoordinate():GetAngleDegrees(vectorCarrier),0) +local distance=UTILS.Round(UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())),0) +local angels=UTILS.Round(UTILS.MetersToFeet(unit:GetHeight()/1000),0) +local state=UTILS.Round(self:_GetFuelState(unit)/1000,1) +local text=string.format("Marshal, %s, marking mom's %d for %d, angels %d, state %.1f",modex,bearing,distance,angels,state) +self:T(self.lid..text) +local FS=UTILS.Split(string.format("%.1f",state),".") +local inboundcall=self:_NewRadioCall(self.MarshalCall.CLICK,unit.UnitName:upper(),text,self.Tmessage,nil,unit.UnitName:upper()) +self:RadioTransmission(self.MarshalRadio,inboundcall) +self:RadioTransmission(self.MarshalRadio,self.PilotCall.MARSHAL,nil,nil,nil,nil,true) +self:_Number2Radio(self.MarshalRadio,modex,nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.PilotCall.MARKINGMOMS,nil,nil,nil,nil,true) +self:_Number2Radio(self.MarshalRadio,tostring(bearing),nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.PilotCall.FOR,nil,nil,nil,nil,true) +self:_Number2Radio(self.MarshalRadio,tostring(distance),nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.PilotCall.ANGELS,nil,nil,nil,nil,true) +self:_Number2Radio(self.MarshalRadio,tostring(angels),nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.PilotCall.STATE,nil,nil,nil,nil,true) +self:_Number2Radio(self.MarshalRadio,FS[1],nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.PilotCall.POINT,nil,nil,nil,nil,true) +self:_Number2Radio(self.MarshalRadio,FS[2],nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.MarshalRadio.CLICK,nil,nil,nil,nil,true) +end +function AIRBOSS:_CommencingCall(unit,modex) +local text=string.format("%s, commencing",modex) +self:T(self.lid..text) +local commencingCall=self:_NewRadioCall(self.MarshalCall.CLICK,unit.UnitName:upper(),text,self.Tmessage,nil,unit.UnitName:upper()) +self:RadioTransmission(self.MarshalRadio,commencingCall) +self:_Number2Radio(self.MarshalRadio,modex,nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.PilotCall.COMMENCING,nil,nil,nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.MarshalRadio.CLICK,nil,nil,nil,nil,true) +end +function AIRBOSS:_LSOCallAircraftBall(modex,nickname,fuelstate) +local text=string.format("%s Ball, %.1f.",nickname,fuelstate) +self:T(self.lid..text) +local NICKNAME=nickname:upper() +local FS=UTILS.Split(string.format("%.1f",fuelstate),".") +local call=self:_NewRadioCall(self.PilotCall[NICKNAME],modex,text,self.Tmessage,nil,modex) +self:RadioTransmission(self.LSORadio,call,nil,nil,nil,nil,true) +self:RadioTransmission(self.LSORadio,self.PilotCall.BALL,nil,nil,nil,nil,true) +self:_Number2Radio(self.LSORadio,FS[1],nil,nil,true) +self:RadioTransmission(self.LSORadio,self.PilotCall.POINT,nil,nil,nil,nil,true) +self:_Number2Radio(self.LSORadio,FS[2],nil,nil,true) +self:RadioTransmission(self.LSORadio,self.LSOCall.CLICK) +end +function AIRBOSS:_MarshalCallGasAtTanker(modex) +local text=string.format("Bingo fuel! Going for gas at the recovery tanker.") +self:T(self.lid..text) +local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL,modex,text,self.Tmessage,nil,modex) +self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.PilotCall.GASATTANKER,nil,nil,nil,true,true) +end +function AIRBOSS:_MarshalCallGasAtDivert(modex,divertname) +local text=string.format("Bingo fuel! Going for gas at divert field %s.",divertname) +self:T(self.lid..text) +local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL,modex,text,self.Tmessage,nil,modex) +self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,nil,true) +self:RadioTransmission(self.MarshalRadio,self.PilotCall.GASATDIVERT,nil,nil,nil,true,true) +end +function AIRBOSS:_MarshalCallRecoveryStopped(case) +local text=string.format("Case %d recovery ops are stopped. Deck is closed.",case) +self:T(self.lid..text) +local call=self:_NewRadioCall(self.MarshalCall.CASE,"AIRBOSS",text,self.Tmessage,"99") +self:RadioTransmission(self.MarshalRadio,call) +self:_Number2Radio(self.MarshalRadio,tostring(case)) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RECOVERYOPSSTOPPED,nil,nil,0.2) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DECKCLOSED,nil,nil,nil,true) +end +function AIRBOSS:_MarshalCallRecoveryPausedUntilFurtherNotice() +local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDNOTICE,"AIRBOSS",nil,self.Tmessage,"99") +self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true) +end +function AIRBOSS:_MarshalCallRecoveryPausedResumedAt(clock) +local _clock=UTILS.Split(clock,"+") +local CT=UTILS.Split(_clock[1],":") +local text=string.format("aircraft recovery is paused and will be resumed at %s.",clock) +self:T(self.lid..text) +local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDRESUMED,"AIRBOSS",text,self.Tmessage,"99") +self:RadioTransmission(self.MarshalRadio,call) +self:_Number2Radio(self.MarshalRadio,CT[1]) +self:_Number2Radio(self.MarshalRadio,CT[2]) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOURS,nil,nil,nil,true) +end +function AIRBOSS:_MarshalCallClearedForRecovery(modex,case) +local text=string.format("you're cleared for Case %d recovery.",case) +self:T(self.lid..text) +local call=self:_NewRadioCall(self.MarshalCall.CLEAREDFORRECOVERY,"MARSHAL",text,self.Tmessage,modex) +local delay=2 +self:RadioTransmission(self.MarshalRadio,call,nil,delay) +self:_Number2Radio(self.MarshalRadio,tostring(case),delay) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RECOVERY,nil,delay,nil,true) +end +function AIRBOSS:_MarshalCallResumeRecovery() +local call=self:_NewRadioCall(self.MarshalCall.RESUMERECOVERY,"AIRBOSS",nil,self.Tmessage,"99") +self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true) +end +function AIRBOSS:_MarshalCallNewFinalBearing(FB) +local text=string.format("new final bearing %03d°.",FB) +self:T(self.lid..text) +local call=self:_NewRadioCall(self.MarshalCall.NEWFB,"AIRBOSS",text,self.Tmessage,"99") +self:RadioTransmission(self.MarshalRadio,call) +self:_Number2Radio(self.MarshalRadio,string.format("%03d",FB),nil,0.2) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true) +end +function AIRBOSS:_MarshalCallCarrierTurnTo(hdg) +local text=string.format("carrier is now starting turn to heading %03d°.",hdg) +self:T(self.lid..text) +local call=self:_NewRadioCall(self.MarshalCall.CARRIERTURNTOHEADING,"AIRBOSS",text,self.Tmessage,"99") +self:RadioTransmission(self.MarshalRadio,call) +self:_Number2Radio(self.MarshalRadio,string.format("%03d",hdg),nil,0.2) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true) +end +function AIRBOSS:_MarshalCallStackFull(modex,nwaiting) +local text=string.format("Marshal stack is currently full. Hold outside 10 NM zone and wait for further instructions. ") +if nwaiting==1 then +text=text..string.format("There is one flight ahead of you.") +elseif nwaiting>1 then +text=text..string.format("There are %d flights ahead of you.",nwaiting) +else +text=text..string.format("You are next in line.") +end +self:T(self.lid..text) +local call=self:_NewRadioCall(self.MarshalCall.STACKFULL,"AIRBOSS",text,self.Tmessage,modex) +self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true) +end +function AIRBOSS:_MarshalCallRecoveryStart(case) +local radial=self:GetRadial(case,true,true,false) +local text=string.format("Starting aircraft recovery Case %d ops.",case) +if case==1 then +text=text..string.format(" BRC %03d°.",self:GetBRC()) +elseif case==2 then +text=text..string.format(" Marshal radial %03d°. BRC %03d°.",radial,self:GetBRC()) +elseif case==3 then +text=text..string.format(" Marshal radial %03d°. Final heading %03d°.",radial,self:GetFinalBearing(false)) +end +self:T(self.lid..text) +local call=self:_NewRadioCall(self.MarshalCall.STARTINGRECOVERY,"AIRBOSS",text,self.Tmessage,"99") +self:RadioTransmission(self.MarshalRadio,call) +self:_Number2Radio(self.MarshalRadio,tostring(case),nil,0.1) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.OPS) +if case>1 then +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.MARSHALRADIAL) +self:_Number2Radio(self.MarshalRadio,string.format("%03d",radial),nil,0.2) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true) +end +end +function AIRBOSS:_MarshalCallArrived(modex,case,brc,altitude,charlie,qfe) +self:F({modex=modex,case=case,brc=brc,altitude=altitude,charlie=charlie,qfe=qfe}) +local angels=self:_GetAngels(altitude) +local QFE=UTILS.Split(string.format("%.2f",qfe),".") +local clock=UTILS.Split(charlie,"+") +local CT=UTILS.Split(clock[1],":") +local text=string.format("Case %d, expected BRC %03d°, hold at angels %d. Expected Charlie Time %s. Altimeter %.2f. Report see me.",case,brc,angels,charlie,qfe) +self:T(self.lid..text) +local casecall=self:_NewRadioCall(self.MarshalCall.CASE,"MARSHAL",text,self.Tmessage,modex) +self:RadioTransmission(self.MarshalRadio,casecall) +self:_Number2Radio(self.MarshalRadio,tostring(case)) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.EXPECTED,nil,nil,0.5) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.BRC) +self:_Number2Radio(self.MarshalRadio,string.format("%03d",brc)) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOLDATANGELS,nil,nil,0.5) +self:_Number2Radio(self.MarshalRadio,tostring(angels)) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.EXPECTED,nil,nil,0.5) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.CHARLIETIME) +self:_Number2Radio(self.MarshalRadio,CT[1]) +self:_Number2Radio(self.MarshalRadio,CT[2]) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOURS) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.ALTIMETER,nil,nil,0.5) +self:_Number2Radio(self.MarshalRadio,QFE[1]) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.POINT) +self:_Number2Radio(self.MarshalRadio,QFE[2]) +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.REPORTSEEME,nil,nil,0.5,true) +end +function AIRBOSS:_AddF10Commands(_unitName) +self:F(_unitName) +local _unit,playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and playername then +local group=_unit:GetGroup() +local gid=group:GetID() +if group and gid then +if not self.menuadded[gid]then +self.menuadded[gid]=true +local _rootPath=nil +if AIRBOSS.MenuF10Root then +if self.menusingle then +_rootPath=AIRBOSS.MenuF10Root +else +_rootPath=missionCommands.addSubMenuForGroup(gid,self.alias,AIRBOSS.MenuF10Root) +end +else +if AIRBOSS.MenuF10[gid]==nil then +AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid,"Airboss") +end +if self.menusingle then +_rootPath=AIRBOSS.MenuF10[gid] +else +_rootPath=missionCommands.addSubMenuForGroup(gid,self.alias,AIRBOSS.MenuF10[gid]) +end +end +local _helpPath=missionCommands.addSubMenuForGroup(gid,"Help",_rootPath) +if self.menumarkzones then +local _markPath=missionCommands.addSubMenuForGroup(gid,"Mark Zones",_helpPath) +if self.menusmokezones then +missionCommands.addCommandForGroup(gid,"Smoke Pattern Zones",_markPath,self._MarkCaseZones,self,_unitName,false) +end +missionCommands.addCommandForGroup(gid,"Flare Pattern Zones",_markPath,self._MarkCaseZones,self,_unitName,true) +if self.menusmokezones then +missionCommands.addCommandForGroup(gid,"Smoke Marshal Zone",_markPath,self._MarkMarshalZone,self,_unitName,false) +end +missionCommands.addCommandForGroup(gid,"Flare Marshal Zone",_markPath,self._MarkMarshalZone,self,_unitName,true) +end +local _skillPath=missionCommands.addSubMenuForGroup(gid,"Skill Level",_helpPath) +missionCommands.addCommandForGroup(gid,"Flight Student",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.EASY) +missionCommands.addCommandForGroup(gid,"Naval Aviator",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.NORMAL) +missionCommands.addCommandForGroup(gid,"TOPGUN Graduate",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.HARD) +missionCommands.addCommandForGroup(gid,"Hints On/Off",_skillPath,self._SetHintsOnOff,self,_unitName) +missionCommands.addCommandForGroup(gid,"My Status",_helpPath,self._DisplayPlayerStatus,self,_unitName) +missionCommands.addCommandForGroup(gid,"Attitude Monitor",_helpPath,self._DisplayAttitude,self,_unitName) +missionCommands.addCommandForGroup(gid,"Radio Check LSO",_helpPath,self._LSORadioCheck,self,_unitName) +missionCommands.addCommandForGroup(gid,"Radio Check Marshal",_helpPath,self._MarshalRadioCheck,self,_unitName) +missionCommands.addCommandForGroup(gid,"Subtitles On/Off",_helpPath,self._SubtitlesOnOff,self,_unitName) +missionCommands.addCommandForGroup(gid,"Trapsheet On/Off",_helpPath,self._TrapsheetOnOff,self,_unitName) +local _kneeboardPath=missionCommands.addSubMenuForGroup(gid,"Kneeboard",_rootPath) +local _resultsPath=missionCommands.addSubMenuForGroup(gid,"Results",_kneeboardPath) +missionCommands.addCommandForGroup(gid,"Greenie Board",_resultsPath,self._DisplayScoreBoard,self,_unitName) +missionCommands.addCommandForGroup(gid,"My LSO Grades",_resultsPath,self._DisplayPlayerGrades,self,_unitName) +missionCommands.addCommandForGroup(gid,"Last Debrief",_resultsPath,self._DisplayDebriefing,self,_unitName) +if self.skipperMenu then +local _skipperPath=missionCommands.addSubMenuForGroup(gid,"Skipper",_kneeboardPath) +local _menusetspeed=missionCommands.addSubMenuForGroup(gid,"Set Speed",_skipperPath) +missionCommands.addCommandForGroup(gid,"10 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,10) +missionCommands.addCommandForGroup(gid,"15 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,15) +missionCommands.addCommandForGroup(gid,"20 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,20) +missionCommands.addCommandForGroup(gid,"25 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,25) +missionCommands.addCommandForGroup(gid,"30 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,30) +local _menusetrtime=missionCommands.addSubMenuForGroup(gid,"Set Time",_skipperPath) +missionCommands.addCommandForGroup(gid,"15 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,15) +missionCommands.addCommandForGroup(gid,"30 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,30) +missionCommands.addCommandForGroup(gid,"45 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,45) +missionCommands.addCommandForGroup(gid,"60 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,60) +missionCommands.addCommandForGroup(gid,"90 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,90) +local _menusetrtime=missionCommands.addSubMenuForGroup(gid,"Set Marshal Radial",_skipperPath) +missionCommands.addCommandForGroup(gid,"+30°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,30) +missionCommands.addCommandForGroup(gid,"+15°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,15) +missionCommands.addCommandForGroup(gid,"0°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,0) +missionCommands.addCommandForGroup(gid,"-15°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,-15) +missionCommands.addCommandForGroup(gid,"-30°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,-30) +missionCommands.addCommandForGroup(gid,"U-turn On/Off",_skipperPath,self._SkipperRecoveryUturn,self,_unitName) +missionCommands.addCommandForGroup(gid,"Start CASE I",_skipperPath,self._SkipperStartRecovery,self,_unitName,1) +missionCommands.addCommandForGroup(gid,"Start CASE II",_skipperPath,self._SkipperStartRecovery,self,_unitName,2) +missionCommands.addCommandForGroup(gid,"Start CASE III",_skipperPath,self._SkipperStartRecovery,self,_unitName,3) +missionCommands.addCommandForGroup(gid,"Stop Recovery",_skipperPath,self._SkipperStopRecovery,self,_unitName) +end +missionCommands.addCommandForGroup(gid,"Carrier Info",_kneeboardPath,self._DisplayCarrierInfo,self,_unitName) +missionCommands.addCommandForGroup(gid,"Weather Report",_kneeboardPath,self._DisplayCarrierWeather,self,_unitName) +missionCommands.addCommandForGroup(gid,"Set Section",_kneeboardPath,self._SetSection,self,_unitName) +missionCommands.addCommandForGroup(gid,"Marshal Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Marshal") +missionCommands.addCommandForGroup(gid,"Pattern Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Pattern") +missionCommands.addCommandForGroup(gid,"Waiting Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Waiting") +missionCommands.addCommandForGroup(gid,"Request Marshal",_rootPath,self._RequestMarshal,self,_unitName) +missionCommands.addCommandForGroup(gid,"Request Commence",_rootPath,self._RequestCommence,self,_unitName) +missionCommands.addCommandForGroup(gid,"Request Refueling",_rootPath,self._RequestRefueling,self,_unitName) +missionCommands.addCommandForGroup(gid,"Spinning",_rootPath,self._RequestSpinning,self,_unitName) +missionCommands.addCommandForGroup(gid,"Emergency Landing",_rootPath,self._RequestEmergency,self,_unitName) +missionCommands.addCommandForGroup(gid,"[Reset My Status]",_rootPath,self._ResetPlayerStatus,self,_unitName) +end +else +self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.",_unitName)) +end +else +self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.",_unitName)) +end +end +function AIRBOSS:_SkipperStartRecovery(_unitName,case) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text=string.format("affirm, Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.",case,self.skipperTime,self.skipperSpeed,tostring(self.skipperUturn)) +if case>1 then +text=text..string.format(" Marshal radial %d°.",self.skipperOffset) +end +if self:IsRecovering()then +text="negative, carrier is already recovering." +self:MessageToPlayer(playerData,text,"AIRBOSS") +return +end +self:MessageToPlayer(playerData,text,"AIRBOSS") +local t0=timer.getAbsTime()+5*60 +local t9=t0+self.skipperTime*60 +local C0=UTILS.SecondsToClock(t0) +local C9=UTILS.SecondsToClock(t9) +self:AddRecoveryWindow(C0,C9,case,self.skipperOffset,true,self.skipperSpeed,self.skipperUturn) +end +end +end +function AIRBOSS:_SkipperStopRecovery(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text="roger, stopping recovery right away." +if not self:IsRecovering()then +text="negative, carrier is currently not recovering." +self:MessageToPlayer(playerData,text,"AIRBOSS") +return +end +self:MessageToPlayer(playerData,text,"AIRBOSS") +self:RecoveryStop() +end +end +end +function AIRBOSS:_SkipperRecoveryOffset(_unitName,offset) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.",offset) +self:MessageToPlayer(playerData,text,"AIRBOSS") +self.skipperOffset=offset +end +end +end +function AIRBOSS:_SkipperRecoveryTime(_unitName,time) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text=string.format("roger, manual recovery time set to %d min.",time) +self:MessageToPlayer(playerData,text,"AIRBOSS") +self.skipperTime=time +end +end +end +function AIRBOSS:_SkipperRecoverySpeed(_unitName,speed) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text=string.format("roger, wind on deck set to %d knots.",speed) +self:MessageToPlayer(playerData,text,"AIRBOSS") +self.skipperSpeed=speed +end +end +end +function AIRBOSS:_SkipperRecoveryUturn(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +self.skipperUturn=not self.skipperUturn +local text=string.format("roger, U-turn is now %s.",tostring(self.skipperUturn)) +self:MessageToPlayer(playerData,text,"AIRBOSS") +end +end +end +function AIRBOSS:_ResetPlayerStatus(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text="roger, status reset executed! You have been removed from all queues." +self:MessageToPlayer(playerData,text,"AIRBOSS") +self:_RemoveFlight(playerData) +if playerData.debriefschedulerID and self.Scheduler then +self.Scheduler:Stop(playerData.debriefschedulerID) +end +self:_InitPlayer(playerData) +end +end +end +function AIRBOSS:_RequestMarshal(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +if self.xtVoiceOvers then +self:_MarshallInboundCall(_unit,playerData.onboard) +end +local inCCA=playerData.unit:IsInZone(self.zoneCCA) +if inCCA then +if self:_InQueue(self.Qmarshal,playerData.group)then +local text=string.format("negative, you are already in the Marshal queue. New marshal request denied!") +self:MessageToPlayer(playerData,text,"MARSHAL") +elseif self:_InQueue(self.Qpattern,playerData.group)then +local text=string.format("negative, you are already in the Pattern queue. Marshal request denied!") +self:MessageToPlayer(playerData,text,"MARSHAL") +elseif self:_InQueue(self.Qwaiting,playerData.group)then +local text=string.format("negative, you are in the Waiting queue with %d flights ahead of you. Marshal request denied!",#self.Qwaiting) +self:MessageToPlayer(playerData,text,"MARSHAL") +elseif not _unit:InAir()then +local text=string.format("negative, you are not airborne. Marshal request denied!") +self:MessageToPlayer(playerData,text,"MARSHAL") +elseif playerData.name~=playerData.seclead then +local text=string.format("negative, your section lead %s needs to request Marshal.",playerData.seclead) +self:MessageToPlayer(playerData,text,"MARSHAL") +else +local freestack=self:_GetFreeStack(playerData.ai) +if freestack then +self:_MarshalPlayer(playerData,freestack) +else +self:_WaitPlayer(playerData) +end +end +else +local text=string.format("negative, you are not inside CCA. Marshal request denied!") +self:MessageToPlayer(playerData,text,"MARSHAL") +end +end +end +end +function AIRBOSS:_RequestEmergency(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text="" +if not self.emergency then +text="negative, no emergency landings on my carrier. We are currently busy. See how you get along!" +elseif not _unit:InAir()then +local zone=self:_GetZoneCarrierBox() +if playerData.unit:IsInZone(zone)then +text="roger, you are now technically in the bolter pattern. Your next step after takeoff is abeam!" +local lead=self:_GetFlightLead(playerData) +self:_SetPlayerStep(lead,AIRBOSS.PatternStep.BOLTER) +for _,sec in pairs(lead.section)do +local sectionmember=sec +self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.BOLTER) +end +self:_RemoveFlightFromQueue(self.Qwaiting,lead) +if self:_InQueue(self.Qmarshal,lead.group)then +self:_RemoveFlightFromMarshalQueue(lead) +else +if not self:_InQueue(self.Qpattern,lead.group)then +self:_AddFlightToPatternQueue(lead) +end +end +else +text=string.format("negative, you are not airborne. Request denied!") +end +else +text="affirmative, you can bypass the pattern and are cleared for final approach!" +playerData.wrappedUpAtWakeLittle=false +playerData.wrappedUpAtWakeFull=false +playerData.wrappedUpAtWakeUnderline=false +playerData.wrappedUpAtStartLittle=false +playerData.wrappedUpAtStartFull=false +playerData.wrappedUpAtStartUnderline=false +playerData.AAatWakeLittle=false +playerData.AAatWakeFull=false +playerData.AAatWakeUnderline=false +local lead=self:_GetFlightLead(playerData) +self:_SetPlayerStep(lead,AIRBOSS.PatternStep.EMERGENCY) +for _,sec in pairs(lead.section)do +local sectionmember=sec +self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.EMERGENCY) +self:_RemoveFlightFromQueue(self.Qspinning,sectionmember) +end +self:_RemoveFlightFromQueue(self.Qwaiting,lead) +if self:_InQueue(self.Qmarshal,lead.group)then +self:_RemoveFlightFromMarshalQueue(lead) +else +if not self:_InQueue(self.Qpattern,lead.group)then +self:_AddFlightToPatternQueue(lead) +end +end +end +self:MessageToPlayer(playerData,text,"AIRBOSS") +end +end +end +function AIRBOSS:_RequestSpinning(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text="" +if not self:_InQueue(self.Qpattern,playerData.group)then +text="negative, you have to be in the pattern to spin it!" +elseif playerData.step==AIRBOSS.PatternStep.SPINNING then +text="negative, you are already spinning." +elseif not(playerData.step==AIRBOSS.PatternStep.BREAKENTRY or +playerData.step==AIRBOSS.PatternStep.EARLYBREAK or +playerData.step==AIRBOSS.PatternStep.LATEBREAK)then +text="negative, you have to be in the right step to spin it!" +else +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.SPINNING) +table.insert(self.Qspinning,playerData) +local call=self:_NewRadioCall(self.LSOCall.SPINIT,"AIRBOSS","Spin it!",self.Tmessage,playerData.onboard) +self:RadioTransmission(self.LSORadio,call,nil,nil,nil,true) +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +local text="Climb to 1200 feet and proceed to the initial again." +self:MessageToPlayer(playerData,text,"AIRBOSS","") +end +return +end +self:MessageToPlayer(playerData,text,"AIRBOSS") +end +end +end +function AIRBOSS:_RequestCommence(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +if self.xtVoiceOvers then +self:_CommencingCall(_unit,playerData.onboard) +end +local text="" +local cleared=false +if _unit:IsInZone(self.zoneCCA)then +local stack=playerData.flag +local _,npattern=self:_GetQueueInfo(self.Qpattern) +if self:_InQueue(self.Qpattern,playerData.group)then +text=string.format("negative, %s, you are already in the Pattern queue.",playerData.name) +elseif not _unit:InAir()then +text=string.format("negative, %s, you are not airborne.",playerData.name) +elseif playerData.seclead~=playerData.name then +text=string.format("negative, %s, your section leader %s has to request commence!",playerData.name,playerData.seclead) +elseif stack>1 then +text=string.format("negative, %s, it's not your turn yet! You are in stack no. %s.",playerData.name,stack) +elseif npattern>=self.Nmaxpattern then +text=string.format("negative ghostrider, pattern is full!\nThere are %d aircraft currently in the pattern.",npattern) +elseif self:IsRecovering()==false and not self.airbossnice then +if self.recoverywindow then +local clock=UTILS.SecondsToClock(self.recoverywindow.START) +text=string.format("negative, carrier is currently not recovery. Next window will open at %s.",clock) +else +text=string.format("negative, carrier is not recovering. No future windows planned.") +end +elseif not self:_InQueue(self.Qmarshal,playerData.group)and not self.airbossnice then +text="negative, you have to request Marshal before you can commence." +else +text=text.."roger." +if not self:IsRecovering()then +text=text.." Carrier is not recovering currently! However, you are cleared anyway as I have a nice day." +end +if not self:_InQueue(self.Qmarshal,playerData.group)then +playerData.case=self.case +if self.TACANon and playerData.difficulty~=AIRBOSS.Difficulty.HARD then +local radial=self:GetRadial(playerData.case,true,true,true) +if playerData.case==1 then +radial=self:GetBRC() +end +text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n",radial,self.TACANchannel,self.TACANmode,self.TACANmorse) +end +for _,flight in pairs(playerData.section)do +flight.case=playerData.case +end +self:_AddFlightToPatternQueue(playerData) +end +cleared=true +end +else +text=string.format("negative, %s, you are not inside the CCA!",playerData.name) +end +self:T(self.lid..text) +self:MessageToPlayer(playerData,text,"MARSHAL") +if cleared then +self:_Commencing(playerData,false) +end +end +end +end +function AIRBOSS:_RequestRefueling(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text +if self.tanker then +if _unit:IsInZone(self.zoneCCA)then +if self.tanker:IsRunning()or self.tanker:IsRefueling()then +local angels=self:_GetAngels(self.tanker.altitude) +text=string.format("affirmative, proceed to tanker at angels %d.",angels) +if self.tanker.TACANon then +text=text..string.format("\nTanker TACAN channel %d%s (%s).",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse) +text=text..string.format("\nRadio frequency %.3f MHz AM.",self.tanker.RadioFreq) +end +if self.tanker:IsRefueling()then +text=text.."\nTanker is currently refueling. You might have to queue up." +end +self:_RemoveFlightFromMarshalQueue(playerData,true) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.REFUELING) +for _,sec in pairs(playerData.section)do +local sectext="follow your section leader to the tanker." +self:MessageToPlayer(sec,sectext,"MARSHAL") +self:_SetPlayerStep(sec,AIRBOSS.PatternStep.REFUELING) +end +elseif self.tanker:IsReturning()then +text="negative, tanker is RTB. Request denied!\nWait for the tanker to be back on station if you can." +end +else +text="negative, you are not inside the CCA yet." +end +else +text="negative, no refueling tanker available." +end +self:MessageToPlayer(playerData,text,"MARSHAL") +end +end +end +function AIRBOSS:_RemoveSectionMember(playerData,sectionmember) +for i,_flight in pairs(playerData.section)do +local flight=_flight +if flight.name==sectionmember.name then +table.remove(playerData.section,i) +return true +end +end +return false +end +function AIRBOSS:_SetSection(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local mycoord=_unit:GetCoordinate() +local dmax=self.maxsectiondistance +local text +if self.NmaxSection==0 then +text=string.format("negative, setting sections is disabled in this mission. You stay alone.") +elseif self:_InQueue(self.Qmarshal,playerData.group)then +text=string.format("negative, you are already in the Marshal queue. Setting section not possible any more!") +elseif self:_InQueue(self.Qpattern,playerData.group)then +text=string.format("negative, you are already in the Pattern queue. Setting section not possible any more!") +else +if playerData.seclead~=playerData.name then +local lead=self.players[playerData.seclead] +if lead then +local removed=self:_RemoveSectionMember(lead,playerData) +if removed then +self:MessageToPlayer(lead,string.format("Flight %s has been removed from your section.",playerData.name),"AIRBOSS","",5) +self:MessageToPlayer(playerData,string.format("You have been removed from %s's section.",lead.name),"AIRBOSS","",5) +end +end +end +local section={} +for _,_flight in pairs(self.flights)do +local flight=_flight +if flight.ai==false and flight.groupname~=playerData.groupname and#flight.section==0 and flight.seclead==flight.name then +local distance=flight.group:GetCoordinate():Get3DDistance(mycoord) +if distance0 then +_playerResults[playerName]=Paverage/n +end +end +end +local text=string.format("Greenie Board (top ten):") +local i=1 +for _playerName,_points in UTILS.spairs(_playerResults,function(t,a,b) +return t[b]=0 then +text=text..string.format("(%.1f)",grade.points) +end +end +i=i+1 +if i>10 then +break +end +end +if i==1 then +text=text.."\nNo results yet." +end +local playerData=self.players[_playername] +if playerData.client then +MESSAGE:New(text,30,nil,true):ToClient(playerData.client) +end +end +end +function AIRBOSS:_DisplayPlayerGrades(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text=string.format("Your last 10 grades, %s:",_playername) +local playerGrades=self.playerscores[_playername]or{} +local p=0 +local n=0 +local m=0 +for i=#playerGrades,1,-1 do +local grade=playerGrades[i] +if grade.points>=0 then +local points=grade.finalscore or grade.points +if m<10 then +text=text..string.format("\n[%d] %s %.1f PT - %s",i,grade.grade,points,grade.details) +if grade.wire and grade.wire<=4 then +text=text..string.format(" %d-wire",grade.wire) +end +if grade.Tgroove and grade.Tgroove<=360 then +text=text..string.format(" Tgroove=%.1f s",grade.Tgroove) +end +end +if grade.finalscore then +p=p+grade.finalscore +n=n+1 +end +m=m+1 +end +end +if n>0 then +text=text..string.format("\nAverage points = %.1f",p/n) +else +text=text..string.format("\nNo data available.") +end +if playerData.client then +MESSAGE:New(text,30,nil,true):ToClient(playerData.client) +end +end +end +end +function AIRBOSS:_DisplayDebriefing(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text=string.format("Debriefing:") +if#playerData.lastdebrief>0 then +text=text..string.format("\n================================\n") +for _,_data in pairs(playerData.lastdebrief)do +local step=_data.step +local comment=_data.hint +text=text..string.format("* %s:",step) +text=text..string.format("%s\n",comment) +end +else +text=text.." Nothing to show yet." +end +self:MessageToPlayer(playerData,text,nil,"",30,true) +end +end +end +function AIRBOSS:_DisplayQueue(_unitname,qname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +local queue=nil +if qname=="Marshal"then +queue=self.Qmarshal +elseif qname=="Pattern"then +queue=self.Qpattern +elseif qname=="Waiting"then +queue=self.Qwaiting +end +local Nqueue,nqueue=self:_GetQueueInfo(queue,playerData.case) +local text=string.format("%s Queue:",qname) +if#queue==0 then +text=text.." empty" +else +local N=0 +if qname=="Marshal"then +for i,_flight in pairs(queue)do +local flight=_flight +local charlie=self:_GetCharlieTime(flight) +local Charlie=UTILS.SecondsToClock(charlie) +local stack=flight.flag +local angels=self:_GetAngels(self:_GetMarshalAltitude(stack,flight.case)) +local _,nunit,nsec=self:_GetFlightUnits(flight,true) +local nick=self:_GetACNickname(flight.actype) +N=N+nunit +text=text..string.format("\n[Stack %d] %s (%s*%d+%d): Case %d, Angels %d, Charlie %s",stack,flight.onboard,nick,nunit,nsec,flight.case,angels,tostring(Charlie)) +end +elseif qname=="Pattern"or qname=="Waiting"then +for i,_flight in pairs(queue)do +local flight=_flight +local _,nunit,nsec=self:_GetFlightUnits(flight,true) +local nick=self:_GetACNickname(flight.actype) +local ptime=UTILS.SecondsToClock(timer.getAbsTime()-flight.time) +N=N+nunit +text=text..string.format("\n[%d] %s (%s*%d+%d): Case %d, T=%s",i,flight.onboard,nick,nunit,nsec,flight.case,ptime) +end +end +text=text..string.format("\nTotal AC: %d (airborne %d)",N,nqueue) +end +self:MessageToPlayer(playerData,text,nil,"",nil,true) +end +end +end +function AIRBOSS:_DisplayCarrierInfo(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +local coord=self:GetCoordinate() +local carrierheading=self.carrier:GetHeading() +local carrierspeed=UTILS.MpsToKnots(self.carrier:GetVelocityMPS()) +local tacan="unknown" +local icls="unknown" +if self.TACANon and self.TACANchannel~=nil then +tacan=string.format("%d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse) +end +if self.ICLSon and self.ICLSchannel~=nil then +icls=string.format("%d (%s)",self.ICLSchannel,self.ICLSmorse) +end +local wind=UTILS.MpsToKnots(select(1,self:GetWindOnDeck())) +local Nmarshal,nmarshal=self:_GetQueueInfo(self.Qmarshal,playerData.case) +local Npattern,npattern=self:_GetQueueInfo(self.Qpattern) +local Nspinning,nspinning=self:_GetQueueInfo(self.Qspinning) +local Nwaiting,nwaiting=self:_GetQueueInfo(self.Qwaiting) +local Ntotal,ntotal=self:_GetQueueInfo(self.flights) +local Tabs=timer.getAbsTime() +local recoverytext="Recovery time windows (max 5):" +if#self.recoverytimes==0 then +recoverytext=recoverytext.." none." +else +local rw=0 +for _,_recovery in pairs(self.recoverytimes)do +local recovery=_recovery +if Tabs=5 then +break +end +end +end +end +local tankertext=nil +if self.tanker then +tankertext=string.format("Recovery tanker frequency %.3f MHz\n",self.tanker.RadioFreq) +if self.tanker.TACANon then +tankertext=tankertext..string.format("Recovery tanker TACAN %d%s (%s)",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse) +else +tankertext=tankertext.."Recovery tanker TACAN n/a" +end +end +local state=self:GetState() +if state=="Idle"then +state="Deck closed" +end +if self.turning then +state=state.." (currently turning)" +end +local text=string.format("%s info:\n",self.alias) +text=text..string.format("================================\n") +text=text..string.format("Carrier state: %s\n",state) +if self.case==1 then +text=text..string.format("Case %d recovery ops\n",self.case) +else +local radial=self:GetRadial(self.case,true,true,false) +text=text..string.format("Case %d recovery ops\nMarshal radial %03d°\n",self.case,radial) +end +text=text..string.format("BRC %03d° - FB %03d°\n",self:GetBRC(),self:GetFinalBearing(true)) +text=text..string.format("Speed %.1f kts - Wind on deck %.1f kts\n",carrierspeed,wind) +text=text..string.format("Tower frequency %.3f MHz\n",self.TowerFreq) +text=text..string.format("Marshal radio %.3f MHz\n",self.MarshalFreq) +text=text..string.format("LSO radio %.3f MHz\n",self.LSOFreq) +text=text..string.format("TACAN Channel %s\n",tacan) +text=text..string.format("ICLS Channel %s\n",icls) +if tankertext then +text=text..tankertext.."\n" +end +text=text..string.format("# A/C total %d (%d)\n",Ntotal,ntotal) +text=text..string.format("# A/C marshal %d (%d)\n",Nmarshal,nmarshal) +text=text..string.format("# A/C pattern %d (%d) - spinning %d (%d)\n",Npattern,npattern,Nspinning,nspinning) +text=text..string.format("# A/C waiting %d (%d)\n",Nwaiting,nwaiting) +text=text..string.format(recoverytext) +self:T2(self.lid..text) +self:MessageToPlayer(playerData,text,nil,"",30,true) +else +self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername)) +end +end +end +function AIRBOSS:_DisplayCarrierWeather(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local text="" +local coord=self:GetCoordinate() +local T=coord:GetTemperature() +local P=coord:GetPressure() +local Wd,Ws=self:GetWind(nil,true) +local Bn,Bd=UTILS.BeaufortScale(Ws) +local WodPA,WodPP=self:GetWindOnDeck() +local WodPA=UTILS.MpsToKnots(WodPA) +local WodPP=UTILS.MpsToKnots(WodPP) +local WD=string.format('%03d°',Wd) +local Ts=string.format("%d°C",T) +local tT=string.format("%d°C",T) +local tW=string.format("%.1f knots",UTILS.MpsToKnots(Ws)) +local tP=string.format("%.2f inHg",UTILS.hPa2inHg(P)) +text=text..string.format("Weather Report at Carrier %s:\n",self.alias) +text=text..string.format("================================\n") +text=text..string.format("Temperature %s\n",tT) +text=text..string.format("Wind from %s at %s (%s)\n",WD,tW,Bd) +text=text..string.format("Wind on deck || %.1f kts, == %.1f kts\n",WodPA,WodPP) +text=text..string.format("QFE %.1f hPa = %s",P,tP) +if self.staticweather then +local clouds,visibility,fog,dust=self:_GetStaticWeather() +text=text..string.format("\nVisibility %.1f NM",UTILS.MetersToNM(visibility)) +text=text..string.format("\nCloud base %d ft",UTILS.MetersToFeet(clouds.base)) +text=text..string.format("\nCloud thickness %d ft",UTILS.MetersToFeet(clouds.thickness)) +text=text..string.format("\nCloud density %d",clouds.density) +text=text..string.format("\nPrecipitation %d",clouds.iprecptns) +if fog then +text=text..string.format("\nFog thickness %d ft",UTILS.MetersToFeet(fog.thickness)) +text=text..string.format("\nFog visibility %d ft",UTILS.MetersToFeet(fog.visibility)) +else +text=text..string.format("\nNo fog") +end +if dust then +text=text..string.format("\nDust density %d",dust) +else +text=text..string.format("\nNo dust") +end +end +self:T2(self.lid..text) +self:MessageToPlayer(self.players[playername],text,nil,"",30,true) +else +self:E(self.lid..string.format("ERROR! Could not find player unit in CarrierWeather! Unit name = %s",_unitname)) +end +end +function AIRBOSS:_SetDifficulty(_unitname,difficulty) +self:T2({difficulty=difficulty,unitname=_unitname}) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.difficulty=difficulty +local text=string.format("roger, your skill level is now: %s.",difficulty) +self:MessageToPlayer(playerData,text,nil,playerData.name,5) +else +self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername)) +end +if playerData.difficulty==AIRBOSS.Difficulty.HARD then +playerData.showhints=false +else +playerData.showhints=true +end +end +end +function AIRBOSS:_SetHintsOnOff(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.showhints=not playerData.showhints +local text="" +if playerData.showhints==true then +text=string.format("roger, hints are now ON.") +else +text=string.format("affirm, hints are now OFF.") +end +self:MessageToPlayer(playerData,text,nil,playerData.name,5) +end +end +end +function AIRBOSS:_DisplayAttitude(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.attitudemonitor=not playerData.attitudemonitor +end +end +end +function AIRBOSS:_SubtitlesOnOff(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.subtitles=not playerData.subtitles +local text="" +if playerData.subtitles==true then +text=string.format("roger, subtitiles are now ON.") +elseif playerData.subtitles==false then +text=string.format("affirm, subtitiles are now OFF.") +end +self:MessageToPlayer(playerData,text,nil,playerData.name,5) +end +end +end +function AIRBOSS:_TrapsheetOnOff(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +local text="" +if self.trapsheet then +playerData.trapon=not playerData.trapon +if playerData.trapon==true then +text=string.format("roger, your trapsheets are now SAVED.") +else +text=string.format("affirm, your trapsheets are NOT SAVED.") +end +else +text="negative, trap sheet data recorder is broken on this carrier." +end +self:MessageToPlayer(playerData,text,nil,playerData.name,5) +end +end +end +function AIRBOSS:_DisplayPlayerStatus(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local steptext=playerData.step +if playerData.step==AIRBOSS.PatternStep.HOLDING then +if playerData.holding==nil then +steptext="Transit to Marshal" +elseif playerData.holding==false then +steptext="Marshal (outside zone)" +elseif playerData.holding==true then +steptext="Marshal Stack Holding" +end +end +local stack=playerData.flag +local stacktext=nil +if stack>0 then +local stackalt=self:_GetMarshalAltitude(stack) +local angels=self:_GetAngels(stackalt) +stacktext=string.format("Marshal Stack %d, Angels %d\n",stack,angels) +if playerData.step==AIRBOSS.PatternStep.HOLDING and playerData.case>1 then +local radial=self:GetRadial(playerData.case,true,true,true) +stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n",radial,angels+15) +end +end +local fuel=playerData.unit:GetFuel()*100 +local fuelstate=self:_GetFuelState(playerData.unit) +local _,nunitsGround=self:_GetFlightUnits(playerData,true) +local _,nunitsAirborne=self:_GetFlightUnits(playerData,false) +local text=string.format("Status of player %s (%s)\n",playerData.name,playerData.callsign) +text=text..string.format("================================\n") +text=text..string.format("Step: %s\n",steptext) +if stacktext then +text=text..stacktext +end +text=text..string.format("Recovery Case: %d\n",playerData.case) +text=text..string.format("Skill Level: %s\n",playerData.difficulty) +text=text..string.format("Modex: %s (%s)\n",playerData.onboard,self:_GetACNickname(playerData.actype)) +text=text..string.format("Fuel State: %.1f lbs/1000 (%.1f %%)\n",fuelstate/1000,fuel) +text=text..string.format("# units: %d (%d airborne)\n",nunitsGround,nunitsAirborne) +text=text..string.format("Section Lead: %s (%d/%d)",tostring(playerData.seclead),#playerData.section+1,self.NmaxSection+1) +for _,_sec in pairs(playerData.section)do +local sec=_sec +text=text..string.format("\n- %s",sec.name) +end +if playerData.step==AIRBOSS.PatternStep.INITIAL then +local zoneinitial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5),self:GetRadial(2,false,false,false)) +local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneinitial) +local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneinitial)) +local brc=self:GetBRC() +text=text..string.format("\nTo Initial: Fly heading %03d° for %.1f NM and turn to BRC %03d°",flyhdg,flydist,brc) +elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then +local zoneplatform=self:_GetZonePlatform(playerData.case):GetCoordinate() +local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneplatform) +local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneplatform)) +local hdg=self:GetRadial(playerData.case,true,true,true) +text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°",flyhdg,flydist,hdg) +end +self:MessageToPlayer(playerData,text,nil,"",30,true) +else +self:E(self.lid..string.format("ERROR: playerData=nil. Unit name=%s, player name=%s",_unitName,_playername)) +end +else +self:E(self.lid..string.format("ERROR: could not find player for unit %s",_unitName)) +end +end +function AIRBOSS:_MarkMarshalZone(_unitName,flare) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local stack=playerData.flag +local case=playerData.case +local text="" +if stack>0 then +local zoneHolding=self:_GetZoneHolding(case,stack) +local zoneThree=self:_GetZoneCommence(case,stack) +local patternalt=self:_GetMarshalAltitude(stack,case) +patternalt=5 +text="roger, marking" +if flare then +text=text..string.format("\n* Marshal zone stack %d with WHITE flares.",stack) +zoneHolding:FlareZone(FLARECOLOR.White,45,nil,patternalt) +text=text.."\n* Commence zone with RED flares." +zoneThree:FlareZone(FLARECOLOR.Red,45,nil,patternalt) +else +text=text..string.format("\n* Marshal zone stack %d with WHITE smoke.",stack) +zoneHolding:SmokeZone(SMOKECOLOR.White,45,patternalt) +text=text.."\n* Commence zone with RED smoke." +zoneThree:SmokeZone(SMOKECOLOR.Red,45,patternalt) +end +else +text="negative, you are currently not in a Marshal stack. No zones will be marked!" +end +self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name) +end +end +end +function AIRBOSS:_MarkCaseZones(_unitName,flare) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local case=playerData.case +local text=string.format("affirm, marking CASE %d zones",case) +if flare then +if case==1 or case==2 then +text=text.."\n* initial with GREEN flares" +self:_GetZoneInitial(case):FlareZone(FLARECOLOR.Green,45) +end +if case==2 or case==3 then +text=text.."\n* approach corridor with GREEN flares" +self:_GetZoneCorridor(case):FlareZone(FLARECOLOR.Green,45) +end +if case==2 or case==3 then +text=text.."\n* platform with RED flares" +self:_GetZonePlatform(case):FlareZone(FLARECOLOR.Red,45) +end +if case==3 then +text=text.."\n* dirty up with YELLOW flares" +self:_GetZoneDirtyUp(case):FlareZone(FLARECOLOR.Yellow,45) +end +if case==2 or case==3 then +if math.abs(self.holdingoffset)>0 then +self:_GetZoneArcIn(case):FlareZone(FLARECOLOR.White,45) +text=text.."\n* arc turn in with WHITE flares" +self:_GetZoneArcOut(case):FlareZone(FLARECOLOR.White,45) +text=text.."\n* arc turn out with WHITE flares" +end +end +if case==3 then +text=text.."\n* bullseye with GREEN flares" +self:_GetZoneBullseye(case):FlareZone(FLARECOLOR.Green,45) +end +if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then +text=text.."\n* abeam landing stop with RED flares" +local ALSPT=self:_GetZoneAbeamLandingSpot() +ALSPT:FlareZone(FLARECOLOR.Red,5,nil,UTILS.FeetToMeters(110)) +text=text.."\n* primary landing spot with GREEN flares" +local LSPT=self:_GetZoneLandingSpot() +LSPT:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight) +end +else +if case==1 or case==2 then +text=text.."\n* initial with GREEN smoke" +self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Green,45) +end +if case==2 or case==3 then +text=text.."\n* approach corridor with GREEN smoke" +self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green,45) +end +if case==2 or case==3 then +text=text.."\n* platform with RED smoke" +self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Red,45) +end +if case==2 or case==3 then +if math.abs(self.holdingoffset)>0 then +self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue,45) +text=text.."\n* arc turn in with BLUE smoke" +self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue,45) +text=text.."\n* arc turn out with BLUE smoke" +end +end +if case==3 then +text=text.."\n* dirty up with ORANGE smoke" +self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange,45) +end +if case==3 then +text=text.."\n* bullseye with GREEN smoke" +self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.Green,45) +end +end +self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name) +end +end +end +function AIRBOSS:_LSORadioCheck(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +self:RadioTransmission(self.LSORadio,self.LSOCall.RADIOCHECK,nil,nil,nil,true) +end +end +end +function AIRBOSS:_MarshalRadioCheck(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RADIOCHECK,nil,nil,nil,true) +end +end +end +function AIRBOSS:_SaveTrapSheet(playerData,grade) +if playerData.trapsheet==nil or#playerData.trapsheet==0 or not io then +return +end +local function _savefile(filename,data) +local f=io.open(filename,"wb") +if f then +f:write(data) +f:close() +else +self:E(self.lid..string.format("ERROR: could not save trap sheet to file %s.\nFile may contain invalid characters.",tostring(filename))) +end +end +local path=self.trappath +if lfs then +path=path or lfs.writedir() +end +local filename=nil +for i=1,9999 do +if self.trapprefix then +filename=string.format("%s_%s-%04d.csv",self.trapprefix,playerData.actype,i) +else +local name=UTILS.ReplaceIllegalCharacters(playerData.name,"_") +filename=string.format("AIRBOSS-%s_Trapsheet-%s_%s-%04d.csv",self.alias,name,playerData.actype,i) +end +if path~=nil then +filename=path.."\\"..filename +end +local _exists=UTILS.FileExists(filename) +if not _exists then +break +end +end +local text=string.format("Saving player %s trapsheet to file %s",playerData.name,filename) +self:I(self.lid..text) +local data="#Time,Rho,X,Z,Alt,AoA,GSE,LUE,Vtot,Vy,Gamma,Pitch,Roll,Yaw,Step,Grade,Points,Details\n" +local g0=playerData.trapsheet[1] +local T0=g0.Time +for i=1,#playerData.trapsheet do +local groove=playerData.trapsheet[i] +local t=groove.Time-T0 +local a=UTILS.MetersToNM(groove.Rho or 0) +local b=-groove.X or 0 +local c=groove.Z or 0 +local d=UTILS.MetersToFeet(groove.Alt or 0) +local e=groove.AoA or 0 +local f=groove.GSE or 0 +local g=-groove.LUE or 0 +local h=UTILS.MpsToKnots(groove.Vel or 0) +local i=(groove.Vy or 0)*196.85 +local j=groove.Gamma or 0 +local k=groove.Pitch or 0 +local l=groove.Roll or 0 +local m=groove.Yaw or 0 +local n=self:_GS(groove.Step,-1)or"n/a" +local o=groove.Grade or"n/a" +local p=groove.GradePoints or 0 +local q=groove.GradeDetail or"n/a" +data=data..string.format("%.2f,%.3f,%.1f,%.1f,%.1f,%.2f,%.2f,%.2f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%s,%s,%.1f,%s\n",t,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q) +end +_savefile(filename,data) +end +function AIRBOSS:onbeforeSave(From,Event,To,path,filename) +if not io then +self:E(self.lid.."ERROR: io not desanitized. Can't save player grades.") +return false +end +if path==nil and not lfs then +self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +return true +end +function AIRBOSS:onafterSave(From,Event,To,path,filename) +local function _savefile(filename,data) +local f=assert(io.open(filename,"wb")) +f:write(data) +f:close() +end +if lfs then +path=path or lfs.writedir() +end +filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias) +if path~=nil then +filename=path.."\\"..filename +end +local scores="Name,Pass,Points Final,Points Pass,Grade,Details,Wire,Tgroove,Case,Wind,Modex,Airframe,Carrier Type,Carrier Name,Theatre,Mission Time,Mission Date,OS Date\n" +local n=0 +for playername,grades in pairs(self.playerscores)do +for i,_grade in pairs(grades)do +local grade=_grade +local wire="n/a" +if grade.wire and grade.wire<=4 then +wire=tostring(grade.wire) +end +local Tgroove="n/a" +if grade.Tgroove and grade.Tgroove<=360 and grade.case<3 then +Tgroove=tostring(UTILS.Round(grade.Tgroove,1)) +end +local finalscore="n/a" +if grade.finalscore then +finalscore=tostring(UTILS.Round(grade.finalscore,1)) +end +scores=scores..string.format("%s,%d,%s,%.1f,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",playername,i,finalscore,grade.points,grade.grade,grade.details,wire,Tgroove,grade.case,grade.wind,grade.modex,grade.airframe,grade.carriertype,grade.carriername,grade.theatre,grade.mitime,grade.midate,grade.osdate) +n=n+1 +end +end +local text=string.format("Saving %d player LSO grades to file %s",n,filename) +self:I(self.lid..text) +_savefile(filename,scores) +end +function AIRBOSS:onbeforeLoad(From,Event,To,path,filename) +local function _fileexists(name) +local f=io.open(name,"r") +if f~=nil then +io.close(f) +return true +else +return false +end +end +if not io then +self:E(self.lid.."WARNING: io not desanitized. Can't load player grades.") +return false +end +if path==nil and not lfs then +self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +if lfs then +path=path or lfs.writedir() +end +filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias) +if path~=nil then +filename=path.."\\"..filename +end +local exists=_fileexists(filename) +if exists then +return true +else +self:E(self.lid..string.format("WARNING: Player LSO grades file %s does not exist.",filename)) +return false +end +end +function AIRBOSS:onafterLoad(From,Event,To,path,filename) +local function _loadfile(filename) +local f=assert(io.open(filename,"rb")) +local data=f:read("*all") +f:close() +return data +end +if lfs then +path=path or lfs.writedir() +end +filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias) +if path~=nil then +filename=path.."\\"..filename +end +local text=string.format("Loading player LSO grades from file %s",filename) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:I(self.lid..text) +local data=_loadfile(filename) +local playergrades=UTILS.Split(data,"\n") +table.remove(playergrades,1) +self.playerscores={} +local n=0 +for _,gradeline in pairs(playergrades)do +local gradedata=UTILS.Split(gradeline,",") +self:T2(gradedata) +local grade={} +local playername=gradedata[1] +if gradedata[3]~=nil and gradedata[3]~="n/a"then +grade.finalscore=tonumber(gradedata[3]) +end +grade.points=tonumber(gradedata[4]) +grade.grade=tostring(gradedata[5]) +grade.details=tostring(gradedata[6]) +if gradedata[7]~=nil and gradedata[7]~="n/a"then +grade.wire=tonumber(gradedata[7]) +end +if gradedata[8]~=nil and gradedata[8]~="n/a"then +grade.Tgroove=tonumber(gradedata[8]) +end +grade.case=tonumber(gradedata[9]) +grade.wind=gradedata[10]or"n/a" +grade.modex=gradedata[11]or"n/a" +grade.airframe=gradedata[12]or"n/a" +grade.carriertype=gradedata[13]or"n/a" +grade.carriername=gradedata[14]or"n/a" +grade.theatre=gradedata[15]or"n/a" +grade.mitime=gradedata[16]or"n/a" +grade.midate=gradedata[17]or"n/a" +grade.osdate=gradedata[18]or"n/a" +self.playerscores[playername]=self.playerscores[playername]or{} +table.insert(self.playerscores[playername],grade) +n=n+1 +self:T2({playername,self.playerscores[playername]}) +end +local text=string.format("Loaded %d player LSO grades from file %s",n,filename) +self:I(self.lid..text) +end +function AIRBOSS:onafterLSOGrade(From,Event,To,playerData,grade) +if self.funkmanSocket then +local trapsheet={};trapsheet.X={};trapsheet.Z={};trapsheet.AoA={};trapsheet.Alt={} +for i=1,#playerData.trapsheet do +local ts=playerData.trapsheet[i] +table.insert(trapsheet.X,UTILS.Round(ts.X,1)) +table.insert(trapsheet.Z,UTILS.Round(ts.Z,1)) +table.insert(trapsheet.AoA,UTILS.Round(ts.AoA,2)) +table.insert(trapsheet.Alt,UTILS.Round(ts.Alt,1)) +end +local result={} +result.command=SOCKET.DataType.LSOGRADE +result.name=playerData.name +result.trapsheet=trapsheet +result.airframe=grade.airframe +result.mitime=grade.mitime +result.midate=grade.midate +result.wind=grade.wind +result.carriertype=grade.carriertype +result.carriername=grade.carriername +result.carrierrwy=grade.carrierrwy +result.landingdist=self.carrierparam.landingdist +result.theatre=grade.theatre +result.case=playerData.case +result.Tgroove=grade.Tgroove +result.wire=grade.wire +result.grade=grade.grade +result.points=grade.points +result.details=grade.details +self:T(self.lid.."Result onafterLSOGrade") +self:T(result) +self.funkmanSocket:SendTable(result) +end +end +RECOVERYTANKER={ +ClassName="RECOVERYTANKER", +Debug=false, +lid=nil, +carrier=nil, +carriertype=nil, +tankergroupname=nil, +tanker=nil, +airbase=nil, +beacon=nil, +TACANchannel=nil, +TACANmode=nil, +TACANmorse=nil, +TACANon=nil, +RadioFreq=nil, +RadioModu=nil, +altitude=nil, +speed=nil, +distStern=nil, +distBow=nil, +dTupdate=nil, +Dupdate=nil, +Hupdate=nil, +Tupdate=nil, +takeoff=nil, +lowfuel=nil, +respawn=nil, +respawninair=nil, +uncontrolledac=nil, +orientation=nil, +orientlast=nil, +position=nil, +alias=nil, +uid=0, +awacs=nil, +callsignname=nil, +callsignnumber=nil, +modex=nil, +eplrs=nil, +recovery=nil, +terminaltype=nil, +unlimitedfuel=false, +} +_RECOVERYTANKERID=0 +RECOVERYTANKER.version="1.0.10" +function RECOVERYTANKER:New(carrierunit,tankergroupname) +local self=BASE:Inherit(self,FSM:New()) +if type(carrierunit)=="string"then +self.carrier=UNIT:FindByName(carrierunit) +else +self.carrier=carrierunit +end +self.carriertype=self.carrier:GetTypeName() +self.tankergroupname=tankergroupname +_RECOVERYTANKERID=_RECOVERYTANKERID+1 +self.uid=_RECOVERYTANKERID +self.carrier:SetState(self.carrier,string.format("RECOVERYTANKER_%d",self.uid),self) +self.alias=string.format("%s_%s_%02d",self.carrier:GetName(),self.tankergroupname,_RECOVERYTANKERID) +self.lid=string.format("RECOVERYTANKER %s | ",self.alias) +self:SetAltitude() +self:SetSpeed() +self:SetRacetrackDistances() +self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) +self:SetTakeoffHot() +self:SetLowFuelThreshold() +self:SetRespawnOnOff() +self:SetTACAN() +self:SetRadio() +self:SetPatternUpdateDistance() +self:SetPatternUpdateHeading() +self:SetPatternUpdateInterval() +self:SetAWACS(false) +self:SetRecoveryAirboss(false) +self.terminaltype=AIRBASE.TerminalType.OpenMedOrBig +if false then +BASE:TraceOnOff(true) +BASE:TraceClass(self.ClassName) +BASE:TraceLevel(1) +end +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","RefuelStart","Refueling") +self:AddTransition("*","RefuelStop","Running") +self:AddTransition("*","Run","Running") +self:AddTransition("Running","RTB","Returning") +self:AddTransition("Returning","Returned","Returned") +self:AddTransition("*","Status","*") +self:AddTransition("Running","PatternUpdate","*") +self:AddTransition("*","Stop","Stopped") +return self +end +function RECOVERYTANKER:SetUnlimitedFuel(OnOff) +self.unlimitedfuel=OnOff +return self +end +function RECOVERYTANKER:SetSpeed(speed) +self.speed=UTILS.KnotsToMps(speed or 274) +return self +end +function RECOVERYTANKER:SetAltitude(altitude) +self.altitude=UTILS.FeetToMeters(altitude or 6000) +return self +end +function RECOVERYTANKER:SetRacetrackDistances(distbow,diststern) +self.distBow=UTILS.NMToMeters(distbow or 10) +self.distStern=-UTILS.NMToMeters(diststern or 4) +return self +end +function RECOVERYTANKER:SetPatternUpdateInterval(interval) +self.dTupdate=(interval or 10)*60 +return self +end +function RECOVERYTANKER:SetPatternUpdateDistance(distancechange) +self.Dupdate=UTILS.NMToMeters(distancechange or 5) +return self +end +function RECOVERYTANKER:SetPatternUpdateHeading(headingchange) +self.Hupdate=headingchange or 5 +return self +end +function RECOVERYTANKER:SetLowFuelThreshold(fuelthreshold) +self.lowfuel=fuelthreshold or 10 +return self +end +function RECOVERYTANKER:SetHomeBase(airbase,terminaltype) +if type(airbase)=="string"then +self.airbase=AIRBASE:FindByName(airbase) +else +self.airbase=airbase +end +if not self.airbase then +self:E(self.lid.."ERROR: Airbase is nil!") +end +if terminaltype then +self.terminaltype=terminaltype +end +return self +end +function RECOVERYTANKER:SetRecoveryAirboss(switch) +if switch==true or switch==nil then +self.recovery=true +else +self.recovery=false +end +return self +end +function RECOVERYTANKER:SetAWACS(switch,eplrs) +if switch==nil or switch==true then +self.awacs=true +else +self.awacs=false +end +if eplrs==nil or eplrs==true then +self.eplrs=true +else +self.eplrs=false +end +return self +end +function RECOVERYTANKER:SetCallsign(callsignname,callsignnumber) +self.callsignname=callsignname +self.callsignnumber=callsignnumber +return self +end +function RECOVERYTANKER:SetModex(modex) +self.modex=modex +return self +end +function RECOVERYTANKER:SetTakeoff(takeofftype) +self.takeoff=takeofftype +return self +end +function RECOVERYTANKER:SetTakeoffHot() +self:SetTakeoff(SPAWN.Takeoff.Hot) +return self +end +function RECOVERYTANKER:SetTakeoffCold() +self:SetTakeoff(SPAWN.Takeoff.Cold) +return self +end +function RECOVERYTANKER:SetTakeoffAir() +self:SetTakeoff(SPAWN.Takeoff.Air) +return self +end +function RECOVERYTANKER:SetRespawnOn() +self.respawn=true +return self +end +function RECOVERYTANKER:SetRespawnOff() +self.respawn=false +return self +end +function RECOVERYTANKER:SetRespawnOnOff(switch) +if switch==nil or switch==true then +self.respawn=true +else +self.respawn=false +end +return self +end +function RECOVERYTANKER:SetRespawnInAir() +self.respawninair=true +return self +end +function RECOVERYTANKER:SetUseUncontrolledAircraft() +self.uncontrolledac=true +return self +end +function RECOVERYTANKER:SetTACANoff() +self.TACANon=false +return self +end +function RECOVERYTANKER:SetTACAN(channel,morse,mode) +self.TACANchannel=channel or 1 +self.TACANmode=mode or"Y" +self.TACANmorse=morse or"TKR" +self.TACANon=true +return self +end +function RECOVERYTANKER:SetRadio(frequency,modulation) +self.RadioFreq=frequency or 251 +self.RadioModu=modulation or"AM" +return self +end +function RECOVERYTANKER:SetDebugModeON() +self.Debug=true +return self +end +function RECOVERYTANKER:SetDebugModeOFF() +self.Debug=false +return self +end +function RECOVERYTANKER:IsReturning() +return self:is("Returning") +end +function RECOVERYTANKER:IsReturned() +return self:is("Returned") +end +function RECOVERYTANKER:IsRunning() +return self:is("Running") +end +function RECOVERYTANKER:IsRefueling() +return self:is("Refueling") +end +function RECOVERYTANKER:IsStopped() +return self:is("Stopped") +end +function RECOVERYTANKER:GetAlias() +return self.alias +end +function RECOVERYTANKER:GetUnitName() +local unit=self.tanker:GetUnit(1) +if unit then +return unit:GetName() +end +return nil +end +function RECOVERYTANKER:onafterStart(From,Event,To) +self:I(string.format("Starting Recovery Tanker v%s for carrier unit %s of type %s for tanker group %s.",RECOVERYTANKER.version,self.carrier:GetName(),self.carriertype,self.tankergroupname)) +self:HandleEvent(EVENTS.EngineShutdown) +self:HandleEvent(EVENTS.Land) +self:HandleEvent(EVENTS.Refueling,self._RefuelingStart) +self:HandleEvent(EVENTS.RefuelingStop,self._RefuelingStop) +self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrDead) +self:HandleEvent(EVENTS.Dead,self._OnEventCrashOrDead) +local Spawn=SPAWN:NewWithAlias(self.tankergroupname,self.alias) +if self.unlimitedfuel then +Spawn:OnSpawnGroup( +function(grp) +grp:CommandSetUnlimitedFuel(self.unlimitedfuel) +end +) +end +Spawn:InitRadioCommsOnOff(true) +Spawn:InitRadioFrequency(self.RadioFreq) +Spawn:InitRadioModulation(self.RadioModu) +Spawn:InitModex(self.modex) +if self.takeoff==SPAWN.Takeoff.Air then +local hdg=self.carrier:GetHeading() +local dist=-self.distStern+UTILS.NMToMeters(4) +local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg+190):SetAltitude(self.altitude) +Spawn:InitHeading(hdg+10) +self.tanker=Spawn:SpawnFromCoordinate(Carrier) +else +if self.uncontrolledac then +self.tanker=GROUP:FindByName(self.tankergroupname) +if self.tanker:IsAlive()then +self.tanker:StartUncontrolled() +else +self:E(string.format("ERROR: No uncontrolled (alive) tanker group with name %s could be found!",self.tankergroupname)) +return +end +else +self.tanker=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,self.terminaltype) +end +end +self:ScheduleOnce(1,self._InitRoute,self,-self.distStern+UTILS.NMToMeters(3)) +if self.TACANon then +self:_ActivateTACAN(2) +end +if self.callsignname then +self.tanker:CommandSetCallsign(self.callsignname,self.callsignnumber,2) +end +if self.eplrs then +self.tanker:CommandEPLRS(true,3) +end +self.orientation=self.carrier:GetOrientationX() +self.orientlast=self.carrier:GetOrientationX() +self.position=self.carrier:GetCoordinate() +self:__Status(10) +return self +end +function RECOVERYTANKER:onafterStatus(From,Event,To) +local time=timer.getTime() +if self.tanker and self.tanker:IsAlive()then +local fuel=self.tanker:GetFuel()*100 +local life=self.tanker:GetUnit(1):GetLife() +local life0=self.tanker:GetUnit(1):GetLife0() +local lifeR=self.tanker:GetUnit(1):GetLifeRelative() +local text=string.format("Recovery tanker %s: state=%s fuel=%.1f, life=%.1f/%.1f=%d",self.tanker:GetName(),self:GetState(),fuel,life,life0,lifeR*100) +self:T(self.lid..text) +MESSAGE:New(text,10):ToAllIf(self.Debug) +if self:IsRunning()then +if fuel100 then +return +end +local text=string.format("Recovery tanker %s started refueling unit %s",self.tanker:GetName(),receiver:GetName()) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +self:RefuelStart(receiver) +end +end +function RECOVERYTANKER:_RefuelingStop(EventData) +if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()then +local receiver=EventData.IniUnit +local dist=receiver:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate()) +if dist>100 then +return +end +local text=string.format("Recovery tanker %s stopped refueling unit %s",self.tanker:GetName(),receiver:GetName()) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +self:RefuelStop(receiver) +end +end +function RECOVERYTANKER:_OnEventCrashOrDead(EventData) +self:F2({eventdata=EventData}) +if EventData and EventData.IniUnit then +local unit=EventData.IniUnit +local unitname=tostring(EventData.IniUnitName) +if EventData.IniGroupName==self.tanker:GetName()then +self:E(self.lid..string.format("Recovery tanker %s crashed!",unitname)) +self:Stop() +if self.respawn then +self:__Start(5) +end +end +end +end +function RECOVERYTANKER:_InitPatternTaskFunction() +local carriername=self.carrier:GetName() +local DCSScript={} +DCSScript[#DCSScript+1]=string.format('local mycarrier = UNIT:FindByName(\"%s\") ',carriername) +DCSScript[#DCSScript+1]=string.format('local mytanker = mycarrier:GetState(mycarrier, \"RECOVERYTANKER_%d\") ',self.uid) +DCSScript[#DCSScript+1]=string.format('mytanker:PatternUpdate()') +local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript))) +return DCSTask +end +function RECOVERYTANKER:_InitRoute(dist,delay) +dist=dist or UTILS.NMToMeters(8) +delay=delay or 1 +self:T(self.lid..string.format("Initializing route of recovery tanker %s.",self.tanker:GetName())) +local Carrier=self.carrier:GetCoordinate() +local hdg=self.carrier:GetHeading() +local p=Carrier:Translate(dist,hdg+190):SetAltitude(self.altitude) +local speed=self.tanker:GetSpeedMax()*0.8 +if self.Debug then +p:MarkToAll(string.format("Enter Pattern WP: alt=%d ft, speed=%d kts",UTILS.MetersToFeet(self.altitude),speed*0.539957)) +end +local task=self:_InitPatternTaskFunction() +local wp={} +if self.takeoff==SPAWN.Takeoff.Air then +wp[#wp+1]=self.tanker:GetCoordinate():SetAltitude(self.altitude):WaypointAirTurningPoint(nil,speed,{},"Spawn Position") +else +wp[#wp+1]=Carrier:WaypointAirTakeOffParking() +end +wp[#wp+1]=p:WaypointAirTurningPoint(nil,speed,{task},"Enter Pattern") +self.tanker:Route(wp,delay) +self:__Run(1) +self.Tupdate=nil +end +function RECOVERYTANKER:_CheckPatternUpdate(dt) +local pos=self.carrier:GetCoordinate() +local vNew=self.carrier:GetOrientationX() +local vOld=self.orientation +local vLast=self.orientlast +vNew.y=0;vOld.y=0;vLast.y=0 +local deltaHeading=math.deg(math.acos(UTILS.VecDot(vNew,vOld)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vOld))) +local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) +self.orientlast=vNew +local turning=deltaLast>=1 +if turning then +self:T2(self.lid..string.format("Carrier is turning. Delta Heading = %.1f",deltaLast)) +end +local Hchange=false +if math.abs(deltaHeading)>=self.Hupdate then +self:T(self.lid..string.format("Carrier heading changed by %d degrees. Turning=%s.",deltaHeading,tostring(turning))) +Hchange=true +end +local dist=pos:Get2DDistance(self.position) +local Dchange=false +if dist>self.Dupdate then +self:T(self.lid..string.format("Carrier position changed by %.1f NM. Turning=%s.",UTILS.MetersToNM(dist),tostring(turning))) +Dchange=true +end +local update=false +if self:IsRunning()and dt>self.dTupdate and not turning then +if Hchange or Dchange then +local text=string.format("Updating tanker %s pattern due to carrier position=%s or heading=%s change.",self.tanker:GetName(),tostring(Dchange),tostring(Hchange)) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +self.orientation=vNew +self.position=pos +update=true +end +end +return update +end +function RECOVERYTANKER:_ActivateTACAN(delay) +if delay and delay>0 then +self:ScheduleOnce(delay,RECOVERYTANKER._ActivateTACAN,self) +else +local unit=self.tanker:GetUnit(1) +if unit and unit:IsAlive()then +local text=string.format("Activating TACAN beacon: channel=%d mode=%s, morse=%s.",self.TACANchannel,self.TACANmode,self.TACANmorse) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +self.beacon=BEACON:New(unit) +self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true) +else +self:E(self.lid.."ERROR: Recovery tanker is not alive!") +end +end +end +function RECOVERYTANKER:_Pattern() +local hdg=self.carrier:GetHeading() +local alt=self.altitude +local Carrier=self.carrier:GetCoordinate() +local width=UTILS.NMToMeters(8) +local p={} +p[1]=self.tanker:GetCoordinate() +p[2]=Carrier:SetAltitude(alt) +p[3]=p[2]:Translate(self.distBow,hdg) +p[4]=p[3]:Translate(width/math.sqrt(2),hdg-45) +p[5]=p[3]:Translate(width,hdg-90) +p[6]=p[5]:Translate(self.distStern-self.distBow,hdg) +p[7]=p[2]:Translate(self.distStern,hdg) +local wp={} +for i=1,#p do +local coord=p[i] +coord:MarkToAll(string.format("Waypoint %d",i)) +table.insert(wp,coord:WaypointAirTurningPoint(nil,UTILS.MpsToKmph(self.speed))) +end +return wp +end +RESCUEHELO={ +ClassName="RESCUEHELO", +Debug=false, +lid=nil, +carrier=nil, +carriertype=nil, +helogroupname=nil, +helo=nil, +airbase=nil, +takeoff=nil, +followset=nil, +formation=nil, +lowfuel=nil, +altitude=nil, +offsetX=nil, +offsetZ=nil, +rescuezone=nil, +respawn=nil, +respawninair=nil, +uncontrolledac=nil, +rescueon=nil, +rescueduration=nil, +rescuespeed=nil, +rescuestopboat=nil, +HeloFuel0=nil, +rtb=nil, +carrierstop=nil, +alias=nil, +uid=0, +modex=nil, +dtFollow=nil, +} +_RESCUEHELOID=0 +RESCUEHELO.version="1.1.0" +function RESCUEHELO:New(carrierunit,helogroupname) +local self=BASE:Inherit(self,FSM:New()) +if type(carrierunit)=="string"then +self.carrier=UNIT:FindByName(carrierunit) +else +self.carrier=carrierunit +end +self.carriertype=self.carrier:GetTypeName() +self.helogroupname=helogroupname +_RESCUEHELOID=_RESCUEHELOID+1 +self.uid=_RESCUEHELOID +self.carrier:SetState(self.carrier,string.format("RESCUEHELO_%d",self.uid),self) +self.alias=string.format("%s_%s_%02d",self.carrier:GetName(),self.helogroupname,_RESCUEHELOID) +self.lid=string.format("RESCUEHELO %s | ",self.alias) +self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) +self:SetTakeoffHot() +self:SetLowFuelThreshold() +self:SetAltitude() +self:SetOffsetX() +self:SetOffsetZ() +self:SetRespawnOn() +self:SetRescueOn() +self:SetRescueZone() +self:SetRescueHoverSpeed() +self:SetRescueDuration() +self:SetFollowTimeInterval() +self:SetRescueStopBoatOff() +self.rtb=false +self.carrierstop=false +if false then +self.Debug=true +BASE:TraceOnOff(true) +BASE:TraceClass(self.ClassName) +BASE:TraceLevel(1) +end +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("Running","Rescue","Rescuing") +self:AddTransition("Running","RTB","Returning") +self:AddTransition("Rescuing","RTB","Returning") +self:AddTransition("Returning","Returned","Returned") +self:AddTransition("Running","Run","Running") +self:AddTransition("Returned","Run","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","Stop","Stopped") +return self +end +function RESCUEHELO:SetLowFuelThreshold(threshold) +self.lowfuel=threshold or 5 +return self +end +function RESCUEHELO:SetHomeBase(airbase) +if type(airbase)=="string"then +self.airbase=AIRBASE:FindByName(airbase) +else +self.airbase=airbase +end +if not self.airbase then +self:E(self.lid.."ERROR: Airbase is nil!") +end +return self +end +function RESCUEHELO:SetRescueZone(radius) +radius=UTILS.NMToMeters(radius or 15) +self.rescuezone=ZONE_UNIT:New("Rescue Zone",self.carrier,radius) +return self +end +function RESCUEHELO:SetRescueHoverSpeed(speed) +self.rescuespeed=UTILS.KnotsToMps(speed or 5) +return self +end +function RESCUEHELO:SetRescueDuration(duration) +self.rescueduration=(duration or 5)*60 +return self +end +function RESCUEHELO:SetRescueOn() +self.rescueon=true +return self +end +function RESCUEHELO:SetRescueOff() +self.rescueon=false +return self +end +function RESCUEHELO:SetRescueStopBoatOn() +self.rescuestopboat=true +return self +end +function RESCUEHELO:SetRescueStopBoatOff() +self.rescuestopboat=false +return self +end +function RESCUEHELO:SetTakeoff(takeofftype) +self.takeoff=takeofftype or SPAWN.Takeoff.Hot +return self +end +function RESCUEHELO:SetTakeoffHot() +self:SetTakeoff(SPAWN.Takeoff.Hot) +return self +end +function RESCUEHELO:SetTakeoffCold() +self:SetTakeoff(SPAWN.Takeoff.Cold) +return self +end +function RESCUEHELO:SetTakeoffAir() +self:SetTakeoff(SPAWN.Takeoff.Air) +return self +end +function RESCUEHELO:SetAltitude(alt) +self.altitude=alt or 70 +return self +end +function RESCUEHELO:SetOffsetX(distance) +self.offsetX=distance or 200 +return self +end +function RESCUEHELO:SetOffsetZ(distance) +self.offsetZ=distance or 240 +return self +end +function RESCUEHELO:SetRespawnOn() +self.respawn=true +return self +end +function RESCUEHELO:SetRespawnOff() +self.respawn=false +return self +end +function RESCUEHELO:SetRespawnOnOff(switch) +if switch==nil or switch==true then +self.respawn=true +else +self.respawn=false +end +return self +end +function RESCUEHELO:SetRespawnInAir() +self.respawninair=true +return self +end +function RESCUEHELO:SetModex(modex) +self.modex=modex +return self +end +function RESCUEHELO:SetFollowTimeInterval(dt) +self.dtFollow=dt or 1.0 +return self +end +function RESCUEHELO:SetUseUncontrolledAircraft() +self.uncontrolledac=true +return self +end +function RESCUEHELO:SetDebugModeON() +self.Debug=true +return self +end +function RESCUEHELO:SetDebugModeOFF() +self.Debug=false +return self +end +function RESCUEHELO:IsReturning() +return self:is("Returning") +end +function RESCUEHELO:IsRunning() +return self:is("Running") +end +function RESCUEHELO:IsRescuing() +return self:is("Rescuing") +end +function RESCUEHELO:IsStopped() +return self:is("Stopped") +end +function RESCUEHELO:GetAlias() +return self.alias +end +function RESCUEHELO:GetUnitName() +local unit=self.helo:GetUnit(1) +if unit then +return unit:GetName() +end +return nil +end +function RESCUEHELO:OnEventLand(EventData) +local group=EventData.IniGroup +if group and group:IsAlive()then +local groupname=group:GetName() +if groupname==self.helo:GetName()then +local airbase=nil +local airbasename="unknown" +if EventData.Place then +airbase=EventData.Place +airbasename=airbase:GetName() +end +local text=string.format("Rescue helo group %s landed at airbase %s.",groupname,airbasename) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +if self:IsRescuing()then +self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.",groupname)) +end +if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then +if not self:IsRescuing()then +self:E(self.lid..string.format("WARNING: Rescue helo %s landed. This should not happen for Takeoff=Air or respawninair=true and no rescue operation in progress.",groupname)) +end +end +self:__Returned(3,airbase) +end +end +end +function RESCUEHELO:_OnEventCrashOrEject(EventData) +self:F2({eventdata=EventData}) +if EventData and EventData.IniUnit then +local unit=EventData.IniUnit +local unitname=tostring(EventData.IniUnitName) +if EventData.IniGroupName~=self.helo:GetName()then +local text=string.format("Unit %s crashed or ejected.",unitname) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +local Vec3=EventData.IniDCSUnit:getPoint() +local coord=COORDINATE:NewFromVec3(Vec3) +if coord and self.rescuezone:IsCoordinateInZone(coord)then +if self.Debug then +coord:MarkToCoalition(self.lid..string.format("Crash site of unit %s.",unitname),self.helo:GetCoalition()) +end +local rightcoalition=EventData.IniGroup:GetCoalition()==self.helo:GetCoalition() +if self:IsRunning()and self.rescueon and rightcoalition then +self:Rescue(coord) +end +end +else +self:E(self.lid..string.format("Rescue helo %s crashed!",unitname)) +self:Stop() +if self.respawn then +self:__Start(5) +end +end +end +end +function RESCUEHELO:onafterStart(From,Event,To) +local text=string.format("Starting Rescue Helo Formation v%s for carrier unit %s of type %s.",RESCUEHELO.version,self.carrier:GetName(),self.carriertype) +self:I(self.lid..text) +self:HandleEvent(EVENTS.Land) +self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrEject) +self:HandleEvent(EVENTS.Ejection,self._OnEventCrashOrEject) +local delay=120 +local Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) +Spawn:InitModex(self.modex) +if self.takeoff==SPAWN.Takeoff.Air then +local hdg=self.carrier:GetHeading() +local dist=UTILS.NMToMeters(0.2) +local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg):SetAltitude(math.max(100,self.altitude)) +Spawn:InitHeading(hdg) +self.helo=Spawn:SpawnFromCoordinate(Carrier) +delay=1 +else +if self.uncontrolledac then +self.helo=GROUP:FindByName(self.helogroupname) +if self.helo and self.helo:IsAlive()then +self.helo:StartUncontrolled() +delay=60 +else +self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) +return +end +else +self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) +if self.takeoff==SPAWN.Takeoff.Runway then +delay=5 +elseif self.takeoff==SPAWN.Takeoff.Hot then +delay=30 +elseif self.takeoff==SPAWN.Takeoff.Cold then +delay=60 +end +end +end +self.followset=SET_GROUP:New() +self.followset:AddGroup(self.helo) +self.HeloFuel0=self.helo:GetFuel() +self.formation=AI_FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier","Follow Carrier at given parameters.") +self.formation:FormationCenterWing(-self.offsetX,50,math.abs(self.altitude),50,self.offsetZ,50) +self.formation:SetFollowTimeInterval(self.dtFollow) +self.formation:SetFlightModeFormation(self.helo) +self.formation:__Start(delay) +self:__Status(1) +return self +end +function RESCUEHELO:onafterStatus(From,Event,To) +local time=timer.getTime() +if self.helo and self.helo:IsAlive()then +local fuel=self.helo:GetFuel()*100 +local fuelrel=fuel/self.HeloFuel0 +local life=self.helo:GetUnit(1):GetLife() +local life0=self.helo:GetUnit(1):GetLife0() +local lifeR=self.helo:GetUnit(1):GetLifeRelative() +local text=string.format("Rescue Helo %s: state=%s fuel=%.1f, rel.fuel=%.1f, life=%.1f/%.1f=%d",self.helo:GetName(),self:GetState(),fuel,fuelrel,life,life0,lifeR*100) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +if self:IsRunning()then +if fuelheight+25 then +dust=true +visibility=math.min(visibility,dustdens) +end +else +local fvis=world.weather.getFogVisibilityDistance() +local fheight=world.weather.getFogThickness() +if fvis>0 and fheight>height+25 then +fog=true +visibility=math.min(visibility,fvis) +end +end +local VISIBILITY="" +if self.metric then +local reportedviz=UTILS.Round(visibility/1000) +if reportedviz>10 then +reportedviz=10 +end +VISIBILITY=string.format("%d",reportedviz) +else +local reportedviz=UTILS.Round(UTILS.MetersToSM(visibility)) +if reportedviz>10 then +reportedviz=10 +end +VISIBILITY=string.format("%d",reportedviz) +end +local cloudbase=clouds.base +local cloudceil=clouds.base+clouds.thickness +local clouddens=clouds.density +local cloudspreset=clouds.preset or"Nothing" +local precepitation=0 +if cloudspreset:find("RainyPreset1")then +clouddens=9 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("RainyPreset2")then +clouddens=9 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("RainyPreset3")then +clouddens=9 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("RainyPreset4")then +clouddens=5 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("RainyPreset5")then +clouddens=5 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("RainyPreset6")then +clouddens=5 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("NEWRAINPRESET4")then +clouddens=5 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("RainyPreset")then +clouddens=9 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("Preset10")then +clouddens=4 +elseif cloudspreset:find("Preset11")then +clouddens=4 +elseif cloudspreset:find("Preset12")then +clouddens=4 +elseif cloudspreset:find("Preset13")then +clouddens=7 +elseif cloudspreset:find("Preset14")then +clouddens=7 +elseif cloudspreset:find("Preset15")then +clouddens=7 +elseif cloudspreset:find("Preset16")then +clouddens=7 +elseif cloudspreset:find("Preset17")then +clouddens=7 +elseif cloudspreset:find("Preset18")then +clouddens=7 +elseif cloudspreset:find("Preset19")then +clouddens=7 +elseif cloudspreset:find("Preset20")then +clouddens=7 +elseif cloudspreset:find("Preset21")then +clouddens=9 +elseif cloudspreset:find("Preset22")then +clouddens=9 +elseif cloudspreset:find("Preset23")then +clouddens=9 +elseif cloudspreset:find("Preset24")then +clouddens=9 +elseif cloudspreset:find("Preset25")then +clouddens=9 +elseif cloudspreset:find("Preset26")then +clouddens=9 +elseif cloudspreset:find("Preset27")then +clouddens=9 +elseif cloudspreset:find("Preset1")then +clouddens=1 +elseif cloudspreset:find("Preset2")then +clouddens=1 +elseif cloudspreset:find("Preset3")then +clouddens=4 +elseif cloudspreset:find("Preset4")then +clouddens=4 +elseif cloudspreset:find("Preset5")then +clouddens=4 +elseif cloudspreset:find("Preset6")then +clouddens=4 +elseif cloudspreset:find("Preset7")then +clouddens=4 +elseif cloudspreset:find("Preset8")then +clouddens=4 +elseif cloudspreset:find("Preset9")then +clouddens=4 +else +self:E(string.format("WARNING! Unknown weather preset: %s",tostring(cloudspreset))) +end +local CLOUDBASE=string.format("%d",UTILS.MetersToFeet(cloudbase)) +local CLOUDCEIL=string.format("%d",UTILS.MetersToFeet(cloudceil)) +if self.metric then +CLOUDBASE=string.format("%d",cloudbase) +CLOUDCEIL=string.format("%d",cloudceil) +end +local CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudbase)) +local CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudceil)) +if self.metric then +CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(cloudbase) +CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(cloudceil) +end +local CloudCover={} +CloudCover=self.Sound.CloudsNotAvailable +local CLOUDSsub=self.gettext:GetEntry("NOCLOUDINFO",self.locale) +if static then +if clouddens>=9 then +CloudCover=self.Sound.CloudsOvercast +CLOUDSsub=self.gettext:GetEntry("OVERCAST",self.locale) +elseif clouddens>=7 then +CloudCover=self.Sound.CloudsBroken +CLOUDSsub=self.gettext:GetEntry("BROKEN",self.locale) +elseif clouddens>=4 then +CloudCover=self.Sound.CloudsScattered +CLOUDSsub=self.gettext:GetEntry("SCATTERED",self.locale) +elseif clouddens>=1 then +CloudCover=self.Sound.CloudsFew +CLOUDSsub=self.gettext:GetEntry("FEWCLOUDS",self.locale) +else +CLOUDBASE=nil +CLOUDCEIL=nil +CloudCover=self.Sound.CloudsNo +CLOUDSsub=self.gettext:GetEntry("NOCLOUDS",self.locale) +end +end +local subtitle="" +subtitle=string.format("%s",self.airbasename) +if(not self.ATISforFARPs)and self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil +and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil +and self.airbasename:find("Field")==nil +then +subtitle=subtitle.." "..self.gettext:GetEntry("AIRPORT",self.locale) +end +if not self.useSRS then +self.radioqueue:NewTransmission(string.format("%s.ogg",self.airbasename),3.0,self.soundpathAirports,nil,nil,subtitle,self.subduration) +end +local alltext=subtitle +local information=self.gettext:GetEntry("INFORMATION",self.locale) +subtitle=string.format("%s %s",information,NATO) +local _INFORMATION=subtitle +if not self.useSRS then +self:Transmission(self.Sound.Information,0.5,subtitle) +self.radioqueue:NewTransmission(string.format("%s.ogg",NATO),0.75,self.soundpathNato) +end +alltext=alltext..";\n"..subtitle +subtitle=string.format("%s Zulu",ZULU) +if not self.useSRS then +self.radioqueue:Number2Transmission(ZULU,nil,0.5) +self:Transmission(self.Sound.Zulu,0.2,subtitle) +end +alltext=alltext..";\n"..subtitle +if not self.zulutimeonly then +local sunrise=self.gettext:GetEntry("SUNRISEAT",self.locale) +subtitle=string.format(sunrise,SUNRISE) +if not self.useSRS and NorthPolar==false then +self:Transmission(self.Sound.SunriseAt,0.5,subtitle) +self.radioqueue:Number2Transmission(SUNRISE,nil,0.2) +self:Transmission(self.Sound.TimeLocal,0.2) +end +alltext=alltext..";\n"..subtitle +local sunset=self.gettext:GetEntry("SUNSETAT",self.locale) +subtitle=string.format(sunset,SUNSET) +if not self.useSRS and NorthPolar==false then +self:Transmission(self.Sound.SunsetAt,0.5,subtitle) +self.radioqueue:Number2Transmission(SUNSET,nil,0.5) +self:Transmission(self.Sound.TimeLocal,0.2) +end +alltext=alltext..";\n"..subtitle +end +if self.useSRS then +WINDFROM=string.gsub(WINDFROM,".","%1 ") +end +if self.metric then +local windfrom=self.gettext:GetEntry("WINDFROMMS",self.locale) +subtitle=string.format(windfrom,WINDFROM,WINDSPEED) +else +local windfrom=self.gettext:GetEntry("WINDFROMKNOTS",self.locale) +subtitle=string.format(windfrom,WINDFROM,WINDSPEED) +end +if turbulence>0 then +subtitle=subtitle..", "..self.gettext:GetEntry("GUSTING",self.locale) +end +local _WIND=subtitle +if not self.useSRS then +self:Transmission(self.Sound.WindFrom,1.0,subtitle) +self.radioqueue:Number2Transmission(WINDFROM) +self:Transmission(self.Sound.At,0.2) +self.radioqueue:Number2Transmission(WINDSPEED) +if self.metric then +self:Transmission(self.Sound.MetersPerSecond,0.2) +else +self:Transmission(self.Sound.Knots,0.2) +end +if turbulence>0 then +self:Transmission(self.Sound.Gusting,0.2) +end +end +alltext=alltext..";\n"..subtitle +if self.metric then +local visi=self.gettext:GetEntry("VISIKM",self.locale) +subtitle=string.format(visi,VISIBILITY) +else +local visi=self.gettext:GetEntry("VISISM",self.locale) +subtitle=string.format(visi,VISIBILITY) +end +if not self.useSRS then +self:Transmission(self.Sound.Visibilty,1.0,subtitle) +self.radioqueue:Number2Transmission(VISIBILITY) +if self.metric then +self:Transmission(self.Sound.Kilometers,0.2) +else +self:Transmission(self.Sound.StatuteMiles,0.2) +end +end +alltext=alltext..";\n"..subtitle +subtitle="" +local wp=false +local wpsub="" +if precepitation==1 then +wp=true +wpsub=wpsub.." "..self.gettext:GetEntry("RAIN",self.locale) +elseif precepitation==2 then +if wp then +wpsub=wpsub.."," +end +wpsub=wpsub.." "..self.gettext:GetEntry("TSTORM",self.locale) +wp=true +elseif precepitation==3 then +wpsub=wpsub.." "..self.gettext:GetEntry("SNOW",self.locale) +wp=true +elseif precepitation==4 then +wpsub=wpsub.." "..self.gettext:GetEntry("SSTROM",self.locale) +wp=true +end +if fog then +if wp then +wpsub=wpsub.."," +end +wpsub=wpsub.." "..self.gettext:GetEntry("FOG",self.locale) +wp=true +end +if dust then +if wp then +wpsub=wpsub.."," +end +wpsub=wpsub.." "..self.gettext:GetEntry("DUST",self.locale) +wp=true +end +if wp then +local phenos=self.gettext:GetEntry("PHENOMENA",self.locale) +subtitle=string.format("%s: %s",phenos,wpsub) +if not self.useSRS then +self:Transmission(self.Sound.WeatherPhenomena,1.0,subtitle) +if precepitation==1 then +self:Transmission(self.Sound.Rain,0.5) +elseif precepitation==2 then +self:Transmission(self.Sound.ThunderStorm,0.5) +elseif precepitation==3 then +self:Transmission(self.Sound.Snow,0.5) +elseif precepitation==4 then +self:Transmission(self.Sound.SnowStorm,0.5) +end +if fog then +self:Transmission(self.Sound.Fog,0.5) +end +if dust then +self:Transmission(self.Sound.Dust,0.5) +end +end +alltext=alltext..";\n"..subtitle +end +if not self.useSRS then +self:Transmission(CloudCover,1.0,CLOUDSsub) +end +if CLOUDBASE and static then +local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100) +local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100) +if self.metric then +local cloudbase=self.gettext:GetEntry("CLOUDBASEM",self.locale) +subtitle=string.format(cloudbase,cbase,cceil) +else +local cloudbase=self.gettext:GetEntry("CLOUDBASEFT",self.locale) +subtitle=string.format(cloudbase,cbase,cceil) +end +if not self.useSRS then +self:Transmission(self.Sound.CloudBase,1.0,subtitle) +if tonumber(CLOUDBASE1000)>0 then +self.radioqueue:Number2Transmission(CLOUDBASE1000) +self:Transmission(self.Sound.Thousand,0.1) +end +if tonumber(CLOUDBASE0100)>0 then +self.radioqueue:Number2Transmission(CLOUDBASE0100) +self:Transmission(self.Sound.Hundred,0.1) +end +self:Transmission(self.Sound.CloudCeiling,0.5) +if tonumber(CLOUDCEIL1000)>0 then +self.radioqueue:Number2Transmission(CLOUDCEIL1000) +self:Transmission(self.Sound.Thousand,0.1) +end +if tonumber(CLOUDCEIL0100)>0 then +self.radioqueue:Number2Transmission(CLOUDCEIL0100) +self:Transmission(self.Sound.Hundred,0.1) +end +if self.metric then +self:Transmission(self.Sound.Meters,0.1) +else +self:Transmission(self.Sound.Feet,0.1) +end +end +end +alltext=alltext..";\n"..subtitle +subtitle="" +local temptext=self.gettext:GetEntry("TEMPERATURE",self.locale) +if self.TDegF then +if temperature<0 then +subtitle=string.format("%s -%s °F",temptext,TEMPERATURE) +else +subtitle=string.format("%s %s °F",temptext,TEMPERATURE) +end +else +if temperature<0 then +subtitle=string.format("%s -%s °C",temptext,TEMPERATURE) +else +subtitle=string.format("%s %s °C",temptext,TEMPERATURE) +end +end +local _TEMPERATURE=subtitle +if not self.useSRS then +self:Transmission(self.Sound.Temperature,1.0,subtitle) +if temperature<0 then +self:Transmission(self.Sound.Minus,0.2) +end +self.radioqueue:Number2Transmission(TEMPERATURE) +if self.TDegF then +self:Transmission(self.Sound.DegreesFahrenheit,0.2) +else +self:Transmission(self.Sound.DegreesCelsius,0.2) +end +end +alltext=alltext..";\n"..subtitle +local dewtext=self.gettext:GetEntry("DEWPOINT",self.locale) +if self.TDegF then +if dewpoint<0 then +subtitle=string.format("%s -%s °F",dewtext,DEWPOINT) +else +subtitle=string.format("%s %s °F",dewtext,DEWPOINT) +end +else +if dewpoint<0 then +subtitle=string.format("%s -%s °C",dewtext,DEWPOINT) +else +subtitle=string.format("%s %s °C",dewtext,DEWPOINT) +end +end +local _DEWPOINT=subtitle +if not self.useSRS then +self:Transmission(self.Sound.DewPoint,1.0,subtitle) +if dewpoint<0 then +self:Transmission(self.Sound.Minus,0.2) +end +self.radioqueue:Number2Transmission(DEWPOINT) +if self.TDegF then +self:Transmission(self.Sound.DegreesFahrenheit,0.2) +else +self:Transmission(self.Sound.DegreesCelsius,0.2) +end +end +alltext=alltext..";\n"..subtitle +local altim=self.gettext:GetEntry("ALTIMETER",self.locale) +if self.PmmHg then +if self.qnhonly then +subtitle=string.format("%s %s.%s mmHg",altim,QNH[1],QNH[2]) +else +subtitle=string.format("%s: QNH %s.%s, QFE %s.%s mmHg",altim,QNH[1],QNH[2],QFE[1],QFE[2]) +end +else +if self.metric then +if self.qnhonly then +subtitle=string.format("%s %s.%s hPa",altim,QNH[1],QNH[2]) +else +subtitle=string.format("%s: QNH %s.%s, QFE %s.%s hPa",altim,QNH[1],QNH[2],QFE[1],QFE[2]) +end +else +if self.qnhonly then +subtitle=string.format("%s %s.%s inHg",altim,QNH[1],QNH[2]) +else +subtitle=string.format("%s: QNH %s.%s, QFE %s.%s inHg",altim,QNH[1],QNH[2],QFE[1],QFE[2]) +end +end +end +if self.ReportmBar and not self.metric then +if self.qnhonly then +subtitle=string.format("%s;\n%s %d hPa",subtitle,altim,mBarqnh) +else +subtitle=string.format("%s;\n%s: QNH %d, QFE %d hPa",subtitle,altim,mBarqnh,mBarqfe) +end +end +local _ALTIMETER=subtitle +if not self.useSRS then +self:Transmission(self.Sound.Altimeter,1.0,subtitle) +if not self.qnhonly then +self:Transmission(self.Sound.QNH,0.5) +end +self.radioqueue:Number2Transmission(QNH[1]) +if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then +self:Transmission(self.Sound.Decimal,0.2) +end +self.radioqueue:Number2Transmission(QNH[2]) +if not self.qnhonly then +self:Transmission(self.Sound.QFE,0.75) +self.radioqueue:Number2Transmission(QFE[1]) +if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then +self:Transmission(self.Sound.Decimal,0.2) +end +self.radioqueue:Number2Transmission(QFE[2]) +end +if self.PmmHg then +self:Transmission(self.Sound.MillimetersOfMercury,0.1) +else +if self.metric then +self:Transmission(self.Sound.HectoPascal,0.1) +else +self:Transmission(self.Sound.InchesOfMercury,0.1) +end +end +end +alltext=alltext..";\n"..subtitle +local _RUNACT +if not self.ATISforFARPs then +local subtitle="" +if runwayLanding and runwayLanding~=runwayTakeoff then +local actrun=self.gettext:GetEntry("ACTIVELANDING",self.locale) +subtitle=string.format("%s %s",actrun,runwayLanding) +if rwyLandingLeft==true then +subtitle=subtitle.." "..self.gettext:GetEntry("LEFT",self.locale) +elseif rwyLandingLeft==false then +subtitle=subtitle.." "..self.gettext:GetEntry("RIGHT",self.locale) +end +alltext=alltext..";\n"..subtitle +if not self.useSRS then +self:Transmission(self.Sound.ActiveRunwayArrival,1.0,subtitle) +self.radioqueue:Number2Transmission(runwayLanding) +if rwyLandingLeft==true then +self:Transmission(self.Sound.Left,0.2) +elseif rwyLandingLeft==false then +self:Transmission(self.Sound.Right,0.2) +end +end +end +if runwayTakeoff then +local actrun=self.gettext:GetEntry("ACTIVERUN",self.locale) +subtitle=string.format("%s %s",actrun,runwayTakeoff) +if rwyTakeoffLeft==true then +subtitle=subtitle.." "..self.gettext:GetEntry("LEFT",self.locale) +elseif rwyTakeoffLeft==false then +subtitle=subtitle.." "..self.gettext:GetEntry("RIGHT",self.locale) +end +alltext=alltext..";\n"..subtitle +if not self.useSRS then +self:Transmission(self.Sound.ActiveRunwayDeparture,1.0,subtitle) +self.radioqueue:Number2Transmission(runwayTakeoff) +if rwyTakeoffLeft==true then +self:Transmission(self.Sound.Left,0.2) +elseif rwyTakeoffLeft==false then +self:Transmission(self.Sound.Right,0.2) +end +end +end +_RUNACT=subtitle +if self.rwylength then +local runact=self.airbase:GetActiveRunway(self.runwaym2t) +local length=runact.length +if not self.metric then +length=UTILS.MetersToFeet(length) +end +local L1000,L0100=self:_GetThousandsAndHundreds(length) +local rwyl=self.gettext:GetEntry("RWYLENGTH",self.locale) +local meters=self.gettext:GetEntry("METERS",self.locale) +local feet=self.gettext:GetEntry("FEET",self.locale) +local subtitle=string.format("%s %d",rwyl,length) +if self.metric then +subtitle=subtitle.." "..meters +else +subtitle=subtitle.." "..feet +end +if not self.useSRS then +self:Transmission(self.Sound.RunwayLength,1.0,subtitle) +if tonumber(L1000)>0 then +self.radioqueue:Number2Transmission(L1000) +self:Transmission(self.Sound.Thousand,0.1) +end +if tonumber(L0100)>0 then +self.radioqueue:Number2Transmission(L0100) +self:Transmission(self.Sound.Hundred,0.1) +end +if self.metric then +self:Transmission(self.Sound.Meters,0.1) +else +self:Transmission(self.Sound.Feet,0.1) +end +end +alltext=alltext..";\n"..subtitle +end +end +if self.elevation then +local elev=self.gettext:GetEntry("ELEVATION",self.locale) +local meters=self.gettext:GetEntry("METERS",self.locale) +local feet=self.gettext:GetEntry("FEET",self.locale) +local elevation=self.airbase:GetHeight() +if not self.metric then +elevation=UTILS.MetersToFeet(elevation) +end +local L1000,L0100=self:_GetThousandsAndHundreds(elevation) +local subtitle=string.format("%s %d",elev,elevation) +if self.metric then +subtitle=subtitle.." "..meters +else +subtitle=subtitle.." "..feet +end +if not self.useSRS then +self:Transmission(self.Sound.Elevation,1.0,subtitle) +if tonumber(L1000)>0 then +self.radioqueue:Number2Transmission(L1000) +self:Transmission(self.Sound.Thousand,0.1) +end +if tonumber(L0100)>0 then +self.radioqueue:Number2Transmission(L0100) +self:Transmission(self.Sound.Hundred,0.1) +end +if self.metric then +self:Transmission(self.Sound.Meters,0.1) +else +self:Transmission(self.Sound.Feet,0.1) +end +end +alltext=alltext..";\n"..subtitle +end +if self.towerfrequency then +local freqs="" +for i,freq in pairs(self.towerfrequency)do +freqs=freqs..string.format("%.3f MHz",freq) +if i<#self.towerfrequency then +freqs=freqs..", " +end +end +local twrfrq=self.gettext:GetEntry("TOWERFREQ",self.locale) +subtitle=string.format("%s %s",twrfrq,freqs) +if not self.useSRS then +self:Transmission(self.Sound.TowerFrequency,1.0,subtitle) +for _,freq in pairs(self.towerfrequency)do +local f=string.format("%.3f",freq) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(self.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(self.Sound.MegaHertz,0.2) +end +end +alltext=alltext..";\n"..subtitle +end +local ils=self:GetNavPoint(self.ils,runwayLanding,rwyLandingLeft) +if ils then +local ilstxt=self.gettext:GetEntry("ILSFREQ",self.locale) +subtitle=string.format("%s %.2f MHz",ilstxt,ils.frequency) +if not self.useSRS then +self:Transmission(self.Sound.ILSFrequency,1.0,subtitle) +local f=string.format("%.2f",ils.frequency) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(self.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(self.Sound.MegaHertz,0.2) +end +alltext=alltext..";\n"..subtitle +end +local ndb=self:GetNavPoint(self.ndbouter,runwayLanding,rwyLandingLeft) +if ndb then +local ndbtxt=self.gettext:GetEntry("OUTERNDB",self.locale) +subtitle=string.format("%s %.2f MHz",ndbtxt,ndb.frequency) +if not self.useSRS then +self:Transmission(self.Sound.OuterNDBFrequency,1.0,subtitle) +local f=string.format("%.2f",ndb.frequency) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(self.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(self.Sound.MegaHertz,0.2) +end +alltext=alltext..";\n"..subtitle +end +local ndb=self:GetNavPoint(self.ndbinner,runwayLanding,rwyLandingLeft) +if ndb then +local ndbtxt=self.gettext:GetEntry("INNERNDB",self.locale) +subtitle=string.format("%s %.2f MHz",ndbtxt,ndb.frequency) +if not self.useSRS then +self:Transmission(self.Sound.InnerNDBFrequency,1.0,subtitle) +local f=string.format("%.2f",ndb.frequency) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(self.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(self.Sound.MegaHertz,0.2) +end +alltext=alltext..";\n"..subtitle +end +if self.vor then +local vortxt=self.gettext:GetEntry("VORFREQ",self.locale) +local vorttstxt=self.gettext:GetEntry("VORFREQTTS",self.locale) +subtitle=string.format("%s %.2f MHz",vortxt,self.vor) +if self.useSRS then +subtitle=string.format("%s %.2f MHz",vorttstxt,self.vor) +end +if not self.useSRS then +self:Transmission(self.Sound.VORFrequency,1.0,subtitle) +local f=string.format("%.2f",self.vor) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(self.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(self.Sound.MegaHertz,0.2) +end +alltext=alltext..";\n"..subtitle +end +if self.tacan then +local tactxt=self.gettext:GetEntry("TACANCH",self.locale) +subtitle=string.format(tactxt,self.tacan) +if not self.useSRS then +self:Transmission(self.Sound.TACANChannel,1.0,subtitle) +self.radioqueue:Number2Transmission(tostring(self.tacan),nil,0.2) +self.radioqueue:NewTransmission("Xray.ogg",0.75,self.soundpathNato,nil,0.2) +end +alltext=alltext..";\n"..subtitle +end +if self.rsbn then +local rsbntxt=self.gettext:GetEntry("RSBNCH",self.locale) +subtitle=string.format("%s %d",rsbntxt,self.rsbn) +if not self.useSRS then +self:Transmission(self.Sound.RSBNChannel,1.0,subtitle) +self.radioqueue:Number2Transmission(tostring(self.rsbn),nil,0.2) +end +alltext=alltext..";\n"..subtitle +end +local ndb=self:GetNavPoint(self.prmg,runwayLanding,rwyLandingLeft) +if ndb then +local prmtxt=self.gettext:GetEntry("PRMGCH",self.locale) +subtitle=string.format("%s %d",prmtxt,ndb.frequency) +if not self.useSRS then +self:Transmission(self.Sound.PRMGChannel,1.0,subtitle) +self.radioqueue:Number2Transmission(tostring(ndb.frequency),nil,0.5) +end +alltext=alltext..";\n"..subtitle +end +if self.useSRS and self.AdditionalInformation then +alltext=alltext..";\n"..self.AdditionalInformation +end +local advtxt=self.gettext:GetEntry("ADVISE",self.locale) +subtitle=string.format("%s %s",advtxt,NATO) +if not self.useSRS then +self:Transmission(self.Sound.AdviceOnInitial,0.5,subtitle) +self.radioqueue:NewTransmission(string.format("%s.ogg",NATO),0.75,self.soundpathNato) +end +alltext=alltext..";\n"..subtitle +self:Report(alltext) +if self.usemarker then +self:UpdateMarker(_INFORMATION,_RUNACT,_WIND,_ALTIMETER,_TEMPERATURE) +end +end +function ATIS:onafterReport(From,Event,To,Text) +self:T({From,Event,To}) +self:T(self.lid..string.format("Report:\n%s",Text)) +if self.useSRS and self.msrs then +local text=string.gsub(Text,"[\r\n]","") +local statute=self.gettext:GetEntry("STATUTE",self.locale) +local degc=self.gettext:GetEntry("DEGREES",self.locale) +local degf=self.gettext:GetEntry("FAHRENHEIT",self.locale) +local inhg=self.gettext:GetEntry("INCHHG",self.locale) +local mmhg=self.gettext:GetEntry("MMHG",self.locale) +local hpa=self.gettext:GetEntry("HECTO",self.locale) +local emes=self.gettext:GetEntry("METERSPER",self.locale) +local tacan=self.gettext:GetEntry("TACAN",self.locale) +local farp=self.gettext:GetEntry("FARP",self.locale) +local text=string.gsub(text,"SM",statute) +text=string.gsub(text,"°C",degc) +text=string.gsub(text,"°F",degf) +text=string.gsub(text,"inHg",inhg) +text=string.gsub(text,"mmHg",mmhg) +text=string.gsub(text,"hPa",hpa) +text=string.gsub(text,"m/s",emes) +text=string.gsub(text,"TACAN",tacan) +text=string.gsub(text,"FARP",farp) +local delimiter=self.gettext:GetEntry("DELIMITER",self.locale) +if string.lower(self.locale)~="en"then +text=string.gsub(text,"(%d+)(%.)(%d+)","%1 "..delimiter.." %3") +end +local text=string.gsub(text,";"," . ") +self:T("SRS TTS: "..text) +local duration=MSRS.getSpeechTime(text,0.95) +self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2) +self.SRSText=text +end +end +function ATIS:OnEventBaseCaptured(EventData) +if EventData and EventData.Place then +local airbase=EventData.Place +if EventData.PlaceName==self.airbasename then +local NewCoalitionAirbase=airbase:GetCoalition() +if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then +self.msrs:SetCoalition(NewCoalitionAirbase) +end +end +end +end +function ATIS:UpdateMarker(information,runact,wind,altimeter,temperature) +if self.markerid then +self.airbase:GetCoordinate():RemoveMark(self.markerid) +end +local text="" +if type(self.frequency)=="table"then +local frequency=table.concat(self.frequency,"/") +local modulation=self.modulation +if type(modulation)=="table"then +modulation=table.concat(self.modulation,"/") +end +text=string.format("ATIS on %s %s, %s:\n",tostring(frequency),tostring(modulation),tostring(information)) +else +text=string.format("ATIS on %.3f %s, %s:\n",self.frequency,UTILS.GetModulationName(self.modulation),tostring(information)) +end +text=text..string.format("%s\n",tostring(runact)) +text=text..string.format("%s\n",tostring(wind)) +text=text..string.format("%s\n",tostring(altimeter)) +text=text..string.format("%s",tostring(temperature)) +self.markerid=self.airbase:GetCoordinate():MarkToAll(text,true) +return self.markerid +end +function ATIS:GetActiveRunway(Takeoff) +local runway=nil +if Takeoff then +runway=self.airbase:GetActiveRunwayTakeoff() +else +runway=self.airbase:GetActiveRunwayLanding() +end +if runway then +return runway.name,runway.isLeft +else +return nil,nil +end +end +function ATIS:GetMagneticRunway(windfrom) +local diffmin=nil +local runway=nil +for _,heading in pairs(self.runwaymag)do +local hdg=self:GetRunwayWithoutLR(heading) +local diff=UTILS.HdgDiff(windfrom,tonumber(hdg)*10) +if diffmin==nil or diff0 then +for _,_cargo in pairs(crates)do +local cgotype=_cargo:GetType() +if _cargo:WasDropped()and cgotype~=CTLD_CARGO.Enum.STATIC then +local ok=false +local chalk=_cargo:GetMark() +if chalk==nil then +ok=true +else +local tag=chalk.tag or"none" +local timestamp=chalk.timestamp or 0 +local gone=timer.getAbsTime()-timestamp +if gone>=self.marktimer then +ok=true +_cargo:WipeMark() +end +end +if ok then +local chalk={} +chalk.tag="Engineers" +chalk.timestamp=timer.getAbsTime() +_cargo:AddMark(chalk) +ind=ind+1 +table.insert(ctable,ind,_cargo) +end +end +end +end +if ind>0 then +local crate=ctable[1] +local static=crate:GetPositionable() +local crate_pos=static:GetCoordinate() +local gpos=group:GetCoordinate() +local distance=self:_GetDistance(gpos,crate_pos) +self:T(string.format("%s Distance to crate: %d",self.lid,distance)) +if distance>30 and distance~=-1 and self:IsStatus("Searching")then +group:RouteGroundTo(crate_pos,15,"Line abreast",1) +self.currwpt=crate_pos +self:Move() +elseif distance<=30 and distance~=-1 then +self:Arrive() +end +else +self:T(self.lid.."No crates in reach!") +end +return self +end +function CTLD_ENGINEERING:Move() +self:T(self.lid.."Move") +self:SetStatus("Moving") +local group=self.Group +local tgtpos=self.currwpt +local gpos=group:GetCoordinate() +local distance=self:_GetDistance(gpos,tgtpos) +self:T(string.format("%s Distance remaining: %d",self.lid,distance)) +if distance<=30 and distance~=-1 then +self:Arrive() +end +return self +end +function CTLD_ENGINEERING:Arrive() +self:T(self.lid.."Arrive") +self:SetStatus("Arrived") +self.currwpt=nil +local Grp=self.Group +Grp:RouteStop() +return self +end +function CTLD_ENGINEERING:_GetDistance(_point1,_point2) +self:T(self.lid.." _GetDistance") +if _point1 and _point2 then +local distance1=_point1:Get2DDistance(_point2) +local distance2=_point1:DistanceFromPointVec2(_point2) +if distance1 and type(distance1)=="number"then +return distance1 +elseif distance2 and type(distance2)=="number"then +return distance2 +else +self:E("*****Cannot calculate distance!") +self:E({_point1,_point2}) +return-1 +end +else +self:E("******Cannot calculate distance!") +self:E({_point1,_point2}) +return-1 +end +end +end +do +CTLD={ +ClassName="CTLD", +verbose=0, +lid="", +coalition=1, +coalitiontxt="blue", +PilotGroups={}, +CtldUnits={}, +FreeVHFFrequencies={}, +FreeUHFFrequencies={}, +FreeFMFrequencies={}, +CargoCounter=0, +Cargo_Troops={}, +Cargo_Crates={}, +Loaded_Cargo={}, +Spawned_Crates={}, +Spawned_Cargo={}, +CrateDistance=35, +PackDistance=35, +debug=false, +wpZones={}, +dropOffZones={}, +pickupZones={}, +DynamicCargo={}, +ChinookTroopCircleRadius=5, +TroopUnloadDistGround=5, +TroopUnloadDistGroundHerc=25, +TroopUnloadDistGroundHook=15, +TroopUnloadDistHoverHook=5, +TroopUnloadDistHover=1.5, +UserSetGroup=nil, +LoadedGroupsTable={}, +keeploadtable=true, +allowCATransport=false, +VehicleMoveFormation=AI.Task.VehicleFormation.VEE, +} +CTLD.RadioModulation={ +AM=0, +FM=1, +} +CTLD.CargoZoneType={ +LOAD="load", +DROP="drop", +MOVE="move", +SHIP="ship", +BEACON="beacon", +} +CTLD.UnitTypeCapabilities={ +["SA342Mistral"]={type="SA342Mistral",crates=false,troops=true,cratelimit=0,trooplimit=4,length=12,cargoweightlimit=400}, +["SA342L"]={type="SA342L",crates=false,troops=true,cratelimit=0,trooplimit=2,length=12,cargoweightlimit=400}, +["SA342M"]={type="SA342M",crates=false,troops=true,cratelimit=0,trooplimit=4,length=12,cargoweightlimit=400}, +["SA342Minigun"]={type="SA342Minigun",crates=false,troops=true,cratelimit=0,trooplimit=2,length=12,cargoweightlimit=400}, +["UH-1H"]={type="UH-1H",crates=true,troops=true,cratelimit=1,trooplimit=8,length=15,cargoweightlimit=700}, +["Mi-8MTV2"]={type="Mi-8MTV2",crates=true,troops=true,cratelimit=2,trooplimit=12,length=15,cargoweightlimit=3000}, +["Mi-8MT"]={type="Mi-8MT",crates=true,troops=true,cratelimit=2,trooplimit=12,length=15,cargoweightlimit=3000}, +["Ka-50"]={type="Ka-50",crates=false,troops=false,cratelimit=0,trooplimit=0,length=15,cargoweightlimit=0}, +["Ka-50_3"]={type="Ka-50_3",crates=false,troops=false,cratelimit=0,trooplimit=0,length=15,cargoweightlimit=0}, +["Mi-24P"]={type="Mi-24P",crates=true,troops=true,cratelimit=2,trooplimit=8,length=18,cargoweightlimit=700}, +["Mi-24V"]={type="Mi-24V",crates=true,troops=true,cratelimit=2,trooplimit=8,length=18,cargoweightlimit=700}, +["Hercules"]={type="Hercules",crates=true,troops=true,cratelimit=7,trooplimit=64,length=25,cargoweightlimit=19000}, +["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500}, +["MH-60R"]={type="MH-60R",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["SH-60B"]={type="SH-60B",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["AH-64D_BLK_II"]={type="AH-64D_BLK_II",crates=false,troops=true,cratelimit=0,trooplimit=2,length=17,cargoweightlimit=200}, +["Bronco-OV-10A"]={type="Bronco-OV-10A",crates=false,troops=true,cratelimit=0,trooplimit=5,length=13,cargoweightlimit=1450}, +["OH-6A"]={type="OH-6A",crates=false,troops=true,cratelimit=0,trooplimit=4,length=7,cargoweightlimit=550}, +["OH58D"]={type="OH58D",crates=false,troops=false,cratelimit=0,trooplimit=0,length=14,cargoweightlimit=400}, +["CH-47Fbl1"]={type="CH-47Fbl1",crates=true,troops=true,cratelimit=4,trooplimit=31,length=20,cargoweightlimit=10800}, +["MosquitoFBMkVI"]={type="MosquitoFBMkVI",crates=true,troops=false,cratelimit=2,trooplimit=0,length=13,cargoweightlimit=1800}, +["M 818"]={type="M 818",crates=true,troops=true,cratelimit=4,trooplimit=12,length=9,cargoweightlimit=4500}, +} +CTLD.FixedWingTypes={ +["Hercules"]="Hercules", +["Bronco"]="Bronco", +["Mosquito"]="Mosquito", +} +CTLD.version="1.3.38" +function CTLD:New(Coalition,Prefixes,Alias) +local self=BASE:Inherit(self,FSM:New()) +BASE:T({Coalition,Prefixes,Alias}) +if Coalition and type(Coalition)=="string"then +if Coalition=="blue"then +self.coalition=coalition.side.BLUE +self.coalitiontxt=Coalition +elseif Coalition=="red"then +self.coalition=coalition.side.RED +self.coalitiontxt=Coalition +elseif Coalition=="neutral"then +self.coalition=coalition.side.NEUTRAL +self.coalitiontxt=Coalition +else +self:E("ERROR: Unknown coalition in CTLD!") +end +else +self.coalition=Coalition +self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition)) +end +if Alias then +self.alias=tostring(Alias) +else +self.alias="UNHCR" +if self.coalition then +if self.coalition==coalition.side.RED then +self.alias="Red CTLD" +elseif self.coalition==coalition.side.BLUE then +self.alias="Blue CTLD" +end +end +end +self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","TroopsPickedUp","*") +self:AddTransition("*","TroopsExtracted","*") +self:AddTransition("*","CratesPickedUp","*") +self:AddTransition("*","TroopsDeployed","*") +self:AddTransition("*","TroopsRTB","*") +self:AddTransition("*","CratesDropped","*") +self:AddTransition("*","CratesBuild","*") +self:AddTransition("*","CratesRepaired","*") +self:AddTransition("*","CratesBuildStarted","*") +self:AddTransition("*","CratesRepairStarted","*") +self:AddTransition("*","CratesPacked","*") +self:AddTransition("*","HelicopterLost","*") +self:AddTransition("*","Load","*") +self:AddTransition("*","Loaded","*") +self:AddTransition("*","Save","*") +self:AddTransition("*","Stop","Stopped") +self.PilotGroups={} +self.CtldUnits={} +self.FreeVHFFrequencies={} +self.FreeUHFFrequencies={} +self.FreeFMFrequencies={} +self.UsedVHFFrequencies={} +self.UsedUHFFrequencies={} +self.UsedFMFrequencies={} +self.RadioSound="beacon.ogg" +self.RadioSoundFC3="beacon.ogg" +self.RadioPath="l10n/DEFAULT/" +self.pickupZones={} +self.dropOffZones={} +self.wpZones={} +self.shipZones={} +self.droppedBeacons={} +self.droppedbeaconref={} +self.droppedbeacontimeout=600 +self.useprecisecoordloads=true +self.Cargo_Crates={} +self.Cargo_Troops={} +self.Cargo_Statics={} +self.Loaded_Cargo={} +self.Spawned_Crates={} +self.Spawned_Cargo={} +self.MenusDone={} +self.DroppedTroops={} +self.DroppedCrates={} +self.CargoCounter=0 +self.CrateCounter=0 +self.TroopCounter=0 +self.Engineers=0 +self.EngineersInField={} +self.EngineerSearch=2000 +self.nobuildmenu=false +self.CrateDistance=35 +self.PackDistance=35 +self.ExtractFactor=3.33 +self.prefixes=Prefixes or{"Cargoheli"} +self.useprefix=true +self.maximumHoverHeight=15 +self.minimumHoverHeight=4 +self.forcehoverload=true +self.hoverautoloading=true +self.dropcratesanywhere=false +self.dropAsCargoCrate=false +self.smokedistance=2000 +self.movetroopstowpzone=true +self.movetroopsdistance=5000 +self.returntroopstobase=true +self.troopdropzoneradius=100 +self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE +self.enableHercules=false +self.enableFixedWing=false +self.FixedMinAngels=165 +self.FixedMaxAngels=2000 +self.FixedMaxSpeed=77 +self.validateAndRepositionUnits=false +self.suppressmessages=false +self.repairtime=300 +self.buildtime=300 +self.placeCratesAhead=false +self.cratecountry=country.id.GERMANY +self.pilotmustopendoors=false +if self.coalition==coalition.side.RED then +self.cratecountry=country.id.RUSSIA +end +self.enableLoadSave=false +self.filepath=nil +self.saveinterval=600 +self.eventoninject=true +self.keeploadtable=true +self.LoadedGroupsTable={} +self.usesubcats=false +self.subcats={} +self.subcatsTroop={} +self.showstockinmenuitems=false +self.onestepmenu=false +self.nobuildinloadzones=true +self.movecratesbeforebuild=true +self.surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} +self.enableChinookGCLoading=true +self.ChinookTroopCircleRadius=5 +self.UserSetGroup=nil +local AliaS=string.gsub(self.alias," ","_") +self.filename=string.format("CTLD_%s_Persist.csv",AliaS) +self.allowcratepickupagain=true +self.enableslingload=false +self.basetype="container_cargo" +self.SmokeColor=SMOKECOLOR.Red +self.FlareColor=FLARECOLOR.Red +for i=1,100 do +math.random() +end +self.allowCATransport=false +self.CATransportSet=nil +self:_GenerateVHFrequencies() +self:_GenerateUHFrequencies() +self:_GenerateFMFrequencies() +return self +end +function CTLD:_GetUnitCapabilities(Unit) +self:T(self.lid.." _GetUnitCapabilities") +local _unit=Unit +local unittype=_unit:GetTypeName() +local capabilities=self.UnitTypeCapabilities[unittype] +if not capabilities or capabilities=={}then +capabilities={} +capabilities.troops=false +capabilities.crates=false +capabilities.cratelimit=0 +capabilities.trooplimit=0 +capabilities.type="generic" +capabilities.length=20 +capabilities.cargoweightlimit=0 +end +return capabilities +end +function CTLD:AllowCATransport(OnOff,ClientSet) +self.allowCATransport=OnOff +self.CATransportSet=ClientSet +return self +end +function CTLD:_GenerateUHFrequencies() +self:T(self.lid.." _GenerateUHFrequencies") +self.FreeUHFFrequencies={} +self.FreeUHFFrequencies=UTILS.GenerateUHFrequencies(243,320) +return self +end +function CTLD:_GenerateFMFrequencies() +self:T(self.lid.." _GenerateFMrequencies") +self.FreeFMFrequencies={} +self.FreeFMFrequencies=UTILS.GenerateFMFrequencies() +return self +end +function CTLD:_GenerateVHFrequencies() +self:T(self.lid.." _GenerateVHFrequencies") +self.FreeVHFFrequencies={} +self.UsedVHFFrequencies={} +self.FreeVHFFrequencies=UTILS.GenerateVHFrequencies() +return self +end +function CTLD:SetTroopDropZoneRadius(Radius) +self:T(self.lid.." SetTroopDropZoneRadius") +local tradius=Radius or 100 +if tradius<25 then tradius=25 end +self.troopdropzoneradius=tradius +return self +end +function CTLD:AddPlayerTask(PlayerTask) +self:T(self.lid.." AddPlayerTask") +if not self.PlayerTaskQueue then +self.PlayerTaskQueue=FIFO:New() +end +self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr) +return self +end +function CTLD:_EventHandler(EventData) +self:T(string.format("%s Event = %d",self.lid,EventData.id)) +local event=EventData +if event.id==EVENTS.PlayerEnterAircraft or event.id==EVENTS.PlayerEnterUnit then +local _coalition=event.IniCoalition +if _coalition~=self.coalition then +return +end +local unitname=event.IniUnitName or"none" +self.MenusDone[unitname]=nil +local _unit=event.IniUnit +local _group=event.IniGroup +if _unit:IsHelicopter()or _group:IsHelicopter()then +local unitname=event.IniUnitName or"none" +self.Loaded_Cargo[unitname]=nil +self:_RefreshF10Menus() +end +if self:IsFixedWing(_unit)and self.enableFixedWing then +local unitname=event.IniUnitName or"none" +self.Loaded_Cargo[unitname]=nil +self:_RefreshF10Menus() +end +if _unit:IsGround()and self.allowCATransport then +local unitname=event.IniUnitName or"none" +self.Loaded_Cargo[unitname]=nil +self:_RefreshF10Menus() +end +return +elseif event.id==EVENTS.Land or event.id==EVENTS.Takeoff then +local unitname=event.IniUnitName +if self.CtldUnits[unitname]then +local _group=event.IniGroup +local _unit=event.IniUnit +self:_RefreshLoadCratesMenu(_group,_unit) +if self:IsFixedWing(_unit)and self.enableFixedWing then +self:_RefreshDropCratesMenu(_group,_unit) +end +end +elseif event.id==EVENTS.PlayerLeaveUnit or event.id==EVENTS.UnitLost then +local unitname=event.IniUnitName or"none" +if self.CtldUnits[unitname]then +local lostcargo=UTILS.DeepCopy(self.Loaded_Cargo[unitname]or{}) +self:__HelicopterLost(1,unitname,lostcargo) +end +self.CtldUnits[unitname]=nil +self.Loaded_Cargo[unitname]=nil +self.MenusDone[unitname]=nil +elseif event.id==EVENTS.NewDynamicCargo then +self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) +self.DynamicCargo[event.IniDynamicCargoName]=event.IniDynamicCargo +elseif event.id==EVENTS.DynamicCargoLoaded then +self:T(self.lid.."GC Loaded Event "..event.IniDynamicCargoName) +local dcargo=event.IniDynamicCargo +local client=CLIENT:FindByPlayerName(dcargo.Owner) +if client and client:IsAlive()then +local unitname=client:GetName()or"none" +local loaded={} +if self.Loaded_Cargo[unitname]then +loaded=self.Loaded_Cargo[unitname] +else +loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +end +loaded.Cratesloaded=loaded.Cratesloaded+1 +table.insert(loaded.Cargo,dcargo) +self.Loaded_Cargo[unitname]=nil +self.Loaded_Cargo[unitname]=loaded +local Group=client:GetGroup() +self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) +self:__CratesPickedUp(1,Group,client,dcargo) +end +elseif event.id==EVENTS.DynamicCargoUnloaded then +self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) +local dcargo=event.IniDynamicCargo +local client=CLIENT:FindByPlayerName(dcargo.Owner) +if client and client:IsAlive()then +local unitname=client:GetName()or"none" +local loaded={} +if self.Loaded_Cargo[unitname]then +loaded=self.Loaded_Cargo[unitname] +loaded.Cratesloaded=loaded.Cratesloaded-1 +if loaded.Cratesloaded<0 then loaded.Cratesloaded=0 end +local Loaded={} +for _,_item in pairs(loaded.Cargo or{})do +self:T(self.lid.."UNLOAD checking: ".._item:GetName()) +self:T(self.lid.."UNLOAD state: "..tostring(_item:WasDropped())) +if _item and _item:GetType()==CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName~=_item:GetName()and not _item:WasDropped()then +table.insert(Loaded,_item) +else +table.insert(Loaded,_item) +end +end +loaded.Cargo=nil +loaded.Cargo=Loaded +self.Loaded_Cargo[unitname]=nil +self.Loaded_Cargo[unitname]=loaded +else +loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +self.Loaded_Cargo[unitname]=loaded +end +local Group=client:GetGroup() +self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) +self:__CratesDropped(1,Group,client,{dcargo}) +end +elseif event.id==EVENTS.DynamicCargoRemoved then +self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) +self.DynamicCargo[event.IniDynamicCargoName]=nil +end +return self +end +function CTLD:_SendMessage(Text,Time,Clearscreen,Group) +self:T(self.lid.." _SendMessage") +if not self.suppressmessages then +local m=MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group) +end +return self +end +function CTLD:_FindTroopsCargoObject(Name) +self:T(self.lid.." _FindTroopsCargoObject") +local cargo=nil +for _,_cargo in pairs(self.Cargo_Troops)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end +return nil +end +function CTLD:_FindCratesCargoObject(Name) +self:T(self.lid.." _FindCratesCargoObject") +local cargo=nil +for _,_cargo in pairs(self.Cargo_Crates)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end +return nil +end +function CTLD:AddAllowedFixedWingType(typename) +if type(typename)=="string"then +self.FixedWingTypes[typename]=typename +elseif typename and typename.ClassName and typename:IsInstanceOf("UNIT")then +local TypeName=typename:GetTypeName()or"none" +self.FixedWingTypes[TypeName]=TypeName +else +self:E(self.lid.."No valid typename or no UNIT handed!") +end +return self +end +function CTLD:PreloadTroops(Unit,Troopname) +self:T(self.lid.." PreloadTroops") +local name=Troopname or"Unknown" +if Unit and Unit:IsAlive()then +local cargo=self:_FindTroopsCargoObject(name) +local group=Unit:GetGroup() +if cargo then +self:_LoadTroops(group,Unit,cargo,true) +else +self:E(self.lid.." Troops preload - Cargo Object "..name.." not found!") +end +end +return self +end +function CTLD:_PreloadCrates(Group,Unit,Cargo,NumberOfCrates) +local group=Group +local unit=Unit +local unitname=unit:GetName() +local unittype=unit:GetTypeName() +local capabilities=self:_GetUnitCapabilities(Unit) +local cancrates=capabilities.crates +local cratelimit=capabilities.cratelimit +if not cancrates then +self:_SendMessage("Sorry this chopper cannot carry crates!",10,false,Group) +return self +else +local numberonboard=0 +local massonboard=0 +local loaded={} +if self.Loaded_Cargo[unitname]then +loaded=self.Loaded_Cargo[unitname] +numberonboard=loaded.Cratesloaded or 0 +massonboard=self:_GetUnitCargoMass(Unit) +else +loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +end +local crate=Cargo +local numbercrates=NumberOfCrates or crate:GetCratesNeeded() +for i=1,numbercrates do +loaded.Cratesloaded=loaded.Cratesloaded+1 +crate:SetHasMoved(true) +crate:SetWasDropped(false) +table.insert(loaded.Cargo,crate) +crate.Positionable=nil +self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,false,Group) +self.Loaded_Cargo[unitname]=loaded +self:_UpdateUnitCargoMass(Unit) +end +end +return self +end +function CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates) +self:T(self.lid.." PreloadCrates") +local name=Cratesname or"Unknown" +if Unit and Unit:IsAlive()then +local cargo=self:_FindCratesCargoObject(name) +local group=Unit:GetGroup() +if cargo then +self:_PreloadCrates(group,Unit,cargo,NumberOfCrates) +else +self:E(self.lid.." Crates preload - Cargo Object "..name.." not found!") +end +end +return self +end +function CTLD:_LoadTroops(Group,Unit,Cargotype,Inject) +self:T(self.lid.." _LoadTroops") +local instock=Cargotype:GetStock() +local cgoname=Cargotype:GetName() +local cgotype=Cargotype:GetType() +local cgonetmass=Cargotype:GetNetMass() +local maxloadable=self:_GetMaxLoadableMass(Unit) +if type(instock)=="number"and tonumber(instock)<=0 and tonumber(instock)~=-1 and not Inject then +self:_SendMessage(string.format("Sorry, all %s are gone!",cgoname),10,false,Group) +return self +end +local grounded=not self:IsUnitInAir(Unit) +local hoverload=self:CanHoverLoad(Unit) +local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if not Inject then +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +if not self.debug then return self end +elseif not grounded and not hoverload then +self:_SendMessage("You need to land or hover in position to load!",10,false,Group) +if not self.debug then return self end +elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load troops!",10,false,Group) +if not self.debug then return self end +end +end +local group=Group +local unit=Unit +local unitname=unit:GetName() +local cargotype=Cargotype +local cratename=cargotype:GetName() +local unittype=unit:GetTypeName() +local capabilities=self:_GetUnitCapabilities(Unit) +local cantroops=capabilities.troops +local trooplimit=capabilities.trooplimit +local troopsize=cargotype:GetCratesNeeded() +local numberonboard=0 +local loaded={} +if self.Loaded_Cargo[unitname]then +loaded=self.Loaded_Cargo[unitname] +numberonboard=loaded.Troopsloaded or 0 +else +loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +end +if troopsize+numberonboard>trooplimit then +self:_SendMessage("Sorry, we\'re crammed already!",10,false,Group) +return +elseif maxloadableself.EngineerSearch then +self:_SendMessage("No unit close enough to repair!",10,false,Group) +return nil,nil +end +local groupname=nearestGroup:GetName() +local function matchstring(String,Table) +local match=false +String=string.gsub(String,"-"," ") +if type(Table)=="table"then +for _,_name in pairs(Table)do +_name=string.gsub(_name,"-"," ") +if string.find(String,_name)then +match=true +break +end +end +else +if type(String)=="string"then +Table=string.gsub(Table,"-"," ") +if string.find(String,Table)then match=true end +end +end +return match +end +local Cargotype=nil +for k,v in pairs(self.Cargo_Crates)do +if matchstring(groupname,v.Templates)and matchstring(groupname,Repairtype)then +Cargotype=v +break +end +end +if Cargotype==nil then +return nil,nil +else +return nearestGroup,Cargotype +end +end +function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering) +self:T(self.lid.." _RepairObjectFromCrates") +local build=Build +local Repairtype=build.Template +local NearestGroup,CargoType=self:_FindRepairNearby(Group,Unit,Repairtype) +if NearestGroup~=nil then +if self.repairtime<2 then self.repairtime=30 end +if not Engineering then +self:_SendMessage(string.format("Repair started using %s taking %d secs",build.Name,self.repairtime),10,false,Group) +end +local name=CargoType:GetName() +local required=CargoType:GetCratesNeeded() +local template=CargoType:GetTemplates() +local ctype=CargoType:GetType() +local object={} +object.Name=CargoType:GetName() +object.Required=required +object.Found=required +object.Template=template +object.CanBuild=true +object.Type=ctype +self:_CleanUpCrates(Crates,Build,Number) +local desttimer=TIMER:New(function()NearestGroup:Destroy(false)end,self) +desttimer:Start(self.repairtime-1) +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate()) +buildtimer:Start(self.repairtime) +self:__CratesRepairStarted(1,Group,Unit) +else +if not Engineering then +self:_SendMessage("Can't repair this unit with "..build.Name,10,false,Group) +else +self:T("Can't repair this unit with "..build.Name) +end +end +return self +end +function CTLD:_ExtractTroops(Group,Unit) +self:T(self.lid.." _ExtractTroops") +local grounded=not self:IsUnitInAir(Unit) +local hoverload=self:CanHoverLoad(Unit) +local hassecondaries=false +if not grounded and not hoverload then +self:_SendMessage("You need to land or hover in position to load!",10,false,Group) +if not self.debug then return self end +end +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to extract troops!",10,false,Group) +if not self.debug then return self end +end +local unit=Unit +local unitname=unit:GetName() +local unittype=unit:GetTypeName() +local capabilities=self:_GetUnitCapabilities(Unit) +local cantroops=capabilities.troops +local trooplimit=capabilities.trooplimit +local unitcoord=unit:GetCoordinate() +local nearestGroup=nil +local nearestGroupIndex=-1 +local nearestDistance=10000000 +local maxdistance=0 +local nearestList={} +local distancekeys={} +local extractdistance=self.CrateDistance*self.ExtractFactor +for k,v in pairs(self.DroppedTroops)do +local distance=self:_GetDistance(v:GetCoordinate(),unitcoord) +local TNow=timer.getTime() +local vtime=v.ExtractTime or TNow-310 +if distance<=extractdistance and distance~=-1 and(TNow-vtime>300)then +nearestGroup=v +nearestGroupIndex=k +nearestDistance=distance +if math.floor(distance)>maxdistance then maxdistance=math.floor(distance)end +if nearestList[math.floor(distance)]then +distance=maxdistance+1 +maxdistance=distance +end +table.insert(nearestList,math.floor(distance),v) +distancekeys[#distancekeys+1]=math.floor(distance) +end +end +if nearestGroup==nil or nearestDistance>extractdistance then +self:_SendMessage("No units close enough to extract!",10,false,Group) +return self +end +table.sort(distancekeys) +local secondarygroups={} +for i=1,#distancekeys do +local nearestGroup=nearestList[distancekeys[i]] +local groupType=string.match(nearestGroup:GetName(),"(.+)-(.+)$") +local Cargotype=nil +for k,v in pairs(self.Cargo_Troops)do +local comparison="" +if type(v.Templates)=="string"then comparison=v.Templates else comparison=v.Templates[1]end +if comparison==groupType then +Cargotype=v +break +end +end +if Cargotype==nil then +self:_SendMessage("Can't onboard "..groupType,10,false,Group) +else +local troopsize=Cargotype:GetCratesNeeded() +local numberonboard=0 +local loaded={} +if self.Loaded_Cargo[unitname]then +loaded=self.Loaded_Cargo[unitname] +numberonboard=loaded.Troopsloaded or 0 +else +loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +end +if troopsize+numberonboard>trooplimit then +self:_SendMessage("Sorry, we\'re crammed already!",10,false,Group) +nearestGroup.ExtractTime=0 +else +self.CargoCounter=self.CargoCounter+1 +nearestGroup.ExtractTime=timer.getTime() +local loadcargotype=CTLD_CARGO:New(self.CargoCounter,Cargotype.Name,Cargotype.Templates,Cargotype.CargoType,true,true,Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) +self:T({cargotype=loadcargotype}) +local running=math.floor(nearestDistance/4)+20 +loaded.Troopsloaded=loaded.Troopsloaded+troopsize +table.insert(loaded.Cargo,loadcargotype) +self.Loaded_Cargo[unitname]=loaded +self:ScheduleOnce(running,self._SendMessage,self,string.format("%s boarded!",Cargotype.Name),10,false,Group) +self:_SendMessage(string.format("%s boarding!",Cargotype.Name),10,false,Group) +self:_RefreshDropTroopsMenu(Group,Unit) +self:_UpdateUnitCargoMass(Unit) +local groupname=nearestGroup:GetName() +self:__TroopsExtracted(running,Group,Unit,nearestGroup,groupname) +local coord=Unit:GetCoordinate()or Group:GetCoordinate() +local Point +if coord then +local heading=unit:GetHeading()or 0 +local Angle=math.floor((heading+160)%360) +Point=coord:Translate(8,Angle):GetVec2() +if Point then +nearestGroup:RouteToVec2(Point,5) +end +end +hassecondaries=false +if type(Cargotype.Templates)=="table"and Cargotype.Templates[2]then +for _,_key in pairs(Cargotype.Templates)do +table.insert(secondarygroups,_key) +hassecondaries=true +end +end +local destroytimer=math.random(10,20) +nearestGroup:Destroy(false,destroytimer) +end +end +end +if hassecondaries==true then +for _,_name in pairs(secondarygroups)do +for _,_group in pairs(nearestList)do +if _group and _group:IsAlive()then +local groupname=string.match(_group:GetName(),"(.+)-(.+)$") +if _name==groupname then +_group:Destroy(false,15) +end +end +end +end +end +self:CleanDroppedTroops() +return self +end +function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +self:T(self.lid.." _GetCrates") +if not drop and not pack then +local cgoname=Cargo:GetName() +local instock=Cargo:GetStock() +if type(instock)=="number"and tonumber(instock)<=0 and tonumber(instock)~=-1 then +self:_SendMessage(string.format("Sorry, we ran out of %s",cgoname),10,false,Group) +return self +end +end +local inzone=false +local drop=drop or false +local ship=nil +local width=20 +local distance=nil +local zone=nil +if not drop and not pack then +inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +elseif drop and not pack then +if self.dropcratesanywhere then +inzone=true +else +inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) +end +elseif pack and not drop then +inzone=true +end +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +if not self.debug then return self end +end +local location=Cargo:GetLocation() +if location then +local unitcoord=Unit:GetCoordinate()or Group:GetCoordinate() +if unitcoord then +if not location:IsCoordinateInZone(unitcoord)then +self:_SendMessage("The requested cargo is not available in this zone!",10,false,Group) +if not self.debug then return self end +end +end +end +local capabilities=self:_GetUnitCapabilities(Unit) +local canloadcratesno=capabilities.cratelimit +local loaddist=self.CrateDistance or 35 +local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,true) +if numbernearby>=canloadcratesno and not drop then +self:_SendMessage("There are enough crates nearby already! Take care of those first!",10,false,Group) +return self +end +local IsHerc=self:IsFixedWing(Unit) +local IsHook=self:IsHook(Unit) +local IsTruck=Unit:IsGround() +local cargotype=Cargo +local number=number or cargotype:GetCratesNeeded() +local cratesneeded=cargotype:GetCratesNeeded() +local cratename=cargotype:GetName() +local cratetemplate="Container" +local cgotype=cargotype:GetType() +local cgomass=cargotype:GetMass() +local isstatic=false +if cgotype==CTLD_CARGO.Enum.STATIC then +cratetemplate=cargotype:GetTemplates() +isstatic=true +end +local position=Unit:GetCoordinate() +local heading=Unit:GetHeading()+1 +local height=Unit:GetHeight() +local droppedcargo={} +local cratedistance=0 +local rheading=0 +local angleOffNose=0 +local addon=0 +if IsHerc or IsHook or IsTruck then +addon=180 +end +heading=(heading+addon)%360 +local row=1 +local column=1 +local initialdist=IsHerc and 16 or(capabilities.length+2) +local startpos=position:Translate(initialdist,heading) +if self.placeCratesAhead==true then +cratedistance=initialdist +end +local cratecoord=nil +for i=1,number do +local cratealias=string.format("%s-%s-%d",cratename,cratetemplate,math.random(1,100000)) +if not self.placeCratesAhead or drop==true then +cratedistance=(i-1)*2.5+capabilities.length +if cratedistance>self.CrateDistance then cratedistance=self.CrateDistance end +if self:IsUnitInAir(Unit)and self:IsFixedWing(Unit)then +rheading=math.random(20,60) +else +rheading=UTILS.RandomGaussian(0,30,-90,90,100) +end +rheading=math.fmod((heading+rheading),360) +cratecoord=position:Translate(cratedistance,rheading) +else +cratedistance=(row-1)*6 +rheading=90 +row=row+1 +cratecoord=startpos:Translate(cratedistance,rheading) +if row>4 then +row=1 +startpos:Translate(6,heading,nil,true) +end +end +self.CrateCounter=self.CrateCounter+1 +local CCat,CType,CShape=Cargo:GetStaticTypeAndShape() +local basetype=CType or self.basetype or"container_cargo" +CCat=CCat or"Cargos" +if isstatic then +basetype=cratetemplate +end +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 dist=shipcoord:Get2DDistance(unitcoord) +dist=dist-(20+math.random(1,10)) +local width=width/2 +local Offy=math.random(-width,width) +local spawnstatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) +:InitCargoMass(cgomass) +:InitCargo(self.enableslingload) +:InitLinkToUnit(Ship,dist,Offy,0) +if CShape then +spawnstatic:InitShape(CShape) +end +if isstatic then +local map=cargotype:GetStaticResourceMap() +spawnstatic.TemplateStaticUnit.resourcePayload=map +end +self.Spawned_Crates[self.CrateCounter]=spawnstatic:Spawn(270,cratealias) +else +local spawnstatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) +:InitCoordinate(cratecoord) +:InitCargoMass(cgomass) +:InitCargo(self.enableslingload) +if CShape then +spawnstatic:InitShape(CShape) +end +if isstatic then +local map=cargotype:GetStaticResourceMap() +spawnstatic.TemplateStaticUnit.resourcePayload=map +end +self.Spawned_Crates[self.CrateCounter]=spawnstatic:Spawn(270,cratealias) +end +local templ=cargotype:GetTemplates() +local sorte=cargotype:GetType() +local subcat=cargotype.Subcategory +self.CargoCounter=self.CargoCounter+1 +local realcargo=nil +if drop then +realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) +local map=cargotype:GetStaticResourceMap() +realcargo:SetStaticResourceMap(map) +local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +if cargotype.TypeNames then +realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) +end +table.insert(droppedcargo,realcargo) +else +realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat) +local map=cargotype:GetStaticResourceMap() +realcargo:SetStaticResourceMap(map) +if cargotype.TypeNames then +realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) +end +end +local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +table.insert(self.Spawned_Cargo,realcargo) +end +if not(drop or pack)then +Cargo:RemoveStock() +end +local text=string.format("Crates for %s have been positioned near you!",cratename) +if drop then +text=string.format("Crates for %s have been dropped!",cratename) +self:__CratesDropped(1,Group,Unit,droppedcargo) +else +self:_SendMessage(text,10,false,Group) +end +self:_RefreshLoadCratesMenu(Group,Unit) +return self +end +function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) +self:T(self.lid.." InjectStatics") +local cratecoord=Zone:GetCoordinate() +if RandomCoord then +cratecoord=Zone:GetRandomCoordinate(5,20) +end +local surface=cratecoord:GetSurfaceType() +if surface==land.SurfaceType.WATER then +return self +end +local cargotype=Cargo +local cratesneeded=cargotype:GetCratesNeeded() +local cratetemplate="Container" +local cratename=cargotype:GetName() +local cgotype=cargotype:GetType() +local cgomass=cargotype:GetMass() +local cratenumber=cargotype:GetCratesNeeded()or 1 +if FromLoad==true then cratenumber=1 end +for i=1,cratenumber do +local cratealias=string.format("%s-%s-%d",cratename,cratetemplate,math.random(1,100000)) +local isstatic=false +if cgotype==CTLD_CARGO.Enum.STATIC then +cratetemplate=cargotype:GetTemplates() +isstatic=true +end +local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() +local basetype=CType or self.basetype or"container_cargo" +CCat=CCat or"Cargos" +if isstatic then +basetype=cratetemplate +end +self.CrateCounter=self.CrateCounter+1 +local spawnstatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) +:InitCargoMass(cgomass) +:InitCargo(self.enableslingload) +:InitCoordinate(cratecoord) +if CShape then +spawnstatic:InitShape(CShape) +end +if isstatic then +local map=cargotype:GetStaticResourceMap() +spawnstatic.TemplateStaticUnit.resourcePayload=map +end +self.Spawned_Crates[self.CrateCounter]=spawnstatic:Spawn(270,cratealias) +local templ=cargotype:GetTemplates() +local sorte=cargotype:GetType() +cargotype.Positionable=self.Spawned_Crates[self.CrateCounter] +table.insert(self.Spawned_Cargo,cargotype) +end +return self +end +function CTLD:InjectStaticFromTemplate(Zone,Template,Mass) +self:T(self.lid.." InjectStaticFromTemplate") +local cargotype=self:GetStaticsCargoFromTemplate(Template,Mass) +self:InjectStatics(Zone,cargotype,true,true) +return self +end +function CTLD:_ListCratesNearby(_group,_unit) +self:T(self.lid.." _ListCratesNearby") +local finddist=self.CrateDistance or 35 +local crates,number,loadedbygc,indexgc=self:_FindCratesNearby(_group,_unit,finddist,true,true) +if number>0 or indexgc>0 then +local text=REPORT:New("Crates Found Nearby:") +text:Add("------------------------------------------------------------") +for _,_entry in pairs(crates)do +local entry=_entry +local name=entry:GetName() +local dropped=entry:WasDropped() +if dropped then +text:Add(string.format("Dropped crate for %s, %dkg",name,entry.PerCrateMass)) +else +text:Add(string.format("Crate for %s, %dkg",name,entry.PerCrateMass)) +end +end +if text:GetCount()==1 then +text:Add(" N O N E") +end +text:Add("------------------------------------------------------------") +if indexgc>0 then +text:Add("Probably ground crew loadable (F8)") +for _,_entry in pairs(loadedbygc)do +local entry=_entry +local name=entry:GetName() +local dropped=entry:WasDropped() +if dropped then +text:Add(string.format("Dropped crate for %s, %dkg",name,entry.PerCrateMass)) +else +text:Add(string.format("Crate for %s, %dkg",name,entry.PerCrateMass)) +end +end +end +self:_SendMessage(text:Text(),30,true,_group) +else +self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) +end +return self +end +function CTLD:_RemoveCratesNearby(_group,_unit) +self:T(self.lid.." _RemoveCratesNearby") +local finddist=self.CrateDistance or 35 +local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true,true) +if number>0 then +local removedIDs={} +local text=REPORT:New("Removing Crates Found Nearby:") +text:Add("------------------------------------------------------------") +for _,_entry in pairs(crates)do +local entry=_entry +local name=entry:GetName()or"none" +text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) +if entry:GetPositionable()then +entry:GetPositionable():Destroy(false) +end +table.insert(removedIDs,entry:GetID()) +end +if text:GetCount()==1 then +text:Add(" N O N E") +end +text:Add("------------------------------------------------------------") +self:_SendMessage(text:Text(),30,true,_group) +self:_CleanupTrackedCrates(removedIDs) +self:_RefreshLoadCratesMenu(_group,_unit) +else +self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) +end +return self +end +function CTLD:_GetDistance(_point1,_point2) +self:T(self.lid.." _GetDistance") +if _point1 and _point2 then +local distance1=_point1:Get2DDistance(_point2) +local distance2=_point1:DistanceFromPointVec2(_point2) +if distance1 and type(distance1)=="number"then +return distance1 +elseif distance2 and type(distance2)=="number"then +return distance2 +else +self:E("*****Cannot calculate distance!") +self:E({_point1,_point2}) +return-1 +end +else +self:E("******Cannot calculate distance!") +self:E({_point1,_point2}) +return-1 +end +end +function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype) +self:T(self.lid.." _FindCratesNearby") +local finddist=_dist +local location=_group:GetCoordinate() +local existingcrates=self.Spawned_Cargo +local index=0 +local indexg=0 +local found={} +local LoadedbyGC={} +local loadedmass=0 +local unittype="none" +local capabilities={} +local maxloadable=2000 +local IsHook=self:IsHook(_unit) +if not _ignoreweight then +maxloadable=self:_GetMaxLoadableMass(_unit) +end +self:T(self.lid.." Max loadable mass: "..maxloadable) +for _,_cargoobject in pairs(existingcrates)do +local cargo=_cargoobject +local static=cargo:GetPositionable() +local weight=cargo:GetMass() +local staticid=cargo:GetID() +self:T(self.lid.." Found cargo mass: "..weight) +if static and static:IsAlive()then +local restricthooktononstatics=self.enableChinookGCLoading and IsHook +self:T(self.lid.." restricthooktononstatics: "..tostring(restricthooktononstatics)) +local cargoisstatic=cargo:GetType()==CTLD_CARGO.Enum.STATIC and true or false +self:T(self.lid.." Cargo is static: "..tostring(cargoisstatic)) +local restricted=cargoisstatic and restricthooktononstatics +self:T(self.lid.." Loading restricted: "..tostring(restricted)) +local staticpos=static:GetCoordinate() +local cando=cargo:UnitCanCarry(_unit) +if ignoretype==true then cando=true end +self:T(self.lid.." Unit can carry: "..tostring(cando)) +local distance=self:_GetDistance(location,staticpos) +self:T(self.lid..string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) +if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true then +index=index+1 +table.insert(found,staticid,cargo) +maxloadable=maxloadable-weight +end +end +end +return found,index,LoadedbyGC,indexg +end +function CTLD:_LoadCratesNearby(Group,Unit) +self:T(self.lid.." _LoadCratesNearby") +local group=Group +local unit=Unit +local unitname=unit:GetName() +local unittype=unit:GetTypeName() +local capabilities=self:_GetUnitCapabilities(Unit) +local cancrates=capabilities.crates +local cratelimit=capabilities.cratelimit +local grounded=not self:IsUnitInAir(Unit) +local canhoverload=self:CanHoverLoad(Unit) +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) +if not self.debug then return self end +end +if not cancrates then +self:_SendMessage("Sorry this chopper cannot carry crates!",10,false,Group) +elseif self.forcehoverload and not canhoverload then +self:_SendMessage("Hover over the crates to pick them up!",10,false,Group) +elseif not grounded and not canhoverload then +self:_SendMessage("Land or hover over the crates to pick them up!",10,false,Group) +else +local numberonboard=0 +local loaded={} +if self.Loaded_Cargo[unitname]then +loaded=self.Loaded_Cargo[unitname] +numberonboard=loaded.Cratesloaded or 0 +else +loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +end +local finddist=self.CrateDistance or 35 +local nearcrates,number=self:_FindCratesNearby(Group,Unit,finddist,false,false) +self:T(self.lid.." Crates found: "..number) +if number==0 and self.hoverautoloading then +return self +elseif number==0 then +self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!",10,false,Group) +return self +elseif numberonboard==cratelimit then +self:_SendMessage("Sorry, we are fully loaded!",10,false,Group) +return self +else +local capacity=cratelimit-numberonboard +local crateidsloaded={} +local crateMap={} +for _,cObj in pairs(nearcrates)do +if not cObj:HasMoved()or self.allowcratepickupagain then +local cName=cObj:GetName()or"Unknown" +crateMap[cName]=crateMap[cName]or{} +table.insert(crateMap[cName],cObj) +end +end +for cName,crateList in pairs(crateMap)do +if capacity<=0 then break end +table.sort(crateList,function(a,b)return a:GetID()>b:GetID()end) +local needed=crateList[1]:GetCratesNeeded()or 1 +local totalFound=#crateList +local loadedHere=0 +while loaded.Cratesloaded0 then +local fullSets=math.floor(loadedHere/needed) +local leftover=loadedHere%needed +if needed>1 then +if fullSets>0 and leftover==0 then +self:_SendMessage(string.format("Loaded %d %s.",fullSets,cName),10,false,Group) +elseif fullSets>0 and leftover>0 then +self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).",fullSets,cName,leftover),10,false,Group) +else +self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.",loadedHere,needed,cName),15,false,Group) +end +else +self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cName),10,false,Group) +end +end +end +self.Loaded_Cargo[unitname]=loaded +self:_UpdateUnitCargoMass(Unit) +self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshLoadCratesMenu(Group,Unit) +self:_CleanupTrackedCrates(crateidsloaded) +self:__CratesPickedUp(1,Group,Unit,loaded.Cargo) +end +end +return self +end +function CTLD:_CleanupTrackedCrates(crateIdsToRemove) +local existingcrates=self.Spawned_Cargo +local newexcrates={} +for _,_crate in pairs(existingcrates)do +local excrate=_crate +local ID=excrate:GetID() +local keep=true +for _,_ID in pairs(crateIdsToRemove)do +if ID==_ID then +keep=false +end +end +local static=_crate:GetPositionable() +if not static or not static:IsAlive()then +keep=false +end +if keep then +table.insert(newexcrates,_crate) +end +end +self.Spawned_Cargo=nil +self.Spawned_Cargo=newexcrates +return self +end +function CTLD:_GetUnitCargoMass(Unit) +self:T(self.lid.." _GetUnitCargoMass") +if not Unit then return 0 end +local unitname=Unit:GetName() +local loadedcargo=self.Loaded_Cargo[unitname]or{} +local loadedmass=0 +if self.Loaded_Cargo[unitname]then +local cargotable=loadedcargo.Cargo or{} +for _,_cargo in pairs(cargotable)do +local cargo=_cargo +local type=cargo:GetType() +if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then +loadedmass=loadedmass+(cargo.PerCrateMass*cargo:GetCratesNeeded()) +end +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and type~=CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped()then +loadedmass=loadedmass+cargo.PerCrateMass +end +if type==CTLD_CARGO.Enum.GCLOADABLE then +local mass=cargo:GetCargoWeight() +loadedmass=loadedmass+mass +end +end +end +return loadedmass +end +function CTLD:_GetMaxLoadableMass(Unit) +self:T(self.lid.." _GetMaxLoadableMass") +if not Unit then return 0 end +local loadable=0 +local loadedmass=self:_GetUnitCargoMass(Unit) +local capabilities=self:_GetUnitCapabilities(Unit) +local maxmass=capabilities.cargoweightlimit or 2000 +loadable=maxmass-loadedmass +return loadable +end +function CTLD:_UpdateUnitCargoMass(Unit) +self:T(self.lid.." _UpdateUnitCargoMass") +local calculatedMass=self:_GetUnitCargoMass(Unit) +Unit:SetUnitInternalCargo(calculatedMass) +return self +end +function CTLD:_ListCargo(Group,Unit) +self:T(self.lid.." _ListCargo") +local unitname=Unit:GetName() +local unittype=Unit:GetTypeName() +local capabilities=self:_GetUnitCapabilities(Unit) +local trooplimit=capabilities.trooplimit +local cratelimit=capabilities.cratelimit +local loadedcargo=self.Loaded_Cargo[unitname]or{} +local loadedmass=self:_GetUnitCargoMass(Unit) +local maxloadable=self:_GetMaxLoadableMass(Unit) +local finddist=self.CrateDistance or 35 +if self.Loaded_Cargo[unitname]then +local no_troops=loadedcargo.Troopsloaded or 0 +local no_crates=loadedcargo.Cratesloaded or 0 +local cargotable=loadedcargo.Cargo or{} +local report=REPORT:New("Transport Checkout Sheet") +report:Add("------------------------------------------------------------") +report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) +report:Add("------------------------------------------------------------") +report:Add(" -- TROOPS --") +for _,_cargo in pairs(cargotable)do +local cargo=_cargo +local type=cargo:GetType() +if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and(not cargo:WasDropped()or self.allowcratepickupagain)then +report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) +end +end +if report:GetCount()==4 then +report:Add(" N O N E") +end +report:Add("------------------------------------------------------------") +report:Add(" -- CRATES --") +local cratecount=0 +local accumCrates={} +for _,_cargo in pairs(cargotable or{})do +local cargo=_cargo +local type=cargo:GetType() +if(type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and type~=CTLD_CARGO.Enum.GCLOADABLE)and(not cargo:WasDropped()or self.allowcratepickupagain)then +local cName=cargo:GetName() +local needed=cargo:GetCratesNeeded()or 1 +accumCrates[cName]=accumCrates[cName]or{count=0,needed=needed} +accumCrates[cName].count=accumCrates[cName].count+1 +end +if type==CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped()then +report:Add(string.format("GC loaded Crate: %s size 1",cargo:GetName())) +cratecount=cratecount+1 +end +end +for cName,data in pairs(accumCrates)do +cratecount=cratecount+data.count +report:Add(string.format("Crate: %s %d/%d",cName,data.count,data.needed)) +end +if cratecount==0 then +report:Add(" N O N E") +end +report:Add("------------------------------------------------------------") +report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") +local text=report:Text() +self:_SendMessage(text,30,true,Group) +else +self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs",trooplimit,cratelimit,maxloadable),10,false,Group) +end +return self +end +function CTLD:_ListInventory(Group,Unit) +self:T(self.lid.." _ListInventory") +local unitname=Unit:GetName() +local unittype=Unit:GetTypeName() +local cgotypes=self.Cargo_Crates +local trptypes=self.Cargo_Troops +local stctypes=self.Cargo_Statics +local function countcargo(cgotable) +local counter=0 +for _,_cgo in pairs(cgotable)do +counter=counter+1 +end +return counter +end +local crateno=countcargo(cgotypes) +local troopno=countcargo(trptypes) +local staticno=countcargo(stctypes) +if(crateno>0 or troopno>0 or staticno>0)then +local report=REPORT:New("Inventory Sheet") +report:Add("------------------------------------------------------------") +report:Add(string.format("Troops: %d, Cratetypes: %d",troopno,crateno+staticno)) +report:Add("------------------------------------------------------------") +report:Add(" -- TROOPS --") +for _,_cargo in pairs(trptypes)do +local cargo=_cargo +local type=cargo:GetType() +if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then +local stockn=cargo:GetStock() +local stock="none" +if stockn==-1 then +stock="unlimited" +elseif stockn>0 then +stock=tostring(stockn) +end +report:Add(string.format("Unit: %s | Soldiers: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) +end +end +if report:GetCount()==4 then +report:Add(" N O N E") +end +report:Add("------------------------------------------------------------") +report:Add(" -- CRATES --") +local cratecount=0 +for _,_cargo in pairs(cgotypes)do +local cargo=_cargo +local type=cargo:GetType() +if(type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then +local stockn=cargo:GetStock() +local stock="none" +if stockn==-1 then +stock="unlimited" +elseif stockn>0 then +stock=tostring(stockn) +end +report:Add(string.format("Type: %s | Crates per Set: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) +cratecount=cratecount+1 +end +end +for _,_cargo in pairs(stctypes)do +local cargo=_cargo +local type=cargo:GetType() +if(type==CTLD_CARGO.Enum.STATIC)and not cargo:WasDropped()then +local stockn=cargo:GetStock() +local stock="none" +if stockn==-1 then +stock="unlimited" +elseif stockn>0 then +stock=tostring(stockn) +end +report:Add(string.format("Type: %s | Stock: %s",cargo:GetName(),stock)) +cratecount=cratecount+1 +end +end +if cratecount==0 then +report:Add(" N O N E") +end +local text=report:Text() +self:_SendMessage(text,30,true,Group) +else +self:_SendMessage(string.format("Nothing in stock!"),10,false,Group) +end +return self +end +function CTLD:IsFixedWing(Unit) +local typename=Unit:GetTypeName()or"none" +for _,_name in pairs(self.FixedWingTypes or{})do +if _name and(typename==_name or string.find(typename,_name,1,true))then +return true +end +end +return false +end +function CTLD:IsHook(Unit) +if not Unit then return false end +local typeName=Unit:GetTypeName() +if not typeName then return false end +if string.find(typeName,"CH.47")then +return true +else +return false +end +end +function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template) +local Positions={} +local template=_DATABASE:GetGroupTemplate(Template) +local numbertroops=#template.units +local slightshift=math.abs(math.random(1,500)/100) +local newcenter=Coordinate:Translate(Radius+slightshift,((Heading+270+math.random(1,10))%360)) +for i=1,360,math.floor(360/numbertroops)do +local phead=((Heading+270+i)%360) +local post=newcenter:Translate(Radius,phead) +local pos1=post:GetVec2() +local p1t={ +x=pos1.x, +y=pos1.y, +heading=phead, +} +table.insert(Positions,p1t) +end +return Positions +end +function CTLD:_UnloadTroops(Group,Unit) +self:T(self.lid.." _UnloadTroops") +local droppingatbase=false +local canunload=true +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to unload troops!",10,false,Group) +if not self.debug then return self end +end +local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if inzone then +droppingatbase=self.returntroopstobase +end +local hoverunload=self:IsCorrectHover(Unit) +local IsHerc=self:IsFixedWing(Unit) +local IsHook=self:IsHook(Unit) +if IsHerc and(not IsHook)then +hoverunload=self:IsCorrectFlightParameters(Unit) +end +local grounded=not self:IsUnitInAir(Unit) +local unitname=Unit:GetName() +if self.Loaded_Cargo[unitname]and(grounded or hoverunload)then +if not droppingatbase or self.debug then +local loadedcargo=self.Loaded_Cargo[unitname]or{} +local cargotable=loadedcargo.Cargo +for _,_cargo in pairs(cargotable)do +local cargo=_cargo +local type=cargo:GetType() +if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then +local name=cargo:GetName()or"none" +local temptable=cargo:GetTemplates()or{} +local position=Group:GetCoordinate() +local zoneradius=self.troopdropzoneradius or 100 +local factor=1 +if IsHerc then +factor=cargo:GetCratesNeeded()or 1 +zoneradius=Unit:GetVelocityMPS()or 100 +end +local zone=ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor) +local randomcoord=zone:GetRandomCoordinate(10,30*factor) +local heading=Group:GetHeading()or 0 +if hoverunload or grounded then +randomcoord=Group:GetCoordinate() +local Angle=(heading+270)%360 +if IsHerc or IsHook then Angle=(heading+180)%360 end +local offset=hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround +if IsHerc then offset=self.TroopUnloadDistGroundHerc or 25 end +if IsHook then +offset=self.TroopUnloadDistGroundHook or 15 +if hoverunload and self.TroopUnloadDistHoverHook then +offset=self.TroopUnloadDistHoverHook or 5 +end +end +randomcoord:Translate(offset,Angle,nil,true) +end +local tempcount=0 +local ishook=self:IsHook(Unit) +if ishook then tempcount=self.ChinookTroopCircleRadius or 5 end +for _,_template in pairs(temptable)do +self.TroopCounter=self.TroopCounter+1 +tempcount=tempcount+1 +local alias=string.format("%s-%d",_template,math.random(1,100000)) +local rad=2.5+(tempcount*2) +local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template) +self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) +:InitDelayOff() +:InitSetUnitAbsolutePositions(Positions) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) +:SpawnFromVec2(randomcoord:GetVec2()) +self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type) +end +cargo:SetWasDropped(true) +if type==CTLD_CARGO.Enum.ENGINEERS then +self.Engineers=self.Engineers+1 +local grpname=self.DroppedTroops[self.TroopCounter]:GetName() +self.EngineersInField[self.Engineers]=CTLD_ENGINEERING:New(name,grpname) +self:_SendMessage(string.format("Dropped Engineers %s into action!",name),10,false,Group) +else +self:_SendMessage(string.format("Dropped Troops %s into action!",name),10,false,Group) +end +end +end +else +self:_SendMessage("Troops have returned to base!",10,false,Group) +self:__TroopsRTB(1,Group,Unit,zonename,zone) +end +local loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +local loadedcargo=self.Loaded_Cargo[unitname]or{} +local cargotable=loadedcargo.Cargo or{} +for _,_cargo in pairs(cargotable)do +local cargo=_cargo +local type=cargo:GetType() +local dropped=cargo:WasDropped() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and not dropped then +table.insert(loaded.Cargo,_cargo) +loaded.Cratesloaded=loaded.Cratesloaded+1 +else +if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and droppingatbase then +local name=cargo:GetName() +local gentroops=self.Cargo_Troops +for _id,_troop in pairs(gentroops)do +if _troop.Name==name then +local stock=_troop:GetStock() +if stock and tonumber(stock)>=0 then _troop:AddStock()end +end +end +end +end +end +self.Loaded_Cargo[unitname]=nil +self.Loaded_Cargo[unitname]=loaded +self:_RefreshDropTroopsMenu(Group,Unit) +self:_UpdateUnitCargoMass(Unit) +else +if IsHerc then +self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) +else +self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group) +end +end +return self +end +function CTLD:_UnloadCrates(Group,Unit) +self:T(self.lid.." _UnloadCrates") +if not self.dropcratesanywhere then +local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) +if not inzone then +self:_SendMessage("You are not close enough to a drop zone!",10,false,Group) +if not self.debug then +return self +end +end +end +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to drop cargo!",10,false,Group) +if not self.debug then return self end +end +local hoverunload=self:IsCorrectHover(Unit) +local IsHerc=self:IsFixedWing(Unit) +local IsHook=self:IsHook(Unit) +if IsHerc and(not IsHook)then +hoverunload=self:IsCorrectFlightParameters(Unit) +end +local grounded=not self:IsUnitInAir(Unit) +local unitname=Unit:GetName() +if self.Loaded_Cargo[unitname]and(grounded or hoverunload)then +local loadedcargo=self.Loaded_Cargo[unitname]or{} +local cargotable=loadedcargo.Cargo +local droppedCount={} +local neededMap={} +for _,_cargo in pairs(cargotable)do +local cargo=_cargo +local type=cargo:GetType() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and type~=CTLD_CARGO.Enum.GCLOADABLE and(not cargo:WasDropped()or self.allowcratepickupagain)then +self:_GetCrates(Group,Unit,cargo,1,true) +cargo:SetWasDropped(true) +cargo:SetHasMoved(true) +local cname=cargo:GetName()or"Unknown" +droppedCount[cname]=(droppedCount[cname]or 0)+1 +if not neededMap[cname]then +neededMap[cname]=cargo:GetCratesNeeded()or 1 +end +end +end +for cname,count in pairs(droppedCount)do +local needed=neededMap[cname]or 1 +if needed>1 then +local full=math.floor(count/needed) +local left=count%needed +if full>0 and left==0 then +self:_SendMessage(string.format("Dropped %d %s.",full,cname),10,false,Group) +elseif full>0 and left>0 then +self:_SendMessage(string.format("Dropped %d %s(s), with %d leftover crate(s).",full,cname,left),10,false,Group) +else +self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group) +end +else +self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group) +end +end +local loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +for _,_cargo in pairs(cargotable)do +local cargo=_cargo +local type=cargo:GetType() +local size=cargo:GetCratesNeeded() +if type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS then +table.insert(loaded.Cargo,_cargo) +loaded.Troopsloaded=loaded.Troopsloaded+size +end +if type==CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped()then +table.insert(loaded.Cargo,_cargo) +loaded.Cratesloaded=loaded.Cratesloaded+size +end +end +self.Loaded_Cargo[unitname]=nil +self.Loaded_Cargo[unitname]=loaded +self:_UpdateUnitCargoMass(Unit) +self:_RefreshDropCratesMenu(Group,Unit) +else +if IsHerc then +self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) +else +self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group) +end +end +return self +end +function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop) +self:T(self.lid.." _BuildCrates") +if self:IsFixedWing(Unit)and self.enableFixedWing and not Engineering then +local speed=Unit:GetVelocityKMH() +if speed>1 then +self:_SendMessage("You need to land / stop to build something, Pilot!",10,false,Group) +return self +end +end +if not Engineering and self.nobuildinloadzones then +local inloadzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if inloadzone then +self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) +return self +end +end +local finddist=self.CrateDistance or 35 +local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) +local buildables={} +local foundbuilds=false +local canbuild=false +if number>0 then +for _,_crate in pairs(crates)do +local Crate=_crate +if(Crate:WasDropped()or not self.movecratesbeforebuild)and not Crate:IsRepair()and not Crate:IsStatic()then +local name=Crate:GetName() +local required=Crate:GetCratesNeeded() +local template=Crate:GetTemplates() +local ctype=Crate:GetType() +local ccoord=Crate:GetPositionable():GetCoordinate() +if not buildables[name]then +local object={} +object.Name=name +object.Required=required +object.Found=1 +object.Template=template +object.CanBuild=false +object.Type=ctype +object.Coord=ccoord:GetVec2() +buildables[name]=object +foundbuilds=true +else +buildables[name].Found=buildables[name].Found+1 +foundbuilds=true +end +if buildables[name].Found>=buildables[name].Required then +buildables[name].CanBuild=true +canbuild=true +end +self:T({buildables=buildables}) +end +end +local report=REPORT:New("Checklist Buildable Crates") +report:Add("------------------------------------------------------------") +for _,_build in pairs(buildables)do +local build=_build +local name=build.Name +local needed=build.Required +local found=build.Found +local txtok="NO" +if build.CanBuild then +txtok="YES" +end +local text=string.format("Type: %s | Required %d | Found %d | Can Build %s",name,needed,found,txtok) +report:Add(text) +end +if not foundbuilds then +report:Add(" --- None found! ---") +if self.movecratesbeforebuild then +report:Add("*** Crates need to be moved before building!") +end +end +report:Add("------------------------------------------------------------") +local text=report:Text() +if not Engineering then +self:_SendMessage(text,30,true,Group) +else +self:T(text) +end +if canbuild then +for _,_build in pairs(buildables)do +local build=_build +if build.CanBuild then +self:_CleanUpCrates(crates,build,number) +if self.buildtime and self.buildtime>0 then +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) +buildtimer:Start(self.buildtime) +self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +self:__CratesBuildStarted(1,Group,Unit,build.Name) +self:_RefreshDropTroopsMenu(Group,Unit) +else +self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) +end +end +end +end +else +if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist),10,false,Group)end +end +return self +end +function CTLD:_PackCratesNearby(Group,Unit) +self:T(self.lid.." _PackCratesNearby") +local location=Group:GetCoordinate() +local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({ZONE_RADIUS:New("TempZone",location:GetVec2(),self.PackDistance,false)}):FilterOnce() +for _,_Group in pairs(nearestGroups.Set)do +for _,_Template in pairs(_DATABASE.Templates.Groups)do +if(string.match(_Group:GetName(),_Template.GroupName))then +for _,_entry in pairs(self.Cargo_Crates)do +if(_entry.Templates[1]==_Template.GroupName)then +_Group:Destroy() +self:_GetCrates(Group,Unit,_entry,nil,false,true) +self:_RefreshLoadCratesMenu(Group,Unit) +self:__CratesPacked(1,Group,Unit,_entry) +return true +end +end +end +end +end +self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) +return false +end +function CTLD:_RepairCrates(Group,Unit,Engineering) +self:T(self.lid.." _RepairCrates") +local finddist=self.CrateDistance or 35 +local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) +local buildables={} +local foundbuilds=false +local canbuild=false +if number>0 then +for _,_crate in pairs(crates)do +local Crate=_crate +if Crate:WasDropped()and Crate:IsRepair()and not Crate:IsStatic()then +local name=Crate:GetName() +local required=Crate:GetCratesNeeded() +local template=Crate:GetTemplates() +local ctype=Crate:GetType() +if not buildables[name]then +local object={} +object.Name=name +object.Required=required +object.Found=1 +object.Template=template +object.CanBuild=false +object.Type=ctype +buildables[name]=object +foundbuilds=true +else +buildables[name].Found=buildables[name].Found+1 +foundbuilds=true +end +if buildables[name].Found>=buildables[name].Required then +buildables[name].CanBuild=true +canbuild=true +end +self:T({repair=buildables}) +end +end +local report=REPORT:New("Checklist Repairs") +report:Add("------------------------------------------------------------") +for _,_build in pairs(buildables)do +local build=_build +local name=build.Name +local needed=build.Required +local found=build.Found +local txtok="NO" +if build.CanBuild then +txtok="YES" +end +local text=string.format("Type: %s | Required %d | Found %d | Can Repair %s",name,needed,found,txtok) +report:Add(text) +end +if not foundbuilds then report:Add(" --- None Found ---")end +report:Add("------------------------------------------------------------") +local text=report:Text() +if not Engineering then +self:_SendMessage(text,30,true,Group) +else +self:T(text) +end +if canbuild then +for _,_build in pairs(buildables)do +local build=_build +if build.CanBuild then +self:_RepairObjectFromCrates(Group,Unit,crates,build,number,Engineering) +end +end +end +else +if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist),10,false,Group)end +end +return self +end +function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,MultiDrop) +self:T(self.lid.." _BuildObjectFromCrates") +if Group and Group:IsAlive()or(RepairLocation and not Repair)then +local name=Build.Name +local ctype=Build.Type +local canmove=false +if ctype==CTLD_CARGO.Enum.VEHICLE then canmove=true end +if ctype==CTLD_CARGO.Enum.STATIC then +return self +end +local temptable=Build.Template or{} +if type(temptable)=="string"then +temptable={temptable} +end +local zone=nil +if RepairLocation and not Repair then +zone=ZONE_RADIUS:New(string.format("Build zone-%d",math.random(1,10000)),RepairLocation:GetVec2(),100) +else +zone=ZONE_GROUP:New(string.format("Unload zone-%d",math.random(1,10000)),Group,100) +end +local randomcoord=Build.Coord or zone:GetRandomCoordinate(35):GetVec2() +if MultiDrop and(not Repair)and canmove then +local randomcoord=zone:GetRandomCoordinate(35):GetVec2() +end +if Repair then +randomcoord=RepairLocation:GetVec2() +end +for _,_template in pairs(temptable)do +self.TroopCounter=self.TroopCounter+1 +local alias=string.format("%s-%d",_template,math.random(1,100000)) +if canmove then +self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) +:SpawnFromVec2(randomcoord) +else +self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) +:SpawnFromVec2(randomcoord) +end +if Repair then +self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) +else +self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) +end +end +else +self:T(self.lid.."Group KIA while building!") +end +return self +end +function CTLD:_GetVehicleFormation() +local VehicleMoveFormation=self.VehicleMoveFormation or AI.Task.VehicleFormation.VEE +if type(self.VehicleMoveFormation)=="table"then +VehicleMoveFormation=self.VehicleMoveFormation[math.random(1,#self.VehicleMoveFormation)] +end +return VehicleMoveFormation +end +function CTLD:_MoveGroupToZone(Group) +self:T(self.lid.." _MoveGroupToZone") +local groupname=Group:GetName()or"none" +local groupcoord=Group:GetCoordinate() +local outcome,name,zone,distance=self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) +self:T({canmove=outcome,name=name,zone=zone,dist=distance,max=self.movetroopsdistance}) +if(distance<=self.movetroopsdistance)and outcome==true and zone~=nil then +local groupname=Group:GetName() +local zonecoord=zone:GetRandomCoordinate(20,125) +local formation=self:_GetVehicleFormation() +Group:SetAIOn() +Group:OptionAlarmStateAuto() +Group:OptionDisperseOnAttack(30) +Group:OptionROEOpenFire() +Group:RouteGroundTo(zonecoord,25,formation) +end +return self +end +function CTLD:_CleanUpCrates(Crates,Build,Number) +self:T(self.lid.." _CleanUpCrates") +local build=Build +local existingcrates=self.Spawned_Cargo +local newexcrates={} +local numberdest=Build.Required +local nametype=Build.Name +local found=0 +local rounds=Number +local destIDs={} +for _,_crate in pairs(Crates)do +local nowcrate=_crate +local name=nowcrate:GetName() +local thisID=nowcrate:GetID() +if name==nametype then +table.insert(destIDs,thisID) +found=found+1 +nowcrate:GetPositionable():Destroy(false) +nowcrate.Positionable=nil +nowcrate.HasBeenDropped=false +end +if found==numberdest then break end +end +self:_CleanupTrackedCrates(destIDs) +return self +end +function CTLD:_DropAndBuild(Group,Unit) +if self.nobuildinloadzones then +if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)then +self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) +return self +end +end +self:_UnloadCrates(Group,Unit) +timer.scheduleFunction(function()self:_BuildCrates(Group,Unit,false,true)end,{},timer.getTime()+1) +end +function CTLD:_DropSingleAndBuild(Group,Unit,setIndex) +if self.nobuildinloadzones then +if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)then +self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) +return self +end +end +self:_UnloadSingleCrateSet(Group,Unit,setIndex) +timer.scheduleFunction(function()self:_BuildCrates(Group,Unit,false)end,{},timer.getTime()+1) +end +function CTLD:_PackAndLoad(Group,Unit) +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) +return self +end +if not self:_PackCratesNearby(Group,Unit)then +return self +end +timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) +return self +end +function CTLD:_PackAndRemove(Group,Unit) +if not self:_PackCratesNearby(Group,Unit)then +return self +end +timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) +return self +end +function CTLD:_GetAndLoad(Group,Unit,cargoObj) +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) +return self +end +self:_GetCrates(Group,Unit,cargoObj) +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1) +end +function CTLD:_GetAllAndLoad(Group,Unit) +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) +return self +end +timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) +end +function CTLD:_RefreshF10Menus() +self:T(self.lid.." _RefreshF10Menus") +self.onestepmenu=self.onestepmenu or false +local PlayerSet=self.PilotGroups +local PlayerTable=PlayerSet:GetSetObjects() +local _UnitList={} +for _,groupObj in pairs(PlayerTable)do +local firstUnit=groupObj:GetFirstUnitAlive() +if firstUnit then +if firstUnit:IsPlayer()then +if firstUnit:IsHelicopter()or(self.enableFixedWing and self:IsFixedWing(firstUnit))then +local _unit=firstUnit:GetName() +_UnitList[_unit]=_unit +end +end +end +end +if self.allowCATransport and self.CATransportSet then +for _,_clientobj in pairs(self.CATransportSet.Set)do +local client=_clientobj +if client:IsGround()then +local cname=client:GetName() +self:T(self.lid.."Adding: "..cname) +_UnitList[cname]=cname +end +end +end +self.CtldUnits=_UnitList +if self.usesubcats then +for _id,_cargo in pairs(self.Cargo_Crates)do +local entry=_cargo +if not self.subcats[entry.Subcategory]then +self.subcats[entry.Subcategory]=entry.Subcategory +end +end +for _id,_cargo in pairs(self.Cargo_Statics)do +local entry=_cargo +if not self.subcats[entry.Subcategory]then +self.subcats[entry.Subcategory]=entry.Subcategory +end +end +for _id,_cargo in pairs(self.Cargo_Troops)do +local entry=_cargo +if not self.subcatsTroop[entry.Subcategory]then +self.subcatsTroop[entry.Subcategory]=entry.Subcategory +end +end +end +local menucount=0 +local menus={} +for _,_unitName in pairs(self.CtldUnits)do +if(not self.MenusDone[_unitName])or(self.showstockinmenuitems==true)then +self:T(self.lid.."Menu not done yet for ".._unitName) +local _unit=UNIT:FindByName(_unitName) +if not _unit and self.allowCATransport then +_unit=CLIENT:FindByName(_unitName) +end +if _unit and _unit:IsAlive()then +local _group=_unit:GetGroup() +if _group then +self:T(self.lid.."Unit and Group exist") +local capabilities=self:_GetUnitCapabilities(_unit) +local cantroops=capabilities.troops +local cancrates=capabilities.crates +local unittype=_unit:GetTypeName() +local isHook=self:IsHook(_unit) +local nohookswitch=true +if _group.CTLDTopmenu then +_group.CTLDTopmenu:Remove() +_group.CTLDTopmenu=nil +end +local toptroops=nil +local topcrates=nil +local topmenu=MENU_GROUP:New(_group,"CTLD",nil) +_group.CTLDTopmenu=topmenu +if cantroops then +local toptroops=MENU_GROUP:New(_group,"Manage Troops",topmenu) +local troopsmenu=MENU_GROUP:New(_group,"Load troops",toptroops) +_group.MyTopTroopsMenu=toptroops +if self.usesubcats then +local subcatmenus={} +for catName,_ in pairs(self.subcatsTroop)do +subcatmenus[catName]=MENU_GROUP:New(_group,catName,troopsmenu) +end +for _,cargoObj in pairs(self.Cargo_Troops)do +if not cargoObj.DontShowInMenu then +local stock=cargoObj:GetStock() +local menutext=cargoObj.Name +if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj) +end +end +else +for _,cargoObj in pairs(self.Cargo_Troops)do +if not cargoObj.DontShowInMenu then +local stock=cargoObj:GetStock() +local menutext=cargoObj.Name +if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj) +end +end +end +local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh() +MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh() +MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh() +local uName=_unit:GetName() +local loadedData=self.Loaded_Cargo[uName] +if loadedData and loadedData.Cargo then +for i,cargoObj in ipairs(loadedData.Cargo)do +if cargoObj and(cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS)and not cargoObj:WasDropped()then +local name=cargoObj:GetName()or"Unknown" +local needed=cargoObj:GetCratesNeeded()or 1 +local cID=cargoObj:GetID() +local line=string.format("Drop: %s",name,needed,cID) +MENU_GROUP_COMMAND:New(_group,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,_group,_unit,cID):Refresh() +end +end +end +end +if cancrates then +local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) +_group.MyTopCratesMenu=topcrates +local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) +if self.onestepmenu then +if self.usesubcats then +local subcatmenus={} +for catName,_ in pairs(self.subcats)do +subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) +end +for _,cargoObj in pairs(self.Cargo_Crates)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) +MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) +end +end +for _,cargoObj in pairs(self.Cargo_Statics)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) +MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) +end +end +else +for _,cargoObj in pairs(self.Cargo_Crates)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +local mSet=MENU_GROUP:New(_group,txt,cratesmenu) +MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) +end +end +for _,cargoObj in pairs(self.Cargo_Statics)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +local mSet=MENU_GROUP:New(_group,txt,cratesmenu) +MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) +end +end +end +else +if self.usesubcats==true then +local subcatmenus={} +for catName,_ in pairs(self.subcats)do +subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) +end +for _,cargoObj in pairs(self.Cargo_Crates)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +end +end +for _,cargoObj in pairs(self.Cargo_Statics)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +end +end +else +for _,cargoObj in pairs(self.Cargo_Crates)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,cargoObj) +end +end +for _,cargoObj in pairs(self.Cargo_Statics)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,cargoObj) +end +end +end +end +local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) +_group.MyLoadCratesMenu=loadCratesMenu +MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) +local dropCratesMenu=MENU_GROUP:New(_group,"Drop Crates",topcrates) +topcrates.DropCratesMenu=dropCratesMenu +if not self.nobuildmenu then +MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates,self._BuildCrates,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Repair",topcrates,self._RepairCrates,self,_group,_unit):Refresh() +end +local removecratesmenu=MENU_GROUP:New(_group,"Remove crates",topcrates) +MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu,self._RemoveCratesNearby,self,_group,_unit) +if self.onestepmenu then +local mPack=MENU_GROUP:New(_group,"Pack crates",topcrates) +MENU_GROUP_COMMAND:New(_group,"Pack",mPack,self._PackCratesNearby,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Pack and Load",mPack,self._PackAndLoad,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Pack and Remove",mPack,self._PackAndRemove,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit) +else +MENU_GROUP_COMMAND:New(_group,"Pack crates",topcrates,self._PackCratesNearby,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit) +end +local uName=_unit:GetName() +local loadedData=self.Loaded_Cargo[uName] +if loadedData and loadedData.Cargo then +local cargoByName={} +for _,cgo in pairs(loadedData.Cargo)do +if cgo and(not cgo:WasDropped())then +local cname=cgo:GetName() +local cneeded=cgo:GetCratesNeeded() +cargoByName[cname]=cargoByName[cname]or{count=0,needed=cneeded} +cargoByName[cname].count=cargoByName[cname].count+1 +end +end +for name,info in pairs(cargoByName)do +local line=string.format("Drop %s (%d/%d)",name,info.count,info.needed) +MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,self,_group,_unit,name) +end +end +end +MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit) +local smoketopmenu=MENU_GROUP:New(_group,"Smokes, Flares, Beacons",topmenu) +MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,false) +local smokeself=MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu) +MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Red) +MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Blue) +MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Green) +MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Orange) +MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.White) +MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,true) +MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu,self.SmokePositionNow,self,_unit,true) +MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu,self.DropBeaconNow,self,_unit):Refresh() +if self:IsFixedWing(_unit)then +MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu,self._ShowFlightParams,self,_group,_unit):Refresh() +else +MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu,self._ShowHoverParams,self,_group,_unit):Refresh() +end +self.MenusDone[_unitName]=true +self:_RefreshLoadCratesMenu(_group,_unit) +self:_RefreshDropCratesMenu(_group,_unit) +end +end +else +self:T(self.lid.." Menus already done for this group!") +end +end +return self +end +function CTLD:_RefreshLoadCratesMenu(Group,Unit) +if not Group.MyLoadCratesMenu then return end +Group.MyLoadCratesMenu:RemoveSubMenus() +local d=self.CrateDistance or 35 +local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) +if n==0 then +MENU_GROUP_COMMAND:New(Group,"No crates found! Rescan?",Group.MyLoadCratesMenu,function()self:_RefreshLoadCratesMenu(Group,Unit)end) +return +end +MENU_GROUP_COMMAND:New(Group,"Load ALL",Group.MyLoadCratesMenu,self._LoadCratesNearby,self,Group,Unit) +local cargoByName={} +for _,crate in pairs(nearby)do +local name=crate:GetName() +cargoByName[name]=cargoByName[name]or{} +table.insert(cargoByName[name],crate) +end +local lineIndex=1 +for cName,list in pairs(cargoByName)do +local needed=list[1]:GetCratesNeeded()or 1 +table.sort(list,function(a,b)return a:GetID()=needed then +label=string.format("%d. Load %s",lineIndex,cName) +i=i+needed +else +label=string.format("%d. Load %s (%d/%d)",lineIndex,cName,left,needed) +i=#list+1 +end +MENU_GROUP_COMMAND:New(Group,label,Group.MyLoadCratesMenu,self._LoadSingleCrateSet,self,Group,Unit,cName) +lineIndex=lineIndex+1 +end +end +end +function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName) +self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil")) +local grounded=not self:IsUnitInAir(Unit) +local hover=self:CanHoverLoad(Unit) +if not grounded and not hover then +self:_SendMessage("You must land or hover to load crates!",10,false,Group) +return self +end +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) +return self +end +local finddist=self.CrateDistance or 35 +local cratesNearby,number=self:_FindCratesNearby(Group,Unit,finddist,false,false) +if number==0 then +self:_SendMessage("No crates found in range!",10,false,Group) +return self +end +local matchingCrates={} +local needed=nil +for _,crateObj in pairs(cratesNearby)do +if crateObj:GetName()==cargoName then +needed=needed or crateObj:GetCratesNeeded() +table.insert(matchingCrates,crateObj) +end +end +if not needed then +self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10,false,Group) +return self +end +local found=#matchingCrates +local unitName=Unit:GetName() +local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} +local capabilities=self:_GetUnitCapabilities(Unit) +local capacity=capabilities.cratelimit or 0 +if loadedData.Cratesloaded>=capacity then +self:_SendMessage("No more capacity to load crates!",10,false,Group) +return self +end +local spaceLeft=capacity-loadedData.Cratesloaded +local toLoad=math.min(found,needed,spaceLeft) +if toLoad<1 then +self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group) +return self +end +local crateIDsLoaded={} +for i=1,toLoad do +local crate=matchingCrates[i] +crate:SetHasMoved(true) +crate:SetWasDropped(false) +table.insert(loadedData.Cargo,crate) +loadedData.Cratesloaded=loadedData.Cratesloaded+1 +local stObj=crate:GetPositionable() +if stObj and stObj:IsAlive()then +stObj:Destroy(false) +end +table.insert(crateIDsLoaded,crate:GetID()) +end +self.Loaded_Cargo[unitName]=loadedData +self:_UpdateUnitCargoMass(Unit) +local newSpawned={} +for _,cObj in ipairs(self.Spawned_Cargo)do +local keep=true +for i=1,toLoad do +if matchingCrates[i]and cObj:GetID()==matchingCrates[i]:GetID()then +keep=false +break +end +end +if keep then +table.insert(newSpawned,cObj) +end +end +self.Spawned_Cargo=newSpawned +local loadedHere=toLoad +if loadedHere=capacity then +self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) +else +local fullSets=math.floor(loadedHere/needed) +local leftover=loadedHere%needed +if needed>1 then +if fullSets>0 and leftover==0 then +self:_SendMessage(string.format("Loaded %d %s.",fullSets,cargoName),10,false,Group) +elseif fullSets>0 and leftover>0 then +self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).",fullSets,cargoName,leftover),10,false,Group) +else +self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.",loadedHere,needed,cargoName),15,false,Group) +end +else +self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) +end +end +self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshDropCratesMenu(Group,Unit) +return self +end +function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) +self:T(self.lid.." _UnloadSingleCrateSet") +if not self.dropcratesanywhere then +local inzone,zoneName,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) +if not inzone then +self:_SendMessage("You are not close enough to a drop zone!",10,false,Group) +if not self.debug then +return self +end +end +end +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to drop cargo!",10,false,Group) +if not self.debug then return self end +end +local unitName=Unit:GetName() +if not self.CrateGroupList or not self.CrateGroupList[unitName]then +self:_SendMessage("No crate groups found for this unit!",10,false,Group) +if not self.debug then return self end +return self +end +local chunk=self.CrateGroupList[unitName][setIndex] +if not chunk then +self:_SendMessage("No crate set found or index invalid!",10,false,Group) +if not self.debug then return self end +return self +end +if#chunk==0 then +self:_SendMessage("No crate found in that set!",10,false,Group) +if not self.debug then return self end +return self +end +local grounded=not self:IsUnitInAir(Unit) +local hoverunload=self:IsCorrectHover(Unit) +local isHerc=self:IsFixedWing(Unit) +local isHook=self:IsHook(Unit) +if isHerc and not isHook then +hoverunload=self:IsCorrectFlightParameters(Unit) +end +if not grounded and not hoverunload then +if isHerc then +self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) +else +self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group) +end +if not self.debug then return self end +return self +end +local crateObj=chunk[1] +if not crateObj then +self:_SendMessage("No crate found in that set!",10,false,Group) +if not self.debug then return self end +return self +end +local needed=crateObj:GetCratesNeeded()or 1 +self:_GetCrates(Group,Unit,crateObj,#chunk,true) +for _,cObj in ipairs(chunk)do +cObj:SetWasDropped(true) +cObj:SetHasMoved(true) +end +local cname=crateObj:GetName()or"Unknown" +local count=#chunk +if needed>1 then +if count==needed then +self:_SendMessage(string.format("Dropped %d %s.",1,cname),10,false,Group) +else +self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group) +end +else +self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group) +end +local loadedData=self.Loaded_Cargo[unitName] +if loadedData and loadedData.Cargo then +local newList={} +local newCratesCount=0 +for _,cObj in ipairs(loadedData.Cargo)do +if not cObj:WasDropped()then +table.insert(newList,cObj) +local ct=cObj:GetType() +if ct~=CTLD_CARGO.Enum.TROOPS and ct~=CTLD_CARGO.Enum.ENGINEERS then +newCratesCount=newCratesCount+1 +end +end +end +loadedData.Cargo=newList +loadedData.Cratesloaded=newCratesCount +self.Loaded_Cargo[unitName]=loadedData +end +self:_UpdateUnitCargoMass(Unit) +self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshLoadCratesMenu(Group,Unit) +return self +end +function CTLD:_RefreshDropCratesMenu(Group,Unit) +if not Group.CTLDTopmenu then return end +local topCrates=Group.MyTopCratesMenu +if not topCrates then return end +if topCrates.DropCratesMenu then +topCrates.DropCratesMenu:RemoveSubMenus() +else +topCrates.DropCratesMenu=MENU_GROUP:New(Group,"Drop Crates",topCrates) +end +local dropCratesMenu=topCrates.DropCratesMenu +local loadedData=self.Loaded_Cargo[Unit:GetName()] +if not loadedData or not loadedData.Cargo then +MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function()end) +return +end +local cargoByName={} +local dropableCrates=0 +for _,cObj in ipairs(loadedData.Cargo)do +if cObj and not cObj:WasDropped()then +local cType=cObj:GetType() +if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then +local name=cObj:GetName()or"Unknown" +cargoByName[name]=cargoByName[name]or{} +table.insert(cargoByName[name],cObj) +dropableCrates=dropableCrates+1 +end +end +end +if dropableCrates==0 then +MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function()end) +return +end +if not self.onestepmenu then +MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) +self.CrateGroupList=self.CrateGroupList or{} +self.CrateGroupList[Unit:GetName()]={} +local lineIndex=1 +for cName,list in pairs(cargoByName)do +local needed=list[1]:GetCratesNeeded()or 1 +table.sort(list,function(a,b)return a:GetID()=needed then +local chunk={} +for n=i,i+needed-1 do +table.insert(chunk,list[n]) +end +local label=string.format("%d. %s",lineIndex,cName) +table.insert(self.CrateGroupList[Unit:GetName()],chunk) +local setIndex=#self.CrateGroupList[Unit:GetName()] +MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) +i=i+needed +else +local chunk={} +for n=i,#list do +table.insert(chunk,list[n]) +end +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +table.insert(self.CrateGroupList[Unit:GetName()],chunk) +local setIndex=#self.CrateGroupList[Unit:GetName()] +MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) +i=#list+1 +end +lineIndex=lineIndex+1 +end +end +else +local mAll=MENU_GROUP:New(Group,"Drop ALL crates",dropCratesMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",mAll,self._UnloadCrates,self,Group,Unit) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",mAll,self._DropAndBuild,self,Group,Unit) +end +self.CrateGroupList=self.CrateGroupList or{} +self.CrateGroupList[Unit:GetName()]={} +local lineIndex=1 +for cName,list in pairs(cargoByName)do +local needed=list[1]:GetCratesNeeded()or 1 +table.sort(list,function(a,b)return a:GetID()=needed then +local chunk={} +for n=i,i+needed-1 do +table.insert(chunk,list[n]) +end +local label=string.format("%d. %s",lineIndex,cName) +table.insert(self.CrateGroupList[Unit:GetName()],chunk) +local setIndex=#self.CrateGroupList[Unit:GetName()] +local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) +end +i=i+needed +else +local chunk={} +for n=i,#list do +table.insert(chunk,list[n]) +end +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +table.insert(self.CrateGroupList[Unit:GetName()],chunk) +local setIndex=#self.CrateGroupList[Unit:GetName()] +MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) +i=#list+1 +end +lineIndex=lineIndex+1 +end +end +end +end +function CTLD:_UnloadSingleTroopByID(Group,Unit,chunkID) +self:T(self.lid.." _UnloadSingleTroopByID chunkID="..tostring(chunkID)) +local droppingatbase=false +local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if inzone then +droppingatbase=self.returntroopstobase +end +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to unload troops!",10,false,Group) +if not self.debug then return self end +end +local hoverunload=self:IsCorrectHover(Unit) +local isHerc=self:IsFixedWing(Unit) +local isHook=self:IsHook(Unit) +if isHerc and not isHook then +hoverunload=self:IsCorrectFlightParameters(Unit) +end +local grounded=not self:IsUnitInAir(Unit) +local unitName=Unit:GetName() +if self.Loaded_Cargo[unitName]and(grounded or hoverunload)then +if not droppingatbase or self.debug then +if not self.TroopsIDToChunk or not self.TroopsIDToChunk[chunkID]then +self:_SendMessage(string.format("No troop cargo chunk found for ID %d!",chunkID),10,false,Group) +if not self.debug then return self end +return self +end +local chunk=self.TroopsIDToChunk[chunkID] +if not chunk or#chunk==0 then +self:_SendMessage(string.format("Troop chunk is empty for ID %d!",chunkID),10,false,Group) +if not self.debug then return self end +return self +end +local foundCargo=chunk[1] +if not foundCargo then +self:_SendMessage(string.format("No troop cargo at chunk %d!",chunkID),10,false,Group) +if not self.debug then return self end +return self +end +local cType=foundCargo:GetType() +local name=foundCargo:GetName()or"none" +local tmpl=foundCargo:GetTemplates()or{} +local zoneradius=self.troopdropzoneradius or 100 +local factor=1 +if isHerc then +factor=foundCargo:GetCratesNeeded()or 1 +zoneradius=Unit:GetVelocityMPS()or 100 +end +local zone=ZONE_GROUP:New(string.format("Unload zone-%s",unitName),Group,zoneradius*factor) +local randomcoord=zone:GetRandomCoordinate(10,30*factor) +local heading=Group:GetHeading()or 0 +if grounded or hoverunload then +randomcoord=Group:GetCoordinate() +local Angle=(heading+270)%360 +if isHerc or isHook then +Angle=(heading+180)%360 +end +local offset=hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround +if isHerc then +offset=self.TroopUnloadDistGroundHerc or 25 +end +if isHook then +offset=self.TroopUnloadDistGroundHook or 15 +if hoverunload and self.TroopUnloadDistHoverHook then +offset=self.TroopUnloadDistHoverHook or 5 +end +end +randomcoord:Translate(offset,Angle,nil,true) +end +local tempcount=0 +if isHook then +tempcount=self.ChinookTroopCircleRadius or 5 +end +for _,_template in pairs(tmpl)do +self.TroopCounter=self.TroopCounter+1 +tempcount=tempcount+1 +local alias=string.format("%s-%d",_template,math.random(1,100000)) +local rad=2.5+(tempcount*2) +local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template) +self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) +:InitDelayOff() +:InitSetUnitAbsolutePositions(Positions) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) +:SpawnFromVec2(randomcoord:GetVec2()) +self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],cType) +end +foundCargo:SetWasDropped(true) +if cType==CTLD_CARGO.Enum.ENGINEERS then +self.Engineers=self.Engineers+1 +local grpname=self.DroppedTroops[self.TroopCounter]:GetName() +self.EngineersInField[self.Engineers]=CTLD_ENGINEERING:New(name,grpname) +self:_SendMessage(string.format("Dropped Engineers %s into action!",name),10,false,Group) +else +self:_SendMessage(string.format("Dropped Troops %s into action!",name),10,false,Group) +end +table.remove(chunk,1) +if#chunk==0 then +self.TroopsIDToChunk[chunkID]=nil +end +else +self:_SendMessage("Troops have returned to base!",10,false,Group) +self:__TroopsRTB(1,Group,Unit,zonename,zone) +if self.TroopsIDToChunk and self.TroopsIDToChunk[chunkID]then +local chunk=self.TroopsIDToChunk[chunkID] +if#chunk>0 then +local firstObj=chunk[1] +local cName=firstObj:GetName() +local gentroops=self.Cargo_Troops +for _id,_troop in pairs(gentroops)do +if _troop.Name==cName then +local st=_troop:GetStock() +if st and tonumber(st)>=0 then +_troop:AddStock() +end +end +end +firstObj:SetWasDropped(true) +table.remove(chunk,1) +if#chunk==0 then +self.TroopsIDToChunk[chunkID]=nil +end +end +end +end +local cargoList=self.Loaded_Cargo[unitName].Cargo +for i=#cargoList,1,-1 do +if cargoList[i]:WasDropped()then +table.remove(cargoList,i) +end +end +local troopsLoaded=0 +local cratesLoaded=0 +for _,cargo in ipairs(cargoList)do +local cT=cargo:GetType() +if cT==CTLD_CARGO.Enum.TROOPS or cT==CTLD_CARGO.Enum.ENGINEERS then +troopsLoaded=troopsLoaded+1 +else +cratesLoaded=cratesLoaded+1 +end +end +self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded +self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded +self:_RefreshDropTroopsMenu(Group,Unit) +else +local isHerc=self:IsFixedWing(Unit) +if isHerc then +self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) +else +self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group) +end +end +return self +end +function CTLD:_RefreshDropTroopsMenu(Group,Unit) +local theGroup=Group +local theUnit=Unit +if not theGroup.CTLDTopmenu then return end +local topTroops=theGroup.MyTopTroopsMenu +if not topTroops then return end +if topTroops.DropTroopsMenu then +topTroops.DropTroopsMenu:Remove() +end +local dropTroopsMenu=MENU_GROUP:New(theGroup,"Drop Troops",topTroops) +topTroops.DropTroopsMenu=dropTroopsMenu +MENU_GROUP_COMMAND:New(theGroup,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,theGroup,theUnit) +local loadedData=self.Loaded_Cargo[theUnit:GetName()] +if not loadedData or not loadedData.Cargo then return end +local troopsByName={} +for _,cargoObj in ipairs(loadedData.Cargo)do +if cargoObj +and(cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) +and not cargoObj:WasDropped() +then +local name=cargoObj:GetName()or"Unknown" +troopsByName[name]=troopsByName[name]or{} +table.insert(troopsByName[name],cargoObj) +end +end +self.TroopsIDToChunk=self.TroopsIDToChunk or{} +for tName,objList in pairs(troopsByName)do +table.sort(objList,function(a,b)return a:GetID()timeout then +local name=beacon.name +self.droppedbeaconref[name]=nil +_beacon=nil +else +table.insert(livebeacontable,beacon) +end +end +self.droppedBeacons=nil +self.droppedBeacons=livebeacontable +return self +end +function CTLD:_ListRadioBeacons(Group,Unit) +self:T(self.lid.." _ListRadioBeacons") +local report=REPORT:New("Active Zone Beacons") +report:Add("------------------------------------------------------------") +local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones,[5]=self.droppedBeacons} +for i=1,5 do +for index,cargozone in pairs(zones[i])do +local czone=cargozone +if czone.active and czone.hasbeacon then +local FMbeacon=czone.fmbeacon +local VHFbeacon=czone.vhfbeacon +local UHFbeacon=czone.uhfbeacon +local Name=czone.name +local FM=FMbeacon.frequency +local VHF=VHFbeacon.frequency*1000 +local UHF=UHFbeacon.frequency +report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",Name,FM,VHF,UHF),"|") +end +end +end +if report:GetCount()==1 then +report:Add(" N O N E") +end +report:Add("------------------------------------------------------------") +self:_SendMessage(report:Text(),30,true,Group) +return self +end +function CTLD:_AddRadioBeacon(Name,Sound,Mhz,Modulation,IsShip,IsDropped) +self:T(self.lid.." _AddRadioBeacon") +local Zone=nil +if IsShip then +Zone=UNIT:FindByName(Name) +elseif IsDropped then +Zone=self.droppedbeaconref[Name] +else +Zone=ZONE:FindByName(Name) +if not Zone then +Zone=AIRBASE:FindByName(Name):GetZone() +end +end +local Sound=Sound or"beacon.ogg" +if Zone then +if IsDropped then +local ZoneCoord=Zone +local ZoneVec3=ZoneCoord:GetVec3()or{x=0,y=0,z=0} +local Frequency=Mhz*1000000 +local Sound=self.RadioPath..Sound +trigger.action.radioTransmission(Sound,ZoneVec3,Modulation,false,Frequency,1000,Name..math.random(1,10000)) +self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = %d %d %d | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation)) +else +local ZoneCoord=Zone:GetCoordinate() +local ZoneVec3=ZoneCoord:GetVec3()or{x=0,y=0,z=0} +local Frequency=Mhz*1000000 +local Sound=self.RadioPath..Sound +trigger.action.radioTransmission(Sound,ZoneVec3,Modulation,false,Frequency,1000,Name..math.random(1,10000)) +self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = {x=%d, y=%d, z=%d} | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation)) +end +else +self:E(self.lid.."***** _AddRadioBeacon: Zone does not exist: "..Name) +end +return self +end +function CTLD:SetSoundfilesFolder(FolderPath) +self:T(self.lid.." SetSoundfilesFolder") +if FolderPath then +local lastchar=string.sub(FolderPath,-1) +if lastchar~="/"then +FolderPath=FolderPath.."/" +end +end +self.RadioPath=FolderPath +self:I(self.lid..string.format("Setting sound files folder to: %s",self.RadioPath)) +return self +end +function CTLD:_RefreshRadioBeacons() +self:T(self.lid.." _RefreshRadioBeacons") +local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones,[5]=self.droppedBeacons} +for i=1,5 do +local IsShip=false +if i==4 then IsShip=true end +local IsDropped=false +if i==5 then IsDropped=true end +for index,cargozone in pairs(zones[i])do +local czone=cargozone +local Sound=self.RadioSound +local Silent=self.RadioSoundFC3 or self.RadioSound +if czone.active and czone.hasbeacon then +local FMbeacon=czone.fmbeacon +local VHFbeacon=czone.vhfbeacon +local UHFbeacon=czone.uhfbeacon +local Name=czone.name +local FM=FMbeacon.frequency +local VHF=VHFbeacon.frequency +local UHF=UHFbeacon.frequency +self:_AddRadioBeacon(Name,Sound,FM,CTLD.RadioModulation.FM,IsShip,IsDropped) +self:_AddRadioBeacon(Name,Sound,VHF,CTLD.RadioModulation.AM,IsShip,IsDropped) +self:_AddRadioBeacon(Name,Silent,UHF,CTLD.RadioModulation.AM,IsShip,IsDropped) +end +end +end +return self +end +function CTLD:IsUnitInZone(Unit,Zonetype) +self:T(self.lid.." IsUnitInZone") +self:T(Zonetype) +local unitname=Unit:GetName() +local zonetable={} +local outcome=false +if Zonetype==CTLD.CargoZoneType.LOAD then +zonetable=self.pickupZones +elseif Zonetype==CTLD.CargoZoneType.DROP then +zonetable=self.dropOffZones +elseif Zonetype==CTLD.CargoZoneType.SHIP then +zonetable=self.shipZones +else +zonetable=self.wpZones +end +local zonecoord=nil +local colorret=nil +local maxdist=1000000 +local zoneret=nil +local zonewret=nil +local zonenameret=nil +local unitcoord=Unit:GetCoordinate() +local unitVec2=unitcoord:GetVec2() +for _,_cargozone in pairs(zonetable)do +local czone=_cargozone +local zonename=czone.name +local active=czone.active +local color=czone.color +local zone=nil +local zoneradius=100 +local zonewidth=20 +if Zonetype==CTLD.CargoZoneType.SHIP then +self:T("Checking Type Ship: "..zonename) +local ZoneUNIT=UNIT:FindByName(zonename) +if not ZoneUNIT then return false end +zonecoord=ZoneUNIT:GetCoordinate() +zoneradius=czone.shiplength +zonewidth=czone.shipwidth +zone=ZONE_UNIT:New(ZoneUNIT:GetName(),ZoneUNIT,zoneradius/2) +elseif ZONE:FindByName(zonename)then +zone=ZONE:FindByName(zonename) +self:T("Checking Zone: "..zonename) +zonecoord=zone:GetCoordinate() +zonewidth=zoneradius +elseif AIRBASE:FindByName(zonename)then +zone=AIRBASE:FindByName(zonename):GetZone() +self:T("Checking Zone: "..zonename) +zonecoord=zone:GetCoordinate() +zoneradius=2000 +zonewidth=zoneradius +end +local distance=self:_GetDistance(zonecoord,unitcoord) +self:T("Distance Zone: "..distance) +self:T("Zone Active: "..tostring(active)) +if(zone:IsVec2InZone(unitVec2)or Zonetype==CTLD.CargoZoneType.MOVE)and active==true and distance=minh)then +outcome=true +end +end +return outcome +end +function CTLD:IsCorrectFlightParameters(Unit) +self:T(self.lid.." IsCorrectFlightParameters") +local outcome=false +if self:IsUnitInAir(Unit)then +local uspeed=Unit:GetVelocityMPS() +local uheight=Unit:GetHeight() +local ucoord=Unit:GetCoordinate() +if not ucoord then +return false +end +local gheight=ucoord:GetLandHeight() +local aheight=uheight-gheight +local minh=self.FixedMinAngels +local maxh=self.FixedMaxAngels +local maxspeed=self.FixedMaxSpeed +local kmspeed=uspeed*3.6 +local knspeed=kmspeed/1.86 +self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed)) +if(aheight<=maxh)and(aheight>=minh)and(uspeed<=maxspeed)then +outcome=true +end +end +return outcome +end +function CTLD:_ShowHoverParams(Group,Unit) +local inhover=self:IsCorrectHover(Unit) +local htxt="true" +if not inhover then htxt="false"end +local text="" +if _SETTINGS:IsMetric()then +text=string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s",self.minimumHoverHeight,self.maximumHoverHeight,htxt) +else +local minheight=UTILS.MetersToFeet(self.minimumHoverHeight) +local maxheight=UTILS.MetersToFeet(self.maximumHoverHeight) +text=string.format("Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s",minheight,maxheight,htxt) +end +self:_SendMessage(text,10,false,Group) +return self +end +function CTLD:_ShowFlightParams(Group,Unit) +local inhover=self:IsCorrectFlightParameters(Unit) +local htxt="true" +if not inhover then htxt="false"end +local text="" +if _SETTINGS:IsImperial()then +local minheight=UTILS.MetersToFeet(self.FixedMinAngels) +local maxheight=UTILS.MetersToFeet(self.FixedMaxAngels) +text=string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s",minheight,maxheight,htxt) +else +local minheight=self.FixedMinAngels +local maxheight=self.FixedMaxAngels +text=string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s",minheight,maxheight,htxt) +end +self:_SendMessage(text,10,false,Group) +return self +end +function CTLD:CanHoverLoad(Unit) +self:T(self.lid.." CanHoverLoad") +if self:IsFixedWing(Unit)then return false end +local outcome=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)and self:IsCorrectHover(Unit) +if not outcome then +outcome=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +return outcome +end +function CTLD:IsUnitInAir(Unit) +local minheight=self.minimumHoverHeight +if self.enableFixedWing and self:IsFixedWing(Unit)then +minheight=5.1 +end +local uheight=Unit:GetHeight() +local ucoord=Unit:GetCoordinate() +if not ucoord then +return false +end +local gheight=ucoord:GetLandHeight() +local aheight=uheight-gheight +if aheight>=minheight then +return true +else +return false +end +end +function CTLD:AutoHoverLoad(Unit) +self:T(self.lid.." AutoHoverLoad") +local unittype=Unit:GetTypeName() +local unitname=Unit:GetName() +local Group=Unit:GetGroup() +local capabilities=self:_GetUnitCapabilities(Unit) +local cancrates=capabilities.crates +local cratelimit=capabilities.cratelimit +if cancrates then +local numberonboard=0 +local loaded={} +if self.Loaded_Cargo[unitname]then +loaded=self.Loaded_Cargo[unitname] +numberonboard=loaded.Cratesloaded or 0 +end +local load=cratelimit-numberonboard +local canload=self:CanHoverLoad(Unit) +if canload and load>0 then +self:_LoadCratesNearby(Group,Unit) +end +end +return self +end +function CTLD:CheckAutoHoverload() +if self.hoverautoloading then +for _,_pilot in pairs(self.CtldUnits)do +local Unit=UNIT:FindByName(_pilot) +if self:CanHoverLoad(Unit)then self:AutoHoverLoad(Unit)end +end +end +return self +end +function CTLD:CleanDroppedTroops() +local troops=self.DroppedTroops +local newtable={} +for _index,_group in pairs(troops)do +self:T({_group.ClassName}) +if _group and _group.ClassName=="GROUP"then +if _group:IsAlive()then +newtable[_index]=_group +end +end +end +self.DroppedTroops=newtable +local engineers=self.EngineersInField +local engtable={} +for _index,_group in pairs(engineers)do +self:T({_group.ClassName}) +if _group and _group:IsNotStatus("Stopped")then +engtable[_index]=_group +end +end +self.EngineersInField=engtable +return self +end +function CTLD:_CountStockPlusInHeloPlusAliveGroups(Restock,Threshold) +local Troopstable={} +for _id,_cargo in pairs(self.Cargo_Crates)do +local generic=_cargo +local genname=generic:GetName() +if generic and generic:GetStock0()>0 and not Troopstable[genname]then +Troopstable[genname]={ +Stock0=generic:GetStock0(), +Stock=generic:GetStock(), +StockR=generic:GetRelativeStock(), +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=generic:GetStock(), +} +if Restock==true then +Troopstable[genname].GenericCargo=generic +end +end +end +for _id,_cargo in pairs(self.Cargo_Troops)do +local generic=_cargo +local genname=generic:GetName() +if generic and generic:GetStock0()>0 and not Troopstable[genname]then +Troopstable[genname]={ +Stock0=generic:GetStock0(), +Stock=generic:GetStock(), +StockR=generic:GetRelativeStock(), +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=generic:GetStock(), +} +if Restock==true then +Troopstable[genname].GenericCargo=generic +end +end +end +for _index,_group in pairs(self.DroppedTroops)do +if _group and _group:IsAlive()then +self:T("Looking at ".._group:GetName().." in the field") +local generic=self:GetGenericCargoObjectFromGroupName(_group:GetName()) +if generic then +local genname=generic:GetName() +self:T("Found Generic "..genname.." in the field. Adding.") +if generic:GetStock0()>0 then +Troopstable[genname].Infield=Troopstable[genname].Infield+1 +Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo +end +else +self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) +end +end +end +for _unitname,_loaded in pairs(self.Loaded_Cargo)do +local _unit=UNIT:FindByName(_unitname) +if _unit and _unit:IsAlive()then +local unitname=_unit:GetName() +local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{} +for _,_cgo in pairs(loadedcargo)do +local cargo=_cgo +local type=cargo.CargoType +local gname=cargo.Name +local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname) +self:T("Looking at "..gname.." in the helo - type = "..type) +if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then +if gcargo and gcargo:GetStock0()>0 then +self:T("Adding "..gname.." in the helo - type = "..type) +if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then +Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1 +end +if(type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then +local counting=gcargo.CratesNeeded +local added=1 +if counting>1 then +added=added/counting +end +Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+added +end +Troopstable[gname].Sum=Troopstable[gname].Infield+Troopstable[gname].Stock+Troopstable[gname].Inhelo+Troopstable[gname].CratesInfield +end +end +end +end +end +if self.Spawned_Cargo then +for i=#self.Spawned_Cargo,1,-1 do +local cargo=self.Spawned_Cargo[i] +if cargo and cargo:GetPositionable()and cargo:GetPositionable():IsAlive()then +local genname=cargo:GetName() +local gcargo=self:_FindCratesCargoObject(genname) +if Troopstable[genname]and gcargo and gcargo:GetStock0()>0 then +local needed=gcargo.CratesNeeded or 1 +local added=1 +if needed>1 then +added=added/needed +end +Troopstable[genname].CratesInfield=Troopstable[genname].CratesInfield+added +Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock ++Troopstable[genname].Inhelo+Troopstable[genname].CratesInfield +end +end +end +for i=#self.Spawned_Cargo,1,-1 do +local cargo=self.Spawned_Cargo[i] +if cargo and cargo:GetPositionable()and cargo:GetPositionable():IsAlive()then +local genname=cargo:GetName() +if Troopstable[genname]then +if Troopstable[genname].Inhelo==0 and Troopstable[genname].CratesInfield<1 then +Troopstable[genname].CratesInfield=0 +Troopstable[genname].Sum=Troopstable[genname].Stock +cargo:GetPositionable():Destroy(false) +table.remove(self.Spawned_Cargo,i) +local leftover=Troopstable[genname].Stock0-(Troopstable[genname].Infield+Troopstable[genname].Inhelo+Troopstable[genname].CratesInfield) +if leftover0 then +local text=string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d",self.lid,pilots,boxes,cc,tc) +local m=MESSAGE:New(text,10,"CTLD"):ToAll() +if self.verbose>0 then +self:I(self.lid.."Cargo and Troops in Stock:") +for _,_troop in pairs(self.Cargo_Crates)do +local name=_troop:GetName() +local stock=_troop:GetStock() +self:I(string.format("-- %s \t\t\t %d",name,stock)) +end +for _,_troop in pairs(self.Cargo_Statics)do +local name=_troop:GetName() +local stock=_troop:GetStock() +self:I(string.format("-- %s \t\t\t %d",name,stock)) +end +for _,_troop in pairs(self.Cargo_Troops)do +local name=_troop:GetName() +local stock=_troop:GetStock() +self:I(string.format("-- %s \t\t %d",name,stock)) +end +end +end +self:__Status(-30) +return self +end +function CTLD:onafterStop(From,Event,To) +self:T({From,Event,To}) +self:UnHandleEvent(EVENTS.PlayerEnterAircraft) +self:UnHandleEvent(EVENTS.PlayerEnterUnit) +self:UnHandleEvent(EVENTS.PlayerLeaveUnit) +self:UnHandleEvent(EVENTS.UnitLost) +self:UnHandleEvent(EVENTS.Shot) +return self +end +function CTLD:onbeforeTroopsPickedUp(From,Event,To,Group,Unit,Cargo) +self:T({From,Event,To}) +return self +end +function CTLD:onbeforeCratesPickedUp(From,Event,To,Group,Unit,Cargo) +self:T({From,Event,To}) +return self +end +function CTLD:onbeforeTroopsExtracted(From,Event,To,Group,Unit,Troops,Groupname) +self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +self.PlayerTaskQueue:ForEach( +function(Task) +local task=Task +local subtype=task:GetSubType() +if Event==subtype and not task:IsDone()then +local targetzone=task.Target:GetObject() +self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")}) +if task:GetProperty("ExtractName")then +local okaygroup=string.find(Groupname,task:GetProperty("ExtractName"),1,true) +if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and okaygroup then +if task.Clients:HasUniqueID(playername)then +task:__Success(-1) +end +end +else +self:T({Text="'ExtractName' Property not set",Name=Groupname,Property=task.Type}) +end +end +end +) +end +return self +end +function CTLD:onbeforeTroopsDeployed(From,Event,To,Group,Unit,Troops) +self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +local dropcoord=Troops:GetCoordinate()or COORDINATE:New(0,0,0) +local dropvec2=dropcoord:GetVec2() +self.PlayerTaskQueue:ForEach( +function(Task) +local task=Task +local subtype=task:GetSubType() +if Event==subtype and not task:IsDone()then +local targetzone=task.Target:GetObject() +if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)then +if task.Clients:HasUniqueID(playername)then +task:__Success(-1) +end +end +end +end +) +end +return self +end +function CTLD:onafterTroopsDeployed(From,Event,To,Group,Unit,Troops,Type) +self:T({From,Event,To}) +if self.movetroopstowpzone and Type~=CTLD_CARGO.Enum.ENGINEERS then +self:_MoveGroupToZone(Troops) +end +return self +end +function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) +return self +end +function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle) +self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +local dropcoord=Vehicle:GetCoordinate()or COORDINATE:New(0,0,0) +local dropvec2=dropcoord:GetVec2() +self.PlayerTaskQueue:ForEach( +function(Task) +local task=Task +local subtype=task:GetSubType() +if Event==subtype and not task:IsDone()then +local targetzone=task.Target:GetObject() +if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)then +if task.Clients:HasUniqueID(playername)then +task:__Success(-1) +end +end +end +end +) +end +return self +end +function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) +self:T({From,Event,To}) +if self.movetroopstowpzone and Vehicle then +local cg=self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) +if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_names[cg:GetName()])))then +self:_MoveGroupToZone(Vehicle) +end +end +return self +end +function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) +self:T({From,Event,To}) +return self +end +function CTLD:onbeforeSave(From,Event,To,path,filename) +self:T({From,Event,To,path,filename}) +if not self.enableLoadSave then +return self +end +if not io then +self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") +return false +end +if path==nil and not lfs then +self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +return true +end +function CTLD:onafterSave(From,Event,To,path,filename) +self:T({From,Event,To,path,filename}) +if not self.enableLoadSave then +return self +end +local function _savefile(filename,data) +local f=assert(io.open(filename,"wb")) +f:write(data) +f:close() +end +if lfs then +path=self.filepath or lfs.writedir() +end +filename=filename or self.filename +if path~=nil then +filename=path.."\\"..filename +end +local grouptable=self.DroppedTroops +local cgovehic=self.Cargo_Crates +local cgotable=self.Cargo_Troops +local stcstable=self.Spawned_Cargo +local statics=nil +local statics={} +self:T(self.lid.."Building Statics Table for Saving") +for _,_cargo in pairs(stcstable)do +local cargo=_cargo +local object=cargo:GetPositionable() +if object and object:IsAlive()and(cargo:WasDropped()or not cargo:HasMoved())then +statics[#statics+1]=cargo +end +end +local function FindCargoType(name,table) +local match=false +local cargo=nil +name=string.gsub(name,"-"," ") +for _ind,_cargo in pairs(table)do +local thiscargo=_cargo +local template=thiscargo:GetTemplates() +if type(template)=="string"then +template={template} +end +for _,_name in pairs(template)do +_name=string.gsub(_name,"-"," ") +if string.find(name,_name)and _cargo:GetType()~=CTLD_CARGO.Enum.REPAIR then +match=true +cargo=thiscargo +end +end +if match then break end +end +return match,cargo +end +local data="Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure,StaticCategory,StaticType,StaticShape,SpawnTime\n" +local n=0 +for _,_grp in pairs(grouptable)do +local group=_grp +if group and group:IsAlive()then +local name=group:GetName() +local template=name +if string.find(template,"#")then +template=string.gsub(name,"#(%d+)$","") +end +local template=string.gsub(name,"-(%d+)$","") +local match,cargo=FindCargoType(template,cgotable) +if not match then +match,cargo=FindCargoType(template,cgovehic) +end +if match then +n=n+1 +local cargo=cargo +local cgoname=cargo.Name +local cgotemp=cargo.Templates +local cgotype=cargo.CargoType +local cgoneed=cargo.CratesNeeded +local cgomass=cargo.PerCrateMass +local scat,stype,sshape=cargo:GetStaticTypeAndShape() +local structure=UTILS.GetCountPerTypeName(group) +local strucdata="" +for typen,anzahl in pairs(structure)do +strucdata=strucdata..typen.."=="..anzahl..";" +end +local spawntime=group.spawntime or timer.getTime()+n +if type(cgotemp)=="table"then +local templates="{" +for _,_tmpl in pairs(cgotemp)do +templates=templates.._tmpl..";" +end +templates=templates.."}" +cgotemp=templates +end +local location=group:GetVec3() +local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s,%f\n" +,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata,scat,stype,sshape or"none",spawntime) +data=data..txt +end +end +end +for _,_cgo in pairs(statics)do +local object=_cgo +local cgoname=object.Name +local cgotemp=object.Templates +if type(cgotemp)=="table"then +local templates="{" +for _,_tmpl in pairs(cgotemp)do +templates=templates.._tmpl..";" +end +templates=templates.."}" +cgotemp=templates +end +local cgotype=object.CargoType +local cgoneed=object.CratesNeeded +local cgomass=object.PerCrateMass +local crateobj=object.Positionable +local location=crateobj:GetVec3() +local scat,stype,sshape=object:GetStaticTypeAndShape() +local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,'none',%s,%s,%s\n" +,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,scat,stype,sshape or"none") +data=data..txt +end +_savefile(filename,data) +if self.enableLoadSave then +local interval=self.saveinterval +local filename=self.filename +local filepath=self.filepath +self:__Save(interval,filepath,filename) +end +return self +end +function CTLD:onbeforeLoad(From,Event,To,path,filename) +self:T({From,Event,To,path,filename}) +if not self.enableLoadSave then +return self +end +local function _fileexists(name) +local f=io.open(name,"r") +if f~=nil then +io.close(f) +return true +else +return false +end +end +filename=filename or self.filename +path=path or self.filepath +if not io then +self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") +return false +end +if path==nil and not lfs then +self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +if lfs then +path=path or lfs.writedir() +end +if path~=nil then +filename=path.."\\"..filename +end +local exists=_fileexists(filename) +if exists then +return true +else +self:E(self.lid..string.format("WARNING: State file %s might not exist.",filename)) +return false +end +end +function CTLD:onafterLoad(From,Event,To,path,filename) +self:T({From,Event,To,path,filename}) +if not self.enableLoadSave then +return self +end +local function _loadfile(filename) +local f=assert(io.open(filename,"rb")) +local data=f:read("*all") +f:close() +return data +end +filename=filename or self.filename +path=path or self.filepath +if lfs then +path=path or lfs.writedir() +end +if path~=nil then +filename=path.."\\"..filename +end +local text=string.format("Loading CTLD state from file %s",filename) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:I(self.lid..text) +local file=assert(io.open(filename,"rb")) +local loadeddata={} +for line in file:lines()do +loadeddata[#loadeddata+1]=line +end +file:close() +table.remove(loadeddata,1) +local n=0 +for _id,_entry in pairs(loadeddata)do +local dataset=UTILS.Split(_entry,",") +local groupname=dataset[1] +local vec2={} +vec2.x=tonumber(dataset[2]) +vec2.y=tonumber(dataset[4]) +local cargoname=dataset[5] +local cargotemplates=dataset[6] +local cargotype=dataset[7] +local size=tonumber(dataset[8]) +local mass=tonumber(dataset[9]) +local StaticCategory=dataset[11] +local StaticType=dataset[12] +local StaticShape=dataset[13] +n=n+1 +local timestamp=tonumber(dataset[14])or(timer.getTime()+n) +self:T2("TimeStamp = "..timestamp) +if type(groupname)=="string"and groupname~="STATIC"then +cargotemplates=string.gsub(cargotemplates,"{","") +cargotemplates=string.gsub(cargotemplates,"}","") +cargotemplates=UTILS.Split(cargotemplates,";") +local structure=nil +if dataset[10]and dataset[10]~="none"then +structure=dataset[10] +structure=string.gsub(structure,",","") +end +local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) +if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then +local injectvehicle=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) +injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) +self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) +elseif cargotype==CTLD_CARGO.Enum.TROOPS or cargotype==CTLD_CARGO.Enum.ENGINEERS then +local injecttroops=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) +self:InjectTroops(dropzone,injecttroops,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) +end +elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then +local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) +local injectstatic=nil +if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then +cargotemplates=string.gsub(cargotemplates,"{","") +cargotemplates=string.gsub(cargotemplates,"}","") +cargotemplates=UTILS.Split(cargotemplates,";") +injectstatic=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) +injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) +elseif cargotype==CTLD_CARGO.Enum.STATIC or cargotype==CTLD_CARGO.Enum.REPAIR then +injectstatic=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) +injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) +local map=cargotype:GetStaticResourceMap() +injectstatic:SetStaticResourceMap(map) +end +if injectstatic then +self:InjectStatics(dropzone,injectstatic,false,true) +end +end +end +if self.keeploadtable then +self:__Loaded(1,self.LoadedGroupsTable) +end +return self +end +end +do +CTLD_HERCULES={ +ClassName="CTLD_HERCULES", +lid="", +Name="", +Version="0.0.3", +} +CTLD_HERCULES.Types={ +["ATGM M1045 HMMWV TOW Air [7183lb]"]={['name']="M1045 HMMWV TOW",['container']=true}, +["ATGM M1045 HMMWV TOW Skid [7073lb]"]={['name']="M1045 HMMWV TOW",['container']=false}, +["APC M1043 HMMWV Armament Air [7023lb]"]={['name']="M1043 HMMWV Armament",['container']=true}, +["APC M1043 HMMWV Armament Skid [6912lb]"]={['name']="M1043 HMMWV Armament",['container']=false}, +["SAM Avenger M1097 Air [7200lb]"]={['name']="M1097 Avenger",['container']=true}, +["SAM Avenger M1097 Skid [7090lb]"]={['name']="M1097 Avenger",['container']=false}, +["APC Cobra Air [10912lb]"]={['name']="Cobra",['container']=true}, +["APC Cobra Skid [10802lb]"]={['name']="Cobra",['container']=false}, +["APC M113 Air [21624lb]"]={['name']="M-113",['container']=true}, +["APC M113 Skid [21494lb]"]={['name']="M-113",['container']=false}, +["Tanker M978 HEMTT [34000lb]"]={['name']="M978 HEMTT Tanker",['container']=false}, +["HEMTT TFFT [34400lb]"]={['name']="HEMTT TFFT",['container']=false}, +["SPG M1128 Stryker MGS [33036lb]"]={['name']="M1128 Stryker MGS",['container']=false}, +["AAA Vulcan M163 Air [21666lb]"]={['name']="Vulcan",['container']=true}, +["AAA Vulcan M163 Skid [21577lb]"]={['name']="Vulcan",['container']=false}, +["APC M1126 Stryker ICV [29542lb]"]={['name']="M1126 Stryker ICV",['container']=false}, +["ATGM M1134 Stryker [30337lb]"]={['name']="M1134 Stryker ATGM",['container']=false}, +["APC LAV-25 Air [22520lb]"]={['name']="LAV-25",['container']=true}, +["APC LAV-25 Skid [22514lb]"]={['name']="LAV-25",['container']=false}, +["M1025 HMMWV Air [6160lb]"]={['name']="Hummer",['container']=true}, +["M1025 HMMWV Skid [6050lb]"]={['name']="Hummer",['container']=false}, +["IFV M2A2 Bradley [34720lb]"]={['name']="M-2 Bradley",['container']=false}, +["IFV MCV-80 [34720lb]"]={['name']="MCV-80",['container']=false}, +["IFV BMP-1 [23232lb]"]={['name']="BMP-1",['container']=false}, +["IFV BMP-2 [25168lb]"]={['name']="BMP-2",['container']=false}, +["IFV BMP-3 [32912lb]"]={['name']="BMP-3",['container']=false}, +["ARV BRDM-2 Air [12320lb]"]={['name']="BRDM-2",['container']=true}, +["ARV BRDM-2 Skid [12210lb]"]={['name']="BRDM-2",['container']=false}, +["APC BTR-80 Air [23936lb]"]={['name']="BTR-80",['container']=true}, +["APC BTR-80 Skid [23826lb]"]={['name']="BTR-80",['container']=false}, +["APC BTR-82A Air [24998lb]"]={['name']="BTR-82A",['container']=true}, +["APC BTR-82A Skid [24888lb]"]={['name']="BTR-82A",['container']=false}, +["SAM ROLAND ADS [34720lb]"]={['name']="Roland Radar",['container']=false}, +["SAM ROLAND LN [34720b]"]={['name']="Roland ADS",['container']=false}, +["SAM SA-13 STRELA [21624lb]"]={['name']="Strela-10M3",['container']=false}, +["AAA ZSU-23-4 Shilka [32912lb]"]={['name']="ZSU-23-4 Shilka",['container']=false}, +["SAM SA-19 Tunguska 2S6 [34720lb]"]={['name']="2S6 Tunguska",['container']=false}, +["Transport UAZ-469 Air [3747lb]"]={['name']="UAZ-469",['container']=true}, +["Transport UAZ-469 Skid [3630lb]"]={['name']="UAZ-469",['container']=false}, +["AAA GEPARD [34720lb]"]={['name']="Gepard",['container']=false}, +["SAM CHAPARRAL Air [21624lb]"]={['name']="M48 Chaparral",['container']=true}, +["SAM CHAPARRAL Skid [21516lb]"]={['name']="M48 Chaparral",['container']=false}, +["SAM LINEBACKER [34720lb]"]={['name']="M6 Linebacker",['container']=false}, +["Transport URAL-375 [14815lb]"]={['name']="Ural-375",['container']=false}, +["Transport M818 [16000lb]"]={['name']="M 818",['container']=false}, +["IFV MARDER [34720lb]"]={['name']="Marder",['container']=false}, +["Transport Tigr Air [15900lb]"]={['name']="Tigr_233036",['container']=true}, +["Transport Tigr Skid [15730lb]"]={['name']="Tigr_233036",['container']=false}, +["IFV TPZ FUCH [33440lb]"]={['name']="TPZ",['container']=false}, +["IFV BMD-1 Air [18040lb]"]={['name']="BMD-1",['container']=true}, +["IFV BMD-1 Skid [17930lb]"]={['name']="BMD-1",['container']=false}, +["IFV BTR-D Air [18040lb]"]={['name']="BTR_D",['container']=true}, +["IFV BTR-D Skid [17930lb]"]={['name']="BTR_D",['container']=false}, +["EWR SBORKA Air [21624lb]"]={['name']="Dog Ear radar",['container']=true}, +["EWR SBORKA Skid [21624lb]"]={['name']="Dog Ear radar",['container']=false}, +["ART 2S9 NONA Air [19140lb]"]={['name']="SAU 2-C9",['container']=true}, +["ART 2S9 NONA Skid [19030lb]"]={['name']="SAU 2-C9",['container']=false}, +["ART GVOZDIKA [34720lb]"]={['name']="SAU Gvozdika",['container']=false}, +["APC MTLB Air [26400lb]"]={['name']="MTLB",['container']=true}, +["APC MTLB Skid [26290lb]"]={['name']="MTLB",['container']=false}, +} +function CTLD_HERCULES:New(Coalition,Alias,CtldObject) +local self=BASE:Inherit(self,FSM:New()) +if Coalition and type(Coalition)=="string"then +if Coalition=="blue"then +self.coalition=coalition.side.BLUE +self.coalitiontxt=Coalition +elseif Coalition=="red"then +self.coalition=coalition.side.RED +self.coalitiontxt=Coalition +elseif Coalition=="neutral"then +self.coalition=coalition.side.NEUTRAL +self.coalitiontxt=Coalition +else +self:E("ERROR: Unknown coalition in CTLD!") +end +else +self.coalition=Coalition +self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition)) +end +if Alias then +self.alias=tostring(Alias) +else +self.alias="UNHCR" +if self.coalition then +if self.coalition==coalition.side.RED then +self.alias="Red CTLD Hercules" +elseif self.coalition==coalition.side.BLUE then +self.alias="Blue CTLD Hercules" +end +end +end +self.lid=string.format("%s (%s) | ",self.alias,self.coalitiontxt) +self.infantrytemplate="Infantry" +self.CTLD=CtldObject +self.verbose=true +self.j=0 +self.carrierGroups={} +self.Cargo={} +self.ParatrooperCount={} +self.ObjectTracker={} +self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") +self:HandleEvent(EVENTS.Shot,self._HandleShot) +self:I(self.lid.."Started") +self:CheckTemplates() +return self +end +function CTLD_HERCULES:CheckTemplates() +self:T(self.lid..'CheckTemplates') +self.Types["Paratroopers 10"]={ +name=self.infantrytemplate, +container=false, +available=false, +} +local missing={} +local nomissing=0 +local found={} +local nofound=0 +for _index,_tab in pairs(self.Types)do +local outcometxt="MISSING" +if _DATABASE.Templates.Groups[_tab.name]then +outcometxt="OK" +self.Types[_index].available=true +found[_tab.name]=true +else +self.Types[_index].available=false +missing[_tab.name]=true +end +if self.verbose then +self:I(string.format(self.lid.."Checking template for %s (%s) ... %s",_index,_tab.name,outcometxt)) +end +end +for _,_name in pairs(found)do +nofound=nofound+1 +end +for _,_name in pairs(missing)do +nomissing=nomissing+1 +end +self:I(string.format(self.lid.."Template Check Summary: Found %d, Missing %d, Total %d",nofound,nomissing,nofound+nomissing)) +return self +end +function CTLD_HERCULES:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,Cargo_Country,GroupSpacing) +self:T(self.lid..'Soldier_SpawnGroup') +self:T(Cargo_Drop_Position) +local InjectTroopsType=CTLD_CARGO:New(nil,self.infantrytemplate,{self.infantrytemplate},CTLD_CARGO.Enum.TROOPS,true,true,10,nil,false,80) +local position=Cargo_Drop_Position:GetVec2() +local dropzone=ZONE_RADIUS:New("Infantry "..math.random(1,10000),position,100) +self.CTLD:InjectTroops(dropzone,InjectTroopsType) +return self +end +function CTLD_HERCULES:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,Cargo_Country) +self:T(self.lid.."Cargo_SpawnGroup") +self:T(Cargo_Type_name) +if Cargo_Type_name~='Container red 1'then +local InjectVehicleType=CTLD_CARGO:New(nil,Cargo_Type_name,{Cargo_Type_name},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000) +local position=Cargo_Drop_Position:GetVec2() +local dropzone=ZONE_RADIUS:New("Vehicle "..math.random(1,10000),position,100) +self.CTLD:InjectVehicles(dropzone,InjectVehicleType) +end +return self +end +function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,dead,Cargo_Country) +self:T(self.lid.."Cargo_SpawnStatic") +self:T("Static "..Cargo_Type_name.." Dead "..tostring(dead)) +local position=Cargo_Drop_Position:GetVec2() +local Zone=ZONE_RADIUS:New("Cargo Static "..math.random(1,10000),position,100) +if not dead then +local injectstatic=CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1) +self.CTLD:InjectStatics(Zone,injectstatic,true,true) +end +return self +end +function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name,_pos) +local theCargo=self.CTLD:_FindCratesCargoObject(_name) +if theCargo then +self.CTLD.CrateCounter=self.CTLD.CrateCounter+1 +local CCat,CType,CShape=theCargo:GetStaticTypeAndShape() +local basetype=CType or self.CTLD.basetype or"container_cargo" +CCat=CCat or"Cargos" +local theStatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) +:InitCargoMass(theCargo.PerCrateMass) +:InitCargo(self.CTLD.enableslingload) +:InitCoordinate(_pos) +if CShape then +theStatic:InitShape(CShape) +end +theStatic:Spawn(270,_name.."-Container-"..math.random(1,100000)) +self.CTLD.Spawned_Crates[self.CTLD.CrateCounter]=theStatic +local newCargo=CTLD_CARGO:New(self.CTLD.CargoCounter,theCargo.Name,theCargo.Templates,theCargo.CargoType,true,false,theCargo.CratesNeeded,self.CTLD.Spawned_Crates[self.CTLD.CrateCounter],true,theCargo.PerCrateMass,nil,theCargo.Subcategory) +local map=theCargo:GetStaticResourceMap() +newCargo:SetStaticResourceMap(map) +table.insert(self.CTLD.Spawned_Cargo,newCargo) +newCargo:SetWasDropped(true) +newCargo:SetHasMoved(true) +end +return self +end +function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direction,Cargo_Content_position,Cargo_Type_name,Cargo_over_water,Container_Enclosed,ParatrooperGroupSpawn,offload_cargo,all_cargo_survive_to_the_ground,all_cargo_gets_destroyed,destroy_cargo_dropped_without_parachute,Cargo_Country) +self:T(self.lid..'Cargo_SpawnObjects') +local CargoHeading=self.CargoHeading +if offload_cargo==true or ParatrooperGroupSpawn==true then +if ParatrooperGroupSpawn==true then +self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,10) +else +self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country) +end +else +if all_cargo_gets_destroyed==true or Cargo_over_water==true then +else +if all_cargo_survive_to_the_ground==true then +if ParatrooperGroupSpawn==true then +self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,true,Cargo_Country) +else +self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country) +end +if Container_Enclosed==true then +if ParatrooperGroupSpawn==false then +self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,"Hercules_Container_Parachute_Static",CargoHeading,false,Cargo_Country) +end +end +end +if destroy_cargo_dropped_without_parachute==true then +if Container_Enclosed==true then +if ParatrooperGroupSpawn==true then +self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,0) +else +if self.CTLD.dropAsCargoCrate then +self:Cargo_SpawnDroppedAsCargo(Cargo_Type_name,Cargo_Content_position) +else +self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country) +self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,"Hercules_Container_Parachute_Static",CargoHeading,false,Cargo_Country) +end +end +else +self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,true,Cargo_Country) +end +end +end +end +return self +end +function CTLD_HERCULES:Calculate_Object_Height_AGL(group) +self:T(self.lid.."Calculate_Object_Height_AGL") +if group.ClassName and group.ClassName=="GROUP"then +local gcoord=group:GetCoordinate() +local height=group:GetHeight() +local lheight=gcoord:GetLandHeight() +self:T(self.lid.."Height "..height-lheight) +return height-lheight +else +if group:isExist()then +local dcsposition=group:getPosition().p +local dcsvec2={x=dcsposition.x,y=dcsposition.z} +local height=math.floor(group:getPosition().p.y-land.getHeight(dcsvec2)) +self.ObjectTracker[group.id_]=dcsposition +self:T(self.lid.."Height "..height) +return height +else +return 0 +end +end +end +function CTLD_HERCULES:Check_SurfaceType(object) +self:T(self.lid.."Check_SurfaceType") +if object:isExist()then +return land.getSurfaceType({x=object:getPosition().p.x,y=object:getPosition().p.z}) +else +return 1 +end +end +function CTLD_HERCULES:Cargo_Track(cargo,initiator) +self:T(self.lid.."Cargo_Track") +local Cargo_Drop_initiator=initiator +if cargo.Cargo_Contents~=nil then +if self:Calculate_Object_Height_AGL(cargo.Cargo_Contents)<10 then +if self:Check_SurfaceType(cargo.Cargo_Contents)==2 or self:Check_SurfaceType(cargo.Cargo_Contents)==3 then +cargo.Cargo_over_water=true +end +local dcsvec3=self.ObjectTracker[cargo.Cargo_Contents.id_]or initiator:GetVec3() +self:T("SPAWNPOSITION: ") +self:T({dcsvec3}) +local Vec2={ +x=dcsvec3.x, +y=dcsvec3.z, +} +local vec3=COORDINATE:NewFromVec2(Vec2) +self.ObjectTracker[cargo.Cargo_Contents.id_]=nil +self:Cargo_SpawnObjects(Cargo_Drop_initiator,cargo.Cargo_Drop_Direction,vec3,cargo.Cargo_Type_name,cargo.Cargo_over_water,cargo.Container_Enclosed,cargo.ParatrooperGroupSpawn,cargo.offload_cargo,cargo.all_cargo_survive_to_the_ground,cargo.all_cargo_gets_destroyed,cargo.destroy_cargo_dropped_without_parachute,cargo.Cargo_Country) +if cargo.Cargo_Contents:isExist()then +cargo.Cargo_Contents:destroy() +end +cargo.scheduleFunctionID:Stop() +cargo={} +end +end +return self +end +function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_NorthCorrection(point) +self:T(self.lid.."Calculate_Cargo_Drop_initiator_NorthCorrection") +if not point.z then +point.z=point.y +point.y=0 +end +local lat,lon=coord.LOtoLL(point) +local north_posit=coord.LLtoLO(lat+1,lon) +return math.atan2(north_posit.z-point.z,north_posit.x-point.x) +end +function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_Heading(Cargo_Drop_initiator) +self:T(self.lid.."Calculate_Cargo_Drop_initiator_Heading") +local Heading=Cargo_Drop_initiator:GetHeading() +Heading=Heading+self:Calculate_Cargo_Drop_initiator_NorthCorrection(Cargo_Drop_initiator:GetVec3()) +if Heading<0 then +Heading=Heading+(2*math.pi) +end +return Heading+0.06 +end +function CTLD_HERCULES:Cargo_Initialize(Initiator,Cargo_Contents,Cargo_Type_name,Container_Enclosed,SoldierGroup,ParatrooperGroupSpawnInit) +self:T(self.lid.."Cargo_Initialize") +local Cargo_Drop_initiator=Initiator:GetName() +if Cargo_Drop_initiator~=nil then +if ParatrooperGroupSpawnInit==true then +self:T("Paratrooper Drop") +if not self.ParatrooperCount[Cargo_Drop_initiator]then +self.ParatrooperCount[Cargo_Drop_initiator]=1 +else +self.ParatrooperCount[Cargo_Drop_initiator]=self.ParatrooperCount[Cargo_Drop_initiator]+1 +end +local Paratroopers=self.ParatrooperCount[Cargo_Drop_initiator] +self:T("Paratrooper Drop Number "..self.ParatrooperCount[Cargo_Drop_initiator]) +local SpawnParas=false +if math.fmod(Paratroopers,10)==0 then +SpawnParas=true +end +self.j=self.j+1 +self.Cargo[self.j]={} +self.Cargo[self.j].Cargo_Drop_Direction=self:Calculate_Cargo_Drop_initiator_Heading(Initiator) +self.Cargo[self.j].Cargo_Contents=Cargo_Contents +self.Cargo[self.j].Cargo_Type_name=Cargo_Type_name +self.Cargo[self.j].Container_Enclosed=Container_Enclosed +self.Cargo[self.j].ParatrooperGroupSpawn=SpawnParas +self.Cargo[self.j].Cargo_Country=Initiator:GetCountry() +if self:Calculate_Object_Height_AGL(Initiator)<5.0 then +self.Cargo[self.j].offload_cargo=true +elseif self:Calculate_Object_Height_AGL(Initiator)<10.0 then +self.Cargo[self.j].all_cargo_survive_to_the_ground=true +elseif self:Calculate_Object_Height_AGL(Initiator)<100.0 then +self.Cargo[self.j].all_cargo_gets_destroyed=true +else +self.Cargo[self.j].all_cargo_gets_destroyed=false +end +local timer=TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) +self.Cargo[self.j].scheduleFunctionID=timer +timer:Start(1,1,600) +else +self.j=self.j+1 +self.Cargo[self.j]={} +self.Cargo[self.j].Cargo_Drop_Direction=self:Calculate_Cargo_Drop_initiator_Heading(Initiator) +self.Cargo[self.j].Cargo_Contents=Cargo_Contents +self.Cargo[self.j].Cargo_Type_name=Cargo_Type_name +self.Cargo[self.j].Container_Enclosed=Container_Enclosed +self.Cargo[self.j].ParatrooperGroupSpawn=false +self.Cargo[self.j].Cargo_Country=Initiator:GetCountry() +if self:Calculate_Object_Height_AGL(Initiator)<5.0 then +self.Cargo[self.j].offload_cargo=true +elseif self:Calculate_Object_Height_AGL(Initiator)<10.0 then +self.Cargo[self.j].all_cargo_survive_to_the_ground=true +elseif self:Calculate_Object_Height_AGL(Initiator)<100.0 then +self.Cargo[self.j].all_cargo_gets_destroyed=true +else +self.Cargo[self.j].destroy_cargo_dropped_without_parachute=true +end +local timer=TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) +self.Cargo[self.j].scheduleFunctionID=timer +timer:Start(1,1,600) +end +end +return self +end +function CTLD_HERCULES:SetType(key,cargoType,cargoNum) +self:T(self.lid.."SetType") +self.carrierGroups[key]['cargoType']=cargoType +self.carrierGroups[key]['cargoNum']=cargoNum +return self +end +function CTLD_HERCULES:_HandleShot(Cargo_Drop_Event) +self:T(self.lid.."Shot Event ID:"..Cargo_Drop_Event.id) +if Cargo_Drop_Event.id==EVENTS.Shot then +local GT_Name="" +local SoldierGroup=false +local ParatrooperGroupSpawnInit=false +local GT_DisplayName=Weapon.getDesc(Cargo_Drop_Event.weapon).typeName:sub(15,-1) +self:T(string.format("%sCargo_Drop_Event: %s",self.lid,Weapon.getDesc(Cargo_Drop_Event.weapon).typeName)) +if(GT_DisplayName=="Squad 30 x Soldier [7950lb]")then +self:Cargo_Initialize(Cargo_Drop_Event.IniGroup,Cargo_Drop_Event.weapon,"Soldier M4 GRG",false,true,true) +end +if self.Types[GT_DisplayName]then +local GT_Name=self.Types[GT_DisplayName]['name'] +local Cargo_Container_Enclosed=self.Types[GT_DisplayName]['container'] +self:Cargo_Initialize(Cargo_Drop_Event.IniGroup,Cargo_Drop_Event.weapon,GT_Name,Cargo_Container_Enclosed) +end +end +return self +end +function CTLD_HERCULES:_HandleBirth(event) +self:T(self.lid.."Birth Event ID:"..event.id) +return self +end +end +CSAR={ +ClassName="CSAR", +verbose=0, +lid="", +coalition=1, +coalitiontxt="blue", +FreeVHFFrequencies={}, +UsedVHFFrequencies={}, +takenOff={}, +csarUnits={}, +downedPilots={}, +landedStatus={}, +addedTo={}, +woundedGroups={}, +inTransitGroups={}, +smokeMarkers={}, +heliVisibleMessage={}, +heliCloseMessage={}, +max_units=6, +hoverStatus={}, +pilotDisabled={}, +pilotLives={}, +useprefix=true, +csarPrefix={}, +template=nil, +mash={}, +smokecolor=4, +rescues=0, +rescuedpilots=0, +limitmaxdownedpilots=true, +maxdownedpilots=10, +useFIFOLimitReplacement=false, +allheligroupset=nil, +topmenuname="CSAR", +ADFRadioPwr=1000, +PilotWeight=80, +CreateRadioBeacons=true, +UserSetGroup=nil, +AllowIRStrobe=false, +IRStrobeRuntime=300, +} +CSAR.AircraftType={} +CSAR.AircraftType["SA342Mistral"]=2 +CSAR.AircraftType["SA342Minigun"]=2 +CSAR.AircraftType["SA342L"]=4 +CSAR.AircraftType["SA342M"]=4 +CSAR.AircraftType["UH-1H"]=8 +CSAR.AircraftType["Mi-8MTV2"]=12 +CSAR.AircraftType["Mi-8MT"]=12 +CSAR.AircraftType["Mi-24P"]=8 +CSAR.AircraftType["Mi-24V"]=8 +CSAR.AircraftType["Bell-47"]=2 +CSAR.AircraftType["UH-60L"]=10 +CSAR.AircraftType["UH-60L_DAP"]=2 +CSAR.AircraftType["AH-64D_BLK_II"]=2 +CSAR.AircraftType["Bronco-OV-10A"]=2 +CSAR.AircraftType["MH-60R"]=10 +CSAR.AircraftType["OH-6A"]=2 +CSAR.AircraftType["OH58D"]=2 +CSAR.AircraftType["CH-47Fbl1"]=31 +CSAR.version="1.0.34" +function CSAR:New(Coalition,Template,Alias) +local self=BASE:Inherit(self,FSM:New()) +BASE:T({Coalition,Template,Alias}) +if Coalition and type(Coalition)=="string"then +if Coalition=="blue"then +self.coalition=coalition.side.BLUE +self.coalitiontxt=Coalition +elseif Coalition=="red"then +self.coalition=coalition.side.RED +self.coalitiontxt=Coalition +elseif Coalition=="neutral"then +self.coalition=coalition.side.NEUTRAL +self.coalitiontxt=Coalition +else +self:E("ERROR: Unknown coalition in CSAR!") +end +else +self.coalition=Coalition +self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition)) +end +if Alias then +self.alias=tostring(Alias) +else +self.alias="Red Cross" +if self.coalition then +if self.coalition==coalition.side.RED then +self.alias="IFRC" +elseif self.coalition==coalition.side.BLUE then +self.alias="CSAR" +end +end +end +self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","PilotDown","*") +self:AddTransition("*","Approach","*") +self:AddTransition("*","Landed","*") +self:AddTransition("*","Boarded","*") +self:AddTransition("*","Returning","*") +self:AddTransition("*","Rescued","*") +self:AddTransition("*","KIA","*") +self:AddTransition("*","Load","*") +self:AddTransition("*","Save","*") +self:AddTransition("*","Stop","Stopped") +self.addedTo={} +self.allheligroupset={} +self.csarUnits={} +self.FreeVHFFrequencies={} +self.heliVisibleMessage={} +self.heliCloseMessage={} +self.hoverStatus={} +self.inTransitGroups={} +self.landedStatus={} +self.lastCrash={} +self.takenOff={} +self.smokeMarkers={} +self.UsedVHFFrequencies={} +self.woundedGroups={} +self.downedPilots={} +self.downedpilotcounter=1 +self.rescues=0 +self.rescuedpilots=0 +self.csarOncrash=false +self.allowDownedPilotCAcontrol=false +self.enableForAI=false +self.smokecolor=4 +self.coordtype=2 +self.immortalcrew=true +self.invisiblecrew=false +self.messageTime=15 +self.pilotRuntoExtractPoint=true +self.loadDistance=75 +self.extractDistance=500 +self.loadtimemax=135 +self.radioSound="beacon.ogg" +self.beaconRefresher=29 +self.allowFARPRescue=true +self.FARPRescueDistance=1000 +self.max_units=6 +self.useprefix=true +self.csarPrefix={"helicargo","MEDEVAC"} +self.template=Template or"generic" +self.mashprefix={"MASH"} +self.autosmoke=false +self.autosmokedistance=2000 +self.limitmaxdownedpilots=true +self.maxdownedpilots=25 +self:_GenerateVHFrequencies() +self.approachdist_far=5000 +self.approachdist_near=3000 +self.pilotmustopendoors=false +self.suppressmessages=false +self.rescuehoverheight=20 +self.rescuehoverdistance=10 +self.countryblue=country.id.USA +self.countryred=country.id.RUSSIA +self.countryneutral=country.id.UN_PEACEKEEPERS +self.csarUsePara=false +self.wetfeettemplate=nil +self.usewetfeet=false +self.allowbronco=false +self.ADFRadioPwr=500 +self.PilotWeight=80 +self.UserSetGroup=nil +self.useSRS=false +self.SRSPath="E:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +self.SRSchannel=300 +self.SRSModulation=radio.modulation.AM +self.SRSport=5002 +self.SRSCulture="en-GB" +self.SRSVoice=MSRS.Voices.Google.Standard.en_GB_Standard_B +self.SRSGPathToCredentials=nil +self.SRSVolume=1.0 +self.SRSGender="male" +self.CSARVoice=MSRS.Voices.Google.Standard.en_US_Standard_A +self.CSARVoiceMS=MSRS.Voices.Microsoft.Hedda +self.coordinate=nil +local AliaS=string.gsub(self.alias," ","_") +self.filename=string.format("CSAR_%s_Persist.csv",AliaS) +self.enableLoadSave=false +self.filepath=nil +self.saveinterval=600 +return self +end +function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet,BeaconName) +self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) +local DownedPilot={} +DownedPilot.desc=Description or"" +DownedPilot.frequency=Frequency or 0 +DownedPilot.index=self.downedpilotcounter +DownedPilot.name=Groupname or Playername or"" +DownedPilot.originalUnit=OriginalUnit or"" +DownedPilot.player=Playername or"" +DownedPilot.side=Side or 0 +DownedPilot.typename=Typename or"" +DownedPilot.group=Group +DownedPilot.timestamp=0 +DownedPilot.alive=true +DownedPilot.wetfeet=Wetfeet or false +DownedPilot.BeaconName=BeaconName +local PilotTable=self.downedPilots +local counter=self.downedpilotcounter +PilotTable[counter]={} +PilotTable[counter]=DownedPilot +self:T({Table=PilotTable}) +self.downedPilots=PilotTable +self.downedpilotcounter=self.downedpilotcounter+1 +return self +end +function CSAR:_PilotsOnboard(_heliName) +self:T(self.lid.." _PilotsOnboard") +local count=0 +if self.inTransitGroups[_heliName]then +for _,_group in pairs(self.inTransitGroups[_heliName])do +count=count+1 +end +end +return count +end +function CSAR:_DoubleEjection(_unitname) +if self.lastCrash[_unitname]then +local _time=self.lastCrash[_unitname] +if timer.getTime()-_time<10 then +self:E(self.lid.."Caught double ejection!") +return true +end +end +self.lastCrash[_unitname]=timer.getTime() +return false +end +function CSAR:AddPlayerTask(PlayerTask) +self:T(self.lid.." AddPlayerTask") +if not self.PlayerTaskQueue then +self.PlayerTaskQueue=FIFO:New() +end +self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr) +return self +end +function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet) +self:T({country,point,frequency,tostring(wetfeet)}) +local freq=frequency or 1000 +local freq=freq/1000 +for i=1,10 do +math.random(i,10000) +end +if point:IsSurfaceTypeWater()or wetfeet then +point.y=0 +end +local template=self.template +if self.usewetfeet and wetfeet then +template=self.wetfeettemplate +end +local alias=string.format("Pilot %.2fkHz-%d",freq,math.random(1,99)) +local coalition=self.coalition +local pilotcacontrol=self.allowDownedPilotCAcontrol +local _spawnedGroup=SPAWN +:NewWithAlias(template,alias) +:InitCoalition(coalition) +:InitCountry(country) +:InitDelayOff() +:SpawnFromCoordinate(point) +return _spawnedGroup,alias +end +function CSAR:_AddSpecialOptions(group) +self:T(self.lid.." _AddSpecialOptions") +self:T({group}) +local immortalcrew=self.immortalcrew +local invisiblecrew=self.invisiblecrew +if immortalcrew then +local _setImmortal={ +id='SetImmortal', +params={ +value=true +} +} +group:SetCommand(_setImmortal) +end +if invisiblecrew then +local _setInvisible={ +id='SetInvisible', +params={ +value=true +} +} +group:SetCommand(_setInvisible) +end +group:OptionAlarmStateGreen() +group:OptionROEHoldFire() +return self +end +function CSAR:_AddCsar(_coalition,_country,_point,_typeName,_unitName,_playerName,_freq,noMessage,_description,forcedesc) +self:T(self.lid.." _AddCsar") +self:T({_coalition,_country,_point,_typeName,_unitName,_playerName,_freq,noMessage,_description}) +local template=self.template +local wetfeet=false +local surface=_point:GetSurfaceType() +if surface==land.SurfaceType.WATER then +wetfeet=true +end +if not _freq then +_freq=self:_GenerateADFFrequency() +if not _freq then _freq=333000 end +end +local _spawnedGroup,_alias=self:_SpawnPilotInField(_country,_point,_freq,wetfeet) +local _typeName=_typeName or"Pilot" +if not noMessage then +if _freq~=0 then +self:_DisplayToAllSAR("MAYDAY MAYDAY! ".._typeName.." is down. ",self.coalition,self.messageTime) +else +self:_DisplayToAllSAR("Troops In Contact. ".._typeName.." requests CASEVAC. ",self.coalition,self.messageTime) +end +end +local BeaconName +if _playerName then +BeaconName=_playerName..math.random(1,10000) +elseif _unitName then +BeaconName=_unitName..math.random(1,10000) +else +BeaconName="Ghost-1-1"..math.random(1,10000) +end +if(_freq and _freq~=0)then +self:_AddBeaconToGroup(_spawnedGroup,_freq,BeaconName) +end +self:_AddSpecialOptions(_spawnedGroup) +local _text=_description +if not forcedesc then +if _playerName~=nil then +if _freq~=0 then +_text="Pilot ".._playerName +else +_text="TIC - ".._playerName +end +elseif _unitName~=nil then +if _freq~=0 then +_text="AI Pilot of ".._unitName +else +_text="TIC - ".._unitName +end +end +end +self:T({_spawnedGroup,_alias}) +local _GroupName=_spawnedGroup:GetName()or _alias +self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet,BeaconName) +self:_InitSARForPilot(_spawnedGroup,_unitName,_freq,noMessage,_playerName) +return _spawnedGroup,_alias +end +function CSAR:_SpawnCsarAtZone(_zone,_coalition,_description,_randomPoint,_nomessage,unitname,typename,forcedesc) +self:T(self.lid.." _SpawnCsarAtZone") +local freq=self:_GenerateADFFrequency() +local _triggerZone=nil +if type(_zone)=="string"then +_triggerZone=ZONE:New(_zone) +elseif type(_zone)=="table"and _zone.ClassName then +if string.find(_zone.ClassName,"ZONE",1)then +_triggerZone=_zone +end +end +if _triggerZone==nil then +self:E(self.lid.."ERROR: Can\'t find zone called ".._zone,10) +return +end +local _description=_description or"PoW" +local unitname=unitname or"Old Rusty" +local typename=typename or"Phantom II" +local pos={} +if _randomPoint then +local _pos=_triggerZone:GetRandomPointVec3() +pos=COORDINATE:NewFromVec3(_pos) +else +pos=_triggerZone:GetCoordinate() +end +local _country=0 +if _coalition==coalition.side.BLUE then +_country=self.countryblue +elseif _coalition==coalition.side.RED then +_country=self.countryred +else +_country=self.countryneutral +end +self:_AddCsar(_coalition,_country,pos,typename,unitname,_description,freq,_nomessage,_description,forcedesc) +return self +end +function CSAR:SpawnCSARAtZone(Zone,Coalition,Description,RandomPoint,Nomessage,Unitname,Typename,Forcedesc) +self:_SpawnCsarAtZone(Zone,Coalition,Description,RandomPoint,Nomessage,Unitname,Typename,Forcedesc) +return self +end +function CSAR:_SpawnCASEVAC(_Point,_coalition,_description,_nomessage,unitname,typename,forcedesc) +self:T(self.lid.." _SpawnCASEVAC") +local _description=_description or"CASEVAC" +local unitname=unitname or"CASEVAC" +local typename=typename or"Ground Commander" +local pos={} +pos=_Point +local _country=0 +if _coalition==coalition.side.BLUE then +_country=self.countryblue +elseif _coalition==coalition.side.RED then +_country=self.countryred +else +_country=self.countryneutral +end +self:_AddCsar(_coalition,_country,pos,typename,unitname,_description,0,_nomessage,_description,forcedesc) +return self +end +function CSAR:SpawnCASEVAC(Point,Coalition,Description,Nomessage,Unitname,Typename,Forcedesc) +self:_SpawnCASEVAC(Point,Coalition,Description,Nomessage,Unitname,Typename,Forcedesc) +return self +end +function CSAR:_EventHandler(EventData) +self:T(self.lid.." _EventHandler") +self:T({Event=EventData.id}) +local _event=EventData +if self.enableForAI==false and _event.IniPlayerName==nil then +return self +end +if _event==nil or _event.initiator==nil then +return self +elseif _event.id==EVENTS.Takeoff then +self:T(self.lid.." Event unit - Takeoff") +local _coalition=_event.IniCoalition +if _coalition~=self.coalition then +return self +end +if _event.IniGroupName then +self.takenOff[_event.IniUnitName]=true +end +return self +elseif _event.id==EVENTS.PlayerEnterAircraft or _event.id==EVENTS.PlayerEnterUnit then +self:T(self.lid.." Event unit - Player Enter") +local _coalition=_event.IniCoalition +self:T("Coalition = "..UTILS.GetCoalitionName(_coalition)) +if _coalition~=self.coalition then +return self +end +if _event.IniPlayerName then +self.takenOff[_event.IniPlayerName]=nil +end +self:T("Taken Off: "..tostring(_event.IniUnit:InAir(true))) +if _event.IniUnit:InAir(true)then +self.takenOff[_event.IniPlayerName]=true +end +local _unit=_event.IniUnit +local _group=_event.IniGroup +local function IsBronco(Group) +local grp=Group +local typename=grp:GetTypeName() +self:T(typename) +if typename=="Bronco-OV-10A"then return true end +return false +end +if _unit:IsHelicopter()or _group:IsHelicopter()or IsBronco(_group)then +self:_AddMedevacMenuItem() +end +return self +elseif(_event.id==EVENTS.PilotDead and self.csarOncrash==false)then +self:T(self.lid.." Event unit - Pilot Dead") +local _unit=_event.IniUnit +local _unitname=_event.IniUnitName +local _group=_event.IniGroup +if _unit==nil then +return self +end +local _coalition=_event.IniCoalition +if _coalition~=self.coalition then +return self +end +if self.takenOff[_event.IniUnitName]==true or _group:IsAirborne()then +if self:_DoubleEjection(_unitname)then +return self +end +else +self:T(self.lid.." Pilot has not taken off, ignore") +end +return self +elseif _event.id==EVENTS.PilotDead or _event.id==EVENTS.Ejection then +if _event.id==EVENTS.PilotDead and self.csarOncrash==false then +return self +end +self:T(self.lid.." Event unit - Pilot Ejected") +local _unit=_event.IniUnit +local _unitname=_event.IniUnitName +local _group=_event.IniGroup +self:T({_unit.UnitName,_unitname,_group.GroupName}) +if _unit==nil then +self:T("Unit NIL!") +return self +end +local _coalition=_group:GetCoalition() +if _coalition~=self.coalition then +self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition)) +return self +end +self:T("Airborne: "..tostring(_group:IsAirborne())) +self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName])) +if not self.takenOff[_event.IniUnitName]and not _group:IsAirborne()then +self:T(self.lid.." Pilot has not taken off, ignore") +end +if self:_DoubleEjection(_unitname)then +self:T("Double Ejection!") +return self +end +local initdcscoord=nil +local initcoord=nil +if _event.id==EVENTS.Ejection and _event.TgtDCSUnit then +initdcscoord=_event.TgtDCSUnit:getPoint() +initcoord=COORDINATE:NewFromVec3(initdcscoord) +self:T({initdcscoord}) +elseif _event.IniDCSUnit then +initdcscoord=_event.IniDCSUnit:getPoint() +initcoord=COORDINATE:NewFromVec3(initdcscoord) +self:T({initdcscoord}) +end +if _event.IniPlayerName then +local PilotTable=self.downedPilots +local _foundPilot=nil +for _,_pilot in pairs(PilotTable)do +if _pilot.player==_event.IniPlayerName and _pilot.alive==true then +_foundPilot=_pilot +break +end +end +if _foundPilot then +self:T("Downed pilot already exists!") +_foundPilot.group:Destroy(false) +self:_RemoveNameFromDownedPilots(_foundPilot.name) +self:_CheckDownedPilotTable() +end +end +if self.limitmaxdownedpilots and self:_ReachedPilotLimit()then +self:T("Maxed Downed Pilot!") +return self +end +local wetfeet=false +local surface=initcoord:GetSurfaceType() +if surface==land.SurfaceType.WATER then +self:T("Wet feet!") +wetfeet=true +end +if self.csarUsePara==false or(self.csarUsePara and wetfeet)then +local _freq=self:_GenerateADFFrequency() +self:_AddCsar(_coalition,_unit:GetCountry(),initcoord,_unit:GetTypeName(),_unit:GetName(),_event.IniPlayerName,_freq,self.suppressmessages,"none") +return self +end +elseif _event.id==EVENTS.Land then +self:T(self.lid.." Landing") +if _event.IniUnitName then +self.takenOff[_event.IniUnitName]=nil +end +if self.allowFARPRescue then +local _unit=_event.IniUnit +if _unit==nil then +self:T(self.lid.." Unit nil on landing") +return self +end +local _coalition=_event.IniGroup:GetCoalition() +if _coalition~=self.coalition then +self:T(self.lid.." Wrong coalition") +return self +end +self.takenOff[_event.IniUnitName]=nil +local _place=_event.Place +if _place==nil then +self:T(self.lid.." Landing Place Nil") +return self +end +if self.inTransitGroups[_event.IniUnitName]==nil then +return self +end +if _place:GetCoalition()==self.coalition or _place:GetCoalition()==coalition.side.NEUTRAL then +self:__Landed(2,_event.IniUnitName,_place) +local IsHeloBase=false +local ABName=_place:GetName() +if ABName and string.find(ABName,"^H")then IsHeloBase=true end +self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) +else +self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) +end +end +return self +end +if(_event.id==EVENTS.LandingAfterEjection and self.csarUsePara==true)then +self:T("LANDING_AFTER_EJECTION") +local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p) +local _unitname="Aircraft" +local _typename="Ejected Pilot" +local _country=_event.initiator:getCountry() +local _coalition=coalition.getCountryCoalition(_country) +self:T("Country = ".._country.." Coalition = ".._coalition) +if _coalition==self.coalition then +local _freq=self:_GenerateADFFrequency() +self:I({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq}) +self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,self.suppressmessages,"none") +Unit.destroy(_event.initiator) +end +end +return self +end +function CSAR:_InitSARForPilot(_downedGroup,_GroupName,_freq,_nomessage,_playername) +self:T(self.lid.." _InitSARForPilot") +local _leader=_downedGroup:GetUnit(1) +local _groupName=_GroupName +local _freqk=_freq/1000 +local _coordinatesText=self:_GetPositionOfWounded(_downedGroup) +local _leadername=_leader:GetName() +if not _nomessage then +if _freq~=0 then +local _text=string.format("%s requests SAR at %s, beacon at %.2f KHz",_groupName,_coordinatesText,_freqk) +if self.coordtype~=2 then +self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) +else +self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) +local coordtext=UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) +local _text=string.format("%s requests SAR at %s, beacon at %.2f kilo hertz",_groupName,coordtext,_freqk) +self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) +end +else +local _text=string.format("Pickup Zone at %s.",_coordinatesText) +if self.coordtype~=2 then +self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) +else +self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) +local coordtext=UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) +local _text=string.format("Pickup Zone at %s.",coordtext) +self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) +end +end +end +for _,_heliName in pairs(self.csarUnits)do +self:_CheckWoundedGroupStatus(_heliName,_groupName) +end +self:__PilotDown(2,_downedGroup,_freqk,_groupName,_coordinatesText,_playername) +return self +end +function CSAR:_CheckNameInDownedPilots(name) +local PilotTable=self.downedPilots +local found=false +local table=nil +for _,_pilot in pairs(PilotTable)do +if _pilot.name==name and _pilot.alive==true then +found=true +table=_pilot +break +end +end +return found,table +end +function CSAR:_RemoveNameFromDownedPilots(name,force) +local PilotTable=self.downedPilots +local found=false +for _index,_pilot in pairs(PilotTable)do +if _pilot.name==name then +self.downedPilots[_index].alive=false +end +end +return found +end +function CSAR:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +if not ShortCallsign or ShortCallsign==false then +self.ShortCallsign=false +else +self.ShortCallsign=true +end +self.Keepnumber=Keepnumber or false +self.CallsignTranslations=CallsignTranslations +return self +end +function CSAR:_GetCustomCallSign(UnitName) +local callsign=UnitName +local unit=UNIT:FindByName(UnitName) +if unit and unit:IsAlive()then +local group=unit:GetGroup() +callsign=group:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) +end +return callsign +end +function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) +self:T(self.lid.." _CheckWoundedGroupStatus") +local _heliName=heliname +local _woundedGroupName=woundedgroupname +self:T({Heli=_heliName,Downed=_woundedGroupName}) +local _found,_downedpilot=self:_CheckNameInDownedPilots(_woundedGroupName) +if not _found then +self:T("...not found in list!") +return +end +local _woundedGroup=_downedpilot.group +if _woundedGroup~=nil and _woundedGroup:IsAlive()then +local _heliUnit=self:_GetSARHeli(_heliName) +local _lookupKeyHeli=_heliName.."_".._woundedGroupName +if _heliUnit==nil then +self.heliVisibleMessage[_lookupKeyHeli]=nil +self.heliCloseMessage[_lookupKeyHeli]=nil +self.landedStatus[_lookupKeyHeli]=nil +self:T("...heliunit nil!") +return +end +local _heliCoord=_heliUnit:GetCoordinate() +local _leaderCoord=_woundedGroup:GetCoordinate() +local _distance=self:_GetDistance(_heliCoord,_leaderCoord) +if(self.autosmoke==true)and(_distance0 then +if self:_CheckCloseWoundedGroup(_distance,_heliUnit,_heliName,_woundedGroup,_woundedGroupName)==true then +_downedpilot.timestamp=timer.getAbsTime() +self:__Approach(-5,heliname,woundedgroupname) +end +elseif _distance>=self.approachdist_near and _distance_lastSmoke then +local _smokecolor=self.smokecolor +local _smokecoord=_woundedLeader:GetCoordinate():Translate(6,math.random(1,360)) +_smokecoord:Smoke(_smokecolor) +self.smokeMarkers[_woundedGroupName]=timer.getTime()+300 +end +return self +end +function CSAR:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName) +self:T(self.lid.." _PickupUnit") +local _heliName=_heliUnit:GetName() +local _groups=self.inTransitGroups[_heliName] +local _unitsInHelicopter=self:_PilotsOnboard(_heliName) +if not _groups then +self.inTransitGroups[_heliName]={} +_groups=self.inTransitGroups[_heliName] +end +local _maxUnits=self.AircraftType[_heliUnit:GetTypeName()] +if _maxUnits==nil then +_maxUnits=self.max_units +end +if _unitsInHelicopter+1>_maxUnits then +self:_DisplayMessageToSAR(_heliUnit,string.format("%s, %s. We\'re already crammed with %d guys! Sorry!",_pilotName,self:_GetCustomCallSign(_heliName),_unitsInHelicopter,_unitsInHelicopter),self.messageTime,false,false,true) +return self +end +local found,downedgrouptable=self:_CheckNameInDownedPilots(_woundedGroupName) +local grouptable=downedgrouptable +self.inTransitGroups[_heliName][_woundedGroupName]= +{ +originalUnit=grouptable.originalUnit, +woundedGroup=_woundedGroupName, +side=self.coalition, +desc=grouptable.desc, +player=grouptable.player, +} +_woundedGroup:Destroy(false) +self:_RemoveNameFromDownedPilots(_woundedGroupName,true) +self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s I\'m in! Get to the MASH ASAP! ",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,true,true) +self:_UpdateUnitCargoMass(_heliName) +self:__Boarded(5,_heliName,_woundedGroupName,grouptable.desc) +return self +end +function CSAR:_UpdateUnitCargoMass(_heliName) +self:T(self.lid.." _UpdateUnitCargoMass") +local calculatedMass=self:_PilotsOnboard(_heliName)*(self.PilotWeight or 80) +local Unit=UNIT:FindByName(_heliName) +if Unit then +Unit:SetUnitInternalCargo(calculatedMass) +end +return self +end +function CSAR:_OrderGroupToMoveToPoint(_leader,_destination) +self:T(self.lid.." _OrderGroupToMoveToPoint") +local group=_leader +local coordinate=_destination:GetVec2() +group:SetAIOn() +group:RouteToVec2(coordinate,5) +return self +end +function CSAR:_IsLoadingDoorOpen(unit_name) +self:T(self.lid.." _IsLoadingDoorOpen") +return UTILS.IsLoadingDoorOpen(unit_name) +end +function CSAR:_CheckCloseWoundedGroup(_distance,_heliUnit,_heliName,_woundedGroup,_woundedGroupName) +self:T(self.lid.." _CheckCloseWoundedGroup") +local _woundedLeader=_woundedGroup +local _lookupKeyHeli=_heliUnit:GetName().."_".._woundedGroupName +local _found,_pilotable=self:_CheckNameInDownedPilots(_woundedGroupName) +local _pilotName=_pilotable.desc +local _reset=true +if(_distance<500)then +self:T(self.lid.."[Pickup Debug] Helo closer than 500m: ".._lookupKeyHeli) +if self.heliCloseMessage[_lookupKeyHeli]==nil then +if self.autosmoke==true then +self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. You\'re close now! Land or hover at the smoke.",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true) +else +self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true) +end +self.heliCloseMessage[_lookupKeyHeli]=true +end +self:T(self.lid.."[Pickup Debug] Checking landed vs Hover for ".._lookupKeyHeli) +if not _heliUnit:InAir()then +self:T(self.lid.."[Pickup Debug] Helo landed: ".._lookupKeyHeli) +if self.pilotRuntoExtractPoint==true then +if(_distance0 then +self:T(self.lid.."[Pickup Debug] Helo hovering not long enough ".._lookupKeyHeli) +self:_DisplayMessageToSAR(_heliUnit,"Hovering above ".._pilotName..". \n\nHold hover for ".._time.." seconds to winch them up. \n\nIf the countdown stops you\'re too far away!",self.messageTime,true) +else +self:T(self.lid.."[Pickup Debug] Helo hovering long enough - door check ".._lookupKeyHeli) +if self.pilotmustopendoors and(self:_IsLoadingDoorOpen(_heliName)==false)then +self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me in!",self.messageTime,true,true) +self:T(self.lid.."[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) +return false +else +self.hoverStatus[_lookupKeyHeli]=nil +self:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName) +self:T(self.lid.."[Pickup Debug] Pilot picked up ".._lookupKeyHeli) +return true +end +end +_reset=false +else +self:T(self.lid.."[Pickup Debug] Helo hovering too high ".._lookupKeyHeli) +self:_DisplayMessageToSAR(_heliUnit,"Too high to winch ".._pilotName.." \nReduce height and hover for 10 seconds!",self.messageTime,true,true) +self:T(self.lid.."[Pickup Debug] Hovering too high, try again next loop ".._lookupKeyHeli) +return false +end +end +end +end +end +if _reset then +self.hoverStatus[_lookupKeyHeli]=nil +end +if _distance<500 then +return true +else +return false +end +end +function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule,IsHeloBase) +self:T(self.lid.." _ScheduledSARFlight") +self:T({heliname,groupname}) +local _heliUnit=self:_GetSARHeli(heliname) +local _woundedGroupName=groupname +if(_heliUnit==nil)then +self.inTransitGroups[heliname]=nil +return +end +if self.inTransitGroups[heliname]==nil or self.inTransitGroups[heliname][_woundedGroupName]==nil then +return +end +local _dist=self:_GetClosestMASH(_heliUnit) +if _dist==-1 then +self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance can not be determined!") +return +end +self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) +if(_distsmokedist then smokedist=self.approachdist_far end +if _closest~=nil and _closest.pilot~=nil and _closest.distance>0 and _closest.distance0 and _closest.distance0 and _closest.distance12 then clock=clock-12 end +end +return clock +end +function CSAR:_AddBeaconToGroup(_group,_freq,BeaconName) +self:T(self.lid.." _AddBeaconToGroup") +if self.CreateRadioBeacons==false then return end +local _group=_group +if _group==nil then +for _i,_current in ipairs(self.UsedVHFFrequencies)do +if _current==_freq then +table.insert(self.FreeVHFFrequencies,_freq) +table.remove(self.UsedVHFFrequencies,_i) +end +end +return +end +if _group:IsAlive()then +local _radioUnit=_group:GetUnit(1) +if _radioUnit then +local name=_radioUnit:GetName() +local Frequency=_freq +local Sound="l10n/DEFAULT/"..self.radioSound +local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0} +self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) +trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName) +end +end +return self +end +function CSAR:_RefreshRadioBeacons() +self:T(self.lid.." _RefreshRadioBeacons") +if self.CreateRadioBeacons==false then return end +if self:_CountActiveDownedPilots()>0 then +local PilotTable=self.downedPilots +for _,_pilot in pairs(PilotTable)do +self:T({_pilot.name}) +local pilot=_pilot +local group=pilot.group +local frequency=pilot.frequency or 0 +local bname=pilot.BeaconName or pilot.name..math.random(1,100000) +if group and group:IsAlive()and frequency>0 then +else +if frequency>0 then +trigger.action.stopRadioTransmission(bname) +end +end +end +end +return self +end +function CSAR:_CountActiveDownedPilots() +self:T(self.lid.." _CountActiveDownedPilots") +local PilotsInFieldN=0 +for _,_unitName in pairs(self.downedPilots)do +self:T({_unitName.desc}) +if _unitName.alive==true then +PilotsInFieldN=PilotsInFieldN+1 +end +end +return PilotsInFieldN +end +function CSAR:_ReachedPilotLimit() +self:T(self.lid.." _ReachedPilotLimit") +local limit=self.maxdownedpilots +local islimited=self.limitmaxdownedpilots +local count=self:_CountActiveDownedPilots() +if islimited and(count>=limit)then +if self.useFIFOLimitReplacement then +local oldIndex=-1 +local oldDownedPilot=nil +for _index,_downedpilot in pairs(self.downedPilots)do +oldIndex=_index +oldDownedPilot=_downedpilot +break +end +if oldDownedPilot then +oldDownedPilot.group:Destroy(false) +oldDownedPilot.alive=false +self:_CheckDownedPilotTable() +return false +end +end +return true +else +return false +end +end +function CSAR:SetOwnSetPilotGroups(Set) +self.UserSetGroup=Set +return self +end +function CSAR:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:I(self.lid.."Started ("..self.version..")") +self:HandleEvent(EVENTS.Takeoff,self._EventHandler) +self:HandleEvent(EVENTS.Land,self._EventHandler) +self:HandleEvent(EVENTS.Ejection,self._EventHandler) +self:HandleEvent(EVENTS.LandingAfterEjection,self._EventHandler) +self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler) +self:HandleEvent(EVENTS.PilotDead,self._EventHandler) +if self.UserSetGroup then +self.allheligroupset=self.UserSetGroup +elseif self.allowbronco then +local prefixes=self.csarPrefix or{} +self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart() +elseif self.useprefix then +local prefixes=self.csarPrefix or{} +self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() +else +self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() +end +self.mash=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() +self.staticmashes=SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() +self.zonemashes=SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterStart() +if not self.coordinate then +local csarhq=self.mash:GetRandom() +if csarhq then +self.coordinate=csarhq:GetCoordinate() +end +end +if self.wetfeettemplate then +self.usewetfeet=true +end +if self.useSRS then +local path=self.SRSPath +local modulation=self.SRSModulation +local channel=self.SRSchannel +self.msrs=MSRS:New(path,channel,modulation) +self.msrs:SetPort(self.SRSport) +self.msrs:SetLabel("CSAR") +self.msrs:SetCulture(self.SRSCulture) +self.msrs:SetCoalition(self.coalition) +self.msrs:SetVoice(self.SRSVoice) +self.msrs:SetGender(self.SRSGender) +if self.SRSGPathToCredentials then +self.msrs:SetProviderOptionsGoogle(self.SRSGPathToCredentials,self.SRSGPathToCredentials) +self.msrs:SetProvider(MSRS.Provider.GOOGLE) +end +self.msrs:SetVolume(self.SRSVolume) +self.msrs:SetLabel("CSAR") +self.SRSQueue=MSRSQUEUE:New("CSAR") +end +self:__Status(-10) +if self.enableLoadSave then +local interval=self.saveinterval +local filename=self.filename +local filepath=self.filepath +self:__Save(interval,filepath,filename) +end +return self +end +function CSAR:_CheckDownedPilotTable() +local pilots=self.downedPilots +local npilots={} +for _ind,_entry in pairs(pilots)do +local _group=_entry.group +if _group:IsAlive()then +npilots[_ind]=_entry +else +if _entry.alive then +self:__KIA(1,_entry.desc) +end +end +end +self.downedPilots=npilots +return self +end +function CSAR:onbeforeStatus(From,Event,To) +self:T({From,Event,To}) +self:_AddMedevacMenuItem() +if not self.BeaconTimer or(self.BeaconTimer and not self.BeaconTimer:IsRunning())then +self.BeaconTimer=TIMER:New(self._RefreshRadioBeacons,self) +self.BeaconTimer:Start(2,self.beaconRefresher) +end +self:_CheckDownedPilotTable() +for _,_sar in pairs(self.csarUnits)do +local PilotTable=self.downedPilots +for _,_entry in pairs(PilotTable)do +if _entry.alive then +local entry=_entry +local name=entry.name +local timestamp=entry.timestamp or 0 +local now=timer.getAbsTime() +if now-timestamp>17 then +self:_CheckWoundedGroupStatus(_sar,name) +end +end +end +end +return self +end +function CSAR:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local NumberOfSARPilots=0 +for _,_unitName in pairs(self.csarUnits)do +NumberOfSARPilots=NumberOfSARPilots+1 +end +local PilotsInFieldN=self:_CountActiveDownedPilots() +local PilotsBoarded=0 +for _,_unitName in pairs(self.inTransitGroups)do +for _,_units in pairs(_unitName)do +PilotsBoarded=PilotsBoarded+1 +end +end +if self.verbose>0 then +local text=string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", +self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots) +self:T(text) +if self.verbose<2 then +self:I(text) +elseif self.verbose>1 then +self:I(text) +local m=MESSAGE:New(text,"10","Status",true):ToCoalition(self.coalition) +end +end +self:__Status(-20) +return self +end +function CSAR:onafterStop(From,Event,To) +self:T({From,Event,To}) +self:UnHandleEvent(EVENTS.Takeoff) +self:UnHandleEvent(EVENTS.Land) +self:UnHandleEvent(EVENTS.Ejection) +self:UnHandleEvent(EVENTS.LandingAfterEjection) +self:UnHandleEvent(EVENTS.PlayerEnterUnit) +self:UnHandleEvent(EVENTS.PlayerEnterAircraft) +self:UnHandleEvent(EVENTS.PilotDead) +self:T(self.lid.."Stopped.") +return self +end +function CSAR:onbeforeApproach(From,Event,To,Heliname,Woundedgroupname) +self:T({From,Event,To,Heliname,Woundedgroupname}) +self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) +return self +end +function CSAR:onbeforeBoarded(From,Event,To,Heliname,Woundedgroupname) +self:T({From,Event,To,Heliname,Woundedgroupname}) +self:_ScheduledSARFlight(Heliname,Woundedgroupname) +local Unit=UNIT:FindByName(Heliname) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +local dropcoord=Unit:GetCoordinate()or COORDINATE:New(0,0,0) +local dropvec2=dropcoord:GetVec2() +self.PlayerTaskQueue:ForEach( +function(Task) +local task=Task +local subtype=task:GetSubType() +if Event==subtype and not task:IsDone()then +local targetzone=task.Target:GetObject() +if(targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)) +or(string.find(task.CSARPilotName,Woundedgroupname))then +if task.Clients:HasUniqueID(playername)then +task:__Success(-1) +end +end +end +end +) +end +return self +end +function CSAR:onbeforeReturning(From,Event,To,Heliname,Woundedgroupname,IsAirPort) +self:T({From,Event,To,Heliname,Woundedgroupname}) +return self +end +function CSAR:onbeforeRescued(From,Event,To,HeliUnit,HeliName,PilotsSaved) +self:T({From,Event,To,HeliName,HeliUnit}) +self.rescues=self.rescues+1 +self.rescuedpilots=self.rescuedpilots+PilotsSaved +local Unit=HeliUnit or UNIT:FindByName(HeliName) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +self.PlayerTaskQueue:ForEach( +function(Task) +local task=Task +local subtype=task:GetSubType() +if Event==subtype and not task:IsDone()then +if task.Clients:HasUniqueID(playername)then +task:__Success(-1) +end +end +end +) +end +return self +end +function CSAR:onbeforePilotDown(From,Event,To,Group,Frequency,Leadername,CoordinatesText,Playername) +self:T({From,Event,To,Group,Frequency,Leadername,CoordinatesText,tostring(Playername)}) +return self +end +function CSAR:onbeforeLanded(From,Event,To,HeliName,Airbase) +self:T({From,Event,To,HeliName,Airbase}) +return self +end +function CSAR:onbeforeSave(From,Event,To,path,filename) +self:T({From,Event,To,path,filename}) +if not self.enableLoadSave then +return self +end +if not io then +self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") +return false +end +if path==nil and not lfs then +self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +return true +end +function CSAR:onafterSave(From,Event,To,path,filename) +self:T({From,Event,To,path,filename}) +if not self.enableLoadSave then +return self +end +local function _savefile(filename,data) +local f=assert(io.open(filename,"wb")) +f:write(data) +f:close() +end +if lfs then +path=self.filepath or lfs.writedir() +end +filename=filename or self.filename +if path~=nil then +filename=path.."\\"..filename +end +local pilots=self.downedPilots +local data="playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n" +local n=0 +for _,_grp in pairs(pilots)do +local DownedPilot=_grp +if DownedPilot and DownedPilot.alive then +local playerName=DownedPilot.player +local group=DownedPilot.group +local coalition=group:GetCoalition() +local country=group:GetCountry() +local description=DownedPilot.desc +local typeName=DownedPilot.typename +local freq=DownedPilot.frequency +local location=group:GetVec3() +local unitName=DownedPilot.originalUnit +local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq) +self:I(self.lid.."Saving to CSAR File: "..txt) +data=data..txt +end +end +_savefile(filename,data) +if self.enableLoadSave then +local interval=self.saveinterval +local filename=self.filename +local filepath=self.filepath +self:__Save(interval,filepath,filename) +end +return self +end +function CSAR:onbeforeLoad(From,Event,To,path,filename) +self:T({From,Event,To,path,filename}) +if not self.enableLoadSave then +return self +end +local function _fileexists(name) +local f=io.open(name,"r") +if f~=nil then +io.close(f) +return true +else +return false +end +end +filename=filename or self.filename +path=path or self.filepath +if not io then +self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") +return false +end +if path==nil and not lfs then +self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +if lfs then +path=path or lfs.writedir() +end +if path~=nil then +filename=path.."\\"..filename +end +local exists=_fileexists(filename) +if exists then +return true +else +self:E(self.lid..string.format("WARNING: State file %s might not exist.",filename)) +return false +end +end +function CSAR:onafterLoad(From,Event,To,path,filename) +self:T({From,Event,To,path,filename}) +if not self.enableLoadSave then +return self +end +local function _loadfile(filename) +local f=assert(io.open(filename,"rb")) +local data=f:read("*all") +f:close() +return data +end +filename=filename or self.filename +path=path or self.filepath +if lfs then +path=path or lfs.writedir() +end +if path~=nil then +filename=path.."\\"..filename +end +local text=string.format("Loading CSAR state from file %s",filename) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:I(self.lid..text) +local file=assert(io.open(filename,"rb")) +local loadeddata={} +for line in file:lines()do +loadeddata[#loadeddata+1]=line +end +file:close() +table.remove(loadeddata,1) +for _id,_entry in pairs(loadeddata)do +local dataset=UTILS.Split(_entry,",") +local playerName=dataset[1] +local vec3={} +vec3.x=tonumber(dataset[2]) +vec3.y=tonumber(dataset[3]) +vec3.z=tonumber(dataset[4]) +local point=COORDINATE:NewFromVec3(vec3) +local coalition=tonumber(dataset[5]) +local country=tonumber(dataset[6]) +local description=dataset[7] +local typeName=dataset[8] +local unitName=dataset[9] +local freq=tonumber(dataset[10]) +self:_AddCsar(coalition,country,point,typeName,unitName,playerName,freq,false,description,nil) +end +return self +end +AIRWING={ +ClassName="AIRWING", +verbose=0, +lid=nil, +menu=nil, +payloads={}, +payloadcounter=0, +pointsCAP={}, +pointsTANKER={}, +pointsAWACS={}, +pointsRecon={}, +markpoints=false, +capOptionPatrolRaceTrack=false, +capFormation=nil, +capOptionVaryStartTime=nil, +capOptionVaryEndTime=nil, +} +AIRWING.version="0.9.7" +function AIRWING:New(warehousename,airwingname) +local self=BASE:Inherit(self,LEGION:New(warehousename,airwingname)) +if not self then +BASE:E(string.format("ERROR: Could not find warehouse %s!",warehousename)) +return nil +end +self.lid=string.format("AIRWING %s | ",self.alias) +self.nflightsCAP=0 +self.nflightsAWACS=0 +self.nflightsRecon=0 +self.nflightsTANKERboom=0 +self.nflightsTANKERprobe=0 +self.nflightsRecoveryTanker=0 +self.nflightsRescueHelo=0 +self.markpoints=false +self:AddTransition("*","FlightOnMission","*") +return self +end +function AIRWING:AddSquadron(Squadron) +table.insert(self.cohorts,Squadron) +self:AddAssetToSquadron(Squadron,Squadron.Ngroups) +if Squadron.attribute==GROUP.Attribute.AIR_AWACS then +self:NewPayload(Squadron.templategroup,-1,AUFTRAG.Type.AWACS) +elseif Squadron.attribute==GROUP.Attribute.AIR_TANKER then +self:NewPayload(Squadron.templategroup,-1,AUFTRAG.Type.TANKER) +end +self:NewPayload(Squadron.templategroup,-1,AUFTRAG.Type.RELOCATECOHORT,0) +Squadron:SetAirwing(self) +if Squadron:IsStopped()then +Squadron:Start() +end +return self +end +function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) +Performance=Performance or 50 +if type(Unit)=="string"then +local name=Unit +Unit=UNIT:FindByName(name) +if not Unit then +Unit=GROUP:FindByName(name) +end +end +if Unit then +if Unit:IsInstanceOf("GROUP")then +Unit=Unit:GetUnit(1) +end +if MissionTypes and type(MissionTypes)~="table"then +MissionTypes={MissionTypes} +end +local payload={} +payload.uid=self.payloadcounter +payload.unitname=Unit:GetName() +payload.aircrafttype=Unit:GetTypeName() +payload.pylons=Unit:GetTemplatePayload() +self:SetPayloadAmount(payload,Npayloads) +payload.capabilities={} +for _,missiontype in pairs(MissionTypes)do +local capability={} +capability.MissionType=missiontype +capability.Performance=Performance +table.insert(payload.capabilities,capability) +end +if not AUFTRAG.CheckMissionType(AUFTRAG.Type.ORBIT,MissionTypes)then +local capability={} +capability.MissionType=AUFTRAG.Type.ORBIT +capability.Performance=50 +table.insert(payload.capabilities,capability) +end +if not AUFTRAG.CheckMissionType(AUFTRAG.Type.RELOCATECOHORT,MissionTypes)then +local capability={} +capability.MissionType=AUFTRAG.Type.RELOCATECOHORT +capability.Performance=50 +table.insert(payload.capabilities,capability) +end +self:T(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s", +payload.unitname,payload.aircrafttype,payload.uid,payload.navail,tostring(payload.unlimited),Performance,table.concat(MissionTypes,", "))) +table.insert(self.payloads,payload) +self.payloadcounter=self.payloadcounter+1 +return payload +end +self:E(self.lid.."ERROR: No UNIT found to create PAYLOAD!") +return nil +end +function AIRWING:SetPayloadAmount(Payload,Navailable) +Navailable=Navailable or 99 +if Payload then +Payload.unlimited=Navailable<0 +if Payload.unlimited then +Payload.navail=1 +else +Payload.navail=Navailable +end +end +return self +end +function AIRWING:IncreasePayloadAmount(Payload,N) +N=N or 1 +if Payload and Payload.navail>=0 then +Payload.navail=Payload.navail+N +Payload.navail=math.max(Payload.navail,0) +end +return self +end +function AIRWING:GetPayloadAmount(Payload) +return Payload.navail +end +function AIRWING:GetPayloadCapabilities(Payload) +return Payload.capabilities +end +function AIRWING:AddPayloadCapability(Payload,MissionTypes,Performance) +if MissionTypes and type(MissionTypes)~="table"then +MissionTypes={MissionTypes} +end +Payload.capabilities=Payload.capabilities or{} +for _,missiontype in pairs(MissionTypes)do +local capability={} +capability.MissionType=missiontype +capability.Performance=Performance +table.insert(Payload.capabilities,capability) +end +return self +end +function AIRWING:FetchPayloadFromStock(UnitType,MissionType,Payloads) +if not self.payloads or#self.payloads==0 then +self:T(self.lid.."WARNING: No payloads in stock!") +return nil +end +if self.verbose>=4 then +self:I(self.lid..string.format("Looking for payload for unit type=%s and mission type=%s",UnitType,MissionType)) +for i,_payload in pairs(self.payloads)do +local payload=_payload +local performance=self:GetPayloadPeformance(payload,MissionType) +self:I(self.lid..string.format("[%d] Payload type=%s navail=%d unlimited=%s",i,payload.aircrafttype,payload.navail,tostring(payload.unlimited))) +end +end +local function sortpayloads(a,b) +local pA=a +local pB=b +if a and b then +local performanceA=self:GetPayloadPeformance(a,MissionType) +local performanceB=self:GetPayloadPeformance(b,MissionType) +return(performanceA>performanceB)or(performanceA==performanceB and a.unlimited==true and b.unlimited~=true)or(performanceA==performanceB and a.unlimited==true and b.unlimited==true and a.navail>b.navail) +elseif not a then +self:I(self.lid..string.format("FF ERROR in sortpayloads: a is nil")) +return false +elseif not b then +self:I(self.lid..string.format("FF ERROR in sortpayloads: b is nil")) +return true +else +self:I(self.lid..string.format("FF ERROR in sortpayloads: a and b are nil")) +return false +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 +return nil +end +return false +end +local payloads={} +for _,_payload in pairs(self.payloads)do +local payload=_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 +if self.verbose>=4 then +self:I(self.lid..string.format("Sorted payloads for mission type %s and aircraft type=%s:",MissionType,UnitType)) +for _,_payload in ipairs(self.payloads)do +local payload=_payload +if payload.aircrafttype==UnitType and AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities)then +local performace=self:GetPayloadPeformance(payload,MissionType) +self:I(self.lid..string.format("- %s payload for %s: avail=%d performace=%d",MissionType,payload.aircrafttype,payload.navail,performace)) +end +end +end +if#payloads==0 then +self:T(self.lid..string.format("WARNING: Could not find a payload for airframe %s mission type %s!",UnitType,MissionType)) +return nil +elseif#payloads==1 then +local payload=payloads[1] +if not payload.unlimited then +payload.navail=payload.navail-1 +end +return payload +else +table.sort(payloads,sortpayloads) +local payload=payloads[1] +if not payload.unlimited then +payload.navail=payload.navail-1 +end +return payload +end +end +function AIRWING:ReturnPayloadFromAsset(asset) +local payload=asset.payload +if payload then +if not payload.unlimited then +payload.navail=payload.navail+1 +end +asset.payload=nil +else +self:E(self.lid.."ERROR: asset had no payload attached!") +end +end +function AIRWING:AddAssetToSquadron(Squadron,Nassets) +if Squadron then +local Group=GROUP:FindByName(Squadron.templatename) +if Group then +local text=string.format("Adding asset %s to squadron %s",Group:GetName(),Squadron.name) +self:T(self.lid..text) +self:AddAsset(Group,Nassets,nil,nil,nil,nil,Squadron.skill,Squadron.livery,Squadron.name) +else +self:E(self.lid.."ERROR: Group does not exist!") +end +else +self:E(self.lid.."ERROR: Squadron does not exit!") +end +return self +end +function AIRWING:GetSquadron(SquadronName) +local squad=self:_GetCohort(SquadronName) +return squad +end +function AIRWING:GetSquadronOfAsset(Asset) +return self:GetSquadron(Asset.squadname) +end +function AIRWING:RemoveAssetFromSquadron(Asset) +local squad=self:GetSquadronOfAsset(Asset) +if squad then +squad:DelAsset(Asset) +end +end +function AIRWING:SetNumberCAP(n) +self.nflightsCAP=n or 1 +return self +end +function AIRWING:SetCAPFormation(Formation) +self.capFormation=Formation +return self +end +function AIRWING:SetCapCloseRaceTrack(OnOff) +self.capOptionPatrolRaceTrack=OnOff +return self +end +function AIRWING:SetCapStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function AIRWING:SetNumberTankerBoom(Nboom) +self.nflightsTANKERboom=Nboom or 1 +return self +end +function AIRWING:ShowPatrolPointMarkers(onoff) +if onoff then +self.markpoints=true +else +self.markpoints=false +end +return self +end +function AIRWING:SetNumberTankerProbe(Nprobe) +self.nflightsTANKERprobe=Nprobe or 1 +return self +end +function AIRWING:SetNumberAWACS(n) +self.nflightsAWACS=n or 1 +return self +end +function AIRWING:SetNumberRecon(n) +self.nflightsRecon=n or 1 +return self +end +function AIRWING:SetNumberRescuehelo(n) +self.nflightsRescueHelo=n or 1 +return self +end +function AIRWING:_PatrolPointMarkerText(point) +local text=string.format("%s Occupied=%d, \nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", +point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed) +return text +end +function AIRWING:UpdatePatrolPointMarker(point) +if self and self.markpoints then +local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", +point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed) +if point.IsZonePoint and point.IsZonePoint==true and point.patrolzone then +local Coordinate=point.patrolzone:GetCoordinate() +point.marker:UpdateCoordinate(Coordinate) +point.marker:UpdateText(text,1.5) +else +point.marker:UpdateText(text,1) +end +end +end +function AIRWING:NewPatrolPoint(Type,Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem) +local patrolpoint={} +patrolpoint.type=Type or"Unknown" +patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10,15)),math.random(360)) +if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE")then +patrolpoint.IsZonePoint=true +patrolpoint.patrolzone=Coordinate +patrolpoint.coord=patrolpoint.patrolzone:GetCoordinate() +else +patrolpoint.IsZonePoint=false +end +patrolpoint.heading=Heading or math.random(360) +patrolpoint.leg=LegLength or 15 +patrolpoint.altitude=Altitude or math.random(10,20)*1000 +patrolpoint.speed=Speed or 350 +patrolpoint.noccupied=0 +patrolpoint.refuelsystem=RefuelSystem +if self.markpoints then +patrolpoint.marker=MARKER:New(Coordinate,"New Patrol Point"):ToAll() +self:UpdatePatrolPointMarker(patrolpoint) +end +return patrolpoint +end +function AIRWING:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) +local patrolpoint=self:NewPatrolPoint("CAP",Coordinate,Altitude,Speed,Heading,LegLength) +table.insert(self.pointsCAP,patrolpoint) +return self +end +function AIRWING:AddPatrolPointRecon(Coordinate,Altitude,Speed,Heading,LegLength) +local patrolpoint=self:NewPatrolPoint("RECON",Coordinate,Altitude,Speed,Heading,LegLength) +table.insert(self.pointsRecon,patrolpoint) +return self +end +function AIRWING:AddPatrolPointTANKER(Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem) +local patrolpoint=self:NewPatrolPoint("Tanker",Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem) +table.insert(self.pointsTANKER,patrolpoint) +return self +end +function AIRWING:AddPatrolPointAWACS(Coordinate,Altitude,Speed,Heading,LegLength) +local patrolpoint=self:NewPatrolPoint("AWACS",Coordinate,Altitude,Speed,Heading,LegLength) +table.insert(self.pointsAWACS,patrolpoint) +return self +end +function AIRWING:SetAirboss(airboss) +self.airboss=airboss +return self +end +function AIRWING:SetTakeoffType(TakeoffType) +TakeoffType=TakeoffType or"Cold" +if TakeoffType:lower()=="hot"then +self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot +elseif TakeoffType:lower()=="cold"then +self.takeoffType=COORDINATE.WaypointType.TakeOffParking +elseif TakeoffType:lower()=="air"then +self.takeoffType=COORDINATE.WaypointType.TurningPoint +else +self.takeoffType=COORDINATE.WaypointType.TakeOffParking +end +return self +end +function AIRWING:SetTakeoffCold() +self:SetTakeoffType("Cold") +return self +end +function AIRWING:SetTakeoffHot() +self:SetTakeoffType("Hot") +return self +end +function AIRWING:SetTakeoffAir() +self:SetTakeoffType("Air") +return self +end +function AIRWING:SetLandingStraightIn() +self.OptionLandingStraightIn=true +return self +end +function AIRWING:SetLandingForcePair() +self.OptionLandingForcePair=true +return self +end +function AIRWING:SetLandingRestrictPair() +self.OptionLandingRestrictPair=true +return self +end +function AIRWING:SetLandingOverheadBreak() +self.OptionLandingOverheadBreak=true +return self +end +function AIRWING:SetOptionPreferVerticalLanding() +self.OptionPreferVerticalLanding=true +return self +end +function AIRWING:SetDespawnAfterLanding(Switch) +if Switch then +self.despawnAfterLanding=Switch +else +self.despawnAfterLanding=true +end +return self +end +function AIRWING:SetDespawnAfterHolding(Switch) +if Switch then +self.despawnAfterHolding=Switch +else +self.despawnAfterHolding=true +end +return self +end +function AIRWING:onafterStart(From,Event,To) +self:GetParent(self,AIRWING).onafterStart(self,From,Event,To) +self:I(self.lid..string.format("Starting AIRWING v%s",AIRWING.version)) +end +function AIRWING:onafterStatus(From,Event,To) +self:GetParent(self).onafterStatus(self,From,Event,To) +local fsmstate=self:GetState() +self:CheckCAP() +self:CheckTANKER() +self:CheckAWACS() +self:CheckRescuhelo() +self:CheckRECON() +self:_TacticalOverview() +self:CheckTransportQueue() +self:CheckMissionQueue() +if self.verbose>=1 then +local Nmissions=self:CountMissionsInQueue() +local Npayloads=self:CountPayloadsInStock(AUFTRAG.Type) +local Npq,Np,Nq=self:CountAssetsOnMission() +local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)",self:CountAssets(),Npq,Np,Nq) +local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s",fsmstate,Nmissions,Npayloads,#self.payloads,#self.cohorts,assets) +self:I(self.lid..text) +end +if self.verbose>=2 then +local text=string.format("Missions Total=%d:",#self.missionqueue) +for i,_mission in pairs(self.missionqueue)do +local mission=_mission +local prio=string.format("%d/%s",mission.prio,tostring(mission.importance));if mission.urgent then prio=prio.." (!)"end +local assets=string.format("%d/%d",mission:CountOpsGroups(),mission:GetNumberOfRequiredAssets()) +local target=string.format("%d/%d Damage=%.1f",mission:CountMissionTargets(),mission:GetTargetInitialNumber(),mission:GetTargetDamage()) +local mystatus=mission:GetLegionStatus(self) +text=text..string.format("\n[%d] %s %s: Status=%s [%s], Prio=%s, Assets=%s, Targets=%s",i,mission.name,mission.type,mystatus,mission.status,prio,assets,target) +end +self:I(self.lid..text) +end +if self.verbose>=3 then +local text="Squadrons:" +for i,_squadron in pairs(self.cohorts)do +local squadron=_squadron +local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName)or"N/A" +local modex=squadron.modex and squadron.modex or-1 +local skill=squadron.skill and tostring(squadron.skill)or"N/A" +text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s",squadron.name,squadron:GetState(),squadron.aircrafttype,squadron:CountAssets(true),#squadron.assets,callsign,modex,skill) +end +self:I(self.lid..text) +end +end +function AIRWING:_GetPatrolData(PatrolPoints,RefuelSystem) +local function sort(a,b) +return a.noccupied0 then +table.sort(PatrolPoints,sort) +for _,_patrolpoint in pairs(PatrolPoints)do +local patrolpoint=_patrolpoint +if patrolpoint.IsZonePoint and patrolpoint.IsZonePoint==true and patrolpoint.patrolzone then +patrolpoint.coord=patrolpoint.patrolzone:GetCoordinate() +end +if(RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem)or RefuelSystem==nil or patrolpoint.refuelsystem==nil then +return patrolpoint +end +end +end +return self:NewPatrolPoint() +end +function AIRWING:CheckCAP() +local Ncap=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission:IsNotOver()and(mission.type==AUFTRAG.Type.GCICAP or mission.type==AUFTRAG.Type.PATROLRACETRACK)and mission.patroldata then +Ncap=Ncap+1 +end +end +for i=1,self.nflightsCAP-Ncap do +local patrol=self:_GetPatrolData(self.pointsCAP) +local altitude=patrol.altitude+1000*patrol.noccupied +local missionCAP=nil +if self.capOptionPatrolRaceTrack then +missionCAP=AUFTRAG:NewPATROL_RACETRACK(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,self.capFormation) +else +missionCAP=AUFTRAG:NewGCICAP(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg) +end +if self.capOptionVaryStartTime then +local ClockStart=math.random(self.capOptionVaryStartTime,self.capOptionVaryEndTime) +missionCAP:SetTime(ClockStart) +end +missionCAP.patroldata=patrol +patrol.noccupied=patrol.noccupied+1 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end +self:AddMission(missionCAP) +end +return self +end +function AIRWING:CheckRECON() +local Ncap=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission:IsNotOver()and mission.type==AUFTRAG.Type.RECON and mission.patroldata then +Ncap=Ncap+1 +end +end +for i=1,self.nflightsRecon-Ncap do +local patrol=self:_GetPatrolData(self.pointsRecon) +local altitude=patrol.altitude +local ZoneSet=SET_ZONE:New() +local Zone=ZONE_RADIUS:New(self.alias.." Recon "..math.random(1,10000),patrol.coord:GetVec2(),UTILS.NMToMeters(patrol.leg/2)) +ZoneSet:AddZone(Zone) +if self.Debug then +Zone:DrawZone(self.coalition,{0,0,1},Alpha,FillColor,FillAlpha,2,true) +end +local missionRECON=AUFTRAG:NewRECON(ZoneSet,patrol.speed,patrol.altitude,true) +missionRECON.patroldata=patrol +missionRECON.categories={AUFTRAG.Category.AIRCRAFT} +patrol.noccupied=patrol.noccupied+1 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end +self:AddMission(missionRECON) +end +return self +end +function AIRWING:CheckTANKER() +local Nboom=0 +local Nprob=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission:IsNotOver()and mission.type==AUFTRAG.Type.TANKER and mission.patroldata then +if mission.refuelSystem==Unit.RefuelingSystem.BOOM_AND_RECEPTACLE then +Nboom=Nboom+1 +elseif mission.refuelSystem==Unit.RefuelingSystem.PROBE_AND_DROGUE then +Nprob=Nprob+1 +end +end +end +for i=1,self.nflightsTANKERboom-Nboom do +local patrol=self:_GetPatrolData(self.pointsTANKER) +local altitude=patrol.altitude+1000*patrol.noccupied +local mission=AUFTRAG:NewTANKER(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,Unit.RefuelingSystem.BOOM_AND_RECEPTACLE) +mission.patroldata=patrol +patrol.noccupied=patrol.noccupied+1 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end +self:AddMission(mission) +end +for i=1,self.nflightsTANKERprobe-Nprob do +local patrol=self:_GetPatrolData(self.pointsTANKER) +local altitude=patrol.altitude+1000*patrol.noccupied +local mission=AUFTRAG:NewTANKER(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,Unit.RefuelingSystem.PROBE_AND_DROGUE) +mission.patroldata=patrol +patrol.noccupied=patrol.noccupied+1 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end +self:AddMission(mission) +end +return self +end +function AIRWING:CheckAWACS() +local N=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission:IsNotOver()and mission.type==AUFTRAG.Type.AWACS and mission.patroldata then +N=N+1 +end +end +for i=1,self.nflightsAWACS-N do +local patrol=self:_GetPatrolData(self.pointsAWACS) +local altitude=patrol.altitude+1000*patrol.noccupied +local mission=AUFTRAG:NewAWACS(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg) +mission.patroldata=patrol +patrol.noccupied=patrol.noccupied+1 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end +self:AddMission(mission) +end +return self +end +function AIRWING:CheckRescuhelo() +local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO}) +if self.airbase then +local name=self.airbase:GetName() +local carrier=UNIT:FindByName(name) +for i=1,self.nflightsRescueHelo-N do +local mission=AUFTRAG:NewRESCUEHELO(carrier) +self:AddMission(mission) +end +end +return self +end +function AIRWING:GetTankerForFlight(flightgroup) +local tankers=self:GetAssetsOnMission(AUFTRAG.Type.TANKER) +if#tankers>0 then +local tankeropt={} +for _,_tanker in pairs(tankers)do +local tanker=_tanker +if flightgroup.refueltype and flightgroup.refueltype==tanker.flightgroup.tankertype then +local tankercoord=tanker.flightgroup.group:GetCoordinate() +local assetcoord=flightgroup.group:GetCoordinate() +local dist=assetcoord:Get2DDistance(tankercoord) +if dist>5 then +table.insert(tankeropt,{tanker=tanker,dist=dist}) +end +end +end +table.sort(tankeropt,function(a,b)return a.dist0 then +return tankeropt[1].tanker +else +return nil +end +end +return nil +end +function AIRWING:SetUsingOpsAwacs(ConnectecdAwacs) +self:I(self.lid.."Added AWACS Object: "..ConnectecdAwacs:GetName()or"unknown") +self.UseConnectedOpsAwacs=true +self.ConnectedOpsAwacs=ConnectecdAwacs +return self +end +function AIRWING:RemoveUsingOpsAwacs() +self:I(self.lid.."Reomve AWACS Object: "..self.ConnectedOpsAwacs:GetName()or"unknown") +self.UseConnectedOpsAwacs=false +return self +end +function AIRWING:onafterFlightOnMission(From,Event,To,FlightGroup,Mission) +self:T(self.lid..string.format("Group %s on %s mission %s",FlightGroup:GetName(),Mission:GetType(),Mission:GetName())) +if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then +self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission) +end +if self.OptionLandingForcePair then +FlightGroup:SetOptionLandingForcePair() +elseif self.OptionLandingOverheadBreak then +FlightGroup:SetOptionLandingOverheadBreak() +elseif self.OptionLandingRestrictPair then +FlightGroup:SetOptionLandingRestrictPair() +elseif self.OptionLandingStraightIn then +FlightGroup:SetOptionLandingStraightIn() +end +if self.OptionPreferVerticalLanding then +FlightGroup:SetOptionPreferVertical() +end +end +function AIRWING:CountPayloadsInStock(MissionTypes,UnitTypes,Payloads) +if MissionTypes then +if type(MissionTypes)=="string"then +MissionTypes={MissionTypes} +end +end +if UnitTypes then +if type(UnitTypes)=="string"then +UnitTypes={UnitTypes} +end +end +local function _checkUnitTypes(payload) +if UnitTypes then +for _,unittype in pairs(UnitTypes)do +if unittype==payload.aircrafttype then +return true +end +end +else +return true +end +return false +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 +return nil +end +return false +end +local n=0 +for _,_payload in pairs(self.payloads)do +local payload=_payload +for _,MissionType in pairs(MissionTypes)do +local specialpayload=_checkPayloads(payload) +local compatible=AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities) +local goforit=specialpayload or(specialpayload==nil and compatible) +if goforit and _checkUnitTypes(payload)then +if payload.unlimited then +return 999 +else +n=n+payload.navail +end +end +end +end +return n +end +function AIRWING:GetPayloadPeformance(Payload,MissionType) +if Payload then +for _,Capability in pairs(Payload.capabilities)do +local capability=Capability +if capability.MissionType==MissionType then +return capability.Performance +end +end +else +self:E(self.lid.."ERROR: Payload is nil!") +end +return-1 +end +function AIRWING:GetPayloadMissionTypes(Payload) +local missiontypes={} +for _,Capability in pairs(Payload.capabilities)do +local capability=Capability +table.insert(missiontypes,capability.MissionType) +end +return missiontypes +end +ARMYGROUP={ +ClassName="ARMYGROUP", +formationPerma=nil, +engage={}, +} +ARMYGROUP.version="1.0.3" +function ARMYGROUP:New(group) +local og=_DATABASE:GetOpsGroup(group) +if og then +og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) +return og +end +local self=BASE:Inherit(self,OPSGROUP:New(group)) +self.lid=string.format("ARMYGROUP %s | ",self.groupname) +self:SetDefaultROE() +self:SetDefaultAlarmstate() +self:SetDefaultEPLRS(self.isEPLRS) +self:SetDefaultEmission() +self:SetDetection() +self:SetPatrolAdInfinitum(false) +self:SetRetreatZones() +self:AddTransition("*","FullStop","Holding") +self:AddTransition("*","Cruise","Cruising") +self:AddTransition("*","RTZ","Returning") +self:AddTransition("Holding","Returned","Returned") +self:AddTransition("Returning","Returned","Returned") +self:AddTransition("*","Detour","OnDetour") +self:AddTransition("OnDetour","DetourReached","Cruising") +self:AddTransition("*","Retreat","Retreating") +self:AddTransition("Retreating","Retreated","Retreated") +self:AddTransition("*","Suppressed","*") +self:AddTransition("*","Unsuppressed","*") +self:AddTransition("Cruising","EngageTarget","Engaging") +self:AddTransition("Holding","EngageTarget","Engaging") +self:AddTransition("OnDetour","EngageTarget","Engaging") +self:AddTransition("Engaging","Disengage","Cruising") +self:AddTransition("*","Rearm","Rearm") +self:AddTransition("Rearm","Rearming","Rearming") +self:AddTransition("*","Rearmed","Cruising") +self:_InitWaypoints() +self:_InitGroup() +self:HandleEvent(EVENTS.Birth,self.OnEventBirth) +self:HandleEvent(EVENTS.Dead,self.OnEventDead) +self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit) +self:HandleEvent(EVENTS.UnitLost,self.OnEventRemoveUnit) +self:HandleEvent(EVENTS.Hit,self.OnEventHit) +self.timerStatus=TIMER:New(self.Status,self):Start(1,30) +self.timerQueueUpdate=TIMER:New(self._QueueUpdate,self):Start(2,5) +self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(2,30) +_DATABASE:AddOpsGroup(self) +return self +end +function ARMYGROUP:SetPatrolAdInfinitum(switch) +if switch==false then +self.adinfinitum=false +else +self.adinfinitum=true +end +return self +end +function ARMYGROUP:GetClosestRoad() +local coord=self:GetCoordinate():GetClosestPointToRoad() +return coord +end +function ARMYGROUP:GetClosestRoadDist() +local road=self:GetClosestRoad() +if road then +local dist=road:Get2DDistance(self:GetCoordinate()) +return dist +end +return math.huge +end +function ARMYGROUP:AddTaskFireAtPoint(Coordinate,Clock,Radius,Nshots,WeaponType,Prio) +Coordinate=self:_CoordinateFromObject(Coordinate) +local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType) +local task=self:AddTask(DCStask,Clock,nil,Prio) +return task +end +function ARMYGROUP:AddTaskBarrage(Clock,Heading,Alpha,Altitude,Radius,Nshots,WeaponType,Prio) +Heading=Heading or 0 +Alpha=Alpha or 60 +Altitude=Altitude or 100 +local distance=Altitude/math.tan(math.rad(Alpha)) +local a=self:GetVec2() +local vec2=UTILS.Vec2Translate(a,distance,Heading) +local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,vec2,Radius,Nshots,WeaponType,Altitude) +local task=self:AddTask(DCStask,Clock,nil,Prio) +return task +end +function ARMYGROUP:AddTaskWaypointFireAtPoint(Coordinate,Waypoint,Radius,Nshots,WeaponType,Prio) +Coordinate=self:_CoordinateFromObject(Coordinate) +Waypoint=Waypoint or self:GetWaypointNext() +local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType) +local task=self:AddTaskWaypoint(DCStask,Waypoint,nil,Prio) +return task +end +function ARMYGROUP:AddTaskAttackGroup(TargetGroup,WeaponExpend,WeaponType,Clock,Prio) +local DCStask=CONTROLLABLE.TaskAttackGroup(nil,TargetGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack) +local task=self:AddTask(DCStask,Clock,nil,Prio) +return task +end +function ARMYGROUP:AddTaskCargoGroup(GroupSet,PickupZone,DeployZone,Clock,Prio) +local DCStask={} +DCStask.id="CargoTransport" +DCStask.params={} +DCStask.params.cargoqueu=1 +local task=self:AddTask(DCStask,Clock,nil,Prio) +return task +end +function ARMYGROUP:SetRetreatZones(RetreatZoneSet) +self.retreatZones=RetreatZoneSet or SET_ZONE:New() +return self +end +function ARMYGROUP:AddRetreatZone(RetreatZone) +self.retreatZones:AddZone(RetreatZone) +return self +end +function ARMYGROUP:SetSuppressionOn(Tave,Tmin,Tmax) +self.suppressionOn=true +self.TsuppressMin=Tmin or 1 +self.TsuppressMin=math.max(self.TsuppressMin,1) +self.TsuppressMax=Tmax or 15 +self.TsuppressMax=math.max(self.TsuppressMax,self.TsuppressMin) +self.TsuppressAve=Tave or 10 +self.TsuppressAve=math.max(self.TsuppressMin) +self.TsuppressAve=math.min(self.TsuppressMax) +self:T(self.lid..string.format("Set ave suppression time to %d seconds.",self.TsuppressAve)) +self:T(self.lid..string.format("Set min suppression time to %d seconds.",self.TsuppressMin)) +self:T(self.lid..string.format("Set max suppression time to %d seconds.",self.TsuppressMax)) +return self +end +function ARMYGROUP:SetSuppressionOff() +self.suppressionOn=false +end +function ARMYGROUP:IsHolding() +return self:Is("Holding") +end +function ARMYGROUP:IsCruising() +return self:Is("Cruising") +end +function ARMYGROUP:IsOnDetour() +return self:Is("OnDetour") +end +function ARMYGROUP:IsCombatReady() +local combatready=true +if self:IsRearming()or self:IsRetreating()or self:IsOutOfAmmo()or self:IsEngaging()or self:IsDead()or self:IsStopped()or self:IsInUtero()then +combatready=false +end +if self:IsPickingup()or self:IsLoading()or self:IsTransporting()or self:IsLoaded()or self:IsCargo()or self:IsCarrier()then +combatready=false +end +return combatready +end +function ARMYGROUP:Status() +local fsmstate=self:GetState() +local alive=self:IsAlive() +if alive then +self:_UpdatePosition() +self:_CheckDetectedUnits() +self:_CheckAmmoStatus() +self:_CheckDamage() +self:_CheckStuck() +if self:IsEngaging()then +self:_UpdateEngageTarget() +end +if self:IsWaiting()then +if self.Twaiting and self.dTwait then +if timer.getAbsTime()>self.Twaiting+self.dTwait then +self.Twaiting=nil +self.dTwait=nil +if self:_CountPausedMissions()>0 then +self:UnpauseMission() +else +self:Cruise() +end +end +end +end +local mission=self:GetMissionCurrent() +if mission and mission.updateDCSTask then +if mission.type==AUFTRAG.Type.CAPTUREZONE then +local Task=mission:GetGroupWaypointTask(self) +if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then +self:_UpdateTask(Task,mission) +end +end +end +else +self:_CheckDamage() +end +if alive~=nil then +if self.verbose>=1 then +local nelem=self:CountElements() +local Nelem=#self.elements +local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks() +local nMissions=self:CountRemainingMissison() +local roe=self:GetROE()or-1 +local als=self:GetAlarmstate()or-1 +local wpidxCurr=self.currentwp +local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr)or 0 +local wpidxNext=self:GetWaypointIndexNext()or 0 +local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext)or 0 +local wpN=#self.waypoints or 0 +local wpF=tostring(self.passedfinalwp) +local speed=UTILS.MpsToKnots(self.velocity or 0) +local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) +local alt=self.position and self.position.y or 0 +local hdg=self.heading or 0 +local formation=self.option.Formation or"unknown" +local life=self.life or 0 +local ammo=self:GetAmmoTot().Total +local ndetected=self.detectionOn and tostring(self.detectedunits:Count())or"Off" +local cargo=0 +for _,_element in pairs(self.elements)do +local element=_element +cargo=cargo+element.weightCargo +end +local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) [%s] | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f", +fsmstate,nelem,Nelem,roe,als,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,wpN,wpF,life,speed,speedEx,formation,hdg,ammo,ndetected,cargo) +self:I(self.lid..text) +end +else +if self.verbose>=1 then +local text=string.format("State %s: Alive=%s",fsmstate,tostring(self:IsAlive())) +self:I(self.lid..text) +end +end +if self.verbose>=2 then +local text="Elements:" +for i,_element in pairs(self.elements)do +local element=_element +local name=element.name +local status=element.status +local unit=element.unit +local life,life0=self:GetLifePoints(element) +local life0=element.life0 +local ammo=self:GetAmmoElement(element) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles,element.weightCargo,element.weightMaxCargo) +end +if#self.elements==0 then +text=text.." none!" +end +self:T(self.lid..text) +end +if self:IsCruising()and self.detectionOn and self.engagedetectedOn then +local targetgroup,targetdist=self:_GetDetectedTarget() +if targetgroup then +self:T(self.lid..string.format("Engaging target group %s at distance %d meters",targetgroup:GetName(),targetdist)) +self:EngageTarget(targetgroup) +end +end +self:_CheckCargoTransport() +self:_PrintTaskAndMissionStatus() +end +function ARMYGROUP:onafterElementSpawned(From,Event,To,Element) +self:T(self.lid..string.format("Element spawned %s",Element.name)) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED) +end +function ARMYGROUP:onafterSpawned(From,Event,To) +self:T(self.lid..string.format("Group spawned!")) +if self.verbose>=1 then +local text=string.format("Initialized Army Group %s:\n",self.groupname) +text=text..string.format("Unit type = %s\n",self.actype) +text=text..string.format("Speed max = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax)) +text=text..string.format("Speed cruise = %.1f Knots\n",UTILS.KmphToKnots(self.speedCruise)) +text=text..string.format("Weight = %.1f kg\n",self:GetWeightTotal()) +text=text..string.format("Cargo bay = %.1f kg\n",self:GetFreeCargobay()) +text=text..string.format("Has EPLRS = %s\n",tostring(self.isEPLRS)) +text=text..string.format("Elements = %d\n",#self.elements) +text=text..string.format("Waypoints = %d\n",#self.waypoints) +text=text..string.format("Radio = %.1f MHz %s %s\n",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu),tostring(self.radio.On)) +text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Rockets,self.ammo.Missiles) +text=text..string.format("FSM state = %s\n",self:GetState()) +text=text..string.format("Is alive = %s\n",tostring(self:IsAlive())) +text=text..string.format("LateActivate = %s\n",tostring(self:IsLateActivated())) +self:I(self.lid..text) +end +self:_UpdatePosition() +self.isDead=false +self.isDestroyed=false +if self.isAI then +self:SwitchROE(self.option.ROE) +self:SwitchAlarmstate(self.option.Alarm) +self:SwitchEmission(self.option.Emission) +self:SwitchEPLRS(self.option.EPLRS) +self:SwitchInvisible(self.option.Invisible) +self:SwitchImmortal(self.option.Immortal) +self:_SwitchTACAN() +if self.radioDefault then +self:SwitchRadio(self.radioDefault.Freq,self.radioDefault.Modu) +else +self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,true) +end +if not self.option.Formation then +end +local Nwp=#self.waypoints +if Nwp>1 and self.isMobile then +self:T(self.lid..string.format("Got %d waypoints on spawn ==> Cruise in -1.0 sec!",Nwp)) +local wp=self:GetWaypointNext() +self.option.Formation=wp.action +self:__Cruise(-1) +else +self:T(self.lid.."No waypoints on spawn ==> Full Stop!") +self:FullStop() +end +end +end +function ARMYGROUP:onbeforeUpdateRoute(From,Event,To,n,N,Speed,Formation) +local allowed=true +local trepeat=nil +if self:IsWaiting()then +self:T(self.lid.."Update route denied. Group is WAITING!") +return false +elseif self:IsInUtero()then +self:T(self.lid.."Update route denied. Group is INUTERO!") +return false +elseif self:IsDead()then +self:T(self.lid.."Update route denied. Group is DEAD!") +return false +elseif self:IsStopped()then +self:T(self.lid.."Update route denied. Group is STOPPED!") +return false +elseif self:IsHolding()then +self:T(self.lid.."Update route denied. Group is holding position!") +return false +elseif self:IsEngaging()then +self:T(self.lid.."Update route allowed. Group is engaging!") +return true +end +if self.taskcurrent>0 then +local task=self:GetTaskByID(self.taskcurrent) +if task then +if task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then +self:T2(self.lid.."Allowing update route for Task: PatrolZone") +elseif task.dcstask.id==AUFTRAG.SpecialTask.RECON then +self:T2(self.lid.."Allowing update route for Task: ReconMission") +elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then +self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") +elseif task.dcstask.id==AUFTRAG.SpecialTask.REARMING then +self:T2(self.lid.."Allowing update route for Task: Rearming") +else +local taskname=task and task.description or"No description" +self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s",self.taskcurrent,tostring(taskname))) +allowed=false +end +else +self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!",self.taskcurrent)) +allowed=false +end +end +if not self.isAI then +allowed=false +end +self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)",self:GetState(),tostring(allowed),tostring(trepeat))) +if trepeat then +self:__UpdateRoute(trepeat,n) +end +return allowed +end +function ARMYGROUP:onafterUpdateRoute(From,Event,To,n,N,Speed,Formation) +n=n or self:GetWaypointIndexNext(self.adinfinitum) +N=N or#self.waypoints +N=math.min(N,#self.waypoints) +local text=string.format("Update route state=%s: n=%s, N=%s, Speed=%s, Formation=%s",self:GetState(),tostring(n),tostring(N),tostring(Speed),tostring(Formation)) +self:T(self.lid..text) +local waypoints={} +local wp=self.waypoints[n] +local coordinate=self:GetCoordinate() +local coordRoad=coordinate:GetClosestPointToRoad() +local roaddist=coordinate:Get2DDistance(coordRoad) +local formation0=wp.action +if formation0==ENUMS.Formation.Vehicle.OnRoad then +if roaddist>10 then +formation0=ENUMS.Formation.Vehicle.OffRoad +else +formation0=ENUMS.Formation.Vehicle.OnRoad +end +end +local current=coordinate:WaypointGround(UTILS.MpsToKmph(self.speedWp),formation0) +table.insert(waypoints,1,current) +if N-n>0 then +for j=n,N do +local i=j-1 +if i==0 then +i=self.currentwp +end +local wp=UTILS.DeepCopy(self.waypoints[j]) +local wp0=self.waypoints[i] +if false and self.attribute==GROUP.Attribute.GROUND_APC then +local text=string.format("FF Update: i=%d, wp[i]=%s, wp[i-1]=%s",i,wp.action,wp0.action) +env.info(text) +end +if Speed then +wp.speed=UTILS.KnotsToMps(tonumber(Speed)) +else +if wp.speed<0.1 then +wp.speed=UTILS.KmphToMps(self.speedCruise) +end +end +if self.formationPerma then +wp.action=self.formationPerma +elseif Formation then +wp.action=Formation +end +if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp0.roaddist>=0 then +local wproad=wp0.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad) +table.insert(waypoints,wproad) +end +if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>=0 then +wp.action=ENUMS.Formation.Vehicle.OffRoad +local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad) +table.insert(waypoints,wproad) +end +table.insert(waypoints,wp) +end +else +local wp=UTILS.DeepCopy(self.waypoints[n]) +if wp.speed<0.1 then +wp.speed=UTILS.KmphToMps(self.speedCruise) +end +local formation=wp.action +if self.formationPerma then +formation=self.formationPerma +elseif Formation then +formation=Formation +end +if formation==ENUMS.Formation.Vehicle.OnRoad then +if roaddist>10 then +local wproad=coordRoad:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad) +table.insert(waypoints,wproad) +end +if wp.roaddist>10 then +local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad) +table.insert(waypoints,wproad) +end +end +if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>10 then +wp.action=ENUMS.Formation.Vehicle.OffRoad +end +table.insert(waypoints,wp) +end +local wp=waypoints[1] +self.option.Formation=wp.action +self.speedWp=wp.speed +self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s",self.speedWp)) +if self.verbose>=10 then +for i,_wp in pairs(waypoints)do +local wp=_wp +local text=string.format("WP #%d UID=%d Formation=%s: Speed=%d m/s, Alt=%d m, Type=%s",i,wp.uid and wp.uid or-1,wp.action,wp.speed,wp.alt,wp.type) +local coord=COORDINATE:NewFromWaypoint(wp):MarkToAll(text) +self:I(text) +end +end +if self:IsEngaging()or not self.passedfinalwp then +self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Formation=%s", +self.currentwp,n,#waypoints,#self.waypoints,UTILS.MpsToKnots(self.speedWp),tostring(self.option.Formation))) +self:Route(waypoints) +else +self:T(self.lid..string.format("WARNING: Passed final WP when UpdateRoute() ==> Full Stop!")) +self:FullStop() +end +end +function ARMYGROUP:onafterGotoWaypoint(From,Event,To,UID,Speed,Formation) +local n=self:GetWaypointIndex(UID) +if n then +Speed=Speed or self:GetSpeedToWaypoint(n) +self:__UpdateRoute(-0.01,n,nil,Speed,Formation) +end +end +function ARMYGROUP:onafterDetour(From,Event,To,Coordinate,Speed,Formation,ResumeRoute) +for _,_wp in pairs(self.waypoints)do +local wp=_wp +if wp.detour then +self:RemoveWaypointByID(wp.uid) +end +end +Speed=Speed or self:GetSpeedCruise() +local uid=self:GetWaypointCurrentUID() +local wp=self:AddWaypoint(Coordinate,Speed,uid,Formation,true) +if ResumeRoute then +wp.detour=1 +else +wp.detour=0 +end +end +function ARMYGROUP:onafterOutOfAmmo(From,Event,To) +self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime())) +local task=self:GetTaskCurrent() +if task then +if task.dcstask.id=="FireAtPoint"or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then +self:T(self.lid..string.format("Cancelling current %s task because out of ammo!",task.dcstask.id)) +self:TaskCancel(task) +end +end +if self.rearmOnOutOfAmmo then +local truck,dist=self:FindNearestAmmoSupply(30) +if truck then +self:T(self.lid..string.format("Found Ammo Truck %s [%s]",truck:GetName(),truck:GetTypeName())) +local Coordinate=truck:GetCoordinate() +self:__Rearm(-1,Coordinate) +return +end +end +if self.retreatOnOutOfAmmo then +self:T(self.lid.."Retreat on out of ammo") +self:__Retreat(-1) +return +end +if self.rtzOnOutOfAmmo and not self:IsMissionTypeInQueue(AUFTRAG.Type.REARMING)then +self:T(self.lid.."RTZ on out of ammo") +self:__RTZ(-1) +end +end +function ARMYGROUP:onbeforeRearm(From,Event,To,Coordinate,Formation) +local dt=nil +local allowed=true +if self:IsOnMission()then +local mission=self:GetMissionCurrent() +if mission and mission.type~=AUFTRAG.Type.REARMING then +self:T(self.lid.."Rearm command but have current mission ==> Pausing mission!") +self:PauseMission() +dt=-0.1 +allowed=false +else +self:T(self.lid.."Rearm command and current mission is REARMING ==> Transition ALLOWED!") +end +end +if self:IsEngaging()then +self:T(self.lid.."Rearm command but currently engaging ==> Disengage!") +self:Disengage() +dt=-0.1 +allowed=false +end +if allowed and not Coordinate then +local truck=self:FindNearestAmmoSupply() +if truck and truck:IsAlive()then +self:__Rearm(-0.1,truck:GetCoordinate(),Formation) +end +return false +end +if dt then +self:T(self.lid..string.format("Trying Rearm again in %.2f sec",dt)) +self:__Rearm(dt,Coordinate,Formation) +allowed=false +end +return allowed +end +function ARMYGROUP:onafterRearm(From,Event,To,Coordinate,Formation) +self:T(self.lid..string.format("Group send to rearm")) +local uid=self:GetWaypointCurrentUID() +local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true) +wp.detour=0 +end +function ARMYGROUP:onafterRearmed(From,Event,To) +self:T(self.lid.."Group rearmed") +local mission=self:GetMissionCurrent() +if mission and mission.type==AUFTRAG.Type.REARMING then +self:MissionDone(mission) +else +self:_CheckGroupDone(1) +end +end +function ARMYGROUP:onbeforeRTZ(From,Event,To,Zone,Formation) +self:T2(self.lid.."onbeforeRTZ") +local zone=Zone or self.homezone +if zone then +if(not self.isMobile)and(not self:IsInZone(zone))then +self:Teleport(zone:GetCoordinate(),0,true) +self:__RTZ(-1,Zone,Formation) +return false +end +else +return false +end +return true +end +function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) +self:T2(self.lid.."onafterRTZ") +local zone=Zone or self.homezone +self:CancelAllMissions() +if zone then +if self:IsInZone(zone)then +self:Returned() +else +self:T(self.lid..string.format("RTZ to Zone %s",zone:GetName())) +local Coordinate=zone:GetRandomCoordinate() +local uid=self:GetWaypointCurrentUID() +local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true) +wp.detour=0 +end +else +self:T(self.lid.."ERROR: No RTZ zone given!") +end +end +function ARMYGROUP:onafterReturned(From,Event,To) +self:T(self.lid..string.format("Group returned")) +if self.legion then +self:T(self.lid..string.format("Adding group back to warehouse stock")) +self.legion:__AddAsset(10,self.group,1) +end +end +function ARMYGROUP:onafterRearming(From,Event,To) +local pos=self:GetCoordinate() +local wp=pos:WaypointGround(0) +self:Route({wp}) +end +function ARMYGROUP:onbeforeRetreat(From,Event,To,Zone,Formation) +if not Zone then +local a=self:GetVec2() +local distmin=math.huge +local zonemin=nil +for _,_zone in pairs(self.retreatZones:GetSet())do +local zone=_zone +local b=zone:GetVec2() +local dist=UTILS.VecDist2D(a,b) +if dist Pausing mission!") +self:PauseMission() +dt=-0.1 +allowed=false +end +if dt then +self:T(self.lid..string.format("Trying Engage again in %.2f sec",dt)) +self:__EngageTarget(dt,Target) +allowed=false +end +return allowed +end +function ARMYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Formation) +self:T(self.lid.."Engaging Target") +if Target:IsInstanceOf("TARGET")then +self.engage.Target=Target +else +self.engage.Target=TARGET:New(Target) +end +self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) +self.engage.roe=self:GetROE() +self.engage.alarmstate=self:GetAlarmstate() +self:SwitchAlarmstate(ENUMS.AlarmState.Auto) +self:SwitchROE(ENUMS.ROE.OpenFire) +local uid=self:GetWaypointCurrentUID() +self.engage.Formation=Formation or ENUMS.Formation.Vehicle.Vee +self.engage.Speed=Speed +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) +self.engage.Waypoint.detour=1 +end +function ARMYGROUP:_UpdateEngageTarget() +if self.engage.Target and self.engage.Target:IsAlive()then +local vec3=self.engage.Target:GetVec3() +if vec3 then +local dist=UTILS.VecDist3D(vec3,self.engage.Coordinate:GetVec3()) +local los=self:HasLoS(vec3) +if dist>100 or los==false then +self.engage.Coordinate:UpdateFromVec3(vec3) +local uid=self:GetWaypointCurrentUID() +self:RemoveWaypointByID(self.engage.Waypoint.uid) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.9) +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) +self.engage.Waypoint.detour=0 +end +else +self:T(self.lid.."Could not get position of target ==> Disengage!") +self:Disengage() +end +else +self:T(self.lid.."Target not ALIVE ==> Disengage!") +self:Disengage() +end +end +function ARMYGROUP:onafterDisengage(From,Event,To) +self:T(self.lid.."Disengage Target") +self:SwitchROE(self.engage.roe) +self:SwitchAlarmstate(self.engage.alarmstate) +local task=self:GetTaskCurrent() +if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then +self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!") +self:TaskDone(task) +end +if self.engage.Waypoint then +self:RemoveWaypointByID(self.engage.Waypoint.uid) +end +self:_CheckGroupDone(1) +end +function ARMYGROUP:onafterDetourReached(From,Event,To) +self:T(self.lid.."Group reached detour coordinate") +end +function ARMYGROUP:onafterFullStop(From,Event,To) +self:T(self.lid..string.format("Full stop!")) +local pos=self:GetCoordinate() +local wp=pos:WaypointGround(0) +self:Route({wp}) +end +function ARMYGROUP:onafterCruise(From,Event,To,Speed,Formation) +self.Twaiting=nil +self.dTwait=nil +self:T(self.lid..string.format("Cruise ==> Update route in 0.01 sec (speed=%s, formation=%s)",tostring(Speed),tostring(Formation))) +self:__UpdateRoute(-0.01,nil,nil,Speed,Formation) +end +function ARMYGROUP:onafterHit(From,Event,To,Enemy) +self:T(self.lid..string.format("ArmyGroup hit by %s",Enemy and Enemy:GetName()or"unknown")) +if self.suppressionOn then +env.info(self.lid.."FF suppress") +self:_Suppress() +end +end +function ARMYGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Formation,Updateroute) +self:T(self.lid..string.format("AddWaypoint Formation = %s",tostring(Formation))) +local coordinate=self:_CoordinateFromObject(Coordinate) +local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) +Speed=Speed or self:GetSpeedCruise() +if not Formation then +if self.formationPerma then +Formation=self.formationPerma +elseif self.optionDefault.Formation then +Formation=self.optionDefault.Formation +elseif self.option.Formation then +Formation=self.option.Formation +else +Formation=ENUMS.Formation.Vehicle.OnRoad +end +self:T2(self.lid..string.format("Formation set to = %s",tostring(Formation))) +end +local wp=coordinate:WaypointGround(UTILS.KnotsToKmph(Speed),Formation) +local waypoint=self:_CreateWaypoint(wp) +self:_AddWaypoint(waypoint,wpnumber) +waypoint.roadcoord=coordinate:GetClosestPointToRoad(false) +if waypoint.roadcoord then +waypoint.roaddist=coordinate:Get2DDistance(waypoint.roadcoord) +else +waypoint.roaddist=1000*1000 +end +self:T(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s",waypoint.uid,wpnumber,Speed,waypoint.roaddist,waypoint.action)) +if Updateroute==nil or Updateroute==true then +self:__UpdateRoute(-0.01) +end +return waypoint +end +function ARMYGROUP:_InitGroup(Template,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,ARMYGROUP._InitGroup,self,Template,0) +else +if self.groupinitialized then +self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") +return +end +local template=Template or self:_GetTemplate() +self.isAI=true +self.isLateActivated=template.lateActivation +self.isUncontrolled=false +self.speedMax=self.group:GetSpeedMax() +if self.speedMax and self.speedMax>3.6 then +self.isMobile=true +else +self.isMobile=false +self.speedMax=0 +end +self.speedCruise=self.speedMax*0.7 +self.ammo=self:GetAmmoTot() +self.radio.On=false +self.radio.Freq=133 +self.radio.Modu=radio.modulation.AM +self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,self.radio.On) +self.option.Formation=template.route.points[1].action +self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad +if not self.tacanDefault then +self:SetDefaultTACAN(nil,nil,nil,nil,true) +end +if not self.tacan then +self.tacan=UTILS.DeepCopy(self.tacanDefault) +end +local units=self.group:GetUnits() +local dcsgroup=Group.getByName(self.groupname) +local size0=dcsgroup:getInitialSize() +local u=dcsgroup:getUnits() +if#units~=size0 then +self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units! u=%d",#units,size0,#u)) +end +for _,unit in pairs(units)do +local unitname=unit:GetName() +self:_AddElementByName(unitname) +end +self.groupinitialized=true +end +return self +end +function ARMYGROUP:SwitchFormation(Formation,Permanently,NoRouteUpdate) +if self:IsAlive()or self:IsInUtero()then +Formation=Formation or(self.optionDefault.Formation or"Off road") +Permanently=Permanently or false +if Permanently then +self.formationPerma=Formation +else +self.formationPerma=nil +end +self.option.Formation=Formation or"Off road" +if self:IsInUtero()then +self:T(self.lid..string.format("Will switch formation to %s (permanently=%s) when group is spawned",tostring(self.option.Formation),tostring(Permanently))) +else +if NoRouteUpdate then +else +self:__UpdateRoute(-1,nil,nil,Formation) +end +self:T(self.lid..string.format("Switching formation to %s (permanently=%s)",tostring(self.option.Formation),tostring(Permanently))) +end +end +return self +end +function ARMYGROUP:FindNearestAmmoSupply(Radius) +Radius=UTILS.NMToMeters(Radius or 30) +local coord=self:GetCoordinate() +local myCoalition=self:GetCoalition() +local units=coord:ScanUnits(Radius) +local dmin=math.huge +local truck=nil +for _,_unit in pairs(units.Set)do +local unit=_unit +if unit:IsAlive()and unit:GetCoalition()==myCoalition and unit:IsAmmoSupply()and unit:GetVelocityKMH()<1 then +local d=coord:Get2DDistance(unit:GetCoord()) +if dself.TsuppressionOver then +self.TsuppressionOver=Tnow+Tsuppress +else +renew=false +end +end +if renew then +self:__Unsuppressed(self.TsuppressionOver-Tnow) +end +self:T(self.lid..string.format("Suppressed for %d sec",Tsuppress)) +end +function ARMYGROUP:onbeforeUnsuppressed(From,Event,To) +local Tnow=timer.getTime() +self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d",Tnow,self.TsuppressionOver)) +if Tnow>=self.TsuppressionOver then +return true +else +return false +end +end +function ARMYGROUP:onafterUnsuppressed(From,Event,To) +local text=string.format("Group %s has recovered!",self:GetName()) +MESSAGE:New(text,10):ToAll() +self:T(self.lid..text) +self:SwitchROE(self.suppressionROE) +if true then +self.group:FlareGreen() +end +end +AUFTRAG={ +ClassName="AUFTRAG", +verbose=0, +lid=nil, +auftragsnummer=nil, +groupdata={}, +legions={}, +statusLegion={}, +requestID={}, +assets={}, +NassetsLegMin={}, +NassetsLegMax={}, +missionFraction=0.5, +enrouteTasks={}, +marker=nil, +markerOn=nil, +markerCoalition=nil, +conditionStart={}, +conditionSuccess={}, +conditionFailure={}, +conditionPush={}, +conditionSuccessSet=false, +conditionFailureSet=false, +repeatDelay=1, +} +_AUFTRAGSNR=0 +AUFTRAG.Type={ +ANTISHIP="Anti Ship", +AWACS="AWACS", +BAI="BAI", +BOMBING="Bombing", +BOMBRUNWAY="Bomb Runway", +BOMBCARPET="Carpet Bombing", +CAP="CAP", +CAS="CAS", +ESCORT="Escort", +FAC="FAC", +FACA="FAC-A", +FERRY="Ferry Flight", +GROUNDESCORT="Ground Escort", +INTERCEPT="Intercept", +ORBIT="Orbit", +GCICAP="Ground Controlled CAP", +RECON="Recon", +RECOVERYTANKER="Recovery Tanker", +RESCUEHELO="Rescue Helo", +SEAD="SEAD", +STRIKE="Strike", +TANKER="Tanker", +TROOPTRANSPORT="Troop Transport", +ARTY="Fire At Point", +PATROLZONE="Patrol Zone", +OPSTRANSPORT="Ops Transport", +AMMOSUPPLY="Ammo Supply", +FUELSUPPLY="Fuel Supply", +ALERT5="Alert5", +ONGUARD="On Guard", +ARMOREDGUARD="Armored Guard", +BARRAGE="Barrage", +ARMORATTACK="Armor Attack", +CASENHANCED="CAS Enhanced", +HOVER="Hover", +LANDATCOORDINATE="Land at Coordinate", +GROUNDATTACK="Ground Attack", +CARGOTRANSPORT="Cargo Transport", +RELOCATECOHORT="Relocate Cohort", +AIRDEFENSE="Air Defence", +EWR="Early Warning Radar", +REARMING="Rearming", +CAPTUREZONE="Capture Zone", +NOTHING="Nothing", +PATROLRACETRACK="Patrol Racetrack", +STRAFING="Strafing", +} +AUFTRAG.SpecialTask={ +FORMATION="Formation", +PATROLZONE="PatrolZone", +RECON="ReconMission", +AMMOSUPPLY="Ammo Supply", +FUELSUPPLY="Fuel Supply", +ALERT5="Alert5", +ONGUARD="On Guard", +ARMOREDGUARD="ArmoredGuard", +BARRAGE="Barrage", +ARMORATTACK="AmorAttack", +HOVER="Hover", +GROUNDATTACK="Ground Attack", +FERRY="Ferry", +RELOCATECOHORT="Relocate Cohort", +AIRDEFENSE="Air Defense", +EWR="Early Warning Radar", +RECOVERYTANKER="Recovery Tanker", +REARMING="Rearming", +CAPTUREZONE="Capture Zone", +NOTHING="Nothing", +PATROLRACETRACK="Patrol Racetrack", +} +AUFTRAG.Status={ +PLANNED="planned", +QUEUED="queued", +REQUESTED="requested", +SCHEDULED="scheduled", +STARTED="started", +EXECUTING="executing", +DONE="done", +CANCELLED="cancelled", +SUCCESS="success", +FAILED="failed", +} +AUFTRAG.GroupStatus={ +SCHEDULED="scheduled", +STARTED="started", +EXECUTING="executing", +PAUSED="paused", +DONE="done", +CANCELLED="cancelled", +} +AUFTRAG.TargetType={ +GROUP="Group", +UNIT="Unit", +STATIC="Static", +COORDINATE="Coordinate", +AIRBASE="Airbase", +SETGROUP="SetGroup", +SETUNIT="SetUnit", +} +AUFTRAG.Category={ +ALL="All", +AIRCRAFT="Aircraft", +AIRPLANE="Airplane", +HELICOPTER="Helicopter", +GROUND="Ground", +NAVAL="Naval", +} +AUFTRAG.version="1.2.1" +function AUFTRAG:New(Type) +local self=BASE:Inherit(self,FSM:New()) +_AUFTRAGSNR=_AUFTRAGSNR+1 +self.type=Type +self.auftragsnummer=_AUFTRAGSNR +self:_SetLogID() +self.status=AUFTRAG.Status.PLANNED +self:SetName() +self:SetPriority() +self:SetTime() +self:SetRequiredAssets() +self.engageAsGroup=true +self.dTevaluate=5 +self.repeated=0 +self.repeatedSuccess=0 +self.repeatedFailure=0 +self.Nrepeat=0 +self.NrepeatFailure=0 +self.NrepeatSuccess=0 +self.Ncasualties=0 +self.Nkills=0 +self.Nelements=0 +self.Ngroups=0 +self.Nassigned=nil +self.Ndead=0 +self:SetStartState(self.status) +self:AddTransition("*","Planned",AUFTRAG.Status.PLANNED) +self:AddTransition(AUFTRAG.Status.PLANNED,"Queued",AUFTRAG.Status.QUEUED) +self:AddTransition(AUFTRAG.Status.QUEUED,"Requested",AUFTRAG.Status.REQUESTED) +self:AddTransition(AUFTRAG.Status.REQUESTED,"Scheduled",AUFTRAG.Status.SCHEDULED) +self:AddTransition(AUFTRAG.Status.PLANNED,"Scheduled",AUFTRAG.Status.SCHEDULED) +self:AddTransition(AUFTRAG.Status.SCHEDULED,"Started",AUFTRAG.Status.STARTED) +self:AddTransition(AUFTRAG.Status.STARTED,"Executing",AUFTRAG.Status.EXECUTING) +self:AddTransition("*","Done",AUFTRAG.Status.DONE) +self:AddTransition("*","Cancel",AUFTRAG.Status.CANCELLED) +self:AddTransition("*","Success",AUFTRAG.Status.SUCCESS) +self:AddTransition("*","Failed",AUFTRAG.Status.FAILED) +self:AddTransition("*","Status","*") +self:AddTransition("*","Stop","*") +self:AddTransition("*","Repeat",AUFTRAG.Status.PLANNED) +self:AddTransition("*","ElementDestroyed","*") +self:AddTransition("*","GroupDead","*") +self:AddTransition("*","AssetDead","*") +self:__Status(-1) +return self +end +function AUFTRAG:NewANTISHIP(Target,Altitude) +local mission=AUFTRAG:New(AUFTRAG.Type.ANTISHIP) +mission:_TargetFromObject(Target) +mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) +mission.missionTask=ENUMS.MissionTask.ANTISHIPSTRIKE +mission.missionAltitude=mission.engageAltitude +mission.missionFraction=0.4 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewHOVER(Coordinate,Altitude,Time,Speed,MissionAlt) +local mission=AUFTRAG:New(AUFTRAG.Type.HOVER) +if Altitude then +mission.hoverAltitude=Coordinate:GetLandHeight()+UTILS.FeetToMeters(Altitude) +else +mission.hoverAltitude=Coordinate:GetLandHeight()+UTILS.FeetToMeters(50) +end +mission:_TargetFromObject(Coordinate) +mission.hoverSpeed=0.1 +mission.hoverTime=Time or 300 +self:SetMissionSpeed(Speed or 150) +self:SetMissionAltitude(MissionAlt or 1000) +mission.missionFraction=0.9 +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.HELICOPTER} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewLANDATCOORDINATE(Coordinate,OuterRadius,InnerRadius,Time,Speed,MissionAlt,CombatLanding,DirectionAfterLand) +local mission=AUFTRAG:New(AUFTRAG.Type.LANDATCOORDINATE) +mission:_TargetFromObject(Coordinate) +mission.stayTime=Time or 300 +mission.stayAt=Coordinate +mission.combatLand=CombatLanding +mission.directionAfter=DirectionAfterLand +self:SetMissionSpeed(Speed or 150) +self:SetMissionAltitude(MissionAlt or 1000) +if OuterRadius then +mission.stayAt=Coordinate:GetRandomCoordinateInRadius(UTILS.FeetToMeters(OuterRadius),UTILS.FeetToMeters(InnerRadius or 0)) +end +mission.missionFraction=0.9 +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.HELICOPTER} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewPATROL_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg,Formation) +local mission=AUFTRAG:New(AUFTRAG.Type.PATROLRACETRACK) +mission:_TargetFromObject(Coordinate) +if Altitude then +mission.TrackAltitude=UTILS.FeetToMeters(Altitude) +else +mission.TrackAltitude=UTILS.FeetToMeters(20000) +end +mission.TrackPoint1=Coordinate +local leg=UTILS.NMToMeters(Leg)or UTILS.NMToMeters(10) +local heading=Heading or 90 +if heading<0 or heading>360 then heading=90 end +mission.TrackPoint2=Coordinate:Translate(leg,heading,true) +mission.TrackSpeed=UTILS.IasToTas(UTILS.KnotsToKmph(Speed or 300),mission.TrackAltitude) +mission.missionSpeed=UTILS.KnotsToKmph(Speed or 300) +mission.missionAltitude=mission.TrackAltitude*0.9 +mission.missionTask=ENUMS.MissionTask.CAP +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg) +local mission=AUFTRAG:New(AUFTRAG.Type.ORBIT) +mission:_TargetFromObject(Coordinate) +if Altitude then +mission.orbitAltitude=UTILS.FeetToMeters(Altitude) +else +mission.orbitAltitude=Coordinate.y +end +mission.orbitSpeed=UTILS.IasToTas(UTILS.KnotsToMps(Speed or 350),mission.orbitAltitude) +mission.missionSpeed=UTILS.KnotsToKmph(Speed or 350) +if Leg then +mission.orbitLeg=UTILS.NMToMeters(Leg) +if Heading and Heading<0 then +mission.orbitHeadingRel=true +Heading=-Heading +end +mission.orbitHeading=Heading +end +mission.missionAltitude=mission.orbitAltitude*0.9 +mission.missionFraction=0.9 +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed) +local mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed) +return mission +end +function AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) +Heading=Heading or math.random(360) +Leg=Leg or 10 +local mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg) +return mission +end +function AUFTRAG:NewORBIT_GROUP(Group,Altitude,Speed,Leg,Heading,OffsetVec2,Distance) +Altitude=Altitude or 6000 +local mission=AUFTRAG:NewORBIT(Group,Altitude,Speed,Heading,Leg) +mission.updateDCSTask=true +if OffsetVec2 then +if OffsetVec2.x then +OffsetVec2.x=UTILS.NMToMeters(OffsetVec2.x) +end +if OffsetVec2.y then +OffsetVec2.y=UTILS.NMToMeters(OffsetVec2.y) +end +if OffsetVec2.r then +OffsetVec2.r=UTILS.NMToMeters(OffsetVec2.r) +end +end +mission.orbitOffsetVec2=OffsetVec2 +mission.orbitDeltaR=UTILS.NMToMeters(Distance or 5) +mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewGCICAP(Coordinate,Altitude,Speed,Heading,Leg) +local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) +mission.type=AUFTRAG.Type.GCICAP +mission:_SetLogID() +mission.missionTask=ENUMS.MissionTask.INTERCEPT +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.AIRCRAFT} +return mission +end +function AUFTRAG:NewTANKER(Coordinate,Altitude,Speed,Heading,Leg,RefuelSystem) +local mission +if Leg==0 then +mission=AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed) +else +mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) +end +mission.type=AUFTRAG.Type.TANKER +mission:_SetLogID() +mission.refuelSystem=RefuelSystem +mission.missionTask=ENUMS.MissionTask.REFUELING +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) +local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) +mission.type=AUFTRAG.Type.AWACS +mission:_SetLogID() +mission.missionTask=ENUMS.MissionTask.AWACS +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewINTERCEPT(Target) +local mission=AUFTRAG:New(AUFTRAG.Type.INTERCEPT) +mission:_TargetFromObject(Target) +mission.missionTask=ENUMS.MissionTask.INTERCEPT +mission.missionFraction=0.1 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewCAP(ZoneCAP,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) +TargetTypes=UTILS.EnsureTable(TargetTypes,true) +Altitude=Altitude or 10000 +local mission=AUFTRAG:NewORBIT(Coordinate or ZoneCAP:GetCoordinate(),Altitude,Speed or 350,Heading,Leg) +mission.type=AUFTRAG.Type.CAP +mission:_SetLogID() +mission.engageZone=ZoneCAP or Coordinate +mission.engageTargetTypes=TargetTypes or{"Air"} +mission.missionTask=ENUMS.MissionTask.CAP +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.missionSpeed=UTILS.KnotsToKmph(UTILS.KnotsToAltKIAS(Speed or 350,Altitude)) +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewCAPGROUP(Grp,Altitude,Speed,RelHeading,Leg,OffsetDist,OffsetAngle,UpdateDistance,TargetTypes,EngageRange) +TargetTypes=UTILS.EnsureTable(TargetTypes,true) +local OffsetVec2={r=OffsetDist or 6,phi=OffsetAngle or 180} +Leg=Leg or 14 +local Heading=nil +if RelHeading then +Heading=-math.abs(RelHeading) +end +local mission=AUFTRAG:NewORBIT_GROUP(Grp,Altitude,Speed,Leg,Heading,OffsetVec2,UpdateDistance) +mission.type=AUFTRAG.Type.CAP +mission:_SetLogID() +local engage=EngageRange or 32 +local zoneCAPGroup=ZONE_GROUP:New("CAPGroup",Grp,UTILS.NMToMeters(engage)) +mission.engageZone=zoneCAPGroup +mission.engageTargetTypes=TargetTypes or{"Air"} +mission.missionTask=ENUMS.MissionTask.CAP +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewCAS(ZoneCAS,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) +TargetTypes=UTILS.EnsureTable(TargetTypes,true) +local mission=AUFTRAG:NewORBIT(Coordinate or ZoneCAS:GetCoordinate(),Altitude or 10000,Speed,Heading,Leg) +mission.type=AUFTRAG.Type.CAS +mission:_SetLogID() +mission.engageZone=ZoneCAS +mission.engageTargetTypes=TargetTypes or{"Helicopters","Ground Units","Light armed ships"} +mission.missionTask=ENUMS.MissionTask.CAS +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewCASENHANCED(CasZone,Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +local mission=AUFTRAG:New(AUFTRAG.Type.CASENHANCED) +if type(CasZone)=="string"then +CasZone=ZONE:New(CasZone) +end +mission:_TargetFromObject(CasZone) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CASENHANCED) +mission:SetEngageDetected(RangeMax,TargetTypes or{"Helicopters","Ground Units","Light armed ships"},CasZone,NoEngageZoneSet) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.missionFraction=0.5 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil +mission.dTevaluate=15 +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewFAC(FacZone,Speed,Altitude,Frequency,Modulation) +local mission=AUFTRAG:New(AUFTRAG.Type.FAC) +if type(FacZone)=="string"then +FacZone=ZONE:FindByName(FacZone) +end +mission:_TargetFromObject(FacZone) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.FAC) +mission.facFreq=Frequency or 133 +mission.facModu=Modulation or radio.modulation.AM +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=1.0 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil +mission.categories={AUFTRAG.Category.AIRCRAFT,AUFTRAG.Category.GROUND} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewFACA(Target,Designation,DataLink,Frequency,Modulation) +local mission=AUFTRAG:New(AUFTRAG.Type.FACA) +mission:_TargetFromObject(Target) +mission.facDesignation=Designation +mission.facDatalink=true +mission.facFreq=Frequency or 133 +mission.facModu=Modulation or radio.modulation.AM +mission.missionTask=ENUMS.MissionTask.AFAC +mission.missionAltitude=nil +mission.missionFraction=0.5 +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewBAI(Target,Altitude) +local mission=AUFTRAG:New(AUFTRAG.Type.BAI) +mission:_TargetFromObject(Target) +mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 5000) +mission.missionTask=ENUMS.MissionTask.GROUNDATTACK +mission.missionAltitude=mission.engageAltitude +mission.missionFraction=0.75 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewSEAD(Target,Altitude) +local mission=AUFTRAG:New(AUFTRAG.Type.SEAD) +mission:_TargetFromObject(Target) +mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) +mission.missionTask=ENUMS.MissionTask.SEAD +mission.missionAltitude=mission.engageAltitude +mission.missionFraction=0.2 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewSEADInZone(TargetZone,Altitude,TargetTypes,Duration) +local mission=AUFTRAG:New(AUFTRAG.Type.SEAD) +mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) +mission.engageZone=TargetZone +mission.engageTargetTypes=TargetTypes or{"Air Defence"} +mission.missionTask=ENUMS.MissionTask.SEAD +mission.missionAltitude=mission.engageAltitude +mission.missionFraction=0.2 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +mission:SetDuration(Duration or 1800) +return mission +end +function AUFTRAG:NewSTRIKE(Target,Altitude,EngageWeaponType) +local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE) +mission:_TargetFromObject(Target) +mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) +mission.missionTask=ENUMS.MissionTask.GROUNDATTACK +mission.missionAltitude=mission.engageAltitude +mission.missionFraction=0.75 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewBOMBING(Target,Altitude,EngageWeaponType,Divebomb) +local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) +mission:_TargetFromObject(Target) +mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) +mission.missionTask=ENUMS.MissionTask.GROUNDATTACK +mission.missionAltitude=mission.engageAltitude*0.8 +mission.missionFraction=0.5 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.NoReaction +mission.optionDivebomb=Divebomb or nil +mission.dTevaluate=5*60 +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewSTRAFING(Target,Altitude,Length) +local mission=AUFTRAG:New(AUFTRAG.Type.STRAFING) +mission:_TargetFromObject(Target) +mission.engageWeaponType=805337088 +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 1000) +mission.engageLength=Length +mission.missionTask=ENUMS.MissionTask.GROUNDATTACK +mission.missionAltitude=mission.engageAltitude*0.8 +mission.missionFraction=0.5 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.NoReaction +mission.dTevaluate=5*60 +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewBOMBRUNWAY(Airdrome,Altitude) +if type(Airdrome)=="string"then +Airdrome=AIRBASE:FindByName(Airdrome) +end +local mission=AUFTRAG:New(AUFTRAG.Type.BOMBRUNWAY) +mission:_TargetFromObject(Airdrome) +mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) +mission.missionTask=ENUMS.MissionTask.RUNWAYATTACK +mission.missionAltitude=mission.engageAltitude*0.8 +mission.missionFraction=0.75 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.dTevaluate=5*60 +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewBOMBCARPET(Target,Altitude,CarpetLength) +local mission=AUFTRAG:New(AUFTRAG.Type.BOMBCARPET) +mission:_TargetFromObject(Target) +mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) +mission.engageLength=CarpetLength or 500 +mission.engageAsGroup=false +mission.engageDirection=nil +mission.missionTask=ENUMS.MissionTask.GROUNDATTACK +mission.missionAltitude=mission.engageAltitude*0.8 +mission.missionFraction=0.5 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.NoReaction +mission.dTevaluate=5*60 +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewGROUNDESCORT(EscortGroup,OrbitDistance,TargetTypes) +local mission=AUFTRAG:New(AUFTRAG.Type.GROUNDESCORT) +if type(EscortGroup)=="string"then +mission.escortGroupName=EscortGroup +mission:_TargetFromObject() +else +mission:_TargetFromObject(EscortGroup) +end +mission.orbitDistance=OrbitDistance and UTILS.NMToMeters(OrbitDistance)or UTILS.NMToMeters(1.5) +mission.engageTargetTypes=TargetTypes or{"Ground vehicles"} +mission.missionTask=ENUMS.MissionTask.GROUNDESCORT +mission.missionFraction=0.1 +mission.missionAltitude=100 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.EvadeFire +mission.categories={AUFTRAG.Category.HELICOPTER} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewESCORT(EscortGroup,OffsetVector,EngageMaxDistance,TargetTypes) +local mission=AUFTRAG:New(AUFTRAG.Type.ESCORT) +if type(EscortGroup)=="string"then +mission.escortGroupName=EscortGroup +mission:_TargetFromObject() +else +mission:_TargetFromObject(EscortGroup) +end +mission.escortVec3=OffsetVector or{x=-100,y=0,z=200} +mission.engageMaxDistance=EngageMaxDistance and UTILS.NMToMeters(EngageMaxDistance)or UTILS.NMToMeters(32) +mission.engageTargetTypes=TargetTypes or{"Air"} +mission.missionTask=ENUMS.MissionTask.ESCORT +mission.missionFraction=0.1 +mission.missionAltitude=1000 +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewRESCUEHELO(Carrier) +local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) +mission:_TargetFromObject(Carrier) +mission.missionTask=ENUMS.MissionTask.NOTHING +mission.missionFraction=0.5 +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionROT=ENUMS.ROT.NoReaction +mission.categories={AUFTRAG.Category.HELICOPTER} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewRECOVERYTANKER(Carrier,Altitude,Speed,Leg,RelHeading,OffsetDist,OffsetAngle,UpdateDistance) +local OffsetVec2={r=OffsetDist or 6,phi=OffsetAngle or 180} +Leg=Leg or 14 +Speed=Speed or 250 +local Heading=nil +if RelHeading then +Heading=-math.abs(RelHeading) +end +local mission=AUFTRAG:NewORBIT_GROUP(Carrier,Altitude,Speed,Leg,Heading,OffsetVec2,UpdateDistance) +mission.type=AUFTRAG.Type.RECOVERYTANKER +mission.missionTask=ENUMS.MissionTask.REFUELING +mission.missionFraction=0.9 +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionROT=ENUMS.ROT.NoReaction +mission.categories={AUFTRAG.Category.AIRPLANE} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet,DropoffCoordinate,PickupCoordinate,PickupRadius) +local mission=AUFTRAG:New(AUFTRAG.Type.TROOPTRANSPORT) +if TransportGroupSet:IsInstanceOf("GROUP")then +mission.transportGroupSet=SET_GROUP:New() +mission.transportGroupSet:AddGroup(TransportGroupSet) +elseif TransportGroupSet:IsInstanceOf("SET_GROUP")then +mission.transportGroupSet=TransportGroupSet +else +mission:E(mission.lid.."ERROR: TransportGroupSet must be a GROUP or SET_GROUP object!") +return nil +end +mission:_TargetFromObject(mission.transportGroupSet) +mission.transportPickup=PickupCoordinate or mission:GetTargetCoordinate() +mission.transportDropoff=DropoffCoordinate +mission.transportPickupRadius=PickupRadius or 100 +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.TROOPTRANSPORT) +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.HELICOPTER,AUFTRAG.Category.GROUND} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewCARGOTRANSPORT(StaticCargo,DropZone) +local mission=AUFTRAG:New(AUFTRAG.Type.CARGOTRANSPORT) +mission:_TargetFromObject(StaticCargo) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CARGOTRANSPORT) +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.categories={AUFTRAG.Category.HELICOPTER} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.groupId=StaticCargo:GetID() +mission.DCStask.params.zoneId=DropZone.ZoneID +mission.DCStask.params.zone=DropZone +mission.DCStask.params.cargo=StaticCargo +return mission +end +function AUFTRAG:NewARTY(Target,Nshots,Radius,Altitude) +local mission=AUFTRAG:New(AUFTRAG.Type.ARTY) +mission:_TargetFromObject(Target) +mission.artyShots=Nshots or nil +mission.artyRadius=Radius or 100 +mission.artyAltitude=Altitude +mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=0 +mission.missionFraction=0.0 +mission.missionWaypointRadius=0.0 +mission.dTevaluate=8*60 +mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewBARRAGE(Zone,Heading,Angle,Radius,Altitude,Nshots) +local mission=AUFTRAG:New(AUFTRAG.Type.BARRAGE) +mission:_TargetFromObject(Zone) +mission.artyShots=Nshots +mission.artyRadius=Radius or 100 +mission.artyAltitude=Altitude +mission.artyHeading=Heading +mission.artyAngle=Angle +mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=0 +mission.missionFraction=0.0 +mission.dTevaluate=10 +mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewPATROLZONE(Zone,Speed,Altitude,Formation) +local mission=AUFTRAG:New(AUFTRAG.Type.PATROLZONE) +if type(Zone)=="string"then +Zone=ZONE:New(Zone) +end +mission:_TargetFromObject(Zone) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.PATROLZONE) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=1.0 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil +mission.categories={AUFTRAG.Category.ALL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.formation=Formation or"Off Road" +return mission +end +function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation,StayInZoneTime) +local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) +mission:_TargetFromObject(OpsZone) +mission.coalition=Coalition +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZONE) +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.StayInZoneTime=StayInZoneTime +mission.missionFraction=0.1 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil +mission.categories={AUFTRAG.Category.ALL} +mission.DCStask=mission:GetDCSMissionTask() +mission.updateDCSTask=true +local params={} +params.formation=Formation or"Off Road" +params.zone=mission:GetObjective() +params.altitude=mission.missionAltitude +params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +mission.DCStask.params=params +return mission +end +function AUFTRAG:NewARMORATTACK(Target,Speed,Formation) +local mission=AUFTRAG:NewGROUNDATTACK(Target,Speed,Formation) +mission.type=AUFTRAG.Type.ARMORATTACK +return mission +end +function AUFTRAG:NewGROUNDATTACK(Target,Speed,Formation) +local mission=AUFTRAG:New(AUFTRAG.Type.GROUNDATTACK) +mission:_TargetFromObject(Target) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.GROUNDATTACK) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.optionFormation="On Road" +mission.missionFraction=0.70 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.categories={AUFTRAG.Category.GROUND} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee +return mission +end +function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) +local mission=AUFTRAG:New(AUFTRAG.Type.RECON) +mission:_TargetFromObject(ZoneSet) +if ZoneSet:IsInstanceOf("SET_ZONE")then +mission.missionZoneSet=ZoneSet +elseif ZoneSet:IsInstanceOf("ZONE_BASE")then +mission.missionZoneSet=SET_ZONE:New() +mission.missionZoneSet:AddZone(ZoneSet) +end +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.RECON) +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionROT=ENUMS.ROT.PassiveDefense +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.5 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or UTILS.FeetToMeters(2000) +mission.categories={AUFTRAG.Category.ALL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.adinfinitum=Adinfinitum +mission.DCStask.params.randomly=Randomly +mission.DCStask.params.formation=Formation +return mission +end +function AUFTRAG:NewAMMOSUPPLY(Zone) +local mission=AUFTRAG:New(AUFTRAG.Type.AMMOSUPPLY) +mission:_TargetFromObject(Zone) +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=1.0 +mission.missionWaypointRadius=0 +mission.categories={AUFTRAG.Category.GROUND} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewFUELSUPPLY(Zone) +local mission=AUFTRAG:New(AUFTRAG.Type.FUELSUPPLY) +mission:_TargetFromObject(Zone) +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=1.0 +mission.categories={AUFTRAG.Category.GROUND} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewREARMING(Zone) +local mission=AUFTRAG:New(AUFTRAG.Type.REARMING) +mission:_TargetFromObject(Zone) +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=1.0 +mission.missionWaypointRadius=0 +mission.categories={AUFTRAG.Category.GROUND} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewALERT5(MissionType) +local mission=AUFTRAG:New(AUFTRAG.Type.ALERT5) +mission.missionTask=self:GetMissionTaskforMissionType(MissionType) +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionROT=ENUMS.ROT.NoReaction +mission.alert5MissionType=MissionType +mission.missionFraction=1.0 +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewONGUARD(Coordinate) +local mission=AUFTRAG:New(AUFTRAG.Type.ONGUARD) +mission:_TargetFromObject(Coordinate) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=1.0 +mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewAIRDEFENSE(Zone) +local mission=AUFTRAG:New(AUFTRAG.Type.AIRDEFENSE) +mission:_TargetFromObject(Zone) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=1.0 +mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewEWR(Zone) +local mission=AUFTRAG:New(AUFTRAG.Type.EWR) +mission:_TargetFromObject(Zone) +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=1.0 +mission.categories={AUFTRAG.Category.GROUND} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:_NewRELOCATECOHORT(Legion,Cohort) +local mission=AUFTRAG:New(AUFTRAG.Type.RELOCATECOHORT) +mission:_TargetFromObject(Legion.spawnzone) +mission.optionROE=ENUMS.ROE.ReturnFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.0 +mission.categories={AUFTRAG.Category.ALL} +mission.DCStask=mission:GetDCSMissionTask() +if Cohort.isGround then +mission.optionFormation=ENUMS.Formation.Vehicle.OnRoad +end +mission.DCStask.params.legion=Legion +mission.DCStask.params.cohort=Cohort +return mission +end +function AUFTRAG:NewNOTHING(RelaxZone) +local mission=AUFTRAG:New(AUFTRAG.Type.NOTHING) +mission:_TargetFromObject(RelaxZone) +mission.optionROE=ENUMS.ROE.WeaponHold +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=1.0 +mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewARMOREDGUARD(Coordinate,Formation) +local mission=AUFTRAG:New(AUFTRAG.Type.ARMOREDGUARD) +mission:_TargetFromObject(Coordinate) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.optionFormation=Formation or"On Road" +mission.missionFraction=1.0 +mission.categories={AUFTRAG.Category.GROUND} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewFromTarget(Target,MissionType) +local mission=nil +if MissionType==AUFTRAG.Type.ANTISHIP then +mission=self:NewANTISHIP(Target,Altitude) +elseif MissionType==AUFTRAG.Type.ARTY then +mission=self:NewARTY(Target,Nshots,Radius) +elseif MissionType==AUFTRAG.Type.BAI then +mission=self:NewBAI(Target,Altitude) +elseif MissionType==AUFTRAG.Type.BOMBCARPET then +mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) +elseif MissionType==AUFTRAG.Type.BOMBING then +mission=self:NewBOMBING(Target,Altitude) +elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then +mission=self:NewBOMBRUNWAY(Target,Altitude) +elseif MissionType==AUFTRAG.Type.STRAFING then +mission=self:NewSTRAFING(Target,Altitude) +elseif MissionType==AUFTRAG.Type.CAS then +mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) +elseif MissionType==AUFTRAG.Type.CASENHANCED then +mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +elseif MissionType==AUFTRAG.Type.INTERCEPT then +mission=self:NewINTERCEPT(Target) +elseif MissionType==AUFTRAG.Type.SEAD then +mission=self:NewSEAD(Target,Altitude) +elseif MissionType==AUFTRAG.Type.STRIKE then +mission=self:NewSTRIKE(Target,Altitude) +elseif MissionType==AUFTRAG.Type.ARMORATTACK then +mission=self:NewARMORATTACK(Target,Speed) +elseif MissionType==AUFTRAG.Type.GROUNDATTACK then +mission=self:NewGROUNDATTACK(Target,Speed,Formation) +else +return nil +end +return mission +end +function AUFTRAG:_DetermineAuftragType(Target) +local group=nil +local airbase=nil +local scenery=nil +local coordinate=nil +local auftrag=nil +if Target:IsInstanceOf("GROUP")then +group=Target +elseif Target:IsInstanceOf("UNIT")then +group=Target:GetGroup() +elseif Target:IsInstanceOf("AIRBASE")then +airbase=Target +elseif Target:IsInstanceOf("SCENERY")then +scenery=Target +end +if group then +local category=group:GetCategory() +local attribute=group:GetAttribute() +if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then +auftrag=AUFTRAG.Type.INTERCEPT +elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then +if attribute==GROUP.Attribute.GROUND_SAM then +auftrag=AUFTRAG.Type.SEAD +elseif attribute==GROUP.Attribute.GROUND_AAA then +auftrag=AUFTRAG.Type.BAI +elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then +auftrag=AUFTRAG.Type.BAI +elseif attribute==GROUP.Attribute.GROUND_INFANTRY then +auftrag=AUFTRAG.Type.CAS +elseif attribute==GROUP.Attribute.GROUND_TANK then +auftrag=AUFTRAG.Type.BAI +else +auftrag=AUFTRAG.Type.BAI +end +elseif category==Group.Category.SHIP then +auftrag=AUFTRAG.Type.ANTISHIP +else +self:T(self.lid.."ERROR: Unknown Group category!") +end +elseif airbase then +auftrag=AUFTRAG.Type.BOMBRUNWAY +elseif scenery then +auftrag=AUFTRAG.Type.STRIKE +elseif coordinate then +auftrag=AUFTRAG.Type.BOMBING +end +return auftrag +end +function AUFTRAG:NewAUTO(EngageGroup) +local mission=nil +local Target=EngageGroup +local auftrag=self:_DetermineAuftragType(EngageGroup) +if auftrag==AUFTRAG.Type.ANTISHIP then +mission=AUFTRAG:NewANTISHIP(Target) +elseif auftrag==AUFTRAG.Type.ARTY then +mission=AUFTRAG:NewARTY(Target) +elseif auftrag==AUFTRAG.Type.AWACS then +mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) +elseif auftrag==AUFTRAG.Type.BAI then +mission=AUFTRAG:NewBAI(Target,Altitude) +elseif auftrag==AUFTRAG.Type.BOMBING then +mission=AUFTRAG:NewBOMBING(Target,Altitude) +elseif auftrag==AUFTRAG.Type.BOMBRUNWAY then +mission=AUFTRAG:NewBOMBRUNWAY(Airdrome,Altitude) +elseif auftrag==AUFTRAG.Type.BOMBCARPET then +mission=AUFTRAG:NewBOMBCARPET(Target,Altitude,CarpetLength) +elseif auftrag==AUFTRAG.Type.CAP then +mission=AUFTRAG:NewCAP(ZoneCAP,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) +elseif auftrag==AUFTRAG.Type.CAS then +mission=AUFTRAG:NewCAS(ZoneCAS,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) +elseif auftrag==AUFTRAG.Type.ESCORT then +mission=AUFTRAG:NewESCORT(EscortGroup,OffsetVector,EngageMaxDistance,TargetTypes) +elseif auftrag==AUFTRAG.Type.FACA then +mission=AUFTRAG:NewFACA(Target,Designation,DataLink,Frequency,Modulation) +elseif auftrag==AUFTRAG.Type.FERRY then +elseif auftrag==AUFTRAG.Type.GCICAP then +mission=AUFTRAG:NewGCICAP(Coordinate,Altitude,Speed,Heading,Leg) +elseif auftrag==AUFTRAG.Type.INTERCEPT then +mission=AUFTRAG:NewINTERCEPT(Target) +elseif auftrag==AUFTRAG.Type.ORBIT then +mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg) +elseif auftrag==AUFTRAG.Type.RECON then +mission=AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) +elseif auftrag==AUFTRAG.Type.RESCUEHELO then +mission=AUFTRAG:NewRESCUEHELO(Carrier) +elseif auftrag==AUFTRAG.Type.SEAD then +mission=AUFTRAG:NewSEAD(Target,Altitude) +elseif auftrag==AUFTRAG.Type.STRIKE then +mission=AUFTRAG:NewSTRIKE(Target,Altitude) +elseif auftrag==AUFTRAG.Type.TANKER then +mission=AUFTRAG:NewTANKER(Coordinate,Altitude,Speed,Heading,Leg,RefuelSystem) +elseif auftrag==AUFTRAG.Type.TROOPTRANSPORT then +mission=AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet,DropoffCoordinate,PickupCoordinate) +elseif auftrag==AUFTRAG.Type.PATROLRACETRACK then +mission=AUFTRAG:NewPATROL_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg,Formation) +else +end +if mission then +mission:SetPriority(10,true) +end +return mission +end +function AUFTRAG:SetTime(ClockStart,ClockStop) +local Tnow=timer.getAbsTime() +local Tstart=Tnow+5 +if ClockStart and type(ClockStart)=="number"then +Tstart=Tnow+ClockStart +elseif ClockStart and type(ClockStart)=="string"then +Tstart=UTILS.ClockToSeconds(ClockStart) +end +local Tstop=nil +if ClockStop and type(ClockStop)=="number"then +Tstop=Tnow+ClockStop +elseif ClockStop and type(ClockStop)=="string"then +Tstop=UTILS.ClockToSeconds(ClockStop) +end +self.Tstart=Tstart +self.Tstop=Tstop +if Tstop then +self.duration=self.Tstop-self.Tstart +end +return self +end +function AUFTRAG:SetDuration(Duration) +self.durationExe=Duration +return self +end +function AUFTRAG:SetTeleport(Switch) +if Switch==nil then +Switch=true +end +self.teleport=Switch +return self +end +function AUFTRAG:SetReturnToLegion(Switch) +self.legionReturn=Switch +self:T(self.lid..string.format("Setting ReturnToLetion=%s",tostring(self.legionReturn))) +return self +end +function AUFTRAG:SetPushTime(ClockPush) +if ClockPush then +if type(ClockPush)=="string"then +self.Tpush=UTILS.ClockToSeconds(ClockPush) +elseif type(ClockPush)=="number"then +self.Tpush=timer.getAbsTime()+ClockPush +end +end +return self +end +function AUFTRAG:SetPriority(Prio,Urgent,Importance) +self.prio=Prio or 50 +self.urgent=Urgent +self.importance=Importance +return self +end +function AUFTRAG:SetRepeat(Nrepeat) +self.Nrepeat=Nrepeat or 0 +return self +end +function AUFTRAG:SetRepeatDelay(RepeatDelay) +self.repeatDelay=RepeatDelay +return self +end +function AUFTRAG:SetRepeatOnFailure(Nrepeat) +self.NrepeatFailure=Nrepeat or 0 +return self +end +function AUFTRAG:SetRepeatOnSuccess(Nrepeat) +self.NrepeatSuccess=Nrepeat or 0 +return self +end +function AUFTRAG:SetReinforce(Nreinforce) +self.reinforce=Nreinforce +return self +end +function AUFTRAG:SetRequiredAssets(NassetsMin,NassetsMax) +self.NassetsMin=NassetsMin or 1 +self.NassetsMax=NassetsMax or self.NassetsMin +if self.NassetsMax0 then +local N=self:CountOpsGroups() +if N Nmin=%d",self.NassetsMin,N,self.reinforce,Nmin)) +end +end +end +return Nmin,Nmax +end +function AUFTRAG:SetAssetsStayAlive(Switch) +if Switch==nil then +Switch=true +end +self.assetStayAlive=Switch +return self +end +function AUFTRAG:SetRequiredEscorts(NescortMin,NescortMax,MissionType,TargetTypes,EngageRange) +self.NescortMin=NescortMin or 1 +self.NescortMax=NescortMax or self.NescortMin +if self.NescortMaxself.Tstop then +return false +end +local startme=self:EvalConditionsAll(self.conditionStart) +if not startme then +return false +end +return true +end +function AUFTRAG:IsReadyToCancel() +local Tnow=timer.getAbsTime() +if self.Tstop and Tnow>=self.Tstop then +return true +end +local failure=self:EvalConditionsAny(self.conditionFailure) +if failure then +self.failurecondition=true +return true +end +local success=self:EvalConditionsAny(self.conditionSuccess) +if success then +self.successcondition=true +return true +end +return false +end +function AUFTRAG:IsReadyToPush() +local Tnow=timer.getAbsTime() +if self.Tpush and Tnow<=self.Tpush then +return false +end +local push=self:EvalConditionsAll(self.conditionPush) +return push +end +function AUFTRAG:EvalConditionsAll(Conditions) +for _,_condition in pairs(Conditions or{})do +local condition=_condition +local istrue=condition.func(unpack(condition.arg)) +if not istrue then +return false +end +end +return true +end +function AUFTRAG:EvalConditionsAny(Conditions) +for _,_condition in pairs(Conditions or{})do +local condition=_condition +local istrue=condition.func(unpack(condition.arg)) +if istrue then +return true +end +end +return false +end +function AUFTRAG:onafterStatus(From,Event,To) +local Tnow=timer.getAbsTime() +if self.escortGroupName then +local group=GROUP:FindByName(self.escortGroupName) +if group and group:IsAlive()then +self:T(self.lid..string.format("ESCORT group %s is now alive. Updating DCS task and adding group to TARGET",tostring(self.escortGroupName))) +self.engageTarget:AddObject(group) +self.DCStask=self:GetDCSMissionTask() +self.escortGroupName=nil +end +end +local Ntargets=self:CountMissionTargets() +local Ntargets0=self:GetTargetInitialNumber() +local Ngroups=self:CountOpsGroups() +local Nassigned=self.Nassigned and self.Nassigned-self.Ndead or 0 +local conditionDone=false +if self.conditionFailureSet then +conditionDone=self:EvalConditionsAny(self.conditionFailure) +end +if self.conditionSuccessSet and not conditionDone then +conditionDone=self:EvalConditionsAny(self.conditionSuccess) +end +if self:IsNotOver()then +if self:CheckGroupsDone()then +self:Done() +elseif(self.Tstop and Tnow>self.Tstop+10)then +self:Cancel() +elseif conditionDone then +self:Cancel() +elseif self.durationExe and self.Texecuting and Tnow-self.Texecuting>self.durationExe then +local Nrepeat=self.Nrepeat +local NrepeatS=self.NrepeatSuccess +local NrepeatF=self.NrepeatFailure +self:Cancel() +self.Nrepeat=Nrepeat +self.NrepeatSuccess=NrepeatS +self.NrepeatFailure=NrepeatF +elseif(Ntargets0>0 and Ntargets==0)then +self:T(self.lid.."No targets left cancelling mission!") +self:Cancel() +elseif self:IsExecuting()and self:_IsNotReinforcing()then +if Ngroups==0 then +self:Done() +else +local done=true +for groupname,data in pairs(self.groupdata or{})do +local groupdata=data +local opsgroup=groupdata.opsgroup +if opsgroup:IsAlive()then +done=false +end +end +if done then +self:Done() +end +end +end +end +local fsmstate=self:GetState() +if fsmstate~=self.status then +self:T(self.lid..string.format("ERROR: FSM state %s != %s mission status!",fsmstate,self.status)) +end +if self.verbose>=1 then +local Cstart=UTILS.SecondsToClock(self.Tstart,true) +local Cstop=self.Tstop and UTILS.SecondsToClock(self.Tstop,true)or"INF" +local targetname=self:GetTargetName()or"unknown" +local Nlegions=#self.legions +local commander=self.commander and self.statusCommander or"N/A" +local chief=self.chief and self.statusChief or"N/A" +self:T(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, legions=%d, commander=%s, chief=%s", +self.status,targetname,Cstart,Cstop,#self.assets,Ngroups,Ntargets,Nlegions,commander,chief)) +end +if self.verbose>=2 then +local text="Group data:" +for groupname,_groupdata in pairs(self.groupdata)do +local groupdata=_groupdata +text=text..string.format("\n- %s: status mission=%s opsgroup=%s",groupname,groupdata.status,groupdata.opsgroup and groupdata.opsgroup:GetState()or"N/A") +end +self:I(self.lid..text) +end +if self.verbose>=3 then +local text=string.format("Assets [N=%d,Nassigned=%s, Ndead=%s]:",self.Nassets or 0,self.Nassigned or 0,self.Ndead or 0) +for i,_asset in pairs(self.assets or{})do +local asset=_asset +text=text..string.format("\n[%d] %s: spawned=%s, requested=%s, reserved=%s",i,asset.spawngroupname,tostring(asset.spawned),tostring(asset.requested),tostring(asset.reserved)) +end +self:I(self.lid..text) +end +local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false +if self:IsOver()and ready2evaluate then +self:Evaluate() +else +self:__Status(-30) +end +if self.markerOn then +self:UpdateMarker() +end +end +function AUFTRAG:Evaluate() +local failed=false +local targetdamage=self:GetTargetDamage() +local owndamage=self.Ncasualties/self.Nelements*100 +local Ntargets=self:CountMissionTargets() +local Ntargets0=self:GetTargetInitialNumber() +local Life=self:GetTargetLife() +local Life0=self:GetTargetInitialLife() +if Ntargets0>0 then +if self.type==AUFTRAG.Type.TROOPTRANSPORT or self.type==AUFTRAG.Type.ESCORT then +if Ntargets0 then +failed=true +end +end +else +if self.Nelements==self.Ncasualties then +failed=true +end +end +local successCondition=self:EvalConditionsAny(self.conditionSuccess) +local failureCondition=self:EvalConditionsAny(self.conditionFailure) +if failureCondition then +failed=true +elseif successCondition then +failed=false +end +if self.verbose>0 then +local text=string.format("Evaluating mission:\n") +text=text..string.format("Own casualties = %d/%d\n",self.Ncasualties,self.Nelements) +text=text..string.format("Own losses = %.1f %%\n",owndamage) +text=text..string.format("Killed units = %d\n",self.Nkills) +text=text..string.format("--------------------------\n") +text=text..string.format("Targets left = %d/%d\n",Ntargets,Ntargets0) +text=text..string.format("Targets life = %.1f/%.1f\n",Life,Life0) +text=text..string.format("Enemy losses = %.1f %%\n",targetdamage) +text=text..string.format("--------------------------\n") +text=text..string.format("Success Cond = %s\n",tostring(successCondition)) +text=text..string.format("Failure Cond = %s\n",tostring(failureCondition)) +text=text..string.format("--------------------------\n") +text=text..string.format("Final Success = %s\n",tostring(not failed)) +text=text..string.format("=========================") +self:I(self.lid..text) +end +if failed then +self:I(self.lid..string.format("Mission %d [%s] failed!",self.auftragsnummer,self.type)) +if self.chief then +self.chief.Nfailure=self.chief.Nfailure+1 +end +self:Failed() +else +self:I(self.lid..string.format("Mission %d [%s] success!",self.auftragsnummer,self.type)) +if self.chief then +self.chief.Nsuccess=self.chief.Nsuccess+1 +end +self:Success() +end +return self +end +function AUFTRAG:GetOpsGroups() +local opsgroups={} +for _,_groupdata in pairs(self.groupdata or{})do +local groupdata=_groupdata +table.insert(opsgroups,groupdata.opsgroup) +end +return opsgroups +end +function AUFTRAG:GetAssetDataByName(AssetName) +return self.groupdata[tostring(AssetName)] +end +function AUFTRAG:GetGroupData(opsgroup) +if opsgroup and self.groupdata then +return self.groupdata[opsgroup.groupname] +end +return nil +end +function AUFTRAG:SetGroupStatus(opsgroup,status) +local oldstatus=self:GetGroupStatus(opsgroup) +self:T(self.lid..string.format("Setting OPSGROUP %s to status %s-->%s",opsgroup and opsgroup.groupname or"nil",tostring(oldstatus),tostring(status))) +if oldstatus==AUFTRAG.GroupStatus.CANCELLED and status==AUFTRAG.GroupStatus.DONE then +else +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +groupdata.status=status +else +self:T(self.lid.."WARNING: Could not SET flight data for flight group. Setting status to DONE") +end +end +local isNotOver=self:IsNotOver() +local groupsDone=self:CheckGroupsDone() +self:T2(self.lid..string.format("Setting OPSGROUP %s status to %s. IsNotOver=%s CheckGroupsDone=%s",opsgroup.groupname,self:GetGroupStatus(opsgroup),tostring(self:IsNotOver()),tostring(groupsDone))) +if isNotOver and groupsDone then +self:T3(self.lid.."All assigned OPSGROUPs done ==> mission DONE!") +self:Done() +else +self:T3(self.lid.."Mission NOT DONE yet!") +end +return self +end +function AUFTRAG:GetGroupStatus(opsgroup) +self:T3(self.lid..string.format("Trying to get Flight status for flight group %s",opsgroup and opsgroup.groupname or"nil")) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +return groupdata.status +else +self:T(self.lid..string.format("WARNING: Could not GET groupdata for opsgroup %s. Returning status DONE.",opsgroup and opsgroup.groupname or"nil")) +return AUFTRAG.GroupStatus.DONE +end +end +function AUFTRAG:AddLegion(Legion) +self:T(self.lid..string.format("Adding legion %s",Legion.alias)) +table.insert(self.legions,Legion) +return self +end +function AUFTRAG:RemoveLegion(Legion) +for i=#self.legions,1,-1 do +local legion=self.legions[i] +if legion.alias==Legion.alias then +self:T(self.lid..string.format("Removing legion %s",Legion.alias)) +table.remove(self.legions,i) +self.statusLegion[Legion.alias]=nil +return self +end +end +self:T(self.lid..string.format("ERROR: Legion %s not found and could not be removed!",Legion.alias)) +return self +end +function AUFTRAG:SetLegionStatus(Legion,Status) +local status=self:GetLegionStatus(Legion) +self:T(self.lid..string.format("Setting LEGION %s to status %s-->%s",Legion.alias,tostring(status),tostring(Status))) +self.statusLegion[Legion.alias]=Status +return self +end +function AUFTRAG:GetLegionStatus(Legion) +local status=self.statusLegion[Legion.alias]or"unknown" +return status +end +function AUFTRAG:SetGroupWaypointCoordinate(opsgroup,coordinate) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +groupdata.waypointcoordinate=coordinate +end +return self +end +function AUFTRAG:SetIngressCoordinate(coordinate) +self.missionIngressCoord=coordinate +self.missionIngressCoordAlt=UTILS.MetersToFeet(coordinate.y)or 10000 +return self +end +function AUFTRAG:GetGroupWaypointCoordinate(opsgroup) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +return groupdata.waypointcoordinate +end +end +function AUFTRAG:SetGroupWaypointTask(opsgroup,task) +self:T2(self.lid..string.format("Setting waypoint task %s",task and task.description or"WTF")) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +groupdata.waypointtask=task +end +end +function AUFTRAG:GetGroupWaypointTask(opsgroup) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +return groupdata.waypointtask +end +end +function AUFTRAG:SetGroupWaypointIndex(opsgroup,waypointindex) +self:T2(self.lid..string.format("Setting Mission waypoint UID=%d",waypointindex)) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +groupdata.waypointindex=waypointindex +end +return self +end +function AUFTRAG:GetGroupWaypointIndex(opsgroup) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +return groupdata.waypointindex +end +end +function AUFTRAG:SetGroupEgressWaypointUID(opsgroup,waypointindex) +self:T2(self.lid..string.format("Setting Egress waypoint UID=%d",waypointindex)) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +groupdata.waypointEgressUID=waypointindex +end +return self +end +function AUFTRAG:GetGroupEgressWaypointUID(opsgroup) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +return groupdata.waypointEgressUID +end +end +function AUFTRAG:CheckGroupsDone() +local fsmState=self:GetState() +for groupname,data in pairs(self.groupdata)do +local groupdata=data +if groupdata then +if not(groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED)then +self:T2(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!",groupdata.opsgroup.groupname,groupdata.status:upper())) +return false +end +end +end +for _,_legion in pairs(self.legions)do +local legion=_legion +local status=self:GetLegionStatus(legion) +if not status==AUFTRAG.Status.CANCELLED then +self:T2(self.lid..string.format("CheckGroupsDone: LEGION %s is not CANCELLED but in state %s. Mission NOT DONE!",legion.alias,status)) +return false +end +end +if self.commander then +if not self.statusCommander==AUFTRAG.Status.CANCELLED then +self:T2(self.lid..string.format("CheckGroupsDone: COMMANDER is not CANCELLED but in state %s. Mission NOT DONE!",self.statusCommander)) +return false +end +end +if self.chief then +if not self.statusChief==AUFTRAG.Status.CANCELLED then +self:T2(self.lid..string.format("CheckGroupsDone: CHIEF is not CANCELLED but in state %s. Mission NOT DONE!",self.statusChief)) +return false +end +end +if self:IsPlanned()or self:IsQueued()or self:IsRequested()then +self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!",self.status,self:GetState())) +return false +end +if self:IsExecuting()and self:_IsReinforcing()then +self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] and reinfoce=%d. Mission NOT DONE!",self.status,self:GetState(),self.reinforce)) +return false +end +if self:IsStarted()and self:CountOpsGroups()==0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] but count of alive OPSGROUP is zero. Mission DONE!",self.status,self:GetState())) +return true +end +if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and self:CountOpsGroups()>0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) +return false +end +return true +end +function AUFTRAG:OnEventUnitLost(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit then +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +for _,_groupdata in pairs(self.groupdata)do +local groupdata=_groupdata +if groupdata and groupdata.opsgroup and groupdata.opsgroup.groupname==EventData.IniGroupName then +self:T(self.lid..string.format("UNIT LOST event for opsgroup %s unit %s",groupdata.opsgroup.groupname,EventData.IniUnitName)) +end +end +end +end +function AUFTRAG:onafterPlanned(From,Event,To) +self.status=AUFTRAG.Status.PLANNED +self:T(self.lid..string.format("New mission status=%s",self.status)) +end +function AUFTRAG:onafterQueued(From,Event,To,Airwing) +self.status=AUFTRAG.Status.QUEUED +self:T(self.lid..string.format("New mission status=%s",self.status)) +end +function AUFTRAG:onafterRequested(From,Event,To) +self.status=AUFTRAG.Status.REQUESTED +self:T(self.lid..string.format("New mission status=%s",self.status)) +end +function AUFTRAG:onafterAssign(From,Event,To) +self.status=AUFTRAG.Status.ASSIGNED +self:T(self.lid..string.format("New mission status=%s",self.status)) +end +function AUFTRAG:onafterScheduled(From,Event,To) +self.status=AUFTRAG.Status.SCHEDULED +self:T(self.lid..string.format("New mission status=%s",self.status)) +end +function AUFTRAG:onafterStarted(From,Event,To) +self.status=AUFTRAG.Status.STARTED +self.Tstarted=timer.getAbsTime() +self:T(self.lid..string.format("New mission status=%s",self.status)) +end +function AUFTRAG:onafterExecuting(From,Event,To) +self.status=AUFTRAG.Status.EXECUTING +self.Texecuting=timer.getAbsTime() +self:T(self.lid..string.format("New mission status=%s",self.status)) +end +function AUFTRAG:onafterElementDestroyed(From,Event,To,OpsGroup,Element) +self.Ncasualties=self.Ncasualties+1 +end +function AUFTRAG:onafterGroupDead(From,Event,To,OpsGroup) +local asset=self:GetAssetByName(OpsGroup.groupname) +if asset then +self:AssetDead(asset) +end +self.Ndead=self.Ndead+1 +end +function AUFTRAG:onafterAssetDead(From,Event,To,Asset) +local N=self:CountOpsGroups() +local notreinforcing=self:_IsNotReinforcing() +self:T(self.lid..string.format("Asset %s dead! Number of ops groups remaining %d (reinforcing=%s)",tostring(Asset.spawngroupname),N,tostring(not notreinforcing))) +if N==0 and notreinforcing then +if self:IsNotOver()then +self:Cancel() +else +end +end +self:DelAsset(Asset) +end +function AUFTRAG:onafterCancel(From,Event,To) +local Ngroups=self:CountOpsGroups() +self:T(self.lid..string.format("CANCELLING mission in status %s. Will wait for %d groups to report mission DONE before evaluation",self.status,Ngroups)) +self.Tover=timer.getAbsTime() +self.Nrepeat=self.repeated +self.NrepeatFailure=self.repeatedFailure +self.NrepeatSuccess=self.repeatedSuccess +self.dTevaluate=0 +if self.chief then +self:T(self.lid..string.format("CHIEF will cancel the mission. Will wait for mission DONE before evaluation!")) +self.chief:MissionCancel(self) +elseif self.commander then +self:T(self.lid..string.format("COMMANDER will cancel the mission. Will wait for mission DONE before evaluation!")) +self.commander:MissionCancel(self) +elseif self.legions and#self.legions>0 then +for _,_legion in pairs(self.legions or{})do +local legion=_legion +self:T(self.lid..string.format("LEGION %s will cancel the mission. Will wait for mission DONE before evaluation!",legion.alias)) +legion:MissionCancel(self) +end +else +self:T(self.lid..string.format("No legion, commander or chief. Attached groups will cancel the mission on their own. Will wait for mission DONE before evaluation!")) +for _,_groupdata in pairs(self.groupdata or{})do +local groupdata=_groupdata +groupdata.opsgroup:MissionCancel(self) +end +end +if self:IsPlanned()or self:IsQueued()or self:IsRequested()or Ngroups==0 then +self:T(self.lid..string.format("Cancelled mission was in %s stage with %d groups assigned and alive. Call it done!",self.status,Ngroups)) +self:Done() +end +end +function AUFTRAG:onafterDone(From,Event,To) +self.status=AUFTRAG.Status.DONE +self:T(self.lid..string.format("New mission status=%s",self.status)) +self.Tover=timer.getAbsTime() +self.Texecuting=nil +self.statusChief=AUFTRAG.Status.DONE +self.statusCommander=AUFTRAG.Status.DONE +for _,_legion in pairs(self.legions)do +local Legion=_legion +self:SetLegionStatus(Legion,AUFTRAG.Status.DONE) +if self.type==AUFTRAG.Type.RELOCATECOHORT then +local requestid=self.requestID[Legion.alias] +if requestid then +self:T(self.lid.."Removing request from pending queue") +Legion:_DeleteQueueItemByID(requestid,Legion.pending) +local Cohort=self.DCStask.params.cohort +Legion:DelCohort(Cohort) +else +self:E(self.lid.."WARNING: Could NOT remove relocation request from from pending queue (all assets were spawned?)") +end +end +end +if self.type==AUFTRAG.Type.RELOCATECOHORT then +local cohort=self.DCStask.params.cohort +cohort:Relocated() +end +end +function AUFTRAG:onafterSuccess(From,Event,To) +self.status=AUFTRAG.Status.SUCCESS +self:T(self.lid..string.format("New mission status=%s",self.status)) +self.statusChief=self.status +self.statusCommander=self.status +for _,_legion in pairs(self.legions)do +local Legion=_legion +self:SetLegionStatus(Legion,self.status) +end +local repeatme=self.repeatedSuccess Repeat mission!",self.repeated+1,N)) +self:__Repeat(self.repeatDelay) +else +self:T(self.lid..string.format("Mission SUCCESS! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1)) +self:Stop() +end +end +function AUFTRAG:onafterFailed(From,Event,To) +self.status=AUFTRAG.Status.FAILED +self:T(self.lid..string.format("New mission status=%s",self.status)) +self.statusChief=self.status +self.statusCommander=self.status +for _,_legion in pairs(self.legions)do +local Legion=_legion +self:SetLegionStatus(Legion,self.status) +end +local repeatme=self.repeatedFailure Repeat mission!",self.repeated+1,N)) +self:__Repeat(self.repeatDelay) +else +self:T(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1)) +self:Stop() +end +end +function AUFTRAG:onbeforeRepeat(From,Event,To) +if not(self.chief or self.commander or#self.legions>0)then +self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, COMMANDER or LEGION! Stopping AUFTRAG") +self:Stop() +return false +end +return true +end +function AUFTRAG:onafterRepeat(From,Event,To) +self.status=AUFTRAG.Status.PLANNED +self:T(self.lid..string.format("New mission status=%s (on Repeat)",self.status)) +self.statusChief=self.status +self.statusCommander=self.status +for _,_legion in pairs(self.legions)do +local Legion=_legion +self:SetLegionStatus(Legion,self.status) +end +self.repeated=self.repeated+1 +if self.chief then +self.statusChief=AUFTRAG.Status.PLANNED +if self.commander then +self.statusCommander=AUFTRAG.Status.PLANNED +end +for _,_legion in pairs(self.legions)do +local legion=_legion +legion:RemoveMission(self) +end +elseif self.commander then +self.statusCommander=AUFTRAG.Status.PLANNED +for _,_legion in pairs(self.legions)do +local legion=_legion +legion:RemoveMission(self) +self:SetLegionStatus(legion,AUFTRAG.Status.PLANNED) +end +elseif#self.legions>0 then +for _,_legion in pairs(self.legions)do +local legion=_legion +legion:RemoveMission(self) +self:SetLegionStatus(legion,AUFTRAG.Status.PLANNED) +legion:AddMission(self) +end +else +self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, COMMANDER or LEGION! Stopping AUFTRAG") +self:Stop() +return +end +self.assets={} +for groupname,_groupdata in pairs(self.groupdata)do +local groupdata=_groupdata +local opsgroup=groupdata.opsgroup +if opsgroup then +self:DelOpsGroup(opsgroup) +end +end +self.groupdata={} +self.Ncasualties=0 +self.Nelements=0 +self.Ngroups=0 +self.Nassigned=nil +self.Ndead=0 +self.DCStask=self:GetDCSMissionTask() +self:__Status(-30) +end +function AUFTRAG:onafterStop(From,Event,To) +self:T(self.lid..string.format("STOPPED mission in status=%s. Removing missions from queues. Stopping CallScheduler!",self.status)) +if self.chief then +self.chief:RemoveMission(self) +end +if self.commander then +self.commander:RemoveMission(self) +end +if#self.legions>0 then +for _,_legion in pairs(self.legions)do +local legion=_legion +legion:RemoveMission(self) +end +end +for _,_groupdata in pairs(self.groupdata)do +local groupdata=_groupdata +groupdata.opsgroup:RemoveMission(self) +end +self.assets={} +self.groupdata={} +self.CallScheduler:Clear() +end +function AUFTRAG:_TargetFromObject(Object) +if not self.engageTarget then +if Object and Object:IsInstanceOf("TARGET")then +self.engageTarget=Object +else +self.engageTarget=TARGET:New(Object) +end +else +end +return self +end +function AUFTRAG:CountMissionTargets() +local N=0 +local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil +if self.engageTarget then +N=self.engageTarget:CountTargets(Coalitions) +end +return N +end +function AUFTRAG:GetTargetInitialNumber() +local target=self:GetTargetData() +if target then +return target.N0 +else +return 0 +end +end +function AUFTRAG:GetTargetInitialLife() +local target=self:GetTargetData() +if target then +return target.life0 +else +return 0 +end +end +function AUFTRAG:GetTargetDamage() +local target=self:GetTargetData() +if target then +return target:GetDamage() +else +return 0 +end +end +function AUFTRAG:GetTargetLife() +local target=self:GetTargetData() +if target then +return target:GetLife() +else +return 0 +end +end +function AUFTRAG:GetTargetData() +return self.engageTarget +end +function AUFTRAG:GetObjective(RefCoordinate,Coalitions) +local objective=self:GetTargetData():GetObject(RefCoordinate,Coalitions) +return objective +end +function AUFTRAG:GetTargetType() +local target=self.engageTarget +if target then +local to=target:GetObjective() +if to then +return to.Type +else +return"Unknown" +end +else +return"Unknown" +end +end +function AUFTRAG:GetTargetVec2() +local coord=self:GetTargetCoordinate() +if coord then +local vec2=coord:GetVec2() +return vec2 +end +return nil +end +function AUFTRAG:GetTargetCoordinate() +if self.transportPickup then +return self.transportPickup +elseif self.missionZoneSet and self.type==AUFTRAG.Type.RECON then +return self.missionZoneSet:GetAverageCoordinate() +elseif self.engageTarget then +local coord=self.engageTarget:GetCoordinate() +return coord +elseif self.type==AUFTRAG.Type.ALERT5 then +return nil +else +self:T(self.lid.."ERROR: Cannot get target coordinate!") +end +return nil +end +function AUFTRAG:GetTargetHeading() +if self.engageTarget then +local heading=self.engageTarget:GetHeading() +return heading +end +return nil +end +function AUFTRAG:GetTargetName() +if self.engageTarget then +local name=self.engageTarget:GetName() +return name +end +return"N/A" +end +function AUFTRAG:GetTargetDistance(FromCoord) +local TargetCoord=self:GetTargetCoordinate() +if TargetCoord and FromCoord then +return TargetCoord:Get2DDistance(FromCoord) +else +self:T(self.lid.."ERROR: TargetCoord or FromCoord does not exist in AUFTRAG:GetTargetDistance() function! Returning 0") +end +return 0 +end +function AUFTRAG:AddAsset(Asset) +self:T(self.lid..string.format("Adding asset \"%s\" to mission",tostring(Asset.spawngroupname))) +self.assets=self.assets or{} +local asset=self:GetAssetByName(Asset.spawngroupname) +if not asset then +table.insert(self.assets,Asset) +self.Nassigned=self.Nassigned or 0 +self.Nassigned=self.Nassigned+1 +end +return self +end +function AUFTRAG:_AddAssets(Assets) +for _,asset in pairs(Assets)do +self:AddAsset(asset) +end +return self +end +function AUFTRAG:DelAsset(Asset) +for i,_asset in pairs(self.assets or{})do +local asset=_asset +if asset.uid==Asset.uid then +self:T(self.lid..string.format("Removing asset \"%s\" from mission",tostring(Asset.spawngroupname))) +table.remove(self.assets,i) +return self +end +end +return self +end +function AUFTRAG:GetAssetByName(Name) +for i,_asset in pairs(self.assets or{})do +local asset=_asset +if asset.spawngroupname==Name then +return asset +end +end +return nil +end +function AUFTRAG:CountOpsGroups() +local N=0 +for _,_groupdata in pairs(self.groupdata)do +local groupdata=_groupdata +if groupdata and groupdata.opsgroup and groupdata.opsgroup:IsAlive()and not groupdata.opsgroup:IsDead()then +N=N+1 +end +end +return N +end +function AUFTRAG:CountOpsGroupsInStatus(Status) +local N=0 +for _,_groupdata in pairs(self.groupdata)do +local groupdata=_groupdata +if groupdata and groupdata.status==Status then +N=N+1 +end +end +return N +end +function AUFTRAG:GetMissionTypesText(MissionTypes) +local text="" +for _,missiontype in pairs(MissionTypes)do +text=text..string.format("%s, ",missiontype) +end +return text +end +function AUFTRAG:SetMissionWaypointCoord(Coordinate) +if Coordinate:IsInstanceOf("ZONE_BASE")then +Coordinate=Coordinate:GetCoordinate() +end +self.missionWaypointCoord=Coordinate +return self +end +function AUFTRAG:SetMissionWaypointRandomization(Radius) +self.missionWaypointRadius=Radius +return self +end +function AUFTRAG:SetMissionEgressCoord(Coordinate,Altitude,Speed) +if Coordinate:IsInstanceOf("ZONE_BASE")then +Coordinate=Coordinate:GetCoordinate() +end +self.missionEgressCoord=Coordinate +if Altitude then +self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude) +self.missionEgressCoordAlt=UTILS.FeetToMeters(Altitude) +end +self.missionEgressCoordSpeed=Speed and Speed or nil +return self +end +function AUFTRAG:SetMissionIngressCoord(Coordinate,Altitude,Speed) +if Coordinate:IsInstanceOf("ZONE_BASE")then +Coordinate=Coordinate:GetCoordinate() +end +self.missionIngressCoord=Coordinate +if Altitude then +self.missionIngressCoord.y=UTILS.FeetToMeters(Altitude) +self.missionIngressCoordAlt=UTILS.FeetToMeters(Altitude or 10000) +end +self.missionIngressCoordSpeed=Speed and Speed or nil +return self +end +function AUFTRAG:SetMissionHoldingCoord(Coordinate,Altitude,Speed,Duration) +if Coordinate:IsInstanceOf("ZONE_BASE")then +Coordinate=Coordinate:GetCoordinate() +end +self.missionHoldingCoord=Coordinate +self.missionHoldingDuration=Duration or 900 +if Altitude then +self.missionHoldingCoord.y=UTILS.FeetToMeters(Altitude) +self.missionHoldingCoordAlt=UTILS.FeetToMeters(Altitude or 10000) +end +self.missionHoldingCoordSpeed=Speed and Speed or nil +return self +end +function AUFTRAG:GetMissionEgressCoord() +return self.missionEgressCoord +end +function AUFTRAG:GetMissionIngressCoord() +return self.missionIngressCoord +end +function AUFTRAG:GetMissionHoldingCoord() +return self.missionHoldingCoord +end +function AUFTRAG:_GetMissionWaypointCoordSet() +if self.missionWaypointCoord then +local coord=self.missionWaypointCoord +if self.missionAltitude then +coord.y=self.missionAltitude +end +return coord +end +end +function AUFTRAG:GetMissionWaypointCoord(group,randomradius,surfacetypes) +if self.missionWaypointCoord then +local coord=self.missionWaypointCoord +if self.missionAltitude then +coord.y=self.missionAltitude +end +return coord +end +local coord=group:GetCoordinate() +if self.missionHoldingCoord then +coord=self.missionHoldingCoord +if self.missionHoldingCoorddAlt then +coord:SetAltitude(self.missionHoldingCoordAlt,true) +end +end +if self.missionIngressCoord then +coord=self.missionIngressCoord +if self.missionIngressCoordAlt then +coord:SetAltitude(self.missionIngressCoordAlt,true) +end +end +local waypointcoord=COORDINATE:New(0,0,0) +if coord then +waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(),self.missionFraction) +else +self:E(self.lid..string.format("ERROR: Cannot get coordinate of group %s (alive=%s)!",tostring(group:GetName()),tostring(group:IsAlive()))) +end +local alt=waypointcoord.y +if randomradius then +waypointcoord=ZONE_RADIUS:New("Temp",waypointcoord:GetVec2(),randomradius):GetRandomCoordinate(nil,nil,surfacetypes):SetAltitude(alt,false) +end +if self.missionAltitude then +waypointcoord:SetAltitude(self.missionAltitude,true) +end +return waypointcoord +end +function AUFTRAG:_SetLogID() +self.lid=string.format("Auftrag #%d %s | ",self.auftragsnummer,tostring(self.type)) +return self +end +function AUFTRAG:_GetRequestID(Legion) +local requestid=nil +local name=nil +if type(Legion)=="string"then +name=Legion +else +name=Legion.alias +end +if name then +requestid=self.requestID[name] +end +return nil +end +function AUFTRAG:_GetRequest(Legion) +local request=nil +local requestID=self:_GetRequestID(Legion) +if requestID then +request=Legion:GetRequestByID(requestID) +end +return request +end +function AUFTRAG:_SetRequestID(Legion,RequestID) +local requestid=nil +local name=nil +if type(Legion)=="string"then +name=Legion +else +name=Legion.alias +end +if name then +if self.requestID[name]then +self:I(self.lid..string.format("WARNING: Mission already has a request ID=%d!",self.requestID[name])) +end +self.requestID[name]=RequestID +end +return self +end +function AUFTRAG:_IsNotReinforcing() +local Nassigned=self.Nassigned and self.Nassigned-self.Ndead or 0 +local notreinforcing=((not self.reinforce)or(self.reinforce==0 and Nassigned<=0)) +return notreinforcing +end +function AUFTRAG:_IsReinforcing() +local reinforcing=not self:_IsNotReinforcing() +return reinforcing +end +function AUFTRAG:UpdateMarker() +local text=string.format("%s %s: %s",self.name,self.type:upper(),self.status:upper()) +text=text..string.format("\n%s",self:GetTargetName()) +text=text..string.format("\nTargets %d/%d, Life Points=%d/%d",self:CountMissionTargets(),self:GetTargetInitialNumber(),self:GetTargetLife(),self:GetTargetInitialLife()) +text=text..string.format("\nOpsGroups %d/%d",self:CountOpsGroups(),self:GetNumberOfRequiredAssets()) +if not self.marker then +local targetcoord=self:GetTargetCoordinate() +if targetcoord then +if self.markerCoaliton and self.markerCoaliton>=0 then +self.marker=MARKER:New(targetcoord,text):ReadOnly():ToCoalition(self.markerCoaliton) +else +self.marker=MARKER:New(targetcoord,text):ReadOnly():ToAll() +end +end +else +if self.marker:GetText()~=text then +self.marker:UpdateText(text) +end +end +return self +end +function AUFTRAG:GetDCSMissionTask() +local DCStasks={} +if self.type==AUFTRAG.Type.ANTISHIP then +local DCStask=CONTROLLABLE.EnRouteTaskAntiShip(nil) +table.insert(self.enrouteTasks,DCStask) +self:_GetDCSAttackTask(self.engageTarget,DCStasks) +elseif self.type==AUFTRAG.Type.AWACS then +local DCStask=CONTROLLABLE.EnRouteTaskAWACS(nil) +table.insert(self.enrouteTasks,DCStask) +elseif self.type==AUFTRAG.Type.BAI then +self:_GetDCSAttackTask(self.engageTarget,DCStasks) +elseif self.type==AUFTRAG.Type.BOMBING then +local coords=self.engageTarget:GetCoordinates() +for _,coord in pairs(coords)do +local DCStask=CONTROLLABLE.TaskBombing(nil,coord:GetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,self.optionDivebomb) +table.insert(DCStasks,DCStask) +end +elseif self.type==AUFTRAG.Type.STRAFING then +local DCStask=CONTROLLABLE.TaskStrafing(nil,self:GetTargetVec2(),self.engageQuantity,self.engageLength,self.engageWeaponType,self.engageWeaponExpend,self.engageDirection,self.engageAsGroup) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.BOMBRUNWAY then +local DCStask=CONTROLLABLE.TaskBombingRunway(nil,self.engageTarget:GetObject(),self.engageWeaponType,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAsGroup) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.BOMBCARPET then +local DCStask=CONTROLLABLE.TaskCarpetBombing(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,self.engageLength) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.CAP then +local Vec2=self.engageZone:GetVec2() +local Radius +if self.engageZone:IsInstanceOf("COORDINATE")then +Radius=UTILS.NMToMeters(20) +else +Radius=self.engageZone:GetRadius() +end +local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,Vec2,Radius,self.engageTargetTypes,Priority) +table.insert(self.enrouteTasks,DCStask) +elseif self.type==AUFTRAG.Type.CAS then +local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,self.engageZone:GetVec2(),self.engageZone:GetRadius(),self.engageTargetTypes,Priority) +table.insert(self.enrouteTasks,DCStask) +elseif self.type==AUFTRAG.Type.ESCORT then +local DCStask=CONTROLLABLE.TaskEscort(nil,self.engageTarget:GetObject(),self.escortVec3,nil,self.engageMaxDistance,self.engageTargetTypes) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.GROUNDESCORT then +local DCSTask=CONTROLLABLE.TaskGroundEscort(nil,self.engageTarget:GetObject(),nil,self.orbitDistance,self.engageTargetTypes) +table.insert(DCStasks,DCSTask) +elseif self.type==AUFTRAG.Type.FACA then +local DCStask=CONTROLLABLE.TaskFAC_AttackGroup(nil,self.engageTarget:GetObject(),self.engageWeaponType,self.facDesignation,self.facDatalink,self.facFreq,self.facModu,CallsignName,CallsignNumber) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.FAC then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.PATROLZONE +local param={} +param.zone=self:GetObjective() +param.altitude=self.missionAltitude +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +DCStask.params=param +table.insert(DCStasks,DCStask) +local DCSenroute=CONTROLLABLE.EnRouteTaskFAC(self,self.facFreq,self.facModu) +table.insert(self.enrouteTasks,DCSenroute) +elseif self.type==AUFTRAG.Type.FERRY then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.FERRY +local param={} +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.RELOCATECOHORT then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.RELOCATECOHORT +local param={} +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.INTERCEPT then +self:_GetDCSAttackTask(self.engageTarget,DCStasks) +elseif self.type==AUFTRAG.Type.ORBIT then +elseif self.type==AUFTRAG.Type.GCICAP then +elseif self.type==AUFTRAG.Type.RECON then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.RECON +local param={} +param.target=self.engageTarget +param.altitude=self.missionAltitude +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +param.lastindex=nil +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.SEAD then +if self.engageZone then +self.engageZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) +local ScanUnitSet=self.engageZone:GetScannedSetUnit() +local SeadUnitSet=SET_UNIT:New() +for _,_unit in pairs(ScanUnitSet.Set)do +local unit=_unit +if unit and unit:IsAlive()and unit:HasSEAD()then +self:T("Adding UNIT for SEAD: "..unit:GetName()) +local task=CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318) +table.insert(DCStasks,task) +SeadUnitSet:AddUnit(unit) +end +end +self.engageTarget=TARGET:New(SeadUnitSet) +else +self:_GetDCSAttackTask(self.engageTarget,DCStasks) +end +elseif self.type==AUFTRAG.Type.STRIKE then +local coords=self.engageTarget:GetCoordinates() +for _,coord in pairs(coords)do +local DCStask=CONTROLLABLE.TaskAttackMapObject(nil,coord:GetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType) +table.insert(DCStasks,DCStask) +end +elseif self.type==AUFTRAG.Type.TANKER or self.type==AUFTRAG.Type.RECOVERYTANKER then +local DCStask=CONTROLLABLE.EnRouteTaskTanker(nil) +table.insert(self.enrouteTasks,DCStask) +elseif self.type==AUFTRAG.Type.TROOPTRANSPORT then +local TaskEmbark=CONTROLLABLE.TaskEmbarking(TaskControllable,self.transportPickup,self.transportGroupSet,self.transportWaitForCargo) +local TaskDisEmbark=CONTROLLABLE.TaskDisembarking(TaskControllable,self.transportDropoff,self.transportGroupSet) +table.insert(DCStasks,TaskEmbark) +table.insert(DCStasks,TaskDisEmbark) +elseif self.type==AUFTRAG.Type.OPSTRANSPORT then +local DCStask={} +DCStask.id="OpsTransport" +local param={} +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.CARGOTRANSPORT then +local TaskCargoTransportation={ +id="CargoTransportation", +params={} +} +table.insert(DCStasks,TaskCargoTransportation) +elseif self.type==AUFTRAG.Type.RESCUEHELO then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.FORMATION +local param={} +param.unitname=self:GetTargetName() +param.offsetX=200 +param.offsetZ=240 +param.altitude=70 +param.dtFollow=1.0 +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.ARTY then +if self.artyShots==1 or self.artyRadius<10 or true then +local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,self:GetTargetVec2(),self.artyRadius,self.artyShots,self.engageWeaponType,self.artyAltitude) +table.insert(DCStasks,DCStask) +else +local Vec2=self:GetTargetVec2() +local zone=ZONE_RADIUS:New("temp",Vec2,self.artyRadius) +for i=1,self.artyShots do +local vec2=zone:GetRandomVec2() +local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,vec2,0,1,self.engageWeaponType,self.artyAltitude) +table.insert(DCStasks,DCStask) +end +end +elseif self.type==AUFTRAG.Type.BARRAGE then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.BARRAGE +local param={} +param.zone=self:GetObjective() +param.altitude=self.artyAltitude +param.radius=self.artyRadius +param.heading=self.artyHeading +param.angle=self.artyAngle +param.shots=self.artyShots +param.weaponTypoe=self.engageWeaponType +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.PATROLZONE then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.PATROLZONE +local param={} +param.zone=self:GetObjective() +param.altitude=self.missionAltitude +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.CAPTUREZONE then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.CAPTUREZONE +local param={} +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.CASENHANCED then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.PATROLZONE +local param={} +param.zone=self:GetObjective() +param.altitude=self.missionAltitude +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.GROUNDATTACK then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.GROUNDATTACK +local param={} +param.target=self:GetTargetData() +param.action="Wedge" +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.AMMOSUPPLY then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY +local param={} +param.zone=self:GetObjective() +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.FUELSUPPLY then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.FUELSUPPLY +local param={} +param.zone=self:GetObjective() +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.REARMING then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.REARMING +local param={} +param.zone=self:GetObjective() +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.ALERT5 then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.ALERT5 +local param={} +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.NOTHING then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.NOTHING +local param={} +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.PATROLRACETRACK then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.PATROLRACETRACK +local param={} +param.TrackAltitude=self.TrackAltitude +param.TrackSpeed=self.TrackSpeed +param.TrackPoint1=self.TrackPoint1 +param.TrackPoint2=self.TrackPoint2 +param.missionSpeed=self.missionSpeed +param.missionAltitude=self.missionAltitude +param.TrackFormation=self.TrackFormation +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.HOVER then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.HOVER +local param={} +param.hoverAltitude=self.hoverAltitude +param.hoverTime=self.hoverTime +param.missionSpeed=self.missionSpeed +param.missionAltitude=self.missionAltitude +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.LANDATCOORDINATE then +local DCStask={} +local Vec2=self.stayAt:GetVec2() +local DCStask=CONTROLLABLE.TaskLandAtVec2(nil,Vec2,self.stayTime,self.combatLand,self.directionAfter) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.ONGUARD or self.type==AUFTRAG.Type.ARMOREDGUARD then +local DCStask={} +DCStask.id=self.type==AUFTRAG.Type.ONGUARD and AUFTRAG.SpecialTask.ONGUARD or AUFTRAG.SpecialTask.ARMOREDGUARD +local param={} +param.coordinate=self:GetObjective() +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.AIRDEFENSE then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.AIRDEFENSE +local param={} +param.zone=self:GetObjective() +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.EWR then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.EWR +local param={} +param.zone=self:GetObjective() +DCStask.params=param +table.insert(DCStasks,DCStask) +local Enroutetask=CONTROLLABLE.EnRouteTaskEWR() +table.insert(self.enrouteTasks,Enroutetask) +else +self:T(self.lid..string.format("ERROR: Unknown mission task!")) +return nil +end +if self.type==AUFTRAG.Type.ORBIT or +self.type==AUFTRAG.Type.CAP or +self.type==AUFTRAG.Type.CAS or +self.type==AUFTRAG.Type.GCICAP or +self.type==AUFTRAG.Type.AWACS or +self.type==AUFTRAG.Type.TANKER or +self.type==AUFTRAG.Type.RECOVERYTANKER then +self.orbitVec2=self:GetTargetVec2() +if self.orbitVec2 then +self.targetHeading=self:GetTargetHeading() +local OffsetVec2=nil +if(self.orbitOffsetVec2~=nil)then +OffsetVec2=UTILS.DeepCopy(self.orbitOffsetVec2) +end +if OffsetVec2 then +if self.orbitOffsetVec2.r then +local r=self.orbitOffsetVec2.r +local phi=(self.orbitOffsetVec2.phi or 0)+self.targetHeading +OffsetVec2.x=r*math.cos(math.rad(phi)) +OffsetVec2.y=r*math.sin(math.rad(phi)) +else +OffsetVec2.x=self.orbitOffsetVec2.x +OffsetVec2.y=self.orbitOffsetVec2.y +end +end +local orbitVec2=OffsetVec2 and UTILS.Vec2Add(self.orbitVec2,OffsetVec2)or self.orbitVec2 +local orbitRaceTrack=nil +if self.orbitLeg then +local heading=0 +if self.orbitHeading then +if self.orbitHeadingRel then +heading=self.targetHeading+self.orbitHeading +else +heading=self.orbitHeading +end +else +heading=self.targetHeading or 0 +end +orbitRaceTrack=UTILS.Vec2Translate(orbitVec2,self.orbitLeg,heading) +end +local orbitRaceTrackCoord=nil +if orbitRaceTrack then +orbitRaceTrackCoord=COORDINATE:NewFromVec2(orbitRaceTrack) +end +local DCStask=CONTROLLABLE.TaskOrbit(nil,COORDINATE:NewFromVec2(orbitVec2),self.orbitAltitude,self.orbitSpeed,orbitRaceTrackCoord) +table.insert(DCStasks,DCStask) +end +end +self:T3({missiontask=DCStasks}) +if#DCStasks==1 then +return DCStasks[1] +else +return CONTROLLABLE.TaskCombo(nil,DCStasks) +end +end +function AUFTRAG:_GetDCSAttackTask(Target,DCStasks) +DCStasks=DCStasks or{} +for _,_target in pairs(Target.targets)do +local target=_target +if target.Type==TARGET.ObjectType.GROUP then +local DCStask=CONTROLLABLE.TaskAttackGroup(nil,target.Object,self.engageWeaponType,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageAsGroup) +table.insert(DCStasks,DCStask) +elseif target.Type==TARGET.ObjectType.UNIT or target.Type==TARGET.ObjectType.STATIC then +local DCStask=CONTROLLABLE.TaskAttackUnit(nil,target.Object,self.engageAsGroup,self.WeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType) +table.insert(DCStasks,DCStask) +end +end +return DCStasks +end +function AUFTRAG:GetMissionTaskforMissionType(MissionType) +local mtask=ENUMS.MissionTask.NOTHING +if MissionType==AUFTRAG.Type.ANTISHIP then +mtask=ENUMS.MissionTask.ANTISHIPSTRIKE +elseif MissionType==AUFTRAG.Type.AWACS then +mtask=ENUMS.MissionTask.AWACS +elseif MissionType==AUFTRAG.Type.BAI then +mtask=ENUMS.MissionTask.GROUNDATTACK +elseif MissionType==AUFTRAG.Type.BOMBCARPET then +mtask=ENUMS.MissionTask.GROUNDATTACK +elseif MissionType==AUFTRAG.Type.BOMBING then +mtask=ENUMS.MissionTask.GROUNDATTACK +elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then +mtask=ENUMS.MissionTask.RUNWAYATTACK +elseif MissionType==AUFTRAG.Type.CAP then +mtask=ENUMS.MissionTask.CAP +elseif MissionType==AUFTRAG.Type.GCICAP then +mtask=ENUMS.MissionTask.CAP +elseif MissionType==AUFTRAG.Type.CAS then +mtask=ENUMS.MissionTask.CAS +elseif MissionType==AUFTRAG.Type.PATROLZONE then +mtask=ENUMS.MissionTask.CAS +elseif MissionType==AUFTRAG.Type.CASENHANCED then +mtask=ENUMS.MissionTask.CAS +elseif MissionType==AUFTRAG.Type.ESCORT then +mtask=ENUMS.MissionTask.ESCORT +elseif MissionType==AUFTRAG.Type.FACA then +mtask=ENUMS.MissionTask.AFAC +elseif MissionType==AUFTRAG.Type.FAC then +mtask=ENUMS.MissionTask.AFAC +elseif MissionType==AUFTRAG.Type.FERRY then +mtask=ENUMS.MissionTask.NOTHING +elseif MissionType==AUFTRAG.Type.GROUNDESCORT then +mtask=ENUMS.MissionTask.GROUNDESCORT +elseif MissionType==AUFTRAG.Type.INTERCEPT then +mtask=ENUMS.MissionTask.INTERCEPT +elseif MissionType==AUFTRAG.Type.RECON then +mtask=ENUMS.MissionTask.RECONNAISSANCE +elseif MissionType==AUFTRAG.Type.SEAD then +mtask=ENUMS.MissionTask.SEAD +elseif MissionType==AUFTRAG.Type.STRIKE then +mtask=ENUMS.MissionTask.GROUNDATTACK +elseif MissionType==AUFTRAG.Type.TANKER then +mtask=ENUMS.MissionTask.REFUELING +elseif MissionType==AUFTRAG.Type.TROOPTRANSPORT then +mtask=ENUMS.MissionTask.TRANSPORT +elseif MissionType==AUFTRAG.Type.CARGOTRANSPORT then +mtask=ENUMS.MissionTask.TRANSPORT +elseif MissionType==AUFTRAG.Type.ARMORATTACK then +mtask=ENUMS.MissionTask.NOTHING +elseif MissionType==AUFTRAG.Type.HOVER then +mtask=ENUMS.MissionTask.NOTHING +elseif MissionType==AUFTRAG.Type.PATROLRACETRACK then +mtask=ENUMS.MissionTask.CAP +end +return mtask +end +function AUFTRAG.CheckMissionType(MissionType,PossibleTypes) +if type(PossibleTypes)=="string"then +PossibleTypes={PossibleTypes} +end +for _,canmission in pairs(PossibleTypes)do +if canmission==MissionType then +return true +end +end +return false +end +function AUFTRAG.CheckMissionCapability(MissionTypes,Capabilities,All) +if type(MissionTypes)~="table"then +MissionTypes={MissionTypes} +end +for _,cap in pairs(Capabilities)do +local capability=cap +for _,MissionType in pairs(MissionTypes)do +if All==true then +if capability.MissionType~=MissionType then +return false +end +else +if capability.MissionType==MissionType then +return true +end +end +end +end +if All==true then +return true +else +return false +end +end +function AUFTRAG.CheckMissionCapabilityAny(MissionTypes,Capabilities) +local res=AUFTRAG.CheckMissionCapability(MissionTypes,Capabilities,false) +return res +end +function AUFTRAG.CheckMissionCapabilityAll(MissionTypes,Capabilities) +local res=AUFTRAG.CheckMissionCapability(MissionTypes,Capabilities,true) +return res +end +do +AWACS={ +ClassName="AWACS", +version="0.2.73", +lid="", +coalition=coalition.side.BLUE, +coalitiontxt="blue", +OpsZone=nil, +StationZone=nil, +AirWing=nil, +Frequency=271, +Modulation=radio.modulation.AM, +Airbase=nil, +AwacsAngels=25, +OrbitZone=nil, +CallSign=CALLSIGN.AWACS.Magic, +CallSignNo=1, +debug=false, +verbose=false, +ManagedGrps={}, +ManagedGrpID=0, +ManagedTaskID=0, +AnchorStacks={}, +CAPIdleAI={}, +CAPIdleHuman={}, +TaskedCAPAI={}, +TaskedCAPHuman={}, +OpenTasks={}, +ManagedTasks={}, +PictureAO={}, +PictureEWR={}, +Contacts={}, +Countactcounter=0, +ContactsAO={}, +RadioQueue={}, +PrioRadioQueue={}, +TacticalQueue={}, +AwacsTimeOnStation=4, +AwacsTimeStamp=0, +EscortsTimeOnStation=4, +EscortsTimeStamp=0, +CAPTimeOnStation=4, +AwacsROE="", +AwacsROT="", +MenuStrict=true, +MaxAIonCAP=3, +AIonCAP=0, +AICAPMissions={}, +ShiftChangeAwacsFlag=false, +ShiftChangeEscortsFlag=false, +ShiftChangeAwacsRequested=false, +ShiftChangeEscortsRequested=false, +CAPAirwings={}, +MonitoringData={}, +MonitoringOn=false, +FlightGroups={}, +AwacsMission=nil, +AwacsInZone=false, +AwacsReady=false, +CatchAllMissions={}, +CatchAllFGs={}, +PictureInterval=300, +ReassignTime=120, +PictureTimeStamp=0, +BorderZone=nil, +RejectZone=nil, +maxassigndistance=100, +PlayerGuidance=true, +ModernEra=true, +callsignshort=true, +keepnumber=true, +callsignTranslations=nil, +TacDistance=45, +MeldDistance=35, +ThreatDistance=25, +AOName="Rock", +AOCoordinate=nil, +clientmenus=nil, +RadarBlur=15, +ReassignmentPause=180, +NoGroupTags=false, +SuppressScreenOutput=false, +NoMissileCalls=true, +GoogleTTSPadding=1, +WindowsTTSPadding=2.5, +PlayerCapAssignment=true, +AllowMarkers=false, +PlayerStationName=nil, +GCI=false, +GCIGroup=nil, +locale="en", +IncludeHelicopters=false, +TacticalMenu=false, +TacticalFrequencies={}, +TacticalSubscribers={}, +TacticalBaseFreq=130, +TacticalIncrFreq=0.5, +TacticalModulation=radio.modulation.AM, +TacticalInterval=120, +DetectionSet=nil, +MaxMissionRange=125, +} +AWACS.CallSignClear={ +[1]="Overlord", +[2]="Magic", +[3]="Wizard", +[4]="Focus", +[5]="Darkstar", +} +AWACS.AnchorNames={ +[1]="One", +[2]="Two", +[3]="Three", +[4]="Four", +[5]="Five", +[6]="Six", +[7]="Seven", +[8]="Eight", +[9]="Nine", +[10]="Ten", +} +AWACS.IFF= +{ +SPADES="Spades", +NEUTRAL="Neutral", +FRIENDLY="Friendly", +ENEMY="Hostile", +BOGEY="Bogey", +} +AWACS.Phonetic= +{ +[1]='Alpha', +[2]='Bravo', +[3]='Charlie', +[4]='Delta', +[5]='Echo', +[6]='Foxtrot', +[7]='Golf', +[8]='Hotel', +[9]='India', +[10]='Juliett', +[11]='Kilo', +[12]='Lima', +[13]='Mike', +[14]='November', +[15]='Oscar', +[16]='Papa', +[17]='Quebec', +[18]='Romeo', +[19]='Sierra', +[20]='Tango', +[21]='Uniform', +[22]='Victor', +[23]='Whiskey', +[24]='Xray', +[25]='Yankee', +[26]='Zulu', +} +AWACS.Shipsize= +{ +[1]="Singleton", +[2]="Two-Ship", +[3]="Heavy", +[4]="Gorilla", +} +AWACS.ROE={ +POLICE="Police", +VID="Visual ID", +IFF="IFF", +BVR="Beyond Visual Range", +} +AWACS.ROT={ +BYPASSESCAPE="Bypass and Escape", +EVADE="Evade Fire", +PASSIVE="Passive Defense", +RETURNFIRE="Return Fire", +OPENFIRE="Open Fire", +} +AWACS.THREATLEVEL={ +GREEN=3, +AMBER=7, +RED=10, +} +AWACS.CapVoices={ +[1]="de-DE-Wavenet-A", +[2]="de-DE-Wavenet-B", +[3]="fr-FR-Wavenet-A", +[4]="fr-FR-Wavenet-B", +[5]="en-GB-Wavenet-A", +[6]="en-GB-Wavenet-B", +[7]="en-GB-Wavenet-D", +[8]="en-AU-Wavenet-B", +[9]="en-US-Wavenet-J", +[10]="en-US-Wavenet-H", +} +AWACS.Messages={ +EN= +{ +DEFEND="%s, %s! %s! %s! Defend!", +VECTORTO="%s, %s. Vector%s %s", +VECTORTOTTS="%s, %s, Vector%s %s", +ANGELS=". Angels ", +ZERO="zero", +VANISHED="%s, %s Group. Vanished.", +VANISHEDTTS="%s, %s group vanished.", +SHIFTCHANGE="%s shift change for %s control.", +GROUPCAP="Group", +GROUP="group", +MILES="miles", +THOUSAND="thousand", +BOGEY="Bogey", +ALLSTATIONS="All Stations", +PICCLEAN="%s. %s. Picture Clean.", +PICTURE="Picture", +ONE="One", +GROUPMULTI="groups", +NOTCHECKEDIN="%s. %s. Negative. You are not checked in.", +CLEAN="%s. %s. Clean.", +DOPE="%s. %s. Bogey Dope. ", +VIDPOS="%s. %s. Copy, target identified as %s.", +VIDNEG="%s. %s. Negative, get closer to target.", +FFNEUTRAL="Neutral", +FFFRIEND="Friendly", +FFHOSTILE="Hostile", +FFSPADES="Spades", +FFCLEAN="Clean", +COPY="%s. %s. Copy.", +TARGETEDBY="Targeted by %s.", +STATUS="Status", +ALREADYCHECKEDIN="%s. %s. Negative. You are already checked in.", +ALPHACHECK="Alpha Check", +CHECKINAI="%s. %s. Checking in as fragged. Expected playtime %d hours. Request Alpha Check %s.", +SAFEFLIGHT="%s. %s. Copy. Have a safe flight home.", +VERYLOW="very low", +AIONSTATION="%s. %s. On station over anchor %d at angels %d. Ready for tasking.", +POPUP="Pop-up", +NEWGROUP="New group", +HIGH=" High.", +VERYFAST=" Very fast.", +FAST=" Fast.", +THREAT="Threat", +MERGED="Merged", +SCREENVID="Intercept and VID %s group.", +SCREENINTER="Intercept %s group.", +ENGAGETAG="Targeted by %s.", +REQCOMMIT="%s. %s group. %s. %s, request commit.", +AICOMMIT="%s. %s group. %s. %s, commit.", +COMMIT="Commit", +SUNRISE="%s. All stations, SUNRISE SUNRISE SUNRISE, %s.", +AWONSTATION="%s on station for %s control.", +STATIONAT="%s. %s. Station at %s at angels %d.", +STATIONATLONG="%s. %s. Station at %s at angels %d doing %d knots.", +STATIONSCREEN="%s. %s.\nStation at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s.", +STATIONTASK="Station at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s", +VECTORSTATION=" to Station", +TEXTOPTIONS1="Lost friendly flight", +TEXTOPTIONS2="Vanished friendly flight", +TEXTOPTIONS3="Faded friendly contact", +TEXTOPTIONS4="Lost contact with", +}, +} +AWACS.TaskDescription={ +ANCHOR="Anchor", +REANCHOR="Re-Anchor", +VID="VID", +IFF="IFF", +INTERCEPT="Intercept", +SWEEP="Sweep", +RTB="RTB", +} +AWACS.TaskStatus={ +IDLE="Idle", +UNASSIGNED="Unassigned", +REQUESTED="Requested", +ASSIGNED="Assigned", +EXECUTING="Executing", +SUCCESS="Success", +FAILED="Failed", +DEAD="Dead", +} +function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,StationZone,Frequency,Modulation) +local self=BASE:Inherit(self,FSM:New()) +if Coalition and type(Coalition)=="string"then +if Coalition=="blue"then +self.coalition=coalition.side.BLUE +self.coalitiontxt=Coalition +elseif Coalition=="red"then +self.coalition=coalition.side.RED +self.coalitiontxt=Coalition +elseif Coalition=="neutral"then +self.coalition=coalition.side.NEUTRAL +self.coalitiontxt=Coalition +else +self:E("ERROR: Unknown coalition in AWACS!") +end +else +self.coalition=Coalition +self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition)) +end +self.Name=Name +self.AirWing=AirWing +AirWing:SetUsingOpsAwacs(self) +self.CAPAirwings=FIFO:New() +self.CAPAirwings:Push(AirWing,1) +self.AwacsFG=nil +self.RadarBlur=15 +if type(OpsZone)=="string"then +self.OpsZone=ZONE:New(OpsZone) +elseif type(OpsZone)=="table"and OpsZone.ClassName and string.find(OpsZone.ClassName,"ZONE")then +self.OpsZone=OpsZone +else +self:E("AWACS - Invalid Zone passed!") +return +end +self.AOCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.coalition)) +self.AOName=self.OpsZone:GetName() +self.UseBullsAO=true +self.ControlZoneRadius=100 +self.StationZone=ZONE:New(StationZone) +self.StationZoneName=StationZone +self.Frequency=Frequency or 271 +self.Modulation=Modulation or radio.modulation.AM +self.MultiFrequency={self.Frequency} +self.MultiModulation={self.Modulation} +self.Airbase=AIRBASE:FindByName(AirbaseName) +self.AwacsAngels=25 +if AwacsOrbit then +self.OrbitZone=ZONE:New(AwacsOrbit) +end +self.BorderZone=nil +self.CallSign=CALLSIGN.AWACS.Magic +self.CallSignNo=1 +self.NoHelos=true +self.AIRequested=0 +self.AIonCAP=0 +self.AICAPMissions=FIFO:New() +self.FlightGroups=FIFO:New() +self.Countactcounter=0 +self.PictureInterval=300 +self.PictureTimeStamp=0 +self.ReassignTime=120 +self.intelstarted=false +self.sunrisedone=false +local speed=250 +self.SpeedBase=speed +self.Speed=speed +self.Heading=0 +self.Leg=50 +self.invisible=false +self.immortal=false +self.callsigntxt="AWACS" +self.AwacsTimeOnStation=4 +self.AwacsTimeStamp=0 +self.EscortsTimeOnStation=4 +self.EscortsTimeStamp=0 +self.ShiftChangeTime=0.25 +self.ShiftChangeAwacsFlag=false +self.ShiftChangeEscortsFlag=false +self.CapSpeedBase=270 +self.CAPTimeOnStation=4 +self.MaxAIonCAP=4 +self.AICAPCAllName=CALLSIGN.Aircraft.Colt +self.AICAPCAllNumber=0 +self.CAPGender="male" +self.CAPCulture="en-US" +self.CAPVoice=nil +self.AwacsMission=nil +self.AwacsInZone=false +self.AwacsReady=false +self.AwacsROE=AWACS.ROE.IFF +self.AwacsROT=AWACS.ROT.BYPASSESCAPE +self.HasEscorts=false +self.EscortTemplate="" +self.EscortMission={} +self.EscortMissionReplacement={} +self.PathToSRS="C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +self.Gender="female" +self.Culture="en-GB" +self.Voice=nil +self.Port=5002 +self.Volume=1.0 +self.RadioQueue=FIFO:New() +self.PrioRadioQueue=FIFO:New() +self.TacticalQueue=FIFO:New() +self.maxspeakentries=3 +self.GoogleTTSPadding=1 +self.WindowsTTSPadding=2.5 +self.clientset=SET_CLIENT:New():FilterActive(true):FilterCoalitions(self.coalitiontxt):FilterCategories("plane"):FilterStart() +self.PlayerGuidance=true +self.ModernEra=true +self.NoGroupTags=false +self.SuppressScreenOutput=false +self.ReassignmentPause=180 +self.callsignshort=true +self.DeclareRadius=5 +self.MenuStrict=true +self.maxassigndistance=100 +self.NoMissileCalls=true +self.PlayerCapAssignment=true +self.ManagedGrps={} +self.ManagedGrpID=0 +self.callsignTranslations=nil +self.AnchorStacks=FIFO:New() +self.AnchorBaseAngels=22 +self.AnchorStackDistance=2 +self.AnchorMaxStacks=4 +self.AnchorMaxAnchors=2 +self.AnchorMaxZones=6 +self.AnchorCurrZones=1 +self.AnchorTurn=-(360/self.AnchorMaxZones) +self:_CreateAnchorStack() +self.ManagedTasks=FIFO:New() +local MonitoringData={} +MonitoringData.AICAPCurrent=0 +MonitoringData.AICAPMax=self.MaxAIonCAP +MonitoringData.Airwings=1 +MonitoringData.PlayersCheckedin=0 +MonitoringData.Players=0 +MonitoringData.AwacsShiftChange=false +MonitoringData.AwacsStateFG="unknown" +MonitoringData.AwacsStateMission="unknown" +MonitoringData.EscortsShiftChange=false +MonitoringData.EscortsStateFG={} +MonitoringData.EscortsStateMission={} +self.MonitoringOn=false +self.MonitoringData=MonitoringData +self.CatchAllMissions={} +self.CatchAllFGs={} +self.PictureAO=FIFO:New() +self.PictureEWR=FIFO:New() +self.Contacts=FIFO:New() +self.CID=0 +self.ContactsAO=FIFO:New() +self.clientmenus=FIFO:New() +self.TacticalMenu=false +self.TacticalBaseFreq=130 +self.TacticalIncrFreq=0.5 +self.TacticalModulation=radio.modulation.AM +self.acticalFrequencies={} +self.TacticalSubscribers={} +self.TacticalInterval=120 +self.DetectionSet=SET_GROUP:New() +self.lid=string.format("%s (%s) | ",self.Name,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","StartUp") +self:AddTransition("StartUp","Started","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","CheckedIn","*") +self:AddTransition("*","CheckedOut","*") +self:AddTransition("*","AssignAnchor","*") +self:AddTransition("*","AssignedAnchor","*") +self:AddTransition("*","ReAnchor","*") +self:AddTransition("*","NewCluster","*") +self:AddTransition("*","NewContact","*") +self:AddTransition("*","LostCluster","*") +self:AddTransition("*","LostContact","*") +self:AddTransition("*","CheckRadioQueue","*") +self:AddTransition("*","CheckTacticalQueue","*") +self:AddTransition("*","EscortShiftChange","*") +self:AddTransition("*","AwacsShiftChange","*") +self:AddTransition("*","FlightOnMission","*") +self:AddTransition("*","Intercept","*") +self:AddTransition("*","InterceptSuccess","*") +self:AddTransition("*","InterceptFailure","*") +self:AddTransition("*","VIDSuccess","*") +self:AddTransition("*","VIDFailure","*") +self:AddTransition("*","Stop","Stopped") +local text=string.format("%sAWACS Version %s Initiated",self.lid,self.version) +self:I(text) +self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler) +self:HandleEvent(EVENTS.Ejection,self._EventHandler) +self:HandleEvent(EVENTS.Crash,self._EventHandler) +self:HandleEvent(EVENTS.Dead,self._EventHandler) +self:HandleEvent(EVENTS.UnitLost,self._EventHandler) +self:HandleEvent(EVENTS.BDA,self._EventHandler) +self:HandleEvent(EVENTS.PilotDead,self._EventHandler) +self:HandleEvent(EVENTS.Shot,self._EventHandler) +self:_InitLocalization() +return self +end +function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number) +self:T(self.lid.."SetTacticalRadios") +if not self.AwacsSRS then +MESSAGE:New("AWACS: Setup SRS in your code BEFORE trying to add tac radios please!",30,"ERROR",true):ToLog():ToAll() +return self +end +self.TacticalMenu=true +self.TacticalBaseFreq=BaseFreq or 130 +self.TacticalIncrFreq=Increase or 0.5 +self.TacticalModulation=Modulation or radio.modulation.AM +self.TacticalInterval=Interval or 120 +local number=Number or 10 +if number<1 then number=1 end +if number>10 then number=10 end +for i=1,number do +local freq=self.TacticalBaseFreq+((i-1)*self.TacticalIncrFreq) +self.TacticalFrequencies[freq]=freq +end +if self.AwacsSRS then +self.TacticalSRS=MSRS:New(self.PathToSRS,self.TacticalBaseFreq,self.TacticalModulation,self.Backend) +self.TacticalSRS:SetCoalition(self.coalition) +self.TacticalSRS:SetGender(self.Gender) +self.TacticalSRS:SetCulture(self.Culture) +self.TacticalSRS:SetVoice(self.Voice) +self.TacticalSRS:SetPort(self.Port) +self.TacticalSRS:SetLabel("AWACS") +self.TacticalSRS:SetVolume(self.Volume) +if self.PathToGoogleKey then +self.TacticalSRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey) +self.TacticalSRS:SetProvider(MSRS.Provider.GOOGLE) +end +self.TacticalSRSQ=MSRSQUEUE:New("Tactical AWACS") +end +return self +end +function AWACS:_RefreshMenuNonSubscribed() +self:T(self.lid.."_RefreshMenuNonSubscribed") +local aliveset=self.clientset:GetAliveSet() +for _,_group in pairs(aliveset)do +local grp=_group +local Group=grp:GetGroup() +local gname=nil +if Group and Group:IsAlive()then +gname=Group:GetName() +self:T(gname) +end +local menustr=self.clientmenus:ReadByID(gname) +local menu=menustr.tactical +if not self.TacticalSubscribers[gname]and menu then +menu:RemoveSubMenus() +for _,_freq in UTILS.spairs(self.TacticalFrequencies)do +local modu=UTILS.GetModulationName(self.TacticalModulation) +local text=string.format("Subscribe to %.3f %s",_freq,modu) +local entry=MENU_GROUP_COMMAND:New(Group,text,menu,self._SubScribeTactRadio,self,Group,_freq) +end +end +end +return self +end +function AWACS:_UnsubScribeTactRadio(Group) +self:T(self.lid.."_UnsubScribeTactRadio") +local text="" +local textScreen="" +local GID,Outcome=self:_GetManagedGrpID(Group) +local gcallsign=self:_GetCallSign(Group,GID)or"Ghost 1" +local gname=Group:GetName()or"unknown" +if Outcome and self.TacticalSubscribers[gname]then +local Freq=self.TacticalSubscribers[gname] +self.TacticalFrequencies[Freq]=Freq +self.TacticalSubscribers[gname]=nil +local modu=self.TacticalModulation==0 and"AM"or"FM" +text=string.format("%s, %s, switch back to AWACS main frequency!",gcallsign,self.callsigntxt) +self:_NewRadioEntry(text,text,GID,true,true,true,false,true) +self:_RefreshMenuNonSubscribed() +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_SubScribeTactRadio(Group,Frequency) +self:T(self.lid.."_SubScribeTactRadio") +local text="" +local textScreen="" +local GID,Outcome=self:_GetManagedGrpID(Group) +local gcallsign=self:_GetCallSign(Group,GID)or"Ghost 1" +local gname=Group:GetName()or"unknown" +if Outcome then +self.TacticalSubscribers[gname]=Frequency +self.TacticalFrequencies[Frequency]=nil +local modu=self.TacticalModulation==0 and"AM"or"FM" +text=string.format("%s, %s, switch to %.3f %s for tactical information!",gcallsign,self.callsigntxt,Frequency,modu) +self:_NewRadioEntry(text,text,GID,true,true,true,false,true) +local menustr=self.clientmenus:ReadByID(gname) +local menu=menustr.tactical +if menu then +menu:RemoveSubMenus() +local text=string.format("Unsubscribe %.3f %s",Frequency,modu) +local entry=MENU_GROUP_COMMAND:New(Group,text,menu,self._UnsubScribeTactRadio,self,Group) +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_CheckSubscribers() +self:T(self.lid.."_InitLocalization") +for _name,_freq in pairs(self.TacticalSubscribers or{})do +local grp=GROUP:FindByName(_name) +if(not grp)or(not grp:IsAlive())then +self.TacticalFrequencies[_freq]=_freq +self.TacticalSubscribers[_name]=nil +end +end +return self +end +function AWACS:_InitLocalization() +self:T(self.lid.."_InitLocalization") +self.gettext=TEXTANDSOUND:New("AWACS","en") +self.locale="en" +for locale,table in pairs(self.Messages)do +local Locale=string.lower(tostring(locale)) +self:T("**** Adding locale: "..Locale) +for ID,Text in pairs(table)do +self:T(string.format('Adding ID %s',tostring(ID))) +self.gettext:AddEntry(Locale,tostring(ID),Text) +end +end +return self +end +function AWACS:SetLocale(Locale) +self:T(self.lid.."SetLocale") +self.locale=Locale or"en" +return self +end +function AWACS:SetBullsCoordinate(Coordinate) +self:T(self.lid.."SetBullsCoordinate") +self.AOCoordinate=Coordinate +return self +end +function AWACS:SetMaxMissionRange(NM) +self.MaxMissionRange=NM or 125 +return self +end +function AWACS:AddFrequencyAndModulation(Frequency,Modulation) +self:T(self.lid.."AddFrequencyAndModulation") +table.insert(self.MultiFrequency,Frequency) +table.insert(self.MultiModulation,Modulation) +if self.AwacsSRS then +self.AwacsSRS:SetFrequencies(self.MultiFrequency) +self.AwacsSRS:SetModulations(self.MultiModulation) +end +return self +end +function AWACS:SetAsGCI(EWR,Delay) +self:T(self.lid.."SetGCI") +local delay=Delay or-5 +if type(EWR)=="string"then +self.GCIGroup=GROUP:FindByName(EWR) +else +self.GCIGroup=EWR +end +self.GCI=true +self:SetEscort(0) +return self +end +function AWACS:_NewRadioEntry(TextTTS,TextScreen,GID,IsGroup,ToScreen,IsNew,FromAI,IsPrio,Tactical) +self:T(self.lid.."_NewRadioEntry") +local RadioEntry={} +RadioEntry.IsNew=IsNew +RadioEntry.TextTTS=TextTTS +RadioEntry.TextScreen=TextScreen or TextTTS +RadioEntry.GroupID=GID +RadioEntry.ToScreen=ToScreen +RadioEntry.Duration=MSRS.getSpeechTime(TextTTS,0.95,false)or 8 +RadioEntry.FromAI=FromAI +RadioEntry.IsGroup=IsGroup +if Tactical then +self.TacticalQueue:Push(RadioEntry) +elseif IsPrio then +self.PrioRadioQueue:Push(RadioEntry) +else +self.RadioQueue:Push(RadioEntry) +end +return self +end +function AWACS:SetBullsEyeAlias(Name) +self:T(self.lid.."_SetBullsEyeAlias") +self.AOName=Name or"Rock" +return self +end +function AWACS:SetTOS(AICHours,CapHours) +self:T(self.lid.."SetTOS") +self.AwacsTimeOnStation=AICHours or 4 +self.CAPTimeOnStation=CapHours or 4 +return self +end +function AWACS:SetReassignmentPause(Seconds) +self.ReassignmentPause=Seconds or 180 +return self +end +function AWACS:SuppressScreenMessages(Switch) +self:T(self.lid.."_SetBullsEyeAlias") +self.SuppressScreenOutput=Switch or false +return self +end +function AWACS:ZipLip() +self:T(self.lid.."ZipLip") +self:SuppressScreenMessages(true) +self.PlayerGuidance=false +self.callsignshort=true +self.NoMissileCalls=true +return self +end +function AWACS:SetCustomCallsigns(translationTable) +self.callsignTranslations=translationTable +end +function AWACS:_GetGIDFromGroupOrName(Group) +self:T(self.lid.."_GetGIDFromGroupOrName") +self:T({Group}) +local GID=0 +local Outcome=false +local CallSign="Ghost 1" +local nametocheck=CallSign +if Group and type(Group)=="string"then +nametocheck=Group +elseif Group and Group:IsInstanceOf("GROUP")then +nametocheck=Group:GetName() +else +return false,0,CallSign +end +local managedgrps=self.ManagedGrps or{} +for _,_managed in pairs(managedgrps)do +local managed=_managed +if managed.GroupName==nametocheck then +GID=managed.GID +Outcome=true +CallSign=managed.CallSign +end +end +self:T({Outcome,GID,CallSign}) +return Outcome,GID,CallSign +end +function AWACS:_EventHandler(EventData) +self:T(self.lid.."_EventHandler") +self:T({Event=EventData.id}) +local Event=EventData +if Event.id==EVENTS.PlayerEnterAircraft or Event.id==EVENTS.PlayerEnterUnit then +if Event.IniCoalition==self.coalition then +self:_SetClientMenus() +end +end +if Event.id==EVENTS.PlayerLeaveUnit and Event.IniGroupName then +self:T("Player group left unit: "..Event.IniGroupName) +self:T("Player name left: "..Event.IniPlayerName) +self:T("Coalition = "..UTILS.GetCoalitionName(Event.IniCoalition)) +if Event.IniCoalition==self.coalition then +local Outcome,GID,CallSign=self:_GetGIDFromGroupOrName(Event.IniGroupName) +if Outcome and GID>0 then +self:T("Task Abort and Checkout Called") +self:_TaskAbort(Event.IniGroupName) +self:_CheckOut(nil,GID,true) +end +end +end +if Event.id==EVENTS.Ejection or Event.id==EVENTS.Crash or Event.id==EVENTS.Dead or Event.id==EVENTS.PilotDead then +if Event.IniCoalition==self.coalition then +local Outcome,GID,CallSign=self:_GetGIDFromGroupOrName(Event.IniGroupName) +if Outcome and GID>0 then +self:_TaskAbort(Event.IniGroupName) +self:_CheckOut(nil,GID,true) +end +end +end +if Event.id==EVENTS.Shot and self.PlayerGuidance and not self.NoMissileCalls then +if Event.IniCoalition~=self.coalition then +self:T("Shot from: "..Event.IniGroupName) +local position=Event.IniGroup:GetCoordinate() +if not position then return self end +local Category=Event.WeaponCategory +local WeaponDesc=EventData.Weapon:getDesc() +self:T({WeaponDesc}) +if WeaponDesc.category==1 and(WeaponDesc.missileCategory==1 or WeaponDesc.missileCategory==2)then +self:T("AAM or SAM Missile fired") +local warndist=25 +local Type="SAM" +if WeaponDesc.category==1 then +Type="Missile" +local guidance=WeaponDesc.guidance or 4 +if guidance==2 then +warndist=10 +elseif guidance==3 then +warndist=25 +elseif guidance==4 then +warndist=15 +elseif guidance==5 then +warndist=10 +end +end +self:_MissileWarning(position,Type,warndist) +end +end +end +return self +end +function AWACS:_MissileWarning(Coordinate,Type,Warndist) +self:T(self.lid.."_MissileWarning Type="..Type.." WarnDist="..Warndist) +if not Coordinate then return self end +local shotzone=ZONE_RADIUS:New("WarningZone",Coordinate:GetVec2(),UTILS.NMToMeters(Warndist)) +local targetgrpset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryAirplane():FilterActive():FilterZones({shotzone}):FilterOnce() +if targetgrpset:Count()>0 then +local targets=targetgrpset:GetSetObjects() +for _,_grp in pairs(targets)do +if _grp and _grp:IsAlive()then +local isPlayer=_grp:IsPlayer() +if isPlayer then +local callsign=self:_GetCallSign(_grp) +local defend=self.gettext:GetEntry("DEFEND",self.locale) +local text=string.format(defend,callsign,Type,Type,Type) +self:_NewRadioEntry(text,text,0,false,self.debug,true,false,true) +end +end +end +end +return self +end +function AWACS:SetRadarBlur(Percent) +local percent=Percent or 15 +if percent<0 then percent=0 end +if percent>100 then percent=100 end +self.RadarBlur=Percent +return self +end +function AWACS:SetColdWar() +self.ModernEra=false +self.AwacsROT=AWACS.ROT.PASSIVE +self.AwacsROE=AWACS.ROE.VID +self.RadarBlur=25 +self:SetInterceptTimeline(35,25,15) +return self +end +function AWACS:SetModernEra() +self.ModernEra=true +self.AwacsROT=AWACS.ROT.EVADE +self.AwacsROE=AWACS.ROE.BVR +self.RadarBlur=15 +return self +end +function AWACS:SetModernEraDefensive() +self.ModernEra=true +self.AwacsROT=AWACS.ROT.EVADE +self.AwacsROE=AWACS.ROE.IFF +self.RadarBlur=15 +return self +end +function AWACS:SetModernEraAggressive() +self.ModernEra=true +self.AwacsROT=AWACS.ROT.RETURNFIRE +self.AwacsROE=AWACS.ROE.BVR +self.RadarBlur=15 +return self +end +function AWACS:SetPolicingModern() +self.ModernEra=true +self.AwacsROT=AWACS.ROT.BYPASSESCAPE +self.AwacsROE=AWACS.ROE.VID +self.RadarBlur=15 +return self +end +function AWACS:SetPolicingColdWar() +self.ModernEra=false +self.AwacsROT=AWACS.ROT.BYPASSESCAPE +self.AwacsROE=AWACS.ROE.VID +self.RadarBlur=25 +self:SetInterceptTimeline(35,25,15) +return self +end +function AWACS:SetPlayerGuidance(Switch) +if(Switch==nil)or(Switch==true)then +self.PlayerGuidance=true +else +self.PlayerGuidance=false +end +return self +end +function AWACS:GetName() +return self.Name or"not set" +end +function AWACS:SetInterceptTimeline(TacDistance,MeldDistance,ThreatDistance) +self.TacDistance=TacDistance or 45 +self.MeldDistance=MeldDistance or 35 +self.ThreatDistance=ThreatDistance or 25 +return self +end +function AWACS:SetAdditionalZone(Zone,Draw) +self:T(self.lid.."SetAdditionalZone") +self.BorderZone=Zone +if self.debug then +Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) +if self.AllowMarkers then +MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) +end +elseif Draw then +Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) +end +return self +end +function AWACS:SetRejectionZone(Zone,Draw) +self:T(self.lid.."SetRejectionZone") +self.RejectZone=Zone +if Draw then +Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) +elseif self.debug then +Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) +if self.AllowMarkers then +MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) +end +end +return self +end +function AWACS:DrawFEZ() +self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true) +return self +end +function AWACS:SetAwacsDetails(CallSign,CallSignNo,Angels,Speed,Heading,Leg) +self:T(self.lid.."SetAwacsDetails") +self.CallSign=CallSign or CALLSIGN.AWACS.Magic +self.CallSignNo=CallSignNo or 1 +self.AwacsAngels=Angels or 25 +local speed=Speed or 250 +self.SpeedBase=speed +self.Speed=speed +self.Heading=Heading or 0 +self.Leg=Leg or 25 +return self +end +function AWACS:SetCustomAWACSCallSign(CallsignTable) +self:T(self.lid.."SetCustomAWACSCallSign") +self.CallSignClear=CallsignTable +return self +end +function AWACS:AddGroupToDetection(Group) +self:T(self.lid.."AddGroupToDetection") +if Group and Group.ClassName and Group.ClassName=="GROUP"then +self.DetectionSet:AddGroup(Group) +elseif Group and Group.ClassName and Group.ClassName=="SET_GROUP"then +self.DetectionSet:AddSet(Group) +end +return self +end +function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend) +self:T(self.lid.."SetSRS") +self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +self.Gender=Gender or MSRS.gender or"male" +self.Culture=Culture or MSRS.culture or"en-US" +self.Port=Port or MSRS.port or 5002 +self.Voice=Voice or MSRS.voice +self.PathToGoogleKey=PathToGoogleKey +self.AccessKey=AccessKey +self.Volume=Volume or 1.0 +self.Backend=Backend or MSRS.backend +BASE:I({backend=self.Backend}) +self.AwacsSRS=MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation,self.Backend) +self.AwacsSRS:SetCoalition(self.coalition) +self.AwacsSRS:SetGender(self.Gender) +self.AwacsSRS:SetCulture(self.Culture) +self.AwacsSRS:SetPort(self.Port) +self.AwacsSRS:SetLabel("AWACS") +self.AwacsSRS:SetVolume(Volume) +if self.PathToGoogleKey then +self.AwacsSRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey) +self.AwacsSRS:SetProvider(MSRS.Provider.GOOGLE) +end +if(not PathToGoogleKey)and self.AwacsSRS:GetProvider()==MSRS.Provider.GOOGLE then +self.PathToGoogleKey=MSRS.poptions.gcloud.credentials +self.Voice=Voice or MSRS.poptions.gcloud.voice +self.AccessKey=AccessKey or MSRS.poptions.gcloud.key +end +self.AwacsSRS:SetVoice(self.Voice) +return self +end +function AWACS:SetSRSVoiceCAP(Gender,Culture,Voice) +self:T(self.lid.."SetSRSVoiceCAP") +self.CAPGender=Gender or"male" +self.CAPCulture=Culture or"en-US" +self.CAPVoice=Voice or"en-GB-Standard-B" +return self +end +function AWACS:SetAICAPDetails(Callsign,MaxAICap,TOS,Speed) +self:T(self.lid.."SetAICAPDetails") +self.CapSpeedBase=Speed or 270 +self.CAPTimeOnStation=TOS or 4 +self.MaxAIonCAP=MaxAICap or 4 +self.AICAPCAllName=Callsign or CALLSIGN.Aircraft.Colt +return self +end +function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance) +self:T(self.lid.."SetEscort") +if EscortNumber and EscortNumber>0 then +self.HasEscorts=true +self.EscortNumber=EscortNumber +else +self.HasEscorts=false +self.EscortNumber=0 +end +self.EscortFormation=Formation +self.OffsetVec=OffsetVector or{x=500,y=100,z=500} +self.EscortEngageMaxDistance=EscortEngageMaxDistance or 45 +return self +end +function AWACS:_MessageVector(GID,Tag,Coordinate,Angels) +self:T(self.lid.."_MessageVector") +local managedgroup=self.ManagedGrps[GID] +local Tag=Tag or"" +if managedgroup and Coordinate then +local tocallsign=managedgroup.CallSign or"Ghost 1" +local group=managedgroup.Group +local groupposition=group:GetCoordinate() +local BRtext,BRtextTTS=self:_ToStringBR(groupposition,Coordinate) +local vector=self.gettext:GetEntry("VECTORTO",self.locale) +local vectortts=self.gettext:GetEntry("VECTORTOTTS",self.locale) +local angelstxt=self.gettext:GetEntry("ANGELS",self.locale) +local text=string.format(vectortts,tocallsign,self.callsigntxt,Tag,BRtextTTS) +local textScreen=string.format(vector,tocallsign,self.callsigntxt,Tag,BRtext) +if Angels then +text=text..angelstxt..tostring(Angels).."." +textScreen=textScreen..angelstxt..tostring(Angels).."." +end +self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false) +end +return self +end +function AWACS:_StartEscorts(Shiftchange) +self:T(self.lid.."_StartEscorts") +local AwacsFG=self.AwacsFG +local group=AwacsFG:GetGroup() +local timeonstation=(self.EscortsTimeOnStation+self.ShiftChangeTime)*3600 +local OffsetX=500 +local OffsetY=500 +local OffsetZ=500 +if self.OffsetVec then +OffsetX=self.OffsetVec.x or 500 +OffsetY=self.OffsetVec.y or 500 +OffsetZ=self.OffsetVec.z or 500 +end +for i=1,self.EscortNumber do +local escort=AUFTRAG:NewESCORT(group,{x=OffsetX*((i+(i%2))/2),y=OffsetY*((i+(i%2))/2),z=(OffsetZ+OffsetZ*((i+(i%2))/2))*(-1)^i},self.EscortEngageMaxDistance,{"Air"}) +escort:SetTime(nil,timeonstation) +if self.Escortformation then +escort:SetFormation(self.Escortformation) +end +escort:SetMissionRange(self.MaxMissionRange) +self.AirWing:AddMission(escort) +self.CatchAllMissions[#self.CatchAllMissions+1]=escort +if Shiftchange then +self.EscortMissionReplacement[i]=escort +else +self.EscortMission[i]=escort +end +end +return self +end +function AWACS:_StartSettings(FlightGroup,Mission) +self:T(self.lid.."_StartSettings") +local Mission=Mission +local AwacsFG=FlightGroup +if self.AwacsMission:GetName()==Mission:GetName()then +self:T("Setting up Awacs") +AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) +AwacsFG:SwitchRadio(self.Frequency,self.Modulation) +AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000) +AwacsFG:SetHomebase(self.Airbase) +AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo) +AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold) +AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN) +AwacsFG:SetDefaultEPLRS(self.ModernEra) +AwacsFG:SetDespawnAfterLanding() +AwacsFG:SetFuelLowRTB(true) +AwacsFG:SetFuelLowThreshold(20) +local group=AwacsFG:GetGroup() +group:SetCommandInvisible(self.invisible) +group:SetCommandImmortal(self.immortal) +group:CommandSetCallsign(self.CallSign,self.CallSignNo,2) +group:CommandEPLRS(self.ModernEra,5) +self.AwacsFG=AwacsFG +self.callsigntxt=string.format("%s",self.CallSignClear[self.CallSign]) +self:__CheckRadioQueue(10) +if self.HasEscorts then +self:_StartEscorts() +end +self.AwacsTimeStamp=timer.getTime() +self.EscortsTimeStamp=timer.getTime() +self.PictureTimeStamp=timer.getTime()+10*60 +self.AwacsReady=true +self:Started() +elseif self.ShiftChangeAwacsRequested and self.AwacsMissionReplacement and self.AwacsMissionReplacement:GetName()==Mission:GetName()then +self:T("Setting up Awacs Replacement") +AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) +AwacsFG:SwitchRadio(self.Frequency,self.Modulation) +AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000) +AwacsFG:SetHomebase(self.Airbase) +self.CallSignNo=self.CallSignNo+1 +AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo) +AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold) +AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN) +AwacsFG:SetDefaultEPLRS(self.ModernEra) +AwacsFG:SetDespawnAfterLanding() +AwacsFG:SetFuelLowRTB(true) +AwacsFG:SetFuelLowThreshold(20) +local group=AwacsFG:GetGroup() +group:SetCommandInvisible(self.invisible) +group:SetCommandImmortal(self.immortal) +group:CommandSetCallsign(self.CallSign,self.CallSignNo,2) +self.callsigntxt=string.format("%s",self.CallSignClear[self.CallSign]) +local shifting=self.gettext:GetEntry("SHIFTCHANGE",self.locale) +local text=string.format(shifting,self.callsigntxt,self.AOName or"Rock") +self:T(self.lid..text) +AwacsFG:RadioTransmission(text,1,false) +self.AwacsFG=AwacsFG +if self.HasEscorts then +self:_StartEscorts(true) +end +self.AwacsTimeStamp=timer.getTime() +self.EscortsTimeStamp=timer.getTime() +self.AwacsReady=true +end +return self +end +function AWACS:_ToStringBULLS(Coordinate,ssml,TTS) +self:T(self.lid.."_ToStringBULLS") +local bullseyename=self.AOName or"Rock" +local BullsCoordinate=self.AOCoordinate +local DirectionVec3=BullsCoordinate:GetDirectionVec3(Coordinate) +local AngleRadians=Coordinate:GetAngleRadians(DirectionVec3) +local Distance=Coordinate:Get2DDistance(BullsCoordinate) +local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),0) +local Bearing=string.format('%03d',AngleDegrees) +local Distance=UTILS.Round(UTILS.MetersToNM(Distance),0) +if ssml then +return string.format("%s %03d, %d",bullseyename,Bearing,Distance) +end +if TTS then +Bearing=self:_ToStringBullsTTS(Bearing) +local zero=self.gettext:GetEntry("ZERO",self.locale) +local BearingTTS=string.gsub(Bearing,"0",zero) +return string.format("%s %s, %d",bullseyename,BearingTTS,Distance) +else +return string.format("%s %s, %d",bullseyename,Bearing,Distance) +end +end +function AWACS:_ToStringBullsTTS(Text) +local text=Text +text=string.gsub(text,"Bullseye","Bulls eye") +text=string.gsub(text,"%d","%1 ") +text=string.gsub(text," ,",".") +text=string.gsub(text," $","") +return text +end +function AWACS:_GetManagedGrpID(Group) +if not Group or not Group:IsAlive()then +self:T(self.lid.."_GetManagedGrpID - Requested Group is not alive!") +return 0,false,"" +end +self:T(self.lid.."_GetManagedGrpID for "..Group:GetName()) +local GID=0 +local Outcome=false +local CallSign="Ghost 1" +local nametocheck=Group:GetName() +local managedgrps=self.ManagedGrps or{} +for _,_managed in pairs(managedgrps)do +local managed=_managed +if managed.GroupName==nametocheck then +GID=managed.GID +Outcome=true +CallSign=managed.CallSign +end +end +return GID,Outcome,CallSign +end +function AWACS:_GetCallSign(Group,GID,IsPlayer) +self:T(self.lid.."_GetCallSign - GID "..tostring(GID)) +if GID and type(GID)=="number"and GID>0 then +local managedgroup=self.ManagedGrps[GID] +self:T("Saved Callsign for TTS = "..tostring(managedgroup.CallSign)) +return managedgroup.CallSign +end +local callsign="Ghost 1" +if Group and Group:IsAlive()then +callsign=Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations,self.callsignCustomFunc,self.callsignCustomArgs) +end +return callsign +end +function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) +if not ShortCallsign or ShortCallsign==false then +self.callsignshort=false +else +self.callsignshort=true +end +self.keepnumber=Keepnumber or false +self.callsignTranslations=CallsignTranslations +self.callsignCustomFunc=CallsignCustomFunc +self.callsignCustomArgs=arg or{} +return self +end +function AWACS:_UpdateContactFromCluster(CID) +self:T(self.lid.."_UpdateContactFromCluster CID="..CID) +local existingcontact=self.Contacts:PullByID(CID) +local ContactTable=existingcontact.Cluster.Contacts or{} +local function GetFirstAliveContact(table) +for _,_contact in pairs(table)do +local contact=_contact +if contact and contact.group and contact.group:IsAlive()then +return contact +end +end +return nil +end +local NewContact=GetFirstAliveContact(ContactTable) +if NewContact then +existingcontact.Contact=NewContact +self.Contacts:Push(existingcontact,existingcontact.CID) +end +return self +end +function AWACS:_CheckMerges() +self:T(self.lid.."_CheckMerges") +for _id,_pilot in pairs(self.ManagedGrps)do +local pilot=_pilot +if pilot.Group and pilot.Group:IsAlive()then +local ppos=pilot.Group:GetCoordinate() +local pcallsign=pilot.CallSign +self:T(self.lid.."Checking for "..pcallsign) +if ppos then +self.Contacts:ForEach( +function(Contact) +local contact=Contact +local cpos=contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate() +local dist=ppos:Get2DDistance(cpos) +local distnm=UTILS.Round(UTILS.MetersToNM(dist),0) +if(pilot.IsPlayer or self.debug)and distnm<=5 then +self:T(self.lid.."Merged") +self:_MergedCall(_id) +end +if(pilot.IsPlayer or self.debug)and distnm>5 and distnm<=self.ThreatDistance then +self:_ThreatRangeCall(_id,Contact) +end +if(pilot.IsPlayer or self.debug)and distnm>self.ThreatDistance and distnm<=self.MeldDistance then +self:_MeldRangeCall(_id,Contact) +end +if(pilot.IsPlayer or self.debug)and distnm>self.MeldDistance and distnm<=self.TacDistance then +self:_TACRangeCall(_id,Contact) +end +end +) +end +end +end +return self +end +function AWACS:_CleanUpContacts() +self:T(self.lid.."_CleanUpContacts") +if self.Contacts:Count()>0 then +local deadcontacts=FIFO:New() +self.Contacts:ForEach( +function(Contact) +local contact=Contact +if not contact.Contact.group:IsAlive()or contact.Target:IsDead()or contact.Target:IsDestroyed()or contact.Target:CountTargets()==0 then +deadcontacts:Push(contact,contact.CID) +self:T("DEAD contact CID="..contact.CID) +end +end +) +if deadcontacts:Count()>0 and(not self.NoGroupTags)then +self:T("DEAD count="..deadcontacts:Count()) +deadcontacts:ForEach( +function(Contact) +local contact=Contact +local vanished=self.gettext:GetEntry("VANISHED",self.locale) +local vanishedtts=self.gettext:GetEntry("VANISHEDTTS",self.locale) +local text=string.format(vanishedtts,self.callsigntxt,contact.TargetGroupNaming) +local textScreen=string.format(vanished,self.callsigntxt,contact.TargetGroupNaming) +self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) +self.Contacts:PullByID(contact.CID) +end +) +end +if self.Contacts:Count()>0 then +self.Contacts:ForEach( +function(Contact) +local contact=Contact +self:_UpdateContactFromCluster(contact.CID) +end +) +end +deadcontacts:Clear() +end +return self +end +function AWACS:_GetIdlePilots() +self:T(self.lid.."_GetIdlePilots") +local AIPilots={} +local HumanPilots={} +for _name,_entry in pairs(self.ManagedGrps)do +local entry=_entry +self:T("Looking at entry "..entry.GID.." Name "..entry.GroupName) +local managedtask=self:_ReadAssignedTaskFromGID(entry.GID) +local overridetask=false +if managedtask then +self:T("Current task = "..(managedtask.ToDo or"Unknown")) +if managedtask.ToDo==AWACS.TaskDescription.ANCHOR then +overridetask=true +end +end +if entry.IsAI then +if entry.FlightGroup:IsAirborne()and((not entry.HasAssignedTask)or overridetask)then +self:T("Adding AI with Callsign: "..entry.CallSign) +AIPilots[#AIPilots+1]=_entry +end +elseif entry.IsPlayer and(not entry.Blocked)and(not entry.Group:IsHelicopter())then +if(not entry.HasAssignedTask)or overridetask then +local TNow=timer.getTime() +if entry.LastTasking and(TNow-entry.LastTasking>self.ReassignTime)then +self:T("Adding Human with Callsign: "..entry.CallSign) +HumanPilots[#HumanPilots+1]=_entry +end +end +end +end +return AIPilots,HumanPilots +end +function AWACS:_TargetSelectionProcess(Untargeted) +self:T(self.lid.."_TargetSelectionProcess") +local maxtargets=3 +local contactstable=self.Contacts:GetDataTable() +local targettable=FIFO:New() +local sortedtargets=FIFO:New() +local prefiltered=FIFO:New() +local HaveTargets=false +self:T(self.lid.."Initial count: "..self.Contacts:Count()) +if Untargeted then +self.Contacts:ForEach( +function(Contact) +local contact=Contact +if contact.Contact.group:IsAlive()and(contact.Status==AWACS.TaskStatus.IDLE or contact.Status==AWACS.TaskStatus.UNASSIGNED)then +if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then +if not(contact.IFF==AWACS.IFF.FRIENDLY or contact.IFF==AWACS.IFF.NEUTRAL)then +prefiltered:Push(contact,contact.CID) +end +else +prefiltered:Push(contact,contact.CID) +end +end +end +) +contactstable=prefiltered:GetDataTable() +self:T(self.lid.."Untargeted: "..prefiltered:Count()) +end +for _,_contact in pairs(contactstable)do +local contact=_contact +local checked=false +local contactname=contact.TargetGroupNaming or"ZETA" +local typename=contact.ReportingName or"Unknown" +self:T(self.lid..string.format("Looking at group %s type %s",contactname,typename)) +local contactcoord=contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate() +local contactvec2=contactcoord:GetVec2() +if self.RejectZone then +local isinrejzone=self.RejectZone:IsVec2InZone(contactvec2) +if isinrejzone then +self:T(self.lid.."Across Border = YES - ignore") +checked=true +end +end +if not self.GCI then +local HVTCoordinate=self.OrbitZone:GetCoordinate() +local distance=UTILS.NMToMeters(200) +if contactcoord then +distance=HVTCoordinate:Get2DDistance(contactcoord) +end +self:T(self.lid.."HVT Distance = "..UTILS.Round(UTILS.MetersToNM(distance),0)) +if UTILS.MetersToNM(distance)<=45 and not checked then +self:T(self.lid.."In HVT Distance = YES") +targettable:Push(contact,distance) +checked=true +end +end +local isinopszone=self.OpsZone:IsVec2InZone(contactvec2) +local distance=self.OpsZone:Get2DDistance(contactcoord) +if isinopszone and not checked then +self:T(self.lid.."In FEZ = YES") +targettable:Push(contact,distance) +checked=true +end +local isinopszone=self.ControlZone:IsVec2InZone(contactvec2) +if isinopszone and not checked then +self:T(self.lid.."In Radar Zone = YES") +local distance=self.AOCoordinate:Get2DDistance(contactcoord) +local AOdist=UTILS.Round(UTILS.MetersToNM(distance),0) +if not contactcoord.Heading then +contactcoord.Heading=self.intel:CalcClusterDirection(contact.Cluster) +end +local aspect=contactcoord:ToStringAspect(self.ControlZone:GetCoordinate()) +local sizing=contact.Cluster.size or self.intel:ClusterCountUnits(contact.Cluster)or 1 +sizing=math.fmod((sizing*0.1),1) +local AOdist2=(AOdist/2)*sizing +AOdist2=UTILS.Round((AOdist/2)+((AOdist/2)-AOdist2),0) +self:T(self.lid.."Aspect = "..aspect.." | Size = "..sizing) +if(AOdist2<75)or(aspect=="Hot")then +local text=string.format("In AO(Adj) dist = %d(%d) NM",AOdist,AOdist2) +self:T(self.lid..text) +targettable:Push(contact,distance) +checked=true +end +end +if self.BorderZone then +local isinborderzone=self.BorderZone:IsVec2InZone(contactvec2) +if isinborderzone and not checked then +self:T(self.lid.."In BorderZone = YES") +targettable:Push(contact,distance) +checked=true +end +end +end +self:T(self.lid.."Post filter count: "..targettable:Count()) +if targettable:Count()>maxtargets then +local targets=targettable:GetSortedDataTable() +targettable:Clear() +for i=1,maxtargets do +targettable:Push(targets[i]) +end +end +sortedtargets:Clear() +prefiltered:Clear() +if targettable:Count()>0 then +HaveTargets=true +end +return HaveTargets,targettable +end +function AWACS:_CreatePicture(AO,Callsign,GID,MaxEntries,IsGeneral) +self:T(self.lid.."_CreatePicture AO="..tostring(AO).." for "..Callsign.." GID "..GID) +local managedgroup=nil +local group=nil +local groupcoord=nil +if not IsGeneral then +managedgroup=self.ManagedGrps[GID] +group=managedgroup.Group +groupcoord=group:GetCoordinate() +end +local fifo=self.PictureAO +local maxentries=self.maxspeakentries or 3 +if MaxEntries and MaxEntries>0 and MaxEntries<=3 then +maxentries=MaxEntries +end +local counter=0 +if not AO then +end +local entries=fifo:GetSize() +if entries1 then +text=text.." "..threatsizetext.."." +textScreen=textScreen.." "..threatsizetext.."." +end +if contact.EngagementTag then +text=text.." "..contact.EngagementTag +textScreen=textScreen.." "..contact.EngagementTag +end +local RadioEntry_IsGroup=false +local RadioEntry_ToScreen=self.debug +if managedgroup and not IsGeneral then +RadioEntry_IsGroup=managedgroup.IsPlayer +RadioEntry_ToScreen=managedgroup.IsPlayer +end +self:_NewRadioEntry(text,textScreen,GID,RadioEntry_IsGroup,RadioEntry_ToScreen,true,false) +end +end +fifo:Clear() +return self +end +function AWACS:_CreateBogeyDope(Callsign,GID,Tactical) +self:T(self.lid.."_CreateBogeyDope for "..Callsign.." GID "..GID) +local managedgroup=self.ManagedGrps[GID] +local group=managedgroup.Group +local groupcoord=group:GetCoordinate() +local fifo=self.ContactsAO +local maxentries=1 +local counter=0 +local entries=fifo:GetSize() +if entries0 then +local IDstack=self.PictureEWR:GetSortedDataTable() +local weneed=3-clustersAO +self:T(string.format("Picture - adding %d/%d contacts from EWR",weneed,clustersEWR)) +if weneed>clustersEWR then +weneed=clustersEWR +end +for i=1,weneed do +self.PictureAO:Push(IDstack[i]) +end +end +clustersAO=self.PictureAO:GetSize() +if clustersAO==0 and clustersEWR==0 then +local picclean=self.gettext:GetEntry("PICCLEAN",self.locale) +text=string.format(picclean,gcallsign,self.callsigntxt) +textScreen=text +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +else +if clustersAO>0 then +local picture=self.gettext:GetEntry("PICTURE",self.locale) +text=string.format("%s, %s. %s. ",gcallsign,self.callsigntxt,picture) +textScreen=string.format("%s, %s. %s. ",gcallsign,self.callsigntxt,picture) +local onetxt=self.gettext:GetEntry("ONE",self.locale) +local grptxt=self.gettext:GetEntry("GROUP",self.locale) +local groupstxt=self.gettext:GetEntry("GROUPMULTI",self.locale) +if clustersAO==1 then +text=string.format("%s%s %s. ",text,onetxt,grptxt) +textScreen=string.format("%s%s %s.\n",textScreen,onetxt,grptxt) +else +text=string.format("%s%d %s. ",text,clustersAO,groupstxt) +textScreen=string.format("%s%d %s.\n",textScreen,clustersAO,groupstxt) +end +self:_NewRadioEntry(text,textScreen,GID,Outcome,true,true,false) +self:_CreatePicture(true,gcallsign,GID,3,general) +self.PictureAO:Clear() +self.PictureEWR:Clear() +end +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,gcallsign,self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_BogeyDope(Group,Tactical) +self:T(self.lid.."_BogeyDope") +local text="" +local textScreen="" +local GID,Outcome=self:_GetManagedGrpID(Group) +local gcallsign=self:_GetCallSign(Group,GID)or"Ghost 1" +if not self.intel then +local clean=self.gettext:GetEntry("CLEAN",self.locale) +text=string.format(clean,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,0,false,true,true,false,true,Tactical) +return self +end +if Outcome then +local managedgroup=self.ManagedGrps[GID] +local pilotgroup=managedgroup.Group +local pilotcoord=managedgroup.Group:GetCoordinate() +local contactstable=self.Contacts:GetDataTable() +for _,_contact in pairs(contactstable)do +local managedcontact=_contact +local contactposition=managedcontact.Cluster.coordinate or managedcontact.Contact.position +local coordVec2=contactposition:GetVec2() +local dist=pilotcoord:Get2DDistance(contactposition) +if self.ControlZone:IsVec2InZone(coordVec2)then +self.ContactsAO:Push(managedcontact,dist) +elseif self.BorderZone and self.BorderZone:IsVec2InZone(coordVec2)then +self.ContactsAO:Push(managedcontact,dist) +else +if self.OrbitZone then +local distance=contactposition:Get2DDistance(self.OrbitZone:GetCoordinate()) +if(distance<=UTILS.NMToMeters(45))then +self.ContactsAO:Push(managedcontact,distance) +end +end +end +end +local contactsAO=self.ContactsAO:GetSize() +if contactsAO==0 then +local clean=self.gettext:GetEntry("CLEAN",self.locale) +text=string.format(clean,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,Outcome,true,false,true,Tactical) +else +if contactsAO>0 then +local dope=self.gettext:GetEntry("DOPE",self.locale) +text=string.format(dope,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +textScreen=string.format(dope,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +local onetxt=self.gettext:GetEntry("ONE",self.locale) +local grptxt=self.gettext:GetEntry("GROUP",self.locale) +local groupstxt=self.gettext:GetEntry("GROUPMULTI",self.locale) +if contactsAO==1 then +text=string.format("%s%s %s. ",text,onetxt,grptxt) +textScreen=string.format("%s%s %s.\n",textScreen,onetxt,grptxt) +else +text=string.format("%s%d %s. ",text,contactsAO,groupstxt) +textScreen=string.format("%s%d %s.\n",textScreen,contactsAO,groupstxt) +end +self:_NewRadioEntry(text,textScreen,GID,Outcome,true,true,false,true,Tactical) +self:_CreateBogeyDope(self:_GetCallSign(Group,GID)or"Ghost 1",GID,Tactical) +end +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,Tactical) +end +return self +end +function AWACS:_ShowAwacsInfo(Group) +self:T(self.lid.."_ShowAwacsInfo") +local report=REPORT:New("Info") +local STN=self.STN +report:Add("====================") +report:Add(string.format("AWACS %s",self.callsigntxt)) +report:Add(string.format("Radio: %.3f %s",self.Frequency,UTILS.GetModulationName(self.Modulation))) +if STN then +report:Add(string.format("Link-16 STN: %s",STN)) +end +report:Add(string.format("Bulls Alias: %s",self.AOName)) +report:Add(string.format("Coordinate: %s",self.AOCoordinate:ToStringLLDDM())) +report:Add("====================") +report:Add(string.format("Assignment Distance: %d NM",self.maxassigndistance)) +report:Add(string.format("TAC Distance: %d NM",self.TacDistance)) +report:Add(string.format("MELD Distance: %d NM",self.MeldDistance)) +report:Add(string.format("THREAT Distance: %d NM",self.ThreatDistance)) +report:Add("====================") +report:Add(string.format("ROE/ROT: %s, %s",self.AwacsROE,self.AwacsROT)) +MESSAGE:New(report:Text(),45,"AWACS"):ToGroup(Group) +return self +end +function AWACS:_VID(Group,Declaration) +self:T(self.lid.."_VID") +local GID,Outcome,Callsign=self:_GetManagedGrpID(Group) +local text="" +local TextTTS="" +if Outcome then +local managedgroup=self.ManagedGrps[GID] +local group=managedgroup.Group +local position=group:GetCoordinate() +local radius=UTILS.NMToMeters(self.DeclareRadius)or UTILS.NMToMeters(5) +local TID=managedgroup.CurrentTask or 0 +if TID>0 then +local task=self.ManagedTasks:ReadByID(TID) +if task.ToDo~=AWACS.TaskDescription.VID then +return self +end +if task.Status~=AWACS.TaskStatus.ASSIGNED then +return self +end +local CID=task.Cluster.CID +local cluster=self.Contacts:ReadByID(CID) +if cluster then +local gposition=cluster.Contact.group:GetCoordinate() +local cposition=gposition or cluster.Cluster.coordinate or cluster.Contact.position +local distance=cposition:Get2DDistance(position) +distance=UTILS.Round(distance,0)+1 +if distance<=radius or self.debug then +self:T("Contact VID as "..Declaration) +cluster.IFF=Declaration +task.Status=AWACS.TaskStatus.SUCCESS +self.ManagedTasks:PullByID(TID) +self.ManagedTasks:Push(task,TID) +self.Contacts:PullByID(CID) +self.Contacts:Push(cluster,CID) +local vidpos=self.gettext:GetEntry("VIDPOS",self.locale) +text=string.format(vidpos,Callsign,self.callsigntxt,Declaration) +self:T(text) +self:__VIDSuccess(3,GID,group,cluster) +else +self:T("Contact VID not close enough") +local vidneg=self.gettext:GetEntry("VIDNEG",self.locale) +text=string.format(vidneg,Callsign,self.callsigntxt) +self:T(text) +self:__VIDFailure(3,GID,group,cluster) +end +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) +end +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_Declare(Group) +self:T(self.lid.."_Declare") +local GID,Outcome,Callsign=self:_GetManagedGrpID(Group) +local text="" +local TextTTS="" +if Outcome then +local managedgroup=self.ManagedGrps[GID] +local group=managedgroup.Group +local position=group:GetCoordinate() +local radius=UTILS.NMToMeters(self.DeclareRadius)or UTILS.NMToMeters(5) +local groupzone=ZONE_GROUP:New(group:GetName(),group,radius) +local Coalitions={"red","neutral"} +if self.coalition==coalition.side.NEUTRAL then +Coalitions={"red","blue"} +elseif self.coalition==coalition.side.RED then +Coalitions={"blue","neutral"} +end +local contactset=SET_GROUP:New():FilterCategoryAirplane():FilterCoalitions(Coalitions):FilterZones({groupzone}):FilterOnce() +local numbercontacts=contactset:CountAlive()or 0 +local foundcontacts={} +if numbercontacts>0 then +contactset:ForEach( +function(airpl) +local distance=position:Get2DDistance(airpl:GetCoordinate()) +distance=UTILS.Round(distance,0)+1 +foundcontacts[distance]=airpl +end +,{} +) +for _dist,_contact in UTILS.spairs(foundcontacts)do +local distanz=_dist +local contact=_contact +local ccoalition=contact:GetCoalition() +local ctypename=contact:GetTypeName() +local ffneutral=self.gettext:GetEntry("FFNEUTRAL",self.locale) +local fffriend=self.gettext:GetEntry("FFFRIEND",self.locale) +local ffhostile=self.gettext:GetEntry("FFHOSTILE",self.locale) +local ffspades=self.gettext:GetEntry("FFSPADES",self.locale) +local friendorfoe=ffneutral +if self.self.ModernEra then +if ccoalition==self.coalition then +friendorfoe=fffriend +elseif ccoalition==coalition.side.NEUTRAL then +friendorfoe=ffneutral +elseif ccoalition~=self.coalition then +friendorfoe=ffhostile +end +else +friendorfoe=ffspades +end +self:T(string.format("Distance %d ContactName %s Coalition %d (%s) TypeName %s",distanz,contact:GetName(),ccoalition,friendorfoe,ctypename)) +text=string.format("%s. %s. %s.",Callsign,self.callsigntxt,friendorfoe) +TextTTS=text +if self.ModernEra then +text=string.format("%s %s.",text,ctypename) +end +break +end +else +local ffclean=self.gettext:GetEntry("FFCLEAN",self.locale) +text=string.format("%s. %s. %s.",Callsign,self.callsigntxt,ffclean) +TextTTS=text +end +self:_NewRadioEntry(TextTTS,text,GID,Outcome,true,true,false,true) +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_Commit(Group) +self:T(self.lid.."_Commit") +local GID,Outcome=self:_GetManagedGrpID(Group) +local text="" +if Outcome then +local Pilot=self.ManagedGrps[GID] +local currtaskid=Pilot.CurrentTask +local managedtask=self.ManagedTasks:ReadByID(currtaskid) +self:T(string.format("TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status)) +if managedtask then +if managedtask.Status==AWACS.TaskStatus.REQUESTED then +managedtask=self.ManagedTasks:PullByID(currtaskid) +managedtask.Status=AWACS.TaskStatus.ASSIGNED +self.ManagedTasks:Push(managedtask,currtaskid) +self:T(string.format("COMMITTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status)) +Pilot.HasAssignedTask=true +Pilot.CurrentTask=currtaskid +self.ManagedGrps[GID]=Pilot +local copy=self.gettext:GetEntry("COPY",self.locale) +local targetedby=self.gettext:GetEntry("TARGETEDBY",self.locale) +text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +local EngagementTag=string.format(targetedby,Pilot.CallSign) +self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.ASSIGNED) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) +else +self:E(self.lid.."Cannot find REQUESTED managed task with TID="..currtaskid.." for GID="..GID) +end +else +self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID) +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_Judy(Group) +self:T(self.lid.."_Judy") +local GID,Outcome=self:_GetManagedGrpID(Group) +local text="" +if Outcome then +local Pilot=self.ManagedGrps[GID] +local currtaskid=Pilot.CurrentTask +local managedtask=self.ManagedTasks:ReadByID(currtaskid) +if managedtask then +if managedtask.Status==AWACS.TaskStatus.REQUESTED or managedtask.Status==AWACS.TaskStatus.UNASSIGNED then +managedtask=self.ManagedTasks:PullByID(currtaskid) +managedtask.Status=AWACS.TaskStatus.ASSIGNED +self.ManagedTasks:Push(managedtask,currtaskid) +local copy=self.gettext:GetEntry("COPY",self.locale) +local targetedby=self.gettext:GetEntry("TARGETEDBY",self.locale) +text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +local EngagementTag=string.format(targetedby,Pilot.CallSign) +self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.ASSIGNED) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) +else +self:E(self.lid.."Cannot find REQUESTED or UNASSIGNED managed task with TID="..currtaskid.." for GID="..GID) +end +else +self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID) +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_Unable(Group) +self:T(self.lid.."_Unable") +local GID,Outcome=self:_GetManagedGrpID(Group) +local text="" +if Outcome then +local Pilot=self.ManagedGrps[GID] +local currtaskid=Pilot.CurrentTask +local managedtask=self.ManagedTasks:ReadByID(currtaskid) +self:T(string.format("UNABLE for TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status)) +if managedtask then +if managedtask.Status==AWACS.TaskStatus.REQUESTED then +managedtask=self.ManagedTasks:PullByID(currtaskid) +managedtask.IsUnassigned=true +managedtask.Status=AWACS.TaskStatus.FAILED +self.ManagedTasks:Push(managedtask,currtaskid) +self:T(string.format("REJECTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status)) +Pilot.HasAssignedTask=false +Pilot.CurrentTask=0 +Pilot.LastTasking=timer.getTime() +self.ManagedGrps[GID]=Pilot +local copy=self.gettext:GetEntry("COPY",self.locale) +text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +local EngagementTag="" +self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.UNASSIGNED) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) +else +self:E(self.lid.."Cannot find REQUESTED managed task with TID="..currtaskid.." for GID="..GID) +end +else +self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID) +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_TaskAbort(Group) +self:T(self.lid.."_TaskAbort") +local Outcome,GID=self:_GetGIDFromGroupOrName(Group) +local text="" +if Outcome then +local Pilot=self.ManagedGrps[GID] +self:T({Pilot}) +local currtaskid=Pilot.CurrentTask +local managedtask=self.ManagedTasks:ReadByID(currtaskid) +if managedtask then +self:T(string.format("ABORT for TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status)) +if managedtask.Status==AWACS.TaskStatus.ASSIGNED then +managedtask=self.ManagedTasks:PullByID(currtaskid) +managedtask.Status=AWACS.TaskStatus.FAILED +managedtask.IsUnassigned=true +self.ManagedTasks:Push(managedtask,currtaskid) +self:T(string.format("ABORTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status)) +Pilot.HasAssignedTask=false +Pilot.CurrentTask=0 +Pilot.LastTasking=timer.getTime() +self.ManagedGrps[GID]=Pilot +local copy=self.gettext:GetEntry("COPY",self.locale) +text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +local EngagementTag="" +self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.UNASSIGNED) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) +else +self:E(self.lid.."Cannot find ASSIGNED managed task with TID="..currtaskid.." for GID="..GID) +end +else +self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID) +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_Showtask(Group) +self:T(self.lid.."_Showtask") +local GID,Outcome,Callsign=self:_GetManagedGrpID(Group) +local text="" +if Outcome then +local managedgroup=self.ManagedGrps[GID] +if managedgroup.IsPlayer then +if managedgroup.CurrentTask>0 and self.ManagedTasks:HasUniqueID(managedgroup.CurrentTask)then +local currenttask=self.ManagedTasks:ReadByID(managedgroup.CurrentTask) +if currenttask then +local status=currenttask.Status +local targettype=currenttask.Target:GetCategory() +local targetstatus=currenttask.Target:GetState() +local ToDo=currenttask.ToDo +local description=currenttask.ScreenText +local descTTS=currenttask.ScreenText +local callsign=Callsign +if self.debug then +local taskreport=REPORT:New("AWACS Tasking Display") +taskreport:Add("===============") +taskreport:Add(string.format("Task for Callsign: %s",Callsign)) +taskreport:Add(string.format("Task: %s with Status: %s",ToDo,status)) +taskreport:Add(string.format("Target of Type: %s",targettype)) +taskreport:Add(string.format("Target in State: %s",targetstatus)) +taskreport:Add("===============") +self:I(taskreport:Text()) +end +local pposition=managedgroup.Group:GetCoordinate()or managedgroup.LastKnownPosition +if currenttask.ToDo==AWACS.TaskDescription.INTERCEPT or currenttask.ToDo==AWACS.TaskDescription.VID then +local targetpos=currenttask.Target:GetCoordinate() +if pposition and targetpos then +local alti=currenttask.Cluster.altitude or currenttask.Contact.altitude or currenttask.Contact.group:GetAltitude() +local direction,direcTTS=self:_ToStringBRA(pposition,targetpos,alti) +description=description.."\nBRA "..direction +descTTS=descTTS..";BRA "..direcTTS +end +elseif currenttask.ToDo==AWACS.TaskDescription.ANCHOR or currenttask.ToDo==AWACS.TaskDescription.REANCHOR then +local targetpos=currenttask.Target:GetCoordinate() +local direction,direcTTS=self:_ToStringBR(pposition,targetpos) +description=description.."\nBR "..direction +descTTS=descTTS..";BR "..direcTTS +end +local statustxt=self.gettext:GetEntry("STATUS",self.locale) +local text=string.format("%s\n%s %s",description,statustxt,status) +local ttstext=string.format("%s. %s. %s",managedgroup.CallSign,self.callsigntxt,descTTS) +ttstext=string.gsub(ttstext,"\\n",";") +ttstext=string.gsub(ttstext,"VID","V I D") +self:_NewRadioEntry(ttstext,text,GID,true,true,false,false,true) +end +end +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) +end +return self +end +function AWACS:_CheckIn(Group) +self:T(self.lid.."_CheckIn "..Group:GetName()) +local GID,Outcome=self:_GetManagedGrpID(Group) +local text="" +local textTTS="" +if not Outcome then +self.ManagedGrpID=self.ManagedGrpID+1 +local managedgroup={} +managedgroup.Group=Group +managedgroup.GroupName=Group:GetName() +managedgroup.IsPlayer=true +managedgroup.IsAI=false +managedgroup.CallSign=self:_GetCallSign(Group,GID,true)or"Ghost 1" +managedgroup.CurrentAuftrag=0 +managedgroup.CurrentTask=0 +managedgroup.HasAssignedTask=true +managedgroup.Blocked=true +managedgroup.GID=self.ManagedGrpID +managedgroup.LastKnownPosition=Group:GetCoordinate() +managedgroup.LastTasking=timer.getTime() +GID=managedgroup.GID +self.ManagedGrps[self.ManagedGrpID]=managedgroup +local alphacheckbulls=self:_ToStringBULLS(Group:GetCoordinate()) +local alphacheckbullstts=self:_ToStringBULLS(Group:GetCoordinate(),false,true) +local alpha=self.gettext:GetEntry("ALPHACHECK",self.locale) +text=string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbulls) +textTTS=string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbullstts) +self:__CheckedIn(1,managedgroup.GID) +if self.PlayerStationName then +self:__AssignAnchor(5,managedgroup.GID,true,self.PlayerStationName) +else +self:__AssignAnchor(5,managedgroup.GID) +end +elseif self.AwacsFG then +local nocheckin=self.gettext:GetEntry("ALREADYCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +textTTS=text +end +self:_NewRadioEntry(textTTS,text,GID,Outcome,true,true,false) +return self +end +function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) +self:T(self.lid.."_CheckInAI "..Group:GetName().." to Auftrag Nr "..AuftragsNr) +local GID,Outcome=self:_GetManagedGrpID(Group) +local text="" +if not Outcome then +self.ManagedGrpID=self.ManagedGrpID+1 +local managedgroup={} +managedgroup.Group=Group +managedgroup.GroupName=Group:GetName() +managedgroup.FlightGroup=FlightGroup +managedgroup.IsPlayer=false +managedgroup.IsAI=true +local callsignstring=UTILS.GetCallsignName(self.AICAPCAllName) +if self.callsignTranslations and self.callsignTranslations[callsignstring]then +callsignstring=self.callsignTranslations[callsignstring] +end +local callsignmajor=math.fmod(self.AICAPCAllNumber,9) +local callsign=string.format("%s %d 1",callsignstring,callsignmajor) +if self.callsignshort then +callsign=string.format("%s %d",callsignstring,callsignmajor) +end +self:T("Assigned Callsign: "..callsign) +managedgroup.CallSign=callsign +managedgroup.CurrentAuftrag=AuftragsNr +managedgroup.HasAssignedTask=false +managedgroup.GID=self.ManagedGrpID +managedgroup.LastKnownPosition=Group:GetCoordinate() +self.ManagedGrps[self.ManagedGrpID]=managedgroup +FlightGroup:SetDefaultRadio(self.Frequency,self.Modulation,false) +FlightGroup:SwitchRadio(self.Frequency,self.Modulation) +local CAPVoice=self.CAPVoice +if self.PathToGoogleKey then +CAPVoice=self.CapVoices[math.floor(math.random(1,10))] +end +FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,CAPVoice,self.Port,self.PathToGoogleKey,"FLIGHT",1) +local checkai=self.gettext:GetEntry("CHECKINAI",self.locale) +text=string.format(checkai,self.callsigntxt,managedgroup.CallSign,self.CAPTimeOnStation,self.AOName) +self:_NewRadioEntry(text,text,managedgroup.GID,Outcome,false,true,true) +local alphacheckbulls=self:_ToStringBULLS(Group:GetCoordinate(),false,true) +local alpha=self.gettext:GetEntry("ALPHACHECK",self.locale) +text=string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbulls) +self:__CheckedIn(1,managedgroup.GID) +local AW=FlightGroup.legion +if AW.HasOwnStation then +self:__AssignAnchor(5,managedgroup.GID,AW.HasOwnStation,AW.StationName) +else +self:__AssignAnchor(5,managedgroup.GID) +end +else +local nocheckin=self.gettext:GetEntry("ALREADYCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +end +self:_NewRadioEntry(text,text,GID,Outcome,false,true,false) +return self +end +function AWACS:_CheckOut(Group,GID,dead) +self:T(self.lid.."_CheckOut") +local GID,Outcome=self:_GetManagedGrpID(Group) +local text="" +if Outcome then +local safeflight=self.gettext:GetEntry("SAFEFLIGHT",self.locale) +text=string.format(safeflight,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +self:T(text) +local managedgroup=self.ManagedGrps[GID] +local Stack=managedgroup.AnchorStackNo +local Angels=managedgroup.AnchorStackAngels +local GroupName=managedgroup.GroupName +if managedgroup.IsPlayer then +if self.clientmenus:HasUniqueID(GroupName)then +local menus=self.clientmenus:PullByID(GroupName) +menus.basemenu:Remove() +if self.TacticalSubscribers[GroupName]then +local Freq=self.TacticalSubscribers[GroupName] +self.TacticalFrequencies[Freq]=Freq +self.TacticalSubscribers[GroupName]=nil +end +end +end +if managedgroup.CurrentTask and managedgroup.CurrentTask>0 then +self.ManagedTasks:PullByID(managedgroup.CurrentTask) +self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false) +end +self.ManagedGrps[GID]=nil +self:__CheckedOut(1,GID,Stack,Angels) +else +if not dead then +local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale) +text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt) +end +end +if not dead then +self:_NewRadioEntry(text,text,GID,Outcome,false,true,false) +end +return self +end +function AWACS:_SetClientMenus() +self:T(self.lid.."_SetClientMenus") +local clientset=self.clientset +local aliveset=clientset:GetSetObjects()or{} +local clientcount=0 +local clientcheckedin=0 +for _,_group in pairs(aliveset)do +local grp=_group +local cgrp=grp:GetGroup() +local cgrpname=nil +if cgrp and cgrp:IsAlive()then +cgrpname=cgrp:GetName() +self:T(cgrpname) +end +if self.MenuStrict then +if cgrp and cgrp:IsAlive()then +clientcount=clientcount+1 +local GID,checkedin=self:_GetManagedGrpID(cgrp) +if checkedin then +clientcheckedin=clientcheckedin+1 +local hasclientmenu=self.clientmenus:ReadByID(cgrpname) +local basemenu=hasclientmenu.basemenu +if hasclientmenu and(not hasclientmenu.menuset)then +self:T(self.lid.."Setting Menus for "..cgrpname) +basemenu:RemoveSubMenus() +local bogeydope=MENU_GROUP_COMMAND:New(cgrp,"Bogey Dope",basemenu,self._BogeyDope,self,cgrp) +local picture=MENU_GROUP_COMMAND:New(cgrp,"Picture",basemenu,self._Picture,self,cgrp) +local declare=MENU_GROUP_COMMAND:New(cgrp,"Declare",basemenu,self._Declare,self,cgrp) +local tasking=MENU_GROUP:New(cgrp,"Tasking",basemenu) +local showtask=MENU_GROUP_COMMAND:New(cgrp,"Showtask",tasking,self._Showtask,self,cgrp) +local commit +local unable +local abort +if self.PlayerCapAssignment then +commit=MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp) +unable=MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp) +abort=MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp) +end +if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then +local vid=MENU_GROUP:New(cgrp,"VID as",tasking) +local hostile=MENU_GROUP_COMMAND:New(cgrp,"Hostile",vid,self._VID,self,cgrp,AWACS.IFF.ENEMY) +local neutral=MENU_GROUP_COMMAND:New(cgrp,"Neutral",vid,self._VID,self,cgrp,AWACS.IFF.NEUTRAL) +local friendly=MENU_GROUP_COMMAND:New(cgrp,"Friendly",vid,self._VID,self,cgrp,AWACS.IFF.FRIENDLY) +end +local tactical +if self.TacticalMenu then +tactical=MENU_GROUP:New(cgrp,"Tactical Radio",basemenu) +if self.TacticalSubscribers[cgrpname]then +local entry=MENU_GROUP_COMMAND:New(cgrp,"Unsubscribe",tactical,self._UnsubScribeTactRadio,self,cgrp) +else +for _,_freq in UTILS.spairs(self.TacticalFrequencies)do +local modu=UTILS.GetModulationName(self.TacticalModulation) +local text=string.format("Subscribe to %.3f %s",_freq,modu) +local entry=MENU_GROUP_COMMAND:New(cgrp,text,tactical,self._SubScribeTactRadio,self,cgrp,_freq) +end +end +end +local ainfo=MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp) +local checkout=MENU_GROUP_COMMAND:New(cgrp,"Check Out",basemenu,self._CheckOut,self,cgrp) +local menus={ +groupname=cgrpname, +menuset=true, +basemenu=basemenu, +checkout=checkout, +picture=picture, +bogeydope=bogeydope, +declare=declare, +tasking=tasking, +showtask=showtask, +unable=unable, +abort=abort, +commit=commit, +tactical=tactical, +} +self.clientmenus:PullByID(cgrpname) +self.clientmenus:Push(menus,cgrpname) +end +elseif not self.clientmenus:HasUniqueID(cgrpname)then +local basemenu=MENU_GROUP:New(cgrp,self.Name,nil) +local checkin=MENU_GROUP_COMMAND:New(cgrp,"Check In",basemenu,self._CheckIn,self,cgrp) +checkin:SetTag(cgrp:GetName()) +basemenu:Refresh() +local menus={ +groupname=cgrpname, +menuset=false, +basemenu=basemenu, +checkin=checkin, +} +self.clientmenus:Push(menus,cgrpname) +local GID,hasentry=self:_GetManagedGrpID(cgrp) +if hasentry then +self:_CheckOut(cgrp,GID,true) +end +end +end +else +if cgrp and cgrp:IsAlive()and not self.clientmenus:HasUniqueID(cgrpname)then +local basemenu=MENU_GROUP:New(cgrp,self.Name,nil) +local picture=MENU_GROUP_COMMAND:New(cgrp,"Picture",basemenu,self._Picture,self,cgrp) +local bogeydope=MENU_GROUP_COMMAND:New(cgrp,"Bogey Dope",basemenu,self._BogeyDope,self,cgrp) +local declare=MENU_GROUP_COMMAND:New(cgrp,"Declare",basemenu,self._Declare,self,cgrp) +local tasking=MENU_GROUP:New(cgrp,"Tasking",basemenu) +local showtask=MENU_GROUP_COMMAND:New(cgrp,"Showtask",tasking,self._Showtask,self,cgrp) +local commit=MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp) +local unable=MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp) +local abort=MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp) +if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then +local vid=MENU_GROUP:New(cgrp,"VID as",tasking) +local hostile=MENU_GROUP_COMMAND:New(cgrp,"Hostile",vid,self._VID,self,cgrp,AWACS.IFF.ENEMY) +local neutral=MENU_GROUP_COMMAND:New(cgrp,"Neutral",vid,self._VID,self,cgrp,AWACS.IFF.NEUTRAL) +local friendly=MENU_GROUP_COMMAND:New(cgrp,"Friendly",vid,self._VID,self,cgrp,AWACS.IFF.FRIENDLY) +end +local ainfo=MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp) +local checkin=MENU_GROUP_COMMAND:New(cgrp,"Check In",basemenu,self._CheckIn,self,cgrp) +local checkout=MENU_GROUP_COMMAND:New(cgrp,"Check Out",basemenu,self._CheckOut,self,cgrp) +basemenu:Refresh() +local menus={ +groupname=cgrpname, +menuset=true, +basemenu=basemenu, +checkin=checkin, +checkout=checkout, +picture=picture, +bogeydope=bogeydope, +declare=declare, +showtask=showtask, +tasking=tasking, +unable=unable, +abort=abort, +commit=commit, +} +self.clientmenus:Push(menus,cgrpname) +end +end +end +self.MonitoringData.Players=clientcount or 0 +self.MonitoringData.PlayersCheckedin=clientcheckedin or 0 +return self +end +function AWACS:_DeleteAnchorStackFromMarker(Name,Coord) +self:T(self.lid.."_DeleteAnchorStackFromMarker") +if self.AnchorStacks:HasUniqueID(Name)and self.PlayerStationName==Name then +local stack=self.AnchorStacks:ReadByID(Name) +local marker=stack.AnchorMarker +if stack.AnchorAssignedID:Count()==0 then +marker:Remove() +if self.debug then +stack.StationZone:UndrawZone() +end +self.AnchorStacks:PullByID(Name) +self.PlayerStationName=nil +else +if self.debug then +self:I(self.lid.."**** Cannot delete station, there are CAPs assigned!") +local text=marker:GetText() +marker:TextUpdate(text.."\nMarked for deletion") +end +end +end +return self +end +function AWACS:_MoveAnchorStackFromMarker(Name,Coord) +self:T(self.lid.."_MoveAnchorStackFromMarker") +if self.AnchorStacks:HasUniqueID(Name)and self.PlayerStationName==Name then +local station=self.AnchorStacks:PullByID(Name) +local stationtag=string.format("Station: %s\nCoordinate: %s",Name,Coord:ToStringLLDDM()) +local marker=station.AnchorMarker +local zone=station.StationZone +if self.debug then +zone:UndrawZone() +end +local radius=self.StationZone:GetRadius() +if radius<10000 then radius=10000 end +station.StationZone=ZONE_RADIUS:New(Name,Coord:GetVec2(),radius) +marker:UpdateCoordinate(Coord) +marker:UpdateText(stationtag) +station.AnchorMarker=marker +if self.debug then +station.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) +end +self.AnchorStacks:Push(station,Name) +end +return self +end +function AWACS:_CreateAnchorStackFromMarker(Name,Coord) +self:T(self.lid.."_CreateAnchorStackFromMarker") +local AnchorStackOne={} +AnchorStackOne.AnchorBaseAngels=self.AnchorBaseAngels +AnchorStackOne.Anchors=FIFO:New() +AnchorStackOne.AnchorAssignedID=FIFO:New() +local newname=Name +for i=1,self.AnchorMaxStacks do +AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) +end +local radius=self.StationZone:GetRadius() +if radius<10000 then radius=10000 end +AnchorStackOne.StationZone=ZONE_RADIUS:New(newname,Coord:GetVec2(),radius) +AnchorStackOne.StationZoneCoordinate=Coord +AnchorStackOne.StationZoneCoordinateText=Coord:ToStringLLDDM() +AnchorStackOne.StationName=newname +if self.debug then +AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) +local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then +AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +else +local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then +AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +end +self.AnchorStacks:Push(AnchorStackOne,newname) +self.PlayerStationName=newname +return self +end +function AWACS:_CreateAnchorStack() +self:T(self.lid.."_CreateAnchorStack") +local stackscreated=self.AnchorStacks:GetSize() +if stackscreated==self.AnchorMaxAnchors then +return false,0 +end +local AnchorStackOne={} +AnchorStackOne.AnchorBaseAngels=self.AnchorBaseAngels +AnchorStackOne.Anchors=FIFO:New() +AnchorStackOne.AnchorAssignedID=FIFO:New() +local newname=self.StationZone:GetName() +for i=1,self.AnchorMaxStacks do +AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) +end +if stackscreated==0 then +local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) +newname=self.StationZone:GetName().."-"..newsubname +AnchorStackOne.StationZone=self.StationZone +AnchorStackOne.StationZoneCoordinate=self.StationZone:GetCoordinate() +AnchorStackOne.StationZoneCoordinateText=self.StationZone:GetCoordinate():ToStringLLDDM() +AnchorStackOne.StationName=newname +if self.debug then +AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) +local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then +AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +else +local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then +AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +end +self.AnchorStacks:Push(AnchorStackOne,newname) +else +local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) +newname=self.StationZone:GetName().."-"..newsubname +local anchorbasecoord=self.OpsZone:GetCoordinate() +local anchorradius=anchorbasecoord:Get2DDistance(self.StationZone:GetCoordinate()) +local angel=self.StationZone:GetCoordinate():GetAngleDegrees(self.OpsZone:GetVec3()) +self:T("Angel Radians= "..angel) +local turn=math.fmod(self.AnchorTurn*stackscreated,360) +if self.AnchorTurn<0 then turn=-turn end +local newanchorbasecoord=anchorbasecoord:Translate(anchorradius,turn+angel) +local radius=self.StationZone:GetRadius() +if radius<10000 then radius=10000 end +AnchorStackOne.StationZone=ZONE_RADIUS:New(newname,newanchorbasecoord:GetVec2(),radius) +AnchorStackOne.StationZoneCoordinate=newanchorbasecoord +AnchorStackOne.StationZoneCoordinateText=newanchorbasecoord:ToStringLLDDM() +AnchorStackOne.StationName=newname +if self.debug then +AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) +local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then +AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +else +local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then +AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +end +self.AnchorStacks:Push(AnchorStackOne,newname) +end +return true,self.AnchorStacks:GetSize() +end +function AWACS:_GetFreeAnchorStack() +self:T(self.lid.."_GetFreeAnchorStack") +local AnchorStackNo,Free=0,false +local availablestacks=self.AnchorStacks:GetPointerStack()or{} +for _id,_entry in pairs(availablestacks)do +local entry=_entry +local data=entry.data +if data.Anchors:IsNotEmpty()then +AnchorStackNo=_id +Free=true +break +end +end +if not Free then +local created,number=self:_CreateAnchorStack() +if created then +self:_GetFreeAnchorStack() +end +end +return AnchorStackNo,Free +end +function AWACS:_AssignAnchorToID(GID,HasOwnStation,StationName) +self:T(self.lid.."_AssignAnchorToID") +if not HasOwnStation then +local AnchorStackNo,Free=self:_GetFreeAnchorStack() +if Free then +local Anchor=self.AnchorStacks:PullByPointer(AnchorStackNo) +local freeangels=Anchor.Anchors:Pull() +Anchor.AnchorAssignedID:Push(GID) +self.AnchorStacks:Push(Anchor,Anchor.StationName) +self:T({Anchor,freeangels}) +self:__AssignedAnchor(5,GID,Anchor,AnchorStackNo,freeangels) +else +self:E(self.lid.."Cannot assign free anchor stack to GID "..GID.." Trying again in 10secs.") +self:__AssignAnchor(10,GID) +end +else +local Anchor=self.AnchorStacks:PullByID(StationName) +local freeangels=Anchor.Anchors:Pull()or 25 +Anchor.AnchorAssignedID:Push(GID) +self.AnchorStacks:Push(Anchor,StationName) +self:T({Anchor,freeangels}) +local StackNo=self.AnchorStacks.stackbyid[StationName].pointer +self:__AssignedAnchor(5,GID,Anchor,StackNo,freeangels) +end +return self +end +function AWACS:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels) +local gid=GID or 0 +local stack=AnchorStackNo or 0 +local angels=Angels or 0 +local debugstring=string.format("%s_RemoveIDFromAnchor for GID=%d Stack=%d Angels=%d",self.lid,gid,stack,angels) +self:T(debugstring) +if stack>0 and angels>0 then +local AnchorStackNo=AnchorStackNo or 1 +local Anchor=self.AnchorStacks:ReadByPointer(AnchorStackNo) +local removedID=Anchor.AnchorAssignedID:PullByID(GID) +Anchor.Anchors:Push(Angels) +end +return self +end +function AWACS:_StartIntel(awacs) +self:T(self.lid.."_StartIntel") +if self.intelstarted then return self end +self.DetectionSet:AddGroup(awacs) +local intel=INTEL:New(self.DetectionSet,self.coalition,self.callsigntxt) +intel:SetClusterAnalysis(true,false,false) +local acceptzoneset=SET_ZONE:New() +acceptzoneset:AddZone(self.ControlZone) +acceptzoneset:AddZone(self.OpsZone) +if not self.GCI then +self.OrbitZone:SetRadius(UTILS.NMToMeters(55)) +acceptzoneset:AddZone(self.OrbitZone) +end +if self.BorderZone then +acceptzoneset:AddZone(self.BorderZone) +end +intel:SetAcceptZones(acceptzoneset) +if self.NoHelos then +intel:SetFilterCategory({Unit.Category.AIRPLANE}) +else +intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) +end +local function NewCluster(Cluster) +self:__NewCluster(5,Cluster) +end +function intel:OnAfterNewCluster(From,Event,To,Cluster) +NewCluster(Cluster) +end +local function NewContact(Contact) +self:__NewContact(5,Contact) +end +function intel:OnAfterNewContact(From,Event,To,Contact) +NewContact(Contact) +end +local function LostContact(Contact) +self:__LostContact(5,Contact) +end +function intel:OnAfterLostContact(From,Event,To,Contact) +LostContact(Contact) +end +local function LostCluster(Cluster,Mission) +self:__LostCluster(5,Cluster,Mission) +end +function intel:OnAfterLostCluster(From,Event,To,Cluster,Mission) +LostCluster(Cluster,Mission) +end +self.intelstarted=true +intel.statusupdate=-30 +intel:__Start(5) +self.intel=intel +return self +end +function AWACS:_GetBlurredSize(size) +self:T(self.lid.."_GetBlurredSize") +local threatsize=0 +local blur=self.RadarBlur +local blurmin=100-blur +local blurmax=100+blur +local actblur=math.random(blurmin,blurmax)/100 +threatsize=math.floor(size*actblur) +if threatsize==0 then threatsize=1 end +if threatsize then end +local threatsizetext=AWACS.Shipsize[1] +if threatsize==2 then +threatsizetext=AWACS.Shipsize[2] +elseif threatsize==3 then +threatsizetext=AWACS.Shipsize[3] +elseif threatsize>3 then +threatsizetext=AWACS.Shipsize[4] +end +return threatsize,threatsizetext +end +function AWACS:_GetThreatLevelText(threatlevel) +self:T(self.lid.."_GetThreatLevelText") +local threattext="GREEN" +if threatlevel<=AWACS.THREATLEVEL.GREEN then +threattext="GREEN" +elseif threatlevel<=AWACS.THREATLEVEL.AMBER then +threattext="AMBER" +else +threattext="RED" +end +return threattext +end +function AWACS:_ToStringBR(FromCoordinate,ToCoordinate) +self:T(self.lid.."_ToStringBR") +local BRText="" +local BRTextTTS="" +local DirectionVec3=FromCoordinate:GetDirectionVec3(ToCoordinate) +local AngleRadians=FromCoordinate:GetAngleRadians(DirectionVec3) +local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),0) +local AngleDegText=string.format("%03d",AngleDegrees) +local AngleDegTextTTS="" +local zero=self.gettext:GetEntry("ZERO",self.locale) +local miles=self.gettext:GetEntry("MILES",self.locale) +AngleDegText=string.gsub(AngleDegText,"%d","%1 ") +AngleDegText=string.gsub(AngleDegText," $","") +AngleDegTextTTS=string.gsub(AngleDegText,"0",zero) +local Distance=ToCoordinate:Get2DDistance(FromCoordinate) +local distancenm=UTILS.Round(UTILS.MetersToNM(Distance),0) +BRText=string.format("%03d, %d %s",AngleDegrees,distancenm,miles) +BRTextTTS=string.format("%s, %d %s",AngleDegText,distancenm,miles) +if self.PathToGoogleKey then +BRTextTTS=string.format("%s, %d %s",AngleDegTextTTS,distancenm,miles) +end +self:T(BRText,BRTextTTS) +return BRText,BRTextTTS +end +function AWACS:_ToStringBRA(FromCoordinate,ToCoordinate,Altitude) +self:T(self.lid.."_ToStringBRA") +local BRText="" +local BRTextTTS="" +local altitude=UTILS.Round(UTILS.MetersToFeet(Altitude)/1000,0) +local DirectionVec3=FromCoordinate:GetDirectionVec3(ToCoordinate) +local AngleRadians=FromCoordinate:GetAngleRadians(DirectionVec3) +local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),0) +local AngleDegText=string.format("%03d",AngleDegrees) +AngleDegText=string.gsub(AngleDegText,"%d","%1 ") +AngleDegText=string.gsub(AngleDegText," $","") +local AngleDegTextTTS=string.gsub(AngleDegText,"0","zero") +local Distance=ToCoordinate:Get2DDistance(FromCoordinate) +local distancenm=UTILS.Round(UTILS.MetersToNM(Distance),0) +local zero=self.gettext:GetEntry("ZERO",self.locale) +local miles=self.gettext:GetEntry("MILES",self.locale) +local thsd=self.gettext:GetEntry("THOUSAND",self.locale) +local vlow=self.gettext:GetEntry("VERYLOW",self.locale) +if altitude>=1 then +BRText=string.format("%03d, %d %s, %d %s",AngleDegrees,distancenm,miles,altitude,thsd) +BRTextTTS=string.format("%s, %d %s, %d %s",AngleDegText,distancenm,miles,altitude,thsd) +if self.PathToGoogleKey then +BRTextTTS=string.format("%s, %d %s, %d %s",AngleDegTextTTS,distancenm,miles,altitude,thsd) +end +else +BRText=string.format("%03d, %d %s, %s",AngleDegrees,distancenm,miles,vlow) +BRTextTTS=string.format("%s, %d %s, %s",AngleDegText,distancenm,miles,vlow) +if self.PathToGoogleKey then +BRTextTTS=string.format("%s, %d %s, %s",AngleDegTextTTS,distancenm,miles,vlow) +end +end +self:T(BRText,BRTextTTS) +return BRText,BRTextTTS +end +function AWACS:_GetBRAfromBullsOrAO(clustercoordinate) +self:T(self.lid.."_GetBRAfromBullsOrAO") +local refcoord=self.AOCoordinate +local BRAText="" +local BRATextTTS="" +local bullsname=self.AOName or"Rock" +local stringbr,stringbrtts=self:_ToStringBR(refcoord,clustercoordinate) +BRAText=string.format("%s %s",bullsname,stringbr) +BRATextTTS=string.format("%s %s",bullsname,stringbrtts) +self:T(BRAText,BRATextTTS) +return BRAText,BRATextTTS +end +function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object,TaskStatus,Auftrag,Cluster,Contact) +self:T(self.lid.."_CreateTaskForGroup "..GroupID.." Description: "..Description) +local managedgroup=self.ManagedGrps[GroupID] +local task={} +self.ManagedTaskID=self.ManagedTaskID+1 +task.TID=self.ManagedTaskID +task.AssignedGroupID=GroupID +task.Status=TaskStatus or AWACS.TaskStatus.ASSIGNED +task.ToDo=Description +task.Auftrag=Auftrag +task.Cluster=Cluster +task.Contact=Contact +task.IsPlayerTask=managedgroup.IsPlayer +task.IsUnassigned=TaskStatus==AWACS.TaskStatus.UNASSIGNED and false or true +if Object and Object:IsInstanceOf("TARGET")then +task.Target=Object +else +task.Target=TARGET:New(Object) +end +task.ScreenText=ScreenText +if Description==AWACS.TaskDescription.ANCHOR or Description==AWACS.TaskDescription.REANCHOR then +task.Target.Type=TARGET.ObjectType.ZONE +end +task.RequestedTimestamp=timer.getTime() +self.ManagedTasks:Push(task,task.TID) +managedgroup.HasAssignedTask=true +managedgroup.CurrentTask=task.TID +self:T({managedgroup}) +self.ManagedGrps[GroupID]=managedgroup +return task.TID +end +function AWACS:_ReadAssignedTaskFromGID(GroupID) +self:T(self.lid.."_GetAssignedTaskFromGID "..GroupID) +local managedgroup=self.ManagedGrps[GroupID] +if managedgroup and managedgroup.HasAssignedTask and managedgroup.CurrentTask~=0 then +local TaskID=managedgroup.CurrentTask +if self.ManagedTasks:HasUniqueID(TaskID)then +return self.ManagedTasks:ReadByID(TaskID) +end +end +return nil +end +function AWACS:_ReadAssignedGroupFromTID(TaskID) +self:T(self.lid.."_ReadAssignedGroupFromTID "..TaskID) +if self.ManagedTasks:HasUniqueID(TaskID)then +local task=self.ManagedTasks:ReadByID(TaskID) +if task and task.AssignedGroupID and task.AssignedGroupID>0 then +return self.ManagedGrps[task.AssignedGroupID] +end +end +return nil +end +function AWACS:_MessageAIReadyForTasking(GID) +self:T(self.lid.."_MessageAIReadyForTasking") +if GID>0 and self.ManagedGrps[GID]then +local managedgroup=self.ManagedGrps[GID] +local GFCallsign=self:_GetCallSign(managedgroup.Group) +local aionst=self.gettext:GetEntry("AIONSTATION",self.locale) +local TextTTS=string.format(aionst,GFCallsign,self.callsigntxt,managedgroup.AnchorStackNo or 1,managedgroup.AnchorStackAngels or 25) +self:_NewRadioEntry(TextTTS,TextTTS,GID,false,false,true,true) +end +return self +end +function AWACS:_UpdateContactEngagementTag(CID,Text,TAC,MELD,TaskStatus) +self:T(self.lid.."_UpdateContactEngagementTag") +local text=Text or"" +local contact=self.Contacts:PullByID(CID) +if contact then +contact.EngagementTag=text +contact.TACCallDone=TAC or false +contact.MeldCallDone=MELD or false +contact.Status=TaskStatus or AWACS.TaskStatus.UNASSIGNED +self.Contacts:Push(contact,CID) +end +return self +end +function AWACS:_CheckTaskQueue() +self:T(self.lid.."_CheckTaskQueue") +local opentasks=0 +local assignedtasks=0 +for _id,_managedgroup in pairs(self.ManagedGrps)do +local group=_managedgroup +if group.Group and group.Group:IsAlive()then +local coordinate=group.Group:GetCoordinate() +if coordinate then +local NewCoordinate=COORDINATE:New(0,0,0) +group.LastKnownPosition=group.LastKnownPosition:UpdateFromCoordinate(coordinate) +self.ManagedGrps[_id]=group +end +end +end +if self.ManagedTasks:IsNotEmpty()then +opentasks=self.ManagedTasks:GetSize() +self:T("Assigned Tasks: "..opentasks) +local taskstack=self.ManagedTasks:GetPointerStack() +for _id,_entry in pairs(taskstack)do +local data=_entry +local entry=data.data +local target=entry.Target +local description=entry.ToDo +if description==AWACS.TaskDescription.ANCHOR or description==AWACS.TaskDescription.REANCHOR then +self:T("Open Task ANCHOR/REANCHOR") +local managedgroup=self.ManagedGrps[entry.AssignedGroupID] +if managedgroup then +local group=managedgroup.Group +if group and group:IsAlive()then +local groupcoord=group:GetCoordinate() +local zone=target:GetObject() +self:T({zone}) +if group:IsInZone(zone)then +self:T("Open Task ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) +target:Stop() +if managedgroup.IsAI then +self:_MessageAIReadyForTasking(managedgroup.GID) +end +managedgroup.HasAssignedTask=false +self.ManagedGrps[entry.AssignedGroupID]=managedgroup +self.ManagedTasks:PullByID(entry.TID) +else +self:T("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID) +end +else +self.ManagedTasks:PullByID(entry.TID) +end +end +elseif description==AWACS.TaskDescription.INTERCEPT then +self:T("Open Tasks INTERCEPT") +local taskstatus=entry.Status +local targetstatus=entry.Target:GetState() +if taskstatus==AWACS.TaskStatus.UNASSIGNED then +self.ManagedTasks:PullByID(entry.TID) +break +end +local managedgroup=self.ManagedGrps[entry.AssignedGroupID] +local auftrag=entry.Auftrag +local auftragstatus="Not Known" +if auftrag then +auftragstatus=auftrag:GetState() +end +local text=string.format("ID=%d | Status=%s | TargetState=%s | AuftragState=%s",entry.TID,taskstatus,targetstatus,auftragstatus) +self:T(text) +if auftrag then +if auftrag:IsExecuting()then +entry.Status=AWACS.TaskStatus.EXECUTING +elseif auftrag:IsSuccess()then +entry.Status=AWACS.TaskStatus.SUCCESS +elseif auftrag:GetState()==AUFTRAG.Status.FAILED then +entry.Status=AWACS.TaskStatus.FAILED +end +if targetstatus=="Dead"then +entry.Status=AWACS.TaskStatus.SUCCESS +elseif targetstatus=="Alive"and auftrag:IsOver()then +entry.Status=AWACS.TaskStatus.FAILED +end +elseif entry.IsPlayerTask then +if entry.Target:IsDead()or entry.Target:IsDestroyed()or entry.Target:CountTargets()==0 then +entry.Status=AWACS.TaskStatus.SUCCESS +elseif entry.Target:IsAlive()then +local targetpos=entry.Target:GetCoordinate() +local outofzones=false +self.RejectZoneSet:ForEachZone( +function(Zone,Position) +local zone=Zone +local pos=Position +if pos and zone:IsVec2InZone(pos)then +outofzones=true +end +end, +targetpos:GetVec2() +) +if not outofzones then +outofzones=true +self.ZoneSet:ForEachZone( +function(Zone,Position) +local zone=Zone +local pos=Position +if pos and zone:IsVec2InZone(pos)then +outofzones=false +end +end, +targetpos:GetVec2() +) +end +if outofzones then +entry.Status=AWACS.TaskStatus.SUCCESS +end +end +end +if entry.Status==AWACS.TaskStatus.SUCCESS then +self:T("Open Tasks INTERCEPT success for GroupID "..entry.AssignedGroupID) +if managedgroup then +self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",true,true,AWACS.TaskStatus.SUCCESS) +managedgroup.HasAssignedTask=false +managedgroup.ContactCID=0 +managedgroup.LastTasking=timer.getTime() +if managedgroup.IsAI then +managedgroup.CurrentAuftrag=0 +else +managedgroup.CurrentTask=0 +end +self.ManagedGrps[entry.AssignedGroupID]=managedgroup +self.ManagedTasks:PullByID(entry.TID) +self:__InterceptSuccess(1) +self:__ReAnchor(5,managedgroup.GID) +end +elseif entry.Status==AWACS.TaskStatus.FAILED then +self:T("Open Tasks INTERCEPT failed for GroupID "..entry.AssignedGroupID) +if managedgroup then +managedgroup.HasAssignedTask=false +self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) +managedgroup.ContactCID=0 +managedgroup.LastTasking=timer.getTime() +if managedgroup.IsAI then +managedgroup.CurrentAuftrag=0 +else +managedgroup.CurrentTask=0 +end +if managedgroup.IsPlayer then +entry.IsPlayerTask=false +end +self.ManagedGrps[entry.AssignedGroupID]=managedgroup +if managedgroup.Group:IsAlive()or(managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive())then +self:__ReAnchor(5,managedgroup.GID) +end +end +self.ManagedTasks:PullByID(entry.TID) +self:__InterceptFailure(1) +elseif entry.Status==AWACS.TaskStatus.REQUESTED then +self:T("Open Tasks INTERCEPT REQUESTED for GroupID "..entry.AssignedGroupID) +local created=entry.RequestedTimestamp or timer.getTime()-120 +local Tnow=timer.getTime() +local Trunning=(Tnow-created)/60 +local text=string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning) +if Trunning>self.ReassignmentPause then +entry.Status=AWACS.TaskStatus.UNASSIGNED +self.ManagedTasks:PullByID(entry.TID) +end +self:T(text) +end +elseif description==AWACS.TaskDescription.VID then +local managedgroup=self.ManagedGrps[entry.AssignedGroupID] +if(not managedgroup)or(not managedgroup.Group:IsAlive())then +self.ManagedTasks:PullByID(entry.TID) +return self +end +if entry.Target:IsDead()or entry.Target:IsDestroyed()or entry.Target:CountTargets()==0 then +entry.Status=AWACS.TaskStatus.SUCCESS +elseif entry.Target:IsAlive()then +self:T("Checking VID target out of bounds") +local targetpos=entry.Target:GetCoordinate() +local outofzones=false +self.RejectZoneSet:ForEachZone( +function(Zone,Position) +local zone=Zone +local pos=Position +if pos and zone:IsVec2InZone(pos)then +outofzones=true +end +end, +targetpos:GetVec2() +) +if not outofzones then +outofzones=true +self.ZoneSet:ForEachZone( +function(Zone,Position) +local zone=Zone +local pos=Position +if pos and zone:IsVec2InZone(pos)then +outofzones=false +end +end, +targetpos:GetVec2() +) +end +if outofzones then +entry.Status=AWACS.TaskStatus.SUCCESS +self:T("Out of bounds - SUCCESS") +end +end +if entry.Status==AWACS.TaskStatus.REQUESTED then +self:T("Open Tasks VID REQUESTED for GroupID "..entry.AssignedGroupID) +local created=entry.RequestedTimestamp or timer.getTime()-120 +local Tnow=timer.getTime() +local Trunning=(Tnow-created)/60 +local text=string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning) +if Trunning>self.ReassignmentPause then +entry.Status=AWACS.TaskStatus.UNASSIGNED +self.ManagedTasks:PullByID(entry.TID) +end +self:T(text) +elseif entry.Status==AWACS.TaskStatus.ASSIGNED then +self:T("Open Tasks VID ASSIGNED for GroupID "..entry.AssignedGroupID) +elseif entry.Status==AWACS.TaskStatus.SUCCESS then +self:T("Open Tasks VID success for GroupID "..entry.AssignedGroupID) +self.ManagedTasks:PullByID(entry.TID) +local Contact=self.Contacts:ReadByID(entry.Contact.CID) +if Contact and(Contact.IFF==AWACS.IFF.FRIENDLY or Contact.IFF==AWACS.IFF.NEUTRAL)then +self:T("IFF outcome friendly/neutral for GroupID "..entry.AssignedGroupID) +if managedgroup then +managedgroup.HasAssignedTask=false +self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) +managedgroup.ContactCID=0 +managedgroup.LastTasking=timer.getTime() +if managedgroup.IsAI then +managedgroup.CurrentAuftrag=0 +else +managedgroup.CurrentTask=0 +end +if managedgroup.IsPlayer then +entry.IsPlayerTask=false +end +self.ManagedGrps[entry.AssignedGroupID]=managedgroup +self:__ReAnchor(5,managedgroup.GID) +end +elseif Contact and Contact.IFF==AWACS.IFF.ENEMY then +self:T("IFF outcome hostile for GroupID "..entry.AssignedGroupID) +entry.ToDo=AWACS.TaskDescription.INTERCEPT +entry.Status=AWACS.TaskStatus.ASSIGNED +local cname=Contact.TargetGroupNaming +entry.ScreenText=string.format("Engage hostile %s group.",cname) +self.ManagedTasks:Push(entry,entry.TID) +local TextTTS=string.format("%s, %s. Engage hostile target!",managedgroup.CallSign,self.callsigntxt) +self:_NewRadioEntry(TextTTS,TextTTS,managedgroup.GID,true,self.debug,true,false,true) +elseif not Contact then +self:T("IFF outcome target DEAD for GroupID "..entry.AssignedGroupID) +if managedgroup then +managedgroup.HasAssignedTask=false +self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) +managedgroup.ContactCID=0 +managedgroup.LastTasking=timer.getTime() +if managedgroup.IsAI then +managedgroup.CurrentAuftrag=0 +else +managedgroup.CurrentTask=0 +end +if managedgroup.IsPlayer then +entry.IsPlayerTask=false +end +self.ManagedGrps[entry.AssignedGroupID]=managedgroup +if managedgroup.Group:IsAlive()or managedgroup.FlightGroup:IsAlive()then +self:__ReAnchor(5,managedgroup.GID) +end +end +end +elseif entry.Status==AWACS.TaskStatus.FAILED then +self:T("Open Tasks VID failed for GroupID "..entry.AssignedGroupID) +if managedgroup then +managedgroup.HasAssignedTask=false +self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) +managedgroup.ContactCID=0 +managedgroup.LastTasking=timer.getTime() +if managedgroup.IsAI then +managedgroup.CurrentAuftrag=0 +else +managedgroup.CurrentTask=0 +end +if managedgroup.IsPlayer then +entry.IsPlayerTask=false +end +self.ManagedGrps[entry.AssignedGroupID]=managedgroup +if managedgroup.Group:IsAlive()or managedgroup.FlightGroup:IsAlive()then +self:__ReAnchor(5,managedgroup.GID) +end +end +self.ManagedTasks:PullByID(entry.TID) +self:__InterceptFailure(1) +end +end +end +end +return self +end +function AWACS:_LogStatistics() +self:T(self.lid.."_LogStatistics") +local text=string.gsub(UTILS.OneLineSerialize(self.MonitoringData),",","\n") +local text=string.gsub(text,"{","\n") +local text=string.gsub(text,"}","") +local text=string.gsub(text,"="," = ") +self:T(text) +if self.MonitoringOn then +MESSAGE:New(text,20,"AWACS",false):ToAll() +end +return self +end +function AWACS:AddCAPAirWing(AirWing,Zone) +self:T(self.lid.."AddCAPAirWing") +if AirWing then +AirWing:SetUsingOpsAwacs(self) +local distance=self.AOCoordinate:Get2DDistance(AirWing:GetCoordinate()) +if Zone then +local stackscreated=self.AnchorStacks:GetSize() +if stackscreated==self.AnchorMaxAnchors then +self:E(self.lid.."Max number of stacks already created!") +else +local AnchorStackOne={} +AnchorStackOne.AnchorBaseAngels=self.AnchorBaseAngels +AnchorStackOne.Anchors=FIFO:New() +AnchorStackOne.AnchorAssignedID=FIFO:New() +local newname=Zone:GetName() +for i=1,self.AnchorMaxStacks do +AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) +end +local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) +newname=Zone:GetName().."-"..newsubname +AnchorStackOne.StationZone=Zone +AnchorStackOne.StationZoneCoordinate=Zone:GetCoordinate() +AnchorStackOne.StationZoneCoordinateText=Zone:GetCoordinate():ToStringLLDDM() +AnchorStackOne.StationName=newname +if self.debug then +AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) +local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then +AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +else +local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then +AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +end +self.AnchorStacks:Push(AnchorStackOne,newname) +AirWing.HasOwnStation=true +AirWing.StationName=newname +end +end +self.CAPAirwings:Push(AirWing,distance) +end +return self +end +function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,ReportingName,Tactical) +self:T(self.lid.."_AnnounceContact") +local tag="" +local Tag=Tag +local CID=0 +if not Tag then +CID=Contact.CID or 0 +Tag=Contact.TargetGroupNaming or"" +end +if self.NoGroupTags then +Tag=nil +end +local isGroup=false +local GID=0 +local grpcallsign="Ghost 1" +if Group and Group:IsAlive()then +GID,isGroup,grpcallsign=self:_GetManagedGrpID(Group) +self:T("GID="..GID.." CheckedIn = "..tostring(isGroup)) +end +local cluster=Contact.Cluster +local intel=self.intel +local size=self.intel:ClusterCountUnits(cluster) +local threatsize,threatsizetext=self:_GetBlurredSize(size) +local clustercoordinate=Contact.Cluster.coordinate or Contact.Contact.position +local heading=Contact.Contact.group:GetHeading()or self.intel:CalcClusterDirection(cluster) +clustercoordinate:SetHeading(Contact.Contact.group:GetHeading()) +local BRAfromBulls,BRAfromBullsTTS=self:_GetBRAfromBullsOrAO(clustercoordinate) +self:T(BRAfromBulls) +self:T(BRAfromBullsTTS) +BRAfromBulls=BRAfromBulls.."." +BRAfromBullsTTS=BRAfromBullsTTS.."." +if isGroup then +BRAfromBulls=clustercoordinate:ToStringBRAANATO(Group:GetCoordinate(),true,true) +BRAfromBullsTTS=string.gsub(BRAfromBulls,"BRAA","brah") +BRAfromBullsTTS=string.gsub(BRAfromBullsTTS,"BRA","brah") +if self.PathToGoogleKey then +BRAfromBullsTTS=clustercoordinate:ToStringBRAANATO(Group:GetCoordinate(),true,true,true,false,true) +end +end +local BRAText="" +local TextScreen="" +if isGroup then +BRAText=string.format("%s, %s.",grpcallsign,self.callsigntxt) +TextScreen=string.format("%s, %s.",grpcallsign,self.callsigntxt) +else +BRAText=string.format("%s.",self.callsigntxt) +TextScreen=string.format("%s.",self.callsigntxt) +end +local newgrp=self.gettext:GetEntry("NEWGROUP",self.locale) +local grptxt=self.gettext:GetEntry("GROUP",self.locale) +local GRPtxt=self.gettext:GetEntry("GROUPCAP",self.locale) +local popup=self.gettext:GetEntry("POPUP",self.locale) +if IsNew and self.PlayerGuidance then +BRAText=string.format("%s %s.",BRAText,newgrp) +TextScreen=string.format("%s %s.",TextScreen,newgrp) +elseif IsPopup then +BRAText=string.format("%s %s %s.",BRAText,popup,grptxt) +TextScreen=string.format("%s %s %s.",TextScreen,popup,grptxt) +elseif IsBogeyDope and Tag and Tag~=""then +BRAText=string.format("%s %s %s.",BRAText,Tag,grptxt) +TextScreen=string.format("%s %s %s.",TextScreen,Tag,grptxt) +else +BRAText=string.format("%s %s.",BRAText,GRPtxt) +TextScreen=string.format("%s %s.",TextScreen,GRPtxt) +end +if not IsBogeyDope then +if Tag and Tag~=""then +BRAText=BRAText.." "..Tag.."." +TextScreen=TextScreen.." "..Tag.."." +end +end +if threatsize>1 then +BRAText=BRAText.." "..BRAfromBullsTTS.." "..threatsizetext.."." +TextScreen=TextScreen.." "..BRAfromBulls.." "..threatsizetext.."." +else +BRAText=BRAText.." "..BRAfromBullsTTS +TextScreen=TextScreen.." "..BRAfromBulls +end +if self.ModernEra then +local high=self.gettext:GetEntry("HIGH",self.locale) +local vfast=self.gettext:GetEntry("VERYFAST",self.locale) +local fast=self.gettext:GetEntry("FAST",self.locale) +if ReportingName and ReportingName~="Bogey"then +ReportingName=string.gsub(ReportingName,"_"," ") +BRAText=BRAText.." "..ReportingName.."." +TextScreen=TextScreen.." "..ReportingName.."." +end +local height=Contact.Contact.group:GetHeight() +local height=UTILS.Round(UTILS.MetersToFeet(height)/1000,0) +if height>=40 then +BRAText=BRAText..high +TextScreen=TextScreen..high +end +local speed=Contact.Contact.group:GetVelocityKNOTS() +if speed>900 then +BRAText=BRAText..vfast +TextScreen=TextScreen..vfast +elseif speed>=600 and speed<=900 then +BRAText=BRAText..fast +TextScreen=TextScreen..fast +end +end +BRAText=string.gsub(BRAText,"BRAA","brah") +BRAText=string.gsub(BRAText,"BRA","brah") +local prio=IsNew or IsBogeyDope +self:_NewRadioEntry(BRAText,TextScreen,GID,isGroup,true,IsNew,false,prio,Tactical) +return self +end +function AWACS:_GetAliveOpsGroupFromTable(OpsGroups) +self:T(self.lid.."_GetAliveOpsGroupFromTable") +local handback=nil +for _,_OG in pairs(OpsGroups or{})do +local OG=_OG +if OG and OG:IsAlive()then +handback=OG +break +end +end +return handback +end +function AWACS:_CleanUpAIMissionStack() +self:T(self.lid.."_CleanUpAIMissionStack") +local CAPMissions=0 +local Alert5Missions=0 +local InterceptMissions=0 +local MissionStack=FIFO:New() +self:T("Checking MissionStack") +for _,_mission in pairs(self.CatchAllMissions)do +local mission=_mission +local type=mission:GetType() +if type==AUFTRAG.Type.ALERT5 and mission:IsNotOver()then +MissionStack:Push(mission,mission.auftragsnummer) +Alert5Missions=Alert5Missions+1 +elseif type==AUFTRAG.Type.CAP and mission:IsNotOver()then +MissionStack:Push(mission,mission.auftragsnummer) +CAPMissions=CAPMissions+1 +elseif type==AUFTRAG.Type.INTERCEPT and mission:IsNotOver()then +MissionStack:Push(mission,mission.auftragsnummer) +InterceptMissions=InterceptMissions+1 +end +end +self.AICAPMissions=nil +self.AICAPMissions=MissionStack +return CAPMissions,Alert5Missions,InterceptMissions +end +function AWACS:_ConsistencyCheck() +self:T(self.lid.."_ConsistencyCheck") +if self.debug then +self:T("CatchAllMissions") +local catchallm={} +local report1=REPORT:New("CatchAll") +report1:Add("====================") +report1:Add("CatchAllMissions") +report1:Add("====================") +for _,_mission in pairs(self.CatchAllMissions)do +local mission=_mission +local nummer=mission.auftragsnummer or 0 +local type=mission:GetType() +local state=mission:GetState() +local FG=mission:GetOpsGroups() +local OG=self:_GetAliveOpsGroupFromTable(FG) +local OGName="UnknownFromMission" +if OG then +OGName=OG:GetName() +end +report1:Add(string.format("Auftrag Nr %d Type %s State %s FlightGroup %s",nummer,type,state,OGName)) +if mission:IsNotOver()then +catchallm[#catchallm+1]=mission +end +end +self.CatchAllMissions=nil +self.CatchAllMissions=catchallm +local catchallfg={} +self:T("CatchAllFGs") +report1:Add("====================") +report1:Add("CatchAllFGs") +report1:Add("====================") +for _,_fg in pairs(self.CatchAllFGs)do +local FG=_fg +local mission=FG:GetMissionCurrent() +local OGName=FG:GetName()or"UnknownFromFG" +local nummer=0 +local type="No Type" +local state="None" +if mission then +type=mission:GetType() +nummer=mission.auftragsnummer or 0 +state=mission:GetState() +end +report1:Add(string.format("Auftrag Nr %d Type %s State %s FlightGroup %s",nummer,type,state,OGName)) +if FG:IsAlive()then +catchallfg[#catchallfg+1]=FG +end +end +report1:Add("====================") +self:T(report1:Text()) +self.CatchAllFGs=nil +self.CatchAllFGs=catchallfg +end +return self +end +function AWACS:_CheckAICAPOnStation() +self:T(self.lid.."_CheckAICAPOnStation") +self:_ConsistencyCheck() +local capmissions,alert5missions,interceptmissions=self:_CleanUpAIMissionStack() +self:T("CAP="..capmissions.." ALERT5="..alert5missions.." Requested="..self.AIRequested) +if self.MaxAIonCAP>0 then +local onstation=capmissions+alert5missions +if capmissions>self.MaxAIonCAP then +self:T(string.format("*** Onstation %d > MaxAIOnCAP %d",onstation,self.MaxAIonCAP)) +local mission=self.AICAPMissions:Pull() +local Groups=mission:GetOpsGroups() +local OpsGroup=self:_GetAliveOpsGroupFromTable(Groups) +local GID,checkedin=self:_GetManagedGrpID(OpsGroup) +mission:__Cancel(30) +self.AIRequested=self.AIRequested-1 +if checkedin then +self:_CheckOut(OpsGroup,GID) +end +end +if capmissions0 then +local report=REPORT:New("CAP Mission Status") +report:Add("===============") +local missions=self.AICAPMissions:GetDataTable() +local i=1 +for _,_Mission in pairs(missions)do +local mission=_Mission +if mission then +i=i+1 +report:Add(string.format("Entry %d",i)) +report:Add(string.format("Mission No %d",mission.auftragsnummer)) +report:Add(string.format("Mission Type %s",mission:GetType())) +report:Add(string.format("Mission State %s",mission:GetState())) +local OpsGroups=mission:GetOpsGroups() +local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) +if OpsGroup then +local OpsName=OpsGroup:GetName()or"Unknown" +local found,GID,OpsCallSign=self:_GetGIDFromGroupOrName(OpsGroup) +report:Add(string.format("Mission FG %s",OpsName)) +report:Add(string.format("Callsign %s",OpsCallSign)) +report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) +else +report:Add("***** Cannot obtain (yet) this missions OpsGroup!") +end +report:Add(string.format("Target Type %s",mission:GetTargetType())) +end +report:Add("===============") +end +if self.debug then +self:I(report:Text()) +end +end +end +return self +end +function AWACS:_SetAIROE(FlightGroup,Group) +self:T(self.lid.."_SetAIROE") +local ROE=self.AwacsROE or AWACS.ROE.POLICE +local ROT=self.AwacsROT or AWACS.ROT.PASSIVE +Group:OptionAlarmStateGreen() +Group:OptionECM_OnlyLockByRadar() +Group:OptionROEHoldFire() +Group:OptionROTEvadeFire() +Group:OptionRTBBingoFuel(true) +Group:OptionKeepWeaponsOnThreat() +local callname=self.AICAPCAllName or CALLSIGN.Aircraft.Colt +self.AICAPCAllNumber=self.AICAPCAllNumber+1 +Group:CommandSetCallsign(callname,math.fmod(self.AICAPCAllNumber,9)) +FlightGroup:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN) +FlightGroup:SetDefaultCallsign(callname,math.fmod(self.AICAPCAllNumber,9)) +if ROE==AWACS.ROE.POLICE or ROE==AWACS.ROE.VID then +FlightGroup:SetDefaultROE(ENUMS.ROE.WeaponHold) +elseif ROE==AWACS.ROE.IFF then +FlightGroup:SetDefaultROE(ENUMS.ROE.ReturnFire) +elseif ROE==AWACS.ROE.BVR then +FlightGroup:SetDefaultROE(ENUMS.ROE.OpenFire) +end +if ROT==AWACS.ROT.BYPASSESCAPE or ROT==AWACS.ROT.PASSIVE then +FlightGroup:SetDefaultROT(ENUMS.ROT.PassiveDefense) +elseif ROT==AWACS.ROT.OPENFIRE or ROT==AWACS.ROT.RETURNFIRE then +FlightGroup:SetDefaultROT(ENUMS.ROT.BypassAndEscape) +elseif ROT==AWACS.ROT.EVADE then +FlightGroup:SetDefaultROT(ENUMS.ROT.EvadeFire) +end +FlightGroup:SetFuelLowRTB(true) +FlightGroup:SetFuelLowThreshold(0.2) +FlightGroup:SetEngageDetectedOff() +FlightGroup:SetOutOfAAMRTB(true) +return self +end +function AWACS:_TACRangeCall(GID,Contact) +self:T(self.lid.."_TACRangeCall") +if not Contact then return self end +local pilotcallsign=self:_GetCallSign(nil,GID) +local managedgroup=self.ManagedGrps[GID] +local contact=Contact.Contact +local contacttag=Contact.TargetGroupNaming +local name=managedgroup.GroupName +if contact then +local position=contact.position +if position then +local distance=position:Get2DDistance(managedgroup.Group:GetCoordinate()) +distance=UTILS.Round(UTILS.MetersToNM(distance)) +local grptxt=self.gettext:GetEntry("GROUP",self.locale) +local miles=self.gettext:GetEntry("MILES",self.locale) +local text=string.format("%s. %s. %s %s, %d %s.",self.callsigntxt,pilotcallsign,contacttag,grptxt,distance,miles) +if not self.TacticalSubscribers[name]then +self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) +end +self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,false,AWACS.TaskStatus.EXECUTING) +if GID and GID~=0 then +if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then +if self.TacticalSubscribers[name]then +self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true) +end +end +end +end +end +return self +end +function AWACS:_MeldRangeCall(GID,Contact) +self:T(self.lid.."_MeldRangeCall") +if not Contact then return self end +local pilotcallsign=self:_GetCallSign(nil,GID) +local managedgroup=self.ManagedGrps[GID] +local flightpos=managedgroup.Group:GetCoordinate() +local contact=Contact.Contact +local contacttag=Contact.TargetGroupNaming or"Bogey" +local name=managedgroup.GroupName +if contact then +local position=contact.position +if position then +local BRATExt="" +if self.PathToGoogleKey then +BRATExt=position:ToStringBRAANATO(flightpos,false,false,true,false,true) +else +BRATExt=position:ToStringBRAANATO(flightpos,false,false) +end +local grptxt=self.gettext:GetEntry("GROUP",self.locale) +local text=string.format("%s. %s. %s %s, %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,BRATExt) +if not self.TacticalSubscribers[name]then +self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) +end +self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,true,AWACS.TaskStatus.EXECUTING) +if GID and GID~=0 then +if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then +local name=managedgroup.GroupName +if self.TacticalSubscribers[name]then +self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true) +end +end +end +end +end +return self +end +function AWACS:_ThreatRangeCall(GID,Contact) +self:T(self.lid.."_ThreatRangeCall") +if not Contact then return self end +local pilotcallsign=self:_GetCallSign(nil,GID) +local managedgroup=self.ManagedGrps[GID] +local flightpos=managedgroup.Group:GetCoordinate()or managedgroup.LastKnownPosition +local contact=Contact.Contact +local contacttag=Contact.TargetGroupNaming or"Bogey" +local name=managedgroup.GroupName +local IsSub=self.TacticalSubscribers[name]and true or false +if contact then +local position=contact.position or contact.group:GetCoordinate() +if position then +local BRATExt="" +if self.PathToGoogleKey then +BRATExt=position:ToStringBRAANATO(flightpos,false,false,true,false,true) +else +BRATExt=position:ToStringBRAANATO(flightpos,false,false) +end +local grptxt=self.gettext:GetEntry("GROUP",self.locale) +local thrt=self.gettext:GetEntry("THREAT",self.locale) +local text=string.format("%s. %s. %s %s, %s. %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,thrt,BRATExt) +if string.find(text,"BRAA",1,true)then +text=string.gsub(text,"BRAA","brah") +elseif string.find(text,"BRA",1,true)then +text=string.gsub(text,"BRA","brah") +end +if IsSub==false then +self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) +end +if GID and GID~=0 then +if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then +local name=managedgroup.GroupName +if self.TacticalSubscribers[name]then +self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true) +end +end +end +end +end +return self +end +function AWACS:_MergedCall(GID) +self:T(self.lid.."_MergedCall") +local pilotcallsign=self:_GetCallSign(nil,GID) +local merge=self.gettext:GetEntry("MERGED",self.locale) +local text=string.format("%s. %s. %s.",self.callsigntxt,pilotcallsign,merge) +local managedgroup=self.ManagedGrps[GID] +local name +if managedgroup then +name=managedgroup.GroupName or"none" +end +if not self.TacticalSubscribers[name]then +self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) +end +if GID and GID~=0 then +local managedgroup=self.ManagedGrps[GID] +if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then +if self.TacticalSubscribers[name]then +self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true) +end +end +end +return self +end +function AWACS:_AssignPilotToTarget(Pilots,Targets) +self:T(self.lid.."_AssignPilotToTarget") +local inreach=false +local Pilot=nil +local closest=UTILS.NMToMeters(self.maxassigndistance+1) +local targets=Targets:GetDataTable() +local Target=nil +for _,_target in pairs(targets)do +local targetgroupcoord=_target.Contact.position +for _,_Pilot in pairs(Pilots)do +local pilotcoord=_Pilot.Group:GetCoordinate() +local targetdist=targetgroupcoord:Get2DDistance(pilotcoord) +if UTILS.MetersToNM(targetdist) "..self.maxassigndistance.."NM! No Assignment!") +end +end +end +if inreach and Pilot and Pilot.IsPlayer then +local callsign=Pilot.CallSign +self.ManagedTasks:PullByID(Pilot.CurrentTask) +Pilot.HasAssignedTask=true +local TargetPosition=Target.Target:GetCoordinate() +local PlayerPositon=Pilot.LastKnownPosition +local TargetAlt=Target.Contact.altitude or Target.Cluster.altitude or Target.Contact.group:GetAltitude() +local TargetDirections,TargetDirectionsTTS=self:_ToStringBRA(PlayerPositon,TargetPosition,TargetAlt) +local ScreenText="" +local TaskType=AWACS.TaskDescription.INTERCEPT +if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then +local interc=self.gettext:GetEntry("SCREENVID",self.locale) +ScreenText=string.format(interc,Target.TargetGroupNaming) +TaskType=AWACS.TaskDescription.VID +else +local interc=self.gettext:GetEntry("SCREENINTER",self.locale) +ScreenText=string.format(interc,Target.TargetGroupNaming) +end +Pilot.CurrentTask=self:_CreateTaskForGroup(Pilot.GID,TaskType,ScreenText,Target.Target,AWACS.TaskStatus.REQUESTED,nil,Target.Cluster,Target.Contact) +Pilot.ContactCID=Target.CID +self.ManagedGrps[Pilot.GID]=Pilot +Target.LinkedTask=Pilot.CurrentTask +Target.LinkedGroup=Pilot.GID +Target.Status=AWACS.TaskStatus.REQUESTED +local targeted=self.gettext:GetEntry("ENGAGETAG",self.locale) +Target.EngagementTag=string.format(targeted,Pilot.CallSign) +self.Contacts:PullByID(Target.CID) +self.Contacts:Push(Target,Target.CID) +local reqcomm=self.gettext:GetEntry("REQCOMMIT",self.locale) +local text=string.format(reqcomm,self.callsigntxt,Target.TargetGroupNaming,TargetDirectionsTTS,Pilot.CallSign) +local textScreen=string.format(reqcomm,self.callsigntxt,Target.TargetGroupNaming,TargetDirections,Pilot.CallSign) +self:_NewRadioEntry(text,textScreen,Pilot.GID,true,self.debug,true,false,true) +elseif inreach and Pilot and Pilot.IsAI then +local callsign=Pilot.CallSign +local FGStatus=Pilot.FlightGroup:GetState() +self:T("Pilot AI Callsign: "..callsign) +self:T("Pilot FG State: "..FGStatus) +local targetstatus=Target.Target:GetState() +self:T("Target State: "..targetstatus) +local currmission=Pilot.FlightGroup:GetMissionCurrent() +if currmission then +self:T("Current Mission: "..currmission:GetType()) +end +local ZoneSet=self.ZoneSet +local RejectZoneSet=self.RejectZoneSet +local intercept=AUFTRAG:NewINTERCEPT(Target.Target) +intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL) +intercept:SetWeaponType(ENUMS.WeaponFlag.Auto) +intercept:SetMissionRange(self.MaxMissionRange) +intercept:AddConditionSuccess( +function(target,zoneset,rzoneset) +local success=true +local target=target +if target:IsDestroyed()or target:IsDead()or target:CountTargets()==0 then return true end +local tgtcoord=target:GetCoordinate() +local tgtvec2=nil +if tgtcoord then +tgtvec2=tgtcoord:GetVec2() +end +local zones=zoneset +local rzones=rzoneset +if tgtvec2 then +zones:ForEachZone( +function(zone) +if zone:IsVec2InZone(tgtvec2)then +success=false +end +end +) +rzones:ForEachZone( +function(zone) +if zone:IsVec2InZone(tgtvec2)then +success=true +end +end +) +end +return success +end, +Target.Target, +ZoneSet, +RejectZoneSet +) +Pilot.FlightGroup:AddMission(intercept) +local Angels=Pilot.AnchorStackAngels or 25 +Angels=Angels*1000 +local AnchorSpeed=self.CapSpeedBase or 270 +AnchorSpeed=UTILS.KnotsToAltKIAS(AnchorSpeed,Angels) +local Anchor=self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) +local capauftrag=AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{}) +capauftrag:SetMissionRange(self.MaxMissionRange) +capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) +Pilot.FlightGroup:AddMission(capauftrag) +if currmission then +currmission:__Cancel(3) +end +self.CatchAllMissions[#self.CatchAllMissions+1]=intercept +self.CatchAllMissions[#self.CatchAllMissions+1]=capauftrag +self.ManagedTasks:PullByID(Pilot.CurrentTask) +Pilot.HasAssignedTask=true +Pilot.CurrentTask=self:_CreateTaskForGroup(Pilot.GID,AWACS.TaskDescription.INTERCEPT,"Intercept Task",Target.Target,AWACS.TaskStatus.ASSIGNED,intercept,Target.Cluster,Target.Contact) +Pilot.CurrentAuftrag=intercept.auftragsnummer +Pilot.ContactCID=Target.CID +self.ManagedGrps[Pilot.GID]=Pilot +Target.LinkedTask=Pilot.CurrentTask +Target.LinkedGroup=Pilot.GID +Target.Status=AWACS.TaskStatus.ASSIGNED +local targeted=self.gettext:GetEntry("ENGAGETAG",self.locale) +Target.EngagementTag=string.format(targeted,Pilot.CallSign) +self.Contacts:PullByID(Target.CID) +self.Contacts:Push(Target,Target.CID) +local altitude=Target.Contact.altitude or Target.Contact.group:GetAltitude() +local position=Target.Cluster.coordinate or Target.Contact.position +if not position then +self.intel:GetClusterCoordinate(Target.Cluster,true) +end +local bratext,bratexttts=self:_ToStringBRA(Pilot.Group:GetCoordinate(),position,altitude or 8000) +local aicomm=self.gettext:GetEntry("AICOMMIT",self.locale) +local text=string.format(aicomm,self.callsigntxt,Target.TargetGroupNaming,bratexttts,Pilot.CallSign) +local textScreen=string.format(aicomm,self.callsigntxt,Target.TargetGroupNaming,bratext,Pilot.CallSign) +self:_NewRadioEntry(text,textScreen,Pilot.GID,true,self.debug,true,false,true) +local comm=self.gettext:GetEntry("COMMIT",self.locale) +local text=string.format("%s. %s.",Pilot.CallSign,comm) +self:_NewRadioEntry(text,text,Pilot.GID,true,self.debug,true,true,true) +self:__Intercept(2) +end +return self +end +function AWACS:onbeforeStart(From,Event,To) +self:T({From,Event,To}) +if self.IncludeHelicopters then +self.clientset:FilterCategories("helicopter") +end +return self +end +function AWACS:onafterStart(From,Event,To) +self:T({From,Event,To}) +local controlzonename="FEZ-"..self.AOName +self.ControlZone=ZONE_RADIUS:New(controlzonename,self.OpsZone:GetVec2(),UTILS.NMToMeters(self.ControlZoneRadius)) +if self.debug then +self.ControlZone:DrawZone(self.coalition,{0,1,0},1,{1,0,0},0.05,3,true) +self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true) +local AOCoordString=self.AOCoordinate:ToStringLLDDM() +local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then +MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end +self.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) +local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if not self.GCI then +if self.AllowMarkers then +MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) +if self.AllowMarkers then +MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) +end +end +else +local AOCoordString=self.AOCoordinate:ToStringLLDDM() +local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then +MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end +if not self.GCI then +if self.AllowMarkers then +MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) +end +end +local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then +MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end +end +if not self.GCI then +local AwacsAW=self.AirWing +local mission=AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) +mission:SetMissionRange(self.MaxMissionRange) +mission:SetRequiredAttribute({GROUP.Attribute.AIR_AWACS}) +local timeonstation=(self.AwacsTimeOnStation+self.ShiftChangeTime)*3600 +mission:SetTime(nil,timeonstation) +self.CatchAllMissions[#self.CatchAllMissions+1]=mission +AwacsAW:AddMission(mission) +self.AwacsMission=mission +self.AwacsInZone=false +self.AwacsReady=false +else +self.AwacsInZone=true +self.AwacsReady=true +self:_StartIntel(self.GCIGroup) +if self.GCIGroup:IsGround()then +self.AwacsFG=ARMYGROUP:New(self.GCIGroup) +self.AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation) +self.AwacsFG:SwitchRadio(self.Frequency,self.Modulation) +elseif self.GCIGroup:IsShip()then +self.AwacsFG=NAVYGROUP:New(self.GCIGroup) +self.AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation) +self.AwacsFG:SwitchRadio(self.Frequency,self.Modulation) +else +self:E(self.lid.."**** Group unsuitable for GCI ops! Needs to be a GROUND or SHIP type group!") +self:Stop() +return self +end +self.callsigntxt=string.format("%s",self.CallSignClear[self.CallSign]) +self:__CheckRadioQueue(-10) +local sunrise=self.gettext:GetEntry("SUNRISE",self.locale) +local text=string.format(sunrise,self.callsigntxt,self.callsigntxt) +self:_NewRadioEntry(text,text,0,false,false,false,false,true) +self:T(self.lid..text) +self.sunrisedone=true +end +local ZoneSet=SET_ZONE:New() +ZoneSet:AddZone(self.ControlZone) +if not self.GCI then +ZoneSet:AddZone(self.OrbitZone) +end +if self.BorderZone then +ZoneSet:AddZone(self.BorderZone) +end +local RejectZoneSet=SET_ZONE:New() +if self.RejectZone then +RejectZoneSet:AddZone(self.RejectZone) +end +self.ZoneSet=ZoneSet +self.RejectZoneSet=RejectZoneSet +if self.AllowMarkers then +local MarkerOps=MARKEROPS_BASE:New("AWACS",{"Station","Delete","Move"}) +local function Handler(Keywords,Coord,Text) +self:T(Text) +for _,_word in pairs(Keywords)do +if string.lower(_word)=="station"then +local Name=string.match(Text," ([%a]+)$") +self:_CreateAnchorStackFromMarker(Name,Coord) +break +elseif string.lower(_word)=="delete"then +local Name=string.match(Text," ([%a]+)$") +self:_DeleteAnchorStackFromMarker(Name,Coord) +break +elseif string.lower(_word)=="move"then +local Name=string.match(Text," ([%a]+)$") +self:_MoveAnchorStackFromMarker(Name,Coord) +break +end +end +end +function MarkerOps:OnAfterMarkAdded(From,Event,To,Text,Keywords,Coord) +Handler(Keywords,Coord,Text) +end +function MarkerOps:OnAfterMarkChanged(From,Event,To,Text,Keywords,Coord) +Handler(Keywords,Coord,Text) +end +function MarkerOps:OnAfterMarkDeleted(From,Event,To) +end +self.MarkerOps=MarkerOps +end +if self.GCI then +self:__Started(-5) +end +if self.TacticalMenu then +self:__CheckTacticalQueue(55) +end +self:__Status(-30) +return self +end +function AWACS:_CheckAwacsStatus() +self:T(self.lid.."_CheckAwacsStatus") +local awacs=nil +if self.AwacsFG then +awacs=self.AwacsFG:GetGroup() +local unit=awacs:GetUnit(1) +if unit then +self.STN=tostring(unit:GetSTN()) +end +end +local monitoringdata=self.MonitoringData +if not self.GCI then +if awacs and awacs:IsAlive()and not self.AwacsInZone then +local orbitzone=self.OrbitZone +if awacs:IsInZone(orbitzone)then +self.AwacsInZone=true +self:T(self.lid.."Arrived in Orbit Zone: "..orbitzone:GetName()) +local onstationtxt=self.gettext:GetEntry("AWONSTATION",self.locale) +local text=string.format(onstationtxt,self.callsigntxt,self.AOName or"Rock") +local textScreen=text +self:_NewRadioEntry(text,textScreen,0,false,true,true,false,true) +end +end +end +if(awacs and awacs:IsAlive())then +if not self.intelstarted then +local alt=UTILS.Round(UTILS.MetersToFeet(awacs:GetAltitude())/1000,0) +if alt>=10 then +self:_StartIntel(awacs) +end +end +if self.intelstarted and not self.sunrisedone then +local alt=UTILS.Round(UTILS.MetersToFeet(awacs:GetAltitude())/1000,0) +if alt>=10 then +local sunrise=self.gettext:GetEntry("SUNRISE",self.locale) +local text=string.format(sunrise,self.callsigntxt,self.callsigntxt) +self:_NewRadioEntry(text,text,0,false,false,false,false,true) +self:T(self.lid..text) +self.sunrisedone=true +end +end +local AWmission=self.AwacsMission +local awstatus=AWmission:GetState() +local AWmissiontime=(timer.getTime()-self.AwacsTimeStamp) +local AWTOSLeft=UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600)-AWmissiontime),0) +AWTOSLeft=UTILS.Round(AWTOSLeft/60,0) +local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0) +local Changedue="No" +if not self.ShiftChangeAwacsFlag and(AWTOSLeft<=ChangeTime or AWmission:IsOver())then +Changedue="Yes" +self.ShiftChangeAwacsFlag=true +self:__AwacsShiftChange(2) +end +local report=REPORT:New("AWACS:") +report:Add("====================") +report:Add("AWACS:") +report:Add(string.format("Auftrag Status: %s",awstatus)) +report:Add(string.format("TOS Left: %d min",AWTOSLeft)) +report:Add(string.format("Needs ShiftChange: %s",Changedue)) +local OpsGroups=AWmission:GetOpsGroups() +local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) +if OpsGroup then +local OpsName=OpsGroup:GetName()or"Unknown" +local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown" +report:Add(string.format("Mission FG %s",OpsName)) +report:Add(string.format("Callsign %s",OpsCallSign)) +report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) +else +report:Add("***** Cannot obtain (yet) this missions OpsGroup!") +end +if self.ShiftChangeAwacsFlag and self.ShiftChangeAwacsRequested then +AWmission=self.AwacsMissionReplacement +local esstatus=AWmission:GetState() +local ESmissiontime=(timer.getTime()-self.AwacsTimeStamp) +local ESTOSLeft=UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600)-ESmissiontime),0) +ESTOSLeft=UTILS.Round(ESTOSLeft/60,0) +local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0) +report:Add("AWACS REPLACEMENT:") +report:Add(string.format("Auftrag Status: %s",esstatus)) +report:Add(string.format("TOS Left: %d min",ESTOSLeft)) +local OpsGroups=AWmission:GetOpsGroups() +local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) +if OpsGroup then +local OpsName=OpsGroup:GetName()or"Unknown" +local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown" +report:Add(string.format("Mission FG %s",OpsName)) +report:Add(string.format("Callsign %s",OpsCallSign)) +report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) +else +report:Add("***** Cannot obtain (yet) this missions OpsGroup!") +end +if AWmission:IsExecuting()then +self.ShiftChangeAwacsFlag=false +self.ShiftChangeAwacsRequested=false +self.sunrisedone=false +if self.AwacsMission and self.AwacsMission:IsNotOver()then +self.AwacsMission:Cancel() +end +self.AwacsMission=self.AwacsMissionReplacement +self.AwacsMissionReplacement=nil +self.AwacsTimeStamp=timer.getTime() +report:Add("*** Replacement DONE ***") +end +report:Add("====================") +end +if self.HasEscorts then +for i=1,self.EscortNumber do +local ESmission=self.EscortMission[i] +if not ESmission then break end +local esstatus=ESmission:GetState() +local ESmissiontime=(timer.getTime()-self.EscortsTimeStamp) +local ESTOSLeft=UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600)-ESmissiontime),0) +ESTOSLeft=UTILS.Round(ESTOSLeft/60,0) +local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0) +local Changedue="No" +if(ESTOSLeft<=ChangeTime and not self.ShiftChangeEscortsFlag)or(ESmission:IsOver()and not self.ShiftChangeEscortsFlag)then +Changedue="Yes" +self.ShiftChangeEscortsFlag=true +self:__EscortShiftChange(2) +end +report:Add("====================") +report:Add("ESCORTS:") +report:Add(string.format("Auftrag Status: %s",esstatus)) +report:Add(string.format("TOS Left: %d min",ESTOSLeft)) +report:Add(string.format("Needs ShiftChange: %s",Changedue)) +local OpsGroups=ESmission:GetOpsGroups() +local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) +if OpsGroup then +local OpsName=OpsGroup:GetName()or"Unknown" +local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown" +report:Add(string.format("Mission FG %s",OpsName)) +report:Add(string.format("Callsign %s",OpsCallSign)) +report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) +monitoringdata.EscortsStateMission[i]=esstatus +monitoringdata.EscortsStateFG[i]=OpsGroup:GetState() +else +report:Add("***** Cannot obtain (yet) this missions OpsGroup!") +end +report:Add("====================") +local RESMission +if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then +RESMission=self.EscortMissionReplacement[i] +local esstatus=RESMission:GetState() +local RESMissiontime=(timer.getTime()-self.EscortsTimeStamp) +local ESTOSLeft=UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600)-RESMissiontime),0) +ESTOSLeft=UTILS.Round(ESTOSLeft/60,0) +local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0) +report:Add("ESCORTS REPLACEMENT:") +report:Add(string.format("Auftrag Status: %s",esstatus)) +report:Add(string.format("TOS Left: %d min",ESTOSLeft)) +local OpsGroups=RESMission:GetOpsGroups() +local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) +if OpsGroup then +local OpsName=OpsGroup:GetName()or"Unknown" +local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown" +report:Add(string.format("Mission FG %s",OpsName)) +report:Add(string.format("Callsign %s",OpsCallSign)) +report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) +else +report:Add("***** Cannot obtain (yet) this missions OpsGroup!") +end +if RESMission and RESMission:IsExecuting()then +self.ShiftChangeEscortsFlag=false +self.ShiftChangeEscortsRequested=false +if ESmission and ESmission:IsNotOver()then +ESmission:__Cancel(1) +end +self.EscortMission[i]=self.EscortMissionReplacement[i] +self.EscortMissionReplacement[i]=nil +self.EscortsTimeStamp=timer.getTime() +report:Add("*** Replacement DONE ***") +end +report:Add("====================") +end +end +end +if self.debug then +self:T(report:Text()) +end +else +local AWmission=self.AwacsMission +local awstatus=AWmission:GetState() +if AWmission:IsOver()then +self:I(self.lid.."*****AWACS is dead!*****") +self.ShiftChangeAwacsFlag=true +self:__AwacsShiftChange(2) +end +end +return monitoringdata +end +function AWACS:onafterStatus(From,Event,To) +self:T({From,Event,To}) +self:_SetClientMenus() +local monitoringdata=self.MonitoringData +if not self.GCI then +monitoringdata=self:_CheckAwacsStatus() +end +local awacsalive=false +if self.AwacsFG then +local awacs=self.AwacsFG:GetGroup() +if awacs and awacs:IsAlive()then +awacsalive=true +end +end +if self:Is("Running")and(awacsalive or self.AwacsInZone)then +if self.AwacsSRS then +self.AwacsSRS:SetCoordinate(self.AwacsFG:GetCoordinate()) +if self.TacticalSRS then +self.TacticalSRS:SetCoordinate(self.AwacsFG:GetCoordinate()) +end +end +self:_CheckAICAPOnStation() +self:_CleanUpContacts() +self:_CheckMerges() +self:_CheckSubscribers() +local outcome,targets=self:_TargetSelectionProcess(true) +self:_CheckTaskQueue() +local AI,Humans=self:_GetIdlePilots() +if outcome and#Humans>0 and self.PlayerCapAssignment then +self:_AssignPilotToTarget(Humans,targets) +end +if outcome and#AI>0 then +self:_AssignPilotToTarget(AI,targets) +end +end +if not self.GCI then +monitoringdata.AwacsShiftChange=self.ShiftChangeAwacsFlag +if self.AwacsFG then +monitoringdata.AwacsStateFG=self.AwacsFG:GetState() +end +monitoringdata.AwacsStateMission=self.AwacsMission:GetState() +monitoringdata.EscortsShiftChange=self.ShiftChangeEscortsFlag +end +monitoringdata.AICAPCurrent=self.AICAPMissions:Count() +monitoringdata.AICAPMax=self.MaxAIonCAP +monitoringdata.Airwings=self.CAPAirwings:Count() +self.MonitoringData=monitoringdata +if self.debug then +self:_LogStatistics() +end +local picturetime=timer.getTime()-self.PictureTimeStamp +if self.AwacsInZone and picturetime>self.PictureInterval then +self.PictureTimeStamp=timer.getTime() +self:_Picture(nil,true) +end +self:__Status(30) +return self +end +function AWACS:onafterStop(From,Event,To) +self:T({From,Event,To}) +self.intel:Stop() +local AWFiFo=self.CAPAirwings +local AWStack=AWFiFo:GetPointerStack() +for _ID,_AWID in pairs(AWStack)do +local SubAW=self.CAPAirwings:ReadByPointer(_ID) +if SubAW then +SubAW:RemoveUsingOpsAwacs() +end +end +self:UnHandleEvent(EVENTS.PlayerEnterAircraft) +self:UnHandleEvent(EVENTS.PlayerEnterUnit) +self:UnHandleEvent(EVENTS.PlayerLeaveUnit) +self:UnHandleEvent(EVENTS.Ejection) +self:UnHandleEvent(EVENTS.Crash) +self:UnHandleEvent(EVENTS.Dead) +self:UnHandleEvent(EVENTS.UnitLost) +self:UnHandleEvent(EVENTS.BDA) +self:UnHandleEvent(EVENTS.PilotDead) +self:UnHandleEvent(EVENTS.Shot) +return self +end +function AWACS:onafterAssignAnchor(From,Event,To,GID,HasOwnStation,StationName) +self:T({From,Event,To,"GID = "..GID}) +self:_AssignAnchorToID(GID,HasOwnStation,StationName) +return self +end +function AWACS:onafterCheckedOut(From,Event,To,GID,AnchorStackNo,Angels) +self:T({From,Event,To,"GID = "..GID}) +self:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels) +return self +end +function AWACS:onafterAssignedAnchor(From,Event,To,GID,Anchor,AnchorStackNo,AnchorAngels) +self:T({From,Event,To,"GID="..GID,"Stack="..AnchorStackNo}) +local managedgroup=self.ManagedGrps[GID] +if not managedgroup then +self:E(self.lid.."**** GID "..GID.." Not Registered!") +return self +end +managedgroup.AnchorStackNo=AnchorStackNo +managedgroup.AnchorStackAngels=AnchorAngels +managedgroup.Blocked=false +local isPlayer=managedgroup.IsPlayer +local isAI=managedgroup.IsAI +local Group=managedgroup.Group +local CallSign=managedgroup.CallSign or"Ghost 1" +local AnchorName=Anchor.StationName or"unknown" +local AnchorCoordTxt=Anchor.StationZoneCoordinateText or"unknown" +local Angels=AnchorAngels or 25 +local AnchorSpeed=self.CapSpeedBase or 270 +local AuftragsNr=managedgroup.CurrentAuftrag +local textTTS="" +if self.PikesSpecialSwitch then +local stationtxt=self.gettext:GetEntry("STATIONAT",self.locale) +textTTS=string.format(stationtxt,CallSign,self.callsigntxt,AnchorName,Angels) +else +local stationtxt=self.gettext:GetEntry("STATIONATLONG",self.locale) +textTTS=string.format(stationtxt,CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed) +end +local ROEROT=self.AwacsROE..", "..self.AwacsROT +local stationtxtsc=self.gettext:GetEntry("STATIONSCREEN",self.locale) +local stationtxtta=self.gettext:GetEntry("STATIONTASK",self.locale) +local textScreen=string.format(stationtxtsc,CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) +local TextTasking=string.format(stationtxtta,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) +self:_NewRadioEntry(textTTS,textScreen,GID,isPlayer,isPlayer,true,false) +managedgroup.CurrentTask=self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.StationZone) +if isAI then +local auftrag=managedgroup.FlightGroup:GetMissionCurrent() +if auftrag then +local auftragtype=auftrag:GetType() +if auftragtype==AUFTRAG.Type.ALERT5 then +local capauftrag=AUFTRAG:NewCAP(Anchor.StationZone,Angels*1000,AnchorSpeed,Anchor.StationZone:GetCoordinate(),0,15,{}) +capauftrag:SetMissionRange(self.MaxMissionRange) +capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) +capauftrag:AddAsset(managedgroup.FlightGroup) +self.CatchAllMissions[#self.CatchAllMissions+1]=capauftrag +managedgroup.FlightGroup:AddMission(capauftrag) +auftrag:Cancel() +else +self:E("**** AssignedAnchor but Auftrag NOT ALERT5!") +end +else +self:E("**** AssignedAnchor but NO Auftrag!") +end +end +self.ManagedGrps[GID]=managedgroup +return self +end +function AWACS:onafterNewCluster(From,Event,To,Cluster) +self:T({From,Event,To,Cluster.index}) +self.CID=self.CID+1 +self.Countactcounter=self.Countactcounter+1 +local ContactTable=Cluster.Contacts or{} +local function GetFirstAliveContact(table) +for _,_contact in pairs(table)do +local contact=_contact +if contact and contact.group and contact.group:IsAlive()then +return contact,contact.group +end +end +return nil +end +local Contact,Group=GetFirstAliveContact(ContactTable) +if not Contact then return self end +if Group and not Group:IsAirborne()then +return self +end +local targetset=SET_GROUP:New() +for _,_grp in pairs(ContactTable)do +local grp=_grp +targetset:AddGroup(grp.group,true) +end +local managedcontact={} +managedcontact.CID=self.CID +managedcontact.Contact=Contact +managedcontact.Cluster=Cluster +managedcontact.IFF=AWACS.IFF.BOGEY +managedcontact.Target=TARGET:New(targetset) +managedcontact.LinkedGroup=0 +managedcontact.LinkedTask=0 +managedcontact.Status=AWACS.TaskStatus.IDLE +local phoneid=math.fmod(self.Countactcounter,27) +if phoneid==0 then phoneid=1 end +managedcontact.TargetGroupNaming=AWACS.Phonetic[phoneid] +managedcontact.ReportingName=Contact.group:GetNatoReportingName() +managedcontact.TACCallDone=false +managedcontact.MeldCallDone=false +managedcontact.EngagementTag="" +local IsPopup=false +if self.OpsZone:IsVec2InZone(Contact.position:GetVec2())then +IsPopup=true +end +Contact.CID=managedcontact.CID +Contact.TargetGroupNaming=managedcontact.TargetGroupNaming +Cluster.CID=managedcontact.CID +Cluster.TargetGroupNaming=managedcontact.TargetGroupNaming +self.Contacts:Push(managedcontact,self.CID) +local ContactCoordinate=Contact.position:GetVec2() +local incontrolzone=self.ControlZone:IsVec2InZone(ContactCoordinate) +local distance=1000000 +if not self.GCI then +distance=Contact.position:Get2DDistance(self.OrbitZone:GetCoordinate()) +end +local inborderzone=false +if self.BorderZone then +inborderzone=self.BorderZone:IsVec2InZone(ContactCoordinate) +end +if incontrolzone or inborderzone or(distance<=UTILS.NMToMeters(55))or IsPopup then +self:_AnnounceContact(managedcontact,true,nil,false,managedcontact.TargetGroupNaming,IsPopup,managedcontact.ReportingName) +end +return self +end +function AWACS:onafterNewContact(From,Event,To,Contact) +self:T({From,Event,To,Contact}) +local tdist=self.ThreatDistance +for _gid,_mgroup in pairs(self.ManagedGrps)do +local managedgroup=_mgroup +local group=managedgroup.Group +if group and group:IsAlive()and group:IsAirborne()then +local cpos=Contact.position or Contact.group:GetCoordinate() +local mpos=group:GetCoordinate() +local dist=cpos:Get2DDistance(mpos) +dist=UTILS.Round(UTILS.MetersToNM(dist),0) +if dist<=tdist then +self:_ThreatRangeCall(_gid,Contact) +end +end +end +return self +end +function AWACS:onafterLostContact(From,Event,To,Contact) +self:T({From,Event,To,Contact}) +return self +end +function AWACS:onafterLostCluster(From,Event,To,Cluster,Mission) +self:T({From,Event,To}) +return self +end +function AWACS:onafterCheckTacticalQueue(From,Event,To) +self:T({From,Event,To}) +if self.clientset:CountAlive()==0 then +self:T(self.lid.."No player connected.") +self:__CheckTacticalQueue(-5) +return self +end +for _name,_freq in pairs(self.TacticalSubscribers)do +local Group=nil +if _name then +Group=GROUP:FindByName(_name) +end +if Group and Group:IsAlive()then +self:_BogeyDope(Group,true) +end +end +if(self.TacticalQueue:IsNotEmpty())then +while self.TacticalQueue:Count()>0 do +local RadioEntry=self.TacticalQueue:Pull() +self:T({RadioEntry}) +local frequency=self.TacticalBaseFreq +if RadioEntry.GroupID and RadioEntry.GroupID~=0 then +local managedgroup=self.ManagedGrps[RadioEntry.GroupID] +if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then +local name=managedgroup.GroupName +frequency=self.TacticalSubscribers[name] +end +end +local gtext=RadioEntry.TextTTS +if self.PathToGoogleKey then +gtext=string.format("%s",gtext) +end +self.TacticalSRSQ:NewTransmission(gtext,nil,self.TacticalSRS,nil,0.5,nil,nil,nil,frequency,self.TacticalModulation) +self:T(RadioEntry.TextTTS) +if RadioEntry.ToScreen and RadioEntry.TextScreen and(not self.SuppressScreenOutput)then +if RadioEntry.GroupID and RadioEntry.GroupID~=0 then +local managedgroup=self.ManagedGrps[RadioEntry.GroupID] +if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then +MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToGroup(managedgroup.Group) +self:T(RadioEntry.TextScreen) +end +else +MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition) +end +end +end +end +if not self:Is("Stopped")then +self:__CheckTacticalQueue(-self.TacticalInterval) +end +return self +end +function AWACS:onafterCheckRadioQueue(From,Event,To) +self:T({From,Event,To}) +local nextcall=10 +if(self.RadioQueue:IsNotEmpty()or self.PrioRadioQueue:IsNotEmpty())then +local RadioEntry=nil +if self.PrioRadioQueue:IsNotEmpty()then +RadioEntry=self.PrioRadioQueue:Pull() +else +RadioEntry=self.RadioQueue:Pull() +end +self:T({RadioEntry}) +if self.clientset:CountAlive()==0 then +self:T(self.lid.."No player connected.") +self:__CheckRadioQueue(-5) +return self +end +if not RadioEntry.FromAI then +if self.PathToGoogleKey then +local gtext=RadioEntry.TextTTS +gtext=string.format("%s",gtext) +self.AwacsSRS:PlayTextExt(gtext,nil,self.MultiFrequency,self.MultiModulation,self.Gender,self.Culture,self.Voice,self.Volume,"AWACS") +else +self.AwacsSRS:PlayTextExt(RadioEntry.TextTTS,nil,self.MultiFrequency,self.MultiModulation,self.Gender,self.Culture,self.Voice,self.Volume,"AWACS") +end +self:T(RadioEntry.TextTTS) +else +if RadioEntry.GroupID and RadioEntry.GroupID~=0 then +local managedgroup=self.ManagedGrps[RadioEntry.GroupID] +if managedgroup and managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive()then +if self.PathToGoogleKey then +local gtext=RadioEntry.TextTTS +gtext=string.format("%s",gtext) +managedgroup.FlightGroup:RadioTransmission(gtext,1,false) +else +managedgroup.FlightGroup:RadioTransmission(RadioEntry.TextTTS,1,false) +end +self:T(RadioEntry.TextTTS) +end +end +end +if RadioEntry.Duration then nextcall=RadioEntry.Duration end +if RadioEntry.ToScreen and RadioEntry.TextScreen and(not self.SuppressScreenOutput)then +if RadioEntry.GroupID and RadioEntry.GroupID~=0 then +local managedgroup=self.ManagedGrps[RadioEntry.GroupID] +if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then +MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToGroup(managedgroup.Group) +self:T(RadioEntry.TextScreen) +end +else +MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition) +end +end +end +if self:Is("Running")then +if self.PathToGoogleKey then +nextcall=nextcall+self.GoogleTTSPadding +else +nextcall=nextcall+self.WindowsTTSPadding +end +self:__CheckRadioQueue(-nextcall) +end +return self +end +function AWACS:onafterEscortShiftChange(From,Event,To) +self:T({From,Event,To}) +if self.AwacsFG and self.ShiftChangeEscortsFlag and not self.ShiftChangeEscortsRequested then +local awacs=self.AwacsFG:GetGroup() +if awacs and awacs:IsAlive()then +self.ShiftChangeEscortsRequested=true +self.EscortsTimeStamp=timer.getTime() +self:_StartEscorts(true) +else +self:E("**** AWACS group dead at onafterEscortShiftChange!") +end +end +return self +end +function AWACS:onafterAwacsShiftChange(From,Event,To) +self:T({From,Event,To}) +if self.AwacsFG and self.ShiftChangeAwacsFlag and not self.ShiftChangeAwacsRequested then +self.ShiftChangeAwacsRequested=true +self.AwacsTimeStamp=timer.getTime() +local AwacsAW=self.AirWing +local mission=AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) +self.CatchAllMissions[#self.CatchAllMissions+1]=mission +local timeonstation=(self.AwacsTimeOnStation+self.ShiftChangeTime)*3600 +mission:SetTime(nil,timeonstation) +mission:SetMissionRange(self.MaxMissionRange) +AwacsAW:AddMission(mission) +self.AwacsMissionReplacement=mission +end +return self +end +function AWACS:onafterFlightOnMission(From,Event,To,FlightGroup,Mission) +self:T({From,Event,To}) +self:T("FlightGroup "..FlightGroup:GetName().." Mission "..Mission:GetName().." Type "..Mission:GetType()) +self.CatchAllFGs[#self.CatchAllFGs+1]=FlightGroup +if not self:Is("Stopped")then +if not self.AwacsReady or self.ShiftChangeAwacsFlag or self.ShiftChangeEscortsFlag then +self:_StartSettings(FlightGroup,Mission) +elseif Mission and(Mission:GetType()==AUFTRAG.Type.CAP or Mission:GetType()==AUFTRAG.Type.ALERT5 or Mission:GetType()==AUFTRAG.Type.ORBIT)then +if not self.FlightGroups:HasUniqueID(FlightGroup:GetName())then +self:T("Pushing FG "..FlightGroup:GetName().." to stack!") +self.FlightGroups:Push(FlightGroup,FlightGroup:GetName()) +end +end +end +return self +end +function AWACS:onafterReAnchor(From,Event,To,GID) +self:T({From,Event,To,GID}) +local managedgroup=self.ManagedGrps[GID] +if managedgroup then +if managedgroup.IsAI then +local AIFG=managedgroup.FlightGroup +if AIFG and AIFG:IsAlive()then +if AIFG:IsFuelLow()or AIFG:IsOutOfMissiles()or AIFG:IsOutOfAmmo()then +local destbase=AIFG.homebase +if not destbase then destbase=self.Airbase end +AIFG:RTB(destbase) +self:_CheckOut(AIFG:GetGroup(),GID) +self.AIRequested=self.AIRequested-1 +else +local Anchor=self.AnchorStacks:ReadByPointer(managedgroup.AnchorStackNo) +local StationZone=Anchor.StationZone +managedgroup.CurrentTask=self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,"Re-Station AI",StationZone) +managedgroup.HasAssignedTask=true +local mission=AIFG:GetMissionCurrent() +if mission then +managedgroup.CurrentAuftrag=mission.auftragsnummer or 0 +else +managedgroup.CurrentAuftrag=0 +end +managedgroup.ContactCID=0 +self.ManagedGrps[GID]=managedgroup +local tostation=self.gettext:GetEntry("VECTORSTATION",self.locale) +self:_MessageVector(GID,tostation,Anchor.StationZoneCoordinate,managedgroup.AnchorStackAngels) +end +else +local savedcallsign=managedgroup.CallSign +local textoptions={} +textoptions[1]=self.gettext:GetEntry("TEXTOPTIONS1",self.locale) +textoptions[2]=self.gettext:GetEntry("TEXTOPTIONS2",self.locale) +textoptions[3]=self.gettext:GetEntry("TEXTOPTIONS3",self.locale) +textoptions[4]=self.gettext:GetEntry("TEXTOPTIONS4",self.locale) +local allstations=self.gettext:GetEntry("ALLSTATIONS",self.locale) +local milestxt=self.gettext:GetEntry("MILES",self.locale) +if managedgroup.LastKnownPosition then +local lastknown=UTILS.DeepCopy(managedgroup.LastKnownPosition) +local faded=textoptions[math.random(1,4)] +local text=string.format("%s. %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign) +local textScreen=string.format("%s, %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign) +local brtext=self:_ToStringBULLS(lastknown) +local brtexttts=self:_ToStringBULLS(lastknown,false,true) +text=text.." "..brtexttts.." "..milestxt.."." +textScreen=textScreen.." "..brtext.." "..milestxt.."." +self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) +end +self.ManagedGrps[GID]=nil +end +elseif managedgroup.IsPlayer then +local PLFG=managedgroup.Group +if PLFG and PLFG:IsAlive()then +local Anchor=self.AnchorStacks:ReadByPointer(managedgroup.AnchorStackNo) +local AnchorName=Anchor.StationName or"unknown" +local AnchorCoordTxt=Anchor.StationZoneCoordinateText or"unknown" +local Angels=managedgroup.AnchorStackAngels or 25 +local AnchorSpeed=self.CapSpeedBase or 270 +local StationZone=Anchor.StationZone +local ROEROT=self.AwacsROE.." "..self.AwacsROT +local stationtxt=self.gettext:GetEntry("STATIONTASK",self.locale) +local TextTasking=string.format(stationtxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) +managedgroup.CurrentTask=self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,StationZone) +managedgroup.HasAssignedTask=true +managedgroup.ContactCID=0 +self.ManagedGrps[GID]=managedgroup +local vectortxt=self.gettext:GetEntry("VECTORSTATION",self.locale) +self:_MessageVector(GID,vectortxt,Anchor.StationZoneCoordinate,managedgroup.AnchorStackAngels) +else +local savedcallsign=managedgroup.CallSign +local textoptions={} +textoptions[1]=self.gettext:GetEntry("TEXTOPTIONS1",self.locale) +textoptions[2]=self.gettext:GetEntry("TEXTOPTIONS2",self.locale) +textoptions[3]=self.gettext:GetEntry("TEXTOPTIONS3",self.locale) +textoptions[4]=self.gettext:GetEntry("TEXTOPTIONS4",self.locale) +local allstations=self.gettext:GetEntry("ALLSTATIONS",self.locale) +local milestxt=self.gettext:GetEntry("MILES",self.locale) +local faded=textoptions[math.random(1,4)] +local text=string.format("%s. %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign) +local textScreen=string.format("%s, %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign) +if managedgroup.LastKnownPosition then +local lastknown=UTILS.DeepCopy(managedgroup.LastKnownPosition) +local brtext=self:_ToStringBULLS(lastknown) +local brtexttts=self:_ToStringBULLS(lastknown,false,true) +text=text.." "..brtexttts.." "..milestxt.."." +textScreen=textScreen.." "..brtext.." "..milestxt.."." +self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) +end +self.ManagedGrps[GID]=nil +end +end +end +end +end +BRIGADE={ +ClassName="BRIGADE", +verbose=0, +rearmingZones={}, +refuellingZones={}, +} +BRIGADE.version="0.1.1" +function BRIGADE:New(WarehouseName,BrigadeName) +local self=BASE:Inherit(self,LEGION:New(WarehouseName,BrigadeName)) +if not self then +BASE:E(string.format("ERROR: Could not find warehouse %s!",WarehouseName)) +return nil +end +self.lid=string.format("BRIGADE %s | ",self.alias) +self:SetRetreatZones() +if self:IsShip()then +local wh=self.warehouse +local group=wh:GetGroup() +self.warehouseOpsGroup=NAVYGROUP:New(group) +self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName()) +end +self:AddTransition("*","ArmyOnMission","*") +return self +end +function BRIGADE:AddPlatoon(Platoon) +table.insert(self.cohorts,Platoon) +self:AddAssetToPlatoon(Platoon,Platoon.Ngroups) +Platoon:SetBrigade(self) +if Platoon:IsStopped()then +Platoon:Start() +end +return self +end +function BRIGADE:AddAssetToPlatoon(Platoon,Nassets) +if Platoon then +local Group=GROUP:FindByName(Platoon.templatename) +if Group then +local text=string.format("Adding asset %s to platoon %s",Group:GetName(),Platoon.name) +self:T(self.lid..text) +self:AddAsset(Group,Nassets,nil,nil,nil,nil,Platoon.skill,Platoon.livery,Platoon.name) +else +self:E(self.lid.."ERROR: Group does not exist!") +end +else +self:E(self.lid.."ERROR: Platoon does not exit!") +end +return self +end +function BRIGADE:SetRetreatZones(RetreatZoneSet) +self.retreatZones=RetreatZoneSet or SET_ZONE:New() +return self +end +function BRIGADE:AddRetreatZone(RetreatZone) +self.retreatZones:AddZone(RetreatZone) +return self +end +function BRIGADE:GetRetreatZones() +return self.retreatZones +end +function BRIGADE:AddRearmingZone(RearmingZone) +local rearmingzone={} +rearmingzone.zone=RearmingZone +rearmingzone.mission=nil +rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(),"Rearming Zone"):ToCoalition(self:GetCoalition()) +table.insert(self.rearmingZones,rearmingzone) +return rearmingzone +end +function BRIGADE:AddRefuellingZone(RefuellingZone) +local supplyzone={} +supplyzone.zone=RefuellingZone +supplyzone.mission=nil +supplyzone.marker=MARKER:New(supplyzone.zone:GetCoordinate(),"Refuelling Zone"):ToCoalition(self:GetCoalition()) +table.insert(self.refuellingZones,supplyzone) +return supplyzone +end +function BRIGADE:GetPlatoon(PlatoonName) +local platoon=self:_GetCohort(PlatoonName) +return platoon +end +function BRIGADE:GetPlatoonOfAsset(Asset) +local platoon=self:GetPlatoon(Asset.squadname) +return platoon +end +function BRIGADE:RemoveAssetFromPlatoon(Asset) +local platoon=self:GetPlatoonOfAsset(Asset) +if platoon then +platoon:DelAsset(Asset) +end +end +function BRIGADE:LoadBackAssetInPosition(Templatename,Position) +self:T(self.lid.."LoadBackAssetInPosition: "..tostring(Templatename)) +local nametbl=UTILS.Split(Templatename,"_") +local name=nametbl[1] +self:T(string.format("*** Target Platoon = %s ***",name)) +local cohorts=self.cohorts or{} +local thisasset=nil +local found=false +for _,_cohort in pairs(cohorts)do +local asset=_cohort:GetName() +self:T(string.format("*** Looking at Platoon = %s ***",asset)) +if asset==name then +self:T("**** Found Platoon ****") +local cohassets=_cohort.assets or{} +for _,_zug in pairs(cohassets)do +local zug=_zug +if zug.assignment==name and zug.requested==false then +self:T("**** Found Asset ****") +found=true +thisasset=zug +break +end +end +end +end +if found then +thisasset.rid=thisasset.uid +thisasset.requested=false +thisasset.score=100 +thisasset.missionTask="CAS" +thisasset.spawned=true +local template=thisasset.templatename +local alias=thisasset.spawngroupname +local spawnasset=SPAWN:NewWithAlias(template,alias) +:InitDelayOff() +:SpawnFromCoordinate(Position) +local request={} +request.assignment=name +request.warehouse=self +request.assets={thisasset} +request.ntransporthome=0 +request.ndelivered=0 +request.ntransport=0 +request.cargoattribute=thisasset.attribute +request.category=thisasset.category +request.cargoassets={thisasset} +request.assetdesc=WAREHOUSE.Descriptor.ASSETLIST +request.cargocategory=thisasset.category +request.toself=true +request.transporttype=WAREHOUSE.TransportType.SELFPROPELLED +request.assetproblem={} +request.born=true +request.prio=50 +request.uid=thisasset.uid +request.airbase=nil +request.timestamp=timer.getAbsTime() +request.assetdescval={thisasset} +request.nasset=1 +request.cargogroupset=SET_GROUP:New() +request.cargogroupset:AddGroup(spawnasset) +request.iscargo=true +self:__AssetSpawned(2,spawnasset,thisasset,request) +end +return self +end +function BRIGADE:onafterStart(From,Event,To) +self:GetParent(self,BRIGADE).onafterStart(self,From,Event,To) +self:I(self.lid..string.format("Starting BRIGADE v%s",BRIGADE.version)) +end +function BRIGADE:onafterStatus(From,Event,To) +self:GetParent(self).onafterStatus(self,From,Event,To) +local fsmstate=self:GetState() +self:CheckTransportQueue() +self:CheckMissionQueue() +for _,_rearmingzone in pairs(self.rearmingZones)do +local rearmingzone=_rearmingzone +if(not rearmingzone.mission)or rearmingzone.mission:IsOver()then +rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone) +self:AddMission(rearmingzone.mission) +end +end +for _,_supplyzone in pairs(self.refuellingZones)do +local supplyzone=_supplyzone +if(not supplyzone.mission)or supplyzone.mission:IsOver()then +supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone) +self:AddMission(supplyzone.mission) +end +end +self:_TacticalOverview() +if self.verbose>=1 then +local Nmissions=self:CountMissionsInQueue() +local Npq,Np,Nq=self:CountAssetsOnMission() +local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]",self:CountAssets(),Npq,Np,Nq) +local text=string.format("%s: Missions=%d, Platoons=%d, Assets=%s",fsmstate,Nmissions,#self.cohorts,assets) +self:I(self.lid..text) +end +if self.verbose>=2 then +local text=string.format("Missions Total=%d:",#self.missionqueue) +for i,_mission in pairs(self.missionqueue)do +local mission=_mission +local prio=string.format("%d/%s",mission.prio,tostring(mission.importance));if mission.urgent then prio=prio.." (!)"end +local assets=string.format("%d/%d",mission:CountOpsGroups(),mission.Nassets or 0) +local target=string.format("%d/%d Damage=%.1f",mission:CountMissionTargets(),mission:GetTargetInitialNumber(),mission:GetTargetDamage()) +text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s",i,mission.name,mission.type,mission.status,prio,assets,target) +end +self:I(self.lid..text) +end +if self.verbose>=2 then +local text=string.format("Transports Total=%d:",#self.transportqueue) +for i,_transport in pairs(self.transportqueue)do +local transport=_transport +local prio=string.format("%d/%s",transport.prio,tostring(transport.importance));if transport.urgent then prio=prio.." (!)"end +local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d",transport.Ncargo,transport.Ndelivered,transport.Ncarrier) +text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s",i,transport.uid,transport:GetState(),prio,carriers) +end +self:I(self.lid..text) +end +if self.verbose>=3 then +local text="Platoons:" +for i,_platoon in pairs(self.cohorts)do +local platoon=_platoon +local callsign=platoon.callsignName and UTILS.GetCallsignName(platoon.callsignName)or"N/A" +local modex=platoon.modex and platoon.modex or-1 +local skill=platoon.skill and tostring(platoon.skill)or"N/A" +text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s",platoon.name,platoon:GetState(),platoon.aircrafttype,platoon:CountAssets(true),#platoon.assets,callsign,modex,skill) +end +self:I(self.lid..text) +end +if self.verbose>=4 then +local text="Rearming Zones:" +for i,_rearmingzone in pairs(self.rearmingZones)do +local rearmingzone=_rearmingzone +text=text..string.format("\n* %s: Mission status=%s, suppliers=%d",rearmingzone.zone:GetName(),rearmingzone.mission:GetState(),rearmingzone.mission:CountOpsGroups()) +end +self:I(self.lid..text) +end +if self.verbose>=4 then +local text="Refuelling Zones:" +for i,_refuellingzone in pairs(self.refuellingZones)do +local refuellingzone=_refuellingzone +text=text..string.format("\n* %s: Mission status=%s, suppliers=%d",refuellingzone.zone:GetName(),refuellingzone.mission:GetState(),refuellingzone.mission:CountOpsGroups()) +end +self:I(self.lid..text) +end +if self.verbose>=5 then +local text="Assets in stock:" +for i,_asset in pairs(self.stock)do +local asset=_asset +text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asset.spawned)) +end +self:I(self.lid..text) +end +end +function BRIGADE:onafterArmyOnMission(From,Event,To,ArmyGroup,Mission) +self:T(self.lid..string.format("Group %s on %s mission %s",ArmyGroup:GetName(),Mission:GetType(),Mission:GetName())) +end +CHIEF={ +ClassName="CHIEF", +verbose=0, +lid=nil, +targetqueue={}, +zonequeue={}, +borderzoneset=nil, +yellowzoneset=nil, +engagezoneset=nil, +tacview=false, +Nsuccess=0, +Nfailure=0, +} +CHIEF.DEFCON={ +GREEN="Green", +YELLOW="Yellow", +RED="Red", +} +CHIEF.Strategy={ +PASSIVE="Passive", +DEFENSIVE="Defensive", +OFFENSIVE="Offensive", +AGGRESSIVE="Aggressive", +TOTALWAR="Total War" +} +CHIEF.version="0.6.1" +function CHIEF:New(Coalition,AgentSet,Alias) +Alias=Alias or"CHIEF" +if type(Coalition)=="string"then +if string.lower(Coalition)=="blue"then +Coalition=coalition.side.BLUE +elseif string.lower(Coalition)=="red"then +Coalition=coalition.side.RED +else +Coalition=coalition.side.NEUTRAL +end +end +local self=BASE:Inherit(self,INTEL:New(AgentSet,Coalition,Alias)) +self:SetBorderZones() +self:SetConflictZones() +self:SetAttackZones() +self:SetThreatLevelRange() +self.Defcon=CHIEF.DEFCON.GREEN +self.strategy=CHIEF.Strategy.DEFENSIVE +self.TransportCategories={Group.Category.HELICOPTER} +self.commander=COMMANDER:New(Coalition) +self:AddTransition("*","MissionAssign","*") +self:AddTransition("*","MissionCancel","*") +self:AddTransition("*","TransportCancel","*") +self:AddTransition("*","OpsOnMission","*") +self:AddTransition("*","ZoneCaptured","*") +self:AddTransition("*","ZoneLost","*") +self:AddTransition("*","ZoneEmpty","*") +self:AddTransition("*","ZoneAttacked","*") +self:AddTransition("*","DefconChange","*") +self:AddTransition("*","StrategyChange","*") +self:AddTransition("*","LegionLost","*") +return self +end +function CHIEF:SetAirToAny() +self:SetFilterCategory({}) +return self +end +function CHIEF:SetAirToAir() +self:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) +return self +end +function CHIEF:SetAirToGround() +self:SetFilterCategory({Unit.Category.GROUND_UNIT}) +return self +end +function CHIEF:SetAirToSea() +self:SetFilterCategory({Unit.Category.SHIP}) +return self +end +function CHIEF:SetAirToSurface() +self:SetFilterCategory({Unit.Category.GROUND_UNIT,Unit.Category.SHIP}) +return self +end +function CHIEF:SetThreatLevelRange(ThreatLevelMin,ThreatLevelMax) +self.threatLevelMin=ThreatLevelMin or 1 +self.threatLevelMax=ThreatLevelMax or 10 +return self +end +function CHIEF:SetDefcon(Defcon) +local gotit=false +for _,defcon in pairs(CHIEF.DEFCON)do +if defcon==Defcon then +gotit=true +end +end +if not gotit then +self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s",tostring(Defcon))) +return self +end +if Defcon~=self.Defcon then +self:DefconChange(Defcon) +end +self.Defcon=Defcon +return self +end +function CHIEF:CreateResource(MissionType,Nmin,Nmax,Attributes,Properties,Categories) +local resources={} +local resource=self:AddToResource(resources,MissionType,Nmin,Nmax,Attributes,Properties,Categories) +return resources,resource +end +function CHIEF:AddToResource(Resource,MissionType,Nmin,Nmax,Attributes,Properties,Categories) +local resource={} +resource.MissionType=MissionType +resource.Nmin=Nmin or 1 +resource.Nmax=Nmax or Nmin +resource.Attributes=UTILS.EnsureTable(Attributes,true) +resource.Properties=UTILS.EnsureTable(Properties,true) +resource.Categories=UTILS.EnsureTable(Categories,true) +resource.carrierNmin=nil +resource.carrierNmax=nil +resource.carrierAttributes=nil +resource.carrierProperties=nil +resource.carrierCategories=nil +table.insert(Resource,resource) +if self.verbose>10 then +local text="Resource:" +for _,_r in pairs(Resource)do +local r=_r +text=text..string.format("\nmission=%s, Nmin=%d, Nmax=%d, attribute=%s, properties=%s",r.MissionType,r.Nmin,r.Nmax,tostring(r.Attributes[1]),tostring(r.Properties[1])) +end +self:I(self.lid..text) +end +return resource +end +function CHIEF:AddTransportToResource(Resource,Nmin,Nmax,CarrierAttributes,CarrierProperties,CarrierCategories) +Resource.carrierNmin=Nmin or 1 +Resource.carrierNmax=Nmax or Nmin +Resource.carrierCategories=UTILS.EnsureTable(CarrierCategories,true) +Resource.carrierAttributes=UTILS.EnsureTable(CarrierAttributes,true) +Resource.carrierProperties=UTILS.EnsureTable(CarrierProperties,true) +return self +end +function CHIEF:DeleteFromResource(Resource,MissionType) +for i=#Resource,1,-1 do +local resource=Resource[i] +if resource.MissionType==MissionType then +if resource.mission and resource.mission:IsNotOver()then +resource.mission:Cancel() +end +table.remove(Resource,i) +end +end +return self +end +function CHIEF:SetResponseOnTarget(NassetsMin,NassetsMax,ThreatLevel,TargetCategory,MissionType,Nunits,Defcon,Strategy) +local bla={} +bla.nAssetMin=NassetsMin or 1 +bla.nAssetMax=NassetsMax or bla.nAssetMin +bla.threatlevel=ThreatLevel or 0 +bla.targetCategory=TargetCategory +bla.missionType=MissionType +bla.nUnits=Nunits or 1 +bla.defcon=Defcon +bla.strategy=Strategy +self.assetNumbers=self.assetNumbers or{} +table.insert(self.assetNumbers,bla) +end +function CHIEF:_GetAssetsForTarget(Target,MissionType) +local threatlevel=Target:GetThreatLevelMax() +local nUnits=Target.N0 +local targetcategory=Target:GetCategory() +self:T(self.lid..string.format("Getting number of assets for target with TL=%d, Category=%s, nUnits=%s, MissionType=%s",threatlevel,targetcategory,nUnits,tostring(MissionType))) +local candidates={} +local threatlevelMatch=nil +for _,_assetnumber in pairs(self.assetNumbers or{})do +local assetnumber=_assetnumber +if(threatlevelMatch==nil and threatlevel>=assetnumber.threatlevel)or(threatlevelMatch~=nil and threatlevelMatch==threatlevel)then +if threatlevelMatch==nil then +threatlevelMatch=threatlevel +end +local nMatch=0 +local cand=true +if assetnumber.targetCategory~=nil then +if assetnumber.targetCategory==targetcategory then +nMatch=nMatch+1 +else +cand=false +end +end +if MissionType and assetnumber.missionType~=nil then +if assetnumber.missionType==MissionType then +nMatch=nMatch+1 +else +cand=false +end +end +if assetnumber.nUnits~=nil then +if assetnumber.nUnits>=nUnits then +nMatch=nMatch+1 +else +cand=false +end +end +if assetnumber.defcon~=nil then +if assetnumber.defcon==self.Defcon then +nMatch=nMatch+1 +else +cand=false +end +end +if assetnumber.strategy~=nil then +if assetnumber.strategy==self.strategy then +nMatch=nMatch+1 +else +cand=false +end +end +if cand then +table.insert(candidates,{assetnumber=assetnumber,nMatch=nMatch}) +end +end +end +if#candidates>0 then +local function _sort(a,b) +return a.nMatch>b.nMatch +end +table.sort(candidates,_sort) +local candidate=candidates[1] +local an=candidate.assetnumber +self:T(self.lid..string.format("Picking candidate with %d matches: NassetsMin=%d, NassetsMax=%d, ThreatLevel=%d, TargetCategory=%s, MissionType=%s, Defcon=%s, Strategy=%s", +candidate.nMatch,an.nAssetMin,an.nAssetMax,an.threatlevel,tostring(an.targetCategory),tostring(an.missionType),tostring(an.defcon),tostring(an.strategy))) +return an.nAssetMin,an.nAssetMax +else +return 1,1 +end +end +function CHIEF:GetDefcon(Defcon) +return self.Defcon +end +function CHIEF:SetLimitMission(Limit,MissionType) +self.commander:SetLimitMission(Limit,MissionType) +return self +end +function CHIEF:SetTacticalOverviewOn() +self.tacview=true +return self +end +function CHIEF:SetTacticalOverviewOff() +self.tacview=false +return self +end +function CHIEF:SetStrategy(Strategy) +if Strategy~=self.strategy then +self:StrategyChange(Strategy) +end +self.strategy=Strategy +return self +end +function CHIEF:GetStrategy() +return self.strategy +end +function CHIEF:GetDefcon(Defcon) +return self.Defcon +end +function CHIEF:GetCommander() +return self.commander +end +function CHIEF:AddAirwing(Airwing) +self:AddLegion(Airwing) +return self +end +function CHIEF:AddBrigade(Brigade) +self:AddLegion(Brigade) +return self +end +function CHIEF:AddFleet(Fleet) +self:AddLegion(Fleet) +return self +end +function CHIEF:AddLegion(Legion) +Legion.chief=self +self.commander:AddLegion(Legion) +return self +end +function CHIEF:RemoveLegion(Legion) +Legion.chief=nil +self.commander:RemoveLegion(Legion) +return self +end +function CHIEF:AddMission(Mission) +Mission.chief=self +Mission.statusChief=AUFTRAG.Status.PLANNED +self:I(self.lid..string.format("Adding mission #%d",Mission.auftragsnummer)) +self.commander:AddMission(Mission) +return self +end +function CHIEF:RemoveMission(Mission) +Mission.chief=nil +self.commander:RemoveMission(Mission) +return self +end +function CHIEF:AddOpsTransport(Transport) +Transport.chief=self +self.commander:AddOpsTransport(Transport) +return self +end +function CHIEF:RemoveTransport(Transport) +Transport.chief=nil +self.commander:RemoveTransport(Transport) +return self +end +function CHIEF:AddTarget(Target) +if not self:IsTarget(Target)then +Target.chief=self +table.insert(self.targetqueue,Target) +end +return self +end +function CHIEF:IsTarget(Target) +for _,_target in pairs(self.targetqueue)do +local target=_target +if target.uid==Target.uid or target:GetName()==Target:GetName()then +return true +end +end +return false +end +function CHIEF:RemoveTarget(Target) +for i,_target in pairs(self.targetqueue)do +local target=_target +if target.uid==Target.uid then +self:T(self.lid..string.format("Removing target %s from queue",Target.name)) +table.remove(self.targetqueue,i) +break +end +end +return self +end +function CHIEF:AddStrategicZone(OpsZone,Priority,Importance,ResourceOccupied,ResourceEmpty) +local stratzone={} +stratzone.opszone=OpsZone +stratzone.prio=Priority or 50 +stratzone.importance=Importance +stratzone.missions={} +if OpsZone:IsStopped()then +OpsZone:Start() +end +if ResourceOccupied then +stratzone.resourceOccup=UTILS.DeepCopy(ResourceOccupied) +else +stratzone.resourceOccup=self:CreateResource(AUFTRAG.Type.ARTY,1,2) +self:AddToResource(stratzone.resourceOccup,AUFTRAG.Type.CASENHANCED,1,2) +end +if ResourceEmpty then +stratzone.resourceEmpty=UTILS.DeepCopy(ResourceEmpty) +else +local resourceEmpty,resourceInfantry=self:CreateResource(AUFTRAG.Type.ONGUARD,1,3,GROUP.Attribute.GROUND_INFANTRY) +self:AddToResource(resourceEmpty,AUFTRAG.Type.ONGUARD,0,1,GROUP.Attribute.GROUND_TANK) +self:AddToResource(resourceEmpty,AUFTRAG.Type.ONGUARD,0,1,GROUP.Attribute.GROUND_IFV) +self:AddTransportToResource(resourceInfantry,0,1,{GROUP.Attribute.AIR_TRANSPORTHELO,GROUP.Attribute.GROUND_APC}) +stratzone.resourceEmpty=resourceEmpty +end +table.insert(self.zonequeue,stratzone) +OpsZone:_AddChief(self) +return stratzone +end +function CHIEF:SetStrategicZoneResourceEmpty(StrategicZone,Resource,NoCopy) +if NoCopy then +StrategicZone.resourceEmpty=Resource +else +StrategicZone.resourceEmpty=UTILS.DeepCopy(Resource) +end +return self +end +function CHIEF:SetStrategicZoneResourceOccupied(StrategicZone,Resource,NoCopy) +if NoCopy then +StrategicZone.resourceOccup=Resource +else +StrategicZone.resourceOccup=UTILS.DeepCopy(Resource) +end +return self +end +function CHIEF:GetStrategicZoneResourceEmpty(StrategicZone) +return StrategicZone.resourceEmpty +end +function CHIEF:GetStrategicZoneResourceOccupied(StrategicZone) +return StrategicZone.resourceOccup +end +function CHIEF:RemoveStrategicZone(OpsZone,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,CHIEF.RemoveStrategicZone,self,OpsZone) +else +for i=#self.zonequeue,1,-1 do +local stratzone=self.zonequeue[i] +if OpsZone.zoneName==stratzone.opszone.zoneName then +self:T(self.lid..string.format("Removing OPS zone \"%s\" from queue! All running missions will be cancelled",OpsZone.zoneName)) +for _,_resource in pairs(stratzone.resourceEmpty)do +local resource=_resource +if resource.mission and resource.mission:IsNotOver()then +resource.mission:Cancel() +end +end +for _,_resource in pairs(stratzone.resourceOccup)do +local resource=_resource +if resource.mission and resource.mission:IsNotOver()then +resource.mission:Cancel() +end +end +table.remove(self.zonequeue,i) +return self +end +end +end +return self +end +function CHIEF:AddRearmingZone(RearmingZone) +local supplyzone=self.commander:AddRearmingZone(RearmingZone) +return supplyzone +end +function CHIEF:AddRefuellingZone(RefuellingZone) +local supplyzone=self.commander:AddRefuellingZone(RefuellingZone) +return supplyzone +end +function CHIEF:AddCapZone(Zone,Altitude,Speed,Heading,Leg) +local zone=self.commander:AddCapZone(Zone,Altitude,Speed,Heading,Leg) +return zone +end +function CHIEF:AddGciCapZone(Zone,Altitude,Speed,Heading,Leg) +local zone=self.commander:AddGciCapZone(Zone,Altitude,Speed,Heading,Leg) +return zone +end +function CHIEF:RemoveGciCapZone(Zone) +local zone=self.commander:RemoveGciCapZone(Zone) +return zone +end +function CHIEF:AddAwacsZone(Zone,Altitude,Speed,Heading,Leg) +local zone=self.commander:AddAwacsZone(Zone,Altitude,Speed,Heading,Leg) +return zone +end +function CHIEF:RemoveAwacsZone(Zone) +local zone=self.commander:RemoveAwacsZone(Zone) +return zone +end +function CHIEF:AddTankerZone(Zone,Altitude,Speed,Heading,Leg,RefuelSystem) +local zone=self.commander:AddTankerZone(Zone,Altitude,Speed,Heading,Leg,RefuelSystem) +return zone +end +function CHIEF:RemoveTankerZone(Zone) +local zone=self.commander:RemoveTankerZone(Zone) +return zone +end +function CHIEF:SetBorderZones(BorderZoneSet) +self.borderzoneset=BorderZoneSet or SET_ZONE:New() +return self +end +function CHIEF:AddBorderZone(Zone) +self.borderzoneset:AddZone(Zone) +return self +end +function CHIEF:RemoveBorderZone(Zone) +self.borderzoneset:Remove(Zone:GetName()) +return self +end +function CHIEF:SetConflictZones(ZoneSet) +self.yellowzoneset=ZoneSet or SET_ZONE:New() +return self +end +function CHIEF:AddConflictZone(Zone) +self.yellowzoneset:AddZone(Zone) +return self +end +function CHIEF:RemoveConflictZone(Zone) +self.yellowzoneset:Remove(Zone:GetName()) +return self +end +function CHIEF:SetAttackZones(ZoneSet) +self.engagezoneset=ZoneSet or SET_ZONE:New() +return self +end +function CHIEF:AddAttackZone(Zone) +self.engagezoneset:AddZone(Zone) +return self +end +function CHIEF:RemoveAttackZone(Zone) +self.engagezoneset:Remove(Zone:GetName()) +return self +end +function CHIEF:AllowGroundTransport() +env.warning("WARNING: CHIEF:AllowGroundTransport() is deprecated and will be removed in the future!") +self.TransportCategories={Group.Category.GROUND,Group.Category.HELICOPTER} +return self +end +function CHIEF:ForbidGroundTransport() +env.warning("WARNING: CHIEF:ForbidGroundTransport() is deprecated and will be removed in the future!") +self.TransportCategories={Group.Category.HELICOPTER} +return self +end +function CHIEF:IsPassive() +return self.strategy==CHIEF.Strategy.PASSIVE +end +function CHIEF:IsDefensive() +return self.strategy==CHIEF.Strategy.DEFENSIVE +end +function CHIEF:IsOffensive() +return self.strategy==CHIEF.Strategy.OFFENSIVE +end +function CHIEF:IsAgressive() +return self.strategy==CHIEF.Strategy.AGGRESSIVE +end +function CHIEF:IsTotalWar() +return self.strategy==CHIEF.Strategy.TOTALWAR +end +function CHIEF:onafterStart(From,Event,To) +local text=string.format("Starting Chief of Staff") +self:I(self.lid..text) +self:GetParent(self).onafterStart(self,From,Event,To) +if self.commander then +if self.commander:GetState()=="NotReadyYet"then +self.commander:Start() +end +end +end +function CHIEF:onafterStatus(From,Event,To) +self:GetParent(self).onafterStatus(self,From,Event,To) +local fsmstate=self:GetState() +for _,_contact in pairs(self.ContactsLost)do +local contact=_contact +if contact.mission and contact.mission:IsNotOver()then +local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.",contact.groupname,contact.mission.type:upper(),contact.mission.name) +MESSAGE:New(text,120,"CHIEF"):ToAll() +self:T(self.lid..text) +contact.mission:Cancel() +end +if contact.target then +self:RemoveTarget(contact.target) +end +end +self.Nborder=0;self.Nconflict=0;self.Nattack=0 +for _,_contact in pairs(self.Contacts)do +local contact=_contact +local group=contact.group +local inred=self:CheckGroupInBorder(group) +if inred then +self.Nborder=self.Nborder+1 +end +local inyellow=self:CheckGroupInConflict(group) +if inyellow then +self.Nconflict=self.Nconflict+1 +end +local inattack=self:CheckGroupInAttack(group) +if inattack then +self.Nattack=self.Nattack+1 +end +if not contact.target then +local Target=TARGET:New(contact.group) +contact.target=Target +Target.contact=contact +self:AddTarget(Target) +end +end +if self.Nborder>0 then +self:SetDefcon(CHIEF.DEFCON.RED) +elseif self.Nconflict>0 then +self:SetDefcon(CHIEF.DEFCON.YELLOW) +else +self:SetDefcon(CHIEF.DEFCON.GREEN) +end +self:CheckTargetQueue() +for _,_target in pairs(self.targetqueue)do +local target=_target +if target and target:IsAlive()and target.chief and target.mission and target.mission:IsNotOver()then +local inborder=self:CheckTargetInZones(target,self.borderzoneset) +local inyellow=self:CheckTargetInZones(target,self.yellowzoneset) +local inattack=self:CheckTargetInZones(target,self.engagezoneset) +if self.strategy==CHIEF.Strategy.PASSIVE then +self:T(self.lid..string.format("Cancelling mission for target %s as strategy is PASSIVE",target:GetName())) +target.mission:Cancel() +elseif self.strategy==CHIEF.Strategy.DEFENSIVE then +if not inborder then +self:T(self.lid..string.format("Cancelling mission for target %s as strategy is DEFENSIVE and not inside border",target:GetName())) +target.mission:Cancel() +end +elseif self.strategy==CHIEF.Strategy.OFFENSIVE then +if not(inborder or inyellow)then +self:T(self.lid..string.format("Cancelling mission for target %s as strategy is OFFENSIVE and not inside border or conflict",target:GetName())) +target.mission:Cancel() +end +elseif self.strategy==CHIEF.Strategy.AGGRESSIVE then +if not(inborder or inyellow or inattack)then +self:T(self.lid..string.format("Cancelling mission for target %s as strategy is AGGRESSIVE and not inside border, conflict or attack",target:GetName())) +target.mission:Cancel() +end +elseif self.strategy==CHIEF.Strategy.TOTALWAR then +end +end +end +self:CheckOpsZoneQueue() +self:_TacticalOverview() +if self.verbose>=1 then +local Nassets=self.commander:CountAssets() +local Ncontacts=#self.Contacts +local Nmissions=#self.commander.missionqueue +local Ntargets=#self.targetqueue +local text=string.format("Defcon=%s Strategy=%s: Assets=%d, Contacts=%d [Border=%d, Conflict=%d, Attack=%d], Targets=%d, Missions=%d", +self.Defcon,self.strategy,Nassets,Ncontacts,self.Nborder,self.Nconflict,self.Nattack,Ntargets,Nmissions) +self:I(self.lid..text) +end +if self.verbose>=2 and#self.Contacts>0 then +local text="Contacts:" +for i,_contact in pairs(self.Contacts)do +local contact=_contact +local mtext="N/A" +if contact.mission then +mtext=string.format("\"%s\" [%s] %s",contact.mission:GetName(),contact.mission:GetType(),contact.mission.status:upper()) +end +text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s",i,contact.groupname,contact.categoryname,contact.typename,contact.threatlevel,mtext) +end +self:I(self.lid..text) +end +if self.verbose>=3 and#self.targetqueue>0 then +local text="Targets:" +for i,_target in pairs(self.targetqueue)do +local target=_target +local mtext="N/A" +if target.mission then +mtext=string.format("\"%s\" [%s] %s",target.mission:GetName(),target.mission:GetType(),target.mission.status:upper()) +end +text=text..string.format("\n[%d] %s: Category=%s, prio=%d, importance=%d, alive=%s [%.1f/%.1f], Mission=%s", +i,target:GetName(),target.category,target.prio,target.importance or-1,tostring(target:IsAlive()),target:GetLife(),target:GetLife0(),mtext) +end +self:I(self.lid..text) +end +if self.verbose>=4 and#self.commander.missionqueue>0 then +local text="Mission queue:" +for i,_mission in pairs(self.commander.missionqueue)do +local mission=_mission +local target=mission:GetTargetName()or"unknown" +text=text..string.format("\n[%d] %s (%s): status=%s, target=%s",i,mission.name,mission.type,mission.status,target) +end +self:I(self.lid..text) +end +if self.verbose>=4 and#self.zonequeue>0 then +local text="Zone queue:" +for i,_stratzone in pairs(self.zonequeue)do +local stratzone=_stratzone +local opszone=stratzone.opszone +local owner=UTILS.GetCoalitionName(opszone.ownerCurrent) +local prevowner=UTILS.GetCoalitionName(opszone.ownerPrevious) +text=text..string.format("\n[%d] %s [%s]: owner=%s [%s] (prio=%d, importance=%s): Blue=%d, Red=%d, Neutral=%d", +i,opszone.zone:GetName(),opszone:GetState(),owner,prevowner,stratzone.prio,tostring(stratzone.importance),opszone.Nblu,opszone.Nred,opszone.Nnut) +end +self:I(self.lid..text) +end +if self.verbose>=5 then +local text="Assets:" +for _,missiontype in pairs(AUFTRAG.Type)do +local N=self.commander:CountAssets(nil,missiontype) +if N>0 then +text=text..string.format("\n- %s: %d",missiontype,N) +end +end +self:I(self.lid..text) +local text="Assets:" +for _,attribute in pairs(WAREHOUSE.Attribute)do +local N=self.commander:CountAssets(nil,nil,attribute) +if N>0 or self.verbose>=10 then +text=text..string.format("\n- %s: %d",attribute,N) +end +end +self:T(self.lid..text) +end +end +function CHIEF:onafterMissionAssign(From,Event,To,Mission,Legions) +if self.commander then +self:T(self.lid..string.format("Assigning mission %s (%s) to COMMANDER",Mission.name,Mission.type)) +Mission.chief=self +Mission.statusChief=AUFTRAG.Status.QUEUED +self.commander:MissionAssign(Mission,Legions) +else +self:E(self.lid..string.format("Mission cannot be assigned as no COMMANDER is defined!")) +end +end +function CHIEF:onafterMissionCancel(From,Event,To,Mission) +self:T(self.lid..string.format("Cancelling mission %s (%s) in status %s",Mission.name,Mission.type,Mission.status)) +Mission.statusChief=AUFTRAG.Status.CANCELLED +if Mission:IsPlanned()then +self:RemoveMission(Mission) +else +if Mission.commander then +Mission.commander:MissionCancel(Mission) +end +end +end +function CHIEF:onafterTransportCancel(From,Event,To,Transport) +self:T(self.lid..string.format("Cancelling transport UID=%d in status %s",Transport.uid,Transport:GetState())) +if Transport:IsPlanned()then +self:RemoveTransport(Transport) +else +if Transport.commander then +Transport.commander:TransportCancel(Transport) +end +end +end +function CHIEF:onafterDefconChange(From,Event,To,Defcon) +self:T(self.lid..string.format("Changing Defcon from %s --> %s",self.Defcon,Defcon)) +end +function CHIEF:onafterStrategyChange(From,Event,To,Strategy) +self:T(self.lid..string.format("Changing Strategy from %s --> %s",self.strategy,Strategy)) +end +function CHIEF:onafterOpsOnMission(From,Event,To,OpsGroup,Mission) +self:T(self.lid..string.format("Group %s on mission %s [%s]",OpsGroup:GetName(),Mission:GetName(),Mission:GetType())) +end +function CHIEF:onafterZoneCaptured(From,Event,To,OpsZone) +self:T(self.lid..string.format("Zone %s captured!",OpsZone:GetName())) +end +function CHIEF:onafterZoneLost(From,Event,To,OpsZone) +self:T(self.lid..string.format("Zone %s lost!",OpsZone:GetName())) +end +function CHIEF:onafterZoneEmpty(From,Event,To,OpsZone) +self:T(self.lid..string.format("Zone %s empty!",OpsZone:GetName())) +end +function CHIEF:onafterZoneAttacked(From,Event,To,OpsZone) +self:T(self.lid..string.format("Zone %s attacked!",OpsZone:GetName())) +end +function CHIEF:_TacticalOverview() +if self.tacview then +local NassetsTotal=self.commander:CountAssets() +local NassetsStock=self.commander:CountAssets(true) +local Ncontacts=#self.Contacts +local NmissionsTotal=#self.commander.missionqueue +local NmissionsRunni=self.commander:CountMissions(AUFTRAG.Type,true) +local Ntargets=#self.targetqueue +local Nzones=#self.zonequeue +local Nagents=self.detectionset:CountAlive() +local text=string.format("Tactical Overview\n") +text=text..string.format("=================\n") +text=text..string.format("Strategy: %s - Defcon: %s - Agents=%s\n",self.strategy,self.Defcon,Nagents) +text=text..string.format("Contacts: %d [Border=%d, Conflict=%d, Attack=%d]\n",Ncontacts,self.Nborder,self.Nconflict,self.Nattack) +text=text..string.format("Assets: %d [Active=%d, Stock=%d]\n",NassetsTotal,NassetsTotal-NassetsStock,NassetsStock) +text=text..string.format("Targets: %d\n",Ntargets) +text=text..string.format("Missions: %d [Running=%d/%d - Success=%d, Failure=%d]\n",NmissionsTotal,NmissionsRunni,self:GetMissionLimit("Total"),self.Nsuccess,self.Nfailure) +for _,mtype in pairs(AUFTRAG.Type)do +local n=self.commander:CountMissions(mtype) +if n>0 then +local N=self.commander:CountMissions(mtype,true) +local limit=self:GetMissionLimit(mtype) +text=text..string.format(" - %s: %d [Running=%d/%d]\n",mtype,n,N,limit) +end +end +text=text..string.format("Strategic Zones: %d\n",Nzones) +for _,_stratzone in pairs(self.zonequeue)do +local stratzone=_stratzone +local owner=stratzone.opszone:GetOwnerName() +text=text..string.format(" - %s: %s - %s [I=%d, P=%d]\n",stratzone.opszone:GetName(),owner,stratzone.opszone:GetState(),stratzone.importance or 0,stratzone.prio or 0) +end +local Ntransports=#self.commander.transportqueue +if Ntransports>0 then +text=text..string.format("Transports: %d\n",Ntransports) +for _,_transport in pairs(self.commander.transportqueue)do +local transport=_transport +text=text..string.format(" - %s",transport:GetState()) +end +end +MESSAGE:New(text,60,nil,true):ToCoalition(self.coalition) +if self.verbose>=4 then +self:I(self.lid..text) +end +end +end +function CHIEF:CheckTargetQueue() +local Ntargets=#self.targetqueue +if Ntargets==0 then +return nil +end +local NoLimit=self:_CheckMissionLimit("Total") +if NoLimit==false then +return nil +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.priotaskB.threatlevel0) +end +table.sort(self.targetqueue,_sort) +local vip=math.huge +for _,_target in pairs(self.targetqueue)do +local target=_target +if target:IsAlive()and target.importance and target.importance=self.threatLevelMin and threatlevel<=self.threatLevelMax +if target.category==TARGET.Category.AIRBASE or target.category==TARGET.Category.ZONE or target.Category==TARGET.Category.COORDINATE then +isThreat=true +end +local text=string.format("Target %s: Alive=%s, Threat=%s, Important=%s",target:GetName(),tostring(isAlive),tostring(isThreat),tostring(isImportant)) +if target.mission then +text=text..string.format(", Mission \"%s\" (%s) [%s]",target.mission:GetName(),target.mission:GetState(),target.mission:GetType()) +if target.mission:IsOver()then +text=text..string.format(" - DONE ==> removing mission") +target.mission=nil +end +else +text=text..string.format(", NO mission yet") +end +self:T2(self.lid..text) +if isAlive and isThreat and isImportant and not target.mission then +local valid=false +if self.strategy==CHIEF.Strategy.PASSIVE then +valid=false +elseif self.strategy==CHIEF.Strategy.DEFENSIVE then +if self:CheckTargetInZones(target,self.borderzoneset)then +valid=true +end +elseif self.strategy==CHIEF.Strategy.OFFENSIVE then +if self:CheckTargetInZones(target,self.borderzoneset)or self:CheckTargetInZones(target,self.yellowzoneset)then +valid=true +end +elseif self.strategy==CHIEF.Strategy.AGGRESSIVE then +if self:CheckTargetInZones(target,self.borderzoneset)or self:CheckTargetInZones(target,self.yellowzoneset)or self:CheckTargetInZones(target,self.engagezoneset)then +valid=true +end +elseif self.strategy==CHIEF.Strategy.TOTALWAR then +valid=true +end +if valid then +self:T(self.lid..string.format("Got valid target %s: category=%s, threatlevel=%d",target:GetName(),target.category,threatlevel)) +local MissionPerformances=self:_GetMissionPerformanceFromTarget(target) +local mission=nil +local Legions=nil +if#MissionPerformances>0 then +for _,_mp in pairs(MissionPerformances)do +local mp=_mp +local notlimited=self:_CheckMissionLimit(mp.MissionType) +if notlimited then +local NassetsMin,NassetsMax=self:_GetAssetsForTarget(target,mp.MissionType) +self:T2(self.lid..string.format("Recruiting assets for mission type %s [performance=%d] of target %s",mp.MissionType,mp.Performance,target:GetName())) +local recruited,assets,legions=self.commander:RecruitAssetsForTarget(target,mp.MissionType,NassetsMin,NassetsMax) +if recruited then +self:T(self.lid..string.format("Recruited %d assets for mission type %s [performance=%d] of target %s",#assets,mp.MissionType,mp.Performance,target:GetName())) +mission=AUFTRAG:NewFromTarget(target,mp.MissionType) +if mission then +mission:_AddAssets(assets) +Legions=legions +break +end +else +self:T(self.lid..string.format("Could NOT recruit assets for mission type %s [performance=%d] of target %s",mp.MissionType,mp.Performance,target:GetName())) +end +end +end +end +if mission and Legions then +target.mission=mission +mission.prio=target.prio +mission.importance=target.importance +self:MissionAssign(mission,Legions) +return +end +end +end +end +end +function CHIEF:_CheckMissionLimit(MissionType) +return self.commander:_CheckMissionLimit(MissionType) +end +function CHIEF:GetMissionLimit(MissionType) +local l=self.commander.limitMission[MissionType] +if not l then +l=999 +end +return l +end +function CHIEF:CheckOpsZoneQueue() +local Nzones=#self.zonequeue +if Nzones==0 then +return nil +end +for i=Nzones,1,-1 do +local stratzone=self.zonequeue[i] +if stratzone.opszone:IsStopped()then +self:RemoveStrategicZone(stratzone.opszone) +end +end +for _,_startzone in pairs(self.zonequeue)do +local stratzone=_startzone +local ownercoalition=stratzone.opszone:GetOwner() +if ownercoalition==self.coalition or stratzone.opszone:IsEmpty()then +for _,_resource in pairs(stratzone.resourceOccup or{})do +local resource=_resource +if resource.mission then +resource.mission:Cancel() +end +end +end +end +if self:IsPassive()then +return +end +local NoLimit=self:_CheckMissionLimit("Total") +if NoLimit==false then +return nil +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio Recruiting for mission type %s: Nmin=%d, Nmax=%d",zoneName,missionType,resource.Nmin,resource.Nmax)) +local recruited=self:RecruitAssetsForZone(stratzone,resource) +if recruited then +self:T(self.lid..string.format("Successfully recruited assets for empty zone \"%s\" [mission type=%s]",zoneName,missionType)) +else +self:T(self.lid..string.format("Could not recruited assets for empty zone \"%s\" [mission type=%s]",zoneName,missionType)) +end +end +end +else +for _,_resource in pairs(stratzone.resourceOccup or{})do +local resource=_resource +local missionType=resource.MissionType +if(not resource.mission)or resource.mission:IsOver()then +self:T2(self.lid..string.format("Zone %s is NOT empty ==> Recruiting for mission type %s: Nmin=%d, Nmax=%d",zoneName,missionType,resource.Nmin,resource.Nmax)) +local recruited=self:RecruitAssetsForZone(stratzone,resource) +if recruited then +self:T(self.lid..string.format("Successfully recruited assets for occupied zone %s, mission type=%s",zoneName,missionType)) +else +self:T(self.lid..string.format("Could not recruited assets for occupied zone %s, mission type=%s",zoneName,missionType)) +end +end +end +end +end +end +end +function CHIEF:CheckGroupInBorder(group) +local inside=self:CheckGroupInZones(group,self.borderzoneset) +return inside +end +function CHIEF:CheckGroupInConflict(group) +local inside=self:CheckGroupInZones(group,self.yellowzoneset) +return inside +end +function CHIEF:CheckGroupInAttack(group) +local inside=self:CheckGroupInZones(group,self.engagezoneset) +return inside +end +function CHIEF:CheckGroupInZones(group,zoneset) +for _,_zone in pairs(zoneset.Set or{})do +local zone=_zone +if group:IsInZone(zone)then +return true +end +end +return false +end +function CHIEF:CheckTargetInZones(target,zoneset) +for _,_zone in pairs(zoneset.Set or{})do +local zone=_zone +if zone:IsCoordinateInZone(target:GetCoordinate())then +return true +end +end +return false +end +function CHIEF:_CreateMissionPerformance(MissionType,Performance) +local mp={} +mp.MissionType=MissionType +mp.Performance=Performance +return mp +end +function CHIEF:_GetMissionPerformanceFromTarget(Target) +local group=nil +local airbase=nil +local scenery=nil +local static=nil +local coordinate=nil +local target=Target:GetObject() +if target:IsInstanceOf("GROUP")then +group=target +elseif target:IsInstanceOf("UNIT")then +group=target:GetGroup() +elseif target:IsInstanceOf("AIRBASE")then +airbase=target +elseif target:IsInstanceOf("STATIC")then +static=target +elseif target:IsInstanceOf("SCENERY")then +scenery=target +end +local TargetCategory=Target:GetCategory() +local missionperf={} +if group then +local category=group:GetCategory() +local attribute=group:GetAttribute() +if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.INTERCEPT,100)) +elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then +if attribute==GROUP.Attribute.GROUND_SAM then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.SEAD,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) +elseif attribute==GROUP.Attribute.GROUND_EWR then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) +elseif attribute==GROUP.Attribute.GROUND_AAA then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,40)) +elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,75)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,70)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,70)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) +elseif attribute==GROUP.Attribute.GROUND_INFANTRY then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,40)) +elseif attribute==GROUP.Attribute.GROUND_TANK then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.CAS,90)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.CASENHANCED,90)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,40)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) +else +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) +end +elseif category==Group.Category.SHIP then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ANTISHIP,100)) +else +self:E(self.lid.."ERROR: Unknown Group category!") +end +elseif airbase then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBRUNWAY,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) +elseif static then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,70)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) +elseif scenery then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.STRIKE,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,70)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) +elseif coordinate then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,100)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) +end +return missionperf +end +function CHIEF:_GetMissionTypeForGroupAttribute(Attribute) +local missionperf={} +if Attribute==GROUP.Attribute.AIR_ATTACKHELO then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.INTERCEPT),100) +elseif Attribute==GROUP.Attribute.GROUND_AAA then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI),100) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING),80) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET),70) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY),30) +elseif Attribute==GROUP.Attribute.GROUND_SAM then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.SEAD),100) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI),90) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY),50) +elseif Attribute==GROUP.Attribute.GROUND_EWR then +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.SEAD),100) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI),100) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY),50) +end +return missionperf +end +function CHIEF:RecruitAssetsForZone(StratZone,Resource) +local Cohorts=self.commander:_GetCohorts() +local MissionType=Resource.MissionType +local NassetsMin=Resource.Nmin +local NassetsMax=Resource.Nmax +local Categories=Resource.Categories +local Attributes=Resource.Attributes +local Properties=Resource.Properties +local TargetVec2=StratZone.opszone.zone:GetVec2() +local RangeMax=nil +if MissionType==AUFTRAG.Type.PATROLZONE or MissionType==AUFTRAG.Type.ONGUARD then +RangeMax=UTILS.NMToMeters(250) +end +if MissionType==AUFTRAG.Type.ARMOREDGUARD then +RangeMax=UTILS.NMToMeters(50) +end +self:T(self.lid..string.format("Recruiting assets for zone %s",StratZone.opszone:GetName())) +self:T(self.lid.."Missiontype="..MissionType) +self:T({categories=Categories}) +self:T({attributes=Attributes}) +self:T({properties=Properties}) +local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,MissionType,nil,NassetsMin,NassetsMax,TargetVec2,nil,RangeMax,nil,nil,nil,nil,Categories,Attributes,Properties) +if recruited then +local mission=nil +self:T2(self.lid..string.format("Recruited %d assets for %s mission STRATEGIC zone %s",#assets,MissionType,tostring(StratZone.opszone.zoneName))) +local TargetZone=StratZone.opszone.zone +local TargetCoord=TargetZone:GetCoordinate() +local transport=nil +local Ntransports=0 +if Resource.carrierNmin and Resource.carrierNmax and Resource.carrierNmax>0 then +self:T(self.lid..string.format("Recruiting carrier assets: Nmin=%s, Nmax=%s",tostring(Resource.carrierNmin),tostring(Resource.carrierNmax))) +local cargoassets=CHIEF._FilterAssets(assets,Resource.Categories,Resource.Attributes,Resource.Properties) +if#cargoassets>0 then +recruited,transport=LEGION.AssignAssetsForTransport(self.commander,self.commander.legions,cargoassets, +Resource.carrierNmin,Resource.carrierNmax,TargetZone,nil,Resource.carrierCategories,Resource.carrierAttributes,Resource.carrierProperties) +Ntransports=transport~=nil and#transport.assets or 0 +self:T(self.lid..string.format("Recruited %d transport carrier assets success=%s",Ntransports,tostring(recruited))) +end +end +if not recruited then +self:T(self.lid..string.format("Could not allocate assets or transport of OPSZONE!")) +LEGION.UnRecruitAssets(assets) +return false +end +self:T2(self.lid..string.format("Recruited %d assets for mission %s",#assets,MissionType)) +if MissionType==AUFTRAG.Type.PATROLZONE or MissionType==AUFTRAG.Type.ONGUARD then +if MissionType==AUFTRAG.Type.PATROLZONE then +mission=AUFTRAG:NewPATROLZONE(TargetZone) +elseif MissionType==AUFTRAG.Type.ONGUARD then +mission=AUFTRAG:NewONGUARD(TargetZone:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND})) +end +mission:SetEngageDetected(25,{"Ground Units","Light armed ships","Helicopters"}) +elseif MissionType==AUFTRAG.Type.CAPTUREZONE then +mission=AUFTRAG:NewCAPTUREZONE(StratZone.opszone,self.coalition) +elseif MissionType==AUFTRAG.Type.CASENHANCED then +local height=UTILS.MetersToFeet(TargetCoord:GetLandHeight())+2500 +local Speed=200 +if assets[1]then +if assets[1].speedmax then +Speed=UTILS.KmphToKnots(assets[1].speedmax*0.7)or 200 +end +end +mission=AUFTRAG:NewCASENHANCED(TargetZone,height,Speed) +elseif MissionType==AUFTRAG.Type.CAS then +local height=UTILS.MetersToFeet(TargetCoord:GetLandHeight())+2500 +local Speed=200 +if assets[1]then +if assets[1].speedmax then +Speed=UTILS.KmphToKnots(assets[1].speedmax*0.7)or 200 +end +end +TargetZone=StratZone.opszone.zoneCircular +local Leg=TargetZone:GetRadius()<=10000 and 5 or UTILS.MetersToNM(TargetZone:GetRadius()) +mission=AUFTRAG:NewCAS(TargetZone,height,Speed,TargetCoord,math.random(0,359),Leg) +elseif MissionType==AUFTRAG.Type.ARTY then +local Radius=TargetZone:GetRadius() +mission=AUFTRAG:NewARTY(TargetCoord,120,Radius) +elseif MissionType==AUFTRAG.Type.ARMOREDGUARD then +mission=AUFTRAG:NewARMOREDGUARD(TargetCoord) +elseif MissionType==AUFTRAG.Type.BOMBCARPET then +mission=AUFTRAG:NewBOMBCARPET(TargetCoord,nil,1000) +elseif MissionType==AUFTRAG.Type.BOMBING then +local coord=TargetZone:GetRandomCoordinate() +mission=AUFTRAG:NewBOMBING(TargetCoord) +elseif MissionType==AUFTRAG.Type.RECON then +mission=AUFTRAG:NewRECON(TargetZone,nil,5000) +elseif MissionType==AUFTRAG.Type.BARRAGE then +mission=AUFTRAG:NewBARRAGE(TargetZone) +elseif MissionType==AUFTRAG.Type.AMMOSUPPLY then +mission=AUFTRAG:NewAMMOSUPPLY(TargetZone) +end +if mission then +mission:_AddAssets(assets) +self:MissionAssign(mission,legions) +StratZone.opszone:_AddMission(self.coalition,MissionType,mission) +mission:SetName(string.format("Stratzone %s-%d",StratZone.opszone:GetName(),mission.auftragsnummer)) +Resource.mission=mission +if transport and Ntransports>0 then +mission.opstransport=transport +transport.opszone=StratZone.opszone +transport.chief=self +transport.commander=self.commander +end +return true +else +self:E(self.lid..string.format("ERROR: Mission type %s not supported for OPSZONE! Unrecruiting assets...",tostring(MissionType))) +LEGION.UnRecruitAssets(assets) +return false +end +end +self:T2(self.lid..string.format("Could NOT recruit assets for %s mission of STRATEGIC zone %s",MissionType,tostring(StratZone.opszone.zoneName))) +return false +end +function CHIEF._FilterAssets(Assets,Categories,Attributes,Properties) +local filtered={} +for _,_asset in pairs(Assets)do +local asset=_asset +local hasCat=CHIEF._CheckAssetCategories(asset,Categories) +local hasAtt=CHIEF._CheckAssetAttributes(asset,Attributes) +local hasPro=CHIEF._CheckAssetProperties(asset,Properties) +if hasAtt and hasCat and hasPro then +table.insert(filtered,asset) +end +end +return filtered +end +function CHIEF._CheckAssetAttributes(Asset,Attributes) +if not Attributes then +return true +end +for _,attribute in pairs(UTILS.EnsureTable(Attributes))do +if attribute==Asset.attribute then +return true +end +end +return false +end +function CHIEF._CheckAssetCategories(Asset,Categories) +if not Categories then +return true +end +for _,attribute in pairs(UTILS.EnsureTable(Categories))do +if attribute==Asset.category then +return true +end +end +return false +end +function CHIEF._CheckAssetProperties(Asset,Properties) +if not Properties then +return true +end +for _,attribute in pairs(UTILS.EnsureTable(Properties))do +if attribute==Asset.DCSdesc then +return true +end +end +return false +end +COHORT={ +ClassName="COHORT", +verbose=0, +lid=nil, +name=nil, +templatename=nil, +assets={}, +missiontypes={}, +repairtime=0, +maintenancetime=0, +livery=nil, +skill=nil, +legion=nil, +Ngroups=0, +engageRange=nil, +tacanChannel={}, +weightAsset=99999, +cargobayLimit=0, +descriptors={}, +properties={}, +operations={}, +} +COHORT.version="0.3.7" +_COHORTNAMES={} +function COHORT:New(TemplateGroupName,Ngroups,CohortName) +local name=tostring(CohortName or TemplateGroupName) +if UTILS.IsAnyInTable(_COHORTNAMES,name)then +env.error(string.format('ERROR: cannot create cohort "%s" because another cohort with that name already exists. Names must be unique!',name)) +return nil +else +table.insert(_COHORTNAMES,name) +end +local self=BASE:Inherit(self,FSM:New()) +self.templatename=TemplateGroupName +self.name=name +self.lid=string.format("COHORT %s | ",self.name) +self.templategroup=GROUP:FindByName(self.templatename) +if not self.templategroup then +self:E(self.lid..string.format("ERROR: Template group %s does not exist!",tostring(self.templatename))) +return nil +end +self.attribute=self.templategroup:GetAttribute() +self.category=self.templategroup:GetCategory() +self.aircrafttype=self.templategroup:GetTypeName() +self.descriptors=self.templategroup:GetUnit(1):GetDesc() +self.properties=self.descriptors.attributes +self.Ngroups=Ngroups or 3 +self:SetSkill(AI.Skill.GOOD) +if self.category==Group.Category.AIRPLANE then +self:SetMissionRange(200) +elseif self.category==Group.Category.HELICOPTER then +self:SetMissionRange(150) +elseif self.category==Group.Category.GROUND then +self:SetMissionRange(75) +elseif self.category==Group.Category.SHIP then +self:SetMissionRange(100) +elseif self.category==Group.Category.TRAIN then +self:SetMissionRange(100) +else +self:SetMissionRange(150) +end +local units=self.templategroup:GetUnits() +self.weightAsset=0 +for i,_unit in pairs(units)do +local unit=_unit +local desc=unit:GetDesc() +local mass=666 +if desc then +mass=desc.massMax or desc.massEmpty +end +self.weightAsset=self.weightAsset+(mass or 666) +if i==1 then +self.cargobayLimit=unit:GetCargoBayFreeWeight() +end +end +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","OnDuty") +self:AddTransition("*","Status","*") +self:AddTransition("OnDuty","Pause","Paused") +self:AddTransition("Paused","Unpause","OnDuty") +self:AddTransition("OnDuty","Relocate","Relocating") +self:AddTransition("Relocating","Relocated","OnDuty") +self:AddTransition("*","Stop","Stopped") +return self +end +function COHORT:SetLivery(LiveryName) +self.livery=LiveryName +return self +end +function COHORT:SetSkill(Skill) +self.skill=Skill +return self +end +function COHORT:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function COHORT:SetTurnoverTime(MaintenanceTime,RepairTime) +self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0 +self.repairtime=RepairTime and RepairTime*60 or 0 +return self +end +function COHORT:SetRadio(Frequency,Modulation) +self.radioFreq=Frequency or 251 +self.radioModu=Modulation or radio.modulation.AM +return self +end +function COHORT:SetGrouping(nunits) +self.ngrouping=nunits or 2 +return self +end +function COHORT:AddMissionCapability(MissionTypes,Performance) +if MissionTypes and type(MissionTypes)~="table"then +MissionTypes={MissionTypes} +end +self.missiontypes=self.missiontypes or{} +for _,missiontype in pairs(MissionTypes)do +local Capability=self:GetMissionCapability(missiontype) +if Capability then +self:E(self.lid.."WARNING: Mission capability already present! No need to add it twice. Will update the performance though!") +Capability.Performance=Performance or 50 +else +local capability={} +capability.MissionType=missiontype +capability.Performance=Performance or 50 +table.insert(self.missiontypes,capability) +self:T(self.lid..string.format("Adding mission capability %s, performance=%d",tostring(capability.MissionType),capability.Performance)) +end +end +self:T2(self.missiontypes) +return self +end +function COHORT:GetMissionCapability(MissionType) +for _,_capability in pairs(self.missiontypes)do +local capability=_capability +if capability.MissionType==MissionType then +return capability +end +end +return nil +end +function COHORT:HasProperty(Property) +for _,property in pairs(self.properties)do +if Property==property then +return true +end +end +return false +end +function COHORT:GetMissionTypes() +local missiontypes={} +for _,Capability in pairs(self.missiontypes)do +local capability=Capability +table.insert(missiontypes,capability.MissionType) +end +return missiontypes +end +function COHORT:GetMissionCapabilities() +return self.missiontypes +end +function COHORT:GetMissionPeformance(MissionType) +for _,Capability in pairs(self.missiontypes)do +local capability=Capability +if capability.MissionType==MissionType then +return capability.Performance +end +end +return-1 +end +function COHORT:SetMissionRange(Range) +self.engageRange=UTILS.NMToMeters(Range or 150) +return self +end +function COHORT:SetCallsign(Callsign,Index,CallsignString) +self.callsignName=Callsign +self.callsignIndex=Index +self.callsignClearName=CallsignString +self.callsign={} +self.callsign.NumberSquad=Callsign +self.callsign.NumberGroup=Index +return self +end +function COHORT:SetAttribute(Attribute) +self.attribute=Attribute +return self +end +function COHORT:GetAttribute() +return self.attribute +end +function COHORT:GetCategory() +return self.category +end +function COHORT:GetProperties() +return self.properties +end +function COHORT:SetModex(Modex,Prefix,Suffix) +self.modex=Modex +self.modexPrefix=Prefix +self.modexSuffix=Suffix +return self +end +function COHORT:SetLegion(Legion) +self.legion=Legion +return self +end +function COHORT:AddAsset(Asset) +self:T(self.lid..string.format("Adding asset %s of type %s",Asset.spawngroupname,Asset.unittype)) +Asset.squadname=self.name +Asset.legion=self.legion +Asset.cohort=self +table.insert(self.assets,Asset) +return self +end +function COHORT:DelAsset(Asset) +for i,_asset in pairs(self.assets)do +local asset=_asset +if Asset.uid==asset.uid then +self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +table.remove(self.assets,i) +break +end +end +return self +end +function COHORT:DelGroup(GroupName) +for i,_asset in pairs(self.assets)do +local asset=_asset +if GroupName==asset.spawngroupname then +self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +table.remove(self.assets,i) +break +end +end +return self +end +function COHORT:RemoveAssets(N) +self:T2(self.lid..string.format("Remove %d assets of Cohort",N)) +N=N or 1 +local n=0 +for i=#self.assets,1,-1 do +local asset=self.assets[i] +self:T2(self.lid..string.format("Checking removing asset %s",asset.spawngroupname)) +if not(asset.requested or asset.spawned or asset.isReserved)then +self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +table.remove(self.assets,i) +n=n+1 +else +self:T2(self.lid..string.format("Could NOT Remove asset %s",asset.spawngroupname)) +end +if n>=N then +break +end +end +self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d",n,N,#self.assets)) +return self +end +function COHORT:GetName() +return self.name +end +function COHORT:GetRadio() +return self.radioFreq,self.radioModu +end +function COHORT:GetCallsign(Asset) +if self.callsignName then +Asset.callsign={} +for i=1,Asset.nunits do +local callsign={} +callsign[1]=self.callsignName +callsign[2]=math.floor(self.callsigncounter/10) +callsign[3]=self.callsigncounter%10 +if callsign[3]==0 then +callsign[3]=1 +self.callsigncounter=self.callsigncounter+2 +else +self.callsigncounter=self.callsigncounter+1 +end +callsign["name"]=self.callsignClearName or UTILS.GetCallsignName(self.callsignName)or"None" +callsign["name"]=string.format("%s%d%d",callsign["name"],callsign[2],callsign[3]) +callsign[4]=callsign["name"] +Asset.callsign[i]=callsign +self:T3({callsign=callsign}) +end +end +end +function COHORT:GetModex(Asset) +if self.modex then +Asset.modex={} +for i=1,Asset.nunits do +Asset.modex[i]=string.format("%03d",self.modex+self.modexcounter) +self.modexcounter=self.modexcounter+1 +self:T3({modex=Asset.modex[i]}) +end +end +end +function COHORT:AddTacanChannel(ChannelMin,ChannelMax) +ChannelMax=ChannelMax or ChannelMin +if ChannelMin>126 then +self:E(self.lid.."ERROR: TACAN Channel must be <= 126! Will not add to available channels") +return self +end +if ChannelMax>126 then +self:E(self.lid.."WARNING: TACAN Channel must be <= 126! Adjusting ChannelMax to 126") +ChannelMax=126 +end +for i=ChannelMin,ChannelMax do +self.tacanChannel[i]=true +end +return self +end +function COHORT:FetchTacan() +local freechannel=nil +for channel,free in pairs(self.tacanChannel)do +if free then +if freechannel==nil or channel=2 then +local text="Weapon data:" +for _,_weapondata in pairs(self.weaponData)do +local weapondata=_weapondata +text=text..string.format("\n- Bit=%s, Rmin=%d m, Rmax=%d m",tostring(weapondata.BitType),weapondata.RangeMin,weapondata.RangeMax) +end +self:I(self.lid..text) +end +return self +end +function COHORT:GetWeaponData(BitType) +return self.weaponData[tostring(BitType)] +end +function COHORT:IsOnDuty() +return self:Is("OnDuty") +end +function COHORT:IsStopped() +return self:Is("Stopped") +end +function COHORT:IsPaused() +return self:Is("Paused") +end +function COHORT:IsRelocating() +return self:Is("Relocating") +end +function COHORT:onafterStart(From,Event,To) +local text=string.format("Starting %s v%s %s [%s]",self.ClassName,self.version,self.name,self.attribute) +self:I(self.lid..text) +self:__Status(-1) +end +function COHORT:_CheckAssetStatus() +if self.verbose>=2 and#self.assets>0 then +local text="" +for j,_asset in pairs(self.assets)do +local asset=_asset +text=text..string.format("\n[%d] %s (%s*%d): ",j,asset.spawngroupname,asset.unittype,asset.nunits) +if asset.spawned then +local mission=self.legion and self.legion:GetAssetCurrentMission(asset)or false +if mission then +local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate()))or 0 +text=text..string.format("Mission %s - %s: Status=%s, Dist=%.1f NM",mission.name,mission.type,mission.status,distance) +else +text=text.."Mission None" +end +text=text..", Flight: " +if asset.flightgroup and asset.flightgroup:IsAlive()then +local status=asset.flightgroup:GetState() +text=text..string.format("%s",status) +if asset.flightgroup:IsFlightgroup()then +local fuelmin=asset.flightgroup:GetFuelMin() +local fuellow=asset.flightgroup:IsFuelLow() +local fuelcri=asset.flightgroup:IsFuelCritical() +text=text..string.format("Fuel=%d",fuelmin) +if fuelcri then +text=text.." (Critical!)" +elseif fuellow then +text=text.." (Low)" +end +end +local lifept,lifept0=asset.flightgroup:GetLifePoints() +text=text..string.format(", Life=%d/%d",lifept,lifept0) +local ammo=asset.flightgroup:GetAmmoTot() +text=text..string.format(", Ammo=%d [G=%d, R=%d, B=%d, M=%d]",ammo.Total,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles) +else +text=text.."N/A" +end +if asset.flightgroup:IsFlightgroup()then +local payload=asset.payload and table.concat(self.legion:GetPayloadMissionTypes(asset.payload),", ")or"None" +text=text..", Payload={"..payload.."}" +end +else +text=text..string.format("In Stock") +if self:IsRepaired(asset)then +text=text..", Combat Ready" +else +text=text..string.format(", Repaired in %d sec",self:GetRepairTime(asset)) +if asset.damage then +text=text..string.format(" (Damage=%.1f)",asset.damage) +end +end +if asset.Treturned then +local T=timer.getAbsTime()-asset.Treturned +text=text..string.format(", Returned for %d sec",T) +end +end +end +self:T(self.lid..text) +end +end +function COHORT:onafterStop(From,Event,To) +self:T(self.lid.."STOPPING Cohort and removing all assets!") +for i=#self.assets,1,-1 do +local asset=self.assets[i] +self:DelAsset(asset) +end +self.CallScheduler:Clear() +end +function COHORT:CanMission(Mission) +local cando=true +if not self:IsOnDuty()then +self:T(self.lid..string.format("Cohort in not OnDuty but in state %s. Cannot do mission %s with target %s",self:GetState(),Mission.name,Mission:GetTargetName())) +return false +end +if not AUFTRAG.CheckMissionType(Mission.type,self:GetMissionTypes())then +self:T(self.lid..string.format("INFO: Cohort cannot do mission type %s (%s, %s)",Mission.type,Mission.name,Mission:GetTargetName())) +return false +end +if Mission.type==AUFTRAG.Type.TANKER then +if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then +self:T(self.lid..string.format("INFO: Correct refueling system requested=%s != %s=available",tostring(Mission.refuelSystem),tostring(self.tankerSystem))) +else +self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available",tostring(Mission.refuelSystem),tostring(self.tankerSystem))) +return false +end +end +local TargetDistance=Mission:GetTargetDistance(self.legion:GetCoordinate()) +local engagerange=Mission.engageRange and math.max(self.engageRange,Mission.engageRange)or self.engageRange +if TargetDistance>engagerange then +self:T(self.lid..string.format("INFO: Cohort is not in range. Target dist=%d > %d NM max mission Range",UTILS.MetersToNM(TargetDistance),UTILS.MetersToNM(engagerange))) +return false +end +return true +end +function COHORT:CountAssets(InStock,MissionTypes,Attributes) +local N=0 +for _,_asset in pairs(self.assets)do +local asset=_asset +if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes,self.missiontypes)then +if Attributes==nil or self:CheckAttribute(Attributes)then +if asset.spawned then +if InStock==false or InStock==nil then +N=N+1 +end +else +if InStock==true or InStock==nil then +N=N+1 +end +end +end +end +end +return N +end +function COHORT:GetOpsGroups(MissionTypes,Attributes) +local set=SET_OPSGROUP:New() +for _,_asset in pairs(self.assets)do +local asset=_asset +if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes,self.missiontypes)then +if Attributes==nil or self:CheckAttribute(Attributes)then +if asset.flightgroup and asset.flightgroup:IsAlive()then +set:AddGroup(asset.flightgroup) +end +end +end +end +return set +end +function COHORT:RecruitAssets(MissionType,Npayloads) +self:T2(self.lid..string.format("Recruiting asset for Mission type=%s",MissionType)) +local assets={} +for _,_asset in pairs(self.assets)do +local asset=_asset +local isRequested=asset.requested +local isReserved=asset.isReserved +local isSpawned=asset.spawned +local isOnMission=self.legion:IsAssetOnMission(asset) +local opsgroup=asset.flightgroup +self:T(self.lid..string.format("Asset %s: requested=%s, reserved=%s, spawned=%s, onmission=%s", +asset.spawngroupname,tostring(isRequested),tostring(isReserved),tostring(isSpawned),tostring(isOnMission))) +if not(isRequested or isReserved)then +if self.legion:IsAssetOnMission(asset)then +if MissionType==AUFTRAG.Type.RELOCATECOHORT then +table.insert(assets,asset) +elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.NOTHING)then +table.insert(assets,asset) +elseif self.legion:IsAssetOnMission(asset,{AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})and MissionType==AUFTRAG.Type.INTERCEPT then +self:T(self.lid..string.format("Adding asset on GCICAP mission for an INTERCEPT mission")) +table.insert(assets,asset) +elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.ONGUARD)and(MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK)then +if not opsgroup:IsOutOfAmmo()then +self:T(self.lid..string.format("Adding asset on ONGUARD mission for an XXX mission")) +table.insert(assets,asset) +end +elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.PATROLZONE)and(MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK)then +if not opsgroup:IsOutOfAmmo()then +self:T(self.lid..string.format("Adding asset on PATROLZONE mission for an XXX mission")) +table.insert(assets,asset) +end +elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.ALERT5)and AUFTRAG.CheckMissionCapability(MissionType,asset.payload.capabilities)and MissionType~=AUFTRAG.Type.ALERT5 then +self:T(self.lid..string.format("Adding asset on ALERT 5 mission for %s mission",MissionType)) +table.insert(assets,asset) +end +else +if asset.spawned then +local flightgroup=asset.flightgroup +if flightgroup and flightgroup:IsAlive()and not(flightgroup:IsDead()or flightgroup:IsStopped())then +local combatready=true +if flightgroup:IsFlightgroup()then +if flightgroup:IsFuelLow()then +combatready=false +end +if MissionType==AUFTRAG.Type.INTERCEPT and not flightgroup:CanAirToAir()then +combatready=false +else +local excludeguns=MissionType==AUFTRAG.Type.BOMBING or MissionType==AUFTRAG.Type.BOMBRUNWAY or MissionType==AUFTRAG.Type.BOMBCARPET or MissionType==AUFTRAG.Type.SEAD or MissionType==AUFTRAG.Type.ANTISHIP +if excludeguns and not flightgroup:CanAirToGround(excludeguns)then +combatready=false +end +end +if flightgroup:IsHolding()or flightgroup:IsLanding()or flightgroup:IsLanded()or flightgroup:IsArrived()then +combatready=false +end +if asset.payload and not AUFTRAG.CheckMissionCapability(MissionType,asset.payload.capabilities)then +combatready=false +end +else +if flightgroup:IsArmygroup()then +if asset.attribute==WAREHOUSE.Attribute.GROUND_ARTILLERY or +asset.attribute==WAREHOUSE.Attribute.GROUND_TANK or +asset.attribute==WAREHOUSE.Attribute.GROUND_INFANTRY or +asset.attribute==WAREHOUSE.Attribute.GROUND_AAA or +asset.attribute==WAREHOUSE.Attribute.GROUND_SAM +then +combatready=true +end +else +combatready=false +end +if flightgroup:IsRearming()or flightgroup:IsRetreating()or flightgroup:IsReturning()then +combatready=false +end +end +if flightgroup:IsLoading()or flightgroup:IsTransporting()or flightgroup:IsUnloading()or flightgroup:IsPickingup()or flightgroup:IsCarrier()then +combatready=false +end +if flightgroup:IsCargo()or flightgroup:IsBoarding()or flightgroup:IsAwaitingLift()then +combatready=false +end +if combatready then +self:T(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY") +table.insert(assets,asset) +end +end +else +if Npayloads>0 and self:IsRepaired(asset)then +table.insert(assets,asset) +Npayloads=Npayloads-1 +end +end +end +end +end +self:T2(self.lid..string.format("Recruited %d assets for Mission type=%s",#assets,MissionType)) +return assets,Npayloads +end +function COHORT:GetRepairTime(Asset) +if Asset.Treturned then +local t=self.maintenancetime +t=t+Asset.damage*self.repairtime +local dt=timer.getAbsTime()-Asset.Treturned +local T=t-dt +return T +else +return 0 +end +end +function COHORT:GetMissionRange(WeaponTypes) +if WeaponTypes and type(WeaponTypes)~="table"then +WeaponTypes={WeaponTypes} +end +local function checkWeaponType(Weapon) +local weapon=Weapon +if WeaponTypes and#WeaponTypes>0 then +for _,weapontype in pairs(WeaponTypes)do +if weapontype==weapon.BitType then +return true +end +end +return false +end +return true +end +local WeaponRange=0 +for _,_weapon in pairs(self.weaponData or{})do +local weapon=_weapon +if weapon.RangeMax>WeaponRange and checkWeaponType(weapon)then +WeaponRange=weapon.RangeMax +end +end +return self.engageRange+WeaponRange +end +function COHORT:IsRepaired(Asset) +if Asset.Treturned then +local Tnow=timer.getAbsTime() +local Trepaired=Asset.Treturned+self.maintenancetime +if Tnow>=Trepaired then +return true +else +return false +end +else +return true +end +end +function COHORT:CheckAttribute(Attributes) +if type(Attributes)~="table"then +Attributes={Attributes} +end +for _,attribute in pairs(Attributes)do +if attribute==self.attribute then +return true +end +end +return false +end +function COHORT:_CheckAmmo() +local units=self.templategroup:GetUnits() +local nammo=0 +local nguns=0 +local nshells=0 +local nrockets=0 +local nmissiles=0 +local nmissilesAA=0 +local nmissilesAG=0 +local nmissilesAS=0 +local nmissilesSA=0 +local nmissilesBM=0 +local nmissilesCR=0 +local ntorps=0 +local nbombs=0 +for _,_unit in pairs(units)do +local unit=_unit +local text=string.format("Unit %s:\n",unit:GetName()) +local ammotable=unit:GetAmmo() +if ammotable then +self:T3(ammotable) +for w=1,#ammotable do +local weapon=ammotable[w] +local Desc=weapon["desc"] +local Warhead=Desc["warhead"] +local Nammo=weapon["count"] +local Category=Desc["category"] +local MissileCategory=(Category==Weapon.Category.MISSILE)and Desc.missileCategory or nil +local TypeName=Desc["typeName"] +local weaponString=UTILS.Split(TypeName,"%.") +local WeaponName=weaponString[#weaponString] +local Rmin=Desc["rangeMin"]or 0 +local Rmax=Desc["rangeMaxAltMin"]or 0 +local Caliber=Warhead and Warhead["caliber"]or 0 +if Category==Weapon.Category.SHELL then +if Caliber<70 then +nguns=nguns+Nammo +else +nshells=nshells+Nammo +end +text=text..string.format("- %d shells [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax) +elseif Category==Weapon.Category.ROCKET then +nrockets=nrockets+Nammo +text=text..string.format("- %d rockets [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax) +elseif Category==Weapon.Category.BOMB then +nbombs=nbombs+Nammo +text=text..string.format("- %d bombs [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax) +elseif Category==Weapon.Category.MISSILE then +if MissileCategory==Weapon.MissileCategory.AAM then +nmissiles=nmissiles+Nammo +nmissilesAA=nmissilesAA+Nammo +if Rmax>0 then +self:AddWeaponRange(UTILS.MetersToNM(Rmin),UTILS.MetersToNM(Rmax),ENUMS.WeaponFlag.AnyAA) +end +elseif MissileCategory==Weapon.MissileCategory.SAM then +nmissiles=nmissiles+Nammo +nmissilesSA=nmissilesSA+Nammo +if Rmax>0 then +end +elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then +nmissiles=nmissiles+Nammo +nmissilesAS=nmissilesAS+Nammo +if Rmax>0 then +self:AddWeaponRange(UTILS.MetersToNM(Rmin),UTILS.MetersToNM(Rmax),ENUMS.WeaponFlag.AntiShipMissile) +end +elseif MissileCategory==Weapon.MissileCategory.BM then +nmissiles=nmissiles+Nammo +nmissilesBM=nmissilesBM+Nammo +if Rmax>0 then +end +elseif MissileCategory==Weapon.MissileCategory.CRUISE then +nmissiles=nmissiles+Nammo +nmissilesCR=nmissilesCR+Nammo +if Rmax>0 then +self:AddWeaponRange(UTILS.MetersToNM(Rmin),UTILS.MetersToNM(Rmax),ENUMS.WeaponFlag.CruiseMissile) +end +elseif MissileCategory==Weapon.MissileCategory.OTHER then +nmissiles=nmissiles+Nammo +nmissilesAG=nmissilesAG+Nammo +end +text=text..string.format("- %d %s missiles [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,self:_MissileCategoryName(MissileCategory),WeaponName,Caliber,Rmin,Rmax) +elseif Category==Weapon.Category.TORPEDO then +ntorps=ntorps+Nammo +text=text..string.format("- %d torpedos [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax) +else +text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,TypeName,Category,tostring(MissileCategory)) +end +end +end +if self.verbose>=5 then +self:I(self.lid..text) +else +self:T2(self.lid..text) +end +end +nammo=nguns+nshells+nrockets+nmissiles+nbombs+ntorps +local ammo={} +ammo.Total=nammo +ammo.Guns=nguns +ammo.Shells=nshells +ammo.Rockets=nrockets +ammo.Bombs=nbombs +ammo.Torpedos=ntorps +ammo.Missiles=nmissiles +ammo.MissilesAA=nmissilesAA +ammo.MissilesAG=nmissilesAG +ammo.MissilesAS=nmissilesAS +ammo.MissilesCR=nmissilesCR +ammo.MissilesBM=nmissilesBM +ammo.MissilesSA=nmissilesSA +return ammo +end +function COHORT:_MissileCategoryName(categorynumber) +local cat="unknown" +if categorynumber==Weapon.MissileCategory.AAM then +cat="air-to-air" +elseif categorynumber==Weapon.MissileCategory.SAM then +cat="surface-to-air" +elseif categorynumber==Weapon.MissileCategory.BM then +cat="ballistic" +elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then +cat="anti-ship" +elseif categorynumber==Weapon.MissileCategory.CRUISE then +cat="cruise" +elseif categorynumber==Weapon.MissileCategory.OTHER then +cat="other" +end +return cat +end +function COHORT:_AddOperation(Operation) +self.operations[Operation.name]=Operation +end +COMMANDER={ +ClassName="COMMANDER", +verbose=0, +coalition=nil, +legions={}, +missionqueue={}, +transportqueue={}, +targetqueue={}, +opsqueue={}, +rearmingZones={}, +refuellingZones={}, +capZones={}, +gcicapZones={}, +awacsZones={}, +tankerZones={}, +limitMission={}, +maxMissionsAssignPerCycle=1, +} +COMMANDER.version="0.1.4" +function COMMANDER:New(Coalition,Alias) +local self=BASE:Inherit(self,FSM:New()) +if Coalition==nil then +env.error("ERROR: Coalition parameter is nil in COMMANDER:New() call!") +return nil +end +self.coalition=Coalition +self.alias=Alias +if self.alias==nil then +if Coalition==coalition.side.BLUE then +self.alias="George S. Patton" +elseif Coalition==coalition.side.RED then +self.alias="Georgy Zhukov" +elseif Coalition==coalition.side.NEUTRAL then +self.alias="Mahatma Gandhi" +end +end +self.lid=string.format("COMMANDER %s [%s] | ",self.alias,UTILS.GetCoalitionName(self.coalition)) +self:SetStartState("NotReadyYet") +self:AddTransition("NotReadyYet","Start","OnDuty") +self:AddTransition("*","Status","*") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("*","MissionAssign","*") +self:AddTransition("*","MissionCancel","*") +self:AddTransition("*","TransportAssign","*") +self:AddTransition("*","TransportCancel","*") +self:AddTransition("*","OpsOnMission","*") +self:AddTransition("*","LegionLost","*") +return self +end +function COMMANDER:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function COMMANDER:SetLimitMission(Limit,MissionType) +MissionType=MissionType or"Total" +if MissionType then +self.limitMission[MissionType]=Limit or 10 +else +self:E(self.lid.."ERROR: No mission type given for setting limit!") +end +return self +end +function COMMANDER:GetCoalition() +return self.coalition +end +function COMMANDER:AddAirwing(Airwing) +self:AddLegion(Airwing) +return self +end +function COMMANDER:AddBrigade(Brigade) +self:AddLegion(Brigade) +return self +end +function COMMANDER:AddFleet(Fleet) +self:AddLegion(Fleet) +return self +end +function COMMANDER:AddLegion(Legion) +Legion.commander=self +table.insert(self.legions,Legion) +return self +end +function COMMANDER:RemoveLegion(Legion) +for i,_legion in pairs(self.legions)do +local legion=_legion +if legion.alias==Legion.alias then +table.remove(self.legions,i) +Legion.commander=nil +end +end +return self +end +function COMMANDER:AddMission(Mission) +if not self:IsMission(Mission)then +Mission.commander=self +Mission.statusCommander=AUFTRAG.Status.PLANNED +table.insert(self.missionqueue,Mission) +end +return self +end +function COMMANDER:AddOpsTransport(Transport) +Transport.commander=self +Transport.statusCommander=OPSTRANSPORT.Status.PLANNED +table.insert(self.transportqueue,Transport) +return self +end +function COMMANDER:RemoveMission(Mission) +for i,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission.auftragsnummer==Mission.auftragsnummer then +self:T(self.lid..string.format("Removing mission %s (%s) status=%s from queue",Mission.name,Mission.type,Mission.status)) +mission.commander=nil +table.remove(self.missionqueue,i) +break +end +end +return self +end +function COMMANDER:RemoveTransport(Transport) +for i,_transport in pairs(self.transportqueue)do +local transport=_transport +if transport.uid==Transport.uid then +self:T(self.lid..string.format("Removing transport UID=%d status=%s from queue",transport.uid,transport:GetState())) +transport.commander=nil +table.remove(self.transportqueue,i) +break +end +end +return self +end +function COMMANDER:AddTarget(Target) +if not self:IsTarget(Target)then +table.insert(self.targetqueue,Target) +end +return self +end +function COMMANDER:AddOperation(Operation) +table.insert(self.opsqueue,Operation) +return self +end +function COMMANDER:IsTarget(Target) +for _,_target in pairs(self.targetqueue)do +local target=_target +if target.uid==Target.uid or target:GetName()==Target:GetName()then +return true +end +end +return false +end +function COMMANDER:RemoveTarget(Target) +for i,_target in pairs(self.targetqueue)do +local target=_target +if target.uid==Target.uid then +self:T(self.lid..string.format("Removing target %s from queue",Target.name)) +table.remove(self.targetqueue,i) +break +end +end +return self +end +function COMMANDER:AddRearmingZone(RearmingZone) +local rearmingzone={} +rearmingzone.zone=RearmingZone +rearmingzone.mission=nil +table.insert(self.rearmingZones,rearmingzone) +return rearmingzone +end +function COMMANDER:AddRefuellingZone(RefuellingZone) +local rearmingzone={} +rearmingzone.zone=RefuellingZone +rearmingzone.mission=nil +table.insert(self.refuellingZones,rearmingzone) +return rearmingzone +end +function COMMANDER:AddCapZone(Zone,Altitude,Speed,Heading,Leg) +local patrolzone={} +patrolzone.zone=Zone +patrolzone.altitude=Altitude or 12000 +patrolzone.heading=Heading or 270 +patrolzone.speed=Speed or 350 +patrolzone.leg=Leg or 30 +patrolzone.mission=nil +table.insert(self.capZones,patrolzone) +return patrolzone +end +function COMMANDER:AddGciCapZone(Zone,Altitude,Speed,Heading,Leg) +local patrolzone={} +patrolzone.zone=Zone +patrolzone.altitude=Altitude or 12000 +patrolzone.heading=Heading or 270 +patrolzone.speed=Speed or 350 +patrolzone.leg=Leg or 30 +patrolzone.mission=nil +table.insert(self.gcicapZones,patrolzone) +return patrolzone +end +function COMMANDER:RemoveGciCapZone(Zone) +local patrolzone={} +patrolzone.zone=Zone +for i,_patrolzone in pairs(self.gcicapZones)do +if _patrolzone.zone==patrolzone.zone then +if _patrolzone.mission and _patrolzone.mission:IsNotOver()then +_patrolzone.mission:Cancel() +end +table.remove(self.gcicapZones,i) +break +end +end +return patrolzone +end +function COMMANDER:AddAwacsZone(Zone,Altitude,Speed,Heading,Leg) +local awacszone={} +awacszone.zone=Zone +awacszone.altitude=Altitude or 12000 +awacszone.heading=Heading or 270 +awacszone.speed=Speed or 350 +awacszone.speed=Speed or 350 +awacszone.leg=Leg or 30 +awacszone.mission=nil +table.insert(self.awacsZones,awacszone) +return awacszone +end +function COMMANDER:RemoveAwacsZone(Zone) +local awacszone={} +awacszone.zone=Zone +for i,_awacszone in pairs(self.awacsZones)do +if _awacszone.zone==awacszone.zone then +if _awacszone.mission and _awacszone.mission:IsNotOver()then +_awacszone.mission:Cancel() +end +table.remove(self.awacsZones,i) +break +end +end +return awacszone +end +function COMMANDER:AddTankerZone(Zone,Altitude,Speed,Heading,Leg,RefuelSystem) +local tankerzone={} +tankerzone.zone=Zone +tankerzone.altitude=Altitude or 12000 +tankerzone.heading=Heading or 270 +tankerzone.speed=Speed or 350 +tankerzone.leg=Leg or 30 +tankerzone.refuelsystem=RefuelSystem +tankerzone.mission=nil +tankerzone.marker=MARKER:New(tankerzone.zone:GetCoordinate(),"Tanker Zone"):ToCoalition(self:GetCoalition()) +table.insert(self.tankerZones,tankerzone) +return tankerzone +end +function COMMANDER:RemoveTankerZone(Zone) +local tankerzone={} +tankerzone.zone=Zone +for i,_tankerzone in pairs(self.tankerZones)do +if _tankerzone.zone==tankerzone.zone then +if _tankerzone.mission and _tankerzone.mission:IsNotOver()then +_tankerzone.mission:Cancel() +end +table.remove(self.tankerZones,i) +break +end +end +return tankerzone +end +function COMMANDER:IsMission(Mission) +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission.auftragsnummer==Mission.auftragsnummer then +return true +end +end +return false +end +function COMMANDER:RelocateCohort(Cohort,Legion,Delay,NcarriersMin,NcarriersMax,TransportLegions) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COMMANDER.RelocateCohort,self,Cohort,Legion,0,NcarriersMin,NcarriersMax,TransportLegions) +else +if Legion:IsCohort(Cohort.name)then +self:E(self.lid..string.format("ERROR: Cohort %s is already part of new legion %s ==> CANNOT Relocate!",Cohort.name,Legion.alias)) +return self +else +table.insert(Legion.cohorts,Cohort) +end +local LegionOld=Cohort.legion +if not LegionOld:IsCohort(Cohort.name)then +self:E(self.lid..string.format("ERROR: Cohort %s is NOT part of this legion %s ==> CANNOT Relocate!",Cohort.name,self.alias)) +return self +end +if LegionOld.alias==Legion.alias then +self:E(self.lid..string.format("ERROR: old legion %s is same as new legion %s ==> CANNOT Relocate!",LegionOld.alias,Legion.alias)) +return self +end +Cohort:Relocate() +local mission=AUFTRAG:_NewRELOCATECOHORT(Legion,Cohort) +mission:AssignCohort(Cohort) +mission:SetRequiredAssets(#Cohort.assets) +if NcarriersMin and NcarriersMin>0 then +mission:SetRequiredTransport(Legion.spawnzone,NcarriersMin,NcarriersMax) +end +if TransportLegions then +for _,legion in pairs(TransportLegions)do +mission:AssignTransportLegion(legion) +end +else +for _,legion in pairs(self.legions)do +mission:AssignTransportLegion(legion) +end +end +mission:SetMissionRange(10000) +self:AddMission(mission) +end +return self +end +function COMMANDER:onafterStart(From,Event,To) +local text=string.format("Starting Commander") +self:I(self.lid..text) +for _,_legion in pairs(self.legions)do +local legion=_legion +if legion:GetState()=="NotReadyYet"then +legion:Start() +end +end +self:__Status(-1) +end +function COMMANDER:onafterStatus(From,Event,To) +local fsmstate=self:GetState() +if self.verbose>=1 then +local text=string.format("Status %s: Legions=%d, Missions=%d, Targets=%d, Transports=%d",fsmstate,#self.legions,#self.missionqueue,#self.targetqueue,#self.transportqueue) +self:T(self.lid..text) +end +self:CheckOpsQueue() +self:CheckTargetQueue() +self:CheckMissionQueue() +self:CheckTransportQueue() +for _,_rearmingzone in pairs(self.rearmingZones)do +local rearmingzone=_rearmingzone +if(not rearmingzone.mission)or rearmingzone.mission:IsOver()then +rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone) +self:AddMission(rearmingzone.mission) +end +end +for _,_supplyzone in pairs(self.refuellingZones)do +local supplyzone=_supplyzone +if(not supplyzone.mission)or supplyzone.mission:IsOver()then +supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone) +self:AddMission(supplyzone.mission) +end +end +for _,_patrolzone in pairs(self.capZones)do +local patrolzone=_patrolzone +if(not patrolzone.mission)or patrolzone.mission:IsOver()then +local Coordinate=patrolzone.zone:GetCoordinate() +patrolzone.mission=AUFTRAG:NewCAP(patrolzone.zone,patrolzone.altitude,patrolzone.speed,Coordinate,patrolzone.heading,patrolzone.leg) +self:AddMission(patrolzone.mission) +end +end +for _,_patrolzone in pairs(self.gcicapZones)do +local patrolzone=_patrolzone +if(not patrolzone.mission)or patrolzone.mission:IsOver()then +local Coordinate=patrolzone.zone:GetCoordinate() +patrolzone.mission=AUFTRAG:NewGCICAP(Coordinate,patrolzone.altitude,patrolzone.speed,patrolzone.heading,patrolzone.leg) +self:AddMission(patrolzone.mission) +end +end +for _,_awacszone in pairs(self.awacsZones)do +local awacszone=_awacszone +if(not awacszone.mission)or awacszone.mission:IsOver()then +local Coordinate=awacszone.zone:GetCoordinate() +awacszone.mission=AUFTRAG:NewAWACS(Coordinate,awacszone.altitude,awacszone.speed,awacszone.heading,awacszone.leg) +self:AddMission(awacszone.mission) +end +end +for _,_tankerzone in pairs(self.tankerZones)do +local tankerzone=_tankerzone +if(not tankerzone.mission)or tankerzone.mission:IsOver()then +local Coordinate=tankerzone.zone:GetCoordinate() +tankerzone.mission=AUFTRAG:NewTANKER(Coordinate,tankerzone.altitude,tankerzone.speed,tankerzone.heading,tankerzone.leg,tankerzone.refuelsystem) +self:AddMission(tankerzone.mission) +end +end +if self.verbose>=2 and#self.legions>0 then +local text="Legions:" +for _,_legion in pairs(self.legions)do +local legion=_legion +local Nassets=legion:CountAssets() +local Nastock=legion:CountAssets(true) +text=text..string.format("\n* %s [%s]: Assets=%s stock=%s",legion.alias,legion:GetState(),Nassets,Nastock) +for _,aname in pairs(AUFTRAG.Type)do +local na=legion:CountAssets(true,{aname}) +local np=legion:CountPayloadsInStock({aname}) +local nm=legion:CountAssetsOnMission({aname}) +if na>0 or np>0 then +text=text..string.format("\n - %s: assets=%d, payloads=%d, on mission=%d",aname,na,np,nm) +end +end +end +self:T(self.lid..text) +if self.verbose>=3 then +local Ntotal=0 +local Nspawned=0 +local Nrequested=0 +local Nreserved=0 +local Nstock=0 +local text="\n===========================================\n" +text=text.."Assets:" +for _,_legion in pairs(self.legions)do +local legion=_legion +for _,_cohort in pairs(legion.cohorts)do +local cohort=_cohort +for _,_asset in pairs(cohort.assets)do +local asset=_asset +local state="In Stock" +if asset.flightgroup then +state=asset.flightgroup:GetState() +local mission=legion:GetAssetCurrentMission(asset) +if mission then +state=state..string.format(", Mission \"%s\" [%s]",mission:GetName(),mission:GetType()) +end +else +if asset.spawned then +env.info("FF ERROR: asset has opsgroup but is NOT spawned!") +end +if asset.requested and asset.isReserved then +env.info("FF ERROR: asset is requested and reserved. Should not be both!") +state="Reserved+Requested!" +elseif asset.isReserved then +state="Reserved" +elseif asset.requested then +state="Requested" +end +end +text=text..string.format("\n[UID=%03d] %s Legion=%s [%s]: State=%s [RID=%s]", +asset.uid,asset.spawngroupname,legion.alias,cohort.name,state,tostring(asset.rid)) +if asset.spawned then +Nspawned=Nspawned+1 +end +if asset.requested then +Nrequested=Nrequested+1 +end +if asset.isReserved then +Nreserved=Nreserved+1 +end +if not(asset.spawned or asset.requested or asset.isReserved)then +Nstock=Nstock+1 +end +Ntotal=Ntotal+1 +end +end +end +text=text.."\n-------------------------------------------" +text=text..string.format("\nNstock = %d",Nstock) +text=text..string.format("\nNreserved = %d",Nreserved) +text=text..string.format("\nNrequested = %d",Nrequested) +text=text..string.format("\nNspawned = %d",Nspawned) +text=text..string.format("\nNtotal = %d (=%d)",Ntotal,Nstock+Nspawned+Nrequested+Nreserved) +text=text.."\n===========================================" +self:I(self.lid..text) +end +end +if self.verbose>=2 and#self.missionqueue>0 then +local text="Mission queue:" +for i,_mission in pairs(self.missionqueue)do +local mission=_mission +local target=mission:GetTargetName()or"unknown" +text=text..string.format("\n[%d] %s (%s): status=%s, target=%s",i,mission.name,mission.type,mission.status,target) +end +self:I(self.lid..text) +end +if self.verbose>=2 and#self.targetqueue>0 then +local text="Target queue:" +for i,_target in pairs(self.targetqueue)do +local target=_target +text=text..string.format("\n[%d] %s: status=%s, life=%d",i,target:GetName(),target:GetState(),target:GetLife()) +end +self:I(self.lid..text) +end +if self.verbose>=2 and#self.transportqueue>0 then +local text="Transport queue:" +for i,_transport in pairs(self.transportqueue)do +local transport=_transport +text=text..string.format("\n[%d] UID=%d: status=%s",i,transport.uid,transport:GetState()) +end +self:I(self.lid..text) +end +self:__Status(-30) +end +function COMMANDER:onafterMissionAssign(From,Event,To,Mission,Legions) +self:AddMission(Mission) +Mission.statusCommander=AUFTRAG.Status.QUEUED +for _,_Legion in pairs(Legions)do +local Legion=_Legion +self:T(self.lid..string.format("Assigning mission \"%s\" [%s] to legion \"%s\"",Mission.name,Mission.type,Legion.alias)) +Legion:AddMission(Mission) +Legion:MissionRequest(Mission) +end +end +function COMMANDER:onafterMissionCancel(From,Event,To,Mission) +self:T(self.lid..string.format("Cancelling mission \"%s\" [%s] in status %s",Mission.name,Mission.type,Mission.status)) +Mission.statusCommander=AUFTRAG.Status.CANCELLED +if Mission:IsPlanned()then +self:RemoveMission(Mission) +else +if#Mission.legions>0 then +for _,_legion in pairs(Mission.legions)do +local legion=_legion +legion:MissionCancel(Mission) +end +end +end +end +function COMMANDER:onafterTransportAssign(From,Event,To,Transport,Legions) +Transport.statusCommander=OPSTRANSPORT.Status.QUEUED +for _,_Legion in pairs(Legions)do +local Legion=_Legion +self:T(self.lid..string.format("Assigning transport UID=%d to legion \"%s\"",Transport.uid,Legion.alias)) +Legion:AddOpsTransport(Transport) +Legion:TransportRequest(Transport) +end +end +function COMMANDER:onafterTransportCancel(From,Event,To,Transport) +self:T(self.lid..string.format("Cancelling Transport UID=%d in status %s",Transport.uid,Transport:GetState())) +Transport.statusCommander=OPSTRANSPORT.Status.CANCELLED +if Transport:IsPlanned()then +self:RemoveTransport(Transport) +else +if#Transport.legions>0 then +for _,_legion in pairs(Transport.legions)do +local legion=_legion +legion:TransportCancel(Transport) +end +end +end +end +function COMMANDER:onafterOpsOnMission(From,Event,To,OpsGroup,Mission) +self:T2(self.lid..string.format("Group \"%s\" on mission \"%s\" [%s]",OpsGroup:GetName(),Mission:GetName(),Mission:GetType())) +end +function COMMANDER:CheckOpsQueue() +local Nops=#self.opsqueue +if Nops==0 then +return nil +end +for _,_ops in pairs(self.opsqueue)do +local operation=_ops +if operation:IsRunning()then +for _,_mission in pairs(operation.missions or{})do +local mission=_mission +if mission.phase==nil or(mission.phase and mission.phase==operation.phase)and mission:IsPlanned()then +self:AddMission(mission) +end +end +for _,_target in pairs(operation.targets or{})do +local target=_target +if(target.phase==nil or(target.phase and target.phase==operation.phase))and(not self:IsTarget(target))then +self:AddTarget(target) +end +end +end +end +end +function COMMANDER:CheckTargetQueue() +local Ntargets=#self.targetqueue +if Ntargets==0 then +return nil +end +for i=#self.targetqueue,1,-1 do +local target=self.targetqueue[i] +if(not target:IsAlive())or target:EvalConditionsAny(target.conditionStop)then +for _,_resource in pairs(target.resources)do +local resource=_resource +if resource.mission and resource.mission:IsNotOver()then +self:MissionCancel(resource.mission) +end +end +table.remove(self.targetqueue,i) +end +end +local NoLimit=self:_CheckMissionLimit("Total") +if NoLimit==false then +return nil +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.priotaskB.threatlevel0) +end +table.sort(self.targetqueue,_sort) +local vip=math.huge +for _,_target in pairs(self.targetqueue)do +local target=_target +if target:IsAlive()and target.importance and target.importance Creating mission type %s: Nmin=%d, Nmax=%d",target:GetName(),missionType,resource.Nmin,resource.Nmax)) +local mission=AUFTRAG:NewFromTarget(target,missionType) +if mission then +mission:SetRequiredAssets(resource.Nmin,resource.Nmax) +mission:SetRequiredAttribute(resource.Attributes) +mission:SetRequiredProperty(resource.Properties) +mission.operation=target.operation +resource.mission=mission +self:AddMission(resource.mission) +end +end +end +end +end +end +function COMMANDER:CheckMissionQueue() +local Nmissions=#self.missionqueue +if Nmissions==0 then +return nil +end +local NoLimit=self:_CheckMissionLimit("Total") +if NoLimit==false then +return nil +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio=(self.maxMissionsAssignPerCycle or 1)then +return +end +end +else +end +end +end +function COMMANDER:SetMaxMissionsAssignPerCycle(MaxMissionsAssignPerCycle) +self.maxMissionsAssignPerCycle=MaxMissionsAssignPerCycle or 1 +return self +end +function COMMANDER:_GetCohorts(Legions,Cohorts,Operation) +local function CheckOperation(LegionOrCohort) +if#self.opsqueue==0 then +return true +end +local isAvail=true +if Operation then +isAvail=false +end +for _,_operation in pairs(self.opsqueue)do +local operation=_operation +local isOps=operation:IsAssignedCohortOrLegion(LegionOrCohort) +if isOps and operation:IsRunning()then +isAvail=false +if Operation==nil then +return false +else +if Operation.uid==operation.uid then +return true +end +end +end +end +return isAvail +end +local cohorts={} +if(Legions and#Legions>0)or(Cohorts and#Cohorts>0)then +for _,_legion in pairs(Legions or{})do +local legion=_legion +local Runway=true +if legion:IsAirwing()then +Runway=legion:IsRunwayOperational()and legion.airbase and legion.airbase:GetCoalition()==legion:GetCoalition() +end +if legion:IsRunning()and Runway then +for _,_cohort in pairs(legion.cohorts)do +local cohort=_cohort +if CheckOperation(cohort.legion)or CheckOperation(cohort)then +table.insert(cohorts,cohort) +end +end +end +end +for _,_cohort in pairs(Cohorts or{})do +local cohort=_cohort +if CheckOperation(cohort)then +table.insert(cohorts,cohort) +end +end +else +for _,_legion in pairs(self.legions)do +local legion=_legion +local Runway=true +if legion:IsAirwing()then +Runway=legion:IsRunwayOperational()and legion.airbase and legion.airbase:GetCoalition()==legion:GetCoalition() +end +if legion:IsRunning()and Runway then +for _,_cohort in pairs(legion.cohorts)do +local cohort=_cohort +if CheckOperation(cohort.legion)or CheckOperation(cohort)then +table.insert(cohorts,cohort) +end +end +end +end +end +return cohorts +end +function COMMANDER:RecruitAssetsForMission(Mission) +self:T2(self.lid..string.format("Recruiting assets for mission \"%s\" [%s]",Mission:GetName(),Mission:GetType())) +local NreqMin,NreqMax=Mission:GetRequiredAssets() +local TargetVec2=Mission:GetTargetVec2() +local Payloads=Mission.payloads +local MaxWeight=nil +if Mission.NcarriersMin then +local legions=self.legions +local cohorts=nil +if Mission.transportLegions or Mission.transportCohorts then +legions=Mission.transportLegions +cohorts=Mission.transportCohorts +end +local Cohorts=LEGION._GetCohorts(legions,cohorts) +local transportcohorts={} +for _,_cohort in pairs(Cohorts)do +local cohort=_cohort +local can=LEGION._CohortCan(cohort,AUFTRAG.Type.OPSTRANSPORT,Mission.carrierCategories,Mission.carrierAttributes,Mission.carrierProperties,nil,TargetVec2) +if can and(MaxWeight==nil or cohort.cargobayLimit>MaxWeight)then +MaxWeight=cohort.cargobayLimit +end +end +if MaxWeight then +self:T(self.lid..string.format("Largest cargo bay available=%.1f",MaxWeight)) +end +end +local legions=self.legions +local cohorts=nil +if Mission.specialLegions or Mission.specialCohorts then +legions=Mission.specialLegions +cohorts=Mission.specialCohorts +end +local Cohorts=LEGION._GetCohorts(legions,cohorts,Mission.operation,self.opsqueue) +self:T(self.lid..string.format("Found %d cohort candidates for mission",#Cohorts)) +local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,Mission.type,Mission.alert5MissionType,NreqMin,NreqMax,TargetVec2,Payloads, +Mission.engageRange,Mission.refuelSystem,nil,nil,MaxWeight,nil,Mission.attributes,Mission.properties,{Mission.engageWeaponType}) +return recruited,assets,legions +end +function COMMANDER:RecruitAssetsForEscort(Mission,Assets) +if Mission.NescortMin and Mission.NescortMax and(Mission.NescortMin>0 or Mission.NescortMax>0)then +local Cohorts=self:_GetCohorts(Mission.escortLegions,Mission.escortCohorts,Mission.operation) +local assigned=LEGION.AssignAssetsForEscort(self,Cohorts,Assets,Mission.NescortMin,Mission.NescortMax,Mission.escortMissionType,Mission.escortTargetTypes,Mission.escortEngageRange) +return assigned +end +return true +end +function COMMANDER:RecruitAssetsForTarget(Target,MissionType,NassetsMin,NassetsMax) +local Cohorts=self:_GetCohorts() +local TargetVec2=Target:GetVec2() +local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,MissionType,nil,NassetsMin,NassetsMax,TargetVec2) +return recruited,assets,legions +end +function COMMANDER:CheckTransportQueue() +local Ntransports=#self.transportqueue +if Ntransports==0 then +return nil +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio0 then +for _,_opsgroup in pairs(cargoOpsGroups)do +local opsgroup=_opsgroup +local weight=opsgroup:GetWeightTotal() +if weight>weightGroup then +weightGroup=weight +end +TotalWeight=TotalWeight+weight +end +end +if weightGroup>0 then +local recruited,assets,legions=self:RecruitAssetsForTransport(transport,weightGroup,TotalWeight) +if recruited then +for _,_asset in pairs(assets)do +local asset=_asset +transport:AddAsset(asset) +end +self:TransportAssign(transport,legions) +return +else +LEGION.UnRecruitAssets(assets) +end +end +else +end +end +end +function COMMANDER:RecruitAssetsForTransport(Transport,CargoWeight,TotalWeight) +if CargoWeight==0 then +return false,{},{} +end +local Cohorts=self:_GetCohorts() +local TargetVec2=Transport:GetDeployZone():GetVec2() +local NreqMin,NreqMax=Transport:GetRequiredCarriers() +local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,AUFTRAG.Type.OPSTRANSPORT,nil,NreqMin,NreqMax,TargetVec2,nil,nil,nil,CargoWeight,TotalWeight) +return recruited,assets,legions +end +function COMMANDER:_CheckMissionLimit(MissionType) +local limit=self.limitMission[MissionType] +if limit then +if MissionType=="Total"then +MissionType=AUFTRAG.Type +end +local N=self:CountMissions(MissionType,true) +if N>=limit then +return false +end +end +return true +end +function COMMANDER:CountAssets(InStock,MissionTypes,Attributes) +local N=0 +for _,_legion in pairs(self.legions)do +local legion=_legion +N=N+legion:CountAssets(InStock,MissionTypes,Attributes) +end +return N +end +function COMMANDER:CountMissions(MissionTypes,OnlyRunning) +local N=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if(not OnlyRunning)or(mission.statusCommander~=AUFTRAG.Status.PLANNED)then +if AUFTRAG.CheckMissionType(mission.type,MissionTypes)then +N=N+1 +end +end +end +return N +end +function COMMANDER:GetAssets(InStock,Legions,MissionTypes,Attributes) +local assets={} +for _,_legion in pairs(Legions or self.legions)do +local legion=_legion +for _,_cohort in pairs(legion.cohorts)do +local cohort=_cohort +for _,_asset in pairs(cohort.assets)do +local asset=_asset +if not(asset.spawned or asset.isReserved or asset.requested)then +table.insert(assets,asset) +end +end +end +end +return assets +end +function COMMANDER:GetLegionsForMission(Mission) +local legions={} +for _,_legion in pairs(self.legions)do +local legion=_legion +local Nassets=0 +if legion:IsAirwing()then +Nassets=legion:CountAssetsWithPayloadsInStock(Mission.payloads,{Mission.type},Attributes) +else +Nassets=legion:CountAssets(true,{Mission.type},Attributes) +end +if Nassets>0 and false then +local coord=Mission:GetTargetCoordinate() +if coord then +local distance=UTILS.MetersToNM(coord:Get2DDistance(legion:GetCoordinate())) +local dist=UTILS.Round(distance/10,0) +self:T(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f",legion.alias,Nassets,distance,dist)) +table.insert(legions,{airwing=legion,distance=distance,dist=dist,targetcoord=coord,nassets=Nassets}) +end +end +if Nassets>0 then +table.insert(legions,legion) +end +end +return legions +end +function COMMANDER:GetAssetsOnMission(MissionTypes) +local assets={} +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if AUFTRAG.CheckMissionType(mission.type,MissionTypes)then +for _,_asset in pairs(mission.assets or{})do +local asset=_asset +table.insert(assets,asset) +end +end +end +return assets +end +FLEET={ +ClassName="FLEET", +verbose=0, +pathfinding=false, +} +FLEET.version="0.0.1" +function FLEET:New(WarehouseName,FleetName) +local self=BASE:Inherit(self,LEGION:New(WarehouseName,FleetName)) +if not self then +BASE:E(string.format("ERROR: Could not find warehouse %s!",WarehouseName)) +return nil +end +self.lid=string.format("FLEET %s | ",self.alias) +self:SetRetreatZones() +if self:IsShip()then +local wh=self.warehouse +local group=wh:GetGroup() +self.warehouseOpsGroup=NAVYGROUP:New(group) +self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName()) +end +self:AddTransition("*","NavyOnMission","*") +return self +end +function FLEET:AddFlotilla(Flotilla) +table.insert(self.cohorts,Flotilla) +self:AddAssetToFlotilla(Flotilla,Flotilla.Ngroups) +Flotilla:SetFleet(self) +if Flotilla:IsStopped()then +Flotilla:Start() +end +return self +end +function FLEET:AddAssetToFlotilla(Flotilla,Nassets) +if Flotilla then +local Group=GROUP:FindByName(Flotilla.templatename) +if Group then +local text=string.format("Adding asset %s to flotilla %s",Group:GetName(),Flotilla.name) +self:T(self.lid..text) +self:AddAsset(Group,Nassets,nil,nil,nil,nil,Flotilla.skill,Flotilla.livery,Flotilla.name) +else +self:E(self.lid.."ERROR: Group does not exist!") +end +else +self:E(self.lid.."ERROR: Flotilla does not exit!") +end +return self +end +function FLEET:SetPathfinding(Switch) +self.pathfinding=Switch +return self +end +function FLEET:SetRetreatZones(RetreatZoneSet) +self.retreatZones=RetreatZoneSet or SET_ZONE:New() +return self +end +function FLEET:AddRetreatZone(RetreatZone) +self.retreatZones:AddZone(RetreatZone) +return self +end +function FLEET:GetRetreatZones() +return self.retreatZones +end +function FLEET:GetFlotilla(FlotillaName) +local flotilla=self:_GetCohort(FlotillaName) +return flotilla +end +function FLEET:GetFlotillaOfAsset(Asset) +local flotilla=self:GetFlotilla(Asset.squadname) +return flotilla +end +function FLEET:RemoveAssetFromFlotilla(Asset) +local flotilla=self:GetFlotillaOfAsset(Asset) +if flotilla then +flotilla:DelAsset(Asset) +end +end +function FLEET:onafterStart(From,Event,To) +self:GetParent(self,FLEET).onafterStart(self,From,Event,To) +self:I(self.lid..string.format("Starting FLEET v%s",FLEET.version)) +end +function FLEET:onafterStatus(From,Event,To) +self:GetParent(self).onafterStatus(self,From,Event,To) +local fsmstate=self:GetState() +self:CheckTransportQueue() +self:CheckMissionQueue() +self:_TacticalOverview() +if self.verbose>=1 then +local Nmissions=self:CountMissionsInQueue() +local Npq,Np,Nq=self:CountAssetsOnMission() +local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]",self:CountAssets(),Npq,Np,Nq) +local text=string.format("%s: Missions=%d, Flotillas=%d, Assets=%s",fsmstate,Nmissions,#self.cohorts,assets) +self:I(self.lid..text) +end +if self.verbose>=2 then +local text=string.format("Missions Total=%d:",#self.missionqueue) +for i,_mission in pairs(self.missionqueue)do +local mission=_mission +local prio=string.format("%d/%s",mission.prio,tostring(mission.importance));if mission.urgent then prio=prio.." (!)"end +local assets=string.format("%d/%d",mission:CountOpsGroups(),mission.Nassets or 0) +local target=string.format("%d/%d Damage=%.1f",mission:CountMissionTargets(),mission:GetTargetInitialNumber(),mission:GetTargetDamage()) +text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s",i,mission.name,mission.type,mission.status,prio,assets,target) +end +self:I(self.lid..text) +end +if self.verbose>=2 then +local text=string.format("Transports Total=%d:",#self.transportqueue) +for i,_transport in pairs(self.transportqueue)do +local transport=_transport +local prio=string.format("%d/%s",transport.prio,tostring(transport.importance));if transport.urgent then prio=prio.." (!)"end +local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d",transport.Ncargo,transport.Ndelivered,transport.Ncarrier) +text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s",i,transport.uid,transport:GetState(),prio,carriers) +end +self:I(self.lid..text) +end +if self.verbose>=3 then +local text="Flotillas:" +for i,_flotilla in pairs(self.cohorts)do +local flotilla=_flotilla +local callsign=flotilla.callsignName and UTILS.GetCallsignName(flotilla.callsignName)or"N/A" +local modex=flotilla.modex and flotilla.modex or-1 +local skill=flotilla.skill and tostring(flotilla.skill)or"N/A" +text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s",flotilla.name,flotilla:GetState(),flotilla.aircrafttype,flotilla:CountAssets(true),#flotilla.assets,callsign,modex,skill) +end +self:I(self.lid..text) +end +end +function FLEET:onafterNavyOnMission(From,Event,To,NavyGroup,Mission) +self:T(self.lid..string.format("Group %s on %s mission %s",NavyGroup:GetName(),Mission:GetType(),Mission:GetName())) +end +FLIGHTCONTROL={ +ClassName="FLIGHTCONTROL", +verbose=0, +lid=nil, +theatre=nil, +airbasename=nil, +airbase=nil, +airbasetype=nil, +zoneAirbase=nil, +parking={}, +runways={}, +flights={}, +clients={}, +atis=nil, +Nlanding=nil, +dTlanding=nil, +Nparkingspots=nil, +holdingpatterns={}, +hpcounter=0, +nosubs=false, +Nplayers=0, +} +FLIGHTCONTROL.FlightStatus={ +UNKNOWN="Unknown", +PARKING="Parking", +READYTX="Ready To Taxi", +TAXIOUT="Taxi To Runway", +READYTO="Ready For Takeoff", +TAKEOFF="Takeoff", +INBOUND="Inbound", +HOLDING="Holding", +LANDING="Landing", +TAXIINB="Taxi To Parking", +ARRIVED="Arrived", +} +FLIGHTCONTROL.version="0.7.7" +function FLIGHTCONTROL:New(AirbaseName,Frequency,Modulation,PathToSRS,Port,GoogleKey) +local self=BASE:Inherit(self,FSM:New()) +self.airbase=AIRBASE:FindByName(AirbaseName) +self.airbasename=AirbaseName +self.lid=string.format("FLIGHTCONTROL %s | ",AirbaseName) +if not self.airbase then +self:E(string.format("ERROR: Could not find airbase %s!",tostring(AirbaseName))) +return nil +end +if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then +self:E(string.format("ERROR: Airbase %s is not an AIRDROME! Script does not handle FARPS or ships.",tostring(AirbaseName))) +return nil +end +self.airbasetype=self.airbase:GetAirbaseCategory() +self.theatre=env.mission.theatre +self.zoneAirbase=ZONE_RADIUS:New("FC",self:GetCoordinate():GetVec2(),UTILS.NMToMeters(5)) +self:_AddHoldingPatternBackup() +self.alias=self.airbasename.." Tower" +self:SetLimitLanding(2,0) +self:SetLimitTaxi(2,false,0) +self:SetLandingInterval() +self:SetFrequency(Frequency,Modulation) +self:SetMarkHoldingPattern(true) +self:SetRunwayRepairtime() +self.nosubs=false +self:SetCallSignOptions(true,true) +self.msrsqueue=MSRSQUEUE:New(self.alias) +self:SetTransmitOnlyWithPlayers(true) +local path=PathToSRS or MSRS.path +local port=Port or MSRS.port or 5002 +self:SetSRSPort(port) +self.msrsTower=MSRS:New(path,Frequency,Modulation) +self.msrsTower:SetPort(port) +if GoogleKey then +self.msrsTower:SetProviderOptionsGoogle(GoogleKey,GoogleKey) +self.msrsTower:SetProvider(MSRS.Provider.GOOGLE) +end +self.msrsTower:SetCoordinate(self:GetCoordinate()) +self:SetSRSTower() +self.msrsPilot=MSRS:New(PathToSRS,Frequency,Modulation) +self.msrsPilot:SetPort(self.Port) +if GoogleKey then +self.msrsPilot:SetProviderOptionsGoogle(GoogleKey,GoogleKey) +self.msrsPilot:SetProvider(MSRS.Provider.GOOGLE) +end +self.msrsTower:SetCoordinate(self:GetCoordinate()) +self:SetSRSPilot() +self.dTmessage=10 +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","StatusUpdate","*") +self:AddTransition("*","PlayerKilledGuard","*") +self:AddTransition("*","PlayerSpeeding","*") +self:AddTransition("*","RunwayDestroyed","*") +self:AddTransition("*","RunwayRepaired","*") +self:AddTransition("*","Stop","Stopped") +_DATABASE:AddFlightControl(self) +return self +end +function FLIGHTCONTROL:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function FLIGHTCONTROL:SetRadioOnlyIfPlayers(Switch) +if Switch==nil or Switch==true then +self.radioOnlyIfPlayers=true +else +self.radioOnlyIfPlayers=false +end +return self +end +function FLIGHTCONTROL:SetTransmitOnlyWithPlayers(Switch) +self.msrsqueue:SetTransmitOnlyWithPlayers(Switch) +return self +end +function FLIGHTCONTROL:SwitchSubtitlesOn() +self.nosubs=false +return self +end +function FLIGHTCONTROL:SwitchSubtitlesOff() +self.nosubs=true +return self +end +function FLIGHTCONTROL:SetFrequency(Frequency,Modulation) +self.frequency=Frequency or 305 +self.modulation=Modulation or radio.modulation.AM +if self.msrsPilot then +self.msrsPilot:SetFrequencies(Frequency) +self.msrsPilot:SetModulations(Modulation) +end +if self.msrsTower then +self.msrsTower:SetFrequencies(Frequency) +self.msrsTower:SetModulations(Modulation) +end +return self +end +function FLIGHTCONTROL:SetSRSPort(Port) +self.Port=Port or 5002 +return self +end +function FLIGHTCONTROL:_SetSRSOptions(msrs,Gender,Culture,Voice,Volume,Label,PathToGoogleCredentials,Port) +Gender=Gender or"female" +Culture=Culture or"en-GB" +Volume=Volume or 1.0 +if msrs then +msrs:SetGender(Gender) +msrs:SetCulture(Culture) +msrs:SetVoice(Voice) +msrs:SetVolume(Volume) +msrs:SetLabel(Label) +msrs:SetCoalition(self:GetCoalition()) +msrs:SetPort(Port or self.Port or 5002) +end +return self +end +function FLIGHTCONTROL:SetSRSTower(Gender,Culture,Voice,Volume,Label) +if self.msrsTower then +self:_SetSRSOptions(self.msrsTower,Gender or"female",Culture or"en-GB",Voice,Volume,Label or self.alias) +end +return self +end +function FLIGHTCONTROL:SetSRSPilot(Gender,Culture,Voice,Volume,Label) +if self.msrsPilot then +self:_SetSRSOptions(self.msrsPilot,Gender or"male",Culture or"en-US",Voice,Volume,Label or"Pilot") +end +return self +end +function FLIGHTCONTROL:SetLimitLanding(Nlanding,Ntakeoff) +self.NlandingTot=Nlanding or 2 +self.NlandingTakeoff=Ntakeoff or 0 +return self +end +function FLIGHTCONTROL:SetLandingInterval(dt) +self.dTlanding=dt or 180 +return self +end +function FLIGHTCONTROL:SetLimitTaxi(Ntaxi,IncludeInbound,Nlanding) +self.NtaxiTot=Ntaxi or 2 +self.NtaxiInbound=IncludeInbound +self.NtaxiLanding=Nlanding or 0 +return self +end +function FLIGHTCONTROL:AddHoldingPattern(ArrivalZone,Heading,Length,FlightlevelMin,FlightlevelMax,Prio) +if type(ArrivalZone)=="string"then +ArrivalZone=ZONE:New(ArrivalZone) +end +self.hpcounter=self.hpcounter+1 +local hp={} +hp.uid=self.hpcounter +hp.arrivalzone=ArrivalZone +hp.name=string.format("%s-%d",ArrivalZone:GetName(),hp.uid) +hp.pos0=ArrivalZone:GetCoordinate() +hp.pos1=hp.pos0:Translate(UTILS.NMToMeters(Length or 15),Heading) +hp.angelsmin=FlightlevelMin or 5 +hp.angelsmax=FlightlevelMax or 15 +hp.prio=Prio or 50 +hp.stacks={} +for i=hp.angelsmin,hp.angelsmax do +local stack={} +stack.angels=i +stack.flightgroup=nil +stack.pos0=UTILS.DeepCopy(hp.pos0) +stack.pos0:SetAltitude(UTILS.FeetToMeters(i*1000)) +stack.pos1=UTILS.DeepCopy(hp.pos1) +stack.pos1:SetAltitude(UTILS.FeetToMeters(i*1000)) +stack.heading=Heading +table.insert(hp.stacks,stack) +end +table.insert(self.holdingpatterns,hp) +local function _sort(a,b) +return a.prio0 then +local text=string.format("Still got %d messages in the radio queue. Will call status again in %.1f sec",#self.msrsqueue,Tqueue) +self:T(self.lid..text) +self:__StatusUpdate(-Tqueue) +return false +end +return true +end +function FLIGHTCONTROL:onafterStatusUpdate() +self:T2(self.lid.."Status update") +self:_CheckMarkHoldingPatterns() +if self:IsRunwayOperational()==false then +local Trepair=self:GetRunwayRepairtime() +if Trepair==0 then +self:RunwayRepaired() +else +self:I(self.lid..string.format("Runway still destroyed! Will be repaired in %d sec",Trepair)) +end +end +self:_CheckFlights() +self:_CheckQueues() +local rwyLanding=self:GetActiveRunwayText() +local rwyTakeoff=self:GetActiveRunwayText(true) +local Nflights=self:CountFlights() +local NQparking=self:CountFlights(FLIGHTCONTROL.FlightStatus.PARKING) +local NQreadytx=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTX) +local NQtaxiout=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIOUT) +local NQreadyto=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTO) +local NQtakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) +local NQinbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.INBOUND) +local NQholding=self:CountFlights(FLIGHTCONTROL.FlightStatus.HOLDING) +local NQlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) +local NQtaxiinb=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB) +local NQarrived=self:CountFlights(FLIGHTCONTROL.FlightStatus.ARRIVED) +local Nqueues=(NQparking+NQreadytx+NQtaxiout+NQreadyto+NQtakeoff)+(NQinbound+NQholding+NQlanding+NQtaxiinb+NQarrived) +local nfree=self.Nparkingspots-NQarrived-NQparking +local Nfree=self:CountParking(AIRBASE.SpotStatus.FREE) +local Noccu=self:CountParking(AIRBASE.SpotStatus.OCCUPIED) +local Nresv=self:CountParking(AIRBASE.SpotStatus.RESERVED) +if Nfree+Noccu+Nresv~=self.Nparkingspots then +self:E(self.lid..string.format("WARNING: Number of parking spots does not match! Nfree=%d, Noccu=%d, Nreserved=%d != %d total",Nfree,Noccu,Nresv,self.Nparkingspots)) +end +if self.verbose>=1 then +local text=string.format("State %s - Runway Landing=%s, Takeoff=%s - Parking F=%d/O=%d/R=%d of %d - Flights=%s: Qpark=%d Qtxout=%d Qready=%d Qto=%d | Qinbound=%d Qhold=%d Qland=%d Qtxinb=%d Qarr=%d", +self:GetState(),rwyLanding,rwyTakeoff,Nfree,Noccu,Nresv,self.Nparkingspots,Nflights,NQparking,NQtaxiout,NQreadyto,NQtakeoff,NQinbound,NQholding,NQlanding,NQtaxiinb,NQarrived) +self:I(self.lid..text) +end +if Nflights==Nqueues then +else +self:E(string.format("WARNING: Number of total flights %d!=%d number of flights in all queues!",Nflights,Nqueues)) +end +if self.verbose>=2 then +local text="Holding Patterns:" +for i,_pattern in pairs(self.holdingpatterns)do +local pattern=_pattern +text=text..string.format("\n[%d] Pattern %s [Prio=%d, UID=%d]: Stacks=%d, Angels %d - %d",i,pattern.name,pattern.prio,pattern.uid,#pattern.stacks,pattern.angelsmin,pattern.angelsmax) +if self.verbose>=4 then +for _,_stack in pairs(pattern.stacks)do +local stack=_stack +local text=string.format("",stack.angels,stack) +end +end +end +self:I(self.lid..text) +end +self:__StatusUpdate(-30) +end +function FLIGHTCONTROL:onafterStop() +self:UnHandleEvent(EVENTS.Birth) +self:UnHandleEvent(EVENTS.EngineStartup) +self:UnHandleEvent(EVENTS.Takeoff) +self:UnHandleEvent(EVENTS.Land) +self:UnHandleEvent(EVENTS.EngineShutdown) +self:UnHandleEvent(EVENTS.Crash) +self:UnHandleEvent(EVENTS.Kill) +end +function FLIGHTCONTROL:OnEventBirth(EventData) +self:F3({EvendData=EventData}) +if EventData and EventData.IniGroupName and EventData.IniUnit then +self:T3(self.lid..string.format("BIRTH: unit = %s",tostring(EventData.IniUnitName))) +self:T3(self.lid..string.format("BIRTH: group = %s",tostring(EventData.IniGroupName))) +local unit=EventData.IniUnit +if unit:IsAir()then +local bornhere=EventData.Place and EventData.Place:GetName()==self.airbasename or false +local playerunit,playername=self:_GetPlayerUnitAndName(EventData.IniUnitName) +if playername or bornhere then +self:ScheduleOnce(0.5,self._CreateFlightGroup,self,EventData.IniGroup) +end +if bornhere then +self:SpawnParkingGuard(unit) +end +end +end +end +function FLIGHTCONTROL:OnEventCrashOrDead(EventData) +if EventData then +if EventData.IniUnitName then +if self.airbase and self.airbasename and self.airbasename==EventData.IniUnitName then +self:RunwayDestroyed() +end +end +end +end +function FLIGHTCONTROL:OnEventLand(EventData) +self:F3({EvendData=EventData}) +self:T2(self.lid..string.format("LAND: unit = %s",tostring(EventData.IniUnitName))) +self:T3(self.lid..string.format("LAND: group = %s",tostring(EventData.IniGroupName))) +end +function FLIGHTCONTROL:OnEventTakeoff(EventData) +self:F3({EvendData=EventData}) +self:T2(self.lid..string.format("TAKEOFF: unit = %s",tostring(EventData.IniUnitName))) +self:T3(self.lid..string.format("TAKEOFF: group = %s",tostring(EventData.IniGroupName))) +local airbase=EventData.Place +local unit=EventData.IniUnit +if not(airbase or unit)then +self:E(self.lid.."WARNING: Airbase or IniUnit is nil in takeoff event!") +return +end +end +function FLIGHTCONTROL:OnEventEngineStartup(EventData) +self:F3({EvendData=EventData}) +self:T2(self.lid..string.format("ENGINESTARTUP: unit = %s",tostring(EventData.IniUnitName))) +self:T3(self.lid..string.format("ENGINESTARTUP: group = %s",tostring(EventData.IniGroupName))) +end +function FLIGHTCONTROL:OnEventEngineShutdown(EventData) +self:F3({EvendData=EventData}) +self:T2(self.lid..string.format("ENGINESHUTDOWN: unit = %s",tostring(EventData.IniUnitName))) +self:T3(self.lid..string.format("ENGINESHUTDOWN: group = %s",tostring(EventData.IniGroupName))) +end +function FLIGHTCONTROL:OnEventKill(EventData) +self:F3({EvendData=EventData}) +self:T2(self.lid..string.format("KILL: ini unit = %s",tostring(EventData.IniUnitName))) +self:T3(self.lid..string.format("KILL: ini group = %s",tostring(EventData.IniGroupName))) +self:T2(self.lid..string.format("KILL: tgt unit = %s",tostring(EventData.TgtUnitName))) +self:T3(self.lid..string.format("KILL: tgt group = %s",tostring(EventData.TgtGroupName))) +local guardPrefix=string.format("Parking Guard %s",self.airbasename) +local victimName=EventData.IniUnitName +local killerName=EventData.TgtUnitName +if victimName and victimName:find(guardPrefix)then +env.info(string.format("Parking guard %s killed!",victimName)) +for _,_flight in pairs(self.flights)do +local flight=_flight +local element=flight:GetElementByName(killerName) +if element then +env.info(string.format("Parking guard %s killed by %s!",victimName,killerName)) +return +end +end +end +end +function FLIGHTCONTROL:onafterRunwayDestroyed(From,Event,To) +self:T(self.lid..string.format("Runway destoyed!")) +self.runwaydestroyed=timer.getAbsTime() +self:TransmissionTower("All flights, our runway was destroyed. All operations are suspended for one hour.",Flight,Delay) +end +function FLIGHTCONTROL:onafterRunwayRepaired(From,Event,To) +self:T(self.lid..string.format("Runway repaired!")) +self.runwaydestroyed=nil +end +function FLIGHTCONTROL:_CheckQueues() +if self.verbose>=2 then +self:_PrintQueue(self.flights,"All flights") +end +local flight,isholding,parking=self:_GetNextFlight() +if flight then +if isholding then +if self:_CheckFlightLanding(flight)then +local dTlanding=99999 +if self.Tlanding then +dTlanding=timer.getAbsTime()-self.Tlanding +end +if parking and dTlanding>=self.dTlanding then +local callsign=self:_GetCallsignName(flight) +local runway=self:GetActiveRunwayText() +local text=string.format("%s, %s, you are cleared to land, runway %s",callsign,self.alias,runway) +self:TransmissionTower(text,flight) +if flight.isAI then +local text=string.format("Runway %s, cleared to land, %s",runway,callsign) +self:TransmissionPilot(text,flight,10) +self:_LandAI(flight,parking) +else +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.LANDING) +end +self.Tlanding=timer.getAbsTime() +end +else +self:T3(self.lid..string.format("FYI: Landing clearance for flight %s denied",flight.groupname)) +end +else +if self:_CheckFlightTakeoff(flight)then +local callsign=self:_GetCallsignName(flight) +local runway=self:GetActiveRunwayText(true) +local text=string.format("%s, %s, taxi to runway %s, hold short",callsign,self.alias,runway) +if self:GetFlightStatus(flight)==FLIGHTCONTROL.FlightStatus.READYTO then +text=string.format("%s, %s, cleared for take-off, runway %s",callsign,self.alias,runway) +end +self:TransmissionTower(text,flight) +if flight.isAI then +local text="Wilco, " +if flight:IsUncontrolled()then +text=text..string.format("starting engines, ") +flight:StartUncontrolled() +end +text=text..string.format("runway %s, %s",runway,callsign) +self:TransmissionPilot(text,flight,10) +for _,_element in pairs(flight.elements)do +local element=_element +if element and element.parking then +local spot=self:GetParkingSpotByID(element.parking.TerminalID) +self:RemoveParkingGuard(spot) +end +end +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAKEOFF) +else +if self:GetFlightStatus(flight)==FLIGHTCONTROL.FlightStatus.READYTO then +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAKEOFF) +else +for _,_element in pairs(flight.elements)do +local element=_element +if element.parking then +local spot=self:GetParkingSpotByID(element.parking.TerminalID) +if element.ai then +self:RemoveParkingGuard(spot,15) +else +self:RemoveParkingGuard(spot,10) +end +end +end +end +end +else +self:T3(self.lid..string.format("FYI: Take off for flight %s denied",flight.groupname)) +end +end +else +self:T2(self.lid..string.format("FYI: No flight in queue for takeoff or landing")) +end +end +function FLIGHTCONTROL:_CheckFlightTakeoff(flight) +local nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) +local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF,nil,true) +local status=self:GetFlightStatus(flight) +if flight.isAI then +if nlanding>self.NtaxiLanding then +self:T(self.lid..string.format("AI flight %s [status=%s] NOT cleared for taxi/takeoff as %d>%d flight(s) landing",flight.groupname,status,nlanding,self.NtaxiLanding)) +return false +end +local ninbound=0 +if self.NtaxiInbound then +ninbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB,nil,true) +end +if ntakeoff+ninbound>=self.NtaxiTot then +self:T(self.lid..string.format("AI flight %s [status=%s] NOT cleared for taxi/takeoff as %d>=%d flight(s) taxi/takeoff",flight.groupname,status,ntakeoff,self.NtaxiTot)) +return false +end +self:T(self.lid..string.format("AI flight %s [status=%s] cleared for taxi/takeoff! nLanding=%d, nTakeoff=%d",flight.groupname,status,nlanding,ntakeoff)) +return true +else +if status==FLIGHTCONTROL.FlightStatus.READYTO then +if nlanding>self.NtaxiLanding then +self:T(self.lid..string.format("Player flight %s [status=%s] not cleared for taxi/takeoff as %d>%d flight(s) landing",flight.groupname,status,nlanding,self.NtaxiLanding)) +return false +end +end +self:T(self.lid..string.format("Player flight %s [status=%s] cleared for taxi/takeoff",flight.groupname,status)) +return true +end +end +function FLIGHTCONTROL:_CheckFlightLanding(flight) +local nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) +local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF,nil,true) +local status=self:GetFlightStatus(flight) +if flight.isAi then +if ntakeoff<=self.NlandingTakeoff and nlandingTholdingMin then +return fg +end +end +local function _sortByFuel(a,b) +local flightA=a +local flightB=b +local fuelA=flightA.group:GetFuelMin() +local fuelB=flightB.group:GetFuelMin() +return fuelATholdingMin then +return fg +end +return nil +end +function FLIGHTCONTROL:_GetNextFightParking() +local OnlyAI=nil +if self:IsRunwayDestroyed()then +OnlyAI=false +end +local QreadyTO=self:GetFlights(FLIGHTCONTROL.FlightStatus.READYTO,OPSGROUP.GroupStatus.TAXIING,OnlyAI) +if#QreadyTO>0 then +return QreadyTO[1] +end +local QreadyTX=self:GetFlights(FLIGHTCONTROL.FlightStatus.READYTX,OPSGROUP.GroupStatus.PARKING,OnlyAI) +if#QreadyTX>0 then +return QreadyTX[1] +end +if self:IsRunwayDestroyed()then +return nil +end +local Qparking=self:GetFlights(FLIGHTCONTROL.FlightStatus.PARKING,nil,true) +local Nparking=#Qparking +if Nparking==0 then +return nil +end +local function _sortByTparking(a,b) +local flightA=a +local flightB=b +return flightA.Tparking=2 then +local text="Parking flights:" +for i,_flight in pairs(Qparking)do +local flight=_flight +text=text..string.format("\n[%d] %s [%s], state=%s [%s]: Tparking=%.1f sec",i,flight.groupname,tostring(flight.actype),flight:GetState(),self:GetFlightStatus(flight),flight:GetParkingTime()) +end +self:I(self.lid..text) +end +for i,_flight in pairs(Qparking)do +local flight=_flight +if flight.isAI and flight.isReadyTO then +return flight +end +end +return nil +end +function FLIGHTCONTROL:_PrintQueue(queue,name) +local text=string.format("%s Queue N=%d:",name,#queue) +if#queue==0 then +text=text.." empty." +else +local time=timer.getAbsTime() +for i,_flight in ipairs(queue)do +local flight=_flight +local fuel=flight.group:GetFuelMin()*100 +local ai=tostring(flight.isAI) +local actype=tostring(flight.actype) +local holding=flight.Tholding and UTILS.SecondsToClock(time-flight.Tholding,true)or"X" +local parking=flight.Tparking and UTILS.SecondsToClock(time-flight.Tparking,true)or"X" +local holding=flight:GetHoldingTime() +if holding>=0 then +holding=UTILS.SecondsToClock(holding,true) +else +holding="X" +end +local parking=flight:GetParkingTime() +if parking>=0 then +parking=UTILS.SecondsToClock(parking,true) +else +parking="X" +end +local nunits=flight:CountElements() +local state=flight:GetState() +local status=self:GetFlightStatus(flight) +text=text..string.format("\n[%d] %s (%s*%d): status=%s | %s, ai=%s, fuel=%d, holding=%s, parking=%s", +i,flight.groupname,actype,nunits,state,status,ai,fuel,holding,parking) +for j,_element in pairs(flight.elements)do +local element=_element +local life=element.unit:GetLife() +local life0=element.unit:GetLife0() +local park=element.parking and tostring(element.parking.TerminalID)or"N/A" +text=text..string.format("\n (%d) %s (%s): status=%s, ai=%s, airborne=%s life=%d/%d spot=%s", +j,tostring(element.modex),element.name,tostring(element.status),tostring(element.ai),tostring(element.unit:InAir()),life,life0,park) +end +end +end +self:I(self.lid..text) +return text +end +function FLIGHTCONTROL:SetFlightStatus(flight,status) +self:T(self.lid..string.format("New status %s-->%s for flight %s",flight.controlstatus or"unknown",status,flight:GetName())) +if flight.controlstatus~=status and not flight.isAI then +self:T(self.lid.."Updating menu in 0.2 sec after flight status change") +flight:_UpdateMenu(0.2) +end +flight.controlstatus=status +end +function FLIGHTCONTROL:GetFlightStatus(flight) +if flight then +return flight.controlstatus or"unkonwn" +end +return"unknown" +end +function FLIGHTCONTROL:IsControlling(flight) +local is=flight.flightcontrol and flight.flightcontrol.airbasename==self.airbasename or false +return is +end +function FLIGHTCONTROL:_InQueue(queue,group) +local name=group:GetName() +for _,_flight in pairs(queue)do +local flight=_flight +if name==flight.groupname then +return true +end +end +return false +end +function FLIGHTCONTROL:GetFlights(Status,GroupStatus,AI) +if Status~=nil or GroupStatus~=nil or AI~=nil then +local flights={} +for _,_flight in pairs(self.flights)do +local flight=_flight +local status=self:GetFlightStatus(flight,Status) +if status==Status then +if AI==nil or AI==flight.isAI then +if GroupStatus==nil or GroupStatus==flight:GetState()then +table.insert(flights,flight) +end +end +end +end +return flights +else +return self.flights +end +end +function FLIGHTCONTROL:CountFlights(Status,GroupStatus,AI) +if Status~=nil or GroupStatus~=nil or AI~=nil then +local flights=self:GetFlights(Status,GroupStatus,AI) +return#flights +else +return#self.flights +end +end +function FLIGHTCONTROL:GetActiveRunway() +local rwy=self.airbase:GetActiveRunway() +return rwy +end +function FLIGHTCONTROL:GetActiveRunwayLanding() +local rwy=self.airbase:GetActiveRunwayLanding() +return rwy +end +function FLIGHTCONTROL:GetActiveRunwayTakeoff() +local rwy=self.airbase:GetActiveRunwayTakeoff() +return rwy +end +function FLIGHTCONTROL:GetActiveRunwayText(Takeoff) +local runway +if Takeoff then +runway=self:GetActiveRunwayTakeoff() +else +runway=self:GetActiveRunwayLanding() +end +local name=self.airbase:GetRunwayName(runway,true) +return name or"XX" +end +function FLIGHTCONTROL:_InitParkingSpots() +local parkingdata=self.airbase:GetParkingSpotsTable() +self.parking={} +self.Nparkingspots=0 +for _,_spot in pairs(parkingdata)do +local spot=_spot +local text=string.format("Parking ID=%d, Terminal=%d: Free=%s, Client=%s, Dist=%.1f",spot.TerminalID,spot.TerminalType,tostring(spot.Free),tostring(spot.ClientName),spot.DistToRwy) +self:T3(self.lid..text) +self.parking[spot.TerminalID]=spot +if spot.Free then +self:SetParkingFree(spot) +else +local unit=spot.Coordinate:FindClosestUnit(20) +if unit then +local unitname=unit and unit:GetName()or"unknown" +local isalive=unit:IsAlive() +self:T2(self.lid..string.format("FF parking spot %d is occupied by unit %s alive=%s",spot.TerminalID,unitname,tostring(isalive))) +if isalive then +self:SetParkingOccupied(spot,unitname) +self:SpawnParkingGuard(unit) +else +self:SetParkingFree(spot) +end +else +self:E(self.lid..string.format("ERROR: Parking spot is NOT FREE but no unit could be found there!")) +end +end +self.Nparkingspots=self.Nparkingspots+1 +end +end +function FLIGHTCONTROL:GetParkingSpotByID(TerminalID) +return self.parking[TerminalID] +end +function FLIGHTCONTROL:_UpdateSpotStatus(spot,status,unitname) +self:T2(self.lid..string.format("Updating parking spot %d status: %s --> %s (unit=%s)",spot.TerminalID,tostring(spot.Status),status,tostring(unitname))) +spot.Status=status +end +function FLIGHTCONTROL:SetParkingFree(spot) +local spot=self:GetParkingSpotByID(spot.TerminalID) +self:_UpdateSpotStatus(spot,AIRBASE.SpotStatus.FREE,spot.OccupiedBy or spot.ReservedBy) +spot.OccupiedBy=nil +spot.ReservedBy=nil +self:RemoveParkingGuard(spot) +self:UpdateParkingMarker(spot) +end +function FLIGHTCONTROL:SetParkingReserved(spot,unitname) +local spot=self:GetParkingSpotByID(spot.TerminalID) +self:_UpdateSpotStatus(spot,AIRBASE.SpotStatus.RESERVED,unitname) +spot.ReservedBy=unitname or"unknown" +self:UpdateParkingMarker(spot) +end +function FLIGHTCONTROL:SetParkingOccupied(spot,unitname) +local spot=self:GetParkingSpotByID(spot.TerminalID) +self:_UpdateSpotStatus(spot,AIRBASE.SpotStatus.OCCUPIED,unitname) +spot.OccupiedBy=unitname or"unknown" +self:UpdateParkingMarker(spot) +end +function FLIGHTCONTROL:UpdateParkingMarker(spot) +if self.markerParking then +local spot=self:GetParkingSpotByID(spot.TerminalID) +if spot.Status==AIRBASE.SpotStatus.FREE then +if spot.Marker then +spot.Marker:Remove() +end +else +local text=string.format("Spot %d (type %d): %s",spot.TerminalID,spot.TerminalType,spot.Status:upper()) +if spot.OccupiedBy then +text=text..string.format("\nOccupied by %s",tostring(spot.OccupiedBy)) +end +if spot.ReservedBy then +text=text..string.format("\nReserved for %s",tostring(spot.ReservedBy)) +end +if spot.ClientSpot then +text=text..string.format("\nClient %s",tostring(spot.ClientName)) +end +if spot.Marker then +if text~=spot.Marker.text or not spot.Marker.shown then +spot.Marker:UpdateText(text) +end +else +spot.Marker=MARKER:New(spot.Coordinate,text):ToAll() +end +end +end +end +function FLIGHTCONTROL:IsParkingFree(spot) +return spot.Status==AIRBASE.SpotStatus.FREE +end +function FLIGHTCONTROL:IsParkingOccupied(spot) +if spot.Status==AIRBASE.SpotStatus.OCCUPIED then +return tostring(spot.OccupiedBy) +else +return false +end +end +function FLIGHTCONTROL:IsParkingReserved(spot) +if spot.Status==AIRBASE.SpotStatus.RESERVED then +return tostring(spot.ReservedBy) +else +return false +end +end +function FLIGHTCONTROL:_GetFreeParkingSpots(terminal) +local freespots={} +local n=0 +for _,_parking in pairs(self.parking)do +local parking=_parking +if self:IsParkingFree(parking)then +if terminal==nil or terminal==parking.terminal then +n=n+1 +table.insert(freespots,parking) +end +end +end +return n,freespots +end +function FLIGHTCONTROL:GetClosestParkingSpot(Coordinate,TerminalType,Status) +local distmin=math.huge +local spotmin=nil +for TerminalID,Spot in pairs(self.parking)do +local spot=Spot +if(Status==nil or Status==spot.Status)and AIRBASE._CheckTerminalType(spot.TerminalType,TerminalType)then +local dist=Coordinate:Get2DDistance(spot.Coordinate) +if dist0 then +text=text..string.format("\n- Parking %d",NQparking) +end +if NQreadytx>0 then +text=text..string.format("\n- Ready to taxi %d",NQreadytx) +end +if NQtaxiout>0 then +text=text..string.format("\n- Taxi to runway %d",NQtaxiout) +end +if NQreadyto>0 then +text=text..string.format("\n- Ready for takeoff %d",NQreadyto) +end +if NQtakeoff>0 then +text=text..string.format("\n- Taking off %d",NQtakeoff) +end +if NQinbound>0 then +text=text..string.format("\n- Inbound %d",NQinbound) +end +if NQholding>0 then +text=text..string.format("\n- Holding pattern %d",NQholding) +end +if NQlanding>0 then +text=text..string.format("\n- Landing %d",NQlanding) +end +if NQtaxiinb>0 then +text=text..string.format("\n- Taxi to parking %d",NQtaxiinb) +end +if NQarrived>0 then +text=text..string.format("\n- Arrived at parking %d",NQarrived) +end +self:TextMessageToFlight(text,flight,15,true) +else +self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) +end +end +function FLIGHTCONTROL:_PlayerRequestInbound(groupname) +local flight=_DATABASE:GetOpsGroup(groupname) +if flight then +if flight:IsAirborne()then +local callsign=self:_GetCallsignName(flight) +local player=flight:GetPlayerElement() +local text=string.format("%s, %s, inbound for landing",self.alias,callsign) +self:TransmissionPilot(text,flight) +local flightcoord=flight:GetCoordinate(nil,player.name) +local dist=flightcoord:Get2DDistance(self:GetCoordinate()) +if distself.NlandingTakeoff then +local text=string.format("%s, negative! We have currently traffic taking off!",callsign) +self:TransmissionTower(text,flight,10) +else +local runway=self:GetActiveRunwayText() +local text=string.format("%s, affirmative, runway %s. Confirm approach!",callsign,runway) +self:TransmissionTower(text,flight,10) +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.LANDING) +end +else +local text=string.format("Negative, you must be INBOUND and CONTROLLED by us!") +self:TextMessageToFlight(text,flight,10) +end +else +self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) +end +end +function FLIGHTCONTROL:_PlayerRequestTaxi(groupname) +local flight=_DATABASE:GetOpsGroup(groupname) +if flight then +local callsign=self:_GetCallsignName(flight) +local text=string.format("%s, %s, request taxi to runway.",self.alias,callsign) +self:TransmissionPilot(text,flight) +if flight:IsParking()then +local text=string.format("%s, %s, hold position until further notice.",callsign,self.alias) +self:TransmissionTower(text,flight,10) +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.READYTX) +elseif flight:IsTaxiing()then +local runway=self:GetActiveRunwayText(true) +local text=string.format("%s, %s, taxi to runway %s, hold short.",callsign,self.alias,runway) +self:TransmissionTower(text,flight,10) +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAXIOUT) +local playerElement=flight:GetPlayerElement() +if playerElement and playerElement.parking then +self:SetParkingFree(playerElement.parking) +end +else +self:TextMessageToFlight(string.format("Negative, you must be PARKING to request TAXI!"),flight) +end +else +self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) +end +end +function FLIGHTCONTROL:_PlayerAbortTaxi(groupname) +local flight=_DATABASE:GetOpsGroup(groupname) +if flight then +local callsign=self:_GetCallsignName(flight) +local text=string.format("%s, %s, cancel my taxi request.",self.alias,callsign) +self:TransmissionPilot(text,flight) +if flight:IsParking()then +local text=string.format("%s, %s, roger, remain on your parking position.",callsign,self.alias) +self:TransmissionTower(text,flight,10) +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.PARKING) +local playerElement=flight:GetPlayerElement() +if playerElement then +self:SpawnParkingGuard(playerElement.unit) +end +elseif flight:IsTaxiing()then +local text=string.format("%s, %s, roger, return to your parking position.",callsign,self.alias) +self:TransmissionTower(text,flight,10) +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAXIINB) +else +self:TextMessageToFlight(string.format("Negative, you must be PARKING or TAXIING to abort TAXI!"),flight) +end +else +self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) +end +end +function FLIGHTCONTROL:_PlayerRequestTakeoff(groupname) +local flight=_DATABASE:GetOpsGroup(groupname) +if flight then +if flight:IsTaxiing()then +local callsign=self:_GetCallsignName(flight) +local text=string.format("%s, %s, ready for departure. Request takeoff.",self.alias,callsign) +self:TransmissionPilot(text,flight) +local Nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) +local Ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) +local text=string.format("%s, %s, ",callsign,self.alias) +if Nlanding==0 then +text=text.."no current traffic. You are cleared for takeoff." +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAKEOFF) +elseif Nlanding>0 then +if Nlanding==1 then +text=text..string.format("negative, we got %d flight inbound before it's your turn. Hold position until futher notice.",Nlanding) +else +text=text..string.format("negative, we got %d flights inbound. Hold positon until futher notice.",Nlanding) +end +end +self:TransmissionTower(text,flight,10) +else +self:TextMessageToFlight(string.format("Negative, you must request TAXI before you can request TAKEOFF!"),flight) +end +else +self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) +end +end +function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname) +local flight=_DATABASE:GetOpsGroup(groupname) +if flight then +local status=self:GetFlightStatus(flight) +if status==FLIGHTCONTROL.FlightStatus.TAKEOFF or status==FLIGHTCONTROL.FlightStatus.READYTO then +local callsign=self:_GetCallsignName(flight) +local text=string.format("%s, %s, abort takeoff.",self.alias,callsign) +self:TransmissionPilot(text,flight) +if flight:IsParking()then +text=string.format("%s, %s, affirm, remain on your parking position.",callsign,self.alias) +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.PARKING) +local playerElement=flight:GetPlayerElement() +if playerElement then +self:SpawnParkingGuard(playerElement.unit) +end +elseif flight:IsTaxiing()then +text=string.format("%s, %s, roger, report whether you want to taxi back or takeoff later.",callsign,self.alias) +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAXIOUT) +else +env.info(self.lid.."ERROR") +end +self:TransmissionTower(text,flight,10) +else +self:TextMessageToFlight("Negative, You are NOT in the takeoff queue",flight) +end +else +self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) +end +end +function FLIGHTCONTROL:_PlayerRequestParking(groupname) +local flight=_DATABASE:GetOpsGroup(groupname) +if flight then +local callsign=self:_GetCallsignName(flight) +local player=flight:GetPlayerElement() +local TerminalType=AIRBASE.TerminalType.FighterAircraft +if flight.isHelo then +TerminalType=AIRBASE.TerminalType.HelicopterUsable +end +local coord=flight:GetCoordinate(nil,player.name) +local spot=self:_GetPlayerSpot(player.name) +if not spot then +spot=self:GetClosestParkingSpot(coord,TerminalType,AIRBASE.SpotStatus.FREE) +end +if spot then +local text=string.format("%s, your assigned parking position is terminal ID %d.",callsign,spot.TerminalID) +self:TransmissionTower(text,flight) +if player.parking then +self:SetParkingFree(player.parking) +end +player.parking=spot +self:SetParkingReserved(spot,player.name) +flight:_UpdateMenu(0.2) +else +local text=string.format("%s, no free parking spot available. Try again later.",callsign) +self:TransmissionTower(text,flight) +end +else +self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) +end +end +function FLIGHTCONTROL:_PlayerCancelParking(groupname) +local flight=_DATABASE:GetOpsGroup(groupname) +if flight then +local callsign=self:_GetCallsignName(flight) +local player=flight:GetPlayerElement() +if player.parking then +self:SetParkingFree(player.parking) +player.parking=nil +self:TextMessageToFlight(string.format("%s, your parking spot reservation at terminal ID %d was cancelled.",callsign,player.parking.TerminalID),flight) +else +self:TextMessageToFlight("You did not have a valid parking spot reservation.",flight) +end +flight:_UpdateMenu(0.2) +else +self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) +end +end +function FLIGHTCONTROL:_PlayerArrived(groupname) +local flight=_DATABASE:GetOpsGroup(groupname) +if flight then +local player=flight:GetPlayerElement() +local coord=flight:GetCoordinate(nil,player.name) +local spot=self:_GetPlayerSpot(player.name) +if player.parking then +spot=self:GetParkingSpotByID(player.parking.TerminalID) +else +if not spot then +spot=self:GetClosestParkingSpot(coord) +end +end +if spot then +local callsign=self:_GetCallsignName(flight) +local dist=coord:Get2DDistance(spot.Coordinate) +if dist<12 then +local text=string.format("%s, %s, arrived at parking position. Terminal ID %d.",self.alias,callsign,spot.TerminalID) +self:TransmissionPilot(text,flight) +local text="" +if spot.ReservedBy and spot.ReservedBy~=player.name then +text=string.format("%s, this spot is already reserved for %s. Find yourself a different parking position.",callsign,self.alias,spot.ReservedBy) +else +text=string.format("%s, %s, roger. Enjoy a cool bevarage in the officers' club.",callsign,self.alias) +flight:ElementParking(player,spot) +self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.PARKING) +if player then +self:SpawnParkingGuard(player.unit) +end +end +self:TransmissionTower(text,flight,10) +else +local text=string.format("%s, %s, arrived at parking position.",self.alias,callsign) +self:TransmissionPilot(text,flight) +local text="" +if spot.ReservedBy then +if spot.ReservedBy==player.name then +text=string.format("%s, %s, you are still %d meters away from your reserved parking position at terminal ID %d. Continue taxiing!",callsign,self.alias,dist,spot.TerminalID) +else +text=string.format("%s, %s, the closest parking spot is already reserved. Continue taxiing to a free spot!",callsign,self.alias) +end +else +text=string.format("%s, %s, you are still %d meters away from the closest parking position. Continue taxiing to a proper spot!",callsign,self.alias,dist) +end +self:TransmissionTower(text,flight,10) +end +else +end +else +self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname))) +end +end +function FLIGHTCONTROL:_CreateFlightGroup(group) +if self:_InQueue(self.flights,group)then +self:E(self.lid..string.format("WARNING: Flight group %s does already exist!",group:GetName())) +return +end +self:T(self.lid..string.format("Creating new flight for group %s of aircraft type %s.",group:GetName(),group:GetTypeName())) +local flight=_DATABASE:GetOpsGroup(group:GetName()) +if not flight then +flight=FLIGHTGROUP:New(group:GetName()) +end +if flight.homebase and flight.homebase:GetName()==self.airbasename then +flight:SetFlightControl(self) +end +return flight +end +function FLIGHTCONTROL:_RemoveFlight(Flight) +for i,_flight in pairs(self.flights)do +local flight=_flight +if flight.groupname==Flight.groupname then +self:T(self.lid..string.format("Removing flight group %s",flight.groupname)) +table.remove(self.flights,i) +Flight.flightcontrol=nil +self:SetFlightStatus(Flight,FLIGHTCONTROL.FlightStatus.UNKNOWN) +return true +end +end +self:E(self.lid..string.format("WARNING: Could NOT remove flight group %s",Flight.groupname)) +end +function FLIGHTCONTROL:_GetFlightFromGroup(group) +if group then +local name=group:GetName() +for i,_flight in pairs(self.flights)do +local flight=_flight +if flight.groupname==name then +return flight,i +end +end +self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.",name)) +end +self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!")) +return nil,nil +end +function FLIGHTCONTROL:_GetFlightElement(unitname) +local unit=UNIT:FindByName(unitname) +if unit then +local flight=self:_GetFlightFromGroup(unit:GetGroup()) +if flight then +for i,_element in pairs(flight.elements)do +local element=_element +if element.unit:GetName()==unitname then +return element,i,flight +end +end +self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.",unitname,flight.groupname)) +end +end +return nil,nil,nil +end +function FLIGHTCONTROL:_CheckFlights() +for i=#self.flights,1,-1 do +local flight=self.flights[i] +if flight:IsDead()then +self:T(self.lid..string.format("Removing DEAD flight %s",tostring(flight.groupname))) +self:_RemoveFlight(flight) +end +end +self.Nplayers=0 +for _,_flight in pairs(self.flights)do +local flight=_flight +if not flight.isAI then +self.Nplayers=self.Nplayers+1 +end +end +if self.speedLimitTaxi then +for _,_flight in pairs(self.flights)do +local flight=_flight +if not flight.isAI then +local playerElement=flight:GetPlayerElement() +local flightstatus=self:GetFlightStatus(flight) +if playerElement then +if(flightstatus==FLIGHTCONTROL.FlightStatus.TAXIINB or flightstatus==FLIGHTCONTROL.FlightStatus.TAXIOUT)and self.speedLimitTaxi then +local speed=playerElement.unit:GetVelocityMPS() +local coord=playerElement.unit:GetCoord() +local onRunway=self:IsCoordinateRunway(coord) +self:T(self.lid..string.format("Player %s speed %.1f knots (max=%.1f) onRunway=%s",playerElement.playerName,UTILS.MpsToKnots(speed),UTILS.MpsToKnots(self.speedLimitTaxi),tostring(onRunway))) +if speed and speed>self.speedLimitTaxi and not onRunway then +local callsign=self:_GetCallsignName(flight) +local text=string.format("%s, slow down, you are taxiing too fast!",callsign) +self:TransmissionTower(text,flight) +local PlayerData=flight:_GetPlayerData() +self:PlayerSpeeding(PlayerData) +end +end +end +end +end +end +end +function FLIGHTCONTROL:_CheckParking() +for TerminalID,_spot in pairs(self.parking)do +local spot=_spot +if spot.Reserved then +if spot.MarkerID then +spot.Coordinate:RemoveMark(spot.MarkerID) +end +spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking reserved for %s",tostring(spot.Reserved)),self:GetCoalition()) +end +for i=1,#self.flights do +local flight=self.flights[i] +for _,_element in pairs(flight.elements)do +local element=_element +if element.parking and element.parking.TerminalID==TerminalID then +if spot.MarkerID then +spot.Coordinate:RemoveMark(spot.MarkerID) +end +spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking spot occupied by %s",tostring(element.name)),self:GetCoalition()) +end +end +end +end +end +function FLIGHTCONTROL:_LandAI(flight,parking) +self:T(self.lid..string.format("Landing AI flight %s.",flight.groupname)) +local respawn=false +if respawn then +local Template=flight.group:GetTemplate() +Template.route.points=wp +for i,unit in pairs(Template.units)do +local spot=parking[i] +local element=flight:GetElementByName(unit.name) +if element then +unit.parking_landing=spot.TerminalID +local text=string.format("Reserving parking spot %d for unit %s",spot.TerminalID,tostring(unit.name)) +self:T(self.lid..text) +self:SetParkingReserved(spot,element.name) +else +env.info("FF error could not get element to assign parking!") +end +end +self:TextMessageToFlight(string.format("Respawning group %s",flight.groupname),flight) +flight:Respawn(Template) +else +flight:ClearToLand() +end +end +function FLIGHTCONTROL:_GetHoldingStack(flight) +self:T(self.lid..string.format("Getting holding point for flight %s",flight:GetName())) +for i,_hp in pairs(self.holdingpatterns)do +local holdingpattern=_hp +self:T(self.lid..string.format("Checking holding point %s",holdingpattern.name)) +for j,_stack in pairs(holdingpattern.stacks)do +local stack=_stack +local name=stack.flightgroup and stack.flightgroup:GetName()or"empty" +self:T(self.lid..string.format("Stack %d: %s",j,name)) +if not stack.flightgroup then +return stack +end +end +end +return nil +end +function FLIGHTCONTROL:_CountFlightsInPattern(Pattern) +local N=0 +for _,_stack in pairs(Pattern.stacks)do +local stack=_stack +if stack.flightgroup then +N=N+1 +end +end +return N +end +function FLIGHTCONTROL:_FlightOnFinal(flight) +local callsign=self:_GetCallsignName(flight) +local text=string.format("%s, final",callsign) +self:TransmissionPilot(text,flight) +return self +end +function FLIGHTCONTROL:TransmissionTower(Text,Flight,Delay) +if self.radioOnlyIfPlayers==true and self.Nplayers==0 then +self:T(self.lid.."No players ==> skipping TOWER radio transmission") +return +end +local text=self:_GetTextForSpeech(Text) +local subgroups=nil +if Flight and not Flight.isAI then +local playerData=Flight:_GetPlayerData() +if playerData.subtitles and(not self.nosubs)then +subgroups=subgroups or{} +table.insert(subgroups,Flight.group) +end +end +local transmission=self.msrsqueue:NewTransmission(text,nil,self.msrsTower,nil,1,subgroups,Text) +self.Tlastmessage=timer.getAbsTime()+(Delay or 0) +self:T(self.lid..string.format("Radio Tower: %s",Text)) +end +function FLIGHTCONTROL:TransmissionPilot(Text,Flight,Delay) +if self.radioOnlyIfPlayers==true and self.Nplayers==0 then +self:T(self.lid.."No players ==> skipping PILOT radio transmission") +return +end +local playerData=Flight:_GetPlayerData() +if playerData==nil or playerData.myvoice then +local text=self:_GetTextForSpeech(Text) +local msrs=self.msrsPilot +if Flight.useSRS and Flight.msrs then +msrs=Flight.msrs +end +local subgroups=nil +if Flight and not Flight.isAI then +local playerData=Flight:_GetPlayerData() +if playerData.subtitles and(not self.nosubs)then +subgroups=subgroups or{} +table.insert(subgroups,Flight.group) +end +end +local coordinate=Flight:GetCoordinate(true) +msrs:SetCoordinate() +self.msrsqueue:NewTransmission(text,nil,msrs,nil,1,subgroups,Text,nil,self.frequency,self.modulation) +end +self.Tlastmessage=timer.getAbsTime()+(Delay or 0) +self:T(self.lid..string.format("Radio Pilot: %s",Text)) +end +function FLIGHTCONTROL:TextMessageToFlight(Text,Flight,Duration,Clear,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,FLIGHTCONTROL.TextMessageToFlight,self,Text,Flight,Duration,Clear,0) +else +if Flight and Flight.group and Flight.group:IsAlive()then +local gid=Flight.group:GetID() +trigger.action.outTextForGroup(gid,self:_CleanText(Text),Duration or 5,Clear) +end +end +end +function FLIGHTCONTROL:_CleanText(Text) +local text=Text:gsub("\n$",""):gsub("\n$","") +return text +end +function FLIGHTCONTROL:_SpawnParkingGuard(unit) +local coordinate=unit:GetCoordinate() +local spot=self:GetClosestParkingSpot(coordinate) +if not spot.ParkingGuard then +local heading=unit:GetHeading() +local size,x,y,z=unit:GetObjectSize() +local xdiff=3 +if AIRBASE._CheckTerminalType(spot.TerminalType,AIRBASE.TerminalType.Shelter)then +xdiff=27-(x*0.5) +end +if(AIRBASE._CheckTerminalType(spot.TerminalType,AIRBASE.TerminalType.OpenMed)or AIRBASE._CheckTerminalType(spot.TerminalType,AIRBASE.TerminalType.Shelter))and self.airbasename==AIRBASE.Sinai.Ramon_Airbase then +xdiff=12 +end +self:T2(self.lid..string.format("Parking guard for %s: heading=%d, length x=%.1f m, xdiff=%.1f m",unit:GetName(),heading,x,xdiff)) +local Coordinate=coordinate:Translate(x*0.5+xdiff,heading) +local lookat=heading-180 +self.parkingGuard:InitHeading(lookat) +if self.parkingGuard:IsInstanceOf("SPAWN")then +end +spot.ParkingGuard=self.parkingGuard:SpawnFromCoordinate(Coordinate) +else +self:E(self.lid.."ERROR: Parking Guard already exists!") +end +end +function FLIGHTCONTROL:SpawnParkingGuard(unit) +if unit and self.parkingGuard then +self:ScheduleOnce(1,FLIGHTCONTROL._SpawnParkingGuard,self,unit) +end +end +function FLIGHTCONTROL:RemoveParkingGuard(spot,delay) +if delay and delay>0 then +self:ScheduleOnce(delay,FLIGHTCONTROL.RemoveParkingGuard,self,spot) +else +if spot.ParkingGuard then +spot.ParkingGuard:Destroy() +spot.ParkingGuard=nil +end +end +end +function FLIGHTCONTROL:_IsFlightOnRunway(flight) +for _,_runway in pairs(self.airbase.runways)do +local runway=_runway +local inzone=flight:IsInZone(runway.zone) +if inzone then +return runway +end +end +return nil +end +function FLIGHTCONTROL:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +if not ShortCallsign or ShortCallsign==false then +self.ShortCallsign=false +else +self.ShortCallsign=true +end +self.Keepnumber=Keepnumber or false +self.CallsignTranslations=CallsignTranslations +return self +end +function FLIGHTCONTROL:_GetCallsignName(flight) +local callsign=flight:GetCallsignName(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) +return callsign +end +function FLIGHTCONTROL:_GetTextForSpeech(text) +local function space(text) +local res="" +for i=1,#text do +local char=text:sub(i,i) +res=res..char.." " +end +return res +end +local t=text:gsub("(%d+)",space) +return t +end +function FLIGHTCONTROL:_GetPlayerUnitAndName(unitName) +if unitName then +local DCSunit=Unit.getByName(unitName) +if DCSunit then +local playername=DCSunit:getPlayerName() +local unit=UNIT:Find(DCSunit) +if DCSunit and unit and playername then +self:T(self.lid..string.format("Found DCS unit %s with player %s",tostring(unitName),tostring(playername))) +return unit,playername +end +end +end +return nil,nil +end +function FLIGHTCONTROL:_CheckMarkHoldingPatterns() +for _,pattern in pairs(self.holdingpatterns)do +local Pattern=pattern +if self.markPatterns then +self:_MarkHoldingPattern(Pattern) +else +self:_UnMarkHoldingPattern(Pattern) +end +end +end +function FLIGHTCONTROL:_MarkHoldingPattern(Pattern) +if not Pattern.markArrow then +Pattern.markArrow=Pattern.pos0:ArrowToAll(Pattern.pos1,nil,{1,0,0},1,{1,1,0},0.5,2,true) +end +if not Pattern.markArrival then +Pattern.markArrival=Pattern.arrivalzone:DrawZone() +end +end +function FLIGHTCONTROL:_UnMarkHoldingPattern(Pattern) +if Pattern.markArrow then +UTILS.RemoveMark(Pattern.markArrow) +Pattern.markArrow=nil +end +if Pattern.markArrival then +UTILS.RemoveMark(Pattern.markArrival) +Pattern.markArrival=nil +end +end +function FLIGHTCONTROL:_AddHoldingPatternBackup() +local runway=self:GetActiveRunway() +local heading=runway.heading +local vec2=self.airbase:GetVec2() +local Vec2=UTILS.Vec2Translate(vec2,UTILS.NMToMeters(5),heading+90) +local ArrivalZone=ZONE_RADIUS:New("Arrival Zone",Vec2,5000) +self.holdingBackup=self:AddHoldingPattern(ArrivalZone,heading,15,5,25,999) +return self +end +FLIGHTGROUP={ +ClassName="FLIGHTGROUP", +homebase=nil, +destbase=nil, +homezone=nil, +destzone=nil, +actype=nil, +speedMax=nil, +rangemax=nil, +ceiling=nil, +fuellow=false, +fuellowthresh=nil, +fuellowrtb=nil, +fuelcritical=nil, +fuelcriticalthresh=nil, +fuelcriticalrtb=false, +outofAAMrtb=false, +outofAGMrtb=false, +flightcontrol=nil, +flaghold=nil, +Tholding=nil, +Tparking=nil, +Twaiting=nil, +menu=nil, +isHelo=nil, +RTBRecallCount=0, +playerSettings={}, +playerWarnings={}, +prohibitAB=false, +jettisonEmptyTanks=true, +jettisonWeapons=true, +} +FLIGHTGROUP.Attribute={ +TRANSPORTPLANE="TransportPlane", +AWACS="AWACS", +FIGHTER="Fighter", +BOMBER="Bomber", +TANKER="Tanker", +TRANSPORTHELO="TransportHelo", +ATTACKHELO="AttackHelo", +UAV="UAV", +OTHER="Other", +} +FLIGHTGROUP.RadioMessage={ +AIRBORNE={normal="Airborn",enhanced="Airborn"}, +TAXIING={normal="Taxiing",enhanced="Taxiing"}, +} +FLIGHTGROUP.PlayerSkill={ +STUDENT="Student", +AVIATOR="Aviator", +GRADUATE="Graduate", +INSTRUCTOR="Instructor", +} +FLIGHTGROUP.Players={} +FLIGHTGROUP.version="1.0.3" +function FLIGHTGROUP:New(group) +local og=_DATABASE:GetOpsGroup(group) +if og then +og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) +return og +end +local self=BASE:Inherit(self,OPSGROUP:New(group)) +self.lid=string.format("FLIGHTGROUP %s | ",self.groupname or"N/A") +self:SetDefaultROE() +self:SetDefaultROT() +self:SetDefaultEPLRS(self.isEPLRS) +self:SetDetection() +self:SetFuelLowThreshold() +self:SetFuelLowRTB() +self:SetFuelCriticalThreshold() +self:SetFuelCriticalRTB() +self.flaghold=USERFLAG:New(string.format("%s_FlagHold",self.groupname)) +self.flaghold:Set(0) +self.holdtime=2*60 +self:AddTransition("*","LandAtAirbase","Inbound") +self:AddTransition("*","RTB","Inbound") +self:AddTransition("*","RTZ","Inbound") +self:AddTransition("Inbound","Holding","Holding") +self:AddTransition("*","Refuel","Going4Fuel") +self:AddTransition("Going4Fuel","Refueled","Cruising") +self:AddTransition("*","LandAt","LandingAt") +self:AddTransition("LandingAt","LandedAt","LandedAt") +self:AddTransition("*","FuelLow","*") +self:AddTransition("*","FuelCritical","*") +self:AddTransition("Cruising","EngageTarget","Engaging") +self:AddTransition("Engaging","Disengage","Cruising") +self:AddTransition("*","ElementParking","*") +self:AddTransition("*","ElementEngineOn","*") +self:AddTransition("*","ElementTaxiing","*") +self:AddTransition("*","ElementTakeoff","*") +self:AddTransition("*","ElementAirborne","*") +self:AddTransition("*","ElementLanded","*") +self:AddTransition("*","ElementArrived","*") +self:AddTransition("*","ElementOutOfAmmo","*") +self:AddTransition("*","Parking","Parking") +self:AddTransition("*","Taxiing","Taxiing") +self:AddTransition("*","Takeoff","Airborne") +self:AddTransition("*","Airborne","Airborne") +self:AddTransition("*","Cruise","Cruising") +self:AddTransition("*","Landing","Landing") +self:AddTransition("*","Landed","Landed") +self:AddTransition("*","Arrived","Arrived") +self:HandleEvent(EVENTS.Birth,self.OnEventBirth) +self:HandleEvent(EVENTS.EngineStartup,self.OnEventEngineStartup) +self:HandleEvent(EVENTS.Takeoff,self.OnEventTakeOff) +self:HandleEvent(EVENTS.Land,self.OnEventLanding) +self:HandleEvent(EVENTS.EngineShutdown,self.OnEventEngineShutdown) +self:HandleEvent(EVENTS.PilotDead,self.OnEventPilotDead) +self:HandleEvent(EVENTS.Ejection,self.OnEventEjection) +self:HandleEvent(EVENTS.Crash,self.OnEventCrash) +self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit) +self:HandleEvent(EVENTS.UnitLost,self.OnEventUnitLost) +self:HandleEvent(EVENTS.Kill,self.OnEventKill) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventPlayerLeaveUnit) +self:_InitGroup() +self:_InitWaypoints() +self.timerStatus=TIMER:New(self.Status,self):Start(1,30) +self.timerQueueUpdate=TIMER:New(self._QueueUpdate,self):Start(2,5) +self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(3,10) +_DATABASE:AddOpsGroup(self) +return self +end +function FLIGHTGROUP:AddTaskEnrouteEngageTargetsInZone(ZoneRadius,TargetTypes,Priority) +local Task=self.group:EnRouteTaskEngageTargetsInZone(ZoneRadius:GetVec2(),ZoneRadius:GetRadius(),TargetTypes,Priority) +self:AddTaskEnroute(Task) +end +function FLIGHTGROUP:GetAirwing() +return self.legion +end +function FLIGHTGROUP:GetAirwingName() +local name=self.legion and self.legion.alias or"None" +return name +end +function FLIGHTGROUP:GetSquadron() +return self.cohort +end +function FLIGHTGROUP:GetSquadronName() +local name=self.cohort and self.cohort:GetName()or"None" +return name +end +function FLIGHTGROUP:SetVTOL() +self.isVTOL=true +return self +end +function FLIGHTGROUP:SetProhibitAfterburner() +self.prohibitAB=true +if self:GetGroup():IsAlive()then +self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,true) +end +return self +end +function FLIGHTGROUP:SetAllowAfterburner() +self.prohibitAB=false +if self:GetGroup():IsAlive()then +self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,false) +end +return self +end +function FLIGHTGROUP:SetJettisonEmptyTanks(Switch) +self.jettisonEmptyTanks=Switch +if self:GetGroup():IsAlive()then +self:GetGroup():SetOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY,Switch) +end +return self +end +function FLIGHTGROUP:SetJettisonWeapons(Switch) +self.jettisonWeapons=not Switch +if self:GetGroup():IsAlive()then +self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_JETT,not Switch) +end +return self +end +function FLIGHTGROUP:SetOptionLandingStraightIn() +self.OptionLandingStraightIn=true +if self:GetGroup():IsAlive()then +self:GetGroup():SetOptionLandingStraightIn() +end +return self +end +function FLIGHTGROUP:SetOptionLandingForcePair() +self.OptionLandingForcePair=true +if self:GetGroup():IsAlive()then +self:GetGroup():SetOptionLandingForcePair() +end +return self +end +function FLIGHTGROUP:SetOptionLandingRestrictPair() +self.OptionLandingRestrictPair=true +if self:GetGroup():IsAlive()then +self:GetGroup():SetOptionLandingRestrictPair() +end +return self +end +function FLIGHTGROUP:SetOptionLandingOverheadBreak() +self.OptionLandingOverheadBreak=true +if self:GetGroup():IsAlive()then +self:GetGroup():SetOptionLandingOverheadBreak() +end +return self +end +function FLIGHTGROUP:SetOptionPreferVertical() +self.OptionPreferVertical=true +if self:GetGroup():IsAlive()then +self:GetGroup():OptionPreferVerticalLanding() +end +return self +end +function FLIGHTGROUP:SetReadyForTakeoff(ReadyTO,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,FLIGHTGROUP.SetReadyForTakeoff,self,ReadyTO,0) +else +self:T(self.lid.."Set Ready for Takeoff switch for flightcontrol") +self.isReadyTO=ReadyTO +end +return self +end +function FLIGHTGROUP:SetFlightControl(flightcontrol) +if self.flightcontrol then +if self.flightcontrol:IsControlling(self)then +return +else +self.flightcontrol:_RemoveFlight(self) +end +end +self:T(self.lid..string.format("Setting FLIGHTCONTROL to airbase %s",flightcontrol.airbasename)) +self.flightcontrol=flightcontrol +if not flightcontrol:IsFlight(self)then +table.insert(flightcontrol.flights,self) +end +return self +end +function FLIGHTGROUP:GetFlightControl() +return self.flightcontrol +end +function FLIGHTGROUP:SetHomebase(HomeAirbase) +if type(HomeAirbase)=="string"then +HomeAirbase=AIRBASE:FindByName(HomeAirbase) +end +self.homebase=HomeAirbase +return self +end +function FLIGHTGROUP:SetDestinationbase(DestinationAirbase) +if type(DestinationAirbase)=="string"then +DestinationAirbase=AIRBASE:FindByName(DestinationAirbase) +end +self.destbase=DestinationAirbase +return self +end +function FLIGHTGROUP:SetAirboss(airboss) +self.airboss=airboss +return self +end +function FLIGHTGROUP:SetFuelLowThreshold(threshold) +self.fuellowthresh=threshold or 25 +return self +end +function FLIGHTGROUP:SetFuelLowRTB(switch) +if switch==false then +self.fuellowrtb=false +else +self.fuellowrtb=true +end +return self +end +function FLIGHTGROUP:SetOutOfAAMRTB(switch) +if switch==false then +self.outofAAMrtb=false +else +self.outofAAMrtb=true +end +return self +end +function FLIGHTGROUP:SetOutOfAGMRTB(switch) +if switch==false then +self.outofAGMrtb=false +else +self.outofAGMrtb=true +end +return self +end +function FLIGHTGROUP:SetFuelLowRefuel(switch) +if switch==false then +self.fuellowrefuel=false +else +self.fuellowrefuel=true +end +return self +end +function FLIGHTGROUP:SetFuelCriticalThreshold(threshold) +self.fuelcriticalthresh=threshold or 10 +return self +end +function FLIGHTGROUP:SetFuelCriticalRTB(switch) +if switch==false then +self.fuelcriticalrtb=false +else +self.fuelcriticalrtb=true +end +return self +end +function FLIGHTGROUP:SetDespawnAfterLanding() +self.despawnAfterLanding=true +return self +end +function FLIGHTGROUP:SetDespawnAfterHolding() +self.despawnAfterHolding=true +return self +end +function FLIGHTGROUP:IsParking(Element) +local is=self:Is("Parking") +if Element then +is=Element.status==OPSGROUP.ElementStatus.PARKING +end +return is +end +function FLIGHTGROUP:IsTaxiing(Element) +local is=self:Is("Taxiing") +if Element then +is=Element.status==OPSGROUP.ElementStatus.TAXIING +end +return is +end +function FLIGHTGROUP:IsAirborne(Element) +local is=self:Is("Airborne")or self:Is("Cruising") +if Element then +is=Element.status==OPSGROUP.ElementStatus.AIRBORNE +end +return is +end +function FLIGHTGROUP:IsCruising() +local is=self:Is("Cruising") +return is +end +function FLIGHTGROUP:IsLanding(Element) +local is=self:Is("Landing") +if Element then +is=Element.status==OPSGROUP.ElementStatus.LANDING +end +return is +end +function FLIGHTGROUP:IsLanded(Element) +local is=self:Is("Landed") +if Element then +is=Element.status==OPSGROUP.ElementStatus.LANDED +end +return is +end +function FLIGHTGROUP:IsArrived(Element) +local is=self:Is("Arrived") +if Element then +is=Element.status==OPSGROUP.ElementStatus.ARRIVED +end +return is +end +function FLIGHTGROUP:IsInbound() +local is=self:Is("Inbound") +return is +end +function FLIGHTGROUP:IsHolding() +local is=self:Is("Holding") +return is +end +function FLIGHTGROUP:IsGoing4Fuel() +local is=self:Is("Going4Fuel") +return is +end +function FLIGHTGROUP:IsLandingAt() +local is=self:Is("LandingAt") +return is +end +function FLIGHTGROUP:IsLandedAt() +local is=self:Is("LandedAt") +return is +end +function FLIGHTGROUP:IsFuelLow() +return self.fuellow +end +function FLIGHTGROUP:IsFuelCritical() +return self.fuelcritical +end +function FLIGHTGROUP:IsFuelGood() +local isgood=not(self.fuellow or self.fuelcritical) +return isgood +end +function FLIGHTGROUP:CanAirToGround(ExcludeGuns) +local ammo=self:GetAmmoTot() +if ExcludeGuns then +return ammo.MissilesAG+ammo.Rockets+ammo.Bombs>0 +else +return ammo.MissilesAG+ammo.Rockets+ammo.Bombs+ammo.Guns>0 +end +end +function FLIGHTGROUP:CanAirToAir(ExcludeGuns) +local ammo=self:GetAmmoTot() +if ExcludeGuns then +return ammo.MissilesAA>0 +else +return ammo.MissilesAA+ammo.Guns>0 +end +end +function FLIGHTGROUP:StartUncontrolled(delay) +if delay and delay>0 then +self:T2(self.lid..string.format("Starting uncontrolled group in %d seconds",delay)) +self:ScheduleOnce(delay,FLIGHTGROUP.StartUncontrolled,self) +else +local alive=self:IsAlive() +if alive~=nil then +local _delay=0 +if alive==false then +self:Activate() +_delay=1 +end +self:T(self.lid.."Starting uncontrolled group") +self.group:StartUncontrolled(_delay) +self.isUncontrolled=false +else +self:T(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive!") +end +end +return self +end +function FLIGHTGROUP:ClearToLand(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,FLIGHTGROUP.ClearToLand,self) +else +if self:IsHolding()then +self:T(self.lid..string.format("Clear to land ==> setting holding flag to 1 (true)")) +self.flaghold:Set(1) +self.Tholding=nil +if self.stack then +self.stack.flightgroup=nil +self.stack=nil +end +end +end +return self +end +function FLIGHTGROUP:GetFuelMin() +local fuelmin=math.huge +for i,_element in pairs(self.elements)do +local element=_element +local unit=element.unit +local life=unit:GetLife() +if unit and unit:IsAlive()and life>1 then +local fuel=unit:GetFuel() +if fuelself.Twaiting+self.dTwait then +end +end +end +if mission and mission.missionHoldingCoord and self.isHoldingAtHoldingPoint==true then +self:T(self.lid.."...yes") +if mission:IsReadyToPush()then +self.flaghold:Set(1) +self.Twaiting=nil +self.dTwait=nil +self.isHoldingAtHoldingPoint=false +end +end +if mission and mission.updateDCSTask then +if(mission:GetType()==AUFTRAG.Type.ORBIT or mission:GetType()==AUFTRAG.Type.RECOVERYTANKER or mission:GetType()==AUFTRAG.Type.CAP)and mission.orbitVec2 then +local vec2=mission:GetTargetVec2() +local hdg=mission:GetTargetHeading() +local hdgchange=false +if mission.orbitLeg then +if UTILS.HdgDiff(hdg,mission.targetHeading)>0 then +hdgchange=true +end +end +local dist=UTILS.VecDist2D(vec2,mission.orbitVec2) +local distchange=dist>mission.orbitDeltaR +self:T3(self.lid..string.format("Checking orbit mission dist=%d meters",dist)) +if distchange or hdgchange then +self:T3(self.lid..string.format("Updating orbit!")) +local DCSTask=mission:GetDCSMissionTask() +local Task=mission:GetGroupWaypointTask(self) +self.controller:resetTask() +self:_SandwitchDCSTask(DCSTask,Task,false,1) +end +elseif mission.type==AUFTRAG.Type.CAPTUREZONE then +local Task=mission:GetGroupWaypointTask(self) +if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then +self:_UpdateTask(Task,mission) +end +end +end +if self:IsParking()then +for _,_element in pairs(self.elements)do +local element=_element +if element.parking then +local dist=self:_GetDistToParking(element.parking,element.unit:GetCoord()) +self:T(self.lid..string.format("Distance to parking spot %d = %.1f meters",element.parking.TerminalID,dist)) +if dist>12 and element.engineOn then +self:ElementTaxiing(element) +end +else +end +end +end +else +self:_CheckDamage() +end +if self.verbose>=1 then +local nelem=self:CountElements() +local Nelem=#self.elements +local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks() +local nMissions=self:CountRemainingMissison() +local roe=self:GetROE()or-1 +local rot=self:GetROT()or-1 +local wpidxCurr=self.currentwp +local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr)or 0 +local wpidxNext=self:GetWaypointIndexNext()or 0 +local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext)or 0 +local wpN=#self.waypoints or 0 +local wpF=tostring(self.passedfinalwp) +local speed=UTILS.MpsToKnots(self.velocity or 0) +local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) +local alt=self.position and self.position.y or 0 +local hdg=self.heading or 0 +local formation=self.option.Formation or"unknown" +local life=self.life or 0 +local ammo=self:GetAmmoTot().Total +local ndetected=self.detectionOn and tostring(self.detectedunits:Count())or"Off" +local cargo=0 +for _,_element in pairs(self.elements)do +local element=_element +cargo=cargo+element.weightCargo +end +local home=self.homebase and self.homebase:GetName()or"unknown" +local dest=self.destbase and self.destbase:GetName()or"unknown" +local curr=self.currbase and self.currbase:GetName()or"N/A" +local text=string.format("%s [%d/%d]: ROE/ROT=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f | Base=%s [%s-->%s]", +fsmstate,nelem,Nelem,roe,rot,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,wpN,wpF,life,speed,speedEx,hdg,ammo,ndetected,cargo,curr,home,dest) +self:I(self.lid..text) +end +if self.verbose>=2 then +local text="Elements:" +for i,_element in pairs(self.elements)do +local element=_element +local name=element.name +local status=element.status +local unit=element.unit +local fuel=unit:GetFuel()or 0 +local life=unit:GetLifeRelative()or 0 +local lp=unit:GetLife() +local lp0=unit:GetLife0() +local parking=element.parking and tostring(element.parking.TerminalID)or"X" +local ammo=self:GetAmmoElement(element) +text=text..string.format("\n[%d] %s: status=%s, fuel=%.1f, life=%.1f [%.1f/%.1f], guns=%d, rockets=%d, bombs=%d, missiles=%d (AA=%d, AG=%d, AS=%s), parking=%s", +i,name,status,fuel*100,life*100,lp,lp0,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles,ammo.MissilesAA,ammo.MissilesAG,ammo.MissilesAS,parking) +end +if#self.elements==0 then +text=text.." none!" +end +self:I(self.lid..text) +end +if self.verbose>=4 and alive then +local ds=self.travelds +local dt=self.dTpositionUpdate +local v=ds/dt +local TmaxFuel=math.huge +for _,_element in pairs(self.elements)do +local element=_element +local fuel=element.unit:GetFuel()or 0 +local dFrel=element.fuelrel-fuel +local dFreldt=dFrel/dt +local Tfuel=fuel/dFreldt +if Tfuel Tfuel=%.1f min",element.name,fuel*100,dFrel*100,dFreldt*100*60,Tfuel/60)) +element.fuelrel=fuel +end +self:T(self.lid..string.format("Travelled ds=%.1f km dt=%.1f s ==> v=%.1f knots. Fuel left for %.1f min",self.traveldist/1000,dt,UTILS.MpsToKnots(v),TmaxFuel/60)) +end +if false then +for _,_element in pairs(self.elements)do +local element=_element +local unit=element.unit +if unit and unit:IsAlive()then +local vec3=unit:GetVec3() +if vec3 and element.pos then +local id=UTILS.GetMarkID() +trigger.action.lineToAll(-1,id,vec3,element.pos,{1,1,1,0.5},1) +end +element.pos=vec3 +end +end +end +if alive and self.group:IsAirborne(true)then +local fuelmin=self:GetFuelMin() +self:T2(self.lid..string.format("Fuel state=%d",fuelmin)) +if fuelmin>=self.fuellowthresh then +self.fuellow=false +end +if fuelmin>=self.fuelcriticalthresh then +self.fuelcritical=false +end +if fuelmin taxiing (if AI)",element.name)) +self:ElementEngineOn(element) +element.engineOn=true +end +end +end +end +function FLIGHTGROUP:OnEventTakeOff(EventData) +self:T3(self.lid.."EVENT: TakeOff") +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +local element=self:GetElementByName(unitname) +if element then +self:T2(self.lid..string.format("EVENT: Element %s took off ==> airborne",element.name)) +self:ElementTakeoff(element,EventData.Place) +end +end +end +function FLIGHTGROUP:OnEventLanding(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +local element=self:GetElementByName(unitname) +local airbase=EventData.Place +local airbasename="unknown" +if airbase then +airbasename=tostring(airbase:GetName()) +end +if element then +self:T3(self.lid..string.format("EVENT: Element %s landed at %s ==> landed",element.name,airbasename)) +self:ElementLanded(element,airbase) +end +end +end +function FLIGHTGROUP:OnEventEngineShutdown(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +local element=self:GetElementByName(unitname) +if element then +element.engineOn=false +if element.unit and element.unit:IsAlive()then +local airbase=self:GetClosestAirbase() +local parking=self:GetParkingSpot(element,100,airbase) +if airbase and parking then +self:ElementArrived(element,airbase,parking) +self:T3(self.lid..string.format("EVENT: Element %s shut down engines ==> arrived",element.name)) +else +self:T3(self.lid..string.format("EVENT: Element %s shut down engines but is not parking. Is it dead?",element.name)) +end +else +end +end +end +end +function FLIGHTGROUP:OnEventCrash(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +local element=self:GetElementByName(unitname) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +self:T(self.lid..string.format("EVENT: Element %s crashed ==> destroyed",element.name)) +self:ElementDestroyed(element) +end +end +end +function FLIGHTGROUP:OnEventUnitLost(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +self:T2(self.lid..string.format("EVENT: Unit %s lost at t=%.3f",EventData.IniUnitName,timer.getTime())) +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +local element=self:GetElementByName(unitname) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +self:T(self.lid..string.format("EVENT: Element %s unit lost ==> destroyed t=%.3f",element.name,timer.getTime())) +self:ElementDestroyed(element) +end +end +end +function FLIGHTGROUP:onafterElementSpawned(From,Event,To,Element) +self:T(self.lid..string.format("Element spawned %s",Element.name)) +if Element.playerName then +self:_InitPlayerData(Element.playerName) +end +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED) +if Element.unit:InAir(not self.isHelo)then +self:__ElementAirborne(0.11,Element) +else +local spot=self:GetParkingSpot(Element,10) +if spot then +self:__ElementParking(0.11,Element,spot) +else +self:T(self.lid..string.format("Element spawned not in air but not on any parking spot.")) +self:__ElementParking(0.11,Element) +end +end +end +function FLIGHTGROUP:onafterElementParking(From,Event,To,Element,Spot) +if Spot then +self:_SetElementParkingAt(Element,Spot) +end +self:T(self.lid..string.format("Element parking %s at spot %s",Element.name,Element.parking and tostring(Element.parking.TerminalID)or"N/A")) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.PARKING) +if self:IsTakeoffCold()then +elseif self:IsTakeoffHot()then +self:__ElementEngineOn(0.5,Element) +Element.engineOn=true +elseif self:IsTakeoffRunway()then +self:__ElementEngineOn(0.5,Element) +Element.engineOn=true +end +end +function FLIGHTGROUP:onafterElementEngineOn(From,Event,To,Element) +self:T(self.lid..string.format("Element %s started engines",Element.name)) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.ENGINEON) +end +function FLIGHTGROUP:onafterElementTaxiing(From,Event,To,Element) +local TerminalID=Element.parking and tostring(Element.parking.TerminalID)or"N/A" +self:T(self.lid..string.format("Element taxiing %s. Parking spot %s is now free",Element.name,TerminalID)) +self:_SetElementParkingFree(Element) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.TAXIING) +end +function FLIGHTGROUP:onafterElementTakeoff(From,Event,To,Element,airbase) +self:T(self.lid..string.format("Element takeoff %s at %s airbase.",Element.name,airbase and airbase:GetName()or"unknown")) +if Element.parking then +self:_SetElementParkingFree(Element) +end +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.TAKEOFF,airbase) +self:__ElementAirborne(0.01,Element) +end +function FLIGHTGROUP:onafterElementAirborne(From,Event,To,Element) +self:T2(self.lid..string.format("Element airborne %s",Element.name)) +self:_SetElementParkingFree(Element) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.AIRBORNE) +end +function FLIGHTGROUP:onafterElementLanded(From,Event,To,Element,airbase) +self:T2(self.lid..string.format("Element landed %s at %s airbase",Element.name,airbase and airbase:GetName()or"unknown")) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.LANDED,airbase) +if self.isHelo then +local Spot=self:GetParkingSpot(Element,10,airbase) +if Spot then +self:_SetElementParkingAt(Element,Spot) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.ARRIVED) +end +end +if self.despawnAfterLanding then +if self.legion then +if airbase and self.legion.airbase and airbase.AirbaseName==self.legion.airbase.AirbaseName then +if self:IsLanded()then +self:ReturnToLegion() +else +self:DespawnElement(Element) +end +end +else +self:DespawnElement(Element) +end +end +end +function FLIGHTGROUP:onafterElementArrived(From,Event,To,Element,airbase,Parking) +self:T(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d",Element.name,airbase and airbase:GetName()or"unknown",Parking and Parking.TerminalID or-99)) +self:_SetElementParkingAt(Element,Parking) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.ARRIVED) +end +function FLIGHTGROUP:onafterElementDestroyed(From,Event,To,Element) +self:GetParent(self).onafterElementDestroyed(self,From,Event,To,Element) +end +function FLIGHTGROUP:onafterElementDead(From,Event,To,Element) +if self.flightcontrol and Element.parking then +self.flightcontrol:SetParkingFree(Element.parking) +end +self:GetParent(self).onafterElementDead(self,From,Event,To,Element) +Element.parking=nil +end +function FLIGHTGROUP:onafterSpawned(From,Event,To) +self:T(self.lid..string.format("Flight spawned")) +if self.verbose>=1 then +local text=string.format("Initialized Flight Group %s:\n",self.groupname) +text=text..string.format("Unit type = %s\n",tostring(self.actype)) +text=text..string.format("Speed max = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax)) +text=text..string.format("Range max = %.1f km\n",self.rangemax/1000) +text=text..string.format("Ceiling = %.1f feet\n",UTILS.MetersToFeet(self.ceiling)) +text=text..string.format("Weight = %.1f kg\n",self:GetWeightTotal()) +text=text..string.format("Cargo bay = %.1f kg\n",self:GetFreeCargobay()) +text=text..string.format("Tanker type = %s\n",tostring(self.tankertype)) +text=text..string.format("Refuel type = %s\n",tostring(self.refueltype)) +text=text..string.format("AI = %s\n",tostring(self.isAI)) +text=text..string.format("Has EPLRS = %s\n",tostring(self.isEPLRS)) +text=text..string.format("Helicopter = %s\n",tostring(self.isHelo)) +text=text..string.format("Elements = %d\n",#self.elements) +text=text..string.format("Waypoints = %d\n",#self.waypoints) +text=text..string.format("Radio = %.1f MHz %s %s\n",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu),tostring(self.radio.On)) +text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Rockets,self.ammo.Bombs,self.ammo.Missiles) +text=text..string.format("FSM state = %s\n",self:GetState()) +text=text..string.format("Is alive = %s\n",tostring(self.group:IsAlive())) +text=text..string.format("LateActivate = %s\n",tostring(self:IsLateActivated())) +text=text..string.format("Uncontrolled = %s\n",tostring(self:IsUncontrolled())) +text=text..string.format("Start Air = %s\n",tostring(self:IsTakeoffAir())) +text=text..string.format("Start Cold = %s\n",tostring(self:IsTakeoffCold())) +text=text..string.format("Start Hot = %s\n",tostring(self:IsTakeoffHot())) +text=text..string.format("Start Rwy = %s\n",tostring(self:IsTakeoffRunway())) +text=text..string.format("Elements:") +for i,_element in pairs(self.elements)do +local element=_element +text=text..string.format("\n[%d] %s: callsign=%s, modex=%s, player=%s",i,element.name,tostring(element.callsign),tostring(element.modex),tostring(element.playerName)) +end +self:I(self.lid..text) +end +self:_UpdatePosition() +self.isDead=false +self.isDestroyed=false +if self.isAI then +self:SwitchROE(self.option.ROE) +self:SwitchROT(self.option.ROT) +self:SwitchEPLRS(self.option.EPLRS) +self:SwitchInvisible(self.option.Invisible) +self:SwitchImmortal(self.option.Immortal) +self:SwitchFormation(self.option.Formation) +self:_SwitchTACAN() +if self.radioDefault then +self:SwitchRadio() +else +self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,self.radio.On) +end +if self.callsignDefault then +self:SwitchCallsign(self.callsignDefault.NumberSquad,self.callsignDefault.NumberGroup) +else +self:SetDefaultCallsign(self.callsign.NumberSquad,self.callsign.NumberGroup) +end +self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_JETT,self.jettisonWeapons) +self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,self.prohibitAB) +self:GetGroup():SetOption(AI.Option.Air.id.RTB_ON_BINGO,false) +self:GetGroup():SetOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY,self.jettisonEmptyTanks) +self:__UpdateRoute(-0.5) +else +if self.currbase then +local flightcontrol=_DATABASE:GetFlightControl(self.currbase:GetName()) +if flightcontrol then +self:SetFlightControl(flightcontrol) +else +self:_UpdateMenu(0.5) +end +else +self:_UpdateMenu(0.5) +end +end +end +function FLIGHTGROUP:onafterParking(From,Event,To) +local airbase=self:GetClosestAirbase() +local airbasename=airbase:GetName()or"unknown" +self:T(self.lid..string.format("Flight is parking at airbase %s",airbasename)) +self.currbase=airbase +if not self.homebase then +self.homebase=airbase +end +self.Tparking=timer.getAbsTime() +local flightcontrol=_DATABASE:GetFlightControl(airbasename) +if flightcontrol then +self:SetFlightControl(flightcontrol) +if self.flightcontrol then +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.PARKING) +end +else +self:T3(self.lid.."INFO: No flight control in onAfterParking!") +end +end +function FLIGHTGROUP:onafterTaxiing(From,Event,To) +self:T(self.lid..string.format("Flight is taxiing")) +self.Tparking=nil +if self.flightcontrol and self.flightcontrol:IsControlling(self)then +if self.isAI then +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAKEOFF) +else +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAXIOUT) +end +end +end +function FLIGHTGROUP:onafterTakeoff(From,Event,To,airbase) +self:T(self.lid..string.format("Flight takeoff from %s",airbase and airbase:GetName()or"unknown airbase")) +if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName()then +self.flightcontrol:_RemoveFlight(self) +self.flightcontrol=nil +end +end +function FLIGHTGROUP:onafterAirborne(From,Event,To) +self:T(self.lid..string.format("Flight airborne")) +self.currbase=nil +self:__Cruise(-0.01) +end +function FLIGHTGROUP:onafterCruise(From,Event,To) +self:T(self.lid..string.format("Flight cruising")) +self.Twaiting=nil +self.dTwait=nil +if self.isAI then +self:_CheckGroupDone(nil,120) +else +self:_UpdateMenu(0.1) +end +end +function FLIGHTGROUP:onafterLanding(From,Event,To) +self:T(self.lid..string.format("Flight is landing")) +self:_SetElementStatusAll(OPSGROUP.ElementStatus.LANDING) +if self.flightcontrol and self.flightcontrol:IsControlling(self)then +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.LANDING) +end +self.Tholding=nil +if self.stack then +self.stack.flightgroup=nil +self.stack=nil +end +end +function FLIGHTGROUP:onafterLanded(From,Event,To,airbase) +self:T(self.lid..string.format("Flight landed at %s",airbase and airbase:GetName()or"unknown place")) +if self.flightcontrol and self.flightcontrol:IsControlling(self)then +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAXIINB) +end +end +function FLIGHTGROUP:onafterLandedAt(From,Event,To) +self:T(self.lid..string.format("Flight landed at")) +if self:IsPickingup()then +self:__Loading(-1) +elseif self:IsTransporting()then +self:__Unloading(-1) +end +end +function FLIGHTGROUP:onafterArrived(From,Event,To) +self:T(self.lid..string.format("Flight arrived")) +if self.flightcontrol then +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.ARRIVED) +end +if not self.isAI then +return +end +local airwing=self:GetAirwing() +if airwing and not(self:IsPickingup()or self:IsTransporting())then +self:T(self.lid..string.format("Airwing asset group %s arrived ==> Adding asset back to stock of airwing %s",self.groupname,airwing.alias)) +self:ReturnToLegion(1) +elseif self.isLandingAtAirbase then +local Template=UTILS.DeepCopy(self.template) +self.isLateActivated=false +Template.lateActivation=self.isLateActivated +self.isUncontrolled=true +Template.uncontrolled=self.isUncontrolled +local SpawnPoint=Template.route.points[1] +SpawnPoint.linkUnit=nil +SpawnPoint.helipadId=nil +SpawnPoint.airdromeId=nil +local airbase=self.isLandingAtAirbase +local AirbaseID=airbase:GetID() +if airbase:IsShip()then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif airbase:IsHelipad()then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif airbase:IsAirdrome()then +SpawnPoint.airdromeId=AirbaseID +end +SpawnPoint.alt=0 +SpawnPoint.type=COORDINATE.WaypointType.TakeOffParking +SpawnPoint.action=COORDINATE.WaypointAction.FromParkingArea +local units=Template.units +for i=#units,1,-1 do +local unit=units[i] +local element=self:GetElementByName(unit.name) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +unit.parking=element.parking and element.parking.TerminalID or nil +unit.parking_id=nil +local vec3=element.unit:GetVec3() +local heading=element.unit:GetHeading() +unit.x=vec3.x +unit.y=vec3.z +unit.alt=vec3.y +unit.heading=math.rad(heading) +unit.psi=-unit.heading +else +table.remove(units,i) +end +end +self:_Respawn(0,Template) +self.isLandingAtAirbase=nil +if self:IsPickingup()then +self:__Loading(-1) +elseif self:IsTransporting()then +self:__Unloading(-1) +end +else +self:T(self.lid..string.format("Despawning group in 5 minutes after arrival!")) +self:Despawn(5*60) +end +end +function FLIGHTGROUP:onafterDead(From,Event,To) +if self.flightcontrol then +self.flightcontrol:_RemoveFlight(self) +self.flightcontrol=nil +end +self:GetParent(self).onafterDead(self,From,Event,To) +end +function FLIGHTGROUP:onbeforeUpdateRoute(From,Event,To,n,N) +local allowed=true +local trepeat=nil +if self:IsAlive()then +self:T3(self.lid.."Update route possible. Group is ALIVE") +elseif self:IsDead()then +self:T(self.lid.."Update route denied. Group is DEAD!") +allowed=false +elseif self:IsInUtero()then +self:T(self.lid.."Update route denied. Group is INUTERO!") +allowed=false +else +self:T(self.lid.."Update route denied ==> checking back in 5 sec") +trepeat=-5 +allowed=false +end +if allowed and self:IsUncontrolled()then +self:T(self.lid.."Update route denied. Group is UNCONTROLLED!") +local mission=self:GetMissionCurrent() +if mission and mission.type==AUFTRAG.Type.ALERT5 then +trepeat=nil +else +trepeat=-5 +end +allowed=false +end +if n and n<1 then +self:T(self.lid.."Update route denied because waypoint n<1!") +allowed=false +end +if not self.currentwp then +self:T(self.lid.."Update route denied because self.currentwp=nil!") +allowed=false +end +local Nn=n or self.currentwp+1 +if not Nn or Nn<1 then +self:T(self.lid.."Update route denied because N=nil or N<1") +trepeat=-5 +allowed=false +end +if self.taskcurrent>0 then +local task=self:GetTaskByID(self.taskcurrent) +if task then +if task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then +self:T2(self.lid.."Allowing update route for Task: PatrolZone") +elseif task.dcstask.id==AUFTRAG.SpecialTask.CAPTUREZONE then +self:T2(self.lid.."Allowing update route for Task: CaptureZone") +elseif task.dcstask.id==AUFTRAG.SpecialTask.RECON then +self:T2(self.lid.."Allowing update route for Task: ReconMission") +elseif task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then +self:T2(self.lid.."Allowing update route for Task: Patrol Race Track") +elseif task.dcstask.id==AUFTRAG.SpecialTask.HOVER then +self:T2(self.lid.."Allowing update route for Task: Hover") +elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then +self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") +elseif task.description and task.description=="Task_Land_At"then +self:T2(self.lid.."Allowing update route for Task: Task_Land_At") +else +local taskname=task and task.description or"No description" +self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s",self.taskcurrent,tostring(taskname))) +allowed=false +end +else +self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!",self.taskcurrent)) +allowed=false +end +end +if not self.isAI then +allowed=false +end +self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)",self:GetState(),tostring(allowed),tostring(trepeat))) +if trepeat then +self:__UpdateRoute(trepeat,n) +end +return allowed +end +function FLIGHTGROUP:onafterUpdateRoute(From,Event,To,n,N) +n=n or self.currentwp+1 +N=N or#self.waypoints +N=math.min(N,#self.waypoints) +local wp={} +local speed=self.group and self.group:GetVelocityKMH()or 100 +local waypointType=COORDINATE.WaypointType.TurningPoint +local waypointAction=COORDINATE.WaypointAction.TurningPoint +if self:IsLanded()or self:IsLandedAt()or self:IsAirborne()==false then +waypointType=COORDINATE.WaypointType.TakeOff +end +local current=self:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO,waypointType,waypointAction,speed,true,nil,{},"Current") +table.insert(wp,current) +for i=n,N do +table.insert(wp,self.waypoints[i]) +end +if wp[2]then +self.speedWp=wp[2].speed +end +local hb=self.homebase and self.homebase:GetName()or"unknown" +local db=self.destbase and self.destbase:GetName()or"unknown" +self:T(self.lid..string.format("Updating route for WP #%d-%d [%s], homebase=%s destination=%s",n,#wp,self:GetState(),hb,db)) +if#wp>1 then +self:Route(wp) +else +if self:IsAirborne()then +self:T(self.lid.."No waypoints left ==> CheckGroupDone") +self:_CheckGroupDone() +end +end +end +function FLIGHTGROUP:onafterOutOfMissilesAA(From,Event,To) +self:T(self.lid.."Group is out of AA Missiles!") +if self.outofAAMrtb then +local airbase=self.destbase or self.homebase +self:T(self.lid.."Calling RTB in onafterOutOfMissilesAA") +self:__RTB(-5,airbase) +end +end +function FLIGHTGROUP:onafterOutOfMissilesAG(From,Event,To) +self:T(self.lid.."Group is out of AG Missiles!") +if self.outofAGMrtb then +local airbase=self.destbase or self.homebase +self:T(self.lid.."Calling RTB in onafterOutOfMissilesAG") +self:__RTB(-5,airbase) +end +end +function FLIGHTGROUP:_CheckGroupDone(delay,waittime) +local fsmstate=self:GetState() +if self:IsAlive()and self.isAI then +if delay and delay>0 then +self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done in %.3f seconds... (t=%.4f)",fsmstate,delay,timer.getTime())) +self:ScheduleOnce(delay,FLIGHTGROUP._CheckGroupDone,self) +else +self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done? (t=%.4f)",fsmstate,timer.getTime())) +if self:IsEngaging()then +self:T(self.lid.."Engaging! Group NOT done...") +return +end +if self:IsGoing4Fuel()then +self:T(self.lid.."Going for FUEL! Group NOT done...") +return +end +local nTasks=self:CountRemainingTasks() +local nMissions=self:CountRemainingMissison() +local nTransports=self:CountRemainingTransports() +local nPaused=self:_CountPausedMissions() +if nPaused>0 and nPaused==nMissions then +local missionpaused=self:_GetPausedMission() +self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...",missionpaused.name,missionpaused.type)) +self:UnpauseMission() +return +end +if self.isLandingAtAirbase then +self:T(self.lid..string.format("Landing at airbase %s! Group NOT done...",self.isLandingAtAirbase:GetName())) +return +end +if self:IsWaiting()then +self:T(self.lid.."Waiting! Group NOT done...") +return +end +self:T(self.lid..string.format("Remaining (final=%s): missions=%d, tasks=%d, transports=%d",tostring(self.passedfinalwp),nMissions,nTasks,nTransports)) +if self:HasPassedFinalWaypoint()or self:GetWaypointIndexNext()==1 then +if self.currentmission==nil and self.taskcurrent==0 and(self.cargoTransport==nil or self.cargoTransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.DELIVERED)then +if nTasks==0 and nMissions==0 and nTransports==0 then +local destbase=self.destbase or self.homebase +local destzone=self.destzone or self.homezone +if waittime then +self:T(self.lid..string.format("Passed Final WP and No current and/or future missions/tasks/transports. Waittime given ==> Waiting for %d sec!",waittime)) +self:Wait(waittime) +elseif destbase then +if self.currbase and self.currbase.AirbaseName==destbase.AirbaseName and self:IsParking()then +self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports AND parking at destination airbase ==> Arrived!") +self:Arrived() +else +if self.currbase==nil or self.currbase.AirbaseName~=destbase.AirbaseName then +self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") +self:__RTB(-0.1,destbase) +end +end +elseif destzone then +self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") +self:__RTZ(-0.1,destzone) +else +self:T(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") +self:__Wait(-1) +end +else +if not self:IsParking()then +self:T(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!",nTasks,nMissions)) +self:__Wait(-1) +end +end +else +self:T(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do",tostring(self.taskcurrent),tostring(self.currentmission))) +end +else +self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route in -0.01 sec",self:GetState())) +self:__UpdateRoute(-0.01) +end +end +end +end +function FLIGHTGROUP:onbeforeRTB(From,Event,To,airbase,SpeedTo,SpeedHold) +self:T(self.lid..string.format("RTB: before event=%s: %s --> %s to %s",Event,From,To,airbase and airbase:GetName()or"None")) +if self:IsAlive()then +local allowed=true +local Tsuspend=nil +if airbase==nil then +self:T(self.lid.."ERROR: Airbase is nil in RTB() call!") +allowed=false +end +if airbase and airbase:GetCoalition()~=self.group:GetCoalition()and airbase:GetCoalition()>0 then +self:T(self.lid..string.format("ERROR: Wrong airbase coalition %d in RTB() call! We allow only same as group %d or neutral airbases 0",airbase:GetCoalition(),self.group:GetCoalition())) +return false +end +if self.currbase and self.currbase:GetName()==airbase:GetName()then +self:T(self.lid.."WARNING: Currbase is already same as RTB airbase. RTB canceled!") +return false +end +if self:IsLanded()then +self:T(self.lid.."WARNING: Flight has already landed. RTB canceled!") +return false +end +if not self.group:IsAirborne(true)then +self:T(self.lid..string.format("WARNING: Group [%s] is not AIRBORNE ==> RTB event is suspended for 20 sec",self:GetState())) +allowed=false +Tsuspend=-20 +local groupspeed=self.group:GetVelocityMPS() +if groupspeed<=1 and not self:IsParking()then +self.RTBRecallCount=self.RTBRecallCount+1 +end +if self.RTBRecallCount>6 then +self:T(self.lid..string.format("WARNING: Group [%s] is not moving and was called RTB %d times. Assuming a problem and despawning!",self:GetState(),self.RTBRecallCount)) +self.RTBRecallCount=0 +self:Despawn(5) +return +end +end +if self:IsFuelGood()then +local Ntot,Nsched,Nwp=self:CountRemainingTasks() +if self.taskcurrent>0 then +self:T(self.lid..string.format("WARNING: Got current task ==> RTB event is suspended for 10 sec")) +Tsuspend=-10 +allowed=false +end +if Nsched>0 then +self:T(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> RTB event is suspended for 10 sec",Nsched)) +Tsuspend=-10 +allowed=false +end +if Nwp>0 then +self:T(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> RTB event is suspended for 10 sec",Nwp)) +Tsuspend=-10 +allowed=false +end +if self.Twaiting and self.dTwait then +self:T(self.lid..string.format("WARNING: Group is Waiting for a specific duration ==> RTB event is canceled",Nwp)) +allowed=false +end +end +if Tsuspend and not allowed then +self:T(self.lid.."Calling RTB in onbeforeRTB") +self:__RTB(Tsuspend,airbase,SpeedTo,SpeedHold) +end +return allowed +else +self:T(self.lid.."WARNING: Group is not alive! RTB call not allowed.") +return false +end +end +function FLIGHTGROUP:onafterRTB(From,Event,To,airbase,SpeedTo,SpeedHold,SpeedLand) +self:T(self.lid..string.format("RTB: event=%s: %s --> %s to %s",Event,From,To,airbase:GetName())) +self.destbase=airbase +self:CancelAllMissions() +self:_LandAtAirbase(airbase,SpeedTo,SpeedHold,SpeedLand) +end +function FLIGHTGROUP:onbeforeLandAtAirbase(From,Event,To,airbase) +if self:IsAlive()then +local allowed=true +local Tsuspend=nil +if airbase==nil then +self:T(self.lid.."ERROR: Airbase is nil in LandAtAirbase() call!") +allowed=false +end +if airbase and airbase:GetCoalition()~=self.group:GetCoalition()and airbase:GetCoalition()>0 then +self:T(self.lid..string.format("ERROR: Wrong airbase coalition %d in LandAtAirbase() call! We allow only same as group %d or neutral airbases 0",airbase:GetCoalition(),self.group:GetCoalition())) +return false +end +if self.currbase and self.currbase:GetName()==airbase:GetName()then +self:T(self.lid.."WARNING: Currbase is already same as LandAtAirbase airbase. LandAtAirbase canceled!") +return false +end +if self:IsLanded()then +self:T(self.lid.."WARNING: Flight has already landed. LandAtAirbase canceled!") +return false +end +if self:IsParking()then +allowed=false +Tsuspend=-30 +self:T(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 30 sec") +elseif self:IsTaxiing()then +allowed=false +Tsuspend=-1 +self:T(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 1 sec") +end +if Tsuspend and not allowed then +self:__LandAtAirbase(Tsuspend,airbase) +end +return allowed +else +self:T(self.lid.."WARNING: Group is not alive! LandAtAirbase call not allowed") +return false +end +end +function FLIGHTGROUP:onafterLandAtAirbase(From,Event,To,airbase) +self.isLandingAtAirbase=airbase +self:_LandAtAirbase(airbase) +end +function FLIGHTGROUP:_LandAtAirbase(airbase,SpeedTo,SpeedHold,SpeedLand) +self.currbase=airbase +self:_PassedFinalWaypoint(true,"_LandAtAirbase") +self.Twaiting=nil +self.dTwait=nil +SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) +SpeedHold=SpeedHold or(self.isHelo and 80 or 250) +SpeedLand=SpeedLand or(self.isHelo and 40 or 170) +self.Tholding=nil +local text=string.format("Flight group set to hold at airbase %s. SpeedTo=%d, SpeedHold=%d, SpeedLand=%d",airbase:GetName(),SpeedTo,SpeedHold,SpeedLand) +self:T(self.lid..text) +local althold=self.isHelo and 1000+math.random(10)*100 or math.random(4,10)*1000 +local c0=self:GetCoordinate() +local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold)) +local p1=nil +local wpap=nil +local fc=_DATABASE:GetFlightControl(airbase:GetName()) +if fc and self.isAI then +local stack=fc:_GetHoldingStack(self) +if stack then +stack.flightgroup=self +self.stack=stack +p0=stack.pos0 +p1=stack.pos1 +if false then +p0:MarkToAll(string.format("%s: Holding stack P0, alt=%d meters",self:GetName(),p0.y)) +p1:MarkToAll(string.format("%s: Holding stack P1, alt=%d meters",self:GetName(),p0.y)) +end +else +end +self:SetFlightControl(fc) +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.INBOUND) +local callsign=self:GetCallsignName() +local text=string.format("%s, %s, inbound for landing",fc.alias,callsign) +fc:TransmissionPilot(text,self) +local text=string.format("%s, %s, roger, hold at angels %d. Report entering the pattern.",callsign,fc.alias,stack.angels) +fc:TransmissionTower(text,self,10) +end +local c1=c0:GetIntermediateCoordinate(p0,0.25):SetAltitude(self.altitudeCruise,true) +local c2=c0:GetIntermediateCoordinate(p0,0.75):SetAltitude(self.altitudeCruise,true) +local x1=self.isHelo and UTILS.NMToMeters(2.0)or UTILS.NMToMeters(10) +local x2=self.isHelo and UTILS.NMToMeters(1.0)or UTILS.NMToMeters(5) +local alpha=math.rad(3) +local h1=x1*math.tan(alpha) +local h2=x2*math.tan(alpha) +local runway=airbase:GetActiveRunwayLanding() +self.flaghold:Set(0) +local holdtime=self.holdtime +if fc or self.airboss then +holdtime=nil +end +local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding",self) +local TaskOrbit=self.group:TaskOrbit(p0,nil,UTILS.KnotsToMps(SpeedHold),p1) +local TaskLand=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1,nil,holdtime) +local TaskHold=self.group:TaskControlled(TaskOrbit,TaskLand) +local TaskKlar=self.group:TaskFunction("FLIGHTGROUP._ClearedToLand",self) +local wp={} +wp[#wp+1]=c1:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{},"Climb") +wp[#wp+1]=c2:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{},"Descent") +wp[#wp+1]=p0:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{TaskArrived,TaskHold,TaskKlar},"Holding Point") +if airbase:IsAirdrome()then +local TaskFinal=self.group:TaskFunction("FLIGHTGROUP._OnFinal",self) +local rheading +if runway then +rheading=runway.heading-180 +else +local wind=airbase:GetCoordinate():GetWind() +rheading=-wind +end +local papp=airbase:GetCoordinate():Translate(x1,rheading):SetAltitude(h1) +wp[#wp+1]=papp:WaypointAirTurningPoint("BARO",UTILS.KnotsToKmph(SpeedLand),{TaskFinal},"Final Approach") +local pland=airbase:GetCoordinate():Translate(x2,rheading):SetAltitude(h2) +wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand),airbase,{},"Landing") +elseif airbase:IsShip()or airbase:IsHelipad()then +local pland=airbase:GetCoordinate() +wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand),airbase,{},"Landing") +end +if self.isAI then +self:Route(wp,1.0) +end +end +function FLIGHTGROUP:onbeforeWait(From,Event,To,Duration,Altitude,Speed) +local allowed=true +local Tsuspend=nil +if self.taskcurrent>0 and not self:IsLandedAt()then +self:T(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) +Tsuspend=-30 +allowed=false +end +if self.cargoTransport and not self:IsLandedAt()then +end +if Tsuspend and not allowed then +self:__Wait(Tsuspend,Duration,Altitude,Speed) +end +return allowed +end +function FLIGHTGROUP:onafterWait(From,Event,To,Duration,Altitude,Speed) +local Coord=self:GetCoordinate() +if Altitude then +Altitude=UTILS.FeetToMeters(Altitude) +else +Altitude=self.altitudeCruise +end +Speed=Speed or(self.isHelo and 20 or 250) +local text=string.format("Group set to wait/orbit at altitude %d m and speed %.1f km/h for %s seconds",Altitude,Speed,tostring(Duration)) +self:T(self.lid..text) +self.flaghold:Set(0) +local TaskOrbit=self.group:TaskOrbit(Coord,Altitude,UTILS.KnotsToMps(Speed)) +local TaskStop=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1,nil,Duration) +local TaskCntr=self.group:TaskControlled(TaskOrbit,TaskStop) +local TaskOver=self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting",self) +local DCSTasks +if Duration or true then +DCSTasks=self.group:TaskCombo({TaskCntr,TaskOver}) +else +DCSTasks=self.group:TaskCombo({TaskOrbit,TaskOver}) +end +self:PushTask(DCSTasks) +self.Twaiting=timer.getAbsTime() +self.dTwait=Duration +end +function FLIGHTGROUP:onafterRefuel(From,Event,To,Coordinate) +local text=string.format("Flight group set to refuel at the nearest tanker") +self:T(self.lid..text) +self:PauseMission() +local TaskRefuel=self.group:TaskRefueling() +local TaskFunction=self.group:TaskFunction("FLIGHTGROUP._FinishedRefuelling",self) +local DCSTasks={TaskRefuel,TaskFunction} +local Speed=self.speedCruise +local coordinate=self:GetCoordinate() +Coordinate=Coordinate or coordinate:Translate(UTILS.NMToMeters(5),self.group:GetHeading(),true) +local wp0=coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true) +local wp9=Coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,"Refuel") +self:Route({wp0,wp9},1) +self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO,true) +end +function FLIGHTGROUP:onafterRefueled(From,Event,To) +local text=string.format("Flight group finished refuelling") +self:T(self.lid..text) +self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO,false) +self:_CheckGroupDone(1) +end +function FLIGHTGROUP:onafterHolding(From,Event,To) +self.flaghold:Set(0) +if self.despawnAfterHolding then +if self.legion then +self:ReturnToLegion(1) +else +self:Despawn(1) +end +return +end +self.Tholding=timer.getAbsTime() +local text=string.format("Flight group %s is HOLDING now",self.groupname) +self:T(self.lid..text) +if self.flightcontrol then +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.HOLDING) +if self.isAI then +local callsign=self:GetCallsignName() +local text=string.format("%s, %s, arrived at holding pattern",self.flightcontrol.alias,callsign) +if self.stack then +text=text..string.format(", angels %d.",self.stack.angels) +end +self.flightcontrol:TransmissionPilot(text,self) +local text=string.format("%s, roger, fly heading %d and wait for landing clearance",callsign,self.stack.heading) +self.flightcontrol:TransmissionTower(text,self,10) +end +elseif self.airboss then +if self.isHelo then +local carrierpos=self.airboss:GetCoordinate() +local carrierheading=self.airboss:GetHeading() +local Distance=UTILS.NMToMeters(5) +local Angle=carrierheading+90 +local altitude=math.random(12,25)*100 +local oc=carrierpos:Translate(Distance,Angle):SetAltitude(altitude,true) +local TaskOrbit=self.group:TaskOrbit(oc,nil,UTILS.KnotsToMps(50)) +local TaskLand=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1) +local TaskHold=self.group:TaskControlled(TaskOrbit,TaskLand) +local TaskKlar=self.group:TaskFunction("FLIGHTGROUP._ClearedToLand",self) +local DCSTask=self.group:TaskCombo({TaskOrbit,TaskHold,TaskKlar}) +self:SetTask(DCSTask) +end +end +end +function FLIGHTGROUP:onafterEngageTarget(From,Event,To,Target) +local DCStask=nil +if Target:IsInstanceOf("UNIT")or Target:IsInstanceOf("STATIC")then +DCStask=self:GetGroup():TaskAttackUnit(Target,true) +elseif Target:IsInstanceOf("GROUP")then +DCStask=self:GetGroup():TaskAttackGroup(Target,nil,nil,nil,nil,nil,nil,true) +elseif Target:IsInstanceOf("SET_UNIT")then +local DCSTasks={} +for _,_unit in pairs(Target:GetSet())do +local unit=_unit +local task=self:GetGroup():TaskAttackUnit(unit,true) +table.insert(DCSTasks) +end +DCStask=self:GetGroup():TaskCombo(DCSTasks) +elseif Target:IsInstanceOf("SET_GROUP")then +local DCSTasks={} +for _,_unit in pairs(Target:GetSet())do +local unit=_unit +local task=self:GetGroup():TaskAttackGroup(Target,nil,nil,nil,nil,nil,nil,true) +table.insert(DCSTasks) +end +DCStask=self:GetGroup():TaskCombo(DCSTasks) +else +self:T("ERROR: unknown Target in EngageTarget! Needs to be a UNIT, STATIC, GROUP, SET_UNIT or SET_GROUP") +return +end +local Task=self:NewTaskScheduled(DCStask,1,"Engage_Target",0) +Task.backupROE=self:GetROE() +self:SwitchROE(ENUMS.ROE.OpenFire) +local mission=self:GetMissionCurrent() +if mission then +self:PauseMission() +end +self:TaskExecute(Task) +end +function FLIGHTGROUP:onafterDisengage(From,Event,To) +self:T(self.lid.."Disengage target") +end +function FLIGHTGROUP:onbeforeLandAt(From,Event,To,Coordinate,Duration) +return self.isHelo +end +function FLIGHTGROUP:onafterLandAt(From,Event,To,Coordinate,Duration) +self:T(self.lid..string.format("Landing at Coordinate for %s seconds",tostring(Duration))) +Coordinate=Coordinate or self:GetCoordinate() +local DCStask=self.group:TaskLandAtVec2(Coordinate:GetVec2(),Duration) +local Task=self:NewTaskScheduled(DCStask,1,"Task_Land_At",0) +self:TaskExecute(Task) +end +function FLIGHTGROUP:onafterFuelLow(From,Event,To) +local fuel=self:GetFuelMin()or 0 +local text=string.format("Low fuel %d for flight group %s",fuel,self.groupname) +self:T(self.lid..text) +self.fuellow=true +local airbase=self.destbase or self.homebase +if self.fuellowrefuel and self.refueltype then +local tanker=self:FindNearestTanker(50) +if tanker then +self:T(self.lid..string.format("Send to refuel at tanker %s",tanker:GetName())) +local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker:GetCoordinate(),0.75) +self:Refuel(coordinate) +return +end +end +if airbase and self.fuellowrtb then +self:T(self.lid.."Calling RTB in onafterFuelLow") +self:RTB(airbase) +end +end +function FLIGHTGROUP:onafterFuelCritical(From,Event,To) +local text=string.format("Critical fuel for flight group %s",self.groupname) +self:T(self.lid..text) +self.fuelcritical=true +local airbase=self.destbase or self.homebase +if airbase and self.fuelcriticalrtb and not self:IsGoing4Fuel()then +self:T(self.lid.."Calling RTB in onafterFuelCritical") +self:RTB(airbase) +end +end +function FLIGHTGROUP._ReachedHolding(group,flightgroup) +flightgroup:T2(flightgroup.lid..string.format("Group reached holding point")) +flightgroup:__Holding(-1) +end +function FLIGHTGROUP._ClearedToLand(group,flightgroup) +flightgroup:T2(flightgroup.lid..string.format("Group was cleared to land")) +flightgroup:__Landing(-1) +end +function FLIGHTGROUP._OnFinal(group,flightgroup) +flightgroup:T2(flightgroup.lid..string.format("Group on final approach")) +local fc=flightgroup.flightcontrol +if fc and fc:IsControlling(flightgroup)then +fc:_FlightOnFinal(flightgroup) +end +end +function FLIGHTGROUP._FinishedRefuelling(group,flightgroup) +flightgroup:T2(flightgroup.lid..string.format("Group finished refueling")) +flightgroup:__Refueled(-1) +end +function FLIGHTGROUP._FinishedWaiting(group,flightgroup) +flightgroup:T(flightgroup.lid..string.format("Group finished waiting")) +flightgroup.Twaiting=nil +flightgroup.dTwait=nil +flightgroup:_CheckGroupDone(0.1) +end +function FLIGHTGROUP:_InitGroup(Template,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,FLIGHTGROUP._InitGroup,self,Template,0) +else +if self.groupinitialized then +self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") +return +end +local group=self.group +self.isHelo=group:IsHelicopter() +self.speedMax=group:GetSpeedMax() +if self.speedMax and self.speedMax>3.6 then +self.isMobile=true +else +self.isMobile=false +self.speedMax=0 +end +local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(110)or UTILS.KnotsToKmph(380) +self.speedCruise=math.min(self.speedMax*0.7,speedCruiseLimit) +self.ammo=self:GetAmmoTot() +local template=Template or self:_GetTemplate() +self.isUncontrolled=template~=nil and template.uncontrolled or false +self.isLateActivated=template~=nil and template.lateActivation or false +if template then +self.radio.Freq=tonumber(template.frequency) +self.radio.Modu=tonumber(template.modulation) +self.radio.On=template.communication +local callsign=template.units[1].callsign +if type(callsign)=="number"then +local cs=tostring(callsign) +callsign={} +callsign[1]=cs:sub(1,1) +callsign[2]=cs:sub(2,2) +callsign[3]=cs:sub(3,3) +end +self.callsign.NumberSquad=tonumber(callsign[1]) +self.callsign.NumberGroup=tonumber(callsign[2]) +self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) +end +if self.isHelo then +self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 +else +self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group +end +if not self.tacanDefault then +self:SetDefaultTACAN(nil,nil,nil,nil,true) +end +if not self.tacan then +self.tacan=UTILS.DeepCopy(self.tacanDefault) +end +self.isAI=not self:_IsHuman(group) +if not self.isAI then +self.menu=self.menu or{} +self.menu.atc=self.menu.atc or{} +self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group,"ATC") +self.menu.atc.help=self.menu.atc.help or MENU_GROUP:New(self.group,"Help",self.menu.atc.root) +end +local units=self.group:GetUnits() +local dcsgroup=Group.getByName(self.groupname) +local size0=dcsgroup:getInitialSize() +if#units~=size0 then +self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!",#units,size0)) +end +for _,unit in pairs(units)do +self:_AddElementByName(unit:GetName()) +end +self.groupinitialized=true +end +return self +end +function FLIGHTGROUP:GetHomebaseFromWaypoints() +local wp=self.waypoints0 and self.waypoints0[1]or nil +if wp then +if wp and wp.action and wp.action==COORDINATE.WaypointAction.FromParkingArea +or wp.action==COORDINATE.WaypointAction.FromParkingAreaHot +or wp.action==COORDINATE.WaypointAction.FromRunway then +local airbaseID=nil +if wp.airdromeId then +airbaseID=wp.airdromeId +else +airbaseID=-wp.helipadId +end +local airbase=AIRBASE:FindByID(airbaseID) +return airbase +end +end +return nil +end +function FLIGHTGROUP:FindNearestAirbase(Radius) +local coord=self:GetCoordinate() +local dmin=math.huge +local airbase=nil +for _,_airbase in pairs(AIRBASE.GetAllAirbases())do +local ab=_airbase +local coalitionAB=ab:GetCoalition() +if coalitionAB==self:GetCoalition()or coalitionAB==coalition.side.NEUTRAL then +if airbase then +local d=ab:GetCoordinate():Get2DDistance(coord) +if dself.currentwp then +self:_PassedFinalWaypoint(false,"AddWaypointLanding") +end +Speed=Speed or self.speedCruise +local Coordinate=Airbase:GetCoordinate() +local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO,COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,Speed,nil,Airbase,{},"Landing Temp",nil) +local waypoint=self:_CreateWaypoint(wp) +if Altitude then +waypoint.alt=UTILS.FeetToMeters(Altitude) +end +self:_AddWaypoint(waypoint,wpnumber) +self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d",wpnumber,Speed,self.currentwp,#self.waypoints)) +if Updateroute==nil or Updateroute==true then +self:__UpdateRoute(-1) +end +return waypoint +end +function FLIGHTGROUP:GetPlayerElement() +for _,_element in pairs(self.elements)do +local element=_element +if not element.ai then +return element +end +end +return nil +end +function FLIGHTGROUP:GetPlayerName() +local playerElement=self:GetPlayerElement() +if playerElement then +return playerElement.playerName +end +return nil +end +function FLIGHTGROUP:_SetElementParkingAt(Element,Spot) +Element.parking=Spot +if Spot then +self:T(self.lid..string.format("Element %s is parking on spot %d",Element.name,Spot.TerminalID)) +local fc=_DATABASE:GetFlightControl(Spot.AirbaseName) +if fc and not self.flightcontrol then +self:SetFlightControl(fc) +end +if self.flightcontrol then +self.flightcontrol:SetParkingOccupied(Element.parking,Element.name) +end +end +end +function FLIGHTGROUP:_SetElementParkingFree(Element) +if Element.parking then +if self.flightcontrol then +self.flightcontrol:SetParkingFree(Element.parking) +end +Element.parking=nil +end +end +function FLIGHTGROUP:_GetOnboardNumber(unitname) +local group=UNIT:FindByName(unitname):GetGroup() +local units=group:GetTemplate().units +local numbers={} +for _,unit in pairs(units)do +if unitname==unit.name then +return tostring(unit.onboard_num) +end +end +return nil +end +function FLIGHTGROUP:_IsHumanUnit(unit) +local playerunit=self:_GetPlayerUnitAndName(unit:GetName()) +if playerunit then +return true +else +return false +end +end +function FLIGHTGROUP:_IsHuman(group) +local units=group:GetUnits() +for _,_unit in pairs(units)do +local human=self:_IsHumanUnit(_unit) +if human then +return true +end +end +return false +end +function FLIGHTGROUP:_GetPlayerUnitAndName(_unitName) +self:F2(_unitName) +if _unitName~=nil then +local DCSunit=Unit.getByName(_unitName) +if DCSunit then +local playername=DCSunit:getPlayerName() +local unit=UNIT:Find(DCSunit) +if DCSunit and unit and playername then +return unit,playername +end +end +end +return nil,nil +end +function FLIGHTGROUP:GetParkingSpot(element,maxdist,airbase) +local coord=element.unit:GetCoordinate() +airbase=airbase or self:GetClosestAirbase() +if airbase==nil then +self:T(self.lid.."No airbase found for element "..element.name) +return nil +end +local parking=airbase.parking +if airbase and airbase:IsShip()then +if#parking>1 then +coord=airbase:GetRelativeCoordinate(coord.x,coord.y,coord.z) +else +coord.x=0 +coord.z=0 +maxdist=500 +end +end +local spot=nil +local dist=nil +local distmin=math.huge +for _,_parking in pairs(parking)do +local parking=_parking +dist=coord:Get2DDistance(parking.Coordinate) +if distsafedist) +return safe +end +local function _clients() +local clients=_DATABASE.CLIENTS +local coords={} +for clientname,client in pairs(clients)do +local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) +if template then +local units=template.units +for i,unit in pairs(units)do +local coord=COORDINATE:New(unit.x,unit.alt,unit.y) +coords[unit.name]=coord +end +end +end +return coords +end +local airbasecategory=airbase:GetAirbaseCategory() +local parkingdata=airbase:GetParkingSpotsTable() +local obstacles={} +for _,_parkingspot in pairs(parkingdata)do +local parkingspot=_parkingspot +local _,_,_,_units,_statics,_sceneries=parkingspot.Coordinate:ScanObjects(scanradius,scanunits,scanstatics,scanscenery) +for _,_unit in pairs(_units)do +local unit=_unit +local _coord=unit:GetCoordinate() +local _size=self:_GetObjectSize(unit:GetDCSObject()) +local _name=unit:GetName() +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="unit"}) +end +local clientcoords=_clients() +for clientname,_coord in pairs(clientcoords)do +table.insert(obstacles,{coord=_coord,size=15,name=clientname,type="client"}) +end +for _,static in pairs(_statics)do +local _vec3=static:getPoint() +local _coord=COORDINATE:NewFromVec3(_vec3) +local _name=static:getName() +local _size=self:_GetObjectSize(static) +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="static"}) +end +for _,scenery in pairs(_sceneries)do +local _vec3=scenery:getPoint() +local _coord=COORDINATE:NewFromVec3(_vec3) +local _name=scenery:getTypeName() +local _size=self:_GetObjectSize(scenery) +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="scenery"}) +end +end +local parking={} +local terminaltype=self:_GetTerminal(self.attribute,airbase:GetAirbaseCategory()) +for i,_element in pairs(self.elements)do +local element=_element +local gotit=false +for _,_parkingspot in pairs(parkingdata)do +local parkingspot=_parkingspot +if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)then +local free=true +local problem=nil +if verysafe and parkingspot.TOAC then +free=false +self:T2(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC).",parkingspot.TerminalID)) +end +for _,obstacle in pairs(obstacles)do +local dist=parkingspot.Coordinate:Get2DDistance(obstacle.coord) +local safe=_overlap(element.size,obstacle.size,dist) +if not safe then +free=false +problem=obstacle +problem.dist=dist +break +end +end +if self.flightcontrol and self.flightcontrol.airbasename==airbase:GetName()then +local problem=self.flightcontrol:IsParkingReserved(parkingspot)or self.flightcontrol:IsParkingOccupied(parkingspot) +if problem then +free=false +end +end +if free then +table.insert(parking,parkingspot) +self:T2(self.lid..string.format("Parking spot %d is free for element %s!",parkingspot.TerminalID,element.name)) +table.insert(obstacles,{coord=parkingspot.Coordinate,size=element.size,name=element.name,type="element"}) +gotit=true +break +else +self:T2(self.lid..string.format("Parking spot %d is occupied or not big enough!",parkingspot.TerminalID)) +end +end +end +if not gotit then +self:T(self.lid..string.format("WARNING: No free parking spot for element %s",element.name)) +return nil +end +end +return parking +end +function FLIGHTGROUP:_GetObjectSize(DCSobject) +local DCSdesc=DCSobject:getDesc() +if DCSdesc.box then +local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) +local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) +local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) +return math.max(x,z),x,y,z +end +return 0,0,0,0 +end +function FLIGHTGROUP:_GetAttribute() +local attribute=FLIGHTGROUP.Attribute.OTHER +local group=self.group +if group then +local transportplane=group:HasAttribute("Transports")and group:HasAttribute("Planes") +local awacs=group:HasAttribute("AWACS") +local fighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers")) +local bomber=group:HasAttribute("Strategic bombers") +local tanker=group:HasAttribute("Tankers") +local uav=group:HasAttribute("UAVs") +local transporthelo=group:HasAttribute("Transport helicopters") +local attackhelicopter=group:HasAttribute("Attack helicopters") +if transportplane then +attribute=FLIGHTGROUP.Attribute.AIR_TRANSPORTPLANE +elseif awacs then +attribute=FLIGHTGROUP.Attribute.AIR_AWACS +elseif fighter then +attribute=FLIGHTGROUP.Attribute.AIR_FIGHTER +elseif bomber then +attribute=FLIGHTGROUP.Attribute.AIR_BOMBER +elseif tanker then +attribute=FLIGHTGROUP.Attribute.AIR_TANKER +elseif transporthelo then +attribute=FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO +elseif attackhelicopter then +attribute=FLIGHTGROUP.Attribute.AIR_ATTACKHELO +elseif uav then +attribute=FLIGHTGROUP.Attribute.AIR_UAV +end +end +return attribute +end +function FLIGHTGROUP:_GetTerminal(_attribute,_category) +local _terminal=AIRBASE.TerminalType.OpenBig +if _attribute==FLIGHTGROUP.Attribute.AIR_FIGHTER or _attribute==FLIGHTGROUP.Attribute.AIR_UAV then +_terminal=AIRBASE.TerminalType.FighterAircraft +elseif _attribute==FLIGHTGROUP.Attribute.AIR_BOMBER or _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTPLANE or _attribute==FLIGHTGROUP.Attribute.AIR_TANKER or _attribute==FLIGHTGROUP.Attribute.AIR_AWACS then +_terminal=AIRBASE.TerminalType.OpenBig +elseif _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO then +_terminal=AIRBASE.TerminalType.HelicopterUsable +else +end +if _category==Airbase.Category.SHIP then +if not(_attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO)then +_terminal=AIRBASE.TerminalType.OpenMedOrBig +end +end +return _terminal +end +function FLIGHTGROUP:_CheckStuck(Despawn) +if not self:IsTaxiing()then +return nil +end +local Tnow=timer.getTime() +local ExpectedSpeed=5 +local speed=self:GetVelocity() +if speed<0.1 then +if ExpectedSpeed>0 and not self.stuckTimestamp then +self:T2(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected",speed,ExpectedSpeed)) +self.stuckTimestamp=Tnow +self.stuckVec3=self:GetVec3() +end +else +self.stuckTimestamp=nil +end +local holdtime=nil +if self.stuckTimestamp then +holdtime=Tnow-self.stuckTimestamp +self:Stuck(holdtime) +if holdtime>=5*60 and holdtime<15*60 then +self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime)) +elseif holdtime>=15*60 then +self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime)) +local mission=self:GetMissionCurrent() +if mission then +self:T(self.lid..string.format("WARNING: Cancelling mission %s [%s] due to being stuck",mission:GetName(),mission:GetType())) +self:MissionCancel(mission) +end +if self.stuckDespawn then +if self.legion then +self:T(self.lid..string.format("Asset is returned to its legion after being stuck!")) +self:ReturnToLegion() +else +self:T(self.lid..string.format("Despawning group after being stuck!")) +self:Despawn() +end +end +end +end +return holdtime +end +function FLIGHTGROUP:_UpdateMenu(delay) +if delay and delay>0 then +self:ScheduleOnce(delay,FLIGHTGROUP._UpdateMenu,self) +else +local player=self:GetPlayerElement() +if player and player.status~=OPSGROUP.ElementStatus.DEAD then +if self.verbose>=2 then +local text=string.format("Updating MENU: State=%s, ATC=%s [%s]",self:GetState(), +self.flightcontrol and self.flightcontrol.airbasename or"None",self.flightcontrol and self.flightcontrol:GetFlightStatus(self)or"Unknown") +MESSAGE:New(text,5):ToGroup(self.group) +self:T(self.lid..text) +end +local position=self:GetCoordinate(nil,player.name) +local fc={} +for airbasename,_flightcontrol in pairs(_DATABASE.FLIGHTCONTROLS)do +local flightcontrol=_flightcontrol +local coord=flightcontrol:GetCoordinate() +local dist=coord:Get2DDistance(position) +table.insert(fc,{airbasename=airbasename,dist=dist}) +end +local function _sort(a,b) +return a.dist=1 then +local fsmstate=self:GetState() +local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName)or"N/A" +local skill=self.skill and tostring(self.skill)or"N/A" +local NassetsTot=#self.assets +local NassetsInS=self:CountAssets(true) +local NassetsQP=0;local NassetsP=0;local NassetsQ=0 +if self.legion then +NassetsQP,NassetsP,NassetsQ=self.legion:CountAssetsOnMission(nil,self) +end +local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]", +fsmstate,self.aircrafttype,callsign,skill,NassetsTot,NassetsInS,NassetsQP,NassetsP,NassetsQ) +self:T(self.lid..text) +if self.verbose>=3 and self.weaponData then +local text="Weapon Data:" +for bit,_weapondata in pairs(self.weaponData)do +local weapondata=_weapondata +text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km",bit,weapondata.RangeMin/1000,weapondata.RangeMax/1000) +end +self:I(self.lid..text) +end +self:_CheckAssetStatus() +end +if not self:IsStopped()then +self:__Status(-60) +end +end +INTEL={ +ClassName="INTEL", +verbose=0, +lid=nil, +alias=nil, +filterCategory={}, +detectionset=nil, +Contacts={}, +ContactsLost={}, +ContactsUnknown={}, +Clusters={}, +clustercounter=1, +clusterradius=15000, +clusteranalysis=true, +clustermarkers=false, +clusterarrows=false, +prediction=300, +detectStatics=false, +} +INTEL.Ctype={ +GROUND="Ground", +NAVAL="Naval", +AIRCRAFT="Aircraft", +STRUCTURE="Structure" +} +INTEL.version="0.3.6" +function INTEL:New(DetectionSet,Coalition,Alias) +local self=BASE:Inherit(self,FSM:New()) +self.detectionset=DetectionSet or SET_GROUP:New() +if Coalition and type(Coalition)=="string"then +if Coalition=="blue"then +Coalition=coalition.side.BLUE +elseif Coalition=="red"then +Coalition=coalition.side.RED +elseif Coalition=="neutral"then +Coalition=coalition.side.NEUTRAL +else +self:E("ERROR: Unknown coalition in INTEL!") +end +end +self.coalition=Coalition or DetectionSet:CountAlive()>0 and DetectionSet:GetFirst():GetCoalition()or nil +if self.coalition then +local coalitionname=UTILS.GetCoalitionName(self.coalition):lower() +self.detectionset:FilterCoalitions(coalitionname) +end +self.detectionset:FilterOnce() +if Alias then +self.alias=tostring(Alias) +else +self.alias="INTEL SPECTRE" +if self.coalition then +if self.coalition==coalition.side.RED then +self.alias="INTEL KGB" +elseif self.coalition==coalition.side.BLUE then +self.alias="INTEL CIA" +end +end +end +self.DetectVisual=true +self.DetectOptical=true +self.DetectRadar=true +self.DetectIRST=true +self.DetectRWR=true +self.DetectDLINK=true +self.statusupdate=-60 +self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("*","Detect","*") +self:AddTransition("*","NewContact","*") +self:AddTransition("*","LostContact","*") +self:AddTransition("*","NewCluster","*") +self:AddTransition("*","LostCluster","*") +self:SetForgetTime() +self:SetAcceptZones() +self:SetRejectZones() +self:SetConflictZones() +return self +end +function INTEL:SetAcceptZones(AcceptZoneSet) +self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddAcceptZone(AcceptZone) +self.acceptzoneset:AddZone(AcceptZone) +return self +end +function INTEL:RemoveAcceptZone(AcceptZone) +self.acceptzoneset:Remove(AcceptZone:GetName(),true) +return self +end +function INTEL:SetRejectZones(RejectZoneSet) +self.rejectzoneset=RejectZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddRejectZone(RejectZone) +self.rejectzoneset:AddZone(RejectZone) +return self +end +function INTEL:RemoveRejectZone(RejectZone) +self.rejectzoneset:Remove(RejectZone:GetName(),true) +return self +end +function INTEL:SetConflictZones(ConflictZoneSet) +self.conflictzoneset=ConflictZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddConflictZone(ConflictZone) +self.conflictzoneset:AddZone(ConflictZone) +return self +end +function INTEL:RemoveConflictZone(ConflictZone) +self.conflictzoneset:Remove(ConflictZone:GetName(),true) +return self +end +function INTEL:SetForgetTime(TimeInterval) +return self +end +function INTEL:SetFilterCategory(Categories) +if type(Categories)~="table"then +Categories={Categories} +end +self.filterCategory=Categories +local text="Filter categories: " +for _,category in pairs(self.filterCategory)do +text=text..string.format("%d,",category) +end +self:T(self.lid..text) +return self +end +function INTEL:SetRadarBlur(minheight,thresheight,thresblur,closing) +self.RadarBlur=true +self.RadarBlurMinHeight=minheight or 250 +self.RadarBlurThresHeight=thresheight or 90 +self.RadarBlurThresBlur=thresblur or 85 +self.RadarBlurClosing=closing or 20 +self.RadarBlurClosingSquare=self.RadarBlurClosing*self.RadarBlurClosing +return self +end +function INTEL:SetAcceptRange(Range) +self.RadarAcceptRange=true +self.RadarAcceptRangeKilometers=Range or 75 +return self +end +function INTEL:FilterCategoryGroup(GroupCategories) +if type(GroupCategories)~="table"then +GroupCategories={GroupCategories} +end +self.filterCategoryGroup=GroupCategories +local text="Filter group categories: " +for _,category in pairs(self.filterCategoryGroup)do +text=text..string.format("%d,",category) +end +self:T(self.lid..text) +return self +end +function INTEL:AddAgent(AgentGroup) +if AgentGroup:IsInstanceOf("OPSGROUP")then +AgentGroup=AgentGroup:GetGroup() +end +self.detectionset:AddGroup(AgentGroup,true) +return self +end +function INTEL:SetClusterAnalysis(Switch,Markers,Arrows) +self.clusteranalysis=Switch +self.clustermarkers=Markers +self.clusterarrows=Arrows +return self +end +function INTEL:SetDetectStatics(Switch) +if Switch and Switch==true then +self.detectStatics=true +else +self.detectStatics=false +end +return self +end +function INTEL:SetVerbosity(Verbosity) +self.verbose=Verbosity or 2 +return self +end +function INTEL:AddMissionToContact(Contact,Mission) +if Mission and Contact then +Contact.mission=Mission +end +return self +end +function INTEL:AddMissionToCluster(Cluster,Mission) +if Mission and Cluster then +Cluster.mission=Mission +end +return self +end +function INTEL:SetClusterRadius(radius) +self.clusterradius=(radius or 15)*1000 +return self +end +function INTEL:SetDetectionTypes(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +self.DetectVisual=DetectVisual and true +self.DetectOptical=DetectOptical and true +self.DetectRadar=DetectRadar and true +self.DetectIRST=DetectIRST and true +self.DetectRWR=DetectRWR and true +self.DetectDLINK=DetectDLINK and true +return self +end +function INTEL:GetContactTable() +if self:Is("Running")then +return self.Contacts +else +return nil +end +end +function INTEL:GetClusterTable() +if self:Is("Running")and self.clusteranalysis then +return self.Clusters +else +return nil +end +end +function INTEL:GetContactName(Contact) +return Contact.groupname +end +function INTEL:GetContactGroup(Contact) +return Contact.group +end +function INTEL:GetContactThreatlevel(Contact) +return Contact.threatlevel +end +function INTEL:GetContactTypeName(Contact) +return Contact.typename +end +function INTEL:GetContactCategoryName(Contact) +return Contact.categoryname +end +function INTEL:GetContactCoordinate(Contact) +return Contact.position +end +function INTEL:onafterStart(From,Event,To) +local text=string.format("Starting INTEL v%s",self.version) +self:I(self.lid..text) +self:__Status(-math.random(10)) +return self +end +function INTEL:onafterStatus(From,Event,To) +local fsmstate=self:GetState() +self.ContactsLost={} +self.ContactsUnknown={} +self:UpdateIntel() +local Ncontacts=#self.Contacts +local Nclusters=#self.Clusters +if self.verbose>=1 then +local text=string.format("Status %s [Agents=%s]: Contacts=%d, Clusters=%d, New=%d, Lost=%d",fsmstate,self.detectionset:CountAlive(),Ncontacts,Nclusters,#self.ContactsUnknown,#self.ContactsLost) +self:I(self.lid..text) +end +if self.verbose>=2 and Ncontacts>0 then +local text="Detected Contacts:" +for _,_contact in pairs(self.Contacts)do +local contact=_contact +local dT=timer.getAbsTime()-contact.Tdetected +text=text..string.format("\n- %s (%s): %s, units=%d, T=%d sec",contact.categoryname,contact.attribute,contact.groupname,contact.isStatic and 1 or contact.group:CountAliveUnits(),dT) +if contact.mission then +local mission=contact.mission +text=text..string.format(" mission name=%s type=%s target=%s",mission.name,mission.type,mission:GetTargetName()or"unknown") +end +end +self:I(self.lid..text) +end +self:__Status(self.statusupdate) +return self +end +function INTEL:UpdateIntel() +local DetectedUnits={} +local RecceDetecting={} +for _,_group in pairs(self.detectionset.Set or{})do +local group=_group +if group and group:IsAlive()then +for _,_recce in pairs(group:GetUnits())do +local recce=_recce +self:GetDetectedUnits(recce,DetectedUnits,RecceDetecting,self.DetectVisual,self.DetectOptical,self.DetectRadar,self.DetectIRST,self.DetectRWR,self.DetectDLINK) +end +end +end +local remove={} +for unitname,_unit in pairs(DetectedUnits)do +local unit=_unit +local inconflictzone=false +if self.conflictzoneset:Count()>0 then +for _,_zone in pairs(self.conflictzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +inconflictzone=true +break +end +end +end +if self.acceptzoneset:Count()>0 then +local inzone=false +for _,_zone in pairs(self.acceptzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +inzone=true +break +end +end +if(not inzone)and(not inconflictzone)then +table.insert(remove,unitname) +end +end +if self.rejectzoneset:Count()>0 then +local inzone=false +for _,_zone in pairs(self.rejectzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +inzone=true +break +end +end +if inzone and(not inconflictzone)then +table.insert(remove,unitname) +end +end +if#self.filterCategory>0 and unit:IsInstanceOf("UNIT")then +local unitcategory=unit:GetUnitCategory() +local keepit=false +for _,filtercategory in pairs(self.filterCategory)do +if unitcategory==filtercategory then +keepit=true +break +end +end +if not keepit then +self:T(self.lid..string.format("Removing unit %s category=%d",unitname,unit:GetCategory())) +table.insert(remove,unitname) +end +end +end +for _,unitname in pairs(remove)do +DetectedUnits[unitname]=nil +end +local DetectedGroups={} +local DetectedStatics={} +local RecceGroups={} +for unitname,_unit in pairs(DetectedUnits)do +local unit=_unit +if unit:IsInstanceOf("UNIT")then +local group=unit:GetGroup() +if group then +local groupname=group:GetName() +DetectedGroups[groupname]=group +RecceGroups[groupname]=RecceDetecting[unitname] +end +else +if self.detectStatics then +DetectedStatics[unitname]=unit +RecceGroups[unitname]=RecceDetecting[unitname] +end +end +end +self:CreateDetectedItems(DetectedGroups,DetectedStatics,RecceGroups) +if self.clusteranalysis then +self:PaintPicture() +end +return self +end +function INTEL:_UpdateContact(Contact) +if Contact.isStatic then +else +if Contact.group and Contact.group:IsAlive()then +Contact.Tdetected=timer.getAbsTime() +Contact.position=Contact.group:GetCoordinate() +Contact.velocity=Contact.group:GetVelocityVec3() +Contact.speed=Contact.group:GetVelocityMPS() +if Contact.group:IsAir()then +Contact.altitude=Contact.group:GetAltitude() +local oldheading=Contact.heading or 1 +local newheading=Contact.group:GetHeading() +if newheading==0 then newheading=1 end +local changeh=math.abs(((oldheading-newheading)+360)%360) +Contact.heading=newheading +if changeh>10 then +Contact.maneuvering=true +else +Contact.maneuvering=false +end +end +end +end +return self +end +function INTEL:_CreateContact(Positionable,RecceName) +if Positionable and Positionable:IsAlive()then +local item={} +if Positionable:IsInstanceOf("GROUP")then +local group=Positionable +item.groupname=group:GetName() +item.group=group +item.Tdetected=timer.getAbsTime() +item.typename=group:GetTypeName() +item.attribute=group:GetAttribute() +item.category=group:GetCategory() +item.categoryname=group:GetCategoryName() +item.threatlevel=group:GetThreatLevel() +item.position=group:GetCoordinate() +item.velocity=group:GetVelocityVec3() +item.speed=group:GetVelocityMPS() +item.recce=RecceName +item.isground=group:IsGround()or false +item.isship=group:IsShip()or false +item.isStatic=false +if group:IsAir()then +item.platform=group:GetNatoReportingName() +item.heading=group:GetHeading() +item.maneuvering=false +item.altitude=group:GetAltitude() +else +item.platform="Unknown" +item.altitude=group:GetAltitude(true) +end +if item.category==Group.Category.AIRPLANE or item.category==Group.Category.HELICOPTER then +item.ctype=INTEL.Ctype.AIRCRAFT +elseif item.category==Group.Category.GROUND or item.category==Group.Category.TRAIN then +item.ctype=INTEL.Ctype.GROUND +elseif item.category==Group.Category.SHIP then +item.ctype=INTEL.Ctype.NAVAL +end +return item +elseif Positionable:IsInstanceOf("STATIC")then +local static=Positionable +item.groupname=static:GetName() +item.group=static +item.Tdetected=timer.getAbsTime() +item.typename=static:GetTypeName()or"Unknown" +item.attribute="Static" +item.category=3 +item.categoryname=static:GetCategoryName()or"Unknown" +item.threatlevel=static:GetThreatLevel()or 0 +item.position=static:GetCoordinate() +item.velocity=static:GetVelocityVec3() +item.speed=0 +item.recce=RecceName +item.isground=true +item.isship=false +item.isStatic=true +item.ctype=INTEL.Ctype.STRUCTURE +return item +else +self:E(self.lid..string.format("ERROR: object needs to be a GROUP or STATIC!")) +end +end +return nil +end +function INTEL:CreateDetectedItems(DetectedGroups,DetectedStatics,RecceDetecting) +self:F({RecceDetecting=RecceDetecting}) +local Tnow=timer.getAbsTime() +for groupname,_group in pairs(DetectedGroups)do +local group=_group +self:KnowObject(group,RecceDetecting[groupname]) +end +for staticname,_static in pairs(DetectedStatics)do +local static=_static +self:KnowObject(static,RecceDetecting[staticname]) +end +for i=#self.Contacts,1,-1 do +local item=self.Contacts[i] +if self:_CheckContactLost(item)then +self:LostContact(item) +self:RemoveContact(item) +end +end +return self +end +function INTEL:GetDetectedUnits(Unit,DetectedUnits,RecceDetecting,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local reccename=Unit:GetName() +local detectedtargets=Unit:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +for DetectionObjectID,Detection in pairs(detectedtargets or{})do +local DetectedObject=Detection.object +if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then +local status,name=pcall( +function() +local name=DetectedObject:getName() +return name +end) +if status then +local unit=UNIT:FindByName(name) +if unit and unit:IsAlive()then +local DetectionAccepted=true +if self.RadarAcceptRange then +local reccecoord=Unit:GetCoordinate() +local coord=unit:GetCoordinate() +local dist=math.floor(coord:Get2DDistance(reccecoord)/1000) +if dist>self.RadarAcceptRangeKilometers then DetectionAccepted=false end +end +if self.RadarBlur then +local reccecoord=Unit:GetCoordinate() +local coord=unit:GetCoordinate() +local dist=math.floor(coord:Get2DDistance(reccecoord)/1000) +local AGL=unit:GetAltitude(true) +local minheight=self.RadarBlurMinHeight or 250 +local thresheight=self.RadarBlurThresHeight or 90 +local thresblur=self.RadarBlurThresBlur or 85 +if dist<=self.RadarBlurClosing then +thresheight=(((dist*dist)/self.RadarBlurClosingSquare)*thresheight) +thresblur=(((dist*dist)/self.RadarBlurClosingSquare)*thresblur) +end +local fheight=math.floor(math.random(1,10000)/100) +local fblur=math.floor(math.random(1,10000)/100) +if fblur>thresblur then DetectionAccepted=false end +if AGL<=minheight and fheight1 then +MESSAGE:New("Radar Blur",10):ToLogIf(self.debug):ToAllIf(self.verbose>1) +MESSAGE:New("Unit "..name.." is at "..math.floor(AGL).."m. Distance "..math.floor(dist).."km.",10):ToLogIf(self.debug):ToAllIf(self.verbose>1) +MESSAGE:New(string.format("fheight = %d/%d | fblur = %d/%d",fheight,thresheight,fblur,thresblur),10):ToLogIf(self.debug):ToAllIf(self.verbose>1) +MESSAGE:New("Detection Accepted = "..tostring(DetectionAccepted),10):ToLogIf(self.debug):ToAllIf(self.verbose>1) +end +end +if DetectionAccepted then +DetectedUnits[name]=unit +RecceDetecting[name]=reccename +self:T(string.format("Unit %s detect by %s",name,reccename)) +end +else +if self.detectStatics then +local static=STATIC:FindByName(name,false) +if static then +DetectedUnits[name]=static +RecceDetecting[name]=reccename +end +end +end +else +self:T(self.lid..string.format("WARNING: Could not get name of detected object ID=%s! Detected by %s",DetectedObject.id_,reccename)) +end +end +end +end +function INTEL:onafterNewContact(From,Event,To,Contact) +self:F(self.lid..string.format("NEW contact %s",Contact.groupname)) +table.insert(self.ContactsUnknown,Contact) +return self +end +function INTEL:onafterLostContact(From,Event,To,Contact) +self:F(self.lid..string.format("LOST contact %s",Contact.groupname)) +table.insert(self.ContactsLost,Contact) +return self +end +function INTEL:onafterNewCluster(From,Event,To,Cluster) +self:F(self.lid..string.format("NEW cluster #%d [%s] of size %d",Cluster.index,Cluster.ctype,Cluster.size)) +self:_AddCluster(Cluster) +return self +end +function INTEL:onafterLostCluster(From,Event,To,Cluster,Mission) +local text=self.lid..string.format("LOST cluster #%d [%s]",Cluster.index,Cluster.ctype) +if Mission then +local mission=Mission +text=text..string.format(" mission name=%s type=%s target=%s",mission.name,mission.type,mission:GetTargetName()or"unknown") +end +self:T(text) +return self +end +function INTEL:KnowObject(Positionable,RecceName,Tdetected) +local Tnow=timer.getAbsTime() +Tdetected=Tdetected or Tnow +if Positionable and Positionable:IsAlive()then +if Tdetected>Tnow then +self:ScheduleOnce(Tdetected-Tnow,self.KnowObject,self,Positionable,RecceName) +else +local name=Positionable:GetName() +local contact=self:GetContactByName(name) +if contact then +self:_UpdateContact(contact) +else +contact=self:_CreateContact(Positionable,RecceName) +if contact then +self:T(string.format("%s contact detected by %s",contact.groupname,RecceName or"unknown")) +self:AddContact(contact) +self:NewContact(contact) +end +end +end +end +return self +end +function INTEL:GetContactByName(groupname) +for i,_contact in pairs(self.Contacts)do +local contact=_contact +if contact.groupname==groupname then +return contact +end +end +return nil +end +function INTEL:_IsContactKnown(Contact) +for i,_contact in pairs(self.Contacts)do +local contact=_contact +if contact.groupname==Contact.groupname then +return true +end +end +return false +end +function INTEL:AddContact(Contact) +if self:_IsContactKnown(Contact)then +self:E(self.lid..string.format("WARNING: Contact %s is already in the contact table!",tostring(Contact.groupname))) +else +self:T(self.lid..string.format("Adding new Contact %s to table",tostring(Contact.groupname))) +table.insert(self.Contacts,Contact) +end +return self +end +function INTEL:RemoveContact(Contact) +for i,_contact in pairs(self.Contacts)do +local contact=_contact +if contact.groupname==Contact.groupname then +table.remove(self.Contacts,i) +end +end +return self +end +function INTEL:_CheckContactLost(Contact) +if Contact.group==nil or not Contact.group:IsAlive()then +return true +end +if Contact.isStatic then +return false +end +local dT=timer.getAbsTime()-Contact.Tdetected +local dTforget=nil +if Contact.category==Group.Category.GROUND then +dTforget=60*60*2 +elseif Contact.category==Group.Category.AIRPLANE then +dTforget=60*10 +elseif Contact.category==Group.Category.HELICOPTER then +dTforget=60*20 +elseif Contact.category==Group.Category.SHIP then +dTforget=60*60 +elseif Contact.category==Group.Category.TRAIN then +dTforget=60*60 +end +if dT>dTforget then +return true +else +return false +end +end +function INTEL:PaintPicture() +self:F(self.lid.."Painting Picture!") +for _,_contact in pairs(self.ContactsLost)do +local contact=_contact +local cluster=self:GetClusterOfContact(contact) +if cluster then +self:RemoveContactFromCluster(contact,cluster) +end +end +local ClusterSet={} +for _i,_cluster in pairs(self.Clusters)do +local cluster=_cluster +if cluster.size>0 and self:ClusterCountUnits(cluster)>0 then +table.insert(ClusterSet,_cluster) +else +if cluster.marker then +cluster.marker:Remove() +end +if cluster.markerID then +COORDINATE:RemoveMark(cluster.markerID) +end +self:LostCluster(cluster,cluster.mission) +end +end +self.Clusters=ClusterSet +self:_UpdateClusterPositions() +for _,_contact in pairs(self.Contacts)do +local contact=_contact +self:T(string.format("Paint Picture: checking for %s",contact.groupname)) +local currentcluster=self:GetClusterOfContact(contact) +if currentcluster then +local isconnected=self:IsContactConnectedToCluster(contact,currentcluster) +if isconnected then +else +self:RemoveContactFromCluster(contact,currentcluster) +local cluster=self:_GetClosestClusterOfContact(contact) +if cluster then +self:AddContactToCluster(contact,cluster) +else +local newcluster=self:_CreateClusterFromContact(contact) +self:NewCluster(newcluster) +end +end +else +self:T(self.lid..string.format("Paint Picture: contact %s has NO current cluster",contact.groupname)) +local cluster=self:_GetClosestClusterOfContact(contact) +if cluster then +self:T(self.lid..string.format("Paint Picture: contact %s has closest cluster #%d",contact.groupname,cluster.index)) +self:AddContactToCluster(contact,cluster) +else +self:T(self.lid..string.format("Paint Picture: contact %s has no closest cluster ==> Create new cluster",contact.groupname)) +local newcluster=self:_CreateClusterFromContact(contact) +self:NewCluster(newcluster) +end +end +end +self:_UpdateClusterPositions() +if self.clustermarkers then +for _,_cluster in pairs(self.Clusters)do +local cluster=_cluster +if self.verbose>=1 then +BASE:I("Updating cluster marker and future position") +end +self:UpdateClusterMarker(cluster) +self:CalcClusterFuturePosition(cluster,300) +end +end +return self +end +function INTEL:_CreateCluster() +local cluster={} +cluster.index=self.clustercounter +cluster.coordinate=COORDINATE:New(0,0,0) +cluster.threatlevelSum=0 +cluster.threatlevelMax=0 +cluster.size=0 +cluster.Contacts={} +cluster.altitude=0 +self.clustercounter=self.clustercounter+1 +return cluster +end +function INTEL:_CreateClusterFromContact(Contact) +local cluster=self:_CreateCluster() +self:T(self.lid..string.format("Created NEW cluster #%d with first contact %s",cluster.index,Contact.groupname)) +cluster.coordinate:UpdateFromCoordinate(Contact.position) +cluster.ctype=Contact.ctype +self:AddContactToCluster(Contact,cluster) +return cluster +end +function INTEL:_AddCluster(Cluster) +table.insert(self.Clusters,Cluster) +return self +end +function INTEL:AddContactToCluster(contact,cluster) +if contact and cluster then +table.insert(cluster.Contacts,contact) +cluster.threatlevelSum=cluster.threatlevelSum+contact.threatlevel +cluster.size=cluster.size+1 +self:GetClusterAltitude(cluster,true) +self:T(self.lid..string.format("Adding contact %s to cluster #%d [%s] ==> New size=%d",contact.groupname,cluster.index,cluster.ctype,cluster.size)) +end +return self +end +function INTEL:RemoveContactFromCluster(contact,cluster) +if contact and cluster then +for i=#cluster.Contacts,1,-1 do +local Contact=cluster.Contacts[i] +if Contact.groupname==contact.groupname then +cluster.threatlevelSum=cluster.threatlevelSum-contact.threatlevel +cluster.size=cluster.size-1 +table.remove(cluster.Contacts,i) +self:T(self.lid..string.format("Removing contact %s from cluster #%d ==> New cluster size=%d",contact.groupname,cluster.index,cluster.size)) +return self +end +end +end +return self +end +function INTEL:CalcClusterThreatlevelSum(cluster) +local threatlevel=0 +for _,_contact in pairs(cluster.Contacts)do +local contact=_contact +threatlevel=threatlevel+contact.threatlevel +end +cluster.threatlevelSum=threatlevel +return threatlevel +end +function INTEL:CalcClusterThreatlevelAverage(cluster) +local threatlevel=self:CalcClusterThreatlevelSum(cluster) +threatlevel=threatlevel/cluster.size +cluster.threatlevelAve=threatlevel +return threatlevel +end +function INTEL:CalcClusterThreatlevelMax(cluster) +local threatlevel=0 +for _,_contact in pairs(cluster.Contacts)do +local contact=_contact +if contact.threatlevel>threatlevel then +threatlevel=contact.threatlevel +end +end +cluster.threatlevelMax=threatlevel +return threatlevel +end +function INTEL:CalcClusterDirection(cluster) +local direction=0 +local speedsum=0 +local n=0 +for _,_contact in pairs(cluster.Contacts)do +local contact=_contact +if(not contact.isStatic)and contact.group:IsAlive()then +local speed=contact.group:GetVelocityKNOTS() +direction=direction+(contact.group:GetHeading()*speed) +n=n+1 +speedsum=speedsum+speed +end +end +if n==0 then +return 0 +else +return math.floor(direction/(speedsum*n)) +end +end +function INTEL:CalcClusterSpeed(cluster) +local velocity=0;local n=0 +for _,_contact in pairs(cluster.Contacts)do +local contact=_contact +if(not contact.isStatic)and contact.group:IsAlive()then +velocity=velocity+contact.group:GetVelocityMPS() +n=n+1 +end +end +if n==0 then +return 0 +else +return math.floor(velocity/n) +end +end +function INTEL:CalcClusterVelocityVec3(cluster) +local v={x=0,y=0,z=0} +for _,_contact in pairs(cluster.Contacts)do +local contact=_contact +if(not contact.isStatic)and contact.group:IsAlive()then +local vec=contact.group:GetVelocityVec3() +v.x=v.x+vec.x +v.y=v.y+vec.y +v.z=v.y+vec.z +end +end +return v +end +function INTEL:CalcClusterFuturePosition(cluster,seconds) +local p=self:GetClusterCoordinate(cluster) +local v=self:CalcClusterVelocityVec3(cluster) +local t=seconds or self.prediction +local Vec3={x=p.x+v.x*t,y=p.y+v.y*t,z=p.z+v.z*t} +local futureposition=COORDINATE:NewFromVec3(Vec3) +if self.clustermarkers and self.clusterarrows then +if cluster.markerID then +COORDINATE:RemoveMark(cluster.markerID) +end +cluster.markerID=p:ArrowToAll(futureposition,self.coalition,{1,0,0},1,{1,1,0},0.5,2,true,"Position Calc") +end +return futureposition +end +function INTEL:CheckContactInClusters(contact) +for _,_cluster in pairs(self.Clusters)do +local cluster=_cluster +for _,_contact in pairs(cluster.Contacts)do +local Contact=_contact +if Contact.groupname==contact.groupname then +return true +end +end +end +return false +end +function INTEL:IsContactConnectedToCluster(contact,cluster) +if contact.ctype~=cluster.ctype then +return false,math.huge +end +for _,_contact in pairs(cluster.Contacts)do +local Contact=_contact +if Contact.groupname~=contact.groupname or cluster.size==1 then +local dist=Contact.position:DistanceFromPointVec2(contact.position) +local airprox=true +if contact.ctype==INTEL.Ctype.AIRCRAFT then +self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,contact.altitude)) +local adist=math.abs(cluster.altitude-contact.altitude) +if adist>UTILS.FeetToMeters(10000)then +airprox=false +end +end +if distUTILS.FeetToMeters(10000)then +airprox=false +end +end +if dist0 then +avgalt=newalt/n +end +Cluster.altitude=avgalt +self:T(string.format("Updating Cluster Altitude: %d",Cluster.altitude)) +return Cluster.altitude +end +function INTEL:GetClusterCoordinate(Cluster,Update) +local x=0;local y=0;local z=0;local n=0 +for _,_contact in pairs(Cluster.Contacts)do +local contact=_contact +local vec3=nil +if Update and contact.group and contact.group:IsAlive()then +vec3=contact.group:GetVec3() +end +if not vec3 then +vec3=contact.position +end +if vec3 then +x=x+vec3.x +y=y+vec3.y +z=z+vec3.z +n=n+1 +end +end +if n>0 then +local Vec3={x=x/n,y=y/n,z=z/n} +Cluster.coordinate:UpdateFromVec3(Vec3) +end +return Cluster.coordinate +end +function INTEL:_CheckClusterCoordinateChanged(Cluster,Coordinate,Threshold) +Threshold=Threshold or 100 +Coordinate=Coordinate or Cluster.coordinate +local a=Coordinate:GetVec3() +local b=self:GetClusterCoordinate(Cluster,true):GetVec3() +local dist=UTILS.VecDist3D(a,b) +if dist>Threshold then +return true +else +return false +end +end +function INTEL:_UpdateClusterPositions() +for _,_cluster in pairs(self.Clusters)do +local cluster=_cluster +local coord=self:GetClusterCoordinate(cluster,true) +local alt=self:GetClusterAltitude(cluster,true) +self:T(self.lid..string.format("Updating Cluster position size: %s",cluster.size)) +end +return self +end +function INTEL:ContactCountUnits(Contact) +if Contact.isStatic then +if Contact.group and Contact.group:IsAlive()then +return 1 +else +return 0 +end +else +if Contact.group then +local n=Contact.group:CountAliveUnits() +return n +else +return 0 +end +end +end +function INTEL:ClusterCountUnits(Cluster) +local unitcount=0 +for _,_contact in pairs(Cluster.Contacts)do +local contact=_contact +unitcount=unitcount+self:ContactCountUnits(contact) +end +return unitcount +end +function INTEL:UpdateClusterMarker(cluster) +local unitcount=self:ClusterCountUnits(cluster) +local text=string.format("Cluster #%d: %s\nSize %d\nUnits %d\nTLsum=%d",cluster.index,cluster.ctype,cluster.size,unitcount,cluster.threatlevelSum) +if not cluster.marker then +cluster.marker=MARKER:New(cluster.coordinate,text):ToCoalition(self.coalition) +else +local refresh=false +if cluster.marker.text~=text then +cluster.marker.text=text +refresh=true +end +local coordchange=self:_CheckClusterCoordinateChanged(cluster,cluster.marker.coordinate) +if coordchange then +cluster.marker.coordinate:UpdateFromCoordinate(cluster.coordinate) +refresh=true +end +if refresh then +cluster.marker:Refresh() +end +end +return self +end +function INTEL:GetHighestThreatContact(Cluster) +local threatlevel=-1 +local rcontact=nil +for _,_contact in pairs(Cluster.Contacts)do +local contact=_contact +if contact.threatlevel>threatlevel then +threatlevel=contact.threatlevel +rcontact=contact +end +end +return rcontact +end +INTEL_DLINK={ +ClassName="INTEL_DLINK", +verbose=0, +lid=nil, +alias=nil, +cachetime=120, +interval=20, +contacts={}, +clusters={}, +contactcoords={}, +} +INTEL_DLINK.version="0.0.2" +function INTEL_DLINK:New(Intels,Alias,Interval,Cachetime) +local self=BASE:Inherit(self,FSM:New()) +self.intels=Intels or{} +self.contacts={} +self.clusters={} +self.contactcoords={} +if Alias then +self.alias=tostring(Alias) +else +self.alias="SPECTRE" +end +self.interval=Interval or 20 +self.lid=string.format("INTEL_DLINK %s | ",self.alias) +self:SetDLinkCacheTime(Cachetime or 120) +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Collect","*") +self:AddTransition("*","Collected","*") +self:AddTransition("*","Stop","Stopped") +return self +end +function INTEL_DLINK:AddIntel(Intel) +self:T(self.lid.."AddIntel") +if Intel then +table.insert(self.intels,Intel) +end +return self +end +function INTEL_DLINK:onafterStart(From,Event,To) +self:T({From,Event,To}) +local text=string.format("Version %s started.",self.version) +self:I(self.lid..text) +self:__Collect(-math.random(1,10)) +return self +end +function INTEL_DLINK:SetDLinkCacheTime(seconds) +self.cachetime=math.abs(seconds or 120) +self:I(self.lid.."Caching for "..self.cachetime.." seconds.") +return self +end +function INTEL_DLINK:onbeforeCollect(From,Event,To) +self:T({From,Event,To}) +self:T("Contacts Data Gathering") +local newcontacts={} +local intels=self.intels +for _,_intel in pairs(intels)do +_intel=_intel +if _intel:Is("Running")then +local ctable=_intel:GetContactTable()or{} +for _,_contact in pairs(ctable)do +local _ID=string.format("%s-%d",_contact.groupname,_contact.Tdetected) +self:T(string.format("Adding %s",_ID)) +newcontacts[_ID]=_contact +end +end +end +self:T("Cleanup") +local contacttable={} +local coordtable={} +local TNow=timer.getAbsTime() +local Tcache=self.cachetime +for _ind,_contact in pairs(newcontacts)do +if TNow-_contact.Tdetected0 then +self:ScheduleOnce(Delay,LEGION.RelocateCohort,self,Cohort,Legion,0,NcarriersMin,NcarriersMax,TransportLegions) +else +if Legion:IsCohort(Cohort.name)then +self:E(self.lid..string.format("ERROR: Cohort %s is already part of new legion %s ==> CANNOT Relocate!",Cohort.name,Legion.alias)) +return self +else +table.insert(Legion.cohorts,Cohort) +end +if not self:IsCohort(Cohort.name)then +self:E(self.lid..string.format("ERROR: Cohort %s is NOT part of this legion %s ==> CANNOT Relocate!",Cohort.name,self.alias)) +return self +end +if self.alias==Legion.alias then +self:E(self.lid..string.format("ERROR: old legion %s is same as new legion %s ==> CANNOT Relocate!",self.alias,Legion.alias)) +return self +end +Cohort:Relocate() +local mission=AUFTRAG:_NewRELOCATECOHORT(Legion,Cohort) +if false then +mission:_AddAssets(Cohort.assets) +self:I(self.lid..string.format("Relocating Cohort %s [nassets=%d] to legion %s",Cohort.name,#Cohort.assets,Legion.alias)) +self:MissionAssign(mission,{self}) +else +mission:AssignCohort(Cohort) +mission:SetRequiredAssets(#Cohort.assets) +if NcarriersMin and NcarriersMin>0 then +mission:SetRequiredTransport(Legion.spawnzone,NcarriersMin,NcarriersMax) +end +if TransportLegions then +for _,legion in pairs(TransportLegions)do +mission:AssignTransportLegion(legion) +end +end +mission:SetMissionRange(10000) +self:AddMission(mission) +end +end +return self +end +function LEGION:_GetCohort(CohortName) +for _,_cohort in pairs(self.cohorts)do +local cohort=_cohort +if cohort.name==CohortName then +return cohort +end +end +return nil +end +function LEGION:IsCohort(CohortName) +for _,_cohort in pairs(self.cohorts)do +local cohort=_cohort +if cohort.name==CohortName then +return true +end +end +return false +end +function LEGION:GetName() +return self.alias +end +function LEGION:_GetCohortOfAsset(Asset) +local cohort=self:_GetCohort(Asset.squadname) +return cohort +end +function LEGION:IsBrigade() +local is=self.ClassName==BRIGADE.ClassName +return is +end +function LEGION:IsAirwing() +local is=self.ClassName==AIRWING.ClassName +return is +end +function LEGION:IsFleet() +local is=self.ClassName==FLEET.ClassName +return is +end +function LEGION:onafterStart(From,Event,To) +self:GetParent(self,LEGION).onafterStart(self,From,Event,To) +self:T3(self.lid..string.format("Starting LEGION v%s",LEGION.version)) +end +function LEGION:CheckMissionQueue() +local Nmissions=#self.missionqueue +if Nmissions==0 then +return nil +end +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission:IsNotOver()and mission:IsReadyToCancel()then +mission:Cancel() +end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end +end +if self:IsAirwing()then +if self:IsRunwayOperational()==false then +return nil +end +local airboss=self.airboss +if airboss then +if not airboss:IsIdle()then +return nil +end +end +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio0 then +local N=mission.Nassigned-mission.Ndead +if N Reinforce=%s", +mission.reinforce,N,mission.Nassigned,mission.Ndead,mission.NassetsMin,tostring(reinforce))) +end +if(mission:IsQueued(self)or reinforce)and mission:IsReadyToGo()and(mission.importance==nil or mission.importance<=vip)then +local recruited,assets,legions=self:RecruitAssetsForMission(mission) +if recruited then +local EscortAvail=self:RecruitAssetsForEscort(mission,assets) +local TransportAvail=true +if EscortAvail then +local Transport=nil +if mission.NcarriersMin then +local Legions=mission.transportLegions or{self} +TransportAvail,Transport=self:AssignAssetsForTransport(Legions,assets,mission.NcarriersMin,mission.NcarriersMax,mission.transportDeployZone,mission.transportDisembarkZone,mission.carrierCategories,mission.carrierAttributes,mission.carrierProperties) +end +if TransportAvail and Transport then +mission.opstransport=Transport +end +end +if EscortAvail and TransportAvail then +self:MissionRequest(mission,assets) +if reinforce then +mission.reinforce=mission.reinforce-#assets +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) +end +return true +else +LEGION.UnRecruitAssets(assets,mission) +end +end +end +end +return nil +end +function LEGION:CheckTransportQueue() +local Ntransports=#self.transportqueue +if Ntransports==0 then +return nil +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio0 then +for i,_asset in pairs(Assetlist)do +local asset=_asset +asset.requested=true +if asset.spawned then +asset.requested=false +end +asset.isReserved=false +if Mission.missionTask then +asset.missionTask=Mission.missionTask +end +if Mission.type==AUFTRAG.Type.ALERT5 then +asset.takeoffType=COORDINATE.WaypointType.TakeOffParking +end +Mission:AddAsset(asset) +end +local assignment=string.format("Mission-%d",Mission.auftragsnummer) +local request=self:_AddRequest(WAREHOUSE.Descriptor.ASSETLIST,Assetlist,#Assetlist,Mission.prio,assignment) +self:T(self.lid..string.format("Added request=%d for Nasssets=%d",request.uid,#Assetlist)) +Mission:_SetRequestID(self,self.queueid) +self:T(self.lid..string.format("Mission %s [%s] got Request ID=%d",Mission:GetName(),Mission:GetType(),self.queueid)) +if request then +if self:IsShip()then +self:T(self.lid.."Warehouse physical structure is SHIP. Requestes assets will be late activated!") +request.lateActivation=true +end +end +end +end +function LEGION:onafterTransportAssign(From,Event,To,Transport,Legions) +for _,_Legion in pairs(Legions)do +local Legion=_Legion +self:T(self.lid..string.format("Assigning transport %d to legion %s",Transport.uid,Legion.alias)) +Legion:AddOpsTransport(Transport) +Legion:TransportRequest(Transport) +end +end +function LEGION:onafterTransportRequest(From,Event,To,OpsTransport) +local AssetList={} +for i,_asset in pairs(OpsTransport.assets)do +local asset=_asset +if asset.wid==self.uid then +asset.requested=true +asset.isReserved=false +asset.missionTask=ENUMS.MissionTask.TRANSPORT +table.insert(AssetList,asset) +end +end +if#AssetList>0 then +OpsTransport:Requested() +OpsTransport:SetLegionStatus(self,OPSTRANSPORT.Status.REQUESTED) +local assignment=string.format("Transport-%d",OpsTransport.uid) +self:_AddRequest(WAREHOUSE.Descriptor.ASSETLIST,AssetList,#AssetList,OpsTransport.prio,assignment) +OpsTransport.requestID[self.alias]=self.queueid +end +end +function LEGION:onafterTransportCancel(From,Event,To,Transport) +self:T(self.lid..string.format("Cancel transport UID=%d",Transport.uid)) +Transport:SetLegionStatus(self,OPSTRANSPORT.Status.CANCELLED) +for i=#Transport.assets,1,-1 do +local asset=Transport.assets[i] +if asset.wid==self.uid then +local opsgroup=asset.flightgroup +if opsgroup then +opsgroup:TransportCancel(Transport) +end +local cargos=Transport:GetCargoOpsGroups(false) +for _,_cargo in pairs(cargos)do +local cargo=_cargo +cargo:_DelMyLift(Transport) +local legion=cargo.legion +if legion then +legion:T(self.lid..string.format("Adding cargo group %s back to legion",cargo:GetName())) +legion:__AddAsset(0.1,cargo.group,1) +end +end +Transport:DelAsset(asset) +asset.requested=nil +asset.isReserved=nil +end +end +if Transport.requestID[self.alias]then +self:_DeleteQueueItemByID(Transport.requestID[self.alias],self.queue) +end +end +function LEGION:onafterMissionCancel(From,Event,To,Mission) +self:T(self.lid..string.format("Cancel mission %s",Mission.name)) +Mission:SetLegionStatus(self,AUFTRAG.Status.CANCELLED) +for i=#Mission.assets,1,-1 do +local asset=Mission.assets[i] +if asset.wid==self.uid then +local opsgroup=asset.flightgroup +if opsgroup then +opsgroup:MissionCancel(Mission) +end +Mission:DelAsset(asset) +asset.requested=nil +asset.isReserved=nil +end +end +local requestID=Mission:_GetRequestID(self) +if requestID then +self:_DeleteQueueItemByID(requestID,self.queue) +end +end +function LEGION:onafterOpsOnMission(From,Event,To,OpsGroup,Mission) +self:T2(self.lid..string.format("Group %s on mission %s [%s]",OpsGroup:GetName(),Mission:GetName(),Mission:GetType())) +if self:IsAirwing()then +self:FlightOnMission(OpsGroup,Mission) +elseif self:IsBrigade()then +self:ArmyOnMission(OpsGroup,Mission) +else +self:NavyOnMission(OpsGroup,Mission) +end +if self:IsBrigade()and self:IsShip()then +OpsGroup:PauseMission() +self.warehouseOpsGroup:Load(OpsGroup,self.warehouseOpsElement) +end +if self.chief then +self.chief:OpsOnMission(OpsGroup,Mission) +end +if self.commander then +self.commander:OpsOnMission(OpsGroup,Mission) +end +end +function LEGION:onafterNewAsset(From,Event,To,asset,assignment) +self:GetParent(self,LEGION).onafterNewAsset(self,From,Event,To,asset,assignment) +local text=string.format("New asset %s with assignment %s and request assignment %s",asset.spawngroupname,tostring(asset.assignment),tostring(assignment)) +self:T(self.lid..text) +local cohort=self:_GetCohort(asset.assignment) +if cohort then +if asset.assignment==assignment then +local nunits=#asset.template.units +local text=string.format("Adding asset to cohort %s: assignment=%s, type=%s, attribute=%s, nunits=%d ngroup=%s",cohort.name,assignment,asset.unittype,asset.attribute,nunits,tostring(cohort.ngrouping)) +self:T(self.lid..text) +if cohort.ngrouping then +local template=asset.template +local N=math.max(#template.units,cohort.ngrouping) +asset.weight=0 +asset.cargobaytot=0 +for i=1,N do +local unit=template.units[i] +if i>nunits then +table.insert(template.units,UTILS.DeepCopy(template.units[1])) +asset.cargobaytot=asset.cargobaytot+asset.cargobay[1] +asset.weight=asset.weight+asset.weights[1] +template.units[i].x=template.units[1].x+5*(i-nunits) +template.units[i].y=template.units[1].y+5*(i-nunits) +else +if i<=cohort.ngrouping then +asset.weight=asset.weight+asset.weights[i] +asset.cargobaytot=asset.cargobaytot+asset.cargobay[i] +end +end +if i>cohort.ngrouping then +template.units[i]=nil +end +end +asset.nunits=cohort.ngrouping +self:T(self.lid..string.format("After regrouping: Nunits=%d, weight=%.1f cargobaytot=%.1f kg",#asset.template.units,asset.weight,asset.cargobaytot)) +end +asset.takeoffType=cohort.takeoffType~=nil and cohort.takeoffType or self.takeoffType +asset.parkingIDs=cohort.parkingIDs +cohort:GetCallsign(asset) +cohort:GetModex(asset) +asset.spawngroupname=string.format("%s_AID-%d",cohort.name,asset.uid) +cohort:AddAsset(asset) +else +self:T(self.lid..string.format("Asset returned to legion ==> calling LegionAssetReturned event")) +asset.takeoffType=cohort.takeoffType +self:LegionAssetReturned(cohort,asset) +end +end +end +function LEGION:onafterLegionAssetReturned(From,Event,To,Cohort,Asset) +self:T(self.lid..string.format("Asset %s from Cohort %s returned! asset.assignment=\"%s\"",Asset.spawngroupname,Cohort.name,tostring(Asset.assignment))) +if Asset.flightgroup and not Asset.flightgroup:IsStopped()then +Asset.flightgroup:Stop() +end +if Asset.flightgroup:IsFlightgroup()then +self:ReturnPayloadFromAsset(Asset) +end +if Asset.tacan then +Cohort:ReturnTacan(Asset.tacan) +end +Asset.Treturned=timer.getAbsTime() +end +function LEGION:onafterAssetSpawned(From,Event,To,group,asset,request) +self:T({From,Event,To,group:GetName(),asset.assignment,request.assignment}) +self:GetParent(self,LEGION).onafterAssetSpawned(self,From,Event,To,group,asset,request) +local cohort=self:_GetCohortOfAsset(asset) +if cohort then +self:T(self.lid..string.format("Cohort asset spawned %s",asset.spawngroupname)) +local flightgroup=self:_CreateFlightGroup(asset) +asset.flightgroup=flightgroup +asset.requested=nil +asset.Treturned=nil +local Tacan=cohort:FetchTacan() +if Tacan then +asset.tacan=Tacan +flightgroup:SwitchTACAN(Tacan,Morse,UnitName,Band) +end +local radioFreq,radioModu=cohort:GetRadio() +if radioFreq then +flightgroup:SwitchRadio(radioFreq,radioModu) +end +if cohort.fuellow then +flightgroup:SetFuelLowThreshold(cohort.fuellow) +end +if cohort.fuellowRefuel then +flightgroup:SetFuelLowRefuel(cohort.fuellowRefuel) +end +local assignment=request.assignment +if self:IsFleet()then +flightgroup:SetPathfinding(self.pathfinding) +end +if string.find(assignment,"Mission-")then +local uid=UTILS.Split(assignment,"-")[2] +local mission=self:GetMissionByID(uid) +local despawnLanding=cohort.despawnAfterLanding~=nil and cohort.despawnAfterLanding or self.despawnAfterLanding +if despawnLanding then +flightgroup:SetDespawnAfterLanding() +end +local despawnHolding=cohort.despawnAfterHolding~=nil and cohort.despawnAfterHolding or self.despawnAfterHolding +if despawnHolding then +flightgroup:SetDespawnAfterHolding() +end +if mission then +if Tacan then +end +flightgroup:AddMission(mission) +if self:IsBrigade()or self:IsFleet()then +flightgroup:SetReturnOnOutOfAmmo() +end +self:__OpsOnMission(5,flightgroup,mission) +else +if Tacan then +end +end +local chief=self.chief or(self.commander and self.commander.chief or nil) +if chief then +self:T(self.lid..string.format("Adding group %s to agents of CHIEF",group:GetName())) +chief.detectionset:AddGroup(asset.flightgroup.group) +end +elseif string.find(assignment,"Transport-")then +local uid=UTILS.Split(assignment,"-")[2] +local transport=self:GetTransportByID(uid) +if transport then +flightgroup:AddOpsTransport(transport) +end +end +end +end +function LEGION:onafterAssetDead(From,Event,To,asset,request) +self:GetParent(self,LEGION).onafterAssetDead(self,From,Event,To,asset,request) +if self.commander and self.commander.chief then +self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) +end +self:DelAsset(asset) +end +function LEGION:onafterDestroyed(From,Event,To) +self:T(self.lid.."Legion warehouse destroyed!") +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +mission:Cancel() +end +for _,_cohort in pairs(self.cohorts)do +local cohort=_cohort +cohort:Stop() +end +self:GetParent(self,LEGION).onafterDestroyed(self,From,Event,To) +end +function LEGION:onafterRequest(From,Event,To,Request) +if Request.toself then +local assets=Request.cargoassets +local Mission=self:GetMissionByID(Request.assignment) +if Mission and assets then +for _,_asset in pairs(assets)do +local asset=_asset +end +end +end +self:GetParent(self,LEGION).onafterRequest(self,From,Event,To,Request) +end +function LEGION:onafterSelfRequest(From,Event,To,groupset,request) +self:GetParent(self,LEGION).onafterSelfRequest(self,From,Event,To,groupset,request) +local mission=self:GetMissionByID(request.assignment) +for _,_asset in pairs(request.assets)do +local asset=_asset +end +for _,_group in pairs(groupset:GetSet())do +local group=_group +end +end +function LEGION:onafterRequestSpawned(From,Event,To,Request,CargoGroupSet,TransportGroupSet) +self:GetParent(self,LEGION).onafterRequestSpawned(self,From,Event,To,Request,CargoGroupSet,TransportGroupSet) +end +function LEGION:onafterCaptured(From,Event,To,Coalition,Country) +self:GetParent(self,LEGION).onafterCaptured(self,From,Event,To,Coalition,Country) +if self.chief then +self.chief.commander:LegionLost(self,Coalition,Country) +self.chief:LegionLost(self,Coalition,Country) +self.chief:RemoveLegion(self) +elseif self.commander then +self.commander:LegionLost(self,Coalition,Country) +self.commander:RemoveLegion(self) +end +end +function LEGION:_CreateFlightGroup(asset) +local opsgroup=nil +if self:IsAirwing()then +opsgroup=FLIGHTGROUP:New(asset.spawngroupname) +elseif self:IsBrigade()then +opsgroup=ARMYGROUP:New(asset.spawngroupname) +opsgroup:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) +elseif self:IsFleet()then +opsgroup=NAVYGROUP:New(asset.spawngroupname) +else +self:E(self.lid.."ERROR: not airwing or brigade!") +end +opsgroup:_SetLegion(self) +opsgroup.cohort=self:_GetCohortOfAsset(asset) +opsgroup.homebase=self.airbase +opsgroup.destbase=self.airbase +opsgroup.homezone=self.spawnzone +if opsgroup.cohort.weaponData then +local text="Weapon data for group:" +opsgroup.weaponData=opsgroup.weaponData or{} +for bittype,_weapondata in pairs(opsgroup.cohort.weaponData)do +local weapondata=_weapondata +opsgroup.weaponData[bittype]=UTILS.DeepCopy(weapondata) +text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km",bittype,weapondata.RangeMin/1000,weapondata.RangeMax/1000) +end +self:T3(self.lid..text) +end +return opsgroup +end +function LEGION:_TacticalOverview() +if self.tacview then +local NassetsTotal=self:CountAssets(nil) +local NassetsStock=self:CountAssets(true) +local NassetsActiv=self:CountAssets(false) +local NmissionsTotal=#self.missionqueue +local NmissionsRunni=self:CountMissionsInQueue() +local text=string.format("Tactical Overview %s\n",self.alias) +text=text..string.format("===================================\n") +text=text..string.format("Assets: %d [Active=%d, Stock=%d]\n",NassetsTotal,NassetsActiv,NassetsStock) +text=text..string.format("Missions: %d [Running=%d]\n",NmissionsTotal,NmissionsRunni) +for _,mtype in pairs(AUFTRAG.Type)do +local n=self:CountMissionsInQueue(mtype) +if n>0 then +local N=self:CountMissionsInQueue(mtype) +text=text..string.format(" - %s: %d [Running=%d]\n",mtype,n,N) +end +end +local Ntransports=#self.transportqueue +if Ntransports>0 then +text=text..string.format("Transports: %d\n",Ntransports) +for _,_transport in pairs(self.transportqueue)do +local transport=_transport +text=text..string.format(" - %s",transport:GetState()) +end +end +MESSAGE:New(text,60,nil,true):ToCoalition(self:GetCoalition()) +end +end +function LEGION:IsAssetOnMission(asset,MissionTypes) +if MissionTypes then +if type(MissionTypes)~="table"then +MissionTypes={MissionTypes} +end +else +MissionTypes=AUFTRAG.Type +end +if asset.flightgroup and asset.flightgroup:IsAlive()then +for _,_mission in pairs(asset.flightgroup.missionqueue or{})do +local mission=_mission +if mission:IsNotOver()then +local status=mission:GetGroupStatus(asset.flightgroup) +if(status==AUFTRAG.GroupStatus.STARTED or status==AUFTRAG.GroupStatus.EXECUTING)and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then +return true +end +end +end +end +return false +end +function LEGION:GetAssetCurrentMission(asset) +if asset.flightgroup then +return asset.flightgroup:GetMissionCurrent() +end +return nil +end +function LEGION:CountPayloadsInStock(MissionTypes,UnitTypes,Payloads) +if MissionTypes then +if type(MissionTypes)=="string"then +MissionTypes={MissionTypes} +end +end +if UnitTypes then +if type(UnitTypes)=="string"then +UnitTypes={UnitTypes} +end +end +local function _checkUnitTypes(payload) +if UnitTypes then +for _,unittype in pairs(UnitTypes)do +if unittype==payload.aircrafttype then +return true +end +end +else +return true +end +return false +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 +return nil +end +return false +end +local n=0 +for _,_payload in pairs(self.payloads or{})do +local payload=_payload +for _,MissionType in pairs(MissionTypes)do +local specialpayload=_checkPayloads(payload) +local compatible=AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities) +local goforit=specialpayload or(specialpayload==nil and compatible) +if goforit and _checkUnitTypes(payload)then +if payload.unlimited then +return 999 +else +n=n+payload.navail +end +end +end +end +return n +end +function LEGION:CountMissionsInQueue(MissionTypes) +MissionTypes=MissionTypes or AUFTRAG.Type +local N=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then +N=N+1 +end +end +return N +end +function LEGION:CountAssets(InStock,MissionTypes,Attributes) +local N=0 +for _,_cohort in pairs(self.cohorts)do +local cohort=_cohort +N=N+cohort:CountAssets(InStock,MissionTypes,Attributes) +end +return N +end +function LEGION:GetOpsGroups(MissionTypes,Attributes) +local setLegion=SET_OPSGROUP:New() +for _,_cohort in pairs(self.cohorts)do +local cohort=_cohort +local setCohort=cohort:GetOpsGroups(MissionTypes,Attributes) +self:T2(self.lid..string.format("Found %d opsgroups of cohort %s",setCohort:Count(),cohort.name)) +setLegion:AddSet(setCohort) +end +return setLegion +end +function LEGION:CountAssetsWithPayloadsInStock(Payloads,MissionTypes,Attributes) +local N=0 +local Npayloads={} +for _,_cohort in pairs(self.cohorts)do +local cohort=_cohort +if Npayloads[cohort.aircrafttype]==nil then +Npayloads[cohort.aircrafttype]=self:CountPayloadsInStock(MissionTypes,cohort.aircrafttype,Payloads) +self:T3(self.lid..string.format("Got Npayloads=%d for type=%s",Npayloads[cohort.aircrafttype],cohort.aircrafttype)) +end +end +for _,_cohort in pairs(self.cohorts)do +local cohort=_cohort +local n=cohort:CountAssets(true,MissionTypes,Attributes) +local p=Npayloads[cohort.aircrafttype]or 0 +local m=math.min(n,p) +N=N+m +Npayloads[cohort.aircrafttype]=Npayloads[cohort.aircrafttype]-m +end +return N +end +function LEGION:CountAssetsOnMission(MissionTypes,Cohort) +local Nq=0 +local Np=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if AUFTRAG.CheckMissionType(mission.type,MissionTypes or AUFTRAG.Type)then +for _,_asset in pairs(mission.assets or{})do +local asset=_asset +if asset.wid==self.uid then +if Cohort==nil or Cohort.name==asset.squadname then +local request,isqueued=self:GetRequestByID(mission.requestID[self.alias]) +if isqueued then +Nq=Nq+1 +else +Np=Np+1 +end +end +end +end +end +end +return Np+Nq,Np,Nq +end +function LEGION:GetAssetsOnMission(MissionTypes) +local assets={} +local Np=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if AUFTRAG.CheckMissionType(mission.type,MissionTypes)then +for _,_asset in pairs(mission.assets or{})do +local asset=_asset +if asset.wid==self.uid then +table.insert(assets,asset) +end +end +end +end +return assets +end +function LEGION:GetAircraftTypes(onlyactive,cohorts) +local unittypes={} +for _,_cohort in pairs(cohorts or self.cohorts)do +local cohort=_cohort +if(not onlyactive)or cohort:IsOnDuty()then +local gotit=false +for _,unittype in pairs(unittypes)do +if cohort.aircrafttype==unittype then +gotit=true +break +end +end +if not gotit then +table.insert(unittypes,cohort.aircrafttype) +end +end +end +return unittypes +end +function LEGION:_CountPayloads(MissionType,Cohorts,Payloads) +local Npayloads={} +for _,_cohort in pairs(Cohorts)do +local cohort=_cohort +if Npayloads[cohort.aircrafttype]==nil then +Npayloads[cohort.aircrafttype]=cohort.legion:IsAirwing()and self:CountPayloadsInStock(MissionType,cohort.aircrafttype,Payloads)or 999 +self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s",Npayloads[cohort.aircrafttype],MissionType,cohort.aircrafttype)) +end +end +return Npayloads +end +function LEGION:RecruitAssetsForMission(Mission) +local NreqMin,NreqMax=Mission:GetRequiredAssets() +local TargetVec2=Mission:GetTargetVec2() +local Payloads=Mission.payloads +local MaxWeight=nil +if Mission.NcarriersMin then +local legions={self} +local cohorts=self.cohorts +if Mission.transportLegions or Mission.transportCohorts then +legions=Mission.transportLegions +cohorts=Mission.transportCohorts +end +local Cohorts=LEGION._GetCohorts(legions,cohorts) +local transportcohorts={} +for _,_cohort in pairs(Cohorts)do +local cohort=_cohort +local can=LEGION._CohortCan(cohort,AUFTRAG.Type.OPSTRANSPORT,Mission.carrierCategories,Mission.carrierAttributes,Mission.carrierProperties,nil,TargetVec2) +if can and(MaxWeight==nil or cohort.cargobayLimit>MaxWeight)then +MaxWeight=cohort.cargobayLimit +end +end +self:T(self.lid..string.format("Largest cargo bay available=%.1f",MaxWeight or 0)) +end +local legions={self} +local cohorts=self.cohorts +if Mission.specialLegions or Mission.specialCohorts then +legions=Mission.specialLegions +cohorts=Mission.specialCohorts +end +local Cohorts=LEGION._GetCohorts(legions,cohorts,Operation,OpsQueue) +local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,Mission.type,Mission.alert5MissionType,NreqMin,NreqMax,TargetVec2,Payloads, +Mission.engageRange,Mission.refuelSystem,nil,nil,MaxWeight,nil,Mission.attributes,Mission.properties,{Mission.engageWeaponType}) +return recruited,assets,legions +end +function LEGION:RecruitAssetsForTransport(Transport) +local cargoOpsGroups=Transport:GetCargoOpsGroups(false) +local weightGroup=0 +local TotalWeight=nil +if#cargoOpsGroups>0 then +TotalWeight=0 +for _,_opsgroup in pairs(cargoOpsGroups)do +local opsgroup=_opsgroup +local weight=opsgroup:GetWeightTotal() +if weight>weightGroup then +weightGroup=weight +end +TotalWeight=TotalWeight+weight +end +else +return false +end +local TargetVec2=Transport:GetDeployZone():GetVec2() +local NreqMin,NreqMax=Transport:GetRequiredCarriers() +local recruited,assets,legions=LEGION.RecruitCohortAssets(self.cohorts,AUFTRAG.Type.OPSTRANSPORT,nil,NreqMin,NreqMax,TargetVec2,nil,nil,nil,weightGroup,TotalWeight) +return recruited,assets,legions +end +function LEGION:RecruitAssetsForEscort(Mission,Assets) +if Mission.NescortMin and Mission.NescortMax and(Mission.NescortMin>0 or Mission.NescortMax>0)then +self:T(self.lid..string.format("Requested escort for mission %s [%s]. Required assets=%d-%d",Mission:GetName(),Mission:GetType(),Mission.NescortMin,Mission.NescortMax)) +local Cohorts={} +for _,_legion in pairs(Mission.escortLegions or{})do +local legion=_legion +for _,_cohort in pairs(legion.cohorts)do +local cohort=_cohort +table.insert(Cohorts,cohort) +end +end +for _,_cohort in pairs(Mission.escortCohorts or{})do +local cohort=_cohort +table.insert(Cohorts,cohort) +end +if#Cohorts==0 then +Cohorts=self.cohorts +end +local assigned=LEGION.AssignAssetsForEscort(self,Cohorts,Assets,Mission.NescortMin,Mission.NescortMax,Mission.escortMissionType,Mission.escortTargetTypes) +return assigned +end +return true +end +function LEGION._GetCohorts(Legions,Cohorts,Operation,OpsQueue) +OpsQueue=OpsQueue or{} +local function CheckOperation(LegionOrCohort) +if#OpsQueue==0 then +return true +end +local isAvail=true +if Operation then +isAvail=false +end +for _,_operation in pairs(OpsQueue)do +local operation=_operation +local isOps=operation:IsAssignedCohortOrLegion(LegionOrCohort) +if isOps and operation:IsRunning()then +isAvail=false +if Operation==nil then +return false +else +if Operation.uid==operation.uid then +return true +end +end +end +end +return isAvail +end +local cohorts={} +if(Legions and#Legions>0)or(Cohorts and#Cohorts>0)then +for _,_legion in pairs(Legions or{})do +local legion=_legion +local Runway=true +if legion:IsAirwing()then +Runway=legion:IsRunwayOperational()and legion.airbase and legion.airbase:GetCoalition()==legion:GetCoalition() +end +if legion:IsRunning()and Runway then +for _,_cohort in pairs(legion.cohorts)do +local cohort=_cohort +if(CheckOperation(cohort.legion)or CheckOperation(cohort))and not UTILS.IsInTable(cohorts,cohort,"name")then +table.insert(cohorts,cohort) +end +end +end +end +for _,_cohort in pairs(Cohorts or{})do +local cohort=_cohort +if CheckOperation(cohort)and not UTILS.IsInTable(cohorts,cohort,"name")then +table.insert(cohorts,cohort) +end +end +end +return cohorts +end +function LEGION._CohortCan(Cohort,MissionType,Categories,Attributes,Properties,WeaponTypes,TargetVec2,RangeMax,RefuelSystem,CargoWeight,MaxWeight) +local function CheckCategory(_cohort) +local cohort=_cohort +if Categories and#Categories>0 then +for _,category in pairs(Categories)do +if category==cohort.category then +return true +end +end +else +return true +end +end +local function CheckAttribute(_cohort) +local cohort=_cohort +if Attributes and#Attributes>0 then +for _,attribute in pairs(Attributes)do +if attribute==cohort.attribute then +return true +end +end +else +return true +end +end +local function CheckProperty(_cohort) +local cohort=_cohort +if Properties and#Properties>0 then +for _,Property in pairs(Properties)do +for property,value in pairs(cohort.properties)do +if Property==property then +return true +end +end +end +else +return true +end +end +local function CheckWeapon(_cohort) +local cohort=_cohort +if WeaponTypes and#WeaponTypes>0 then +for _,WeaponType in pairs(WeaponTypes)do +if WeaponType==ENUMS.WeaponFlag.Auto then +return true +else +for _,_weaponData in pairs(cohort.weaponData or{})do +local weaponData=_weaponData +if weaponData.BitType==WeaponType then +return true +end +end +end +end +return false +else +return true +end +end +local function CheckRange(_cohort) +local cohort=_cohort +local TargetDistance=TargetVec2 and UTILS.VecDist2D(TargetVec2,cohort.legion:GetVec2())or 0 +local Rmax=cohort:GetMissionRange(WeaponTypes) +local RangeMax=RangeMax or 0 +local InRange=(RangeMax and math.max(RangeMax,Rmax)or Rmax)>=TargetDistance +return InRange +end +local function CheckRefueling(_cohort) +local cohort=_cohort +if RefuelSystem then +if cohort.tankerSystem then +return RefuelSystem==cohort.tankerSystem +else +return false +end +else +return true +end +end +local function CheckCargoWeight(_cohort) +local cohort=_cohort +if CargoWeight~=nil then +return cohort.cargobayLimit>=CargoWeight +else +return true +end +end +local function CheckMaxWeight(_cohort) +local cohort=_cohort +if MaxWeight~=nil then +cohort:T(string.format("Cohort weight=%.1f | max weight=%.1f",cohort.weightAsset,MaxWeight)) +return cohort.weightAsset<=MaxWeight +else +return true +end +end +local can=AUFTRAG.CheckMissionCapability(MissionType,Cohort.missiontypes) +if can then +can=CheckCategory(Cohort) +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of mission types",Cohort.name)) +return false +end +if can then +if MissionType==AUFTRAG.Type.RELOCATECOHORT then +can=Cohort:IsRelocating() +else +can=Cohort:IsOnDuty() +end +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of category",Cohort.name)) +return false +end +if can then +can=CheckAttribute(Cohort) +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of readyiness",Cohort.name)) +return false +end +if can then +can=CheckProperty(Cohort) +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of attribute",Cohort.name)) +return false +end +if can then +can=CheckWeapon(Cohort) +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of property",Cohort.name)) +return false +end +if can then +can=CheckRange(Cohort) +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of weapon type",Cohort.name)) +return false +end +if can then +can=CheckRefueling(Cohort) +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of range",Cohort.name)) +return false +end +if can then +can=CheckCargoWeight(Cohort) +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of refueling system",Cohort.name)) +return false +end +if can then +can=CheckMaxWeight(Cohort) +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of cargo weight",Cohort.name)) +return false +end +if can then +return true +else +Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of max weight",Cohort.name)) +return false +end +return nil +end +function LEGION.RecruitCohortAssets(Cohorts,MissionTypeRecruit,MissionTypeOpt,NreqMin,NreqMax,TargetVec2,Payloads,RangeMax,RefuelSystem,CargoWeight,TotalWeight,MaxWeight,Categories,Attributes,Properties,WeaponTypes) +local Assets={} +local Legions={} +if MissionTypeOpt==nil then +MissionTypeOpt=MissionTypeRecruit +end +for _,_cohort in pairs(Cohorts)do +local cohort=_cohort +local can=LEGION._CohortCan(cohort,MissionTypeRecruit,Categories,Attributes,Properties,WeaponTypes,TargetVec2,RangeMax,RefuelSystem,CargoWeight,MaxWeight) +if can then +local assets,npayloads=cohort:RecruitAssets(MissionTypeRecruit,999) +for _,asset in pairs(assets)do +table.insert(Assets,asset) +end +end +end +if#Assets==0 then +return false,{},{} +end +LEGION._OptimizeAssetSelection(Assets,MissionTypeOpt,TargetVec2,false,TotalWeight) +for _,_asset in pairs(Assets)do +local asset=_asset +if asset.legion:IsAirwing()and not asset.payload then +asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype,MissionTypeOpt,Payloads) +end +end +for i=#Assets,1,-1 do +local asset=Assets[i] +if asset.legion:IsAirwing()and not asset.payload then +table.remove(Assets,i) +end +end +LEGION._OptimizeAssetSelection(Assets,MissionTypeOpt,TargetVec2,true,TotalWeight) +local Nassets=math.min(#Assets,NreqMax) +if#Assets>=NreqMin then +local cargobay=0 +for i=1,Nassets do +local asset=Assets[i] +asset.isReserved=true +Legions[asset.legion.alias]=asset.legion +if TotalWeight then +local N=math.floor(asset.cargobaytot/asset.nunits/CargoWeight)*asset.nunits +cargobay=cargobay+N*CargoWeight +if cargobay>=TotalWeight then +Nassets=i +break +end +end +end +for i=#Assets,Nassets+1,-1 do +local asset=Assets[i] +if asset.legion:IsAirwing()and not asset.spawned then +asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s",asset.spawngroupname)) +asset.legion:ReturnPayloadFromAsset(asset) +end +table.remove(Assets,i) +end +return true,Assets,Legions +else +for i=1,#Assets do +local asset=Assets[i] +if asset.legion:IsAirwing()and not asset.spawned then +asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s",asset.spawngroupname)) +asset.legion:ReturnPayloadFromAsset(asset) +end +end +return false,{},{} +end +return false,{},{} +end +function LEGION.UnRecruitAssets(Assets,Mission) +for i=1,#Assets do +local asset=Assets[i] +asset.isReserved=false +if asset.legion:IsAirwing()and not asset.spawned then +asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s",asset.spawngroupname)) +asset.legion:ReturnPayloadFromAsset(asset) +end +if Mission then +Mission:DelAsset(asset) +end +end +end +function LEGION:AssignAssetsForEscort(Cohorts,Assets,NescortMin,NescortMax,MissionType,TargetTypes,EngageRange) +if NescortMin and NescortMax and(NescortMin>0 or NescortMax>0)then +self:T(self.lid..string.format("Requested escort for %d assets from %d cohorts. Required escort assets=%d-%d",#Assets,#Cohorts,NescortMin,NescortMax)) +local Escorts={} +local EscortAvail=true +for _,_asset in pairs(Assets)do +local asset=_asset +local TargetVec2=asset.legion:GetVec2() +local Categories={Group.Category.HELICOPTER} +local targetTypes={"Ground Units"} +if asset.category==Group.Category.AIRPLANE then +Categories={Group.Category.AIRPLANE} +targetTypes={"Air"} +end +TargetTypes=TargetTypes or targetTypes +local Erecruited,eassets,elegions=LEGION.RecruitCohortAssets(Cohorts,AUFTRAG.Type.ESCORT,MissionType,NescortMin,NescortMax,TargetVec2,nil,nil,nil,nil,nil,nil,Categories) +if Erecruited then +Escorts[asset.spawngroupname]={EscortLegions=elegions,EscortAssets=eassets,ecategory=asset.category} +else +EscortAvail=false +break +end +end +if EscortAvail then +local N=0 +for groupname,value in pairs(Escorts)do +local Elegions=value.EscortLegions +local Eassets=value.EscortAssets +local ecategory=value.ecategory +for _,_legion in pairs(Elegions)do +local legion=_legion +local OffsetVector=nil +if ecategory==Group.Category.GROUND then +OffsetVector={} +OffsetVector.x=0 +OffsetVector.y=UTILS.FeetToMeters(1000) +OffsetVector.z=0 +elseif MissionType==AUFTRAG.Type.SEAD then +OffsetVector={} +OffsetVector.x=-100 +OffsetVector.y=500 +OffsetVector.z=500 +end +local escort=AUFTRAG:NewESCORT(groupname,OffsetVector,EngageRange,TargetTypes) +if MissionType==AUFTRAG.Type.SEAD then +escort.missionTask=ENUMS.MissionTask.SEAD +local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil) +table.insert(escort.enrouteTasks,DCStask) +end +for _,_asset in pairs(Eassets)do +local asset=_asset +escort:AddAsset(asset) +N=N+1 +end +self:MissionAssign(escort,{legion}) +end +end +self:T(self.lid..string.format("Recruited %d escort assets",N)) +return true +else +self:T(self.lid..string.format("Could not get at least one escort!")) +for groupname,value in pairs(Escorts)do +local Eassets=value.EscortAssets +LEGION.UnRecruitAssets(Eassets) +end +return false +end +else +self:T(self.lid..string.format("No escort required! NescortMin=%s, NescortMax=%s",tostring(NescortMin),tostring(NescortMax))) +return true +end +end +function LEGION:AssignAssetsForTransport(Legions,CargoAssets,NcarriersMin,NcarriersMax,DeployZone,DisembarkZone,Categories,Attributes,Properties) +if NcarriersMin and NcarriersMax and(NcarriersMin>0 or NcarriersMax>0)then +local Cohorts=LEGION._GetCohorts(Legions) +local CargoLegions={};local CargoWeight=nil;local TotalWeight=0 +for _,_asset in pairs(CargoAssets)do +local asset=_asset +CargoLegions[asset.legion.alias]=asset.legion +if CargoWeight==nil or asset.weight>CargoWeight then +CargoWeight=asset.weight +end +TotalWeight=TotalWeight+asset.weight +end +self:T(self.lid..string.format("Cargo weight=%.1f",CargoWeight)) +self:T(self.lid..string.format("Total weight=%.1f",TotalWeight)) +local TargetVec2=DeployZone:GetVec2() +local TransportAvail,CarrierAssets,CarrierLegions= +LEGION.RecruitCohortAssets(Cohorts,AUFTRAG.Type.OPSTRANSPORT,nil,NcarriersMin,NcarriersMax,TargetVec2,nil,nil,nil,CargoWeight,TotalWeight,nil,Categories,Attributes,Properties) +if TransportAvail then +local Transport=OPSTRANSPORT:New(nil,nil,DeployZone) +if DisembarkZone then +Transport:SetDisembarkZone(DisembarkZone) +end +self:T(self.lid..string.format("Transport available with %d carrier assets",#CarrierAssets)) +for _,_legion in pairs(CargoLegions)do +local legion=_legion +local pickupzone=legion.spawnzone +local tpz=Transport:AddTransportZoneCombo(nil,pickupzone,Transport:GetDeployZone()) +tpz.PickupAirbase=legion:IsRunwayOperational()and legion.airbase or nil +Transport:SetEmbarkZone(legion.spawnzone,tpz) +for _,_asset in pairs(CargoAssets)do +local asset=_asset +if asset.legion.alias==legion.alias then +Transport:AddAssetCargo(asset,tpz) +end +end +end +for _,_asset in pairs(CarrierAssets)do +local asset=_asset +Transport:AddAsset(asset) +end +self:TransportAssign(Transport,CarrierLegions) +return true,Transport +else +self:T(self.lid..string.format("Transport assets could not be allocated ==> Unrecruiting assets")) +LEGION.UnRecruitAssets(CarrierAssets) +return false,nil +end +return nil,nil +end +return true,nil +end +function LEGION.CalculateAssetMissionScore(asset,MissionType,TargetVec2,IncludePayload,TotalWeight) +local score=0 +if asset.skill==AI.Skill.AVERAGE then +score=score+0 +elseif asset.skill==AI.Skill.GOOD then +score=score+10 +elseif asset.skill==AI.Skill.HIGH then +score=score+20 +elseif asset.skill==AI.Skill.EXCELLENT then +score=score+30 +end +score=score+asset.cohort:GetMissionPeformance(MissionType) +local function scorePayload(Payload,MissionType) +for _,Capability in pairs(Payload.capabilities)do +local capability=Capability +if capability.MissionType==MissionType then +return capability.Performance +end +end +return 0 +end +if IncludePayload and asset.payload then +score=score+scorePayload(asset.payload,MissionType) +end +local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2()or asset.legion:GetVec2() +local distance=0 +if TargetVec2 and OrigVec2 then +distance=UTILS.MetersToNM(UTILS.VecDist2D(OrigVec2,TargetVec2)) +if asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then +distance=UTILS.Round(distance/10,0) +else +distance=UTILS.Round(distance,0) +end +end +score=score-distance +if asset.spawned and asset.flightgroup and asset.flightgroup:IsAlive()then +local currmission=asset.flightgroup:GetMissionCurrent() +if currmission then +if currmission.type==AUFTRAG.Type.ALERT5 and currmission.alert5MissionType==MissionType then +score=score+25 +elseif(currmission.type==AUFTRAG.Type.GCICAP or currmission.type==AUFTRAG.Type.PATROLRACETRACK)and MissionType==AUFTRAG.Type.INTERCEPT then +score=score+35 +elseif(currmission.type==AUFTRAG.Type.ONGUARD or currmission.type==AUFTRAG.Type.PATROLZONE)and(MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK)then +score=score+25 +elseif currmission.type==AUFTRAG.Type.NOTHING then +score=score+30 +end +end +if MissionType==AUFTRAG.Type.OPSTRANSPORT or MissionType==AUFTRAG.Type.AMMOSUPPLY or MissionType==AUFTRAG.Type.AWACS or MissionType==AUFTRAG.Type.FUELSUPPLY or MissionType==AUFTRAG.Type.TANKER then +score=score-10 +else +if asset.flightgroup:IsOutOfAmmo()then +score=score-1000 +end +end +end +if MissionType==AUFTRAG.Type.OPSTRANSPORT then +if TotalWeight then +if asset.cargobaymax=2 then +asset.legion:I(asset.legion.lid..string.format("Asset %s [spawned=%s] score=%d",asset.spawngroupname,tostring(asset.spawned),score)) +end +return score +end +function LEGION._OptimizeAssetSelection(assets,MissionType,TargetVec2,IncludePayload,TotalWeight) +for _,_asset in pairs(assets)do +local asset=_asset +asset.score=LEGION.CalculateAssetMissionScore(asset,MissionType,TargetVec2,IncludePayload,TotalWeight) +if IncludePayload then +local RandomScoreMax=asset.legion and asset.legion.RandomAssetScore or LEGION.RandomAssetScore +local RandomScore=math.random(0,RandomScoreMax) +asset.score=asset.score+RandomScore +end +end +local function optimize(a,b) +local assetA=a +local assetB=b +return(assetA.score>assetB.score) +end +table.sort(assets,optimize) +if LEGION.verbose>0 then +local text=string.format("Optimized %d assets for %s mission/transport (payload=%s):",#assets,MissionType,tostring(IncludePayload)) +for i,Asset in pairs(assets)do +local asset=Asset +text=text..string.format("\n%d. %s [%s]: score=%d",i,asset.spawngroupname,asset.squadname,asset.score or-1) +asset.score=nil +end +env.info(text) +end +end +function LEGION:GetMissionByID(mid) +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission.auftragsnummer==tonumber(mid)then +return mission +end +end +return nil +end +function LEGION:GetTransportByID(uid) +for _,_transport in pairs(self.transportqueue)do +local transport=_transport +if transport.uid==tonumber(uid)then +return transport +end +end +return nil +end +function LEGION:GetMissionFromRequestID(RequestID) +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +local mid=mission.requestID[self.alias] +if mid and mid==RequestID then +return mission +end +end +return nil +end +function LEGION:GetMissionFromRequest(Request) +return self:GetMissionFromRequestID(Request.uid) +end +function LEGION:FetchPayloadFromStock(UnitType,MissionType,Payloads) +return nil +end +function LEGION:ReturnPayloadFromAsset(asset) +return nil +end +NAVYGROUP={ +ClassName="NAVYGROUP", +turning=false, +intowind=nil, +intowindcounter=0, +Qintowind={}, +pathCorridor=400, +engage={}, +} +NAVYGROUP.version="1.0.3" +function NAVYGROUP:New(group) +local og=_DATABASE:GetOpsGroup(group) +if og then +og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) +return og +end +local self=BASE:Inherit(self,OPSGROUP:New(group)) +self.lid=string.format("NAVYGROUP %s | ",self.groupname) +self:SetDefaultROE() +self:SetDefaultAlarmstate() +self:SetDefaultEPLRS(self.isEPLRS) +self:SetDefaultEmission() +self:SetDetection() +self:SetPatrolAdInfinitum(true) +self:SetPathfinding(false) +self:AddTransition("*","FullStop","Holding") +self:AddTransition("*","Cruise","Cruising") +self:AddTransition("*","RTZ","Returning") +self:AddTransition("Returning","Returned","Returned") +self:AddTransition("*","Detour","Cruising") +self:AddTransition("*","DetourReached","*") +self:AddTransition("*","Retreat","Retreating") +self:AddTransition("Retreating","Retreated","Retreated") +self:AddTransition("Cruising","EngageTarget","Engaging") +self:AddTransition("Holding","EngageTarget","Engaging") +self:AddTransition("OnDetour","EngageTarget","Engaging") +self:AddTransition("Engaging","Disengage","Cruising") +self:AddTransition("*","TurnIntoWind","Cruising") +self:AddTransition("*","TurnedIntoWind","*") +self:AddTransition("*","TurnIntoWindStop","*") +self:AddTransition("*","TurnIntoWindOver","*") +self:AddTransition("*","TurningStarted","*") +self:AddTransition("*","TurningStopped","*") +self:AddTransition("*","CollisionWarning","*") +self:AddTransition("*","ClearAhead","*") +self:AddTransition("Cruising","Dive","Cruising") +self:AddTransition("Engaging","Dive","Engaging") +self:AddTransition("Cruising","Surface","Cruising") +self:AddTransition("Engaging","Surface","Engaging") +self:_InitWaypoints() +self:_InitGroup() +self:HandleEvent(EVENTS.Birth,self.OnEventBirth) +self:HandleEvent(EVENTS.Dead,self.OnEventDead) +self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit) +self:HandleEvent(EVENTS.UnitLost,self.OnEventRemoveUnit) +self.timerStatus=TIMER:New(self.Status,self):Start(1,30) +self.timerQueueUpdate=TIMER:New(self._QueueUpdate,self):Start(2,5) +self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(2,60) +_DATABASE:AddOpsGroup(self) +return self +end +function NAVYGROUP:SetPatrolAdInfinitum(switch) +if switch==false then +self.adinfinitum=false +else +self.adinfinitum=true +end +return self +end +function NAVYGROUP:SetPathfinding(Switch,CorridorWidth) +self.pathfindingOn=Switch +self.pathCorridor=CorridorWidth or 400 +return self +end +function NAVYGROUP:SetPathfindingOn(CorridorWidth) +self:SetPathfinding(true,CorridorWidth) +return self +end +function NAVYGROUP:SetPathfindingOff() +self:SetPathfinding(false,self.pathCorridor) +return self +end +function NAVYGROUP:SetIntoWindLegacy(SwitchOn) +if SwitchOn==nil then +SwitchOn=true +end +self.intowindold=SwitchOn +return self +end +function NAVYGROUP:AddTaskFireAtPoint(Coordinate,Clock,Radius,Nshots,WeaponType,Prio) +local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType) +local task=self:AddTask(DCStask,Clock,nil,Prio) +return task +end +function NAVYGROUP:AddTaskWaypointFireAtPoint(Coordinate,Waypoint,Radius,Nshots,WeaponType,Prio,Duration) +Waypoint=Waypoint or self:GetWaypointNext() +local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType) +local task=self:AddTaskWaypoint(DCStask,Waypoint,nil,Prio,Duration) +return task +end +function NAVYGROUP:AddTaskAttackGroup(TargetGroup,WeaponExpend,WeaponType,Clock,Prio) +local DCStask=CONTROLLABLE.TaskAttackGroup(nil,TargetGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack) +local task=self:AddTask(DCStask,Clock,nil,Prio) +return task +end +function NAVYGROUP:_CreateTurnIntoWind(starttime,stoptime,speed,uturn,offset) +local Tnow=timer.getAbsTime() +if starttime and type(starttime)=="number"then +starttime=UTILS.SecondsToClock(Tnow+starttime) +end +starttime=starttime or UTILS.SecondsToClock(Tnow) +local Tstart=UTILS.ClockToSeconds(starttime) +if uturn==nil then +uturn=true +end +local Tstop=Tstart+90*60 +if stoptime==nil then +Tstop=Tstart+90*60 +elseif type(stoptime)=="number"then +Tstop=Tstart+stoptime +else +Tstop=UTILS.ClockToSeconds(stoptime) +end +if Tstart>Tstop then +self:E(string.format("ERROR:Into wind stop time %s lies before start time %s. Input rejected!",UTILS.SecondsToClock(Tstart),UTILS.SecondsToClock(Tstop))) +return self +end +if Tstop<=Tnow then +self:E(string.format("WARNING: Into wind stop time %s already over. Tnow=%s! Input rejected.",UTILS.SecondsToClock(Tstop),UTILS.SecondsToClock(Tnow))) +return self +end +self.intowindcounter=self.intowindcounter+1 +local recovery={} +recovery.Tstart=Tstart +recovery.Tstop=Tstop +recovery.Open=false +recovery.Over=false +recovery.Speed=speed or 20 +recovery.Uturn=uturn and uturn or false +recovery.Offset=offset or 0 +recovery.Id=self.intowindcounter +return recovery +end +function NAVYGROUP:AddTurnIntoWind(starttime,stoptime,speed,uturn,offset) +local recovery=self:_CreateTurnIntoWind(starttime,stoptime,speed,uturn,offset) +table.insert(self.Qintowind,recovery) +return recovery +end +function NAVYGROUP:GetTurnIntoWind(TID) +if TID then +for _,_turn in pairs(self.Qintowind)do +local turn=_turn +if turn.Id==TID then +return turn +end +end +else +return self.intowind +end +return nil +end +function NAVYGROUP:ExtendTurnIntoWind(Duration,TurnIntoWind) +Duration=Duration or 300 +local TID=TurnIntoWind and TurnIntoWind.Id or nil +local turn=self:GetTurnIntoWind(TID) +if turn then +turn.Tstop=turn.Tstop+Duration +self:T(self.lid..string.format("Extending turn into wind by %d seconds. New stop time is %s",Duration,UTILS.SecondsToClock(turn.Tstop))) +else +self:E(self.lid.."Could not get turn into wind to extend!") +end +return self +end +function NAVYGROUP:RemoveTurnIntoWind(IntoWindData) +if self.intowind and self.intowind.Id==IntoWindData.Id then +self:TurnIntoWindStop() +return +end +for i,_tiw in pairs(self.Qintowind)do +local tiw=_tiw +if tiw.Id==IntoWindData.Id then +table.remove(self.Qintowind,i) +break +end +end +return self +end +function NAVYGROUP:IsHolding() +return self:Is("Holding") +end +function NAVYGROUP:IsCruising() +return self:Is("Cruising") +end +function NAVYGROUP:IsOnDetour() +return self:Is("OnDetour") +end +function NAVYGROUP:IsDiving() +return self:Is("Diving") +end +function NAVYGROUP:IsTurning() +return self.turning +end +function NAVYGROUP:IsSteamingIntoWind() +if self.intowind then +return true +else +return false +end +end +function NAVYGROUP:IsRecovering() +if self.intowind then +if self.intowind.Recovery==true then +return true +else +return false +end +else +return false +end +end +function NAVYGROUP:IsLaunching() +if self.intowind then +if self.intowind.Recovery==false then +return true +else +return false +end +else +return false +end +end +function NAVYGROUP:Status() +local fsmstate=self:GetState() +local alive=self:IsAlive() +local freepath=0 +if alive then +self:_UpdatePosition() +self:_CheckDetectedUnits() +self:_CheckTurning() +local disttoWP=math.min(self:GetDistanceToWaypoint(),UTILS.NMToMeters(10)) +freepath=disttoWP +if not self:IsTurning()then +freepath=self:_CheckFreePath(freepath,100) +if disttoWP>1 and freepathself.Twaiting+self.dTwait then +self.Twaiting=nil +self.dTwait=nil +if self:_CountPausedMissions()>0 then +self:UnpauseMission() +else +self:Cruise() +end +end +end +end +local mission=self:GetMissionCurrent() +if mission and mission.updateDCSTask then +if mission.type==AUFTRAG.Type.CAPTUREZONE then +local Task=mission:GetGroupWaypointTask(self) +if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then +self:_UpdateTask(Task,mission) +end +end +end +else +self:_CheckDamage() +end +if alive~=nil then +if self.verbose>=1 then +local nelem=self:CountElements() +local Nelem=#self.elements +local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks() +local nMissions=self:CountRemainingMissison() +local roe=self:GetROE()or-1 +local als=self:GetAlarmstate()or-1 +local wpidxCurr=self.currentwp +local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr)or 0 +local wpidxNext=self:GetWaypointIndexNext()or 0 +local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext)or 0 +local wpN=#self.waypoints or 0 +local wpF=tostring(self.passedfinalwp) +local speed=UTILS.MpsToKnots(self.velocity or 0) +local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) +local alt=self.position and self.position.y or 0 +local hdg=self.heading or 0 +local life=self.life or 0 +local ammo=self:GetAmmoTot().Total +local ndetected=self.detectionOn and tostring(self.detectedunits:Count())or"Off" +local cargo=0 +for _,_element in pairs(self.elements)do +local element=_element +cargo=cargo+element.weightCargo +end +local intowind=self:IsSteamingIntoWind()and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(),true)or"N/A" +local turning=tostring(self:IsTurning()) +local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f | Turn=%s Collision=%d IntoWind=%s", +fsmstate,nelem,Nelem,roe,als,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,wpN,wpF,life,speed,speedEx,hdg,ammo,ndetected,cargo,turning,freepath,intowind) +self:I(self.lid..text) +end +else +local text=string.format("State %s: Alive=%s",fsmstate,tostring(self:IsAlive())) +self:T(self.lid..text) +end +if alive and self.verbose>=2 and#self.Qintowind>0 then +local text=string.format(self.lid.."Turn into wind time windows:") +if#self.Qintowind==0 then +text=text.." none!" +end +for i,_recovery in pairs(self.Qintowind)do +local recovery=_recovery +local Cstart=UTILS.SecondsToClock(recovery.Tstart) +local Cstop=UTILS.SecondsToClock(recovery.Tstop) +text=text..string.format("\n[%d] ID=%d Start=%s Stop=%s Open=%s Over=%s",i,recovery.Id,Cstart,Cstop,tostring(recovery.Open),tostring(recovery.Over)) +end +self:I(self.lid..text) +end +if self.verbose>=2 then +local text="Elements:" +for i,_element in pairs(self.elements)do +local element=_element +local name=element.name +local status=element.status +local unit=element.unit +local life,life0=self:GetLifePoints(element) +local life0=element.life0 +local ammo=self:GetAmmoElement(element) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles,element.weightCargo,element.weightMaxCargo) +end +if#self.elements==0 then +text=text.." none!" +end +self:I(self.lid..text) +end +if self:IsCruising()and self.detectionOn and self.engagedetectedOn then +local targetgroup,targetdist=self:_GetDetectedTarget() +if targetgroup then +self:I(self.lid..string.format("Engaging target group %s at distance %d meters",targetgroup:GetName(),targetdist)) +self:EngageTarget(targetgroup) +end +end +self:_CheckCargoTransport() +self:_PrintTaskAndMissionStatus() +end +function NAVYGROUP:onafterElementSpawned(From,Event,To,Element) +self:T(self.lid..string.format("Element spawned %s",Element.name)) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED) +end +function NAVYGROUP:onafterSpawned(From,Event,To) +self:T(self.lid..string.format("Group spawned!")) +if self.verbose>=1 then +local text=string.format("Initialized Navy Group %s [GID=%d]:\n",self.groupname,self.group:GetID()) +text=text..string.format("Unit type = %s\n",self.actype) +text=text..string.format("Speed max = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax)) +text=text..string.format("Speed cruise = %.1f Knots\n",UTILS.KmphToKnots(self.speedCruise)) +text=text..string.format("Weight = %.1f kg\n",self:GetWeightTotal()) +text=text..string.format("Cargo bay = %.1f kg\n",self:GetFreeCargobay()) +text=text..string.format("Has EPLRS = %s\n",tostring(self.isEPLRS)) +text=text..string.format("Is Submarine = %s\n",tostring(self.isSubmarine)) +text=text..string.format("Elements = %d\n",#self.elements) +text=text..string.format("Waypoints = %d\n",#self.waypoints) +text=text..string.format("Radio = %.1f MHz %s %s\n",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu),tostring(self.radio.On)) +text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Rockets,self.ammo.Missiles,self.ammo.Torpedos) +text=text..string.format("FSM state = %s\n",self:GetState()) +text=text..string.format("Is alive = %s\n",tostring(self:IsAlive())) +text=text..string.format("LateActivate = %s\n",tostring(self:IsLateActivated())) +self:I(self.lid..text) +end +self:_UpdatePosition() +self.isDead=false +self.isDestroyed=false +if self.isAI then +self:SwitchROE(self.option.ROE) +self:SwitchAlarmstate(self.option.Alarm) +self:SwitchEmission(self.option.Emission) +self:SwitchEPLRS(self.option.EPLRS) +self:SwitchInvisible(self.option.Invisible) +self:SwitchImmortal(self.option.Immortal) +self:_SwitchTACAN() +self:_SwitchICLS() +if self.radioDefault then +else +self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,false) +end +if#self.waypoints>1 then +self:__Cruise(-0.1) +else +self:FullStop() +end +end +end +function NAVYGROUP:onbeforeUpdateRoute(From,Event,To,n,Speed,Depth) +local allowed=true +local trepeat=nil +if self:IsWaiting()then +self:T(self.lid.."Update route denied. Group is WAITING!") +return false +elseif self:IsInUtero()then +self:T(self.lid.."Update route denied. Group is INUTERO!") +return false +elseif self:IsDead()then +self:T(self.lid.."Update route denied. Group is DEAD!") +return false +elseif self:IsStopped()then +self:T(self.lid.."Update route denied. Group is STOPPED!") +return false +elseif self:IsHolding()then +self:T(self.lid.."Update route denied. Group is holding position!") +return false +elseif self:IsEngaging()then +self:T(self.lid.."Update route allowed. Group is engaging!") +return true +end +if self.taskcurrent>0 then +local task=self:GetTaskByID(self.taskcurrent) +if task then +if task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then +self:T2(self.lid.."Allowing update route for Task: PatrolZone") +elseif task.dcstask.id==AUFTRAG.SpecialTask.RECON then +self:T2(self.lid.."Allowing update route for Task: ReconMission") +elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then +self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") +elseif task.dcstask.id==AUFTRAG.SpecialTask.REARMING then +self:T2(self.lid.."Allowing update route for Task: Rearming") +else +local taskname=task and task.description or"No description" +self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s",self.taskcurrent,tostring(taskname))) +allowed=false +end +else +self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!",self.taskcurrent)) +allowed=false +end +end +if not self.isAI then +allowed=false +end +self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)",self:GetState(),tostring(allowed),tostring(trepeat))) +if trepeat then +self:__UpdateRoute(trepeat,n) +end +return allowed +end +function NAVYGROUP:onafterUpdateRoute(From,Event,To,n,N,Speed,Depth) +n=n or self:GetWaypointIndexNext() +N=N or#self.waypoints +N=math.min(N,#self.waypoints) +local waypoints={} +for i=n,N do +local wp=UTILS.DeepCopy(self.waypoints[i]) +if Speed then +wp.speed=UTILS.KnotsToMps(Speed) +else +if wp.speed<0.1 then +wp.speed=UTILS.KmphToMps(self.speedCruise) +end +end +if Depth then +wp.alt=-Depth +elseif self.depth then +wp.alt=-self.depth +else +wp.alt=wp.alt or 0 +end +if i==n then +self.speedWp=wp.speed +self.altWp=wp.alt +end +table.insert(waypoints,wp) +end +local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speedWp),self.altWp) +table.insert(waypoints,1,current) +if self:IsEngaging()or not self.passedfinalwp then +if self.verbose>=10 then +for i=1,#waypoints do +local wp=waypoints[i] +local text=string.format("%s Waypoint [%d] UID=%d speed=%d m/s",self.groupname,i-1,wp.uid or-1,wp.speed) +self:I(self.lid..text) +COORDINATE:NewFromWaypoint(wp):MarkToAll(text) +end +end +self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m",self.currentwp,n,#waypoints,#self.waypoints,UTILS.MpsToKnots(self.speedWp),self.altWp)) +self:Route(waypoints) +else +self:E(self.lid..string.format("WARNING: Passed final WP ==> Full Stop!")) +self:FullStop() +end +end +function NAVYGROUP:onafterDetour(From,Event,To,Coordinate,Speed,Depth,ResumeRoute) +Depth=Depth or 0 +Speed=Speed or self:GetSpeedCruise() +local uid=self:GetWaypointCurrent().uid +local wp=self:AddWaypoint(Coordinate,Speed,uid,Depth,true) +if ResumeRoute then +wp.detour=1 +else +wp.detour=0 +end +end +function NAVYGROUP:onafterDetourReached(From,Event,To) +self:T(self.lid.."Group reached detour coordinate.") +end +function NAVYGROUP:onafterTurnIntoWind(From,Event,To,IntoWind) +local heading,speed=self:GetHeadingIntoWind(IntoWind.Offset,IntoWind.Speed) +IntoWind.Heading=heading +IntoWind.Open=true +IntoWind.Coordinate=self:GetCoordinate(true) +self.intowind=IntoWind +self:T(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f, Tstart=%d Tstop=%d",IntoWind.Heading,speed,IntoWind.Tstart,IntoWind.Tstop)) +local distance=UTILS.NMToMeters(1000) +local coord=self:GetCoordinate() +local Coord=coord:Translate(distance,IntoWind.Heading) +local uid=self:GetWaypointCurrent().uid +local wptiw=self:AddWaypoint(Coord,speed,uid) +wptiw.intowind=true +IntoWind.waypoint=wptiw +if IntoWind.Uturn and false then +IntoWind.Coordinate:MarkToAll("Return coord") +end +end +function NAVYGROUP:onbeforeTurnIntoWindStop(From,Event,To) +if self.intowind then +return true +else +return false +end +end +function NAVYGROUP:onafterTurnIntoWindStop(From,Event,To) +self:TurnIntoWindOver(self.intowind) +end +function NAVYGROUP:onafterTurnIntoWindOver(From,Event,To,IntoWindData) +if IntoWindData and self.intowind and IntoWindData.Id==self.intowind.Id then +self:T2(self.lid.."Turn Into Wind Over!") +self.intowind.Over=true +self.intowind.Open=false +self:RemoveWaypointByID(self.intowind.waypoint.uid) +if self.intowind.Uturn then +self:T(self.lid.."FF Turn Into Wind Over ==> Uturn!") +local uid=self:GetWaypointCurrent().uid +local wp=self:AddWaypoint(self.intowind.Coordinate,self:GetSpeedCruise(),uid);wp.temp=true +else +local indx=self:GetWaypointIndexNext() +local speed=self:GetSpeedToWaypoint(indx) +self:T(self.lid..string.format("FF Turn Into Wind Over ==> Next WP Index=%d at %.1f knots via update route!",indx,speed)) +self:__UpdateRoute(-1,indx,nil,speed) +end +self.intowind=nil +self:RemoveTurnIntoWind(IntoWindData) +end +end +function NAVYGROUP:onafterFullStop(From,Event,To) +self:T(self.lid.."Full stop ==> holding") +local pos=self:GetCoordinate() +local wp=pos:WaypointNaval(0) +self:Route({wp}) +end +function NAVYGROUP:onafterCruise(From,Event,To,Speed) +self.Twaiting=nil +self.dTwait=nil +self.depth=nil +self:__UpdateRoute(-0.1,nil,nil,Speed) +end +function NAVYGROUP:onafterDive(From,Event,To,Depth,Speed) +Depth=Depth or 50 +self:I(self.lid..string.format("Diving to %d meters",Depth)) +self.depth=Depth +self:__UpdateRoute(-1,nil,nil,Speed) +end +function NAVYGROUP:onafterSurface(From,Event,To,Speed) +self.depth=0 +self:__UpdateRoute(-1,nil,nil,Speed) +end +function NAVYGROUP:onafterTurningStarted(From,Event,To) +self.turning=true +end +function NAVYGROUP:onafterTurningStopped(From,Event,To) +self.turning=false +self.collisionwarning=false +if self:IsSteamingIntoWind()then +self:TurnedIntoWind() +end +end +function NAVYGROUP:onafterCollisionWarning(From,Event,To,Distance) +self:T(self.lid..string.format("Iceberg ahead in %d meters!",Distance or-1)) +self.collisionwarning=true +end +function NAVYGROUP:onafterEngageTarget(From,Event,To,Target) +self:T(self.lid.."Engaging Target") +if Target:IsInstanceOf("TARGET")then +self.engage.Target=Target +else +self.engage.Target=TARGET:New(Target) +end +self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.9) +self.engage.roe=self:GetROE() +self.engage.alarmstate=self:GetAlarmstate() +self:SwitchAlarmstate(ENUMS.AlarmState.Auto) +self:SwitchROE(ENUMS.ROE.OpenFire) +local uid=self:GetWaypointCurrent().uid +self.engage.Waypoint=self:AddWaypoint(intercoord,nil,uid,Formation,true) +self.engage.Waypoint.detour=1 +end +function NAVYGROUP:_UpdateEngageTarget() +if self.engage.Target and self.engage.Target:IsAlive()then +local vec3=self.engage.Target:GetVec3() +if vec3 then +local dist=UTILS.VecDist3D(vec3,self.engage.Coordinate:GetVec3()) +if dist>100 then +self.engage.Coordinate:UpdateFromVec3(vec3) +local uid=self:GetWaypointCurrent().uid +self:RemoveWaypointByID(self.engage.Waypoint.uid) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.9) +self.engage.Waypoint=self:AddWaypoint(intercoord,nil,uid,Formation,true) +self.engage.Waypoint.detour=0 +end +else +self:Disengage() +end +else +self:Disengage() +end +end +function NAVYGROUP:onafterDisengage(From,Event,To) +self:T(self.lid.."Disengage Target") +self:SwitchROE(self.engage.roe) +self:SwitchAlarmstate(self.engage.alarmstate) +local task=self:GetTaskCurrent() +if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then +self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!") +self:TaskDone(task) +end +if self.engage.Waypoint then +self:RemoveWaypointByID(self.engage.Waypoint.uid) +end +self:_CheckGroupDone(1) +end +function NAVYGROUP:onafterOutOfAmmo(From,Event,To) +self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime())) +if self.retreatOnOutOfAmmo then +self:__Retreat(-1) +return +end +if self.rtzOnOutOfAmmo then +self:__RTZ(-1) +end +local task=self:GetTaskCurrent() +if task then +if task.dcstask.id=="FireAtPoint"or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then +self:T(self.lid..string.format("Cancelling current %s task because out of ammo!",task.dcstask.id)) +self:TaskCancel(task) +end +end +end +function NAVYGROUP:onafterRTZ(From,Event,To,Zone,Formation) +local zone=Zone or self.homezone +self:CancelAllMissions() +if zone then +if self:IsInZone(zone)then +self:Returned() +else +self:T(self.lid..string.format("RTZ to Zone %s",zone:GetName())) +local Coordinate=zone:GetRandomCoordinate() +local uid=self:GetWaypointCurrentUID() +local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true) +wp.detour=0 +end +else +self:T(self.lid.."ERROR: No RTZ zone given!") +end +end +function NAVYGROUP:onafterReturned(From,Event,To) +self:T(self.lid..string.format("Group returned")) +if self.legion then +self:T(self.lid..string.format("Adding group back to warehouse stock")) +self.legion:__AddAsset(10,self.group,1) +end +end +function NAVYGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Depth,Updateroute) +local coordinate=self:_CoordinateFromObject(Coordinate) +local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) +Speed=Speed or self:GetSpeedCruise() +local wp=coordinate:WaypointNaval(UTILS.KnotsToKmph(Speed),Depth) +local waypoint=self:_CreateWaypoint(wp) +if Depth then +waypoint.alt=UTILS.FeetToMeters(Depth) +end +self:_AddWaypoint(waypoint,wpnumber) +self:T(self.lid..string.format("Adding NAVAL waypoint index=%d uid=%d, speed=%.1f knots. Last waypoint passed was #%d. Total waypoints #%d",wpnumber,waypoint.uid,Speed,self.currentwp,#self.waypoints)) +if Updateroute==nil or Updateroute==true then +self:__UpdateRoute(-0.01) +end +return waypoint +end +function NAVYGROUP:_InitGroup(Template,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,NAVYGROUP._InitGroup,self,Template,0) +else +if self.groupinitialized then +self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") +return +end +local template=Template or self:_GetTemplate() +self.isAI=true +self.isLateActivated=template.lateActivation +self.isUncontrolled=false +self.speedMax=self.group:GetSpeedMax() +if self.speedMax and self.speedMax>3.6 then +self.isMobile=true +else +self.isMobile=false +self.speedMax=0 +end +self.speedCruise=self.speedMax*0.7 +self.ammo=self:GetAmmoTot() +self.radio.On=true +self.radio.Freq=tonumber(template.units[1].frequency)/1000000 +self.radio.Modu=tonumber(template.units[1].modulation) +self.optionDefault.Formation="Off Road" +self.option.Formation=self.optionDefault.Formation +if not self.tacanDefault then +self:SetDefaultTACAN(nil,nil,nil,nil,true) +end +if not self.tacan then +self.tacan=UTILS.DeepCopy(self.tacanDefault) +end +if not self.iclsDefault then +self:SetDefaultICLS(nil,nil,nil,true) +end +if not self.icls then +self.icls=UTILS.DeepCopy(self.iclsDefault) +end +local units=self.group:GetUnits() +local dcsgroup=Group.getByName(self.groupname) +local size0=dcsgroup:getInitialSize() +if#units~=size0 then +self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!",#units,size0)) +end +for _,unit in pairs(units)do +self:_AddElementByName(unit:GetName()) +end +self.groupinitialized=true +end +return self +end +function NAVYGROUP:_CheckFreePath(DistanceMax,dx) +local distance=DistanceMax or 5000 +local dx=dx or 100 +if self:IsTurning()then +return distance +end +local offsetY=0.1 +if UTILS.GetDCSMap()==DCSMAP.Caucasus then +offsetY=5.01 +end +local vec3=self:GetVec3() +vec3.y=offsetY +local heading=self:GetHeading() +local function LoS(dist) +local checkvec3=UTILS.VecTranslate(vec3,dist,heading) +local los=land.isVisible(vec3,checkvec3) +return los +end +if LoS(DistanceMax)then +return DistanceMax +end +local function check() +local xmin=0 +local xmax=DistanceMax +local Nmax=100 +local eps=100 +local N=1 +while N<=Nmax do +local d=xmax-xmin +local x=xmin+d/2 +local los=LoS(x) +self:T(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s",N,xmin,xmax,x,d,tostring(los))) +if los and d<=eps then +return x +end +if los then +xmin=x +else +xmax=x +end +N=N+1 +end +return 0 +end +local _check=check() +return _check +end +function NAVYGROUP:_CheckTurning() +local unit=self.group:GetUnit(1) +if unit and unit:IsAlive()then +local vNew=self.orientX +local vLast=self.orientXLast +vNew.y=0;vLast.y=0 +local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) +local turning=math.abs(deltaLast)>=2 +if self.turning and not turning then +self:TurningStopped() +elseif turning and not self.turning then +self:TurningStarted() +end +self.turning=turning +end +end +function NAVYGROUP:_CheckTurnsIntoWind() +local time=timer.getAbsTime() +if self.intowind then +if time>=self.intowind.Tstop then +self:TurnIntoWindOver(self.intowind) +end +else +local IntoWind=self:GetTurnIntoWindNext() +if IntoWind then +self:TurnIntoWind(IntoWind) +end +end +end +function NAVYGROUP:GetTurnIntoWindNext() +if#self.Qintowind>0 then +local time=timer.getAbsTime() +table.sort(self.Qintowind,function(a,b)return a.Tstart=recovery.Tstart and time0 and windSpeed<3 then +degreesAdjustment=30 +elseif windSpeed>=3 and windSpeed<5 then +degreesAdjustment=20 +elseif windSpeed>=5 and windSpeed<8 then +degreesAdjustment=8 +elseif windSpeed>=8 and windSpeed<13 then +degreesAdjustment=4 +elseif windSpeed>=13 then +degreesAdjustment=0 +end +return degreesAdjustment +end +Offset=Offset or 0 +local windfrom,vwind=self:GetWind() +local intowind=windfrom-Offset+adjustDegreesForWindSpeed(vwind) +if vwind<0.1 then +intowind=self:GetHeading() +end +if intowind<0 then +intowind=intowind+360 +end +local vtot=math.max(vdeck-UTILS.MpsToKnots(vwind),4) +return intowind,vtot +end +function NAVYGROUP:GetHeadingIntoWind_new(Offset,vdeck) +Offset=Offset or 0 +local windfrom,vwind=self:GetWind(18) +vwind=UTILS.MpsToKnots(vwind) +local windto=(windfrom+180)%360 +local alpha=math.rad(-Offset) +local Vmin=4 +local Vmax=UTILS.KmphToKnots(self.speedMax) +local C=math.sqrt(math.cos(alpha)^2/math.sin(alpha)^2+1) +local vdeckMax=vwind+math.cos(alpha)*Vmax +local vdeckMin=vwind+math.cos(alpha)*Vmin +local v=0 +local theta=0 +if vdeck>vdeckMax then +v=Vmax +theta=math.asin(v/(vwind*C))-math.asin(-1/C) +elseif vdeckvwind then +theta=math.pi/2 +v=math.sqrt(vdeck^2-vwind^2) +else +theta=math.asin(vdeck*math.sin(alpha)/vwind) +v=vdeck*math.cos(alpha)-vwind*math.cos(theta) +end +local intowind=(540+(windto+math.deg(theta)))%360 +self:T(self.lid..string.format("Heading into Wind: vship=%.1f, vwind=%.1f, WindTo=%03d°, Theta=%03d°, Heading=%03d",v,vwind,windto,theta,intowind)) +return intowind,v +end +function NAVYGROUP:GetHeadingIntoWind(Offset,vdeck) +if self.intowindold then +return self:GetHeadingIntoWind_old(Offset,vdeck) +else +return self:GetHeadingIntoWind_new(Offset,vdeck) +end +end +function NAVYGROUP:_FindPathToNextWaypoint() +self:T3(self.lid.."Path finding") +local astar=ASTAR:New() +local position=self:GetCoordinate() +local wpnext=self:GetWaypointNext() +if wpnext==nil then +return +end +local nextwp=wpnext.coordinate +if wpnext.intowind then +local hdg=self:GetHeading() +nextwp=position:Translate(UTILS.NMToMeters(20),hdg,true) +end +local speed=UTILS.MpsToKnots(wpnext.speed) +astar:SetStartCoordinate(position) +astar:SetEndCoordinate(nextwp) +local dist=position:Get2DDistance(nextwp) +if dist<5 then +return +end +local boxwidth=dist*2 +local spacex=dist*0.1 +local delta=dist/10 +astar:CreateGrid({land.SurfaceType.WATER},boxwidth,spacex,delta,delta,self.verbose>10) +astar:SetValidNeighbourLoS(self.pathCorridor) +local function findpath() +local path=astar:GetPath(true,true) +if path then +local uid=self:GetWaypointCurrent().uid +for i,_node in ipairs(path)do +local node=_node +local wp=self:AddWaypoint(node.coordinate,speed,uid) +wp.astar=true +uid=wp.uid +if self.verbose>=10 then +node.coordinate:MarkToAll(string.format("Path node #%d",i)) +end +end +return#path>0 +else +return false +end +end +return findpath() +end +OPERATION={ +ClassName="OPERATION", +verbose=0, +branches={}, +counterPhase=0, +counterBranch=0, +counterEdge=0, +cohorts={}, +legions={}, +targets={}, +missions={}, +} +_OPERATIONID=0 +OPERATION.PhaseStatus={ +PLANNED="Planned", +ACTIVE="Active", +OVER="Over", +} +OPERATION.version="0.2.0" +function OPERATION:New(Name) +local self=BASE:Inherit(self,FSM:New()) +_OPERATIONID=_OPERATIONID+1 +self.uid=_OPERATIONID +self.name=Name or string.format("Operation-%02d",_OPERATIONID) +self.lid=string.format("%s | ",self.name) +self:SetStartState("Planned") +self.branchMaster=self:AddBranch("Master") +self.conditionStart=CONDITION:New("Operation %s start",self.name) +self.conditionStart:SetNoneResult(false) +self.conditionStart:SetDefaultPersistence(false) +self.conditionOver=CONDITION:New("Operation %s over",self.name) +self.conditionOver:SetNoneResult(false) +self.conditionOver:SetDefaultPersistence(false) +self.branchActive=self.branchMaster +self:AddTransition("*","Start","Running") +self:AddTransition("*","StatusUpdate","*") +self:AddTransition("Running","Pause","Paused") +self:AddTransition("Paused","Unpause","Running") +self:AddTransition("*","PhaseOver","*") +self:AddTransition("*","PhaseNext","*") +self:AddTransition("*","PhaseChange","*") +self:AddTransition("*","BranchSwitch","*") +self:AddTransition("*","Over","Over") +self:AddTransition("*","Stop","Stopped") +self:__StatusUpdate(-1) +return self +end +function OPERATION:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function OPERATION:SetTime(ClockStart,ClockStop) +local Tnow=timer.getAbsTime() +local Tstart=Tnow+5 +if ClockStart and type(ClockStart)=="number"then +Tstart=Tnow+ClockStart +elseif ClockStart and type(ClockStart)=="string"then +Tstart=UTILS.ClockToSeconds(ClockStart) +end +local Tstop=nil +if ClockStop and type(ClockStop)=="number"then +Tstop=Tnow+ClockStop +elseif ClockStop and type(ClockStop)=="string"then +Tstop=UTILS.ClockToSeconds(ClockStop) +end +self.Tstart=Tstart +self.Tstop=Tstop +if Tstop then +self.duration=self.Tstop-self.Tstart +end +return self +end +function OPERATION:AddConditonOverAll(Function,...) +local cf=self.conditionOver:AddFunctionAll(Function,...) +return cf +end +function OPERATION:AddConditonOverAny(Phase,Function,...) +local cf=self.conditionOver:AddFunctionAny(Function,...) +return cf +end +function OPERATION:AddPhase(Name,Branch,Duration) +Branch=Branch or self.branchMaster +local phase=self:_CreatePhase(Name) +phase.branch=Branch +phase.duration=Duration +self:T(self.lid..string.format("Adding phase %s to branch %s",phase.name,Branch.name)) +table.insert(Branch.phases,phase) +return phase +end +function OPERATION:InsertPhaseAfter(PhaseAfter,Name) +for i=1,#self.phases do +local phase=self.phases[i] +if PhaseAfter.uid==phase.uid then +local phase=self:_CreatePhase(Name) +end +end +return nil +end +function OPERATION:GetName() +return self.name or"Unknown" +end +function OPERATION:GetPhaseByName(Name) +for _,_branch in pairs(self.branches)do +local branch=_branch +for _,_phase in pairs(branch.phases or{})do +local phase=_phase +if phase.name==Name then +return phase +end +end +end +return nil +end +function OPERATION:SetPhaseStatus(Phase,Status) +if Phase then +self:T(self.lid..string.format("Phase %s status: %s-->%s",tostring(Phase.name),tostring(Phase.status),tostring(Status))) +Phase.status=Status +if Phase.status==OPERATION.PhaseStatus.ACTIVE then +Phase.Tstart=timer.getAbsTime() +Phase.nActive=Phase.nActive+1 +elseif Phase.status==OPERATION.PhaseStatus.OVER then +self:PhaseOver(Phase) +end +end +return self +end +function OPERATION:GetPhaseStatus(Phase) +return Phase.status +end +function OPERATION:SetPhaseConditonOver(Phase,Condition) +if Phase then +self:T(self.lid..string.format("Setting phase %s conditon over %s",self:GetPhaseName(Phase),Condition and Condition.name or"None")) +Phase.conditionOver=Condition +end +return self +end +function OPERATION:AddPhaseConditonOverAll(Phase,Function,...) +if Phase then +local cf=Phase.conditionOver:AddFunctionAll(Function,...) +return cf +end +return nil +end +function OPERATION:AddPhaseConditonOverAny(Phase,Function,...) +if Phase then +local cf=Phase.conditionOver:AddFunctionAny(Function,...) +return cf +end +return nil +end +function OPERATION:SetConditionFunctionPersistence(ConditionFunction,IsPersistent) +ConditionFunction.persistence=IsPersistent +return self +end +function OPERATION:AddPhaseConditonRepeatAll(Phase,Function,...) +if Phase then +Phase.conditionRepeat:AddFunctionAll(Function,...) +end +return self +end +function OPERATION:GetPhaseConditonOver(Phase,Condition) +return Phase.conditionOver +end +function OPERATION:GetPhaseNactive(Phase) +return Phase.nActive +end +function OPERATION:GetPhaseName(Phase) +Phase=Phase or self.phase +if Phase then +return Phase.name +end +return"None" +end +function OPERATION:GetPhaseActive() +return self.phase +end +function OPERATION:GetPhaseIndex(Phase) +local branch=Phase.branch +for i,_phase in pairs(branch.phases)do +local phase=_phase +if phase.uid==Phase.uid then +return i,branch +end +end +return nil +end +function OPERATION:GetPhaseNext(Branch,PhaseStatus) +Branch=Branch or self:GetBranchActive() +local phases=Branch.phases or{} +local phase=nil +if self.phase and self.phase.branch.uid==Branch.uid then +phase=self.phase +end +local N=#phases +self:T(self.lid..string.format("Getting next phase! Branch=%s, Phases=%d, Status=%s",Branch.name,N,tostring(PhaseStatus))) +if N>0 then +if phase==nil and PhaseStatus==nil then +return phases[1] +end +local n=1 +if phase then +n=self:GetPhaseIndex(phase)+1 +end +for i=n,N do +local phase=phases[i] +if PhaseStatus==nil or PhaseStatus==phase.status then +return phase +end +end +end +return nil +end +function OPERATION:CountPhases(Status,Branch) +Branch=Branch or self.branchActive +local N=0 +for _,_phase in pairs(Branch.phases)do +local phase=_phase +if Status==nil or Status==phase.status then +N=N+1 +end +end +return N +end +function OPERATION:AddBranch(Name) +local branch=self:_CreateBranch(Name) +table.insert(self.branches,branch) +return branch +end +function OPERATION:GetBranchMaster() +return self.branchMaster +end +function OPERATION:GetBranchActive() +return self.branchActive or self.branchMaster +end +function OPERATION:GetBranchName(Branch) +Branch=Branch or self:GetBranchActive() +if Branch then +return Branch.name +end +return"None" +end +function OPERATION:AddEdge(PhaseFrom,PhaseTo,ConditionSwitch) +local edge={} +edge.phaseFrom=PhaseFrom +edge.phaseTo=PhaseTo +edge.branchFrom=PhaseFrom.branch +edge.branchTo=PhaseTo.branch +if ConditionSwitch then +edge.conditionSwitch=ConditionSwitch +else +edge.conditionSwitch=CONDITION:New("Edge") +edge.conditionSwitch:SetNoneResult(true) +end +table.insert(edge.branchFrom.edges,edge) +return edge +end +function OPERATION:AddEdgeConditonSwitchAll(Edge,Function,...) +if Edge then +local cf=Edge.conditionSwitch:AddFunctionAll(Function,...) +return cf +end +return nil +end +function OPERATION:AddMission(Mission,Phase) +Mission.phase=Phase +Mission.operation=self +table.insert(self.missions,Mission) +return self +end +function OPERATION:AddTarget(Target,Phase) +Target.phase=Phase +Target.operation=self +table.insert(self.targets,Target) +return self +end +function OPERATION:GetTargets(Phase) +local N={} +for _,_target in pairs(self.targets)do +local target=_target +if target:IsAlive()and(Phase==nil or target.phase==Phase)then +table.insert(N,target) +end +end +return N +end +function OPERATION:CountTargets(Phase) +local N=0 +for _,_target in pairs(self.targets)do +local target=_target +if target:IsAlive()and(Phase==nil or target.phase==Phase)then +N=N+1 +end +end +return N +end +function OPERATION:AssignCohort(Cohort) +self:T(self.lid..string.format("Assiging Cohort %s to operation",Cohort.name)) +self.cohorts[Cohort.name]=Cohort +end +function OPERATION:AssignLegion(Legion) +self.legions[Legion.alias]=Legion +end +function OPERATION:IsAssignedLegion(Legion) +local legion=self.legions[Legion.alias] +if legion then +self:T(self.lid..string.format("Legion %s is assigned to this operation",Legion.alias)) +return true +else +self:T(self.lid..string.format("Legion %s is NOT assigned to this operation",Legion.alias)) +return false +end +end +function OPERATION:IsAssignedCohort(Cohort) +local cohort=self.cohorts[Cohort.name] +if cohort then +self:T(self.lid..string.format("Cohort %s is assigned to this operation",Cohort.name)) +return true +else +local Legion=Cohort.legion +if Legion and self:IsAssignedLegion(Legion)then +self:T(self.lid..string.format("Legion %s of Cohort %s is assigned to this operation",Legion.alias,Cohort.name)) +return true +end +self:T(self.lid..string.format("Cohort %s is NOT assigned to this operation",Cohort.name)) +return false +end +return nil +end +function OPERATION:IsAssignedCohortOrLegion(Object) +local isAssigned=nil +if Object:IsInstanceOf("COHORT")then +isAssigned=self:IsAssignedCohort(Object) +elseif Object:IsInstanceOf("LEGION")then +isAssigned=self:IsAssignedLegion(Object) +else +self:E(self.lid.."ERROR: Unknown Object!") +end +return isAssigned +end +function OPERATION:IsPlanned() +local is=self:is("Planned") +return is +end +function OPERATION:IsRunning() +local is=self:is("Running") +return is +end +function OPERATION:IsPaused() +local is=self:is("Paused") +return is +end +function OPERATION:IsOver() +local is=self:is("Over") +return is +end +function OPERATION:IsStopped() +local is=self:is("Stopped") +return is +end +function OPERATION:IsNotOver() +local is=not(self:IsOver()or self:IsStopped()) +return is +end +function OPERATION:IsPhaseActive(Phase) +if Phase and Phase.status and Phase.status==OPERATION.PhaseStatus.ACTIVE then +return true +end +return false +end +function OPERATION:IsPhaseActive(Phase) +local phase=self:GetPhaseActive() +if phase and phase.uid==Phase.uid then +return true +else +return false +end +return nil +end +function OPERATION:IsPhasePlanned(Phase) +if Phase and Phase.status and Phase.status==OPERATION.PhaseStatus.PLANNED then +return true +end +return false +end +function OPERATION:IsPhaseOver(Phase) +if Phase and Phase.status and Phase.status==OPERATION.PhaseStatus.OVER then +return true +end +return false +end +function OPERATION:onafterStart(From,Event,To) +self:T(self.lid..string.format("Starting Operation!")) +return self +end +function OPERATION:onafterStatusUpdate(From,Event,To) +local Tnow=timer.getAbsTime() +local fsmstate=self:GetState() +if self:IsPlanned()then +if(self.Tstart and Tnow>self.Tstart or self.Tstart==nil)and(self.conditionStart==nil or self.conditionStart:Evaluate())then +self:Start() +end +elseif self:IsNotOver()then +if(self.Tstop and Tnow>self.Tstop or self.Tstop==nil)and(self.conditionOver==nil or self.conditionOver:Evaluate())then +self:Over() +end +end +if self:IsRunning()then +self:_CheckPhases() +end +if self.verbose>=1 then +local phaseName=self:GetPhaseName() +local branchName=self:GetBranchName() +local NphaseTot=self:CountPhases() +local NphaseAct=self:CountPhases(OPERATION.PhaseStatus.ACTIVE) +local NphasePla=self:CountPhases(OPERATION.PhaseStatus.PLANNED) +local NphaseOvr=self:CountPhases(OPERATION.PhaseStatus.OVER) +local text=string.format("State=%s: Phase=%s [%s], Phases=%d [Active=%d, Planned=%d, Over=%d]",fsmstate,phaseName,branchName,NphaseTot,NphaseAct,NphasePla,NphaseOvr) +self:I(self.lid..text) +end +if self.verbose>=2 then +local text="Phases:" +for i,_phase in pairs(self.branchActive.phases)do +local phase=_phase +text=text..string.format("\n[%d] %s [uid=%d]: status=%s Nact=%d",i,phase.name,phase.uid,tostring(phase.status),phase.nActive) +end +if text=="Phases:"then text=text.." None"end +self:I(self.lid..text) +end +self:__StatusUpdate(-30) +return self +end +function OPERATION:onafterPhaseNext(From,Event,To) +local Phase=self:GetPhaseNext() +if Phase then +self:PhaseChange(Phase) +else +self:Over() +end +return self +end +function OPERATION:onafterPhaseChange(From,Event,To,Phase) +local oldphase="None" +if self.phase then +if self.phase.status~=OPERATION.PhaseStatus.OVER then +self:SetPhaseStatus(self.phase,OPERATION.PhaseStatus.OVER) +end +oldphase=self.phase.name +end +self:I(self.lid..string.format("Phase change: %s --> %s",oldphase,Phase.name)) +self.phase=Phase +self:SetPhaseStatus(Phase,OPERATION.PhaseStatus.ACTIVE) +return self +end +function OPERATION:onafterPhaseOver(From,Event,To,Phase) +Phase.conditionOver:RemoveNonPersistant() +end +function OPERATION:onafterBranchSwitch(From,Event,To,Branch,Phase) +self:T(self.lid..string.format("Switching to branch %s",Branch.name)) +self.branchActive=Branch +self:PhaseChange(Phase) +return self +end +function OPERATION:onafterOver(From,Event,To) +self:T(self.lid..string.format("Operation is over!")) +self.phase=nil +for _,_branch in pairs(self.branches)do +local branch=_branch +for _,_phase in pairs(branch.phases)do +local phase=_phase +if not self:IsPhaseOver(phase)then +self:SetPhaseStatus(phase,OPERATION.PhaseStatus.OVER) +end +end +end +return self +end +function OPERATION:_CheckPhases() +local phase=self:GetPhaseActive() +if phase and phase.conditionOver then +local isOver=phase.conditionOver:Evaluate() +local Tnow=timer.getAbsTime() +if phase.duration and phase.Tstart and Tnow-phase.Tstart>phase.duration then +isOver=true +end +if isOver then +self:SetPhaseStatus(phase,OPERATION.PhaseStatus.OVER) +end +end +if phase==nil or phase.status==OPERATION.PhaseStatus.OVER then +for _,_edge in pairs(self.branchActive.edges)do +local edge=_edge +if phase then +end +if(edge.phaseFrom==nil)or(phase and edge.phaseFrom.uid==phase.uid)then +local switch=edge.conditionSwitch:Evaluate() +if switch then +local phaseTo=edge.phaseTo or self:GetPhaseNext(edge.branchTo,nil) +if phaseTo then +self:BranchSwitch(edge.branchTo,phaseTo) +else +self:Over() +end +return +end +end +end +self:PhaseNext() +end +end +function OPERATION:_CreatePhase(Name) +self.counterPhase=self.counterPhase+1 +local phase={} +phase.uid=self.counterPhase +phase.name=Name or string.format("Phase-%02d",self.counterPhase) +phase.conditionOver=CONDITION:New(Name.." Over") +phase.conditionOver:SetDefaultPersistence(false) +phase.status=OPERATION.PhaseStatus.PLANNED +phase.nActive=0 +return phase +end +function OPERATION:_CreateBranch(Name) +self.counterBranch=self.counterBranch+1 +local branch={} +branch.uid=self.counterBranch +branch.name=Name or string.format("Branch-%02d",self.counterBranch) +branch.phases={} +branch.edges={} +return branch +end +OPSGROUP={ +ClassName="OPSGROUP", +verbose=0, +lid=nil, +groupname=nil, +group=nil, +template=nil, +isLateActivated=nil, +waypoints=nil, +waypoints0=nil, +currentwp=1, +elements={}, +taskqueue={}, +taskcounter=nil, +taskcurrent=nil, +taskenroute=nil, +taskpaused={}, +missionqueue={}, +currentmission=nil, +detectedunits={}, +detectedgroups={}, +attribute=nil, +checkzones=nil, +inzones=nil, +groupinitialized=nil, +wpcounter=1, +radio={}, +option={}, +optionDefault={}, +tacan={}, +icls={}, +callsign={}, +Ndestroyed=0, +Nkills=0, +Nhit=0, +weaponData={}, +cargoqueue={}, +cargoBay={}, +mycarrier={}, +carrierLoader={}, +carrierUnloader={}, +useMEtasks=false, +pausedmissions={}, +} +OPSGROUP.ElementStatus={ +INUTERO="InUtero", +SPAWNED="Spawned", +PARKING="Parking", +ENGINEON="Engine On", +TAXIING="Taxiing", +TAKEOFF="Takeoff", +AIRBORNE="Airborne", +LANDING="Landing", +LANDED="Landed", +ARRIVED="Arrived", +DEAD="Dead", +} +OPSGROUP.GroupStatus={ +INUTERO="InUtero", +PARKING="Parking", +TAXIING="Taxiing", +AIRBORNE="Airborne", +INBOUND="Inbound", +LANDING="Landing", +LANDED="Landed", +ARRIVED="Arrived", +DEAD="Dead", +} +OPSGROUP.TaskStatus={ +SCHEDULED="scheduled", +EXECUTING="executing", +PAUSED="paused", +DONE="done", +} +OPSGROUP.TaskType={ +SCHEDULED="scheduled", +WAYPOINT="waypoint", +} +OPSGROUP.CarrierStatus={ +NOTCARRIER="not carrier", +PICKUP="pickup", +LOADING="loading", +LOADED="loaded", +TRANSPORTING="transporting", +UNLOADING="unloading", +} +OPSGROUP.CargoStatus={ +AWAITING="Awaiting carrier", +NOTCARGO="not cargo", +ASSIGNED="assigned to carrier", +BOARDING="boarding", +LOADED="loaded", +} +OPSGROUP.version="1.0.4" +function OPSGROUP:New(group) +local self=BASE:Inherit(self,FSM:New()) +if type(group)=="string"then +self.groupname=group +self.group=GROUP:FindByName(self.groupname) +else +self.group=group +self.groupname=group:GetName() +end +self.lid=string.format("OPSGROUP %s | ",tostring(self.groupname)) +if self.group then +if not self:IsExist()then +self:E(self.lid.."ERROR: GROUP does not exist! Returning nil") +return nil +end +end +if UTILS.IsInstanceOf(group,"OPSGROUP")then +self:E(self.lid.."ERROR: GROUP is already an OPSGROUP: "..tostring(self.groupname).."!") +return group +end +self:_SetTemplate() +self.dcsgroup=self:GetDCSGroup() +self.controller=self.dcsgroup:getController() +self.category=self.dcsgroup:getCategory() +if self.category==Group.Category.GROUND then +self.isArmygroup=true +elseif self.category==Group.Category.TRAIN then +self.isArmygroup=true +self.isTrain=true +elseif self.category==Group.Category.SHIP then +self.isNavygroup=true +elseif self.category==Group.Category.AIRPLANE then +self.isFlightgroup=true +elseif self.category==Group.Category.HELICOPTER then +self.isFlightgroup=true +self.isHelo=true +else +end +self.attribute=self.group:GetAttribute() +local units=self.group:GetUnits() +if units then +local masterunit=units[1] +if masterunit then +self.descriptors=masterunit:GetDesc() +self.actype=masterunit:GetTypeName() +self.isSubmarine=masterunit:HasAttribute("Submarines") +self.isEPLRS=masterunit:HasAttribute("Datalink") +if self:IsFlightgroup()then +self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000 +self.ceiling=self.descriptors.Hmax +self.tankertype=select(2,masterunit:IsTanker()) +self.refueltype=select(2,masterunit:IsRefuelable()) +end +end +end +self.detectedunits=SET_UNIT:New() +self.detectedgroups=SET_GROUP:New() +self.inzones=SET_ZONE:New() +self:SetDefaultAltitude() +self:SetReturnToLegion() +self.spot={} +self.spot.On=false +self.spot.timer=TIMER:New(self._UpdateLaser,self) +self.spot.Coordinate=COORDINATE:New(0,0,0) +self:SetLaser(1688,true,false,0.5) +self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO +self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER +self:SetCarrierLoaderAllAspect() +self:SetCarrierUnloaderAllAspect() +self.taskcurrent=0 +self.taskcounter=0 +self:SetStartState("InUtero") +self:AddTransition("InUtero","Spawned","Spawned") +self:AddTransition("*","Respawn","InUtero") +self:AddTransition("*","Dead","InUtero") +self:AddTransition("*","InUtero","InUtero") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("*","Hit","*") +self:AddTransition("*","Damaged","*") +self:AddTransition("*","Destroyed","*") +self:AddTransition("*","UpdateRoute","*") +self:AddTransition("*","PassingWaypoint","*") +self:AddTransition("*","PassedFinalWaypoint","*") +self:AddTransition("*","GotoWaypoint","*") +self:AddTransition("*","Wait","*") +self:AddTransition("*","Stuck","*") +self:AddTransition("*","DetectedUnit","*") +self:AddTransition("*","DetectedUnitNew","*") +self:AddTransition("*","DetectedUnitKnown","*") +self:AddTransition("*","DetectedUnitLost","*") +self:AddTransition("*","DetectedGroup","*") +self:AddTransition("*","DetectedGroupNew","*") +self:AddTransition("*","DetectedGroupKnown","*") +self:AddTransition("*","DetectedGroupLost","*") +self:AddTransition("*","OutOfAmmo","*") +self:AddTransition("*","OutOfGuns","*") +self:AddTransition("*","OutOfRockets","*") +self:AddTransition("*","OutOfBombs","*") +self:AddTransition("*","OutOfMissiles","*") +self:AddTransition("*","OutOfTorpedos","*") +self:AddTransition("*","OutOfMissilesAA","*") +self:AddTransition("*","OutOfMissilesAG","*") +self:AddTransition("*","OutOfMissilesAS","*") +self:AddTransition("*","EnterZone","*") +self:AddTransition("*","LeaveZone","*") +self:AddTransition("*","LaserOn","*") +self:AddTransition("*","LaserOff","*") +self:AddTransition("*","LaserCode","*") +self:AddTransition("*","LaserPause","*") +self:AddTransition("*","LaserResume","*") +self:AddTransition("*","LaserLostLOS","*") +self:AddTransition("*","LaserGotLOS","*") +self:AddTransition("*","TaskExecute","*") +self:AddTransition("*","TaskPause","*") +self:AddTransition("*","TaskCancel","*") +self:AddTransition("*","TaskDone","*") +self:AddTransition("*","MissionStart","*") +self:AddTransition("*","MissionExecute","*") +self:AddTransition("*","MissionCancel","*") +self:AddTransition("*","PauseMission","*") +self:AddTransition("*","UnpauseMission","*") +self:AddTransition("*","MissionDone","*") +self:AddTransition("*","ElementInUtero","*") +self:AddTransition("*","ElementSpawned","*") +self:AddTransition("*","ElementDestroyed","*") +self:AddTransition("*","ElementDead","*") +self:AddTransition("*","ElementDamaged","*") +self:AddTransition("*","ElementHit","*") +self:AddTransition("*","Board","*") +self:AddTransition("*","Embarked","*") +self:AddTransition("*","Disembarked","*") +self:AddTransition("*","Pickup","*") +self:AddTransition("*","Loading","*") +self:AddTransition("*","Load","*") +self:AddTransition("*","Loaded","*") +self:AddTransition("*","LoadingDone","*") +self:AddTransition("*","Transport","*") +self:AddTransition("*","Unloading","*") +self:AddTransition("*","Unload","*") +self:AddTransition("*","Unloaded","*") +self:AddTransition("*","UnloadingDone","*") +self:AddTransition("*","Delivered","*") +self:AddTransition("*","TransportCancel","*") +self:AddTransition("*","HoverStart","*") +self:AddTransition("*","HoverEnd","*") +return self +end +function OPSGROUP:GetCoalition() +return self.group:GetCoalition() +end +function OPSGROUP:GetLifePoints(Element) +local life=0 +local life0=0 +if Element then +local unit=Element.unit +if unit then +life=unit:GetLife() +life0=unit:GetLife0() +life=math.min(life,life0) +end +else +for _,element in pairs(self.elements)do +local l,l0=self:GetLifePoints(element) +life=life+l +life0=life0+l0 +end +end +return life,life0 +end +function OPSGROUP:GetAttribute() +return self.attribute +end +function OPSGROUP:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function OPSGROUP:_SetLegion(Legion) +self:T2(self.lid..string.format("Adding opsgroup to legion %s",Legion.alias)) +self.legion=Legion +return self +end +function OPSGROUP:SetReturnToLegion(Switch) +if Switch==false then +self.legionReturn=false +else +self.legionReturn=true +end +self:T(self.lid..string.format("Setting ReturnToLegion=%s",tostring(self.legionReturn))) +return self +end +function OPSGROUP:SetDefaultSpeed(Speed) +if Speed then +self.speedCruise=UTILS.KnotsToKmph(Speed) +end +return self +end +function OPSGROUP:GetSpeedCruise() +local speed=UTILS.KmphToKnots(self.speedCruise or self.speedMax*0.7) +return speed +end +function OPSGROUP:SetDefaultAltitude(Altitude) +if Altitude then +self.altitudeCruise=UTILS.FeetToMeters(Altitude) +else +if self:IsFlightgroup()then +if self.isHelo then +self.altitudeCruise=UTILS.FeetToMeters(1500) +else +self.altitudeCruise=UTILS.FeetToMeters(10000) +end +else +self.altitudeCruise=0 +end +end +return self +end +function OPSGROUP:GetCruiseAltitude() +local alt=UTILS.MetersToFeet(self.altitudeCruise) +return alt +end +function OPSGROUP:SetAltitude(Altitude,Keep,RadarAlt) +if Altitude then +Altitude=UTILS.FeetToMeters(Altitude) +else +if self:IsFlightgroup()then +if self.isHelo then +Altitude=UTILS.FeetToMeters(1500) +else +Altitude=UTILS.FeetToMeters(10000) +end +else +Altitude=0 +end +end +local AltType="BARO" +if RadarAlt then +AltType="RADIO" +end +if self.controller then +self.controller:setAltitude(Altitude,Keep,AltType) +end +return self +end +function OPSGROUP:GetAltitude() +local alt=0 +if self.group then +alt=self.group:GetAltitude() +alt=UTILS.MetersToFeet(alt) +end +return alt +end +function OPSGROUP:SetSpeed(Speed,Keep,AltCorrected) +if Speed then +else +Speed=UTILS.KmphToKnots(self.speedMax) +end +if AltCorrected then +local altitude=self:GetAltitude() +Speed=UTILS.KnotsToAltKIAS(Speed,altitude) +end +Speed=UTILS.KnotsToMps(Speed) +if self.controller then +self.controller:setSpeed(Speed,Keep) +end +return self +end +function OPSGROUP:SetDetection(Switch) +self:T(self.lid..string.format("Detection is %s",tostring(Switch))) +self.detectionOn=Switch +return self +end +function OPSGROUP:GetDCSObject() +return self.dcsgroup +end +function OPSGROUP:KnowTarget(TargetObject,KnowType,KnowDist,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.KnowTarget,self,TargetObject,KnowType,KnowDist,0) +else +if TargetObject:IsInstanceOf("GROUP")then +TargetObject=TargetObject:GetUnit(1) +elseif TargetObject:IsInstanceOf("OPSGROUP")then +TargetObject=TargetObject.group:GetUnit(1) +end +local object=TargetObject:GetDCSObject() +for _,_element in pairs(self.elements)do +local element=_element +if element.controller then +element.controller:knowTarget(object,true,true) +end +end +self:T(self.lid..string.format("We should now know target %s",TargetObject:GetName())) +end +return self +end +function OPSGROUP:IsTargetDetected(TargetObject) +local objects={} +if TargetObject:IsInstanceOf("GROUP")then +for _,unit in pairs(TargetObject:GetUnits())do +table.insert(objects,unit:GetDCSObject()) +end +elseif TargetObject:IsInstanceOf("OPSGROUP")then +for _,unit in pairs(TargetObject.group:GetUnits())do +table.insert(objects,unit:GetDCSObject()) +end +elseif TargetObject:IsInstanceOf("UNIT")or TargetObject:IsInstanceOf("STATIC")then +table.insert(objects,TargetObject:GetDCSObject()) +end +for _,object in pairs(objects or{})do +local detected,visible,lastTime,type,distance,lastPos,lastVel=self.controller:isTargetDetected(object,1,2,4,8,16,32) +if detected then +return true +end +for _,_element in pairs(self.elements)do +local element=_element +if element.controller then +local detected,visible,lastTime,type,distance,lastPos,lastVel= +element.controller:isTargetDetected(object,1,2,4,8,16,32) +if detected then +return true +end +end +end +end +return false +end +function OPSGROUP:InWeaponRange(TargetCoord,WeaponBitType,RefCoord) +RefCoord=RefCoord or self:GetCoordinate() +local dist=TargetCoord:Get2DDistance(RefCoord) +if WeaponBitType then +local weapondata=self:GetWeaponData(WeaponBitType) +if weapondata then +if dist>=weapondata.RangeMin and dist<=weapondata.RangeMax then +return true +else +return false +end +end +else +for _,_weapondata in pairs(self.weaponData or{})do +local weapondata=_weapondata +if dist>=weapondata.RangeMin and dist<=weapondata.RangeMax then +return true +end +end +return false +end +return nil +end +function OPSGROUP:GetCoordinateInRange(TargetCoord,WeaponBitType,RefCoord,SurfaceTypes) +local coordInRange=nil +RefCoord=RefCoord or self:GetCoordinate() +local weapondata=self:GetWeaponData(WeaponBitType) +local dh={0,-5,5,-10,10,-15,15,-20,20,-25,25,-30,30,-35,35,-40,40,-45,45,-50,50,-55,55,-60,60,-65,65,-70,70,-75,75,-80,80} +local function _checkSurface(point) +if SurfaceTypes then +local stype=point:GetSurfaceType() +for _,sf in pairs(SurfaceTypes)do +if sf==stype then +return true +end +end +return false +else +return true +end +end +if weapondata then +local heading=TargetCoord:HeadingTo(RefCoord) +local dist=RefCoord:Get2DDistance(TargetCoord) +local range=nil +if dist>weapondata.RangeMax then +range=weapondata.RangeMax +self:T(self.lid..string.format("Out of max range = %.1f km by %.1f km for weapon %s",weapondata.RangeMax/1000,(weapondata.RangeMax-dist)/1000,tostring(WeaponBitType))) +elseif dist=ThreatLevelMin and threatlevel<=ThreatLevelMax then +if threatlevellevelmax then +threat=unit +levelmax=threatlevel +end +end +return threat,levelmax +end +function OPSGROUP:SetEngageDetectedOn(RangeMax,TargetTypes,EngageZoneSet,NoEngageZoneSet) +if TargetTypes then +if type(TargetTypes)~="table"then +TargetTypes={TargetTypes} +end +else +TargetTypes={"All"} +end +if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE")then +local zoneset=SET_ZONE:New():AddZone(EngageZoneSet) +EngageZoneSet=zoneset +end +if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE")then +local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet) +NoEngageZoneSet=zoneset +end +self.engagedetectedOn=true +self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) +self.engagedetectedTypes=TargetTypes +self.engagedetectedEngageZones=EngageZoneSet +self.engagedetectedNoEngageZones=NoEngageZoneSet +self:T(self.lid..string.format("Engage detected ON: Rmax=%d NM",UTILS.MetersToNM(self.engagedetectedRmax))) +self:SetDetection(true) +return self +end +function OPSGROUP:SetEngageDetectedOff() +self:T(self.lid..string.format("Engage detected OFF")) +self.engagedetectedOn=false +return self +end +function OPSGROUP:SetRearmOnOutOfAmmo() +self.rearmOnOutOfAmmo=true +return self +end +function OPSGROUP:SetRetreatOnOutOfAmmo() +self.retreatOnOutOfAmmo=true +return self +end +function OPSGROUP:SetReturnOnOutOfAmmo() +self.rtzOnOutOfAmmo=true +return self +end +function OPSGROUP:SetCargoBayLimit(Weight,UnitName) +for _,_element in pairs(self.elements)do +local element=_element +if UnitName==nil or UnitName==element.name then +element.weightMaxCargo=Weight +if element.unit then +element.unit:SetCargoBayWeightLimit(Weight) +end +end +end +return self +end +function OPSGROUP:HasLoS(Coordinate,Element,OffsetElement,OffsetCoordinate) +if Coordinate then +local Vec3={x=Coordinate.x,y=Coordinate.y,z=Coordinate.z} +if OffsetCoordinate then +Vec3=UTILS.VecAdd(Vec3,OffsetCoordinate) +end +local function checklos(vec3) +if vec3 then +if OffsetElement then +vec3=UTILS.VecAdd(vec3,OffsetElement) +end +local _los=land.isVisible(vec3,Vec3) +return _los +end +return nil +end +if Element then +if Element.unit and Element.unit:IsAlive()then +local vec3=Element.unit:GetVec3() +local los=checklos(vec3) +return los +end +else +local gotit=false +for _,_element in pairs(self.elements)do +local element=_element +if element and element.unit and element.unit:IsAlive()then +gotit=true +local vec3=element.unit:GetVec3() +local los=checklos(vec3) +if los then +return true +end +end +end +if gotit then +return false +end +end +end +return nil +end +function OPSGROUP:GetGroup() +return self.group +end +function OPSGROUP:GetName() +return self.groupname +end +function OPSGROUP:GetDCSGroup() +local DCSGroup=Group.getByName(self.groupname) +return DCSGroup +end +function OPSGROUP:GetUnit(UnitNumber) +local DCSUnit=self:GetDCSUnit(UnitNumber) +if DCSUnit then +local unit=UNIT:Find(DCSUnit) +return unit +end +return nil +end +function OPSGROUP:GetDCSUnit(UnitNumber) +local DCSGroup=self:GetDCSGroup() +if DCSGroup then +local unit=DCSGroup:getUnit(UnitNumber or 1) +return unit +else +self:E(self.lid..string.format("ERROR: DCS group does not exist! Cannot get unit")) +end +return nil +end +function OPSGROUP:GetDCSUnits() +local DCSGroup=self:GetDCSGroup() +if DCSGroup then +local units=DCSGroup:getUnits() +return units +end +return nil +end +function OPSGROUP:GetVec2(UnitName) +local vec3=self:GetVec3(UnitName) +if vec3 then +local vec2={x=vec3.x,y=vec3.z} +return vec2 +end +return nil +end +function OPSGROUP:GetVec3(UnitName) +local vec3=nil +local carrier=self:_GetMyCarrierElement() +if carrier and carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded()then +local unit=carrier.unit +if unit and unit:IsExist()then +vec3=unit:GetVec3() +return vec3 +end +end +if self:IsExist()then +local unit=nil +if UnitName then +unit=Unit.getByName(UnitName) +else +unit=self:GetDCSUnit() +end +if unit then +local vec3=unit:getPoint() +return vec3 +end +end +if self.position then +return self.position +end +return nil +end +function OPSGROUP:GetCoordinate(NewObject,UnitName) +local vec3=self:GetVec3(UnitName)or self.position +if vec3 then +self.coordinate=self.coordinate or COORDINATE:New(0,0,0) +self.coordinate.x=vec3.x +self.coordinate.y=vec3.y +self.coordinate.z=vec3.z +if NewObject then +local coord=COORDINATE:NewFromCoordinate(self.coordinate) +return coord +else +return self.coordinate +end +else +self:T(self.lid.."WARNING: Cannot get coordinate!") +end +return nil +end +function OPSGROUP:GetVelocity(UnitName) +if self:IsExist()then +local unit=nil +if UnitName then +unit=Unit.getByName(UnitName) +else +unit=self:GetDCSUnit() +end +if unit then +local velvec3=unit:getVelocity() +local vel=UTILS.VecNorm(velvec3) +return vel +else +self:T(self.lid.."WARNING: Unit does not exist. Cannot get velocity!") +end +else +self:T(self.lid.."WARNING: Group does not exist. Cannot get velocity!") +end +return nil +end +function OPSGROUP:GetHeading(UnitName) +if self:IsExist()then +local unit=nil +if UnitName then +unit=Unit.getByName(UnitName) +else +unit=self:GetDCSUnit() +end +if unit then +local pos=unit:getPosition() +local heading=math.atan2(pos.x.z,pos.x.x) +if heading<0 then +heading=heading+2*math.pi +end +heading=math.deg(heading) +return heading +end +else +self:T(self.lid.."WARNING: Group does not exist. Cannot get heading!") +end +return nil +end +function OPSGROUP:GetOrientation(UnitName) +if self:IsExist()then +local unit=nil +if UnitName then +unit=Unit.getByName(UnitName) +else +unit=self:GetDCSUnit() +end +if unit then +local pos=unit:getPosition() +return pos.x,pos.y,pos.z +end +else +self:T(self.lid.."WARNING: Group does not exist. Cannot get orientation!") +end +return nil +end +function OPSGROUP:GetOrientationX(UnitName) +local X,Y,Z=self:GetOrientation(UnitName) +return X +end +function OPSGROUP:CheckTaskDescriptionUnique(description) +for _,_task in pairs(self.taskqueue)do +local task=_task +if task.description==description then +return false +end +end +return true +end +function OPSGROUP:DespawnUnit(UnitName,Delay,NoEventRemoveUnit) +self:T(self.lid.."Despawn element "..tostring(UnitName)) +local element=self:GetElementByName(UnitName) +if element then +local DCSunit=Unit.getByName(UnitName) +if DCSunit then +DCSunit:destroy() +self:ElementInUtero(element) +if not NoEventRemoveUnit then +self:CreateEventRemoveUnit(timer.getTime(),DCSunit) +end +end +end +end +function OPSGROUP:DespawnElement(Element,Delay,NoEventRemoveUnit) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.DespawnElement,self,Element,0,NoEventRemoveUnit) +else +if Element then +local DCSunit=Unit.getByName(Element.name) +if DCSunit then +DCSunit:destroy() +if not NoEventRemoveUnit then +self:CreateEventRemoveUnit(timer.getTime(),DCSunit) +end +end +end +end +return self +end +function OPSGROUP:Despawn(Delay,NoEventRemoveUnit) +if Delay and Delay>0 then +self.scheduleIDDespawn=self:ScheduleOnce(Delay,OPSGROUP.Despawn,self,0,NoEventRemoveUnit) +else +self:T(self.lid..string.format("Despawning Group!")) +local DCSGroup=self:GetDCSGroup() +if DCSGroup then +local units=self:GetDCSUnits() +for i=1,#units do +local unit=units[i] +if unit then +local name=unit:getName() +if name then +self:DespawnUnit(name,0,NoEventRemoveUnit) +end +end +end +end +end +return self +end +function OPSGROUP:ReturnToLegion(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.ReturnToLegion,self) +else +if self.legion then +self:T(self.lid..string.format("Adding asset back to LEGION")) +self.legion:AddAsset(self.group,1) +else +self:E(self.lid..string.format("ERROR: Group does not belong to a LEGION!")) +end +end +return self +end +function OPSGROUP:DestroyUnit(UnitName,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.DestroyUnit,self,UnitName,0) +else +local unit=Unit.getByName(UnitName) +if unit then +local EventTime=timer.getTime() +if self:IsFlightgroup()then +self:CreateEventUnitLost(EventTime,unit) +else +self:CreateEventDead(EventTime,unit) +end +unit:destroy() +end +end +end +function OPSGROUP:Destroy(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.Destroy,self,0) +else +self:T(self.lid.."Destroying group!") +local units=self:GetDCSUnits() +if units then +for _,unit in pairs(units)do +if unit then +self:DestroyUnit(unit:getName()) +end +end +end +end +return self +end +function OPSGROUP:Activate(delay) +if delay and delay>0 then +self:T2(self.lid..string.format("Activating late activated group in %d seconds",delay)) +self:ScheduleOnce(delay,OPSGROUP.Activate,self) +else +if self:IsAlive()==false then +self:T(self.lid.."Activating late activated group") +self.group:Activate() +self.isLateActivated=false +elseif self:IsAlive()==true then +self:T(self.lid.."WARNING: Activating group that is already activated") +else +self:T(self.lid.."ERROR: Activating group that is does not exist!") +end +end +return self +end +function OPSGROUP:Deactivate(delay) +if delay and delay>0 then +self:ScheduleOnce(delay,OPSGROUP.Deactivate,self) +else +if self:IsAlive()==true then +self.template.lateActivation=true +local template=UTILS.DeepCopy(self.template) +self:_Respawn(0,template) +end +end +return self +end +function OPSGROUP:SelfDestruction(Delay,ExplosionPower,ElementName) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.SelfDestruction,self,0,ExplosionPower,ElementName) +else +for i,_element in pairs(self.elements)do +local element=_element +if ElementName==nil or ElementName==element.name then +local unit=element.unit +if unit and unit:IsAlive()then +unit:Explode(ExplosionPower or 100) +end +end +end +end +return self +end +function OPSGROUP:SetSRS(PathToSRS,Gender,Culture,Voice,Port,PathToGoogleKey,Label,Volume) +self.useSRS=true +local path=PathToSRS or MSRS.path +local port=Port or MSRS.port +self.msrs=MSRS:New(path,self.frequency,self.modulation) +self.msrs:SetGender(Gender) +self.msrs:SetCulture(Culture) +self.msrs:SetVoice(Voice) +self.msrs:SetPort(port) +self.msrs:SetLabel(Label) +if PathToGoogleKey then +self.msrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) +self.msrs:SetProvider(MSRS.Provider.GOOGLE) +end +self.msrs:SetCoalition(self:GetCoalition()) +self.msrs:SetVolume(Volume) +return self +end +function OPSGROUP:RadioTransmission(Text,Delay,SayCallsign,Frequency) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.RadioTransmission,self,Text,0,SayCallsign) +else +if self.useSRS and self.msrs then +local freq,modu,radioon=self:GetRadio() +local coord=self:GetCoordinate() +self.msrs:SetCoordinate(coord) +if Frequency then +self.msrs:SetFrequencies(Frequency) +else +self.msrs:SetFrequencies(freq) +end +self.msrs:SetModulations(modu) +if SayCallsign then +local callsign=self:GetCallsignName() +Text=string.format("%s, %s",callsign,Text) +end +self:T(self.lid..string.format("Radio transmission on %.3f MHz %s: %s",freq,UTILS.GetModulationName(modu),Text)) +self.msrs:PlayText(Text) +end +end +return self +end +function OPSGROUP:SetCarrierLoaderAllAspect(Length,Width) +self.carrierLoader.type="front" +self.carrierLoader.length=Length or 50 +self.carrierLoader.width=Width or 20 +return self +end +function OPSGROUP:SetCarrierLoaderFront(Length,Width) +self.carrierLoader.type="front" +self.carrierLoader.length=Length or 50 +self.carrierLoader.width=Width or 20 +return self +end +function OPSGROUP:SetCarrierLoaderBack(Length,Width) +self.carrierLoader.type="back" +self.carrierLoader.length=Length or 50 +self.carrierLoader.width=Width or 20 +return self +end +function OPSGROUP:SetCarrierLoaderStarboard(Length,Width) +self.carrierLoader.type="right" +self.carrierLoader.length=Length or 50 +self.carrierLoader.width=Width or 20 +return self +end +function OPSGROUP:SetCarrierLoaderPort(Length,Width) +self.carrierLoader.type="left" +self.carrierLoader.length=Length or 50 +self.carrierLoader.width=Width or 20 +return self +end +function OPSGROUP:SetCarrierUnloaderAllAspect(Length,Width) +self.carrierUnloader.type="front" +self.carrierUnloader.length=Length or 50 +self.carrierUnloader.width=Width or 20 +return self +end +function OPSGROUP:SetCarrierUnloaderFront(Length,Width) +self.carrierUnloader.type="front" +self.carrierUnloader.length=Length or 50 +self.carrierUnloader.width=Width or 20 +return self +end +function OPSGROUP:SetCarrierUnloaderBack(Length,Width) +self.carrierUnloader.type="back" +self.carrierUnloader.length=Length or 50 +self.carrierUnloader.width=Width or 20 +return self +end +function OPSGROUP:SetCarrierUnloaderStarboard(Length,Width) +self.carrierUnloader.type="right" +self.carrierUnloader.length=Length or 50 +self.carrierUnloader.width=Width or 20 +return self +end +function OPSGROUP:SetCarrierUnloaderPort(Length,Width) +self.carrierUnloader.type="left" +self.carrierUnloader.length=Length or 50 +self.carrierUnloader.width=Width or 20 +return self +end +function OPSGROUP:IsInZone(Zone) +local vec2=self:GetVec2() +local is=false +if vec2 then +is=Zone:IsVec2InZone(vec2) +else +self:T3(self.lid.."WARNING: Cannot get vec2 at IsInZone()!") +end +return is +end +function OPSGROUP:Get2DDistance(Coordinate) +local a=self:GetVec2() +local b={} +if Coordinate.z then +b.x=Coordinate.x +b.y=Coordinate.z +else +b.x=Coordinate.x +b.y=Coordinate.y +end +local dist=UTILS.VecDist2D(a,b) +return dist +end +function OPSGROUP:IsFlightgroup() +return self.isFlightgroup +end +function OPSGROUP:IsArmygroup() +return self.isArmygroup +end +function OPSGROUP:IsNavygroup() +return self.isNavygroup +end +function OPSGROUP:IsExist() +local DCSGroup=self:GetDCSGroup() +if DCSGroup then +local exists=DCSGroup:isExist() +return exists +end +return nil +end +function OPSGROUP:IsActive() +if self.group then +local active=self.group:IsActive() +return active +end +return nil +end +function OPSGROUP:IsAlive() +if self.group then +local alive=self.group:IsAlive() +return alive +end +return nil +end +function OPSGROUP:IsLateActivated() +return self.isLateActivated +end +function OPSGROUP:IsInUtero() +local is=self:Is("InUtero")and not self:IsDead() +return is +end +function OPSGROUP:IsSpawned() +local is=self:Is("Spawned") +return is +end +function OPSGROUP:IsDead() +return self.isDead +end +function OPSGROUP:IsDestroyed() +return self.isDestroyed +end +function OPSGROUP:IsStopped() +local is=self:Is("Stopped") +return is +end +function OPSGROUP:IsUncontrolled() +return self.isUncontrolled +end +function OPSGROUP:HasPassedFinalWaypoint() +return self.passedfinalwp +end +function OPSGROUP:IsRearming() +local rearming=self:Is("Rearming")or self:Is("Rearm") +return rearming +end +function OPSGROUP:IsOutOfAmmo() +return self.outofAmmo +end +function OPSGROUP:IsOutOfBombs() +return self.outofBombs +end +function OPSGROUP:IsOutOfGuns() +return self.outofGuns +end +function OPSGROUP:IsOutOfMissiles() +return self.outofMissiles +end +function OPSGROUP:IsOutOfTorpedos() +return self.outofTorpedos +end +function OPSGROUP:IsLasing() +return self.spot.On +end +function OPSGROUP:IsRetreating() +local is=self:is("Retreating")or self:is("Retreated") +return is +end +function OPSGROUP:IsRetreated() +local is=self:is("Retreated") +return is +end +function OPSGROUP:IsReturning() +local is=self:is("Returning") +return is +end +function OPSGROUP:IsEngaging() +local is=self:is("Engaging") +return is +end +function OPSGROUP:IsWaiting() +if self.Twaiting then +return true +end +return false +end +function OPSGROUP:IsNotCarrier() +return self.carrierStatus==OPSGROUP.CarrierStatus.NOTCARRIER +end +function OPSGROUP:IsCarrier() +return not self:IsNotCarrier() +end +function OPSGROUP:IsPickingup() +return self.carrierStatus==OPSGROUP.CarrierStatus.PICKUP +end +function OPSGROUP:IsLoading() +return self.carrierStatus==OPSGROUP.CarrierStatus.LOADING +end +function OPSGROUP:IsTransporting() +return self.carrierStatus==OPSGROUP.CarrierStatus.TRANSPORTING +end +function OPSGROUP:IsUnloading() +return self.carrierStatus==OPSGROUP.CarrierStatus.UNLOADING +end +function OPSGROUP:IsCargo(CheckTransport) +return not self:IsNotCargo(CheckTransport) +end +function OPSGROUP:IsNotCargo(CheckTransport) +local notcargo=self.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO +if notcargo then +return true +else +if CheckTransport then +if self.cargoTransportUID==nil then +return true +else +return false +end +else +return false +end +end +return notcargo +end +function OPSGROUP:_AddMyLift(Transport) +self.mylifts=self.mylifts or{} +self.mylifts[Transport.uid]=true +return self +end +function OPSGROUP:_DelMyLift(Transport) +if self.mylifts then +self.mylifts[Transport.uid]=nil +end +return self +end +function OPSGROUP:IsAwaitingLift(Transport) +if self.mylifts then +for uid,iswaiting in pairs(self.mylifts)do +if Transport==nil or Transport.uid==uid then +if iswaiting==true then +return true +end +end +end +end +return false +end +function OPSGROUP:_GetPausedMission() +if self.pausedmissions and#self.pausedmissions>0 then +for _,mid in pairs(self.pausedmissions)do +if mid then +local mission=self:GetMissionByID(mid) +if mission and mission:IsNotOver()then +return mission +end +end +end +end +return nil +end +function OPSGROUP:_CountPausedMissions() +local N=0 +if self.pausedmissions and#self.pausedmissions>0 then +for _,mid in pairs(self.pausedmissions)do +local mission=self:GetMissionByID(mid) +if mission and mission:IsNotOver()then +N=N+1 +end +end +end +return N +end +function OPSGROUP:_RemovePausedMission(AuftragsNummer) +if self.pausedmissions and#self.pausedmissions>0 then +for i=#self.pausedmissions,1,-1 do +local mid=self.pausedmissions[i] +if mid==AuftragsNummer then +table.remove(self.pausedmissions,i) +return self +end +end +end +return self +end +function OPSGROUP:IsBoarding(CarrierGroupName) +if CarrierGroupName then +local carrierGroup=self:_GetMyCarrierGroup() +if carrierGroup and carrierGroup.groupname~=CarrierGroupName then +return false +end +end +return self.cargoStatus==OPSGROUP.CargoStatus.BOARDING +end +function OPSGROUP:IsLoaded(CarrierGroupName) +local isloaded=self.cargoStatus==OPSGROUP.CargoStatus.LOADED +if not isloaded then +return false +end +if CarrierGroupName then +if type(CarrierGroupName)~="table"then +CarrierGroupName={CarrierGroupName} +end +for _,CarrierName in pairs(CarrierGroupName)do +local carrierGroup=self:_GetMyCarrierGroup() +if carrierGroup and carrierGroup.groupname==CarrierName then +return isloaded +end +end +return false +end +return isloaded +end +function OPSGROUP:IsBusy() +if self:IsBoarding()then +return true +end +if self:IsRearming()then +return true +end +if self:IsReturning()then +return true +end +if self:IsPickingup()or self:IsLoading()or self:IsTransporting()or self:IsUnloading()then +return true +end +if self:IsEngaging()then +return true +end +return false +end +function OPSGROUP:GetWaypoints() +return self.waypoints +end +function OPSGROUP:MarkWaypoints(Duration) +for i,_waypoint in pairs(self.waypoints or{})do +local waypoint=_waypoint +local text=string.format("Waypoint ID=%d of %s",waypoint.uid,self.groupname) +text=text..string.format("\nSpeed=%.1f kts, Alt=%d ft (%s)",UTILS.MpsToKnots(waypoint.speed),UTILS.MetersToFeet(waypoint.alt or 0),"BARO") +if waypoint.marker then +if waypoint.marker.text~=text then +waypoint.marker.text=text +end +else +waypoint.marker=MARKER:New(waypoint.coordinate,text):ToCoalition(self:GetCoalition()) +end +end +if Duration then +self:RemoveWaypointMarkers(Duration) +end +return self +end +function OPSGROUP:RemoveWaypointMarkers(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.RemoveWaypointMarkers,self) +else +for i,_waypoint in pairs(self.waypoints or{})do +local waypoint=_waypoint +if waypoint.marker then +waypoint.marker:Remove() +end +end +end +return self +end +function OPSGROUP:GetWaypointByID(uid) +for _,_waypoint in pairs(self.waypoints or{})do +local waypoint=_waypoint +if waypoint.uid==uid then +return waypoint +end +end +return nil +end +function OPSGROUP:GetWaypointByIndex(index) +for i,_waypoint in pairs(self.waypoints)do +local waypoint=_waypoint +if i==index then +return waypoint +end +end +return nil +end +function OPSGROUP:GetWaypointUIDFromIndex(index) +for i,_waypoint in pairs(self.waypoints)do +local waypoint=_waypoint +if i==index then +return waypoint.uid +end +end +return nil +end +function OPSGROUP:GetWaypointIndex(uid) +if uid then +for i,_waypoint in pairs(self.waypoints or{})do +local waypoint=_waypoint +if waypoint.uid==uid then +return i +end +end +end +return nil +end +function OPSGROUP:GetWaypointIndexNext(cyclic,i) +if cyclic==nil then +cyclic=self.adinfinitum +end +local N=#self.waypoints +i=i or self.currentwp +local n=math.min(i+1,N) +if cyclic and i==N then +n=1 +end +return n +end +function OPSGROUP:GetWaypointIndexCurrent() +return self.currentwp or 1 +end +function OPSGROUP:GetWaypointIndexAfterID(uid) +local index=self:GetWaypointIndex(uid) +if index then +return index+1 +else +return#self.waypoints+1 +end +end +function OPSGROUP:GetWaypoint(indx) +return self.waypoints[indx] +end +function OPSGROUP:GetWaypointFinal() +return self.waypoints[#self.waypoints] +end +function OPSGROUP:GetWaypointNext(cyclic) +local n=self:GetWaypointIndexNext(cyclic) +return self.waypoints[n] +end +function OPSGROUP:GetWaypointCurrent() +return self.waypoints[self.currentwp] +end +function OPSGROUP:GetWaypointCurrentUID() +local wp=self:GetWaypointCurrent() +if wp then +return wp.uid +end +return nil +end +function OPSGROUP:GetNextWaypointCoordinate(cyclic) +local waypoint=self:GetWaypointNext(cyclic) +return waypoint.coordinate +end +function OPSGROUP:GetWaypointCoordinate(index) +local waypoint=self:GetWaypoint(index) +if waypoint then +return waypoint.coordinate +end +return nil +end +function OPSGROUP:GetWaypointSpeed(indx) +local waypoint=self:GetWaypoint(indx) +if waypoint then +return UTILS.MpsToKnots(waypoint.speed) +end +return nil +end +function OPSGROUP:GetWaypointUID(waypoint) +return waypoint.uid +end +function OPSGROUP:GetWaypointID(indx) +local waypoint=self:GetWaypoint(indx) +if waypoint then +return waypoint.uid +end +return nil +end +function OPSGROUP:GetSpeedToWaypoint(indx) +local speed=self:GetWaypointSpeed(indx) +if speed<=0.01 then +speed=self:GetSpeedCruise() +end +return speed +end +function OPSGROUP:GetDistanceToWaypoint(indx) +local dist=0 +if#self.waypoints>0 then +indx=indx or self:GetWaypointIndexNext() +local wp=self:GetWaypoint(indx) +if wp then +local coord=self:GetCoordinate() +dist=coord:Get2DDistance(wp.coordinate) +end +end +return dist +end +function OPSGROUP:GetTimeToWaypoint(indx) +local s=self:GetDistanceToWaypoint(indx) +local v=self:GetVelocity() +local t=s/v +if t==math.inf then +return 365*24*60*60 +elseif t==math.nan then +return 0 +else +return t +end +end +function OPSGROUP:GetExpectedSpeed() +if self:IsHolding()or self:Is("Rearming")or self:IsWaiting()or self:IsRetreated()then +return 0 +else +return self.speedWp or 0 +end +end +function OPSGROUP:RemoveWaypointByID(uid) +local index=self:GetWaypointIndex(uid) +if index then +self:RemoveWaypoint(index) +end +return self +end +function OPSGROUP:RemoveWaypoint(wpindex) +if self.waypoints then +local wp=self:GetWaypoint(wpindex) +local istemp=wp.temp or wp.detour or wp.astar or wp.missionUID +local N=#self.waypoints +if N==1 then +self:T(self.lid..string.format("ERROR: Cannot remove waypoint with index=%d! It is the only waypoint and a group needs at least ONE waypoint",wpindex)) +return self +end +if wpindex>N then +self:T(self.lid..string.format("ERROR: Cannot remove waypoint with index=%d as there are only N=%d waypoints!",wpindex,N)) +return self +end +if wp and wp.marker then +wp.marker:Remove() +end +table.remove(self.waypoints,wpindex) +local n=#self.waypoints +self:T(self.lid..string.format("Removing waypoint UID=%d [temp=%s]: index=%d [currentwp=%d]. N %d-->%d",wp.uid,tostring(istemp),wpindex,self.currentwp,N,n)) +if wpindex>self.currentwp then +if self.currentwp>=n and not(self.adinfinitum or istemp)then +self:_PassedFinalWaypoint(true,"Removed FUTURE waypoint we are currently moving to and that was the LAST waypoint") +end +self:_CheckGroupDone(1) +else +if self.currentwp==1 then +if self.adinfinitum then +self.currentwp=#self.waypoints +else +self.currentwp=1 +end +else +self.currentwp=self.currentwp-1 +end +if(self.adinfinitum or istemp)then +self:_PassedFinalWaypoint(false,"Removed PASSED temporary waypoint") +end +end +end +return self +end +function OPSGROUP:OnEventBirth(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +if self.isFlightgroup then +if EventData.Place then +self.homebase=self.homebase or EventData.Place +self.currbase=EventData.Place +else +self.currbase=nil +end +if self.homebase and not self.destbase then +self.destbase=self.homebase +end +self:T(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned",unitname,self.currbase and self.currbase:GetName()or"unknown")) +else +self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned",unitname)) +end +local element=self:GetElementByName(unitname) +if element and element.status~=OPSGROUP.ElementStatus.SPAWNED then +self:T(self.lid..string.format("EVENT: Element %s born ==> spawned",unitname)) +self:T2(self.lid..string.format("DCS unit=%s isExist=%s",tostring(EventData.IniDCSUnit:getName()),tostring(EventData.IniDCSUnit:isExist()))) +self:ElementSpawned(element) +end +end +end +function OPSGROUP:OnEventHit(EventData) +if EventData and EventData.TgtGroup and EventData.TgtUnit and EventData.TgtGroupName and EventData.TgtGroupName==self.groupname then +self:T2(self.lid..string.format("EVENT: Unit %s hit!",EventData.TgtUnitName)) +local unit=EventData.TgtUnit +local group=EventData.TgtGroup +local unitname=EventData.TgtUnitName +local element=self:GetElementByName(unitname) +self.Nhit=self.Nhit or 0 +self.Nhit=self.Nhit+1 +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +self:ElementHit(element,EventData.IniUnit) +end +end +end +function OPSGROUP:OnEventDead(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +self:T2(self.lid..string.format("EVENT: Unit %s dead!",EventData.IniUnitName)) +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +local element=self:GetElementByName(unitname) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed",element.name)) +self:ElementDestroyed(element) +end +end +end +function OPSGROUP:OnEventRemoveUnit(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +self:T2(self.lid..string.format("EVENT: Unit %s removed!",EventData.IniUnitName)) +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +local element=self:GetElementByName(unitname) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +self:T(self.lid..string.format("EVENT: Element %s removed ==> dead",element.name)) +self:ElementDead(element) +end +end +end +function OPSGROUP:OnEventPlayerLeaveUnit(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +self:T2(self.lid..string.format("EVENT: Player left Unit %s!",EventData.IniUnitName)) +local unit=EventData.IniUnit +local group=EventData.IniGroup +local unitname=EventData.IniUnitName +local element=self:GetElementByName(unitname) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +self:T(self.lid..string.format("EVENT: Player left Element %s ==> dead",element.name)) +self:ElementDead(element) +end +end +end +function OPSGROUP:OnEventKill(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +local targetname=tostring(EventData.TgtUnitName) +self:T2(self.lid..string.format("EVENT: Unit %s killed object %s!",tostring(EventData.IniUnitName),targetname)) +local target=UNIT:FindByName(targetname) +if not target then +target=STATIC:FindByName(targetname,false) +end +if target then +self:T(self.lid..string.format("EVENT: Unit %s killed unit/static %s!",tostring(EventData.IniUnitName),targetname)) +self.Nkills=self.Nkills+1 +local mission=self:GetMissionCurrent() +if mission then +mission.Nkills=mission.Nkills+1 +end +end +end +end +function OPSGROUP:SetTask(DCSTask) +if self:IsAlive()then +if self.taskenroute and#self.taskenroute>0 then +if tostring(DCSTask.id)=="ComboTask"then +for _,task in pairs(self.taskenroute)do +table.insert(DCSTask.params.tasks,1,task) +end +else +local tasks=UTILS.DeepCopy(self.taskenroute) +table.insert(tasks,DCSTask) +DCSTask=self.group.TaskCombo(self,tasks) +end +end +self.controller:setTask(DCSTask) +local text=string.format("SETTING Task %s",tostring(DCSTask.id)) +if tostring(DCSTask.id)=="ComboTask"then +for i,task in pairs(DCSTask.params.tasks)do +text=text..string.format("\n[%d] %s",i,tostring(task.id)) +end +end +self:T(self.lid..text) +end +return self +end +function OPSGROUP:PushTask(DCSTask) +if self:IsAlive()then +if self.taskenroute and#self.taskenroute>0 then +if tostring(DCSTask.id)=="ComboTask"then +for _,task in pairs(self.taskenroute)do +table.insert(DCSTask.params.tasks,1,task) +end +else +local tasks=UTILS.DeepCopy(self.taskenroute) +table.insert(tasks,DCSTask) +DCSTask=self.group.TaskCombo(self,tasks) +end +end +self.controller:pushTask(DCSTask) +local text=string.format("PUSHING Task %s",tostring(DCSTask.id)) +if tostring(DCSTask.id)=="ComboTask"then +for i,task in pairs(DCSTask.params.tasks)do +text=text..string.format("\n[%d] %s",i,tostring(task.id)) +end +end +self:T(self.lid..text) +end +return self +end +function OPSGROUP:HasTaskController() +local hastask=nil +if self.controller then +hastask=self.controller:hasTask() +end +self:T3(self.lid..string.format("Controller hasTask=%s",tostring(hastask))) +return hastask +end +function OPSGROUP:ClearTasks() +local hastask=self:HasTaskController() +if self:IsAlive()and self.controller and self:HasTaskController()then +self:T(self.lid..string.format("CLEARING Tasks")) +self.controller:resetTask() +end +return self +end +function OPSGROUP:AddTask(task,clock,description,prio,duration) +local newtask=self:NewTaskScheduled(task,clock,description,prio,duration) +table.insert(self.taskqueue,newtask) +self:T(self.lid..string.format("Adding SCHEDULED task %s starting at %s",newtask.description,UTILS.SecondsToClock(newtask.time,true))) +self:T3({newtask=newtask}) +return newtask +end +function OPSGROUP:NewTaskScheduled(task,clock,description,prio,duration) +self.taskcounter=self.taskcounter+1 +local time=timer.getAbsTime()+5 +if clock then +if type(clock)=="string"then +time=UTILS.ClockToSeconds(clock) +elseif type(clock)=="number"then +time=timer.getAbsTime()+clock +end +end +local newtask={} +newtask.status=OPSGROUP.TaskStatus.SCHEDULED +newtask.dcstask=task +newtask.description=description or task.id +newtask.prio=prio or 50 +newtask.time=time +newtask.id=self.taskcounter +newtask.duration=duration +newtask.waypoint=-1 +newtask.type=OPSGROUP.TaskType.SCHEDULED +newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d",self.groupname,newtask.id)) +newtask.stopflag:Set(0) +return newtask +end +function OPSGROUP:AddTaskWaypoint(task,Waypoint,description,prio,duration) +Waypoint=Waypoint or self:GetWaypointNext() +if Waypoint then +self.taskcounter=self.taskcounter+1 +local newtask={} +newtask.description=description or string.format("Task #%d",self.taskcounter) +newtask.status=OPSGROUP.TaskStatus.SCHEDULED +newtask.dcstask=task +newtask.prio=prio or 50 +newtask.id=self.taskcounter +newtask.duration=duration +newtask.time=0 +newtask.waypoint=Waypoint.uid +newtask.type=OPSGROUP.TaskType.WAYPOINT +newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d",self.groupname,newtask.id)) +newtask.stopflag:Set(0) +table.insert(self.taskqueue,newtask) +self:T(self.lid..string.format("Adding WAYPOINT task %s at WP ID=%d",newtask.description,newtask.waypoint)) +self:T3({newtask=newtask}) +return newtask +end +return nil +end +function OPSGROUP:AddTaskEnroute(task) +if not self.taskenroute then +self.taskenroute={} +end +local gotit=false +for _,Task in pairs(self.taskenroute)do +if Task.id==task.id then +gotit=true +break +end +end +if not gotit then +self:T(self.lid..string.format("Adding enroute task")) +table.insert(self.taskenroute,task) +end +end +function OPSGROUP:GetTasksWaypoint(id) +local tasks={} +self:_SortTaskQueue() +for _,_task in pairs(self.taskqueue)do +local task=_task +if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then +table.insert(tasks,task) +end +end +return tasks +end +function OPSGROUP:CountTasksWaypoint(id) +local n=0 +for _,_task in pairs(self.taskqueue)do +local task=_task +if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then +n=n+1 +end +end +return n +end +function OPSGROUP:_SortTaskQueue() +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio=task.time then +return task +end +end +return nil +end +function OPSGROUP:GetTaskCurrent() +local task=self:GetTaskByID(self.taskcurrent,OPSGROUP.TaskStatus.EXECUTING) +return task +end +function OPSGROUP:GetTaskByID(id,status) +for _,_task in pairs(self.taskqueue)do +local task=_task +if task.id==id then +if status==nil or status==task.status then +return task +end +end +end +return nil +end +function OPSGROUP:onbeforeTaskExecute(From,Event,To,Task) +local Mission=self:GetMissionByTaskID(Task.id) +if Mission and(Mission.Tpush or#Mission.conditionPush>0)then +if Mission:IsReadyToPush()then +if self:IsWaiting()then +self.Twaiting=nil +self.dTwait=nil +if self:IsFlightgroup()then +self.flaghold:Set(1) +end +end +else +if self:IsWaiting()then +else +local alt=Mission.missionAltitude and UTILS.MetersToFeet(Mission.missionAltitude)or nil +self:Wait(nil,alt) +end +local dt=Mission.Tpush and Mission.Tpush-timer.getAbsTime()or 20 +self:T(self.lid..string.format("Mission %s task execute suspended for %d seconds",Mission.name,dt)) +self:__TaskExecute(-dt,Task) +return false +end +end +if Mission and Mission.opstransport then +local delivered=Mission.opstransport:IsCargoDelivered(self.groupname) +if not delivered then +local dt=30 +self:T(self.lid..string.format("Mission %s task execute suspended for %d seconds because we were not delivered",Mission.name,dt)) +self:__TaskExecute(-dt,Task) +if(self:IsArmygroup()or self:IsNavygroup())and self:IsCruising()then +self:FullStop() +end +return false +end +end +return true +end +function OPSGROUP:onafterTaskExecute(From,Event,To,Task) +local text=string.format("Task %s ID=%d execute",tostring(Task.description),Task.id) +self:T(self.lid..text) +self:T2({Task}) +if self.taskcurrent>0 then +self:TaskCancel() +end +self.taskcurrent=Task.id +Task.timestamp=timer.getAbsTime() +Task.status=OPSGROUP.TaskStatus.EXECUTING +if self:GetTaskCurrent()==nil then +table.insert(self.taskqueue,Task) +end +local Mission=self:GetMissionByTaskID(self.taskcurrent) +self:_UpdateTask(Task,Mission) +if Mission then +self:MissionExecute(Mission) +end +end +function OPSGROUP:_UpdateTask(Task,Mission) +Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) +if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then +local followSet=SET_GROUP:New():AddGroup(self.group) +local param=Task.dcstask.params +local followUnit=UNIT:FindByName(param.unitname) +Task.formation=AI_FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION,"Follow X at given parameters.") +Task.formation:FormationCenterWing(-param.offsetX,50,math.abs(param.altitude),50,param.offsetZ,50) +Task.formation:SetFollowTimeInterval(param.dtFollow) +Task.formation:SetFlightModeFormation(self.group) +Task.formation:Start() +elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then +local zone=Task.dcstask.params.zone +local surfacetypes=nil +if self:IsArmygroup()then +surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD} +elseif self:IsNavygroup()then +surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER} +end +local Coordinate=zone:GetRandomCoordinate(nil,nil,surfacetypes) +local Speed=Task.dcstask.params.speed and UTILS.MpsToKnots(Task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise) +local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude)or nil +local currUID=self:GetWaypointCurrent().uid +local wp=nil +if self.isFlightgroup then +wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) +elseif self.isArmygroup then +wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Task.dcstask.params.formation) +elseif self.isNavygroup then +wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) +end +wp.missionUID=Mission and Mission.auftragsnummer or nil +elseif Task.dcstask.id==AUFTRAG.SpecialTask.RECON then +local target=Task.dcstask.params.target +self.reconindecies={} +for i=1,#target.targets do +table.insert(self.reconindecies,i) +end +local n=1 +if Task.dcstask.params.randomly then +n=UTILS.GetRandomTableElement(self.reconindecies) +else +table.remove(self.reconindecies,n) +end +local object=target.targets[n] +local zone=object.Object +local Coordinate=zone:GetRandomCoordinate() +local Speed=Task.dcstask.params.speed and UTILS.MpsToKnots(Task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise) +local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude)or nil +local currUID=self:GetWaypointCurrent().uid +local wp=nil +if self.isFlightgroup then +wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) +elseif self.isArmygroup then +wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Task.dcstask.params.formation) +elseif self.isNavygroup then +wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) +end +wp.missionUID=Mission and Mission.auftragsnummer or nil +elseif Task.dcstask.id==AUFTRAG.SpecialTask.AMMOSUPPLY or Task.dcstask.id==AUFTRAG.SpecialTask.FUELSUPPLY then +elseif Task.dcstask.id==AUFTRAG.SpecialTask.REARMING then +local rearmed=self:_CheckAmmoFull() +if rearmed then +self:T2(self.lid.."Ammo already full ==> reaming task done!") +self:TaskDone(Task) +else +self:T2(self.lid.."Ammo not full ==> Rearm()") +self:Rearm() +end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then +elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD or Task.dcstask.id==AUFTRAG.SpecialTask.ARMOREDGUARD then +if self:IsArmygroup()or self:IsNavygroup()then +self:FullStop() +else +end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then +if self:IsArmygroup()or self:IsNavygroup()then +self:__FullStop(0.1) +else +end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.AIRDEFENSE or Task.dcstask.id==AUFTRAG.SpecialTask.EWR then +if self:IsArmygroup()or self:IsNavygroup()then +self:FullStop() +else +end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then +local target=Task.dcstask.params.target +local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax)or nil +if Task.dcstask.params.speed then +speed=UTILS.MpsToKnots(Task.dcstask.params.speed) +end +if target then +self:EngageTarget(target,speed,Task.dcstask.params.formation) +end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then +if self.isFlightgroup then +self:T("We are Special Auftrag Patrol Race Track, starting now ...") +local aircraft=self:GetGroup() +aircraft:PatrolRaceTrack(Task.dcstask.params.TrackPoint1,Task.dcstask.params.TrackPoint2,Task.dcstask.params.TrackAltitude,Task.dcstask.params.TrackSpeed,Task.dcstask.params.TrackFormation,false,1) +end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.HOVER then +if self.isFlightgroup then +self:T("We are Special Auftrag HOVER, hovering now ...") +local alt=Task.dcstask.params.hoverAltitude +local time=Task.dcstask.params.hoverTime +local mSpeed=Task.dcstask.params.missionSpeed or self.speedCruise or 150 +local Speed=UTILS.KmphToKnots(mSpeed) +local CruiseAlt=UTILS.FeetToMeters(Task.dcstask.params.missionAltitude or 1000) +local helo=self:GetGroup() +helo:SetSpeed(0.01,true) +helo:SetAltitude(alt,true,"BARO") +self:HoverStart() +local function FlyOn(Helo,Speed,CruiseAlt,Task) +if Helo then +Helo:SetSpeed(Speed,true) +Helo:SetAltitude(CruiseAlt,true,"BARO") +self:T("We are Special Auftrag HOVER, end of hovering now ...") +self:TaskDone(Task) +self:HoverEnd() +end +end +local timer=TIMER:New(FlyOn,helo,Speed,CruiseAlt,Task) +timer:Start(time) +end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then +self:T(self.lid.."Executing task for relocation mission") +local legion=Task.dcstask.params.legion +local Coordinate=legion.spawnzone:GetRandomCoordinate() +local currUID=self:GetWaypointCurrent().uid +local wp=nil +if self.isArmygroup then +self:T2(self.lid.."Routing group to spawn zone of new legion") +wp=ARMYGROUP.AddWaypoint(self,Coordinate,UTILS.KmphToKnots(self.speedCruise),currUID,Mission.optionFormation) +elseif self.isFlightgroup then +self:T2(self.lid.."Routing group to intermediate point near new legion") +Coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate,0.8) +wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,UTILS.KmphToKnots(self.speedCruise),currUID,UTILS.MetersToFeet(self.altitudeCruise)) +elseif self.isNavygroup then +self:T2(self.lid.."Routing group to spawn zone of new legion") +wp=NAVYGROUP.AddWaypoint(self,Coordinate,UTILS.KmphToKnots(self.speedCruise),currUID) +else +end +wp.missionUID=Mission and Mission.auftragsnummer or nil +elseif Task.dcstask.id==AUFTRAG.SpecialTask.CAPTUREZONE then +if self:IsEngaging()then +self:T2(self.lid..string.format("CaptureZone: Engaging currently!")) +else +local Coalitions=UTILS.GetCoalitionEnemy(self:GetCoalition(),false) +local zoneCurr=Task.target +if zoneCurr then +self:T(self.lid..string.format("Current target zone=%s owner=%s",zoneCurr:GetName(),zoneCurr:GetOwnerName())) +if zoneCurr:GetOwner()==self:GetCoalition()then +self:T(self.lid..string.format("Zone %s captured ==> Task DONE!",zoneCurr:GetName())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else +self:TaskDone(Task) +end +else +self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) +if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then +self:T(self.lid..string.format("Zone %s NOT captured and EXECUTING ==> Find target",zoneCurr:GetName())) +local targetgroup=zoneCurr:GetScannedGroupSet():GetClosestGroup(self.coordinate,Coalitions) +if targetgroup then +self:T(self.lid..string.format("Zone %s NOT captured: engaging target %s",zoneCurr:GetName(),targetgroup:GetName())) +self:EngageTarget(targetgroup) +else +if self:IsFlightgroup()then +self:T(self.lid..string.format("Zone %s not captured but no target group could be found ==> TaskDone as FLIGHTGROUPS cannot capture zones",zoneCurr:GetName())) +self:TaskDone(Task) +else +self:T(self.lid..string.format("Zone %s not captured but no target group could be found. Should be captured in the next zone evaluation.",zoneCurr:GetName())) +end +end +else +self:T(self.lid..string.format("Zone %s NOT captured and NOT EXECUTING",zoneCurr:GetName())) +end +end +else +self:T(self.lid..string.format("NO Current target zone=%s")) +end +end +else +if Task.type==OPSGROUP.TaskType.SCHEDULED or Task.ismission then +local DCSTask=nil +if Task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then +local vec2=self:GetVec2() +local param=Task.dcstask.params +local heading=param.heading or math.random(1,360) +local Altitude=param.altitude or 500 +local Alpha=param.angle or math.random(45,85) +local distance=Altitude/math.tan(math.rad(Alpha)) +local tvec2=UTILS.Vec2Translate(vec2,distance,heading) +self:T(self.lid..string.format("Barrage: Shots=%s, Altitude=%d m, Angle=%d°, heading=%03d°, distance=%d m",tostring(param.shots),Altitude,Alpha,heading,distance)) +DCSTask=CONTROLLABLE.TaskFireAtPoint(nil,tvec2,param.radius,param.shots,param.weaponType,Altitude) +elseif Task.ismission and Task.dcstask.id=='FireAtPoint'then +DCSTask=UTILS.DeepCopy(Task.dcstask) +local ammo=self:GetAmmoTot() +local nAmmo=ammo.Total +local weaponType=DCSTask.params.weaponType or-1 +if weaponType==ENUMS.WeaponFlag.CruiseMissile then +nAmmo=ammo.MissilesCR +elseif weaponType==ENUMS.WeaponFlag.AnyRocket then +nAmmo=ammo.Rockets +elseif weaponType==ENUMS.WeaponFlag.Cannons then +nAmmo=ammo.Guns +end +local nShots=DCSTask.params.expendQty or 1 +self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) +if nShots==-1 then +nShots=nAmmo +self:T(self.lid..string.format("Fire at point taking max amount of ammo = %d",nShots)) +elseif nShots<1 then +local p=nShots +nShots=UTILS.Round(p*nAmmo,0) +self:T(self.lid..string.format("Fire at point taking %.1f percent amount of ammo = %d",p,nShots)) +else +nShots=math.min(nShots,nAmmo) +end +DCSTask.params.expendQty=nShots +else +DCSTask=Task.dcstask +end +self:_SandwitchDCSTask(DCSTask,Task) +elseif Task.type==OPSGROUP.TaskType.WAYPOINT then +else +self:T(self.lid.."ERROR: Unknown task type: ") +end +end +end +function OPSGROUP:_SandwitchDCSTask(DCSTask,Task,SetTask,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP._SandwitchDCSTask,self,DCSTask,Task,SetTask) +else +local DCStasks={} +if DCSTask.id=='ComboTask'then +for TaskID,Task in ipairs(DCSTask.params.tasks)do +table.insert(DCStasks,Task) +end +else +table.insert(DCStasks,DCSTask) +end +local TaskCombo=self.group:TaskCombo(DCStasks) +local TaskCondition=self.group:TaskCondition(nil,Task.stopflag:GetName(),1,nil,Task.duration) +local TaskControlled=self.group:TaskControlled(TaskCombo,TaskCondition) +local TaskDone=self.group:TaskFunction("OPSGROUP._TaskDone",self,Task) +local TaskFinal=self.group:TaskCombo({TaskControlled,TaskDone}) +if SetTask then +self:SetTask(TaskFinal) +else +self:PushTask(TaskFinal) +end +end +end +function OPSGROUP:onafterTaskCancel(From,Event,To,Task) +local currenttask=self:GetTaskCurrent() +Task=Task or currenttask +if Task then +if currenttask and Task.id==currenttask.id then +local stopflag=Task.stopflag:Get() +local text=string.format("Current task %s ID=%d cancelled (flag %s=%d)",Task.description,Task.id,Task.stopflag:GetName(),stopflag) +self:T(self.lid..text) +Task.stopflag:Set(1) +local done=false +if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then +Task.formation:Stop() +done=true +elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then +done=true +elseif Task.dcstask.id==AUFTRAG.SpecialTask.RECON then +done=true +elseif Task.dcstask.id==AUFTRAG.SpecialTask.AMMOSUPPLY then +done=true +elseif Task.dcstask.id==AUFTRAG.SpecialTask.FUELSUPPLY then +done=true +elseif Task.dcstask.id==AUFTRAG.SpecialTask.REARMING then +done=true +elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then +done=true +elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD or Task.dcstask.id==AUFTRAG.SpecialTask.ARMOREDGUARD then +done=true +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then +done=true +elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then +done=true +elseif stopflag==1 or(not self:IsAlive())or self:IsDead()or self:IsStopped()then +done=true +end +if done then +self:TaskDone(Task) +end +else +self:T(self.lid..string.format("TaskCancel: Setting task %s ID=%d to DONE",Task.description,Task.id)) +self:TaskDone(Task) +end +else +local text=string.format("WARNING: No (current) task to cancel!") +self:T(self.lid..text) +end +end +function OPSGROUP:onbeforeTaskDone(From,Event,To,Task) +local allowed=true +if Task.status==OPSGROUP.TaskStatus.PAUSED then +allowed=false +end +return allowed +end +function OPSGROUP:onafterTaskDone(From,Event,To,Task) +local text=string.format("Task done: %s ID=%d",Task.description,Task.id) +self:T(self.lid..text) +if Task.id==self.taskcurrent then +self.taskcurrent=0 +end +Task.status=OPSGROUP.TaskStatus.DONE +if Task.backupROE then +self:SwitchROE(Task.backupROE) +end +local Mission=self:GetMissionByTaskID(Task.id) +if Mission and Mission:IsNotOver()then +local status=Mission:GetGroupStatus(self) +if status~=AUFTRAG.GroupStatus.PAUSED then +if Mission.type==AUFTRAG.Type.CAPTUREZONE and Mission:CountMissionTargets()>0 then +self:T(self.lid.."Remove mission waypoints") +self:_RemoveMissionWaypoints(Mission,false) +if self:IsFlightgroup()then +else +self:T(self.lid.."Task done ==> Route to mission for next opszone") +self:MissionStart(Mission) +return +end +end +local EgressUID=Mission:GetGroupEgressWaypointUID(self) +if EgressUID then +self:T(self.lid..string.format("Task Done but Egress waypoint defined ==> Will call Mission Done once group passed waypoint UID=%d!",EgressUID)) +else +self:T(self.lid.."Task Done ==> Mission Done!") +self:MissionDone(Mission) +end +else +if self:IsOnMission(Mission.auftragsnummer)then +self.currentmission=nil +end +self:T(self.lid.."Remove mission waypoints") +self:_RemoveMissionWaypoints(Mission,false) +end +else +if Task.description=="Engage_Target"then +self:T(self.lid.."Task DONE Engage_Target ==> Cruise") +self:Disengage() +end +if Task.description==AUFTRAG.SpecialTask.ONGUARD or Task.description==AUFTRAG.SpecialTask.ARMOREDGUARD or Task.description==AUFTRAG.SpecialTask.NOTHING then +self:T(self.lid.."Task DONE OnGuard ==> Cruise") +self:Cruise() +end +if Task.description=="Task_Land_At"then +self:T(self.lid.."Taske DONE Task_Land_At ==> Wait") +self:Cruise() +self:Wait(20,100) +else +self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") +self:_CheckGroupDone(1) +end +end +end +function OPSGROUP:AddMission(Mission) +Mission:AddOpsGroup(self) +Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.SCHEDULED) +Mission:Scheduled() +Mission.Nelements=Mission.Nelements+#self.elements +Mission.Ngroups=Mission.Ngroups+1 +table.insert(self.missionqueue,Mission) +self.adinfinitum=Mission.DCStask.params.adinfinitum and Mission.DCStask.params.adinfinitum or false +local text=string.format("Added %s mission %s starting at %s, stopping at %s", +tostring(Mission.type),tostring(Mission.name),UTILS.SecondsToClock(Mission.Tstart,true),Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop,true)or"INF") +self:T(self.lid..text) +return self +end +function OPSGROUP:RemoveMission(Mission) +for i=#self.missionqueue,1,-1 do +local mission=self.missionqueue[i] +if mission.auftragsnummer==Mission.auftragsnummer then +local Task=Mission:GetGroupWaypointTask(self) +if Task then +self:RemoveTask(Task) +end +for j=#self.pausedmissions,1,-1 do +local mid=self.pausedmissions[j] +if Mission.auftragsnummer==mid then +table.remove(self.pausedmissions,j) +end +end +table.remove(self.missionqueue,i) +return self +end +end +return self +end +function OPSGROUP:CancelAllMissions() +self:T(self.lid.."Cancelling ALL missions!") +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +local mystatus=mission:GetGroupStatus(self) +if not(mystatus==AUFTRAG.GroupStatus.DONE or mystatus==AUFTRAG.GroupStatus.CANCELLED)then +self:T(self.lid.."Cancelling mission "..tostring(mission:GetName())) +self:MissionCancel(mission) +end +end +end +function OPSGROUP:CountRemainingMissison() +local N=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission and mission:IsNotOver()then +local status=mission:GetGroupStatus(self) +if status~=AUFTRAG.GroupStatus.DONE and status~=AUFTRAG.GroupStatus.CANCELLED then +N=N+1 +end +end +end +return N +end +function OPSGROUP:CountRemainingTransports() +local N=0 +for _,_transport in pairs(self.cargoqueue)do +local transport=_transport +local mystatus=transport:GetCarrierTransportStatus(self) +local status=transport:GetState() +self:T(self.lid..string.format("Transport my status=%s [%s]",mystatus,status)) +if transport and mystatus==OPSTRANSPORT.Status.SCHEDULED and status~=OPSTRANSPORT.Status.DELIVERED and status~=OPSTRANSPORT.Status.CANCELLED then +N=N+1 +end +end +if N==0 and self.cargoTransport and +self.cargoTransport:GetState()~=OPSTRANSPORT.Status.DELIVERED and self.cargoTransport:GetCarrierTransportStatus(self)~=OPSTRANSPORT.Status.DELIVERED and +self.cargoTransport:GetState()~=OPSTRANSPORT.Status.CANCELLED and self.cargoTransport:GetCarrierTransportStatus(self)~=OPSTRANSPORT.Status.CANCELLED then +N=1 +end +return N +end +function OPSGROUP:_GetNextMission() +if self:IsPickingup()or self:IsLoading()or self:IsTransporting()or self:IsUnloading()or self:IsLoaded()then +return nil +end +local Nmissions=#self.missionqueue +if Nmissions==0 then +return nil +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio3.6 or true then +self:RouteToMission(Mission,3) +else +self:T(self.lid.."Immobile GROUP!") +local Clock=Mission.Tpush and UTILS.SecondsToClock(Mission.Tpush)or 5 +local Task=self:AddTask(Mission.DCStask,Clock,Mission.name,Mission.prio,Mission.duration) +Task.ismission=true +Mission:SetGroupWaypointTask(self,Task) +self:__TaskExecute(3,Task) +end +end +function OPSGROUP:onafterMissionExecute(From,Event,To,Mission) +local text=string.format("Executing %s Mission %s, target %s",Mission.type,tostring(Mission.name),Mission:GetTargetName()) +self:T(self.lid..text) +Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.EXECUTING) +Mission:Executing() +if self:IsHolding()and not self:HasPassedFinalWaypoint()then +self:Cruise() +end +if Mission.engagedetectedOn then +self:SetEngageDetectedOn(UTILS.MetersToNM(Mission.engagedetectedRmax),Mission.engagedetectedTypes,Mission.engagedetectedEngageZones,Mission.engagedetectedNoEngageZones) +end +if self.isFlightgroup then +if Mission.prohibitABExecute==true then +self:SetProhibitAfterburner() +self:T(self.lid.."Set prohibit AB") +elseif Mission.prohibitABExecute==false then +self:SetAllowAfterburner() +self:T2(self.lid.."Set allow AB") +end +end +end +function OPSGROUP:onafterPauseMission(From,Event,To) +local Mission=self:GetMissionCurrent() +if Mission then +Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.PAUSED) +local Task=Mission:GetGroupWaypointTask(self) +self:T(self.lid..string.format("Pausing current mission %s. Task=%s",tostring(Mission.name),tostring(Task and Task.description or"WTF"))) +self:TaskCancel(Task) +self:_RemoveMissionWaypoints(Mission) +table.insert(self.pausedmissions,1,Mission.auftragsnummer) +end +end +function OPSGROUP:onafterUnpauseMission(From,Event,To) +local mission=self:_GetPausedMission() +if mission then +self:T(self.lid..string.format("Unpausing mission %s [%s]",mission:GetName(),mission:GetType())) +mission.unpaused=true +self:MissionStart(mission) +for i,mid in pairs(self.pausedmissions)do +if mid==mission.auftragsnummer then +self:T(self.lid..string.format("Removing paused mission id=%d",mid)) +table.remove(self.pausedmissions,i) +break +end +end +else +self:T(self.lid.."ERROR: No mission to unpause!") +end +end +function OPSGROUP:onafterMissionCancel(From,Event,To,Mission) +if self:IsOnMission(Mission.auftragsnummer)then +local Task=Mission:GetGroupWaypointTask(self) +if Task then +self:T(self.lid..string.format("Cancel current mission %s. Task=%s",tostring(Mission.name),tostring(Task and Task.description or"WTF"))) +self:TaskCancel(Task) +else +self:MissionDone(Mission) +end +else +Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.CANCELLED) +self:RemoveMission(Mission) +self:_CheckGroupDone(1) +end +end +function OPSGROUP:_RemoveMissionWaypoints(Mission,Silently) +for i=#self.waypoints,1,-1 do +local wp=self.waypoints[i] +if wp.missionUID==Mission.auftragsnummer then +if Silently then +table.remove(self.waypoints,i) +else +self:RemoveWaypoint(i) +end +end +end +end +function OPSGROUP:onafterMissionDone(From,Event,To,Mission) +local text=string.format("Mission %s DONE!",Mission.name) +self:T(self.lid..text) +Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) +if self:IsOnMission(Mission.auftragsnummer)then +self.currentmission=nil +end +self:_RemoveMissionWaypoints(Mission) +if Mission.patroldata then +Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 +AIRWING.UpdatePatrolPointMarker(self,Mission.patroldata) +end +if Mission.engagedetectedOn then +self:SetEngageDetectedOff() +end +if Mission.optionROE then +self:SwitchROE() +end +if self:IsFlightgroup()and Mission.optionROT then +self:SwitchROT() +end +if Mission.optionAlarm then +self:SwitchAlarmstate() +end +if Mission.optionEPLRS then +self:SwitchEPLRS() +end +if Mission.optionEmission then +self:SwitchEmission() +end +if Mission.optionInvisible then +self:SwitchInvisible() +end +if Mission.optionImmortal then +self:SwitchImmortal() +end +if Mission.optionFormation and self:IsFlightgroup()then +self:SwitchFormation() +end +if Mission.radio then +self:SwitchRadio() +end +if Mission.tacan then +self:_SwitchTACAN() +local cohort=self.cohort +if cohort then +cohort:ReturnTacan(Mission.tacan.Channel) +end +local asset=Mission:GetAssetByName(self.groupname) +if asset then +asset.tacan=nil +end +end +if Mission.icls then +self:_SwitchICLS() +end +if self.legion and Mission.legionReturn~=nil then +self:SetReturnToLegion(Mission.legionReturn) +end +local delay=1 +if Mission.type==AUFTRAG.Type.ARTY then +delay=60 +elseif Mission.type==AUFTRAG.Type.RELOCATECOHORT then +local legion=Mission.DCStask.params.legion +self:T(self.lid..string.format("Asset relocated to new legion=%s",tostring(legion.alias))) +local asset=Mission:GetAssetByName(self.groupname) +if asset then +asset.wid=legion.uid +end +self.legion=legion +if self.isArmygroup then +self:T2(self.lid.."Adding asset via ReturnToLegion()") +self:ReturnToLegion() +elseif self.isFlightgroup then +self:T2(self.lid.."Adding asset via RTB to new legion airbase") +self:RTB(self.legion.airbase) +end +return +end +if self.isFlightgroup then +if Mission.prohibitAB==true then +self:T2("Setting prohibit AB") +self:SetProhibitAfterburner() +elseif Mission.prohibitAB==false then +self:T2("Setting allow AB") +self:SetAllowAfterburner() +end +end +if self.legion and self.legionReturn==false and self.waypoints and#self.waypoints==1 then +local Coordinate=self:GetCoordinate() +if self.isArmygroup then +ARMYGROUP.AddWaypoint(self,Coordinate,0,nil,nil,false) +elseif self.isNavygroup then +NAVYGROUP.AddWaypoint(self,Coordinate,0,nil,nil,false) +end +self:RemoveWaypoint(1) +self:_PassedFinalWaypoint(true,"Passed final waypoint as group is done with mission but should NOT return to its legion") +end +self:_CheckGroupDone(delay) +end +function OPSGROUP:RouteToMission(mission,delay) +if delay and delay>0 then +self:ScheduleOnce(delay,OPSGROUP.RouteToMission,self,mission) +else +self:T(self.lid..string.format("Route To Mission")) +if self:IsDead()or self:IsStopped()then +self:T(self.lid..string.format("Route To Mission: I am DEAD or STOPPED! Ooops...")) +return +end +if self:IsCargo()then +self:T(self.lid..string.format("Route To Mission: I am CARGO! You cannot route me...")) +return +end +if mission.type==AUFTRAG.Type.OPSTRANSPORT then +self:T(self.lid..string.format("Route To Mission: I am OPSTRANSPORT! Add transport and return...")) +self:AddOpsTransport(mission.opstransport) +return +end +if mission.type==AUFTRAG.Type.ALERT5 then +self:T(self.lid..string.format("Route To Mission: I am ALERT5! Go right to MissionExecute()...")) +self:MissionExecute(mission) +return +end +local uid=self:GetWaypointCurrentUID() +local waypointcoord=nil +local currentcoord=self:GetCoordinate() +local roadcoord=currentcoord:GetClosestPointToRoad() +local roaddist=nil +if roadcoord then +roaddist=currentcoord:Get2DDistance(roadcoord) +end +local targetzone=nil +local randomradius=mission.missionWaypointRadius or 1000 +local surfacetypes=nil +if self:IsArmygroup()then +surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD} +elseif self:IsNavygroup()then +surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER} +end +local targetobject=mission:GetObjective(currentcoord,UTILS.GetCoalitionEnemy(self:GetCoalition(),true)) +if targetobject then +self:T(self.lid..string.format("Route to mission target object %s",targetobject:GetName())) +end +if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname)then +local tzc=mission.opstransport:GetTZCofCargo(self.groupname) +local pickupzone=tzc.PickupZone +if self:IsInZone(pickupzone)then +self:PauseMission() +self:FullStop() +return +else +waypointcoord=pickupzone:GetRandomCoordinate() +end +elseif mission.type==AUFTRAG.Type.PATROLZONE or +mission.type==AUFTRAG.Type.BARRAGE or +mission.type==AUFTRAG.Type.AMMOSUPPLY or +mission.type==AUFTRAG.Type.FUELSUPPLY or +mission.type==AUFTRAG.Type.REARMING or +mission.type==AUFTRAG.Type.AIRDEFENSE or +mission.type==AUFTRAG.Type.EWR then +targetzone=targetobject +waypointcoord=targetzone:GetRandomCoordinate(nil,nil,surfacetypes) +elseif mission.type==AUFTRAG.Type.ONGUARD or mission.type==AUFTRAG.Type.ARMOREDGUARD then +waypointcoord=mission:GetMissionWaypointCoord(self.group,nil,surfacetypes) +elseif mission.type==AUFTRAG.Type.NOTHING then +targetzone=targetobject +waypointcoord=targetzone:GetRandomCoordinate(nil,nil,surfacetypes) +elseif mission.type==AUFTRAG.Type.HOVER then +local zone=targetobject +waypointcoord=zone:GetCoordinate() +elseif mission.type==AUFTRAG.Type.RELOCATECOHORT then +local ToCoordinate=mission.DCStask.params.legion:GetCoordinate() +if self.isFlightgroup then +waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate,0.2):SetAltitude(self.altitudeCruise) +elseif self.isArmygroup then +if roadcoord then +waypointcoord=roadcoord +else +waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate,100) +end +else +waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate,0.05) +end +elseif mission.type==AUFTRAG.Type.CAPTUREZONE then +targetzone=targetobject:GetZone() +waypointcoord=targetzone:GetRandomCoordinate(nil,nil,surfacetypes) +else +waypointcoord=mission:GetMissionWaypointCoord(self.group,randomradius,surfacetypes) +end +for _,task in pairs(mission.enrouteTasks)do +self:AddTaskEnroute(task) +end +local SpeedToMission=mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed)or self:GetSpeedCruise() +if mission.type==AUFTRAG.Type.TROOPTRANSPORT then +mission.DCStask=mission:GetDCSMissionTask(self.group) +local pradius=mission.transportPickupRadius +local pickupZone=ZONE_RADIUS:New("Pickup Zone",mission.transportPickup:GetVec2(),pradius) +for _,_group in pairs(mission.transportGroupSet.Set)do +local group=_group +if group and group:IsAlive()then +local pcoord=pickupZone:GetRandomCoordinate(20,pradius,{land.SurfaceType.LAND,land.SurfaceType.ROAD}) +local DCSTask=group:TaskEmbarkToTransport(pcoord,pradius) +group:SetTask(DCSTask,5) +end +end +elseif mission.type==AUFTRAG.Type.ARTY then +local targetcoord=mission:GetTargetCoordinate() +local inRange=self:InWeaponRange(targetcoord,mission.engageWeaponType,waypointcoord) +if inRange then +else +local coordInRange=self:GetCoordinateInRange(targetcoord,mission.engageWeaponType,waypointcoord,surfacetypes) +if coordInRange then +local waypoint=nil +if self:IsFlightgroup()then +waypoint=FLIGHTGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) +elseif self:IsArmygroup()then +waypoint=ARMYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,mission.optionFormation,false) +elseif self:IsNavygroup()then +waypoint=NAVYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) +end +waypoint.missionUID=mission.auftragsnummer +waypointcoord=coordInRange +uid=waypoint.uid +end +end +end +local d=currentcoord:Get2DDistance(waypointcoord) +self:T(self.lid..string.format("Distance to ingress waypoint=%.1f m",d)) +local waypoint=nil +if self:IsFlightgroup()then +local ingresscoord=mission:GetMissionIngressCoord() +local holdingcoord=mission:GetMissionHoldingCoord() +if holdingcoord then +waypoint=FLIGHTGROUP.AddWaypoint(self,holdingcoord,mission.missionHoldingCoordSpeed or SpeedToMission,uid,UTILS.MetersToFeet(mission.missionHoldingCoordAlt or self.altitudeCruise),false) +uid=waypoint.uid +self.flaghold:Set(0) +local TaskOrbit=self.group:TaskOrbit(holdingcoord,mission.missionHoldingCoordAlt,UTILS.KnotsToMps(mission.missionHoldingCoordSpeed or SpeedToMission)) +local TaskStop=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1,nil,mission.missionHoldingDuration or 900) +local TaskCntr=self.group:TaskControlled(TaskOrbit,TaskStop) +local TaskOver=self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting",self) +local DCSTasks=self.group:TaskCombo({TaskCntr,TaskOver}) +local waypointtask=self:AddTaskWaypoint(DCSTasks,waypoint,"Holding") +waypointtask.ismission=false +self.isHoldingAtHoldingPoint=true +end +if ingresscoord then +waypoint=FLIGHTGROUP.AddWaypoint(self,ingresscoord,mission.missionIngressCoordSpeed or SpeedToMission,uid,UTILS.MetersToFeet(mission.missionIngressCoordAlt or self.altitudeCruise),false) +uid=waypoint.uid +end +waypoint=FLIGHTGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) +elseif self:IsArmygroup()then +local formation=mission.optionFormation +if d<1000 or mission.type==AUFTRAG.Type.RELOCATECOHORT then +formation=ENUMS.Formation.Vehicle.OffRoad +end +waypoint=ARMYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,formation,false) +elseif self:IsNavygroup()then +waypoint=NAVYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) +end +waypoint.missionUID=mission.auftragsnummer +local waypointtask=self:AddTaskWaypoint(mission.DCStask,waypoint,mission.name,mission.prio,mission.duration) +waypointtask.ismission=true +waypointtask.target=targetobject +mission:SetGroupWaypointTask(self,waypointtask) +mission:SetGroupWaypointIndex(self,waypoint.uid) +local egresscoord=mission:GetMissionEgressCoord() +if egresscoord then +local Ewaypoint=nil +if self:IsFlightgroup()then +Ewaypoint=FLIGHTGROUP.AddWaypoint(self,egresscoord,mission.missionEgressCoordSpeed or SpeedToMission,waypoint.uid,UTILS.MetersToFeet(mission.missionEgressCoordAlt or self.altitudeCruise),false) +elseif self:IsArmygroup()then +Ewaypoint=ARMYGROUP.AddWaypoint(self,egresscoord,SpeedToMission,waypoint.uid,mission.optionFormation,false) +elseif self:IsNavygroup()then +Ewaypoint=NAVYGROUP.AddWaypoint(self,egresscoord,SpeedToMission,waypoint.uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) +end +Ewaypoint.missionUID=mission.auftragsnummer +mission:SetGroupEgressWaypointUID(self,Ewaypoint.uid) +end +if targetzone and self:IsInZone(targetzone)then +self:T(self.lid.."Already in mission zone ==> TaskExecute()") +self:TaskExecute(waypointtask) +return +elseif d<25 then +self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") +self:TaskExecute(waypointtask) +return +end +if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then +self:Teleport(waypointcoord,nil,true) +self:__TaskExecute(-1,waypointtask) +else +if self:IsArmygroup()then +self:Cruise(SpeedToMission) +elseif self:IsNavygroup()then +self:Cruise(SpeedToMission) +elseif self:IsFlightgroup()then +self:UpdateRoute() +end +end +self:_SetMissionOptions(mission) +end +end +function OPSGROUP:_SetMissionOptions(mission) +if mission.optionROE then +self:SwitchROE(mission.optionROE) +end +if mission.optionROT then +self:SwitchROT(mission.optionROT) +end +if mission.optionAlarm then +self:SwitchAlarmstate(mission.optionAlarm) +end +if mission.optionEPLRS then +self:SwitchEPLRS(mission.optionEPLRS) +end +if mission.optionEmission then +self:SwitchEmission(mission.optionEmission) +end +if mission.optionInvisible then +self:SwitchInvisible(mission.optionInvisible) +end +if mission.optionImmortal then +self:SwitchImmortal(mission.optionImmortal) +end +if mission.optionFormation and self:IsFlightgroup()then +self:SwitchFormation(mission.optionFormation) +end +if mission.radio then +self:SwitchRadio(mission.radio.Freq,mission.radio.Modu) +end +if mission.tacan then +self:SwitchTACAN(mission.tacan.Channel,mission.tacan.Morse,mission.tacan.BeaconName,mission.tacan.Band) +end +if mission.icls then +self:SwitchICLS(mission.icls.Channel,mission.icls.Morse,mission.icls.UnitName) +end +if self.isFlightgroup then +if mission.prohibitAB==true then +self:SetProhibitAfterburner() +self:T2("Set prohibit AB") +elseif mission.prohibitAB==false then +self:SetAllowAfterburner() +self:T2("Set allow AB") +end +end +return self +end +function OPSGROUP:_QueueUpdate() +if self:IsExist()then +local mission=self:_GetNextMission() +if mission then +local currentmission=self:GetMissionCurrent() +if currentmission then +if mission.urgent and mission.prio0 then +self:T(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) +Tsuspend=-30 +allowed=false +end +if self.cargoTransport then +self:T(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) +Tsuspend=-30 +allowed=false +end +if Tsuspend and not allowed then +self:__Wait(Tsuspend,Duration) +end +return allowed +end +function OPSGROUP:onafterWait(From,Event,To,Duration) +self:FullStop() +self.Twaiting=timer.getAbsTime() +self.dTwait=Duration +end +function OPSGROUP:onafterPassingWaypoint(From,Event,To,Waypoint) +local task=self:GetTaskCurrent() +local mission=nil +if task then +mission=self:GetMissionByTaskID(task.id) +end +if task and task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then +self:RemoveWaypointByID(Waypoint.uid) +local zone=task.dcstask.params.zone +local surfacetypes=nil +if self:IsArmygroup()then +surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD} +elseif self:IsNavygroup()then +surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER} +end +local Coordinate=zone:GetRandomCoordinate(nil,nil,surfacetypes) +local Speed=task.dcstask.params.speed and UTILS.MpsToKnots(task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise) +local Altitude=UTILS.MetersToFeet(task.dcstask.params.altitude or self.altitudeCruise) +local currUID=self:GetWaypointCurrent().uid +local wp=nil +if self.isFlightgroup then +wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) +elseif self.isArmygroup then +wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,task.dcstask.params.formation) +elseif self.isNavygroup then +wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) +end +wp.missionUID=mission and mission.auftragsnummer or nil +elseif task and task.dcstask.id==AUFTRAG.SpecialTask.RECON then +local target=task.dcstask.params.target +if self.adinfinitum and#self.reconindecies==0 then +self.reconindecies={} +for i=1,#target.targets do +table.insert(self.reconindecies,i) +end +end +if#self.reconindecies>0 then +local n=1 +if task.dcstask.params.randomly then +n=UTILS.GetRandomTableElement(self.reconindecies) +else +n=self.reconindecies[1] +table.remove(self.reconindecies,1) +end +local object=target.targets[n] +local zone=object.Object +local Coordinate=zone:GetRandomCoordinate() +local Speed=task.dcstask.params.speed and UTILS.MpsToKnots(task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise) +local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude)or nil +local currUID=self:GetWaypointCurrent().uid +local wp=nil +if self.isFlightgroup then +wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) +elseif self.isArmygroup then +wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,task.dcstask.params.formation) +elseif self.isNavygroup then +wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude) +end +wp.missionUID=mission and mission.auftragsnummer or nil +else +local wpindex=self:GetWaypointIndex(Waypoint.uid) +if wpindex==nil or wpindex==#self.waypoints then +if not self.adinfinitum or#self.waypoints<=1 then +self:_PassedFinalWaypoint(true,"Passing waypoint and NOT adinfinitum and #self.waypoints<=1") +end +end +self:TaskDone(task) +end +elseif task and task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then +local legion=task.dcstask.params.legion +self:T(self.lid..string.format("Asset arrived at relocation task waypoint ==> Task Done!")) +self:TaskDone(task) +elseif task and task.dcstask.id==AUFTRAG.SpecialTask.REARMING then +self:T(self.lid..string.format("FF Rearming Mission ==> Rearm()")) +self:Rearm() +else +local ntasks=self:_SetWaypointTasks(Waypoint) +local wpindex=self:GetWaypointIndex(Waypoint.uid) +if wpindex==nil or wpindex==#self.waypoints then +if self.adinfinitum then +if Waypoint.missionUID then +else +if#self.waypoints<=1 then +self:_PassedFinalWaypoint(true,"PassingWaypoint: adinfinitum but only ONE WAYPOINT left") +else +self:__UpdateRoute(-0.01,1,1) +end +end +else +self:_PassedFinalWaypoint(true,"PassingWaypoint: wpindex=#self.waypoints (or wpindex=nil)") +end +elseif wpindex==1 then +if self.adinfinitum then +if#self.waypoints<=1 then +self:_PassedFinalWaypoint(true,"PassingWaypoint: adinfinitum but only ONE WAYPOINT left") +else +if not Waypoint.missionUID then +self:__UpdateRoute(-0.01,2) +end +end +end +end +local isEgress=false +if Waypoint.missionUID then +self:T2(self.lid..string.format("Passing mission waypoint UID=%s",tostring(Waypoint.uid))) +local mission=self:GetMissionByID(Waypoint.missionUID) +local EgressUID=mission and mission:GetGroupEgressWaypointUID(self)or nil +isEgress=EgressUID and Waypoint.uid==EgressUID +if isEgress and mission:GetGroupStatus(self)~=AUFTRAG.GroupStatus.DONE then +self:MissionDone(mission) +end +end +if ntasks==0 and self:HasPassedFinalWaypoint()and not isEgress then +self:_CheckGroupDone(0.01) +end +local text=string.format("Group passed waypoint %s/%d ID=%d: final=%s detour=%s astar=%s", +tostring(wpindex),#self.waypoints,Waypoint.uid,tostring(self.passedfinalwp),tostring(Waypoint.detour),tostring(Waypoint.astar)) +self:T(self.lid..text) +end +local wpnext=self:GetWaypointNext() +if wpnext then +self.speedWp=wpnext.speed +self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s",self.speedWp)) +end +end +function OPSGROUP:_SetWaypointTasks(Waypoint) +local tasks=self:GetTasksWaypoint(Waypoint.uid) +local text=string.format("WP uid=%d tasks:",Waypoint.uid) +local missiontask=nil +if#tasks>0 then +for i,_task in pairs(tasks)do +local task=_task +text=text..string.format("\n[%d] %s",i,task.description) +if task.ismission then +missiontask=task +end +end +else +text=text.." None" +end +self:T(self.lid..text) +if missiontask then +self:T(self.lid.."Executing mission task") +local mission=self:GetMissionByTaskID(missiontask.id) +if mission then +if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname)then +self:PauseMission() +return +end +end +self:TaskExecute(missiontask) +return 1 +end +local taskswp={} +for _,task in pairs(tasks)do +local Task=task +table.insert(taskswp,self.group:TaskFunction("OPSGROUP._TaskExecute",self,Task)) +local TaskCondition=self.group:TaskCondition(nil,Task.stopflag:GetName(),1,nil,Task.duration) +table.insert(taskswp,self.group:TaskControlled(Task.dcstask,TaskCondition)) +table.insert(taskswp,self.group:TaskFunction("OPSGROUP._TaskDone",self,Task)) +end +if#taskswp>0 then +self:SetTask(self.group:TaskCombo(taskswp)) +end +return#taskswp +end +function OPSGROUP:onafterPassedFinalWaypoint(From,Event,To) +self:T(self.lid..string.format("Group passed final waypoint")) +end +function OPSGROUP:onafterGotoWaypoint(From,Event,To,UID,Speed) +local n=self:GetWaypointIndex(UID) +if n then +Speed=Speed or self:GetSpeedToWaypoint(n) +self:T(self.lid..string.format("Goto Waypoint UID=%d index=%d from %d at speed %.1f knots",UID,n,self.currentwp,Speed)) +self:__UpdateRoute(0.1,n,nil,Speed) +end +end +function OPSGROUP:onafterDetectedUnit(From,Event,To,Unit) +local unitname=Unit and Unit:GetName()or"unknown" +self:T2(self.lid..string.format("Detected unit %s",unitname)) +if self.detectedunits:FindUnit(unitname)then +self:DetectedUnitKnown(Unit) +else +self:DetectedUnitNew(Unit) +end +end +function OPSGROUP:onafterDetectedUnitNew(From,Event,To,Unit) +self:T(self.lid..string.format("Detected New unit %s",Unit:GetName())) +self.detectedunits:AddUnit(Unit) +end +function OPSGROUP:onafterDetectedGroup(From,Event,To,Group) +local groupname=Group and Group:GetName()or"unknown" +self:T(self.lid..string.format("Detected group %s",groupname)) +if self.detectedgroups:FindGroup(groupname)then +self:DetectedGroupKnown(Group) +else +self:DetectedGroupNew(Group) +end +end +function OPSGROUP:onafterDetectedGroupNew(From,Event,To,Group) +self:T(self.lid..string.format("Detected New group %s",Group:GetName())) +self.detectedgroups:AddGroup(Group) +end +function OPSGROUP:onafterEnterZone(From,Event,To,Zone) +local zonename=Zone and Zone:GetName()or"unknown" +self:T2(self.lid..string.format("Entered Zone %s",zonename)) +self.inzones:Add(Zone:GetName(),Zone) +end +function OPSGROUP:onafterLeaveZone(From,Event,To,Zone) +local zonename=Zone and Zone:GetName()or"unknown" +self:T2(self.lid..string.format("Left Zone %s",zonename)) +self.inzones:Remove(zonename,true) +end +function OPSGROUP:onbeforeLaserOn(From,Event,To,Target) +if self.spot.On then +return false +end +if Target then +self:SetLaserTarget(Target) +else +self:T(self.lid.."ERROR: No target provided for LASER!") +return false +end +local element=self:GetElementAlive() +if element then +self.spot.element=element +local offsetY=2 +if self.isFlightgroup or self.isNavygroup then +offsetY=element.height +end +self.spot.offset={x=0,y=offsetY,z=0} +if self.spot.CheckLOS then +local los=self:HasLoS(self.spot.Coordinate,self.spot.element,self.spot.offset) +if los then +self:LaserGotLOS() +else +self:T(self.lid.."LASER got no LOS currently. Trying to switch the laser on again in 10 sec") +self:__LaserOn(-10,Target) +return false +end +end +else +self:T(self.lid.."ERROR: No element alive for lasing") +return false +end +return true +end +function OPSGROUP:onafterLaserOn(From,Event,To,Target) +if not self.spot.timer:IsRunning()then +self.spot.timer:Start(nil,self.spot.dt) +end +local DCSunit=self.spot.element.unit:GetDCSObject() +self.spot.Laser=Spot.createLaser(DCSunit,self.spot.offset,self.spot.vec3,self.spot.Code or 1688) +if self.spot.IRon then +self.spot.IR=Spot.createInfraRed(DCSunit,self.spot.offset,self.spot.vec3) +end +self.spot.On=true +self.spot.Paused=false +self:T(self.lid.."Switching LASER on") +end +function OPSGROUP:onbeforeLaserOff(From,Event,To) +return self.spot.On or self.spot.Paused +end +function OPSGROUP:onafterLaserOff(From,Event,To) +self:T(self.lid.."Switching LASER off") +if self.spot.On then +self.spot.Laser:destroy() +self.spot.IR:destroy() +self.spot.Laser=nil +self.spot.IR=nil +end +self.spot.timer:Stop() +self.spot.TargetUnit=nil +self.spot.On=false +self.spot.Paused=false +end +function OPSGROUP:onafterLaserPause(From,Event,To) +self:T(self.lid.."Switching LASER off temporarily") +self.spot.Laser:destroy() +self.spot.IR:destroy() +self.spot.Laser=nil +self.spot.IR=nil +self.spot.On=false +self.spot.Paused=true +end +function OPSGROUP:onbeforeLaserResume(From,Event,To) +return self.spot.Paused +end +function OPSGROUP:onafterLaserResume(From,Event,To) +self:T(self.lid.."Resuming LASER") +self.spot.Paused=false +local target=nil +if self.spot.TargetType==0 then +target=self.spot.Coordinate +elseif self.spot.TargetType==1 or self.spot.TargetType==2 then +target=self.spot.TargetUnit +elseif self.spot.TargetType==3 then +target=self.spot.TargetGroup +end +if target then +self:T(self.lid.."Switching LASER on again") +self:LaserOn(target) +end +end +function OPSGROUP:onafterLaserCode(From,Event,To,Code) +self.spot.Code=Code or 1688 +self:T2(self.lid..string.format("Setting LASER Code to %d",self.spot.Code)) +if self.spot.On then +self:T(self.lid..string.format("New LASER Code is %d",self.spot.Code)) +self.spot.Laser:setCode(self.spot.Code) +end +end +function OPSGROUP:onafterLaserLostLOS(From,Event,To) +self.spot.LOS=false +self.spot.lostLOS=true +if self.spot.On then +self:LaserPause() +end +end +function OPSGROUP:onafterLaserGotLOS(From,Event,To) +self.spot.LOS=true +if self.spot.lostLOS then +self.spot.lostLOS=false +if self.spot.Paused then +self:LaserResume() +end +end +end +function OPSGROUP:SetLaserTarget(Target) +if Target then +if Target:IsInstanceOf("SCENERY")then +self.spot.TargetType=0 +self.spot.offsetTarget={x=0,y=3,z=0} +elseif Target:IsInstanceOf("POSITIONABLE")then +local target=Target +if target:IsAlive()then +if target:IsInstanceOf("GROUP")then +self.spot.TargetGroup=target +self.spot.TargetUnit=target:GetHighestThreat() +self.spot.TargetType=3 +else +self.spot.TargetUnit=target +if target:IsInstanceOf("STATIC")then +self.spot.TargetType=1 +elseif target:IsInstanceOf("UNIT")then +self.spot.TargetType=2 +end +end +local size,x,y,z=self.spot.TargetUnit:GetObjectSize() +if y then +self.spot.offsetTarget={x=0,y=y*0.75,z=0} +else +self.spot.offsetTarget={x=0,2,z=0} +end +else +self:T("WARNING: LASER target is not alive!") +return +end +elseif Target:IsInstanceOf("COORDINATE")then +self.spot.TargetType=0 +self.spot.offsetTarget={x=0,y=0,z=0} +else +self:T(self.lid.."ERROR: LASER target should be a POSITIONABLE (GROUP, UNIT or STATIC) or a COORDINATE object!") +return +end +self.spot.vec3=UTILS.VecAdd(Target:GetVec3(),self.spot.offsetTarget) +self.spot.Coordinate:UpdateFromVec3(self.spot.vec3) +end +end +function OPSGROUP:_UpdateLaser() +if self.spot.TargetUnit then +if self.spot.TargetUnit:IsAlive()then +local vec3=self.spot.TargetUnit:GetVec3() +vec3=UTILS.VecAdd(vec3,self.spot.offsetTarget) +local dist=UTILS.VecDist3D(vec3,self.spot.vec3) +self.spot.vec3=vec3 +self.spot.Coordinate:UpdateFromVec3(vec3) +if dist>1 then +if self.spot.On then +self.spot.Laser:setPoint(vec3) +if self.spot.IRon then +self.spot.IR:setPoint(vec3) +end +end +end +else +if self.spot.TargetGroup and self.spot.TargetGroup:IsAlive()then +local unit=self.spot.TargetGroup:GetHighestThreat() +if unit then +self:T(self.lid..string.format("Switching to target unit %s in the group",unit:GetName())) +self.spot.TargetUnit=unit +return +else +self:T(self.lid.."Target is not alive any more ==> switching LASER off") +self:LaserOff() +return +end +else +self:T(self.lid.."Target is not alive any more ==> switching LASER off") +self:LaserOff() +return +end +end +end +if self.spot.CheckLOS then +local los=self:HasLoS(self.spot.Coordinate,self.spot.element,self.spot.offset) +if los then +if self.spot.lostLOS then +self:LaserGotLOS() +end +else +if not self.spot.lostLOS then +self:LaserLostLOS() +end +end +end +end +function OPSGROUP:onbeforeElementSpawned(From,Event,To,Element) +if Element and Element.status==OPSGROUP.ElementStatus.SPAWNED then +self:T2(self.lid..string.format("Element %s is already spawned ==> Transition denied!",Element.name)) +return false +end +return true +end +function OPSGROUP:onafterElementInUtero(From,Event,To,Element) +self:T(self.lid..string.format("Element in utero %s",Element.name)) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.INUTERO) +end +function OPSGROUP:onafterElementDamaged(From,Event,To,Element) +self:T(self.lid..string.format("Element damaged %s",Element.name)) +if Element and(Element.status~=OPSGROUP.ElementStatus.DEAD and Element.status~=OPSGROUP.ElementStatus.INUTERO)then +local lifepoints=0 +if Element.DCSunit and Element.DCSunit:isExist()then +lifepoints=Element.DCSunit:getLife() +self:T(self.lid..string.format("Element life %s: %.2f/%.2f",Element.name,lifepoints,Element.life0)) +else +self:T(self.lid..string.format("Element.DCSunit %s does not exist!",Element.name)) +end +if lifepoints<=1.0 then +self:T(self.lid..string.format("Element %s life %.2f <= 1.0 ==> Destroyed!",Element.name,lifepoints)) +self:ElementDestroyed(Element) +end +end +end +function OPSGROUP:onafterElementHit(From,Event,To,Element,Enemy) +Element.Nhit=Element.Nhit+1 +self:T(self.lid..string.format("Element hit %s by %s [n=%d, N=%d]",Element.name,Enemy and Enemy:GetName()or"unknown",Element.Nhit,self.Nhit)) +self:__Hit(-3,Enemy) +end +function OPSGROUP:onafterHit(From,Event,To,Enemy) +self:T(self.lid..string.format("Group hit by %s",Enemy and Enemy:GetName()or"unknown")) +end +function OPSGROUP:onafterElementDestroyed(From,Event,To,Element) +self:T(self.lid..string.format("Element destroyed %s",Element.name)) +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +mission:ElementDestroyed(self,Element) +end +self.Ndestroyed=self.Ndestroyed+1 +self:ElementDead(Element) +end +function OPSGROUP:onafterElementDead(From,Event,To,Element) +self:T(self.lid..string.format("Element dead %s at t=%.3f",Element.name,timer.getTime())) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.DEAD) +if self.spot.On and self.spot.element.name==Element.name then +self:LaserOff() +if self:GetNelements()>0 then +local target=nil +if self.spot.TargetType==0 then +target=self.spot.Coordinate +elseif self.spot.TargetType==1 or self.spot.TargetType==2 then +if self.spot.TargetUnit and self.spot.TargetUnit:IsAlive()then +target=self.spot.TargetUnit +end +elseif self.spot.TargetType==3 then +if self.spot.TargetGroup and self.spot.TargetGroup:IsAlive()then +target=self.spot.TargetGroup +end +end +if target then +self:__LaserOn(-1,target) +end +end +end +for i=#Element.cargoBay,1,-1 do +local mycargo=Element.cargoBay[i] +if mycargo.group then +self:_DelCargobay(mycargo.group) +if mycargo.group and not(mycargo.group:IsDead()or mycargo.group:IsStopped())then +mycargo.group:_RemoveMyCarrier() +if mycargo.reserved then +mycargo.group:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) +else +for _,cargoelement in pairs(mycargo.group.elements)do +self:T2(self.lid.."Cargo element dead "..cargoelement.name) +mycargo.group:ElementDead(cargoelement) +end +end +end +else +if self.cargoTZC then +for _,_cargo in pairs(self.cargoTZC.Cargos)do +local cargo=_cargo +if cargo.uid==mycargo.cargoUID then +cargo.storage.cargoLost=cargo.storage.cargoLost+mycargo.storageAmount +end +end +end +self:_DelCargobayElement(Element,mycargo) +end +end +end +function OPSGROUP:onafterRespawn(From,Event,To,Template) +self:T(self.lid.."Respawning group!") +local template=UTILS.DeepCopy(Template or self.template) +template.lateActivation=false +self:_Respawn(0,template) +end +function OPSGROUP:Teleport(Coordinate,Delay,NoPauseMission) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.Teleport,self,Coordinate,0,NoPauseMission) +else +self:T(self.lid.."FF Teleporting...") +if self:IsOnMission()and not NoPauseMission then +self:T(self.lid.."Pausing current mission for telport") +self:PauseMission() +end +local Template=UTILS.DeepCopy(self.template) +Template.lateActivation=self:IsLateActivated() +Template.uncontrolled=false +if self:IsFlightgroup()then +Template.route.points[1]=Coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,300,true,nil,nil,"Spawnpoint") +elseif self:IsArmygroup()then +Template.route.points[1]=Coordinate:WaypointGround(0) +elseif self:IsNavygroup()then +Template.route.points[1]=Coordinate:WaypointNaval(0) +end +local units=Template.units +local d={} +for i=1,#units do +local unit=units[i] +d[i]={x=Coordinate.x+(units[i].x-units[1].x),y=Coordinate.z+units[i].y-units[1].y} +end +for i=#units,1,-1 do +local unit=units[i] +local element=self:GetElementByName(unit.name) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +unit.parking=nil +unit.parking_id=nil +local vec3=element.unit:GetVec3() +local heading=element.unit:GetHeading() +unit.x=d[i].x +unit.y=d[i].y +unit.alt=Coordinate.y +unit.heading=math.rad(heading) +unit.psi=-unit.heading +else +table.remove(units,i) +end +end +self:_Respawn(0,Template,true) +end +end +function OPSGROUP:_Respawn(Delay,Template,Reset) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP._Respawn,self,0,Template,Reset) +else +self:T2(self.lid.."FF _Respawn") +Template=Template or self:_GetTemplate(true) +self.Ndestroyed=0 +self.Nhit=0 +if self:IsAlive()then +local units=Template.units +for i=#units,1,-1 do +local unit=units[i] +local element=self:GetElementByName(unit.name) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +if not Reset then +unit.parking=element.parking and element.parking.TerminalID or unit.parking +unit.parking_id=nil +local vec3=element.unit:GetVec3() +local heading=element.unit:GetHeading() +unit.x=vec3.x +unit.y=vec3.z +unit.alt=vec3.y +unit.heading=math.rad(heading) +unit.psi=-unit.heading +end +else +table.remove(units,i) +self.Ndestroyed=self.Ndestroyed+1 +end +end +self:Despawn(0,true) +end +for _,_element in pairs(self.elements)do +local element=_element +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +self:ElementInUtero(element) +end +end +self:_Spawn(0.01,Template) +end +return self +end +function OPSGROUP:_Spawn(Delay,Template) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template) +else +self:T2({Template=Template}) +if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(Template.units) +end +self.group=_DATABASE:Spawn(Template) +self.group:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) +self.dcsgroup=self:GetDCSGroup() +self.controller=self.dcsgroup:getController() +self.isLateActivated=Template.lateActivation +self.isUncontrolled=Template.uncontrolled +self.isDead=false +self.isDestroyed=false +self.groupinitialized=false +self.wpcounter=1 +self.currentwp=1 +self:_InitWaypoints() +self:_InitGroup(Template,0.001) +end +end +function OPSGROUP:onafterInUtero(From,Event,To) +self:T(self.lid..string.format("Group inutero at t=%.3f",timer.getTime())) +end +function OPSGROUP:onafterDamaged(From,Event,To) +self:T(self.lid..string.format("Group damaged at t=%.3f",timer.getTime())) +end +function OPSGROUP:onafterDestroyed(From,Event,To) +self:T(self.lid..string.format("Group destroyed at t=%.3f",timer.getTime())) +self.isDestroyed=true +end +function OPSGROUP:onbeforeDead(From,Event,To) +if self.Ndestroyed==#self.elements then +self:Destroyed() +end +end +function OPSGROUP:onafterDead(From,Event,To) +self:T(self.lid..string.format("Group dead at t=%.3f",timer.getTime())) +self.isDead=true +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +self:T(self.lid.."Cancelling mission because group is dead! Mission name "..tostring(mission:GetName())) +self:MissionCancel(mission) +mission:GroupDead(self) +end +self:ClearWaypoints() +self.groupinitialized=false +self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO +self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER +local mycarrier=self:_GetMyCarrierGroup() +if mycarrier and not mycarrier:IsDead()then +mycarrier:_DelCargobay(self) +self:_RemoveMyCarrier() +end +for i,_transport in pairs(self.cargoqueue)do +local transport=_transport +transport:__DeadCarrierGroup(1,self) +end +self.cargoqueue={} +self.cargoTransport=nil +self.cargoTZC=nil +if self.Ndestroyed==#self.elements then +if self.cohort then +self.cohort:DelGroup(self.groupname) +end +else +end +if self.legion then +if not self:IsInUtero()then +local asset=self.legion:GetAssetByName(self.groupname) +local request=self.legion:GetRequestByID(asset.rid) +self.legion:AssetDead(asset,request) +end +self:__Stop(-5) +elseif not self.isAI then +self:__Stop(-1) +end +end +function OPSGROUP:onbeforeStop(From,Event,To) +if self:IsAlive()then +self:T(self.lid..string.format("WARNING: Group is still alive! Will not stop the FSM. Use :Despawn() instead")) +return false +end +return true +end +function OPSGROUP:onafterStop(From,Event,To) +self:UnHandleEvent(EVENTS.Birth) +self:UnHandleEvent(EVENTS.Dead) +self:UnHandleEvent(EVENTS.RemoveUnit) +if self.isFlightgroup then +self:UnHandleEvent(EVENTS.EngineStartup) +self:UnHandleEvent(EVENTS.Takeoff) +self:UnHandleEvent(EVENTS.Land) +self:UnHandleEvent(EVENTS.EngineShutdown) +self:UnHandleEvent(EVENTS.PilotDead) +self:UnHandleEvent(EVENTS.Ejection) +self:UnHandleEvent(EVENTS.Crash) +self.currbase=nil +elseif self.isArmygroup then +self:UnHandleEvent(EVENTS.Hit) +end +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +self:MissionCancel(mission) +end +self.timerCheckZone:Stop() +self.timerQueueUpdate:Stop() +self.timerStatus:Stop() +self.CallScheduler:Clear() +if self.Scheduler then +self.Scheduler:Clear() +end +if self.flightcontrol then +for _,_element in pairs(self.elements)do +local element=_element +if element.parking then +self.flightcontrol:SetParkingFree(element.parking) +end +end +self.flightcontrol:_RemoveFlight(self) +end +if self:IsAlive()and not(self:IsDead()or self:IsStopped())then +local life,life0=self:GetLifePoints() +local state=self:GetState() +local text=string.format("WARNING: Group is still alive! Current state=%s. Life points=%d/%d. Use OPSGROUP:Destroy() or OPSGROUP:Despawn() for a clean stop",state,life,life0) +self:T(self.lid..text) +end +_DATABASE.FLIGHTGROUPS[self.groupname]=nil +self:T(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +end +function OPSGROUP:onafterOutOfAmmo(From,Event,To) +self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime())) +end +function OPSGROUP:_CheckCargoTransport() +local Time=timer.getAbsTime() +if self.verbose>=1 then +local text="" +for _,_element in pairs(self.elements)do +local element=_element +for _,_cargo in pairs(element.cargoBay)do +local cargo=_cargo +if cargo.group then +text=text..string.format("\n- %s in carrier %s, reserved=%s",tostring(cargo.group:GetName()),tostring(element.name),tostring(cargo.reserved)) +else +text=text..string.format("\n- storage %s=%d kg in carrier %s [UID=%s]", +tostring(cargo.storageType),tostring(cargo.storageAmount*cargo.storageWeight),tostring(element.name),tostring(cargo.cargoUID)) +end +end +end +if text==""then +text=" empty" +end +self:T(self.lid.."Cargo bay:"..text) +end +if self.verbose>=3 then +local text="" +for i,_transport in pairs(self.cargoqueue)do +local transport=_transport +local pickupzone=transport:GetPickupZone() +local deployzone=transport:GetDeployZone() +local pickupname=pickupzone and pickupzone:GetName()or"unknown" +local deployname=deployzone and deployzone:GetName()or"unknown" +text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s",i,transport.uid,transport:GetState(),pickupname,deployname) +for j,_cargo in pairs(transport:GetCargos())do +local cargo=_cargo +if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then +local state=cargo.opsgroup:GetState() +local status=cargo.opsgroup.cargoStatus +local name=cargo.opsgroup.groupname +local carriergroup,carrierelement,reserved=cargo.opsgroup:_GetMyCarrier() +local carrierGroupname=carriergroup and carriergroup.groupname or"none" +local carrierElementname=carrierelement and carrierelement.name or"none" +text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s",j,name,state,status,carrierGroupname,carrierElementname,tostring(cargo.delivered)) +else +end +end +end +if text~=""then +self:T(self.lid.."Cargo queue:"..text) +end +end +if self.cargoTransport and self.cargoTransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.DELIVERED then +self:DelOpsTransport(self.cargoTransport) +self.cargoTransport=nil +self.cargoTZC=nil +end +local mission=self:GetMissionCurrent() +if(not self.cargoTransport)and(mission==nil or mission.type==AUFTRAG.Type.NOTHING)then +self.cargoTransport=self:_GetNextCargoTransport() +if self.cargoTransport and mission then +self:MissionCancel(mission) +end +if self.cargoTransport and not self:IsActive()then +self:Activate() +end +end +if self.cargoTransport then +if self:IsNotCarrier()then +self.Tpickingup=nil +self.Tloading=nil +self.Ttransporting=nil +self.Tunloading=nil +self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) +if self.cargoTZC then +self:T(self.lid..string.format("Not carrier ==> pickup at %s [TZC UID=%d]",self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName()or"unknown",self.cargoTZC.uid)) +self:__Pickup(-1) +else +self:T2(self.lid.."Not carrier ==> No TZC found") +end +elseif self:IsPickingup()then +self.Tpickingup=self.Tpickingup or Time +local tpickingup=Time-self.Tpickingup +self:T(self.lid..string.format("Picking up at %s [TZC UID=%d] for %s sec...",self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName()or"unknown",self.cargoTZC.uid,tpickingup)) +elseif self:IsLoading()then +self.Tloading=self.Tloading or Time +local tloading=Time-self.Tloading +self:T(self.lid..string.format("Loading at %s [TZC UID=%d] for %.1f sec...",self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName()or"unknown",self.cargoTZC.uid,tloading)) +local boarding=false +local gotcargo=false +for _,_cargo in pairs(self.cargoTZC.Cargos)do +local cargo=_cargo +if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then +if cargo.opsgroup and cargo.opsgroup:IsBoarding(self.groupname)then +boarding=true +end +if cargo.opsgroup and cargo.opsgroup:IsLoaded(self.groupname)then +gotcargo=true +end +else +local mycargo=self:_GetMyCargoBayFromUID(cargo.uid) +if mycargo and mycargo.storageAmount>0 then +gotcargo=true +end +end +end +if gotcargo and self.cargoTransport:_CheckRequiredCargos(self.cargoTZC,self)and not boarding then +self:T(self.lid.."Boarding/loading finished ==> Loaded") +self.Tloading=nil +self:LoadingDone() +else +self:Loading() +end +elseif self:IsTransporting()then +self.Ttransporting=self.Ttransporting or Time +local ttransporting=Time-self.Ttransporting +self:T(self.lid.."Transporting (nothing to do)") +elseif self:IsUnloading()then +self.Tunloading=self.Tunloading or Time +local tunloading=Time-self.Tunloading +self:T(self.lid.."Unloading ==> Checking if all cargo was delivered") +local delivered=true +for _,_cargo in pairs(self.cargoTZC.Cargos)do +local cargo=_cargo +if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then +local carrierGroup=cargo.opsgroup:_GetMyCarrierGroup() +if(carrierGroup and carrierGroup:GetName()==self:GetName())and not cargo.delivered then +delivered=false +break +end +else +local mycargo=self:_GetMyCargoBayFromUID(cargo.uid) +if mycargo and not cargo.delivered then +delivered=false +break +end +end +end +if delivered then +self:T(self.lid.."Unloading finished ==> UnloadingDone") +self:UnloadingDone() +else +self:Unloading() +end +end +if self.verbose>=2 and self.cargoTransport then +local pickupzone=self.cargoTransport:GetPickupZone(self.cargoTZC) +local deployzone=self.cargoTransport:GetDeployZone(self.cargoTZC) +local pickupname=pickupzone and pickupzone:GetName()or"unknown" +local deployname=deployzone and deployzone:GetName()or"unknown" +local text=string.format("Carrier [%s]: %s --> %s",self.carrierStatus,pickupname,deployname) +for _,_cargo in pairs(self.cargoTransport:GetCargos(self.cargoTZC))do +local cargo=_cargo +if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then +local name=cargo.opsgroup:GetName() +local gstatus=cargo.opsgroup:GetState() +local cstatus=cargo.opsgroup.cargoStatus +local weight=cargo.opsgroup:GetWeightTotal() +local carriergroup,carrierelement,reserved=cargo.opsgroup:_GetMyCarrier() +local carrierGroupname=carriergroup and carriergroup.groupname or"none" +local carrierElementname=carrierelement and carrierelement.name or"none" +text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s",name,weight,gstatus,cstatus,carrierElementname,carrierGroupname,tostring(cargo.delivered)) +else +end +end +self:I(self.lid..text) +end +end +return self +end +function OPSGROUP:_IsInCargobay(OpsGroup) +for _,_element in pairs(self.elements)do +local element=_element +for _,_cargo in pairs(element.cargoBay)do +local cargo=_cargo +if cargo.group.groupname==OpsGroup.groupname then +return true +end +end +end +return false +end +function OPSGROUP:_AddCargobay(CargoGroup,CarrierElement,Reserved) +local cargo=self:_GetCargobay(CargoGroup) +if cargo then +cargo.reserved=Reserved +else +cargo={} +cargo.group=CargoGroup +cargo.reserved=Reserved +table.insert(CarrierElement.cargoBay,cargo) +end +CargoGroup:_SetMyCarrier(self,CarrierElement,Reserved) +self.cargoBay[CargoGroup.groupname]=CarrierElement.name +if not Reserved then +local weight=CargoGroup:GetWeightTotal() +self:AddWeightCargo(CarrierElement.name,weight) +end +return self +end +function OPSGROUP:_AddCargobayStorage(CarrierElement,CargoUID,StorageType,StorageAmount,StorageWeight) +local MyCargo=self:_CreateMyCargo(CargoUID,nil,StorageType,StorageAmount,StorageWeight) +self:_AddMyCargoBay(MyCargo,CarrierElement) +end +function OPSGROUP:_CreateMyCargo(CargoUID,OpsGroup,StorageType,StorageAmount,StorageWeight) +local cargo={} +cargo.cargoUID=CargoUID +cargo.group=OpsGroup +cargo.storageType=StorageType +cargo.storageAmount=StorageAmount +cargo.storageWeight=StorageWeight +cargo.reserved=false +return cargo +end +function OPSGROUP:_AddMyCargoBay(MyCargo,CarrierElement) +table.insert(CarrierElement.cargoBay,MyCargo) +if not MyCargo.reserved then +local weight=0 +if MyCargo.group then +weight=MyCargo.group:GetWeightTotal() +else +weight=MyCargo.storageAmount*MyCargo.storageWeight +end +self:AddWeightCargo(CarrierElement.name,weight) +end +end +function OPSGROUP:_GetMyCargoBayFromUID(uid) +for _,_element in pairs(self.elements)do +local element=_element +for i,_mycargo in pairs(element.cargoBay)do +local mycargo=_mycargo +if mycargo.cargoUID and mycargo.cargoUID==uid then +return mycargo,element,i +end +end +end +return nil,nil,nil +end +function OPSGROUP:GetCargoGroups(CarrierName) +local cargos={} +for _,_element in pairs(self.elements)do +local element=_element +if CarrierName==nil or element.name==CarrierName then +for _,_cargo in pairs(element.cargoBay)do +local cargo=_cargo +if not cargo.reserved then +table.insert(cargos,cargo.group) +end +end +end +end +return cargos +end +function OPSGROUP:_GetCargobay(CargoGroup) +local CarrierElement=nil +local cargobayIndex=nil +local reserved=nil +for i,_element in pairs(self.elements)do +local element=_element +for j,_cargo in pairs(element.cargoBay)do +local cargo=_cargo +if cargo.group and cargo.group.groupname==CargoGroup.groupname then +return cargo,j,element +end +end +end +return nil,nil,nil +end +function OPSGROUP:_GetCargobayElement(Element,CargoUID) +self:T3({Element=Element,CargoUID=CargoUID}) +for i,_mycargo in pairs(Element.cargoBay)do +local mycargo=_mycargo +if mycargo.cargoUID and mycargo.cargoUID==CargoUID then +return mycargo +end +end +return nil +end +function OPSGROUP:_DelCargobayElement(Element,MyCargo) +for i,_mycargo in pairs(Element.cargoBay)do +local mycargo=_mycargo +if mycargo.cargoUID and MyCargo.cargoUID and mycargo.cargoUID==MyCargo.cargoUID then +if MyCargo.group then +self:RedWeightCargo(Element.name,MyCargo.group:GetWeightTotal()) +else +self:RedWeightCargo(Element.name,MyCargo.storageAmount*MyCargo.storageWeight) +end +table.remove(Element.cargoBay,i) +return true +end +end +return false +end +function OPSGROUP:_DelCargobay(CargoGroup) +if self.cargoBay[CargoGroup.groupname]then +self.cargoBay[CargoGroup.groupname]=nil +end +local cargoBayItem,cargoBayIndex,CarrierElement=self:_GetCargobay(CargoGroup) +if cargoBayItem and cargoBayIndex then +self:T(self.lid..string.format("Removing cargo group %s from cargo bay (index=%d) of carrier %s",CargoGroup:GetName(),cargoBayIndex,CarrierElement.name)) +table.remove(CarrierElement.cargoBay,cargoBayIndex) +if not cargoBayItem.reserved then +local weight=CargoGroup:GetWeightTotal() +self:RedWeightCargo(CarrierElement.name,weight) +end +return true +end +self:T(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") +return false +end +function OPSGROUP:_GetNextCargoTransport() +local coord=self:GetCoordinate() +local function _sort(a,b) +local transportA=a +local transportB=b +return(transportA.priomaxweight then +maxweight=weight +end +end +end +return maxweight +end +function OPSGROUP:GetWeightCargo(UnitName,IncludeReserved) +local weight=0 +for _,_element in pairs(self.elements)do +local element=_element +if(UnitName==nil or UnitName==element.name)and element.status~=OPSGROUP.ElementStatus.DEAD then +weight=weight+element.weightCargo or 0 +end +end +local gewicht=0 +for _,_element in pairs(self.elements)do +local element=_element +if(UnitName==nil or UnitName==element.name)and(element and element.status~=OPSGROUP.ElementStatus.DEAD)then +for _,_cargo in pairs(element.cargoBay)do +local cargo=_cargo +if(not cargo.reserved)or(cargo.reserved==true and(IncludeReserved==true or IncludeReserved==nil))then +if cargo.group then +gewicht=gewicht+cargo.group:GetWeightTotal() +else +gewicht=gewicht+cargo.storageAmount*cargo.storageWeight +end +end +end +end +end +self:T3(self.lid..string.format("Unit=%s (reserved=%s): weight=%d, gewicht=%d",tostring(UnitName),tostring(IncludeReserved),weight,gewicht)) +if IncludeReserved==false and gewicht~=weight then +self:T(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f",weight,gewicht)) +end +return gewicht +end +function OPSGROUP:GetWeightCargoMax(UnitName) +local weight=0 +for _,_element in pairs(self.elements)do +local element=_element +if(UnitName==nil or UnitName==element.name)and element.status~=OPSGROUP.ElementStatus.DEAD then +weight=weight+element.weightMaxCargo +end +end +return weight +end +function OPSGROUP:GetCargoOpsGroups() +local opsgroups={} +for _,_element in pairs(self.elements)do +local element=_element +for _,_cargo in pairs(element.cargoBay)do +local cargo=_cargo +table.insert(opsgroups,cargo.group) +end +end +return opsgroups +end +function OPSGROUP:AddWeightCargo(UnitName,Weight) +local element=self:GetElementByName(UnitName) +if element then +element.weightCargo=element.weightCargo+Weight +self:T(self.lid..string.format("%s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg",UnitName,Weight,element.weightCargo)) +if self.isFlightgroup and element.unit and element.unit:IsAlive()then +trigger.action.setUnitInternalCargo(element.name,element.weightCargo) +end +end +return self +end +function OPSGROUP:RedWeightCargo(UnitName,Weight) +self:AddWeightCargo(UnitName,-Weight) +return self +end +function OPSGROUP:_GetWeightStorage(Storage,Total,Reserved,Amount) +local weight=Storage.cargoAmount +if not Total then +weight=weight-Storage.cargoLost-Storage.cargoLoaded-Storage.cargoDelivered +end +if Reserved then +weight=weight-Storage.cargoReserved +end +if not Amount then +weight=weight*Storage.cargoWeight +end +return weight +end +function OPSGROUP:CanCargo(Cargo) +if Cargo then +local weight=math.huge +if Cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then +local weight=Cargo.opsgroup:GetWeightTotal() +for _,_element in pairs(self.elements)do +local element=_element +if element and element.status~=OPSGROUP.ElementStatus.DEAD and element.weightMaxCargo>=weight then +return true +end +end +else +weight=Cargo.storage.cargoWeight +end +local bay=0 +for _,_element in pairs(self.elements)do +local element=_element +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +bay=bay+element.weightMaxCargo +end +end +if bay>=weight then +return true +end +end +return false +end +function OPSGROUP:FindCarrierForCargo(Weight) +for _,_element in pairs(self.elements)do +local element=_element +local free=self:GetFreeCargobay(element.name) +if free>=Weight then +return element +else +self:T3(self.lid..string.format("%s: Weight %d>%d free cargo bay",element.name,Weight,free)) +end +end +return nil +end +function OPSGROUP:_SetMyCarrier(CarrierGroup,CarrierElement,Reserved) +self:T(self.lid..string.format("Setting My Carrier: %s (%s), reserved=%s",CarrierGroup:GetName(),tostring(CarrierElement.name),tostring(Reserved))) +self.mycarrier.group=CarrierGroup +self.mycarrier.element=CarrierElement +self.mycarrier.reserved=Reserved +self.cargoTransportUID=CarrierGroup.cargoTransport and CarrierGroup.cargoTransport.uid or nil +end +function OPSGROUP:_GetMyCarrierGroup() +if self.mycarrier and self.mycarrier.group then +return self.mycarrier.group +end +return nil +end +function OPSGROUP:_GetMyCarrierElement() +if self.mycarrier and self.mycarrier.element then +return self.mycarrier.element +end +return nil +end +function OPSGROUP:_IsMyCarrierReserved() +if self.mycarrier then +return self.mycarrier.reserved +end +return nil +end +function OPSGROUP:_GetMyCarrier() +return self.mycarrier.group,self.mycarrier.element,self.mycarrier.reserved +end +function OPSGROUP:_RemoveMyCarrier() +self:T(self.lid..string.format("Removing my carrier!")) +self.mycarrier.group=nil +self.mycarrier.element=nil +self.mycarrier.reserved=nil +self.mycarrier={} +self.cargoTransportUID=nil +return self +end +function OPSGROUP:onafterPickup(From,Event,To) +local oldstatus=self.carrierStatus +self:_NewCarrierStatus(OPSGROUP.CarrierStatus.PICKUP) +local TZC=self.cargoTZC +local Zone=TZC.PickupZone +local inzone=self:IsInZone(Zone) +local airbasePickup=TZC.PickupAirbase +local ready4loading=false +if self:IsArmygroup()or self:IsNavygroup()then +ready4loading=inzone +else +ready4loading=self.currbase and airbasePickup and self.currbase:GetName()==airbasePickup:GetName()and self:IsParking() +if ready4loading==false and self.isHelo and self:IsLandedAt()and inzone then +ready4loading=true +end +end +if ready4loading then +if(self:IsArmygroup()or self:IsNavygroup())and not self:IsHolding()then +self:FullStop() +end +self:__Loading(-5) +else +local surfacetypes=nil +if self:IsArmygroup()or self:IsFlightgroup()then +surfacetypes={land.SurfaceType.LAND} +elseif self:IsNavygroup()then +surfacetypes={land.SurfaceType.WATER} +end +local Coordinate=Zone:GetRandomCoordinate(nil,nil,surfacetypes) +local uid=self:GetWaypointCurrentUID() +if self:IsFlightgroup()then +if self:IsParking()and self:IsUncontrolled()then +self:StartUncontrolled() +end +if airbasePickup then +local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) +if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then +for i=#path.waypoints,1,-1 do +local wp=path.waypoints[i] +local coordinate=COORDINATE:NewFromWaypoint(wp) +local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true +uid=waypoint.uid +if i==1 then +waypoint.temp=false +waypoint.detour=1 +end +end +else +local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate,0.5) +local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),true);waypoint.detour=1 +end +elseif self.isHelo then +local waypoint=FLIGHTGROUP.AddWaypoint(self,Coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),false);waypoint.detour=1 +else +self:T(self.lid.."ERROR: Transportcarrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") +end +if self.isHelo and self:IsLandedAt()then +local Task=self:GetTaskCurrent() +if Task then +self:TaskCancel(Task) +else +self:T(self.lid.."ERROR: No current task but landed at?!") +end +end +if self:IsWaiting()then +self:__Cruise(-2) +end +elseif self:IsNavygroup()then +local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) +if path then +for i=#path.waypoints,1,-1 do +local wp=path.waypoints[i] +local coordinate=COORDINATE:NewFromWaypoint(wp) +local waypoint=NAVYGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true +uid=waypoint.uid +end +end +local waypoint=NAVYGROUP.AddWaypoint(self,Coordinate,nil,uid,self.altitudeCruise,false);waypoint.detour=1 +self:__Cruise(-2) +elseif self:IsArmygroup()then +local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) +local Formation=self.cargoTransport:_GetFormationPickup(self.cargoTZC,self) +if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then +for i=#path.waypoints,1,-1 do +local wp=path.waypoints[i] +local coordinate=COORDINATE:NewFromWaypoint(wp) +local waypoint=ARMYGROUP.AddWaypoint(self,coordinate,nil,uid,wp.action,false);waypoint.temp=true +uid=waypoint.uid +end +end +local waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,nil,uid,Formation,false);waypoint.detour=1 +self:__Cruise(-2,nil,Formation) +end +end +end +function OPSGROUP:_SortCargo(Cargos) +local function _sort(a,b) +local cargoA=a +local cargoB=b +local weightA=0 +local weightB=0 +if cargoA.opsgroup then +weightA=cargoA.opsgroup:GetWeightTotal() +else +weightA=self:_GetWeightStorage(cargoA.storage) +end +if cargoB.opsgroup then +weightB=cargoB.opsgroup:GetWeightTotal() +else +weightB=self:_GetWeightStorage(cargoB.storage) +end +return weightA>weightB +end +table.sort(Cargos,_sort) +return Cargos +end +function OPSGROUP:onafterLoading(From,Event,To) +self:_NewCarrierStatus(OPSGROUP.CarrierStatus.LOADING) +local cargos={} +for _,_cargo in pairs(self.cargoTZC.Cargos)do +local cargo=_cargo +local canCargo=self:CanCargo(cargo) +local isCarrier=false +local isNotCargo=true +local isHolding=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and(cargo.opsgroup:IsHolding()or cargo.opsgroup:IsLoaded())or true +local inZone=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and(cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone)or cargo.opsgroup:IsInUtero())or true +local isOnMission=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsOnMission()or false +if isOnMission then +local mission=cargo.opsgroup:GetMissionCurrent() +if mission and((mission.opstransport and mission.opstransport.uid==self.cargoTransport.uid)or mission.type==AUFTRAG.Type.NOTHING)then +isOnMission=not isHolding +end +end +local isAvail=true +if cargo.type==OPSTRANSPORT.CargoType.STORAGE then +local nAvail=cargo.storage.storageFrom:GetAmount(cargo.storage.cargoType) +if nAvail>0 then +isAvail=true +else +isAvail=false +end +else +isCarrier=cargo.opsgroup:IsPickingup()or cargo.opsgroup:IsLoading()or cargo.opsgroup:IsTransporting()or cargo.opsgroup:IsUnloading() +isNotCargo=cargo.opsgroup:IsNotCargo(true) +end +local isDead=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsDead()or false +self:T(self.lid..string.format("Loading: canCargo=%s, isCarrier=%s, isNotCargo=%s, isHolding=%s, isOnMission=%s", +tostring(canCargo),tostring(isCarrier),tostring(isNotCargo),tostring(isHolding),tostring(isOnMission))) +if canCargo and inZone and isNotCargo and isHolding and isAvail and(not(cargo.delivered or isDead or isCarrier or isOnMission))then +table.insert(cargos,cargo) +end +end +self:_SortCargo(cargos) +for _,_cargo in pairs(cargos)do +local cargo=_cargo +local weight=nil +if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then +weight=cargo.opsgroup:GetWeightTotal() +local carrier=self:FindCarrierForCargo(weight) +if carrier then +cargo.opsgroup:Board(self,carrier) +end +else +weight=self:_GetWeightStorage(cargo.storage,false) +local Amount=cargo.storage.storageFrom:GetAmount(cargo.storage.cargoType) +local Weight=Amount*cargo.storage.cargoWeight +weight=math.min(weight,Weight) +self:T(self.lid..string.format("Loading storage weight=%d kg (warehouse has %d kg)!",weight,Weight)) +for _,_element in pairs(self.elements)do +local element=_element +local free=self:GetFreeCargobay(element.name) +local w=math.min(weight,free) +if w>=cargo.storage.cargoWeight then +local amount=math.floor(w/cargo.storage.cargoWeight) +cargo.storage.storageFrom:RemoveAmount(cargo.storage.cargoType,amount) +cargo.storage.cargoLoaded=cargo.storage.cargoLoaded+amount +self:_AddCargobayStorage(element,cargo.uid,cargo.storage.cargoType,amount,cargo.storage.cargoWeight) +weight=weight-amount*cargo.storage.cargoWeight +local text=string.format("Element %s: loaded amount=%d (weight=%d) ==> left=%d kg",element.name,amount,amount*cargo.storage.cargoWeight,weight) +self:T(self.lid..text) +if weight<=0 then +break +end +end +end +end +end +end +function OPSGROUP:_NewCargoStatus(Status) +if self.verbose>=2 then +self:I(self.lid..string.format("New cargo status: %s --> %s",tostring(self.cargoStatus),tostring(Status))) +end +self.cargoStatus=Status +end +function OPSGROUP:_NewCarrierStatus(Status) +if self.verbose>=2 then +self:I(self.lid..string.format("New carrier status: %s --> %s",tostring(self.carrierStatus),tostring(Status))) +end +self.carrierStatus=Status +end +function OPSGROUP:_TransferCargo(CargoGroup,CarrierGroup,CarrierElement) +self:T(self.lid..string.format("Transferring cargo %s to new carrier group %s",CargoGroup:GetName(),CarrierGroup:GetName())) +self:Unload(CargoGroup) +CarrierGroup:Load(CargoGroup,CarrierElement) +end +function OPSGROUP:onafterLoad(From,Event,To,CargoGroup,Carrier) +self:T(self.lid..string.format("Loading group %s",tostring(CargoGroup.groupname))) +local carrier=Carrier or CargoGroup:_GetMyCarrierElement() +if not carrier then +local weight=CargoGroup:GetWeightTotal() +carrier=self:FindCarrierForCargo(weight) +end +if carrier then +CargoGroup:_NewCargoStatus(OPSGROUP.CargoStatus.LOADED) +CargoGroup:ClearWaypoints() +self:_AddCargobay(CargoGroup,carrier,false) +if CargoGroup:IsAlive()then +CargoGroup:Despawn(0,true) +end +CargoGroup:Embarked(self,carrier) +self:Loaded(CargoGroup) +if self.cargoTransport then +CargoGroup:_DelMyLift(self.cargoTransport) +self.cargoTransport:Loaded(CargoGroup,self,carrier) +else +self:T(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!")) +end +else +self:T(self.lid.."ERROR: Cargo has no carrier on Load event!") +end +end +function OPSGROUP:onafterLoadingDone(From,Event,To) +self:T(self.lid.."Carrier Loading Done ==> Transport") +self:__Transport(1) +end +function OPSGROUP:onbeforeTransport(From,Event,To) +if self.cargoTransport==nil then +return false +elseif self.cargoTransport:IsDelivered()then +return false +end +return true +end +function OPSGROUP:onafterTransport(From,Event,To) +self:_NewCarrierStatus(OPSGROUP.CarrierStatus.TRANSPORTING) +local Zone=self.cargoTZC.DeployZone +local inzone=self:IsInZone(Zone) +local airbaseDeploy=self.cargoTZC.DeployAirbase +local ready2deploy=false +if self:IsArmygroup()or self:IsNavygroup()then +ready2deploy=inzone +else +ready2deploy=self.currbase and airbaseDeploy and self.currbase:GetName()==airbaseDeploy:GetName()and self:IsParking() +if ready2deploy==false and(self.isHelo or self.isVTOL)and self:IsLandedAt()and inzone then +ready2deploy=true +end +end +if inzone then +if(self:IsArmygroup()or self:IsNavygroup())and not self:IsHolding()then +self:FullStop() +end +self:__Unloading(-5) +else +local surfacetypes=nil +if self:IsArmygroup()or self:IsFlightgroup()then +surfacetypes={land.SurfaceType.LAND} +elseif self:IsNavygroup()then +surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER} +end +local Coordinate=Zone:GetRandomCoordinate(nil,nil,surfacetypes) +local uid=self:GetWaypointCurrentUID() +if self:IsFlightgroup()then +if self:IsParking()and self:IsUncontrolled()then +self:StartUncontrolled() +end +if airbaseDeploy then +local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) +if path then +for i=1,#path.waypoints do +local wp=path.waypoints[i] +local coordinate=COORDINATE:NewFromWaypoint(wp) +local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true +uid=waypoint.uid +if i==#path.waypoints then +waypoint.temp=false +waypoint.detour=1 +end +end +else +local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate,0.5) +local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),true);waypoint.detour=1 +end +elseif self.isHelo then +local waypoint=FLIGHTGROUP.AddWaypoint(self,Coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),false);waypoint.detour=1 +else +self:T(self.lid.."ERROR: Aircraft (cargo carrier) cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") +end +if self.isHelo and self:IsLandedAt()then +local Task=self:GetTaskCurrent() +if Task then +self:TaskCancel(Task) +else +self:T(self.lid.."ERROR: No current task but landed at?!") +end +end +elseif self:IsArmygroup()then +local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) +local Formation=self.cargoTransport:_GetFormationTransport(self.cargoTZC,self) +if path then +for i=1,#path.waypoints do +local wp=path.waypoints[i] +local coordinate=COORDINATE:NewFromWaypoint(wp) +local waypoint=ARMYGROUP.AddWaypoint(self,coordinate,nil,uid,wp.action,false);waypoint.temp=true +uid=waypoint.uid +end +end +local waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,nil,uid,Formation,false);waypoint.detour=1 +self:Cruise(nil,Formation) +elseif self:IsNavygroup()then +local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC) +if path then +for i=1,#path.waypoints do +local wp=path.waypoints[i] +local coordinate=COORDINATE:NewFromWaypoint(wp) +local waypoint=NAVYGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true +uid=waypoint.uid +end +end +local waypoint=NAVYGROUP.AddWaypoint(self,Coordinate,nil,uid,self.altitudeCruise,false);waypoint.detour=1 +self:Cruise() +end +end +end +function OPSGROUP:onafterUnloading(From,Event,To) +self:_NewCarrierStatus(OPSGROUP.CarrierStatus.UNLOADING) +self:T(self.lid.."Unloading..") +local zone=self.cargoTZC.DisembarkZone or self.cargoTZC.DeployZone +for _,_cargo in pairs(self.cargoTZC.Cargos)do +local cargo=_cargo +if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then +if cargo.opsgroup:IsLoaded(self.groupname)and not cargo.opsgroup:IsDead()then +local carrier=nil +local carrierGroup=nil +local disembarkToCarriers=cargo.disembarkCarriers~=nil or self.cargoTZC.disembarkToCarriers +if cargo.disembarkZone then +zone=cargo.disembarkZone +end +self:T(self.lid..string.format("Unloading cargo %s to zone %s",cargo.opsgroup:GetName(),zone and zone:GetName()or"No Zone Found!")) +if zone and zone:IsInstanceOf("ZONE_AIRBASE")and zone:GetAirbase():IsShip()then +local shipname=zone:GetAirbase():GetName() +local ship=UNIT:FindByName(shipname) +local group=ship:GetGroup() +carrierGroup=_DATABASE:GetOpsGroup(group:GetName()) +carrier=carrierGroup:GetElementByName(shipname) +end +if disembarkToCarriers then +self:T(self.lid..string.format("Trying to find disembark carriers in zone %s",zone:GetName())) +local disembarkCarriers=cargo.disembarkCarriers or self.cargoTZC.DisembarkCarriers +carrier,carrierGroup=self.cargoTransport:FindTransferCarrierForCargo(cargo.opsgroup,zone,disembarkCarriers,self.cargoTZC.DeployAirbase) +end +if(disembarkToCarriers and carrier and carrierGroup)or(not disembarkToCarriers)then +cargo.delivered=true +self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 +if carrier and carrierGroup then +self:_TransferCargo(cargo.opsgroup,carrierGroup,carrier) +elseif zone and zone:IsInstanceOf("ZONE_AIRBASE")and zone:GetAirbase():IsShip()then +self:T(self.lid.."ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") +self:Unload(cargo.opsgroup) +else +if self.cargoTransport:GetDisembarkInUtero(self.cargoTZC)then +self:Unload(cargo.opsgroup) +else +local DisembarkZone=cargo.disembarkZone or self.cargoTransport:GetDisembarkZone(self.cargoTZC) +local Coordinate=nil +if DisembarkZone then +Coordinate=DisembarkZone:GetRandomCoordinate() +else +local element=cargo.opsgroup:_GetMyCarrierElement() +if element then +local zoneCarrier=self:GetElementZoneUnload(element.name) +Coordinate=zoneCarrier:GetRandomCoordinate() +else +self:E(self.lid..string.format("ERROR carrier element nil!")) +end +end +local Heading=math.random(0,359) +local activation=self.cargoTransport:GetDisembarkActivation(self.cargoTZC) +if cargo.disembarkActivation~=nil then +activation=cargo.disembarkActivation +end +self:Unload(cargo.opsgroup,Coordinate,activation,Heading) +end +self.cargoTransport:Unloaded(cargo.opsgroup,self) +end +else +self:T(self.lid.."Cargo needs carrier but no carrier is avaiable (yet)!") +end +else +end +else +if not cargo.delivered then +for _,_element in pairs(self.elements)do +local element=_element +local mycargo=self:_GetCargobayElement(element,cargo.uid) +if mycargo then +cargo.storage.storageTo:AddAmount(mycargo.storageType,mycargo.storageAmount) +cargo.storage.cargoDelivered=cargo.storage.cargoDelivered+mycargo.storageAmount +cargo.storage.cargoLoaded=cargo.storage.cargoLoaded-mycargo.storageAmount +self:_DelCargobayElement(element,mycargo) +self:T2(self.lid..string.format("Cargo loaded=%d, delivered=%d, lost=%d",cargo.storage.cargoLoaded,cargo.storage.cargoDelivered,cargo.storage.cargoLost)) +end +end +local amountToDeliver=self:_GetWeightStorage(cargo.storage,false,false,true) +local amountTotal=self:_GetWeightStorage(cargo.storage,true,false,true) +local text=string.format("Amount delivered=%d, total=%d",amountToDeliver,amountTotal) +self:T(self.lid..text) +if amountToDeliver<=0 then +cargo.delivered=true +self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 +local text=string.format("Ndelivered=%d delivered=%s",self.cargoTransport.Ndelivered,tostring(cargo.delivered)) +self:T(self.lid..text) +end +end +end +end +end +function OPSGROUP:onbeforeUnload(From,Event,To,OpsGroup,Coordinate,Heading) +local removed=self:_DelCargobay(OpsGroup) +return removed +end +function OPSGROUP:onafterUnload(From,Event,To,OpsGroup,Coordinate,Activated,Heading) +OpsGroup:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) +if Coordinate then +local Template=UTILS.DeepCopy(OpsGroup.template) +if Activated==false then +Template.lateActivation=true +else +Template.lateActivation=false +end +for _,Unit in pairs(Template.units)do +local element=OpsGroup:GetElementByName(Unit.name) +if element then +local vec3=element.vec3 +local rvec2={x=Unit.x-Template.x,y=Unit.y-Template.y} +local cvec2={x=Coordinate.x,y=Coordinate.z} +Unit.x=cvec2.x+rvec2.x +Unit.y=cvec2.y+rvec2.y +Unit.alt=land.getHeight({x=Unit.x,y=Unit.y}) +Unit.heading=Heading and math.rad(Heading)or Unit.heading +Unit.psi=-Unit.heading +end +end +OpsGroup:_Respawn(0,Template) +if OpsGroup:IsNavygroup()then +OpsGroup:ClearWaypoints() +OpsGroup.currentwp=1 +OpsGroup.passedfinalwp=true +NAVYGROUP.AddWaypoint(OpsGroup,Coordinate,nil,nil,nil,false) +elseif OpsGroup:IsArmygroup()then +OpsGroup:ClearWaypoints() +OpsGroup.currentwp=1 +OpsGroup.passedfinalwp=true +ARMYGROUP.AddWaypoint(OpsGroup,Coordinate,nil,nil,nil,false) +end +else +OpsGroup.position=self:GetVec3() +end +OpsGroup:Disembarked(OpsGroup:_GetMyCarrierGroup(),OpsGroup:_GetMyCarrierElement()) +self:Unloaded(OpsGroup) +OpsGroup:_RemoveMyCarrier() +end +function OPSGROUP:onafterUnloaded(From,Event,To,OpsGroupCargo) +self:T(self.lid..string.format("Unloaded OPSGROUP %s",OpsGroupCargo:GetName())) +if OpsGroupCargo.legion and OpsGroupCargo:IsInZone(OpsGroupCargo.legion.spawnzone)then +self:T(self.lid..string.format("Unloaded group %s returned to legion",OpsGroupCargo:GetName())) +OpsGroupCargo:Returned() +end +local paused=OpsGroupCargo:_CountPausedMissions()>0 +if paused then +OpsGroupCargo:UnpauseMission() +end +end +function OPSGROUP:onafterUnloadingDone(From,Event,To) +self:T(self.lid.."Cargo unloading done..") +if self:IsFlightgroup()and self:IsLandedAt()then +local Task=self:GetTaskCurrent() +self:__TaskCancel(5,Task) +end +local delivered=self:_CheckGoPickup(self.cargoTransport) +if not delivered then +self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) +if self.cargoTZC then +self:T(self.lid.."Unloaded: Still cargo left ==> Pickup") +self:Pickup() +else +self:T(self.lid..string.format("WARNING: Not all cargo was delivered but could not get a transport zone combo ==> setting carrier state to NOT CARRIER")) +self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) +end +else +self:T(self.lid.."Unloaded: ALL cargo unloaded ==> Delivered (current)") +self:Delivered(self.cargoTransport) +end +end +function OPSGROUP:onafterDelivered(From,Event,To,CargoTransport) +if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then +if self:IsPickingup()then +local wpindex=self:GetWaypointIndexNext(false) +if wpindex then +self:RemoveWaypoint(wpindex) +end +self.isLandingAtAirbase=nil +elseif self:IsLoading()then +elseif self:IsTransporting()then +elseif self:IsUnloading()then +end +self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) +if self:IsFlightgroup()then +local function atbase(_airbase) +local airbase=_airbase +if airbase and self.currbase then +if airbase.AirbaseName==self.currbase.AirbaseName then +return true +end +end +return false +end +if self:IsUncontrolled()and not atbase(self.destbase)then +self:StartUncontrolled() +end +if self:IsLandedAt()then +local Task=self:GetTaskCurrent() +self:TaskCancel(Task) +end +else +self:__Cruise(-0.1) +end +self.cargoTransport:SetCarrierTransportStatus(self,OPSTRANSPORT.Status.DELIVERED) +self:T(self.lid..string.format("All cargo of transport UID=%d delivered ==> check group done in 0.2 sec",self.cargoTransport.uid)) +self:_CheckGroupDone(0.2) +end +end +function OPSGROUP:onafterTransportCancel(From,Event,To,Transport) +if self.cargoTransport and self.cargoTransport.uid==Transport.uid then +self:T(self.lid..string.format("Cancel current transport %d",Transport.uid)) +local calldelivered=false +if self:IsPickingup()then +calldelivered=true +elseif self:IsLoading()then +local cargos=Transport:GetCargoOpsGroups(false) +for _,_opsgroup in pairs(cargos)do +local opsgroup=_opsgroup +if opsgroup:IsBoarding(self.groupname)then +opsgroup:RemoveWaypoint(self.currentwp+1) +self:_DelCargobay(opsgroup) +opsgroup:_RemoveMyCarrier() +opsgroup:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) +elseif opsgroup:IsLoaded(self.groupname)then +local zoneCarrier=self:GetElementZoneUnload(opsgroup:_GetMyCarrierElement().name) +local Coordinate=zoneCarrier and zoneCarrier:GetRandomCoordinate()or self.cargoTransport:GetEmbarkZone(self.cargoTZC):GetRandomCoordinate() +local Heading=math.random(0,359) +self:Unload(opsgroup,Coordinate,self.cargoTransport:GetDisembarkActivation(self.cargoTZC),Heading) +self.cargoTransport:Unloaded(opsgroup,self) +end +end +calldelivered=true +elseif self:IsTransporting()then +elseif self:IsUnloading()then +else +end +if calldelivered then +self:__Delivered(-2,Transport) +end +else +Transport:SetCarrierTransportStatus(self,AUFTRAG.GroupStatus.CANCELLED) +self:DelOpsTransport(Transport) +self:_CheckGroupDone(1) +end +end +function OPSGROUP:onbeforeBoard(From,Event,To,CarrierGroup,Carrier) +if self:IsDead()then +self:T(self.lid.."Group DEAD ==> Deny Board transition!") +return false +elseif CarrierGroup:IsDead()then +self:T(self.lid.."Carrier Group DEAD ==> Deny Board transition!") +self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) +return false +elseif Carrier.status==OPSGROUP.ElementStatus.DEAD then +self:T(self.lid.."Carrier Element DEAD ==> Deny Board transition!") +self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) +return false +end +return true +end +function OPSGROUP:onafterBoard(From,Event,To,CarrierGroup,Carrier) +local CarrierIsArmyOrNavy=CarrierGroup:IsArmygroup()or CarrierGroup:IsNavygroup() +local CargoIsArmyOrNavy=self:IsArmygroup()or self:IsNavygroup() +if(CarrierIsArmyOrNavy and(CarrierGroup:GetVelocity(Carrier.name)<=1))or(CarrierGroup:IsFlightgroup()and(CarrierGroup:IsParking()or CarrierGroup:IsLandedAt()))then +local board=self.speedMax>0 and CargoIsArmyOrNavy and self:IsAlive()and CarrierGroup:IsAlive() +if self:IsArmygroup()and CarrierGroup:IsNavygroup()then +board=false +end +if self:IsLoaded()then +self:T(self.lid..string.format("Group is loaded currently ==> Moving directly to new carrier - No Unload(), Disembart() events triggered!")) +self:_RemoveMyCarrier() +CarrierGroup:Load(self) +elseif board then +self:_NewCargoStatus(OPSGROUP.CargoStatus.BOARDING) +self:T(self.lid..string.format("Boarding group=%s [%s], carrier=%s",CarrierGroup:GetName(),CarrierGroup:GetState(),tostring(Carrier.name))) +local Coordinate=Carrier.unit:GetCoordinate() +self:ClearWaypoints(self.currentwp+1) +if self.isArmygroup then +local waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,nil,nil,ENUMS.Formation.Vehicle.Diamond);waypoint.detour=1 +self:Cruise() +else +local waypoint=NAVYGROUP.AddWaypoint(self,Coordinate);waypoint.detour=1 +self:Cruise() +end +CarrierGroup:_AddCargobay(self,Carrier,true) +else +self:T(self.lid..string.format("Board [loaded=%s] with direct load to carrier group=%s, element=%s",tostring(self:IsLoaded()),CarrierGroup:GetName(),tostring(Carrier.name))) +local mycarriergroup=self:_GetMyCarrierGroup() +if mycarriergroup then +self:T(self.lid..string.format("Current carrier group %s",mycarriergroup:GetName())) +end +if mycarriergroup and mycarriergroup:GetName()~=CarrierGroup:GetName()then +self:T(self.lid.."Unloading from mycarrier") +mycarriergroup:Unload(self) +end +CarrierGroup:Load(self) +end +else +self:T(self.lid.."Carrier not ready for boarding yet ==> repeating boarding call in 10 sec") +self:__Board(-10,CarrierGroup,Carrier) +CarrierGroup:_AddCargobay(self,Carrier,true) +end +end +function OPSGROUP:_CheckInZones() +if self.checkzones and self:IsAlive()then +local Ncheck=self.checkzones:Count() +local Ninside=self.inzones:Count() +self:T(self.lid..string.format("Check if group is in %d zones. Currently it is in %d zones.",self.checkzones:Count(),self.inzones:Count())) +local leftzones={} +for inzonename,inzone in pairs(self.inzones:GetSet())do +local isstillinzone=self.group:IsInZone(inzone) +if not isstillinzone then +table.insert(leftzones,inzone) +end +end +for _,leftzone in pairs(leftzones)do +self:LeaveZone(leftzone) +end +local enterzones={} +for checkzonename,_checkzone in pairs(self.checkzones:GetSet())do +local checkzone=_checkzone +local isincheckzone=self.group:IsInZone(checkzone) +if isincheckzone and not self.inzones:_Find(checkzonename)then +table.insert(enterzones,checkzone) +end +end +for _,enterzone in pairs(enterzones)do +self:EnterZone(enterzone) +end +end +end +function OPSGROUP:_CheckDetectedUnits() +if self.detectionOn and self.group and not self:IsDead()then +local detectedtargets=self.group:GetDetectedTargets() +local detected={} +local groups={} +for DetectionObjectID,Detection in pairs(detectedtargets or{})do +local DetectedObject=Detection.object +if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then +local unit=UNIT:Find(DetectedObject) +if unit and unit:IsAlive()then +local unitname=unit:GetName() +table.insert(detected,unit) +self:DetectedUnit(unit) +local group=unit:GetGroup() +if group then +groups[group:GetName()]=group +end +end +end +end +for groupname,group in pairs(groups)do +self:DetectedGroup(group) +end +local lost={} +for _,_unit in pairs(self.detectedunits:GetSet())do +local unit=_unit +local gotit=false +for _,_du in pairs(detected)do +local du=_du +if unit:GetName()==du:GetName()then +gotit=true +end +end +if not gotit then +table.insert(lost,unit:GetName()) +self:DetectedUnitLost(unit) +end +end +self.detectedunits:RemoveUnitsByName(lost) +local lost={} +for _,_group in pairs(self.detectedgroups:GetSet())do +local group=_group +local gotit=false +for _,_du in pairs(groups)do +local du=_du +if group:GetName()==du:GetName()then +gotit=true +end +end +if not gotit then +table.insert(lost,group:GetName()) +self:DetectedGroupLost(group) +end +end +self.detectedgroups:RemoveGroupsByName(lost) +end +end +function OPSGROUP:_CheckGroupDone(delay) +local fsmstate=self:GetState() +if self:IsAlive()and self.isAI then +if delay and delay>0 then +self:T(self.lid..string.format("Check OPSGROUP done? [state=%s] in %.3f seconds...",fsmstate,delay)) +self:ScheduleOnce(delay,self._CheckGroupDone,self) +else +self:T(self.lid..string.format("Check OSGROUP done? [state=%s]",fsmstate)) +if self:IsEngaging()then +self:T(self.lid.."Engaging! Group NOT done ==> UpdateRoute()") +self:UpdateRoute() +return +end +if self:IsReturning()then +self:T(self.lid.."Returning! Group NOT done...") +return +end +if self:IsRearming()then +self:T(self.lid.."Rearming! Group NOT done...") +return +end +if self:IsRetreating()then +self:T(self.lid.."Retreating! Group NOT done...") +return +end +if self:IsBoarding()then +self:T(self.lid.."Boarding! Group NOT done...") +return +end +if self:IsWaiting()then +self:T(self.lid.."Waiting! Group NOT done...") +return +end +local nTasks=self:CountRemainingTasks() +local nMissions=self:CountRemainingMissison() +local nTransports=self:CountRemainingTransports() +local nPaused=self:_CountPausedMissions() +if nPaused>0 and nPaused==nMissions then +local missionpaused=self:_GetPausedMission() +self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...",missionpaused.name,missionpaused.type)) +self:UnpauseMission() +return +end +if nTasks>0 or nMissions>0 or nTransports>0 then +self:T(self.lid..string.format("Group still has tasks, missions or transports ==> NOT DONE")) +return +end +local waypoint=self:GetWaypoint(self.currentwp) +if waypoint then +local ntasks=self:CountTasksWaypoint(waypoint.uid) +if ntasks>0 then +self:T(self.lid..string.format("Still got %d tasks for the current waypoint UID=%d ==> RETURN (no action)",ntasks,waypoint.uid)) +return +end +end +if self.adinfinitum then +if#self.waypoints>0 then +local i=self:GetWaypointIndexNext(true) +local speed=self:GetSpeedToWaypoint(i) +self:Cruise(speed) +self:T(self.lid..string.format("Adinfinitum=TRUE ==> Goto WP index=%d at speed=%d knots",i,speed)) +else +self:T(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) +self:__FullStop(-1) +end +else +if self:HasPassedFinalWaypoint()then +if self.legion and self.legionReturn then +self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE, LEGION set ==> RTZ")) +if self.isArmygroup then +self:T2(self.lid.."RTZ to legion spawn zone") +self:RTZ(self.legion.spawnzone) +elseif self.isNavygroup then +self:T2(self.lid.."RTZ to legion port zone") +self:RTZ(self.legion.portzone) +end +else +self:__FullStop(-1) +self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE ==> Full Stop")) +end +else +if#self.waypoints>0 then +self:T(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) +self:Cruise() +else +self:T(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) +self:__FullStop(-1) +end +end +end +end +end +end +function OPSGROUP:_CheckStuck() +if self:IsHolding()or self:Is("Rearming")or self:IsWaiting()or self:HasPassedFinalWaypoint()then +return +end +local Tnow=timer.getTime() +local ExpectedSpeed=self:GetExpectedSpeed() +local speed=self:GetVelocity() +if speed<0.1 then +if ExpectedSpeed>0 and not self.stuckTimestamp then +self:T2(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected",speed,ExpectedSpeed)) +self.stuckTimestamp=Tnow +self.stuckVec3=self:GetVec3() +end +else +self.stuckTimestamp=nil +end +if self.stuckTimestamp then +local holdtime=Tnow-self.stuckTimestamp +if holdtime>=5*60 and holdtime<10*60 then +self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime)) +if self:IsEngaging()then +self:__Disengage(1) +elseif self:IsReturning()then +self:T2(self.lid.."RTZ because of stuck") +self:__RTZ(1) +else +self:__Cruise(1) +end +elseif holdtime>=10*60 and holdtime<30*60 then +self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime)) +local mission=self:GetMissionCurrent() +if mission then +self:T(self.lid..string.format("WARNING: Cancelling mission %s [%s] due to being stuck",mission:GetName(),mission:GetType())) +self:MissionCancel(mission) +else +if self:IsReturning()then +self:T2(self.lid.."RTZ because of stuck") +self:__RTZ(1) +else +self:__Cruise(1) +end +end +elseif holdtime>=30*60 then +self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime)) +if self.legion then +self:T(self.lid..string.format("Asset is returned to its legion after being stuck!")) +self:ReturnToLegion() +end +end +end +end +function OPSGROUP:_CheckDamage() +self:T(self.lid..string.format("Checking damage...")) +self.life=0 +local damaged=false +for _,_element in pairs(self.elements)do +local element=_element +if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then +local life=element.unit:GetLife() +self.life=self.life+life +if life0 then +local ammo=self:GetAmmoTot() +if self:IsRearming()then +if ammo.Total>=self.ammo.Total then +self:Rearmed() +end +end +if self.outofAmmo and ammo.Total>0 then +self.outofAmmo=false +end +if ammo.Total==0 and not self.outofAmmo then +self.outofAmmo=true +self:OutOfAmmo() +end +if self.outofGuns and ammo.Guns>0 then +self.outofGuns=false +end +if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then +self.outofGuns=true +self:OutOfGuns() +end +if self.outofRockets and ammo.Rockets>0 then +self.outofRockets=false +end +if ammo.Rockets==0 and self.ammo.Rockets>0 and not self.outofRockets then +self.outofRockets=true +self:OutOfRockets() +end +if self.outofBombs and ammo.Bombs>0 then +self.outofBombs=false +end +if ammo.Bombs==0 and self.ammo.Bombs>0 and not self.outofBombs then +self.outofBombs=true +self:OutOfBombs() +end +if self.outofMissiles and ammo.Missiles>0 then +self.outofMissiles=false +end +if ammo.Missiles==0 and self.ammo.Missiles>0 and not self.outofMissiles then +self.outofMissiles=true +self:OutOfMissiles() +end +if self.outofMissilesAA and ammo.MissilesAA>0 then +self.outofMissilesAA=false +end +if ammo.MissilesAA==0 and self.ammo.MissilesAA>0 and not self.outofMissilesAA then +self.outofMissilesAA=true +self:OutOfMissilesAA() +end +if self.outofMissilesAG and ammo.MissilesAG>0 then +self.outofMissilesAG=false +end +if ammo.MissilesAG==0 and self.ammo.MissilesAG>0 and not self.outofMissilesAG then +self.outofMissilesAG=true +self:OutOfMissilesAG() +end +if self.outofMissilesAS and ammo.MissilesAS>0 then +self.outofMissilesAS=false +end +if ammo.MissilesAS==0 and self.ammo.MissilesAS>0 and not self.outofMissilesAS then +self.outofMissilesAS=true +self:OutOfMissilesAS() +end +if self.outofTorpedos and ammo.Torpedos>0 then +self.outofTorpedos=false +end +if ammo.Torpedos==0 and self.ammo.Torpedos>0 and not self.outofTorpedos then +self.outofTorpedos=true +self:OutOfTorpedos() +end +if self:IsEngaging()and ammo.Total==0 then +self:Disengage() +end +end +end +function OPSGROUP:_PrintTaskAndMissionStatus() +if self.verbose>=3 and#self.taskqueue>0 then +local text=string.format("Tasks #%d",#self.taskqueue) +for i,_task in pairs(self.taskqueue)do +local task=_task +local name=task.description +local taskid=task.dcstask.id or"unknown" +local status=task.status +local clock=UTILS.SecondsToClock(task.time,true) +local eta=task.time-timer.getAbsTime() +local started=task.timestamp and UTILS.SecondsToClock(task.timestamp,true)or"N/A" +local duration=-1 +if task.duration then +duration=task.duration +if task.timestamp then +duration=task.duration-(timer.getAbsTime()-task.timestamp) +else +duration=task.duration +end +end +if task.type==OPSGROUP.TaskType.SCHEDULED then +text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d",i,taskid,name,status,clock,eta,started,duration) +elseif task.type==OPSGROUP.TaskType.WAYPOINT then +text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d",i,taskid,name,status,task.waypoint,started,duration,task.stopflag:Get()) +end +end +self:I(self.lid..text) +end +if self.verbose>=2 then +local Mission=self:GetMissionByID(self.currentmission) +local text=string.format("Missions %d, Current: %s",self:CountRemainingMissison(),Mission and Mission.name or"none") +for i,_mission in pairs(self.missionqueue)do +local mission=_mission +local Cstart=UTILS.SecondsToClock(mission.Tstart,true) +local Cstop=mission.Tstop and UTILS.SecondsToClock(mission.Tstop,true)or"INF" +text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d", +i,tostring(mission.name),mission.type,mission:GetGroupStatus(self),tostring(mission.status),Cstart,Cstop,mission.prio,tostring(mission:GetGroupWaypointIndex(self)),mission:CountMissionTargets()) +end +self:I(self.lid..text) +end +end +function OPSGROUP:_SimpleTaskFunction(Function,uid) +local DCSScript={} +DCSScript[#DCSScript+1]=string.format('local mygroup = _DATABASE:FindOpsGroup(\"%s\") ',self.groupname) +DCSScript[#DCSScript+1]=string.format('%s(mygroup, %d)',Function,uid) +local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript))) +return DCSTask +end +function OPSGROUP:_CreateWaypoint(waypoint) +waypoint.uid=self.wpcounter +waypoint.npassed=0 +waypoint.coordinate=COORDINATE:New(waypoint.x,waypoint.alt,waypoint.y) +waypoint.name=string.format("Waypoint UID=%d",waypoint.uid) +waypoint.patrol=false +waypoint.detour=false +waypoint.astar=false +waypoint.temp=false +local taskswp={} +local TaskPassingWaypoint=self:_SimpleTaskFunction("OPSGROUP._PassingWaypoint",waypoint.uid) +table.insert(taskswp,TaskPassingWaypoint) +waypoint.task=self.group:TaskCombo(taskswp) +self.wpcounter=self.wpcounter+1 +return waypoint +end +function OPSGROUP:_AddWaypoint(waypoint,wpnumber) +wpnumber=wpnumber or#self.waypoints+1 +table.insert(self.waypoints,wpnumber,waypoint) +self:T(self.lid..string.format("Adding waypoint at index=%d with UID=%d",wpnumber,waypoint.uid)) +if self.currentwp and wpnumber>self.currentwp then +self:_PassedFinalWaypoint(false,string.format("_AddWaypoint: wpnumber/index %d>%d self.currentwp",wpnumber,self.currentwp)) +end +end +function OPSGROUP:_InitWaypoints(WpIndexMin,WpIndexMax) +self.waypoints={} +self.waypoints0={} +local template=_DATABASE:GetGroupTemplate(self.groupname) +if template==nil then +return self +end +self.waypoints0=UTILS.DeepCopy(template.route.points) +WpIndexMin=WpIndexMin or 1 +WpIndexMax=WpIndexMax or#self.waypoints0 +WpIndexMax=math.min(WpIndexMax,#self.waypoints0) +for i=WpIndexMin,WpIndexMax do +local wp=self.waypoints0[i] +local Coordinate=COORDINATE:NewFromWaypoint(wp) +wp.speed=wp.speed or 0 +local speedknots=UTILS.MpsToKnots(wp.speed) +if i<=2 then +self.speedWp=wp.speed +self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s",self.speedWp)) +end +local Speed=UTILS.MpsToKnots(wp.speed) +local Waypoint=nil +if self:IsFlightgroup()then +Waypoint=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,nil,Altitude,false) +elseif self:IsArmygroup()then +Waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,nil,wp.action,false) +elseif self:IsNavygroup()then +Waypoint=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,nil,Depth,false) +end +local DCStasks=wp.task and wp.task.params.tasks or nil +if DCStasks and self.useMEtasks then +for _,DCStask in pairs(DCStasks)do +if DCStask.id and DCStask.id~="WrappedAction"then +self:AddTaskWaypoint(DCStask,Waypoint,"ME Task") +end +end +end +end +self:T(self.lid..string.format("Initializing %d waypoints",#self.waypoints)) +if self:IsFlightgroup()then +self.homebase=self.homebase or self:GetHomebaseFromWaypoints() +local destbase=self:GetDestinationFromWaypoints() +self.destbase=self.destbase or destbase +self.currbase=self:GetHomebaseFromWaypoints() +if destbase and#self.waypoints>1 then +table.remove(self.waypoints,#self.waypoints) +end +if self.destbase==nil then +self.destbase=self.homebase +end +end +if#self.waypoints>0 then +if#self.waypoints==1 then +self:_PassedFinalWaypoint(true,"_InitWaypoints: #self.waypoints==1") +end +else +self:T(self.lid.."WARNING: No waypoints initialized. Number of waypoints is 0!") +end +return self +end +function OPSGROUP:Route(waypoints,delay) +if delay and delay>0 then +self:ScheduleOnce(delay,OPSGROUP.Route,self,waypoints) +else +if self:IsAlive()then +local DCSTask={ +id='Mission', +params={ +airborne=self:IsFlightgroup(), +route={points=waypoints}, +}, +} +self:SetTask(DCSTask) +else +self:T(self.lid.."ERROR: Group is not alive! Cannot route group.") +end +end +return self +end +function OPSGROUP:_UpdateWaypointTasks(n) +local waypoints=self.waypoints or{} +local nwaypoints=#waypoints +for i,_wp in pairs(waypoints)do +local wp=_wp +if i>=n or nwaypoints==1 then +self:T2(self.lid..string.format("Updating waypoint task for waypoint %d/%d ID=%d. Last waypoint passed %d",i,nwaypoints,wp.uid,self.currentwp)) +local taskswp={} +local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint",self,wp.uid) +table.insert(taskswp,TaskPassingWaypoint) +wp.task=self.group:TaskCombo(taskswp) +end +end +end +function OPSGROUP._PassingWaypoint(opsgroup,uid) +local text=string.format("Group passing waypoint uid=%d",uid) +opsgroup:T(opsgroup.lid..text) +local waypoint=opsgroup:GetWaypointByID(uid) +if waypoint then +waypoint.npassed=waypoint.npassed+1 +local currentwp=opsgroup.currentwp +opsgroup.currentwp=opsgroup:GetWaypointIndex(uid) +local wpistemp=waypoint.temp or waypoint.detour or waypoint.astar +if wpistemp then +opsgroup:RemoveWaypointByID(uid) +end +local wpnext=opsgroup:GetWaypointNext() +if wpnext then +opsgroup:T(opsgroup.lid..string.format("Next waypoint UID=%d index=%d",wpnext.uid,opsgroup:GetWaypointIndex(wpnext.uid))) +if opsgroup.isArmygroup then +opsgroup.option.Formation=wpnext.action +end +opsgroup.speed=wpnext.speed +if opsgroup.speed<0.01 then +opsgroup.speed=UTILS.KmphToMps(opsgroup.speedCruise) +end +else +opsgroup:_PassedFinalWaypoint(true,"_PassingWaypoint No next Waypoint found") +end +if opsgroup.currentwp==#opsgroup.waypoints and not(opsgroup.adinfinitum or wpistemp)then +opsgroup:_PassedFinalWaypoint(true,"_PassingWaypoint currentwp==#waypoints and NOT adinfinitum and NOT a temporary waypoint") +end +if waypoint.temp then +if(opsgroup:IsNavygroup()or opsgroup:IsArmygroup())and opsgroup.currentwp==#opsgroup.waypoints then +opsgroup:Cruise() +end +elseif waypoint.astar then +opsgroup:Cruise() +elseif waypoint.detour then +if opsgroup:IsRearming()then +opsgroup:Rearming() +elseif opsgroup:IsRetreating()then +opsgroup:Retreated() +elseif opsgroup:IsReturning()then +opsgroup:Returned() +elseif opsgroup:IsPickingup()then +if opsgroup:IsFlightgroup()then +if opsgroup.cargoTZC then +if opsgroup.cargoTZC.PickupAirbase then +opsgroup:LandAtAirbase(opsgroup.cargoTZC.PickupAirbase) +else +local coordinate=opsgroup.cargoTZC.PickupZone:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND}) +opsgroup:LandAt(coordinate,60*60) +end +else +local coordinate=opsgroup:GetCoordinate() +opsgroup:LandAt(coordinate,60*60) +end +else +opsgroup:FullStop() +opsgroup:__Loading(-5) +end +elseif opsgroup:IsTransporting()then +if opsgroup:IsFlightgroup()then +if opsgroup.cargoTZC then +if opsgroup.cargoTZC.DeployAirbase then +opsgroup:LandAtAirbase(opsgroup.cargoTZC.DeployAirbase) +else +local coordinate=opsgroup.cargoTZC.DeployZone:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND}) +opsgroup:LandAt(coordinate,60*60) +end +else +local coordinate=opsgroup:GetCoordinate() +opsgroup:LandAt(coordinate,60*60) +end +else +opsgroup:FullStop() +opsgroup:Unloading() +end +elseif opsgroup:IsBoarding()then +local carrierGroup=opsgroup:_GetMyCarrierGroup() +local carrier=opsgroup:_GetMyCarrierElement() +if carrierGroup and carrierGroup:IsAlive()then +if carrier and carrier.unit and carrier.unit:IsAlive()then +carrierGroup:Load(opsgroup) +else +opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier UNIT as it is NOT alive!") +end +else +opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier GROUP as it is NOT alive!") +end +elseif opsgroup:IsEngaging()then +opsgroup:T(opsgroup.lid.."Passing engaging waypoint") +else +opsgroup:DetourReached() +if waypoint.detour==0 then +opsgroup:FullStop() +elseif waypoint.detour==1 then +opsgroup:Cruise() +else +opsgroup:E("ERROR: waypoint.detour should be 0 or 1") +opsgroup:FullStop() +end +end +else +if opsgroup.ispathfinding then +opsgroup.ispathfinding=false +end +opsgroup:PassingWaypoint(waypoint) +end +end +end +function OPSGROUP._TaskExecute(group,opsgroup,task) +local text=string.format("_TaskExecute %s",task.description) +opsgroup:T3(opsgroup.lid..text) +if opsgroup then +opsgroup:TaskExecute(task) +end +end +function OPSGROUP._TaskDone(group,opsgroup,task) +local text=string.format("_TaskDone %s",task.description) +opsgroup:T(opsgroup.lid..text) +if opsgroup then +opsgroup:TaskDone(task) +end +end +function OPSGROUP:SetDefaultROE(roe) +self.optionDefault.ROE=roe or ENUMS.ROE.ReturnFire +return self +end +function OPSGROUP:SwitchROE(roe) +if self:IsAlive()or self:IsInUtero()then +self.option.ROE=roe or self.optionDefault.ROE +if self:IsInUtero()then +self:T2(self.lid..string.format("Setting current ROE=%d when GROUP is SPAWNED",self.option.ROE)) +else +self.group:OptionROE(self.option.ROE) +self:T(self.lid..string.format("Setting current ROE=%d (%s)",self.option.ROE,self:_GetROEName(self.option.ROE))) +end +else +self:T(self.lid.."WARNING: Cannot switch ROE! Group is not alive") +end +return self +end +function OPSGROUP:_GetROEName(roe) +local name="unknown" +if roe==0 then +name="Weapon Free" +elseif roe==1 then +name="Open Fire/Weapon Free" +elseif roe==2 then +name="Open Fire" +elseif roe==3 then +name="Return Fire" +elseif roe==4 then +name="Weapon Hold" +end +return name +end +function OPSGROUP:GetROE() +return self.option.ROE or self.optionDefault.ROE +end +function OPSGROUP:SetDefaultROT(rot) +self.optionDefault.ROT=rot or ENUMS.ROT.PassiveDefense +return self +end +function OPSGROUP:SwitchROT(rot) +if self:IsFlightgroup()then +if self:IsAlive()or self:IsInUtero()then +self.option.ROT=rot or self.optionDefault.ROT +if self:IsInUtero()then +self:T2(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED",self.option.ROT)) +else +self.group:OptionROT(self.option.ROT) +self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)",self.option.ROT)) +end +else +self:T(self.lid.."WARNING: Cannot switch ROT! Group is not alive") +end +end +return self +end +function OPSGROUP:GetROT() +return self.option.ROT or self.optionDefault.ROT +end +function OPSGROUP:SetDefaultAlarmstate(alarmstate) +self.optionDefault.Alarm=alarmstate or 0 +return self +end +function OPSGROUP:SwitchAlarmstate(alarmstate) +if self:IsAlive()or self:IsInUtero()then +if self.isArmygroup or self.isNavygroup then +self.option.Alarm=alarmstate or self.optionDefault.Alarm +if self:IsInUtero()then +self:T2(self.lid..string.format("Setting current Alarm State=%d when GROUP is SPAWNED",self.option.Alarm)) +else +if self.option.Alarm==0 then +self.group:OptionAlarmStateAuto() +elseif self.option.Alarm==1 then +self.group:OptionAlarmStateGreen() +elseif self.option.Alarm==2 then +self.group:OptionAlarmStateRed() +else +self:T("ERROR: Unknown Alarm State! Setting to AUTO") +self.group:OptionAlarmStateAuto() +self.option.Alarm=0 +end +self:T(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)",self.option.Alarm)) +end +end +else +self:T(self.lid.."WARNING: Cannot switch Alarm State! Group is not alive.") +end +return self +end +function OPSGROUP:GetAlarmstate() +return self.option.Alarm or self.optionDefault.Alarm +end +function OPSGROUP:SetDefaultEPLRS(OnOffSwitch) +if OnOffSwitch==nil then +self.optionDefault.EPLRS=self.isEPLRS +else +self.optionDefault.EPLRS=OnOffSwitch +end +return self +end +function OPSGROUP:SwitchEPLRS(OnOffSwitch) +if self:IsAlive()or self:IsInUtero()then +if OnOffSwitch==nil then +self.option.EPLRS=self.optionDefault.EPLRS +else +self.option.EPLRS=OnOffSwitch +end +if self:IsInUtero()then +self:T2(self.lid..string.format("Setting current EPLRS=%s when GROUP is SPAWNED",tostring(self.option.EPLRS))) +else +self.group:CommandEPLRS(self.option.EPLRS) +self:T(self.lid..string.format("Setting current EPLRS=%s",tostring(self.option.EPLRS))) +end +else +self:E(self.lid.."WARNING: Cannot switch EPLRS! Group is not alive") +end +return self +end +function OPSGROUP:GetEPLRS() +return self.option.EPLRS or self.optionDefault.EPLRS +end +function OPSGROUP:SetDefaultEmission(OnOffSwitch) +if OnOffSwitch==nil then +self.optionDefault.Emission=true +else +self.optionDefault.Emission=OnOffSwitch +end +return self +end +function OPSGROUP:SwitchEmission(OnOffSwitch) +if self:IsAlive()or self:IsInUtero()then +if OnOffSwitch==nil then +self.option.Emission=self.optionDefault.Emission +else +self.option.Emission=OnOffSwitch +end +if self:IsInUtero()then +self:T2(self.lid..string.format("Setting current EMISSION=%s when GROUP is SPAWNED",tostring(self.option.Emission))) +else +self.group:EnableEmission(self.option.Emission) +self:T(self.lid..string.format("Setting current EMISSION=%s",tostring(self.option.Emission))) +end +else +self:E(self.lid.."WARNING: Cannot switch Emission! Group is not alive") +end +return self +end +function OPSGROUP:GetEmission() +return self.option.Emission or self.optionDefault.Emission +end +function OPSGROUP:SetDefaultInvisible(OnOffSwitch) +if OnOffSwitch==nil then +self.optionDefault.Invisible=true +else +self.optionDefault.Invisible=OnOffSwitch +end +return self +end +function OPSGROUP:SwitchInvisible(OnOffSwitch) +if self:IsAlive()or self:IsInUtero()then +if OnOffSwitch==nil then +self.option.Invisible=self.optionDefault.Invisible +else +self.option.Invisible=OnOffSwitch +end +if self:IsInUtero()then +self:T2(self.lid..string.format("Setting current INVISIBLE=%s when GROUP is SPAWNED",tostring(self.option.Invisible))) +else +self.group:SetCommandInvisible(self.option.Invisible) +self:T(self.lid..string.format("Setting current INVISIBLE=%s",tostring(self.option.Invisible))) +end +else +self:E(self.lid.."WARNING: Cannot switch Invisible! Group is not alive") +end +return self +end +function OPSGROUP:SetDefaultImmortal(OnOffSwitch) +if OnOffSwitch==nil then +self.optionDefault.Immortal=true +else +self.optionDefault.Immortal=OnOffSwitch +end +return self +end +function OPSGROUP:SwitchImmortal(OnOffSwitch) +if self:IsAlive()or self:IsInUtero()then +if OnOffSwitch==nil then +self.option.Immortal=self.optionDefault.Immortal +else +self.option.Immortal=OnOffSwitch +end +if self:IsInUtero()then +self:T2(self.lid..string.format("Setting current IMMORTAL=%s when GROUP is SPAWNED",tostring(self.option.Immortal))) +else +self.group:SetCommandImmortal(self.option.Immortal) +self:T(self.lid..string.format("Setting current IMMORTAL=%s",tostring(self.option.Immortal))) +end +else +self:E(self.lid.."WARNING: Cannot switch Immortal! Group is not alive") +end +return self +end +function OPSGROUP:SetDefaultTACAN(Channel,Morse,UnitName,Band,OffSwitch) +self.tacanDefault={} +self.tacanDefault.Channel=Channel or 74 +self.tacanDefault.Morse=Morse or"XXX" +self.tacanDefault.BeaconName=UnitName +if self:IsFlightgroup()then +Band=Band or"Y" +else +Band=Band or"X" +end +self.tacanDefault.Band=Band +if OffSwitch then +self.tacanDefault.On=false +else +self.tacanDefault.On=true +end +return self +end +function OPSGROUP:_SwitchTACAN(Tacan) +if Tacan then +self:SwitchTACAN(Tacan.Channel,Tacan.Morse,Tacan.BeaconName,Tacan.Band) +else +if self.tacanDefault.On then +self:SwitchTACAN() +else +self:TurnOffTACAN() +end +end +end +function OPSGROUP:SwitchTACAN(Channel,Morse,UnitName,Band) +if self:IsInUtero()then +self:T(self.lid..string.format("Switching TACAN to DEFAULT when group is spawned")) +self:SetDefaultTACAN(Channel,Morse,UnitName,Band) +elseif self:IsAlive()then +Channel=Channel or self.tacanDefault.Channel +Morse=Morse or self.tacanDefault.Morse +Band=Band or self.tacanDefault.Band +UnitName=UnitName or self.tacanDefault.BeaconName +local unit=self:GetUnit(1) +if UnitName then +if type(UnitName)=="number"then +unit=self.group:GetUnit(UnitName) +else +unit=UNIT:FindByName(UnitName) +end +end +if not unit then +self:T(self.lid.."WARNING: Could not get TACAN unit. Trying first unit in the group") +unit=self:GetUnit(1) +end +if unit and unit:IsAlive()then +local UnitID=unit:GetID() +local Type=BEACON.Type.TACAN +local System=BEACON.System.TACAN +if self:IsFlightgroup()then +System=BEACON.System.TACAN_TANKER_Y +end +local Frequency=UTILS.TACANToFrequency(Channel,Band) +unit:CommandActivateBeacon(Type,System,Frequency,UnitID,Channel,Band,true,Morse,true) +self.tacan.Channel=Channel +self.tacan.Morse=Morse +self.tacan.Band=Band +self.tacan.BeaconName=unit:GetName() +self.tacan.BeaconUnit=unit +self.tacan.On=true +self:T(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s",self.tacan.Channel,self.tacan.Band,tostring(self.tacan.Morse),self.tacan.BeaconName)) +else +self:T(self.lid.."ERROR: Cound not set TACAN! Unit is not alive") +end +else +self:T(self.lid.."ERROR: Cound not set TACAN! Group is not alive and not in utero any more") +end +return self +end +function OPSGROUP:TurnOffTACAN() +if self.tacan.BeaconUnit and self.tacan.BeaconUnit:IsAlive()then +self.tacan.BeaconUnit:CommandDeactivateBeacon() +end +self:T(self.lid..string.format("Switching TACAN OFF")) +self.tacan.On=false +end +function OPSGROUP:GetTACAN() +return self.tacan.Channel,self.tacan.Morse,self.tacan.Band,self.tacan.On,self.tacan.BeaconName +end +function OPSGROUP:GetBeaconTACAN() +return self.tacan +end +function OPSGROUP:SetDefaultICLS(Channel,Morse,UnitName,OffSwitch) +self.iclsDefault={} +self.iclsDefault.Channel=Channel or 1 +self.iclsDefault.Morse=Morse or"XXX" +self.iclsDefault.BeaconName=UnitName +if OffSwitch then +self.iclsDefault.On=false +else +self.iclsDefault.On=true +end +return self +end +function OPSGROUP:_SwitchICLS(Icls) +if Icls then +self:SwitchICLS(Icls.Channel,Icls.Morse,Icls.BeaconName) +else +if self.iclsDefault.On then +self:SwitchICLS() +else +self:TurnOffICLS() +end +end +end +function OPSGROUP:SwitchICLS(Channel,Morse,UnitName) +if self:IsInUtero()then +self:SetDefaultICLS(Channel,Morse,UnitName) +self:T2(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED",self.iclsDefault.Channel,tostring(self.iclsDefault.Morse),tostring(self.iclsDefault.BeaconName))) +elseif self:IsAlive()then +Channel=Channel or self.iclsDefault.Channel +Morse=Morse or self.iclsDefault.Morse +local unit=self:GetUnit(1) +if UnitName then +if type(UnitName)=="number"then +unit=self:GetUnit(UnitName) +else +unit=UNIT:FindByName(UnitName) +end +end +if not unit then +self:T(self.lid.."WARNING: Could not get ICLS unit. Trying first unit in the group") +unit=self:GetUnit(1) +end +if unit and unit:IsAlive()then +local UnitID=unit:GetID() +unit:CommandActivateICLS(Channel,UnitID,Morse) +self.icls.Channel=Channel +self.icls.Morse=Morse +self.icls.Band=nil +self.icls.BeaconName=unit:GetName() +self.icls.BeaconUnit=unit +self.icls.On=true +self:T(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s",self.icls.Channel,tostring(self.icls.Morse),self.icls.BeaconName)) +else +self:T(self.lid.."ERROR: Cound not set ICLS! Unit is not alive.") +end +end +return self +end +function OPSGROUP:TurnOffICLS() +if self.icls.BeaconUnit and self.icls.BeaconUnit:IsAlive()then +self.icls.BeaconUnit:CommandDeactivateICLS() +end +self:T(self.lid..string.format("Switching ICLS OFF")) +self.icls.On=false +end +function OPSGROUP:SetDefaultRadio(Frequency,Modulation,OffSwitch) +self.radioDefault={} +self.radioDefault.Freq=Frequency or 251 +self.radioDefault.Modu=Modulation or radio.modulation.AM +if OffSwitch then +self.radioDefault.On=false +else +self.radioDefault.On=true +end +return self +end +function OPSGROUP:GetRadio() +return self.radio.Freq,self.radio.Modu,self.radio.On +end +function OPSGROUP:SwitchRadio(Frequency,Modulation) +if self:IsInUtero()then +self:SetDefaultRadio(Frequency,Modulation) +self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED",self.radioDefault.Freq,UTILS.GetModulationName(self.radioDefault.Modu))) +elseif self:IsAlive()then +Frequency=Frequency or self.radioDefault.Freq +Modulation=Modulation or self.radioDefault.Modu +if self:IsFlightgroup()and not self.radio.On then +self.group:SetOption(AI.Option.Air.id.SILENCE,false) +end +self.group:CommandSetFrequency(Frequency,Modulation) +self.radio.Freq=Frequency +self.radio.Modu=Modulation +self.radio.On=true +self:T(self.lid..string.format("Switching radio to frequency %.3f MHz %s",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu))) +else +self:T(self.lid.."ERROR: Cound not set Radio! Group is not alive or not in utero any more") +end +return self +end +function OPSGROUP:TurnOffRadio() +if self:IsAlive()then +if self:IsFlightgroup()then +self.group:SetOption(AI.Option.Air.id.SILENCE,true) +self.radio.On=false +self:T(self.lid..string.format("Switching radio OFF")) +else +self:T(self.lid.."ERROR: Radio can only be turned off for aircraft!") +end +end +return self +end +function OPSGROUP:SetDefaultFormation(Formation) +self.optionDefault.Formation=Formation +return self +end +function OPSGROUP:SwitchFormation(Formation) +if self:IsAlive()then +Formation=Formation or self.optionDefault.Formation +if self:IsFlightgroup()then +self.group:SetOption(AI.Option.Air.id.FORMATION,Formation) +elseif self.isArmygroup then +else +self:T(self.lid.."ERROR: Formation can only be set for aircraft or ground units!") +return self +end +self.option.Formation=Formation +self:T(self.lid..string.format("Switching formation to %s",tostring(self.option.Formation))) +end +return self +end +function OPSGROUP:SetDefaultCallsign(CallsignName,CallsignNumber) +self:T(self.lid..string.format("Setting Default callsing %s-%s",tostring(CallsignName),tostring(CallsignNumber))) +self.callsignDefault={} +self.callsignDefault.NumberSquad=CallsignName +self.callsignDefault.NumberGroup=CallsignNumber or 1 +self.callsignDefault.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) +return self +end +function OPSGROUP:SwitchCallsign(CallsignName,CallsignNumber) +if self:IsInUtero()then +self:SetDefaultCallsign(CallsignName,CallsignNumber) +elseif self:IsAlive()then +CallsignName=CallsignName or self.callsignDefault.NumberSquad +CallsignNumber=CallsignNumber or self.callsignDefault.NumberGroup +self.callsign.NumberSquad=CallsignName +self.callsign.NumberGroup=CallsignNumber +self:T(self.lid..string.format("Switching callsign to %d-%d",self.callsign.NumberSquad,self.callsign.NumberGroup)) +self.group:CommandSetCallsign(self.callsign.NumberSquad,self.callsign.NumberGroup) +self.callsignName=UTILS.GetCallsignName(self.callsign.NumberSquad).."-"..self.callsign.NumberGroup +self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) +for _,_element in pairs(self.elements)do +local element=_element +if element.status~=OPSGROUP.ElementStatus.DEAD then +element.callsign=element.unit:GetCallsign() +end +end +else +self:T(self.lid.."ERROR: Group is not alive and not in utero! Cannot switch callsign") +end +return self +end +function OPSGROUP:GetCallsignName(ShortCallsign,Keepnumber,CallsignTranslations) +local element=self:GetElementAlive() +if element then +self:T2(self.lid..string.format("Callsign %s",tostring(element.callsign))) +local name=element.callsign or"Ghostrider11" +name=name:gsub("-","") +if self.group:IsPlayer()or CallsignTranslations then +name=self.group:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) +end +return name +end +return"Ghostrider11" +end +function OPSGROUP:_UpdatePosition() +if self:IsExist()then +self.positionLast=self.position or self:GetVec3() +self.headingLast=self.heading or self:GetHeading() +self.orientXLast=self.orientX or self:GetOrientationX() +self.velocityLast=self.velocity or self.group:GetVelocityMPS() +self.position=self:GetVec3() +self.heading=self:GetHeading() +self.orientX=self:GetOrientationX() +self.velocity=self:GetVelocity() +for _,_element in pairs(self.elements)do +local element=_element +element.vec3=self:GetVec3(element.name) +end +local Tnow=timer.getTime() +self.dTpositionUpdate=self.TpositionUpdate and Tnow-self.TpositionUpdate or 0 +self.TpositionUpdate=Tnow +if not self.traveldist then +self.traveldist=0 +end +self.travelds=UTILS.VecNorm(UTILS.VecSubstract(self.position,self.positionLast)) +self.traveldist=self.traveldist+self.travelds +end +return self +end +function OPSGROUP:_AllSameStatus(status) +for _,_element in pairs(self.elements)do +local element=_element +if element.status==OPSGROUP.ElementStatus.DEAD then +elseif element.status~=status then +return false +end +end +return true +end +function OPSGROUP:_AllSimilarStatus(status) +if status==OPSGROUP.ElementStatus.DEAD then +for _,_element in pairs(self.elements)do +local element=_element +if element.status~=OPSGROUP.ElementStatus.DEAD then +return false +end +end +return true +end +for _,_element in pairs(self.elements)do +local element=_element +self:T2(self.lid..string.format("Status=%s, element %s status=%s",status,element.name,element.status)) +if element.status~=OPSGROUP.ElementStatus.DEAD then +if status==OPSGROUP.ElementStatus.INUTERO then +if element.status~=status then +return false +end +elseif status==OPSGROUP.ElementStatus.SPAWNED then +if element.status~=status and +element.status==OPSGROUP.ElementStatus.INUTERO then +return false +end +elseif status==OPSGROUP.ElementStatus.PARKING then +if element.status~=status or +(element.status==OPSGROUP.ElementStatus.INUTERO or +element.status==OPSGROUP.ElementStatus.SPAWNED)then +return false +end +elseif status==OPSGROUP.ElementStatus.ENGINEON then +if element.status~=status and +(element.status==OPSGROUP.ElementStatus.INUTERO or +element.status==OPSGROUP.ElementStatus.SPAWNED or +element.status==OPSGROUP.ElementStatus.PARKING)then +return false +end +elseif status==OPSGROUP.ElementStatus.TAXIING then +if element.status~=status and +(element.status==OPSGROUP.ElementStatus.INUTERO or +element.status==OPSGROUP.ElementStatus.SPAWNED or +element.status==OPSGROUP.ElementStatus.PARKING or +element.status==OPSGROUP.ElementStatus.ENGINEON)then +return false +end +elseif status==OPSGROUP.ElementStatus.TAKEOFF then +if element.status~=status and +(element.status==OPSGROUP.ElementStatus.INUTERO or +element.status==OPSGROUP.ElementStatus.SPAWNED or +element.status==OPSGROUP.ElementStatus.PARKING or +element.status==OPSGROUP.ElementStatus.ENGINEON or +element.status==OPSGROUP.ElementStatus.TAXIING)then +return false +end +elseif status==OPSGROUP.ElementStatus.AIRBORNE then +if element.status~=status and +(element.status==OPSGROUP.ElementStatus.INUTERO or +element.status==OPSGROUP.ElementStatus.SPAWNED or +element.status==OPSGROUP.ElementStatus.PARKING or +element.status==OPSGROUP.ElementStatus.ENGINEON or +element.status==OPSGROUP.ElementStatus.TAXIING or +element.status==OPSGROUP.ElementStatus.TAKEOFF)then +return false +end +elseif status==OPSGROUP.ElementStatus.LANDED then +if element.status~=status and +(element.status==OPSGROUP.ElementStatus.AIRBORNE or +element.status==OPSGROUP.ElementStatus.LANDING)then +return false +end +elseif status==OPSGROUP.ElementStatus.ARRIVED then +if element.status~=status and +(element.status==OPSGROUP.ElementStatus.AIRBORNE or +element.status==OPSGROUP.ElementStatus.LANDING or +element.status==OPSGROUP.ElementStatus.LANDED)then +return false +end +end +else +end +end +self:T2(self.lid..string.format("All %d elements have similar status %s ==> returning TRUE",#self.elements,status)) +return true +end +function OPSGROUP:_UpdateStatus(element,newstatus,airbase) +local oldstatus=element.status +element.status=newstatus +self:T3(self.lid..string.format("UpdateStatus element=%s: %s --> %s",element.name,oldstatus,newstatus)) +for _,_element in pairs(self.elements)do +local Element=_element +self:T3(self.lid..string.format("Element %s: %s",Element.name,Element.status)) +end +if newstatus==OPSGROUP.ElementStatus.INUTERO then +if self:_AllSimilarStatus(newstatus)then +self:InUtero() +end +elseif newstatus==OPSGROUP.ElementStatus.SPAWNED then +if self:_AllSimilarStatus(newstatus)then +self:Spawned() +end +elseif newstatus==OPSGROUP.ElementStatus.PARKING then +if self:_AllSimilarStatus(newstatus)then +self:Parking() +end +elseif newstatus==OPSGROUP.ElementStatus.ENGINEON then +elseif newstatus==OPSGROUP.ElementStatus.TAXIING then +if self:_AllSimilarStatus(newstatus)then +self:Taxiing() +end +elseif newstatus==OPSGROUP.ElementStatus.TAKEOFF then +if self:_AllSimilarStatus(newstatus)then +self:Takeoff(airbase) +end +elseif newstatus==OPSGROUP.ElementStatus.AIRBORNE then +if self:_AllSimilarStatus(newstatus)then +self:Airborne() +end +elseif newstatus==OPSGROUP.ElementStatus.LANDED then +if self:_AllSimilarStatus(newstatus)then +if self:IsLandingAt()then +self:LandedAt() +else +self:Landed(airbase) +end +end +elseif newstatus==OPSGROUP.ElementStatus.ARRIVED then +if self:_AllSimilarStatus(newstatus)then +if self:IsLanded()then +self:Arrived() +elseif self:IsAirborne()then +self:Landed() +self:Arrived() +end +end +elseif newstatus==OPSGROUP.ElementStatus.DEAD then +if self:_AllSimilarStatus(newstatus)then +self:Dead() +end +end +end +function OPSGROUP:_SetElementStatusAll(status) +for _,_element in pairs(self.elements)do +local element=_element +if element.status~=OPSGROUP.ElementStatus.DEAD then +element.status=status +end +end +end +function OPSGROUP:GetElementByName(unitname) +if unitname and type(unitname)=="string"then +for _,_element in pairs(self.elements)do +local element=_element +if element.name==unitname then +return element +end +end +end +return nil +end +function OPSGROUP:GetElementZoneBoundingBox(UnitName) +local element=self:GetElementByName(UnitName) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +element.zoneBoundingbox=element.zoneBoundingbox or ZONE_POLYGON_BASE:New(element.name.." Zone Bounding Box",{}) +local l=element.length +local w=element.width +local X=self:GetOrientationX(element.name) +local heading=math.deg(math.atan2(X.z,X.x)) +self:T(self.lid..string.format("Element %s bouding box: l=%d w=%d heading=%d",element.name,l,w,heading)) +local b={} +b[1]={x=l/2,y=-w/2} +b[2]={x=l/2,y=w/2} +b[3]={x=-l/2,y=w/2} +b[4]={x=-l/2,y=-w/2} +for i,p in pairs(b)do +b[i]=UTILS.Vec2Rotate2D(p,heading) +end +local vec2=self:GetVec2(element.name) +local d=UTILS.Vec2Norm(vec2) +local h=UTILS.Vec2Hdg(vec2) +for i,p in pairs(b)do +b[i]=UTILS.Vec2Translate(p,d,h) +end +element.zoneBoundingbox:UpdateFromVec2(b) +return element.zoneBoundingbox +end +return nil +end +function OPSGROUP:GetElementZoneLoad(UnitName) +local element=self:GetElementByName(UnitName) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +element.zoneLoad=element.zoneLoad or ZONE_POLYGON_BASE:New(element.name.." Zone Load",{}) +self:_GetElementZoneLoader(element,element.zoneLoad,self.carrierLoader) +return element.zoneLoad +end +return nil +end +function OPSGROUP:GetElementZoneUnload(UnitName) +local element=self:GetElementByName(UnitName) +if element and element.status~=OPSGROUP.ElementStatus.DEAD then +element.zoneUnload=element.zoneUnload or ZONE_POLYGON_BASE:New(element.name.." Zone Unload",{}) +self:_GetElementZoneLoader(element,element.zoneUnload,self.carrierUnloader) +return element.zoneUnload +end +return nil +end +function OPSGROUP:_GetElementZoneLoader(Element,Zone,Loader) +if Element.status~=OPSGROUP.ElementStatus.DEAD then +local l=Element.length +local w=Element.width +local X=self:GetOrientationX(Element.name) +local heading=math.deg(math.atan2(X.z,X.x)) +local b={} +if Loader.type:lower()=="front"then +table.insert(b,{x=l/2,y=-Loader.width/2}) +table.insert(b,{x=l/2+Loader.length,y=-Loader.width/2}) +table.insert(b,{x=l/2+Loader.length,y=Loader.width/2}) +table.insert(b,{x=l/2,y=Loader.width/2}) +elseif Loader.type:lower()=="back"then +table.insert(b,{x=-l/2,y=-Loader.width/2}) +table.insert(b,{x=-l/2-Loader.length,y=-Loader.width/2}) +table.insert(b,{x=-l/2-Loader.length,y=Loader.width/2}) +table.insert(b,{x=-l/2,y=Loader.width/2}) +elseif Loader.type:lower()=="left"then +table.insert(b,{x=Loader.length/2,y=-w/2}) +table.insert(b,{x=Loader.length/2,y=-w/2-Loader.width}) +table.insert(b,{x=-Loader.length/2,y=-w/2-Loader.width}) +table.insert(b,{x=-Loader.length/2,y=-w/2}) +elseif Loader.type:lower()=="right"then +table.insert(b,{x=Loader.length/2,y=w/2}) +table.insert(b,{x=Loader.length/2,y=w/2+Loader.width}) +table.insert(b,{x=-Loader.length/2,y=w/2+Loader.width}) +table.insert(b,{x=-Loader.length/2,y=w/2}) +else +b[1]={x=l/2,y=-w/2} +b[2]={x=l/2,y=w/2} +b[3]={x=-l/2,y=w/2} +b[4]={x=-l/2,y=-w/2} +table.insert(b,{x=b[1].x+Loader.length,y=b[1].y-Loader.width}) +table.insert(b,{x=b[2].x+Loader.length,y=b[2].y+Loader.width}) +table.insert(b,{x=b[3].x-Loader.length,y=b[3].y+Loader.width}) +table.insert(b,{x=b[4].x-Loader.length,y=b[4].y-Loader.width}) +end +for i,p in pairs(b)do +b[i]=UTILS.Vec2Rotate2D(p,heading) +end +local vec2=self:GetVec2(Element.name) +local d=UTILS.Vec2Norm(vec2) +local h=UTILS.Vec2Hdg(vec2) +for i,p in pairs(b)do +b[i]=UTILS.Vec2Translate(p,d,h) +end +Zone:UpdateFromVec2(b) +return Zone +end +return nil +end +function OPSGROUP:GetElementAlive() +for _,_element in pairs(self.elements)do +local element=_element +if element.status~=OPSGROUP.ElementStatus.DEAD then +if element.unit and element.unit:IsAlive()then +return element +end +end +end +return nil +end +function OPSGROUP:GetNelements(status) +local n=0 +for _,_element in pairs(self.elements)do +local element=_element +if element.status~=OPSGROUP.ElementStatus.DEAD then +if element.unit and element.unit:IsAlive()then +if status==nil or element.status==status then +n=n+1 +end +end +end +end +return n +end +function OPSGROUP:GetAmmoElement(element) +return self:GetAmmoUnit(element.unit) +end +function OPSGROUP:GetAmmoTot() +local units=self.group:GetUnits() +local Ammo={} +Ammo.Total=0 +Ammo.Guns=0 +Ammo.Rockets=0 +Ammo.Bombs=0 +Ammo.Torpedos=0 +Ammo.Missiles=0 +Ammo.MissilesAA=0 +Ammo.MissilesAG=0 +Ammo.MissilesAS=0 +Ammo.MissilesCR=0 +Ammo.MissilesSA=0 +for _,_unit in pairs(units or{})do +local unit=_unit +if unit and unit:IsExist()then +local ammo=self:GetAmmoUnit(unit) +Ammo.Total=Ammo.Total+ammo.Total +Ammo.Guns=Ammo.Guns+ammo.Guns +Ammo.Rockets=Ammo.Rockets+ammo.Rockets +Ammo.Bombs=Ammo.Bombs+ammo.Bombs +Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos +Ammo.Missiles=Ammo.Missiles+ammo.Missiles +Ammo.MissilesAA=Ammo.MissilesAA+ammo.MissilesAA +Ammo.MissilesAG=Ammo.MissilesAG+ammo.MissilesAG +Ammo.MissilesAS=Ammo.MissilesAS+ammo.MissilesAS +Ammo.MissilesCR=Ammo.MissilesCR+ammo.MissilesCR +Ammo.MissilesSA=Ammo.MissilesSA+ammo.MissilesSA +end +end +return Ammo +end +function OPSGROUP:GetAmmoUnit(unit,display) +if display==nil then +display=false +end +local nammo=0 +local nshells=0 +local nrockets=0 +local nmissiles=0 +local nmissilesAA=0 +local nmissilesAG=0 +local nmissilesAS=0 +local nmissilesSA=0 +local nmissilesBM=0 +local nmissilesCR=0 +local ntorps=0 +local nbombs=0 +unit=unit or self.group:GetUnit(1) +if unit and unit:IsExist()then +local text=string.format("OPSGROUP group %s - unit %s:\n",self.groupname,unit:GetName()) +local ammotable=unit:GetAmmo() +if ammotable then +local weapons=#ammotable +for w=1,weapons do +local Nammo=ammotable[w]["count"] +local rmin=ammotable[w]["desc"]["rangeMin"]or 0 +local rmax=ammotable[w]["desc"]["rangeMaxAltMin"]or 0 +local Tammo=ammotable[w]["desc"]["typeName"] +local _weaponString=UTILS.Split(Tammo,"%.") +local _weaponName=_weaponString[#_weaponString] +local Category=ammotable[w].desc.category +local MissileCategory=nil +if Category==Weapon.Category.MISSILE then +MissileCategory=ammotable[w].desc.missileCategory +end +if Category==Weapon.Category.SHELL then +nshells=nshells+Nammo +text=text..string.format("- %d shells of type %s, range=%d - %d meters\n",Nammo,_weaponName,rmin,rmax) +elseif Category==Weapon.Category.ROCKET then +nrockets=nrockets+Nammo +text=text..string.format("- %d rockets of type %s, \n",Nammo,_weaponName,rmin,rmax) +elseif Category==Weapon.Category.BOMB then +nbombs=nbombs+Nammo +text=text..string.format("- %d bombs of type %s\n",Nammo,_weaponName) +elseif Category==Weapon.Category.MISSILE then +if MissileCategory==Weapon.MissileCategory.AAM then +nmissiles=nmissiles+Nammo +nmissilesAA=nmissilesAA+Nammo +elseif MissileCategory==Weapon.MissileCategory.SAM then +nmissiles=nmissiles+Nammo +nmissilesSA=nmissilesSA+Nammo +elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then +nmissiles=nmissiles+Nammo +nmissilesAS=nmissilesAS+Nammo +elseif MissileCategory==Weapon.MissileCategory.BM then +nmissiles=nmissiles+Nammo +nmissilesBM=nmissilesBM+Nammo +elseif MissileCategory==Weapon.MissileCategory.CRUISE then +nmissiles=nmissiles+Nammo +nmissilesCR=nmissilesCR+Nammo +elseif MissileCategory==Weapon.MissileCategory.OTHER then +nmissiles=nmissiles+Nammo +nmissilesAG=nmissilesAG+Nammo +end +text=text..string.format("- %d %s missiles of type %s, range=%d - %d meters\n",Nammo,self:_MissileCategoryName(MissileCategory),_weaponName,rmin,rmax) +elseif Category==Weapon.Category.TORPEDO then +ntorps=ntorps+Nammo +text=text..string.format("- %d torpedos of type %s\n",Nammo,_weaponName) +else +text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,Tammo,Category,tostring(MissileCategory)) +end +end +end +if display then +self:I(self.lid..text) +else +self:T3(self.lid..text) +end +end +nammo=nshells+nrockets+nmissiles+nbombs+ntorps +local ammo={} +ammo.Total=nammo +ammo.Guns=nshells +ammo.Rockets=nrockets +ammo.Bombs=nbombs +ammo.Torpedos=ntorps +ammo.Missiles=nmissiles +ammo.MissilesAA=nmissilesAA +ammo.MissilesAG=nmissilesAG +ammo.MissilesAS=nmissilesAS +ammo.MissilesCR=nmissilesCR +ammo.MissilesBM=nmissilesBM +ammo.MissilesSA=nmissilesSA +return ammo +end +function OPSGROUP:_MissileCategoryName(categorynumber) +local cat="unknown" +if categorynumber==Weapon.MissileCategory.AAM then +cat="air-to-air" +elseif categorynumber==Weapon.MissileCategory.SAM then +cat="surface-to-air" +elseif categorynumber==Weapon.MissileCategory.BM then +cat="ballistic" +elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then +cat="anti-ship" +elseif categorynumber==Weapon.MissileCategory.CRUISE then +cat="cruise" +elseif categorynumber==Weapon.MissileCategory.OTHER then +cat="other" +end +return cat +end +function OPSGROUP:_PassedFinalWaypoint(final,comment) +self:T(self.lid..string.format("Passed final waypoint=%s [from %s]: comment \"%s\"",tostring(final),tostring(self.passedfinalwp),tostring(comment))) +if final==true and not self.passedfinalwp then +self:PassedFinalWaypoint() +end +self.passedfinalwp=final +end +function OPSGROUP:_CoordinateFromObject(Object) +if Object then +if Object:IsInstanceOf("COORDINATE")then +return Object +else +if Object:IsInstanceOf("POSITIONABLE")or Object:IsInstanceOf("ZONE_BASE")then +self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") +local coord=Object:GetCoordinate() +return coord +else +self:T(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") +end +end +else +self:T(self.lid.."ERROR: Object passed is nil!") +end +return nil +end +function OPSGROUP:_IsElement(unitname) +for _,_element in pairs(self.elements)do +local element=_element +if element.name==unitname then +return true +end +end +return false +end +function OPSGROUP:CountElements(States) +if States then +if type(States)=="string"then +States={States} +end +else +States=OPSGROUP.ElementStatus +end +local IncludeDeads=true +local N=0 +for _,_element in pairs(self.elements)do +local element=_element +if element and(IncludeDeads or element.status~=OPSGROUP.ElementStatus.DEAD)then +for _,state in pairs(States)do +if element.status==state then +N=N+1 +break +end +end +end +end +return N +end +function OPSGROUP:_AddElementByName(unitname) +local unit=UNIT:FindByName(unitname) +if unit then +local element=self:GetElementByName(unitname) +if element then +else +element={} +element.status=OPSGROUP.ElementStatus.INUTERO +table.insert(self.elements,element) +end +element.name=unitname +element.unit=unit +element.DCSunit=Unit.getByName(unitname) +element.gid=element.DCSunit:getNumber() +element.uid=element.DCSunit:getID() +element.controller=element.DCSunit:getController() +element.Nhit=0 +element.opsgroup=self +local unittemplate=unit:GetTemplate() +if unittemplate==nil then +if element.DCSunit:getPlayerName()then +element.skill="Client" +end +else +element.skill=unittemplate~=nil and unittemplate.skill or"Unknown" +end +if element.skill=="Client"or element.skill=="Player"then +element.ai=false +element.client=CLIENT:FindByName(unitname) +element.playerName=element.DCSunit:getPlayerName() +else +element.ai=true +end +element.descriptors=unit:GetDesc() +element.category=unit:GetUnitCategory() +element.categoryname=unit:GetCategoryName() +element.typename=unit:GetTypeName() +element.ammo0=self:GetAmmoUnit(unit,false) +element.life=unit:GetLife() +element.life0=math.max(unit:GetLife0(),element.life) +element.size,element.length,element.height,element.width=unit:GetObjectSize() +element.weightEmpty=element.descriptors.massEmpty or 666 +if self.isArmygroup then +element.weightMaxTotal=element.weightEmpty+10*95 +elseif self.isNavygroup then +element.weightMaxTotal=element.weightEmpty+10*1000 +else +element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+8*95 +end +unit:SetCargoBayWeightLimit() +element.weightMaxCargo=unit.__.CargoBayWeightLimit +if element.cargoBay then +element.weightCargo=self:GetWeightCargo(element.name,false) +else +element.cargoBay={} +element.weightCargo=0 +end +element.weight=element.weightEmpty+element.weightCargo +element.callsign=element.unit:GetCallsign() +element.fuelmass=element.fuelmass0 or 99999 +element.fuelrel=element.unit:GetFuel()or 1 +if self.isFlightgroup and unittemplate then +element.modex=unittemplate.onboard_num +element.payload=unittemplate.payload +element.pylons=unittemplate.payload and unittemplate.payload.pylons or nil +element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 +else +element.callsign="Peter-1-1" +element.modex="000" +element.payload={} +element.pylons={} +end +local text=string.format("Adding element %s: status=%s, skill=%s, life=%.1f/%.1f category=%s (%d), type=%s, size=%.1f (L=%.1f H=%.1f W=%.1f), weight=%.1f/%.1f (cargo=%.1f/%.1f)", +element.name,element.status,element.skill,element.life,element.life0,element.categoryname,element.category,element.typename, +element.size,element.length,element.height,element.width,element.weight,element.weightMaxTotal,element.weightCargo,element.weightMaxCargo) +self:T(self.lid..text) +if unit:IsAlive()and element.status~=OPSGROUP.ElementStatus.SPAWNED then +self:__ElementSpawned(0.05,element) +end +return element +end +return nil +end +function OPSGROUP:_SetTemplate(Template) +self.template=Template or UTILS.DeepCopy(_DATABASE:GetGroupTemplate(self.groupname)) +self:T3(self.lid.."Setting group template") +return self +end +function OPSGROUP:_GetTemplate(Copy) +if self.template then +if Copy then +local template=UTILS.DeepCopy(self.template) +return template +else +return self.template +end +else +self:T(self.lid..string.format("ERROR: No template was set yet!")) +end +return nil +end +function OPSGROUP:ClearWaypoints(IndexMin,IndexMax) +IndexMin=IndexMin or 1 +IndexMax=IndexMax or#self.waypoints +for i=IndexMax,IndexMin,-1 do +table.remove(self.waypoints,i) +end +end +function OPSGROUP:_GetDetectedTarget() +local targetgroup=nil +local targetdist=math.huge +for _,_group in pairs(self.detectedgroups:GetSet())do +local group=_group +if group and group:IsAlive()then +local targetVec3=group:GetVec3() +local distance=UTILS.VecDist3D(self.position,targetVec3) +if distance<=self.engagedetectedRmax and distance=1 then +local text=string.format("Added cargo groups:") +local Weight=0 +for _,_cargo in pairs(self:GetCargos())do +local cargo=_cargo +local weight=cargo.opsgroup:GetWeightTotal() +Weight=Weight+weight +text=text..string.format("\n- %s [%s] weight=%.1f kg",cargo.opsgroup:GetName(),cargo.opsgroup:GetState(),weight) +end +text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg",self.Ncargo,Weight) +self:I(self.lid..text) +end +return self +end +function OPSTRANSPORT:AddCargoStorage(StorageFrom,StorageTo,CargoType,CargoAmount,CargoWeight,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +local cargo=self:_CreateCargoStorage(StorageFrom,StorageTo,CargoType,CargoAmount,CargoWeight,TransportZoneCombo) +if cargo then +self.Ncargo=self.Ncargo+1 +table.insert(TransportZoneCombo.Cargos,cargo) +end +end +function OPSTRANSPORT:SetPickupZone(PickupZone,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +TransportZoneCombo.PickupZone=PickupZone +if PickupZone and PickupZone:IsInstanceOf("ZONE_AIRBASE")then +TransportZoneCombo.PickupAirbase=PickupZone._.ZoneAirbase +end +return self +end +function OPSTRANSPORT:GetPickupZone(TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +return TransportZoneCombo.PickupZone +end +function OPSTRANSPORT:SetDeployZone(DeployZone,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +TransportZoneCombo.DeployZone=DeployZone +if DeployZone and DeployZone:IsInstanceOf("ZONE_AIRBASE")then +TransportZoneCombo.DeployAirbase=DeployZone._.ZoneAirbase +end +return self +end +function OPSTRANSPORT:GetDeployZone(TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +return TransportZoneCombo.DeployZone +end +function OPSTRANSPORT:SetEmbarkZone(EmbarkZone,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +TransportZoneCombo.EmbarkZone=EmbarkZone or TransportZoneCombo.PickupZone +return self +end +function OPSTRANSPORT:GetEmbarkZone(TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +return TransportZoneCombo.EmbarkZone +end +function OPSTRANSPORT:SetDisembarkZone(DisembarkZone,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +TransportZoneCombo.DisembarkZone=DisembarkZone +return self +end +function OPSTRANSPORT:GetDisembarkZone(TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +return TransportZoneCombo.DisembarkZone +end +function OPSTRANSPORT:SetDisembarkActivation(Active,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +if Active==true or Active==nil then +TransportZoneCombo.disembarkActivation=true +else +TransportZoneCombo.disembarkActivation=false +end +return self +end +function OPSTRANSPORT:GetDisembarkActivation(TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +return TransportZoneCombo.disembarkActivation +end +function OPSTRANSPORT:SetDisembarkCarriers(Carriers,TransportZoneCombo) +self:T(self.lid.."Setting transfer carriers!") +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +TransportZoneCombo.disembarkToCarriers=true +self:_AddDisembarkCarriers(Carriers,TransportZoneCombo.DisembarkCarriers) +return self +end +function OPSTRANSPORT:_AddDisembarkCarriers(Carriers,Table) +if Carriers:IsInstanceOf("GROUP")or Carriers:IsInstanceOf("OPSGROUP")then +local carrier=self:_GetOpsGroupFromObject(Carriers) +if carrier then +table.insert(Table,carrier) +end +elseif Carriers:IsInstanceOf("SET_GROUP")or Carriers:IsInstanceOf("SET_OPSGROUP")then +for _,object in pairs(Carriers:GetSet())do +local carrier=self:_GetOpsGroupFromObject(object) +if carrier then +table.insert(Table,carrier) +end +end +else +self:E(self.lid.."ERROR: Carriers must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") +end +end +function OPSTRANSPORT:GetDisembarkCarriers(TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +return TransportZoneCombo.DisembarkCarriers +end +function OPSTRANSPORT:SetDisembarkInUtero(InUtero,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +if InUtero==true or InUtero==nil then +TransportZoneCombo.disembarkInUtero=true +else +TransportZoneCombo.disembarkInUtero=false +end +return self +end +function OPSTRANSPORT:GetDisembarkInUtero(TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +return TransportZoneCombo.disembarkInUtero +end +function OPSTRANSPORT:SetFormationPickup(Formation,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +TransportZoneCombo.PickupFormation=Formation +return self +end +function OPSTRANSPORT:_GetFormationDefault(OpsGroup) +if OpsGroup.isArmygroup then +return self.formationArmy +elseif OpsGroup.isFlightgroup then +if OpsGroup.isHelo then +return self.formationHelo +else +return self.formationPlane +end +else +return ENUMS.Formation.Vehicle.OffRoad +end +return nil +end +function OPSTRANSPORT:_GetFormationPickup(TransportZoneCombo,OpsGroup) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +local formation=TransportZoneCombo.PickupFormation or self:_GetFormationDefault(OpsGroup) +return formation +end +function OPSTRANSPORT:SetFormationTransport(Formation,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +TransportZoneCombo.TransportFormation=Formation +return self +end +function OPSTRANSPORT:_GetFormationTransport(TransportZoneCombo,OpsGroup) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +local formation=TransportZoneCombo.TransportFormation or self:_GetFormationDefault(OpsGroup) +return formation +end +function OPSTRANSPORT:SetRequiredCargos(Cargos,TransportZoneCombo) +self:T(self.lid.."Setting required cargos!") +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +TransportZoneCombo.RequiredCargos=TransportZoneCombo.RequiredCargos or{} +if Cargos:IsInstanceOf("GROUP")or Cargos:IsInstanceOf("OPSGROUP")then +local cargo=self:_GetOpsGroupFromObject(Cargos) +if cargo then +table.insert(TransportZoneCombo.RequiredCargos,cargo) +end +elseif Cargos:IsInstanceOf("SET_GROUP")or Cargos:IsInstanceOf("SET_OPSGROUP")then +for _,object in pairs(Cargos:GetSet())do +local cargo=self:_GetOpsGroupFromObject(object) +if cargo then +table.insert(TransportZoneCombo.RequiredCargos,cargo) +end +end +else +self:E(self.lid.."ERROR: Required Cargos must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") +end +return self +end +function OPSTRANSPORT:GetRequiredCargos(TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +return TransportZoneCombo.RequiredCargos +end +function OPSTRANSPORT:SetRequiredCarriers(NcarriersMin,NcarriersMax) +self.nCarriersMin=NcarriersMin or 1 +self.nCarriersMax=NcarriersMax or self.nCarriersMin +if self.nCarriersMax0 then +self:ScheduleOnce(Delay,OPSTRANSPORT._DelCarrier,self,CarrierGroup) +else +if self:IsCarrier(CarrierGroup)then +for i=#self.carriers,1,-1 do +local carrier=self.carriers[i] +if carrier.groupname==CarrierGroup.groupname then +self:T(self.lid..string.format("Removing carrier %s",CarrierGroup.groupname)) +table.remove(self.carriers,i) +end +end +end +end +return self +end +function OPSTRANSPORT:_GetCarrierNames() +local names={} +for _,_carrier in pairs(self.carriers)do +local carrier=_carrier +if carrier:IsAlive()~=nil then +table.insert(names,carrier.groupname) +end +end +return names +end +function OPSTRANSPORT:GetCargoOpsGroups(Delivered,Carrier,TransportZoneCombo) +local cargos=self:GetCargos(TransportZoneCombo,Carrier,Delivered) +local opsgroups={} +for _,_cargo in pairs(cargos)do +local cargo=_cargo +if cargo.type=="OPSGROUP"then +if cargo.opsgroup and not(cargo.opsgroup:IsDead()or cargo.opsgroup:IsStopped())then +table.insert(opsgroups,cargo.opsgroup) +end +end +end +return opsgroups +end +function OPSTRANSPORT:GetCargoStorages(Delivered,Carrier,TransportZoneCombo) +local cargos=self:GetCargos(TransportZoneCombo,Carrier,Delivered) +local opsgroups={} +for _,_cargo in pairs(cargos)do +local cargo=_cargo +if cargo.type=="STORAGE"then +table.insert(opsgroups,cargo.storage) +end +end +return opsgroups +end +function OPSTRANSPORT:GetCarriers() +return self.carriers +end +function OPSTRANSPORT:GetCargos(TransportZoneCombo,Carrier,Delivered) +local tczs=self.tzCombos +if TransportZoneCombo then +tczs={TransportZoneCombo} +end +local cargos={} +for _,_tcz in pairs(tczs)do +local tcz=_tcz +for _,_cargo in pairs(tcz.Cargos)do +local cargo=_cargo +if Delivered==nil or cargo.delivered==Delivered then +if Carrier==nil or Carrier:CanCargo(cargo)then +table.insert(cargos,cargo) +end +end +end +end +return cargos +end +function OPSTRANSPORT:GetCargoTotalWeight(Cargo,IncludeReserved) +local weight=0 +if Cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then +weight=Cargo.opsgroup:GetWeightTotal(nil,IncludeReserved) +else +if type(Cargo.storage.cargoType)=="number"then +if IncludeReserved then +return Cargo.storage.cargoAmount+Cargo.storage.cargoReserved +else +return Cargo.storage.cargoAmount +end +else +if IncludeReserved then +return Cargo.storage.cargoAmount*100 +else +return(Cargo.storage.cargoAmount+Cargo.storage.cargoReserved)*100 +end +end +end +return weight +end +function OPSTRANSPORT:SetTime(ClockStart,ClockStop) +local Tnow=timer.getAbsTime() +local Tstart=Tnow+5 +if ClockStart and type(ClockStart)=="number"then +Tstart=Tnow+ClockStart +elseif ClockStart and type(ClockStart)=="string"then +Tstart=UTILS.ClockToSeconds(ClockStart) +end +local Tstop=nil +if ClockStop and type(ClockStop)=="number"then +Tstop=Tnow+ClockStop +elseif ClockStop and type(ClockStop)=="string"then +Tstop=UTILS.ClockToSeconds(ClockStop) +end +self.Tstart=Tstart +self.Tstop=Tstop +if Tstop then +self.duration=self.Tstop-self.Tstart +end +return self +end +function OPSTRANSPORT:SetPriority(Prio,Importance,Urgent) +self.prio=Prio or 50 +self.urgent=Urgent +self.importance=Importance +return self +end +function OPSTRANSPORT:SetVerbosity(Verbosity) +self.verbose=Verbosity or 0 +return self +end +function OPSTRANSPORT:AddConditionStart(ConditionFunction,...) +if ConditionFunction then +local condition={} +condition.func=ConditionFunction +condition.arg={} +if arg then +condition.arg=arg +end +table.insert(self.conditionStart,condition) +end +return self +end +function OPSTRANSPORT:AddPathTransport(PathGroup,Reversed,Radius,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +if type(PathGroup)=="string"then +PathGroup=GROUP:FindByName(PathGroup) +end +local path={} +path.category=PathGroup:GetCategory() +path.radius=Radius or 0 +path.waypoints=PathGroup:GetTaskRoute() +table.insert(TransportZoneCombo.TransportPaths,path) +return self +end +function OPSTRANSPORT:_GetPathTransport(Category,TransportZoneCombo) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +local pathsTransport=TransportZoneCombo.TransportPaths +if pathsTransport and#pathsTransport>0 then +local paths={} +for _,_path in pairs(pathsTransport)do +local path=_path +if path.category==Category then +table.insert(paths,path) +end +end +if#paths>0 then +local path=paths[math.random(#paths)] +return path +end +end +return nil +end +function OPSTRANSPORT:SetCarrierTransportStatus(CarrierGroup,Status) +local oldstatus=self:GetCarrierTransportStatus(CarrierGroup) +self:T(self.lid..string.format("New carrier transport status for %s: %s --> %s",CarrierGroup:GetName(),oldstatus,Status)) +self.carrierTransportStatus[CarrierGroup.groupname]=Status +return self +end +function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) +local status=self.carrierTransportStatus[CarrierGroup.groupname]or"unknown" +return status +end +function OPSTRANSPORT:GetUID() +return self.uid +end +function OPSTRANSPORT:GetNcargoDelivered() +return self.Ndelivered +end +function OPSTRANSPORT:GetNcargoTotal() +return self.Ncargo +end +function OPSTRANSPORT:GetNcarrier() +return self.Ncarrier +end +function OPSTRANSPORT:AddAsset(Asset,TransportZoneCombo) +self:T(self.lid..string.format("Adding asset carrier \"%s\" to transport",tostring(Asset.spawngroupname))) +self.assets=self.assets or{} +table.insert(self.assets,Asset) +return self +end +function OPSTRANSPORT:DelAsset(Asset) +for i,_asset in pairs(self.assets or{})do +local asset=_asset +if asset.uid==Asset.uid then +self:T(self.lid..string.format("Removing asset \"%s\" from transport",tostring(Asset.spawngroupname))) +table.remove(self.assets,i) +return self +end +end +return self +end +function OPSTRANSPORT:AddAssetCargo(Asset,TransportZoneCombo) +self:T(self.lid..string.format("Adding asset cargo \"%s\" to transport and TZC=%s",tostring(Asset.spawngroupname),TransportZoneCombo and TransportZoneCombo.uid or"N/A")) +self.assetsCargo=self.assetsCargo or{} +table.insert(self.assetsCargo,Asset) +TransportZoneCombo.assetsCargo=TransportZoneCombo.assetsCargo or{} +TransportZoneCombo.assetsCargo[Asset.spawngroupname]=Asset +return self +end +function OPSTRANSPORT:GetTZCofCargo(GroupName) +for _,_tzc in pairs(self.tzCombos)do +local tzc=_tzc +for _,_cargo in pairs(tzc.Cargos)do +local cargo=_cargo +if cargo.opsgroup:GetName()==GroupName then +return tzc +end +end +end +return nil +end +function OPSTRANSPORT:AddLegion(Legion) +self:T(self.lid..string.format("Adding legion %s",Legion.alias)) +table.insert(self.legions,Legion) +return self +end +function OPSTRANSPORT:RemoveLegion(Legion) +for i=#self.legions,1,-1 do +local legion=self.legions[i] +if legion.alias==Legion.alias then +self:T(self.lid..string.format("Removing legion %s",Legion.alias)) +table.remove(self.legions,i) +return self +end +end +self:E(self.lid..string.format("ERROR: Legion %s not found and could not be removed!",Legion.alias)) +return self +end +function OPSTRANSPORT:IsCarrier(CarrierGroup) +if CarrierGroup then +for _,_carrier in pairs(self.carriers)do +local carrier=_carrier +if carrier.groupname==CarrierGroup.groupname then +return true +end +end +end +return false +end +function OPSTRANSPORT:IsReadyToGo() +local text=self.lid.."Is ReadyToGo? " +local Tnow=timer.getAbsTime() +local gotzones=false +for _,_tz in pairs(self.tzCombos)do +local tz=_tz +if tz.PickupZone and tz.DeployZone then +gotzones=true +break +end +end +if not gotzones then +text=text.."No, pickup/deploy zone combo not yet defined!" +return false +end +if self.Tstart and Tnowself.Tstop then +text=text.."Nope, stop time already passed!" +self:T(text) +return false +end +local startme=self:EvalConditionsAll(self.conditionStart) +if not startme then +text=text..("No way, at least one start condition is not true!") +self:T(text) +return false +end +text=text.."Yes!" +self:T(text) +return true +end +function OPSTRANSPORT:SetLegionStatus(Legion,Status) +local status=self:GetLegionStatus(Legion) +self:T(self.lid..string.format("Setting LEGION %s to status %s-->%s",Legion.alias,tostring(status),tostring(Status))) +self.statusLegion[Legion.alias]=Status +return self +end +function OPSTRANSPORT:GetLegionStatus(Legion) +local status=self.statusLegion[Legion.alias]or"unknown" +return status +end +function OPSTRANSPORT:IsPlanned() +local is=self:is(OPSTRANSPORT.Status.PLANNED) +return is +end +function OPSTRANSPORT:IsQueued(Legion) +local is=self:is(OPSTRANSPORT.Status.QUEUED) +if Legion then +is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.QUEUED +end +return is +end +function OPSTRANSPORT:IsRequested(Legion) +local is=self:is(OPSTRANSPORT.Status.REQUESTED) +if Legion then +is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.REQUESTED +end +return is +end +function OPSTRANSPORT:IsScheduled() +local is=self:is(OPSTRANSPORT.Status.SCHEDULED) +return is +end +function OPSTRANSPORT:IsExecuting() +local is=self:is(OPSTRANSPORT.Status.EXECUTING) +return is +end +function OPSTRANSPORT:IsDelivered(Nmin) +local is=self:is(OPSTRANSPORT.Status.DELIVERED) +if is==false and Nmin and self.Ndelivered>=math.min(self.Ncargo,Nmin)then +is=true +end +return is +end +function OPSTRANSPORT:onafterStatusUpdate(From,Event,To) +local fsmstate=self:GetState() +if self.verbose>=1 then +local text=string.format("%s: Ncargo=%d/%d, Ncarrier=%d/%d, Nlegions=%d",fsmstate:upper(),self.Ncargo,self.Ndelivered,#self.carriers,self.Ncarrier,#self.legions) +if self.verbose>=2 then +for i,_tz in pairs(self.tzCombos)do +local tz=_tz +local pickupzone=tz.PickupZone and tz.PickupZone:GetName()or"Unknown" +local deployzone=tz.DeployZone and tz.DeployZone:GetName()or"Unknown" +text=text..string.format("\n[%d] %s --> %s: Ncarriers=%d, Ncargo=%d (%d)",i,pickupzone,deployzone,tz.Ncarriers,#tz.Cargos,tz.Ncargo) +end +end +if self.verbose>=3 then +text=text..string.format("\nCargos:") +for _,_cargo in pairs(self:GetCargos())do +local cargo=_cargo +if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then +local carrier=cargo.opsgroup:_GetMyCarrierElement() +local name=carrier and carrier.name or"none" +local cstate=carrier and carrier.status or"N/A" +text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s [UID=%s]", +cargo.opsgroup:GetName(),cargo.opsgroup.cargoStatus:upper(),cargo.opsgroup:GetState(),cargo.opsgroup:GetWeightTotal(),name,cstate,tostring(cargo.delivered),tostring(cargo.opsgroup.cargoTransportUID)) +else +local storage=cargo.storage +text=text..string.format("\n- storage type=%s: amount: total=%d loaded=%d, lost=%d, delivered=%d, delivered=%s [UID=%s]", +storage.cargoType,storage.cargoAmount,storage.cargoLoaded,storage.cargoLost,storage.cargoDelivered,tostring(cargo.delivered),tostring(cargo.uid)) +end +end +text=text..string.format("\nCarriers:") +for _,_carrier in pairs(self.carriers)do +local carrier=_carrier +text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]", +carrier:GetName(),carrier.carrierStatus:upper(),carrier:GetState(), +carrier:GetWeightCargo(nil,false),carrier:GetWeightCargo(),carrier:GetWeightCargoMax(), +carrier:GetFreeCargobay(nil,false),carrier:GetFreeCargobay(),carrier:GetFreeCargobayMax()) +end +end +self:I(self.lid..text) +end +self:_CheckDelivered() +if not self:IsDelivered()then +self:__StatusUpdate(-30) +end +end +function OPSTRANSPORT:IsCargoDelivered(GroupName) +for _,_cargo in pairs(self:GetCargos())do +local cargo=_cargo +if cargo.opsgroup:GetName()==GroupName then +return cargo.delivered +end +end +return nil +end +function OPSTRANSPORT:onafterPlanned(From,Event,To) +self:T(self.lid..string.format("New status: %s-->%s",From,To)) +end +function OPSTRANSPORT:onafterScheduled(From,Event,To) +self:T(self.lid..string.format("New status: %s-->%s",From,To)) +end +function OPSTRANSPORT:onafterExecuting(From,Event,To) +self:T(self.lid..string.format("New status: %s-->%s",From,To)) +end +function OPSTRANSPORT:onbeforeDelivered(From,Event,To) +if From==OPSTRANSPORT.Status.DELIVERED then +return false +end +return true +end +function OPSTRANSPORT:onafterDelivered(From,Event,To) +self:T(self.lid..string.format("New status: %s-->%s",From,To)) +for i=#self.carriers,1,-1 do +local carrier=self.carriers[i] +if self:GetCarrierTransportStatus(carrier)~=OPSTRANSPORT.Status.DELIVERED then +carrier:Delivered(self) +end +end +end +function OPSTRANSPORT:onafterLoaded(From,Event,To,OpsGroupCargo,OpsGroupCarrier,CarrierElement) +self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s",OpsGroupCargo:GetName(),tostring(CarrierElement.name))) +end +function OPSTRANSPORT:onafterUnloaded(From,Event,To,OpsGroupCargo,OpsGroupCarrier) +self:I(self.lid..string.format("Unloaded OPSGROUP %s",OpsGroupCargo:GetName())) +end +function OPSTRANSPORT:onafterDeadCarrierGroup(From,Event,To,OpsGroup) +self:I(self.lid..string.format("Carrier OPSGROUP %s dead!",OpsGroup:GetName())) +self.NcarrierDead=self.NcarrierDead+1 +self:_DelCarrier(OpsGroup) +if#self.carriers==0 then +self:DeadCarrierAll() +end +end +function OPSTRANSPORT:onafterDeadCarrierAll(From,Event,To) +self:I(self.lid..string.format("ALL Carrier OPSGROUPs are dead!")) +if self.opszone then +self:I(self.lid..string.format("Cancelling transport on CHIEF level")) +self.chief:TransportCancel(self) +else +self:_CheckDelivered() +if not self:IsDelivered()then +self:Planned() +end +end +end +function OPSTRANSPORT:onafterCancel(From,Event,To) +local Ngroups=#self.carriers +self:I(self.lid..string.format("CANCELLING transport in status %s. Will wait for %d carrier groups to report DONE before evaluation",self:GetState(),Ngroups)) +self.Tover=timer.getAbsTime() +if self.chief then +self:T(self.lid..string.format("CHIEF will cancel the transport. Will wait for mission DONE before evaluation!")) +self.chief:TransportCancel(self) +elseif self.commander then +self:T(self.lid..string.format("COMMANDER will cancel the transport. Will wait for transport DELIVERED before evaluation!")) +self.commander:TransportCancel(self) +elseif self.legions and#self.legions>0 then +for _,_legion in pairs(self.legions or{})do +local legion=_legion +self:T(self.lid..string.format("LEGION %s will cancel the transport. Will wait for transport DELIVERED before evaluation!",legion.alias)) +legion:TransportCancel(self) +end +else +self:T(self.lid..string.format("No legion, commander or chief. Attached OPS groups will cancel the transport on their own. Will wait for transport DELIVERED before evaluation!")) +for _,_carrier in pairs(self:GetCarriers())do +local carrier=_carrier +carrier:TransportCancel(self) +end +local cargos=self:GetCargoOpsGroups(false) +for _,_cargo in pairs(cargos)do +local cargo=_cargo +cargo:_DelMyLift(self) +end +end +if self:IsPlanned()or self:IsQueued()or self:IsRequested()or Ngroups==0 then +self:T(self.lid..string.format("Cancelled transport was in %s stage with %d carrier groups assigned and alive. Call it DELIVERED!",self:GetState(),Ngroups)) +self:Delivered() +end +end +function OPSTRANSPORT:_CheckDelivered() +if self.Ncargo>0 then +local done=true +local dead=true +for _,_cargo in pairs(self:GetCargos())do +local cargo=_cargo +if cargo.delivered then +dead=false +elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup==nil then +dead=false +elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsDestroyed()then +elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsDead()then +elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsStopped()then +dead=false +else +done=false +dead=false +end +end +if dead then +self:I(self.lid.."All cargo DEAD ==> Delivered!") +self:Delivered() +elseif done then +self:I(self.lid.."All cargo DONE ==> Delivered!") +self:Delivered() +end +end +end +function OPSTRANSPORT:_CheckRequiredCargos(TransportZoneCombo,CarrierGroup) +TransportZoneCombo=TransportZoneCombo or self.tzcDefault +local requiredCargos=TransportZoneCombo.Cargos +if TransportZoneCombo.RequiredCargos and#TransportZoneCombo.RequiredCargos>0 then +requiredCargos=TransportZoneCombo.RequiredCargos +else +requiredCargos={} +for _,_cargo in pairs(TransportZoneCombo.Cargos)do +local cargo=_cargo +table.insert(requiredCargos,cargo.opsgroup) +end +end +if requiredCargos==nil or#requiredCargos==0 then +return true +end +local carrierNames=self:_GetCarrierNames() +local weightmin=nil +for _,_cargo in pairs(requiredCargos)do +local cargo=_cargo +local isLoaded=cargo:IsLoaded(carrierNames) +if not isLoaded then +local weight=cargo:GetWeightTotal() +if weightmin==nil or weight=1 then +local dist=tz.PickupZone:Get2DDistance(vec2) +local ncarriers=0 +for _,_carrier in pairs(self.carriers)do +local carrier=_carrier +if carrier and carrier:IsAlive()and carrier.cargoTZC and carrier.cargoTZC.uid==tz.uid then +ncarriers=ncarriers+1 +end +end +local candidate={tzc=tz,distance=dist/1000,ncargo=ncargo,ncarriers=ncarriers} +candidate.penalty=penalty(candidate) +table.insert(candidates,candidate) +end +end +end +if#candidates>0 then +local function optTZC(candA,candB) +return candA.penalty=3 then +local text="TZC optimized" +for i,candidate in pairs(candidates)do +text=text..string.format("\n[%d] TPZ=%d, Ncarriers=%d, Ncargo=%d, Distance=%.1f km, PENALTY=%d",i,candidate.tzc.uid,candidate.ncarriers,candidate.ncargo,candidate.distance,candidate.penalty) +end +self:I(self.lid..text) +end +return candidates[1].tzc +else +self:T(self.lid..string.format("Could NOT find a pickup zone (with cargo) for carrier group %s",Carrier:GetName())) +end +return nil +end +function OPSTRANSPORT:_GetOpsGroupFromObject(Object) +local opsgroup=nil +if Object:IsInstanceOf("OPSGROUP")then +opsgroup=Object +elseif Object:IsInstanceOf("GROUP")then +opsgroup=_DATABASE:GetOpsGroup(Object) +if not opsgroup then +if Object:IsAir()then +opsgroup=FLIGHTGROUP:New(Object) +elseif Object:IsShip()then +opsgroup=NAVYGROUP:New(Object) +else +opsgroup=ARMYGROUP:New(Object) +end +end +else +self:E(self.lid.."ERROR: Object must be a GROUP or OPSGROUP object!") +return nil +end +return opsgroup +end +OPSZONE={ +ClassName="OPSZONE", +verbose=0, +Nred=0, +Nblu=0, +Nnut=0, +Ncoal={}, +Tred=0, +Tblu=0, +Tnut=0, +chiefs={}, +Missions={}, +UpdateSeconds=120, +} +OPSZONE.ZoneType={ +Circular="Circular", +Polygon="Polygon", +} +OPSZONE.version="0.6.2" +function OPSZONE:New(Zone,CoalitionOwner) +local self=BASE:Inherit(self,FSM:New()) +if Zone then +if type(Zone)=="string"then +local Name=Zone +Zone=ZONE:FindByName(Name) +if not Zone then +local airbase=AIRBASE:FindByName(Name) +if airbase then +Zone=ZONE_AIRBASE:New(Name,2000) +end +end +if not Zone then +self:E(string.format("ERROR: No ZONE or ZONE_AIRBASE found for name: %s",Name)) +return nil +end +end +else +self:E("ERROR: First parameter Zone is nil in OPSZONE:New(Zone) call!") +return nil +end +if Zone:IsInstanceOf("ZONE_AIRBASE")then +self.airbase=Zone._.ZoneAirbase +self.airbaseName=self.airbase:GetName() +self.zoneType=OPSZONE.ZoneType.Circular +self.zoneCircular=Zone +elseif Zone:IsInstanceOf("ZONE_RADIUS")then +self.zoneType=OPSZONE.ZoneType.Circular +self.zoneCircular=Zone +elseif Zone:IsInstanceOf("ZONE_POLYGON_BASE")then +self.zoneType=OPSZONE.ZoneType.Polygon +local zone=Zone +self.zoneCircular=zone:GetZoneRadius(nil,true) +else +self:E("ERROR: OPSZONE must be a SPHERICAL zone due to DCS restrictions!") +return nil +end +self.lid=string.format("OPSZONE %s | ",Zone:GetName()) +self.zone=Zone +self.zoneName=Zone:GetName() +self.zoneRadius=self.zoneCircular:GetRadius() +self.Missions={} +self.ScanUnitSet=SET_UNIT:New():FilterZones({Zone}) +self.ScanGroupSet=SET_GROUP:New():FilterZones({Zone}) +_DATABASE:AddOpsZone(self) +self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL +self.ownerPrevious=CoalitionOwner or coalition.side.NEUTRAL +self.isContested=false +self.Ncoal[coalition.side.BLUE]=0 +self.Ncoal[coalition.side.RED]=0 +self.Ncoal[coalition.side.NEUTRAL]=0 +if self.airbase then +self.ownerCurrent=self.airbase:GetCoalition() +self.ownerPrevious=self.airbase:GetCoalition() +end +self:SetObjectCategories() +self:SetUnitCategories() +self:SetDrawZone() +self:SetMarkZone(true) +self:SetCaptureTime() +self:SetCaptureNunits() +self:SetCaptureThreatlevel() +self.timerStatus=TIMER:New(OPSZONE.Status,self) +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Empty") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("*","Evaluated","*") +self:AddTransition("*","Captured","Guarded") +self:AddTransition("Empty","Guarded","Guarded") +self:AddTransition("*","Empty","Empty") +self:AddTransition("*","Attacked","Attacked") +self:AddTransition("*","Defeated","Guarded") +return self +end +function OPSZONE:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function OPSZONE:SetObjectCategories(Categories) +if Categories and type(Categories)~="table"then +Categories={Categories} +end +self.ObjectCategories=Categories or{Object.Category.UNIT,Object.Category.STATIC} +return self +end +function OPSZONE:SetUnitCategories(Categories) +if Categories and type(Categories)~="table"then +Categories={Categories} +end +self.UnitCategories=Categories or{Unit.Category.GROUND_UNIT} +return self +end +function OPSZONE:SetCaptureThreatlevel(Threatlevel) +self.threatlevelCapture=Threatlevel or 0 +return self +end +function OPSZONE:SetCaptureNunits(Nunits) +Nunits=Nunits or 1 +self.nunitsCapture=Nunits +return self +end +function OPSZONE:SetCaptureTime(Tcapture) +self.TminCaptured=Tcapture or 0 +return self +end +function OPSZONE:SetNeutralCanCapture(CanCapture) +self.neutralCanCapture=CanCapture +return self +end +function OPSZONE:SetDrawZone(Switch) +if Switch==false then +self.drawZone=false +else +self.drawZone=true +end +return self +end +function OPSZONE:SetDrawZoneForCoalition(Switch) +if Switch==true then +self.drawZoneForCoalition=true +else +self.drawZoneForCoalition=false +end +return self +end +function OPSZONE:SetMarkZone(Switch,ReadOnly) +if Switch then +self.markZone=true +local Coordinate=self:GetCoordinate() +self.markerText=self:_GetMarkerText() +self.marker=self.marker or MARKER:New(Coordinate,self.markerText) +if ReadOnly==false then +self.marker.readonly=false +else +self.marker.readonly=true +end +self.marker:ToAll() +else +if self.marker then +self.marker:Remove() +end +self.marker=nil +self.markZone=false +end +return self +end +function OPSZONE:GetOwner() +return self.ownerCurrent +end +function OPSZONE:GetOwnerName() +return UTILS.GetCoalitionName(self.ownerCurrent) +end +function OPSZONE:GetCoordinate() +local coordinate=self.zone:GetCoordinate() +return coordinate +end +function OPSZONE:GetScannedUnitSet() +return self.ScanUnitSet +end +function OPSZONE:GetScannedGroupSet() +return self.ScanGroupSet +end +function OPSZONE:GetRandomCoordinate(inner,outer,surfacetypes) +local zone=self:GetZone() +local coord=zone:GetRandomCoordinate(inner,outer,surfacetypes) +return coord +end +function OPSZONE:GetName() +return self.zoneName +end +function OPSZONE:GetZone() +return self.zone +end +function OPSZONE:GetPreviousOwner() +return self.ownerPrevious +end +function OPSZONE:GetAttackDuration() +if self:IsAttacked()and self.Tattacked then +local dT=timer.getAbsTime()-self.Tattacked +return dT +end +return nil +end +function OPSZONE:FindByName(ZoneName) +local Found=_DATABASE:FindOpsZone(ZoneName) +return Found +end +function OPSZONE:IsRed() +local is=self.ownerCurrent==coalition.side.RED +return is +end +function OPSZONE:IsBlue() +local is=self.ownerCurrent==coalition.side.BLUE +return is +end +function OPSZONE:IsNeutral() +local is=self.ownerCurrent==coalition.side.NEUTRAL +return is +end +function OPSZONE:IsCoalition(Coalition) +local is=self.ownerCurrent==Coalition +return is +end +function OPSZONE:IsStarted() +local is=not self:IsStopped() +return is +end +function OPSZONE:IsStopped() +local is=self:is("Stopped") +return is +end +function OPSZONE:IsGuarded() +local is=self:is("Guarded") +return is +end +function OPSZONE:IsEmpty() +local is=self:is("Empty") +return is +end +function OPSZONE:IsAttacked() +local is=self:is("Attacked") +return is +end +function OPSZONE:IsContested() +return self.isContested +end +function OPSZONE:IsStopped() +local is=self:is("Stopped") +return is +end +function OPSZONE:onafterStart(From,Event,To) +self:I(self.lid..string.format("Starting OPSZONE v%s",OPSZONE.version)) +self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status,self) +local EveryUpdateIn=self.UpdateSeconds or 120 +self.timerStatus:Start(1,EveryUpdateIn) +if self.airbase then +self:HandleEvent(EVENTS.BaseCaptured) +end +return self +end +function OPSZONE:onafterStop(From,Event,To) +self:I(self.lid..string.format("Stopping OPSZONE")) +self.timerStatus:Stop() +self.zone:UndrawZone() +if self.markZone then +self.marker:Remove() +end +self:UnHandleEvent(EVENTS.BaseCaptured) +self.CallScheduler:Clear() +if self.Scheduler then +self.Scheduler:Clear() +end +end +function OPSZONE:Status() +local fsmstate=self:GetState() +local contested=tostring(self:IsContested()) +if self.verbose>=1 then +local text=string.format("State %s: Owner %d (previous %d), contested=%s, Nunits: red=%d, blue=%d, neutral=%d",fsmstate,self.ownerCurrent,self.ownerPrevious,contested,self.Nred,self.Nblu,self.Nnut) +self:I(self.lid..text) +end +self:Scan() +self:EvaluateZone() +self:_UpdateMarker() +if self.zone.DrawID and not self.drawZone then +self.zone:UndrawZone() +end +end +function OPSZONE:onbeforeCaptured(From,Event,To,NewOwnerCoalition) +if self.ownerCurrent==NewOwnerCoalition then +self:T(self.lid.."") +end +return true +end +function OPSZONE:onafterCaptured(From,Event,To,NewOwnerCoalition) +self:T(self.lid..string.format("Zone captured by coalition=%d",NewOwnerCoalition)) +self.ownerPrevious=self.ownerCurrent +self.ownerCurrent=NewOwnerCoalition +if self.drawZone then +self.zone:UndrawZone() +local color=self:_GetZoneColor() +local coalition=nil +if self.drawZoneForCoalition then +coalition=self.ownerCurrent +end +self.zone:DrawZone(coalition,color,1.0,color,0.5) +end +for _,_chief in pairs(self.chiefs)do +local chief=_chief +if chief.coalition==self.ownerCurrent then +chief:ZoneCaptured(self) +else +chief:ZoneLost(self) +end +end +end +function OPSZONE:onafterEmpty(From,Event,To) +self:T(self.lid..string.format("Zone is empty EVENT")) +end +function OPSZONE:onafterAttacked(From,Event,To,AttackerCoalition) +self:T(self.lid..string.format("Zone is being attacked by coalition=%s!",tostring(AttackerCoalition))) +end +function OPSZONE:onafterDefeated(From,Event,To,DefeatedCoalition) +self:T(self.lid..string.format("Defeated attack on zone by coalition=%d",DefeatedCoalition)) +self.Tattacked=nil +end +function OPSZONE:onenterGuarded(From,Event,To) +if From~=To then +self:T(self.lid..string.format("Zone is guarded")) +self.Tattacked=nil +if self.drawZone then +self.zone:UndrawZone() +local color=self:_GetZoneColor() +local coalition=nil +if self.drawZoneForCoalition then +coalition=self.ownerCurrent +end +self.zone:DrawZone(coalition,color,1.0,color,0.5) +end +end +end +function OPSZONE:onenterAttacked(From,Event,To,AttackerCoalition) +if From~="Attacked"then +self:T(self.lid..string.format("Zone is Attacked")) +self.Tattacked=timer.getAbsTime() +if AttackerCoalition then +for _,_chief in pairs(self.chiefs)do +local chief=_chief +if chief.coalition~=AttackerCoalition then +chief:ZoneAttacked(self) +end +end +end +if self.drawZone then +self.zone:UndrawZone() +local color={1,204/255,204/255} +local coalition=nil +if self.drawZoneForCoalition then +coalition=self.ownerCurrent +end +self.zone:DrawZone(coalition,color,1.0,color,0.5) +end +self:_CleanMissionTable() +end +end +function OPSZONE:onenterEmpty(From,Event,To) +if From~=To then +self:T(self.lid..string.format("Zone is empty now")) +for _,_chief in pairs(self.chiefs)do +local chief=_chief +chief:ZoneEmpty(self) +end +if self.drawZone then +self.zone:UndrawZone() +local color=self:_GetZoneColor() +local coalition=nil +if self.drawZoneForCoalition then +coalition=self.ownerCurrent +end +self.zone:DrawZone(coalition,color,1.0,color,0.2) +end +end +end +function OPSZONE:Scan() +if self.verbose>=3 then +local text=string.format("Scanning zone %s R=%.1f m",self.zoneName,self.zoneRadius) +self:I(self.lid..text) +end +local SphereSearch={id=world.VolumeType.SPHERE,params={point=self.zone:GetVec3(),radius=self.zoneRadius}} +local Nred=0 +local Nblu=0 +local Nnut=0 +local Tred=0 +local Tblu=0 +local Tnut=0 +self.ScanGroupSet:Clear(false) +self.ScanUnitSet:Clear(false) +local function EvaluateZone(_ZoneObject) +local ZoneObject=_ZoneObject +if ZoneObject then +local ObjectCategory=Object.getCategory(ZoneObject) +if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive()then +local DCSUnit=ZoneObject +local function Included() +if not self.UnitCategories then +return true +else +local CategoryDCSUnit=ZoneObject:getDesc().category +for _,UnitCategory in pairs(self.UnitCategories)do +if UnitCategory==CategoryDCSUnit then +return true +end +end +end +return false +end +if Included()then +local Coalition=DCSUnit:getCoalition() +local tl=0 +local unit=UNIT:Find(DCSUnit) +if unit then +local inzone=true +if self.zoneType==OPSZONE.ZoneType.Polygon then +inzone=unit:IsInZone(self.zone) +end +if inzone then +tl=unit:GetThreatLevel() +self.ScanUnitSet:AddUnit(unit) +local group=unit:GetGroup() +if group then +self.ScanGroupSet:AddGroup(group,true) +end +if Coalition==coalition.side.RED then +Nred=Nred+1 +Tred=Tred+tl +elseif Coalition==coalition.side.BLUE then +Nblu=Nblu+1 +Tblu=Tblu+tl +elseif Coalition==coalition.side.NEUTRAL then +Nnut=Nnut+1 +Tnut=Tnut+tl +end +if self.verbose>=4 then +self:I(self.lid..string.format("Found unit %s (coalition=%d)",DCSUnit:getName(),Coalition)) +end +end +end +end +elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist()then +local DCSStatic=ZoneObject +local Coalition=DCSStatic:getCoalition() +local inzone=true +if self.zoneType==OPSZONE.ZoneType.Polygon then +local Vec3=DCSStatic:getPoint() +inzone=self.zone:IsVec3InZone(Vec3) +end +if inzone then +if Coalition==coalition.side.RED then +Nred=Nred+1 +elseif Coalition==coalition.side.BLUE then +Nblu=Nblu+1 +elseif Coalition==coalition.side.NEUTRAL then +Nnut=Nnut+1 +end +if self.verbose>=4 then +self:I(self.lid..string.format("Found static %s (coalition=%d)",DCSStatic:getName(),Coalition)) +end +end +elseif ObjectCategory==Object.Category.SCENERY then +local SceneryType=ZoneObject:getTypeName() +local SceneryName=ZoneObject:getName() +self:T2(self.lid..string.format("Found scenery type=%s, name=%s",SceneryType,SceneryName)) +end +end +return true +end +world.searchObjects(self.ObjectCategories,SphereSearch,EvaluateZone) +if self.verbose>=3 then +local text=string.format("Scan result Nred=%d, Nblue=%d, Nneutral=%d",Nred,Nblu,Nnut) +if self.verbose>=4 then +for _,_unit in pairs(self.ScanUnitSet:GetSet())do +local unit=_unit +text=text..string.format("\nUnit %s coalition=%s",unit:GetName(),unit:GetCoalitionName()) +end +for _,_group in pairs(self.ScanGroupSet:GetSet())do +local group=_group +text=text..string.format("\nGroup %s coalition=%s",group:GetName(),group:GetCoalitionName()) +end +end +self:I(self.lid..text) +end +self.Nred=Nred +self.Nblu=Nblu +self.Nnut=Nnut +self.Ncoal[coalition.side.BLUE]=Nblu +self.Ncoal[coalition.side.RED]=Nred +self.Ncoal[coalition.side.NEUTRAL]=Nnut +self.Tblu=Tblu +self.Tred=Tred +self.Tnut=Tnut +return self +end +function OPSZONE:EvaluateZone() +local Nred=self.Nred +local Nblu=self.Nblu +local Nnut=self.Nnut +local Tnow=timer.getAbsTime() +local function captured(coal) +if not self.airbase then +if not self.Tcaptured then +self.Tcaptured=Tnow +end +if Tnow-self.Tcaptured>=self.TminCaptured then +self:Captured(coal) +self.Tcaptured=nil +end +end +end +if self:IsRed()then +if Nred==0 then +if Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then +captured(coalition.side.BLUE) +elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then +captured(coalition.side.NEUTRAL) +end +else +if Nblu>0 then +if not self:IsAttacked()and self.Tblu>=self.threatlevelCapture then +self:Attacked(coalition.side.BLUE) +end +elseif Nblu==0 then +if self:IsAttacked()and self:IsContested()then +self:Defeated(coalition.side.BLUE) +elseif self:IsEmpty()then +self:Guarded() +end +end +end +if Nblu==0 then +self.isContested=false +else +self.isContested=true +end +elseif self:IsBlue()then +if Nblu==0 then +if Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then +captured(coalition.side.RED) +elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then +captured(coalition.side.NEUTRAL) +end +else +if Nred>0 then +if not self:IsAttacked()and self.Tred>=self.threatlevelCapture then +self:Attacked(coalition.side.RED) +end +elseif Nred==0 then +if self:IsAttacked()and self:IsContested()then +self:Defeated(coalition.side.RED) +elseif self:IsEmpty()then +self:Guarded() +end +end +end +if Nred==0 then +self.isContested=false +else +self.isContested=true +end +elseif self:IsNeutral()then +if Nred>0 and Nblu>0 then +self:T(self.lid.."FF neutrals left neutral zone and red and blue are present! What to do?") +if not self:IsAttacked()then +self:Attacked() +end +self.isContested=true +elseif Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then +captured(coalition.side.RED) +elseif Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then +captured(coalition.side.BLUE) +end +else +self:E(self.lid.."ERROR: Unknown coaliton!") +end +if Nblu==0 and Nred==0 and Nnut==0 and(not self:IsEmpty())then +self:Empty() +end +if self.airbase then +local airbasecoalition=self.airbase:GetCoalition() +if airbasecoalition~=self.ownerCurrent then +self:T(self.lid..string.format("Captured airbase %s: Coaltion %d-->%d",self.airbaseName,self.ownerCurrent,airbasecoalition)) +self:Captured(airbasecoalition) +end +end +self:Evaluated() +end +function OPSZONE:OnEventHit(EventData) +if self.HitsOn then +local UnitHit=EventData.TgtUnit +if UnitHit and UnitHit:IsInZone(self)and UnitHit:GetCoalition()==self.ownerCurrent then +self.HitTimeLast=timer.getTime() +if not self:IsAttacked()then +self:T3(self.lid.."Hit ==> Attack") +self:Attacked() +end +end +end +end +function OPSZONE:OnEventBaseCaptured(EventData) +if EventData and EventData.Place and self.airbase and self.airbaseName then +local airbase=EventData.Place +if EventData.PlaceName==self.airbaseName then +local CoalitionNew=airbase:GetCoalition() +self:I(self.lid..string.format("EVENT BASE CAPTURED: New coalition of airbase %s: %d [previous=%d]",self.airbaseName,CoalitionNew,self.ownerCurrent)) +if CoalitionNew~=self.ownerCurrent then +self:Captured(CoalitionNew) +end +end +end +end +function OPSZONE:_GetZoneColor() +local color={0,0,0} +if self.ownerCurrent==coalition.side.NEUTRAL then +color=self.ZoneOwnerNeutral or{1,1,1} +elseif self.ownerCurrent==coalition.side.BLUE then +color=self.ZoneOwnerBlue or{0,0,1} +elseif self.ownerCurrent==coalition.side.RED then +color=self.ZoneOwnerRed or{1,0,0} +else +end +return color +end +function OPSZONE:SetZoneColor(Neutral,Blue,Red) +self.ZoneOwnerNeutral=Neutral or{1,1,1} +self.ZoneOwnerBlue=Blue or{0,0,1} +self.ZoneOwnerRed=Red or{1,0,0} +return self +end +function OPSZONE:_UpdateMarker() +if self.markZone then +local text=self:_GetMarkerText() +if text~=self.markerText then +self.markerText=text +self.marker:UpdateText(self.markerText) +end +end +end +function OPSZONE:_GetMarkerText() +local owner=UTILS.GetCoalitionName(self.ownerCurrent) +local prevowner=UTILS.GetCoalitionName(self.ownerPrevious) +local text=string.format("%s [N=%d, TL=%d T=%d]:\nOwner=%s [%s]\nState=%s [Contested=%s]\nBlue=%d [TL=%d]\nRed=%d [TL=%d]\nNeutral=%d [TL=%d]", +self.zoneName,self.nunitsCapture or 0,self.threatlevelCapture or 0,self.TminCaptured or 0, +owner,prevowner,self:GetState(),tostring(self:IsContested()), +self.Nblu,self.Tblu,self.Nred,self.Tred,self.Nnut,self.Tnut) +return text +end +function OPSZONE:_AddChief(Chief) +table.insert(self.chiefs,Chief) +end +function OPSZONE:_AddMission(Coalition,Type,Auftrag) +local entry={} +entry.Coalition=Coalition or coalition.side.NEUTRAL +entry.Type=Type or"" +entry.Mission=Auftrag or nil +table.insert(self.Missions,entry) +return self +end +function OPSZONE:_GetMissions() +return self.Missions +end +function OPSZONE:_FindMissions(Coalition,Type) +local foundmissions={} +local found=false +for _,_entry in pairs(self.Missions)do +local entry=_entry +if entry.Coalition==Coalition and entry.Type==Type and entry.Mission and entry.Mission:IsNotOver()then +table.insert(foundmissions,entry.Mission) +found=true +end +end +return found,foundmissions +end +function OPSZONE:_CleanMissionTable() +local missions={} +for _,_entry in pairs(self.Missions)do +local entry=_entry +if entry.Mission and entry.Mission:IsNotOver()then +table.insert(missions,entry) +end +end +self.Missions=missions +return self +end +PLATOON={ +ClassName="PLATOON", +verbose=0, +weaponData={}, +} +PLATOON.version="0.1.0" +function PLATOON:New(TemplateGroupName,Ngroups,PlatoonName) +local self=BASE:Inherit(self,COHORT:New(TemplateGroupName,Ngroups,PlatoonName)) +self:AddMissionCapability(AUFTRAG.Type.NOTHING,50) +self.isGround=true +self.ammo=self:_CheckAmmo() +return self +end +function PLATOON:SetBrigade(Brigade) +self.legion=Brigade +return self +end +function PLATOON:GetBrigade() +return self.legion +end +function PLATOON:onafterStatus(From,Event,To) +if self.verbose>=1 then +local fsmstate=self:GetState() +local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName)or"N/A" +local skill=self.skill and tostring(self.skill)or"N/A" +local NassetsTot=#self.assets +local NassetsInS=self:CountAssets(true) +local NassetsQP=0;local NassetsP=0;local NassetsQ=0 +if self.legion then +NassetsQP,NassetsP,NassetsQ=self.legion:CountAssetsOnMission(nil,self) +end +local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]", +fsmstate,self.aircrafttype,callsign,skill,NassetsTot,NassetsInS,NassetsQP,NassetsP,NassetsQ) +self:T(self.lid..text) +if self.verbose>=3 and self.weaponData then +local text="Weapon Data:" +for bit,_weapondata in pairs(self.weaponData)do +local weapondata=_weapondata +text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km",bit,weapondata.RangeMin/1000,weapondata.RangeMax/1000) +end +self:I(self.lid..text) +end +self:_CheckAssetStatus() +end +if not self:IsStopped()then +self:__Status(-60) +end +end +do +_PlayerTaskNr=0 +PLAYERTASK={ +ClassName="PLAYERTASK", +verbose=false, +lid=nil, +PlayerTaskNr=nil, +Type=nil, +TTSType=nil, +Target=nil, +Clients=nil, +Repeat=false, +repeats=0, +RepeatNo=1, +TargetMarker=nil, +SmokeColor=nil, +FlareColor=nil, +conditionSuccess={}, +conditionFailure={}, +TaskController=nil, +timestamp=0, +lastsmoketime=0, +Freetext=nil, +FreetextTTS=nil, +TaskSubType=nil, +NextTaskSuccess={}, +NextTaskFailure={}, +FinalState="none", +PreviousCount=0, +} +PLAYERTASK.version="0.1.28" +function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) +local self=BASE:Inherit(self,FSM:New()) +self.Type=Type +self.Repeat=false +self.repeats=0 +self.RepeatNo=1 +self.Clients=FIFO:New() +self.TargetMarker=nil +self.SmokeColor=SMOKECOLOR.Red +self.conditionSuccess={} +self.conditionFailure={} +self.TaskController=nil +self.timestamp=timer.getAbsTime() +self.TTSType=TTSType or"close air support" +self.lastsmoketime=0 +if type(Repeat)=="boolean"and Repeat==true and type(Times)=="number"and Times>1 then +self.Repeat=true +self.RepeatNo=Times or 1 +end +_PlayerTaskNr=_PlayerTaskNr+1 +self.PlayerTaskNr=_PlayerTaskNr +self.lid=string.format("PlayerTask #%d %s | ",self.PlayerTaskNr,tostring(self.Type)) +if Target and Target.ClassName and Target.ClassName=="TARGET"then +self.Target=Target +elseif Target and Target.ClassName then +self.Target=TARGET:New(Target) +else +self:E(self.lid.."*** NO VALID TARGET!") +return self +end +self.PreviousCount=self.Target:CountTargets() +self:T(self.lid.."Created.") +self:SetStartState("Planned") +self:AddTransition("*","Planned","Planned") +self:AddTransition("*","Requested","Requested") +self:AddTransition("*","ClientAdded","*") +self:AddTransition("*","ClientRemoved","*") +self:AddTransition("*","Executing","Executing") +self:AddTransition("*","Progress","*") +self:AddTransition("*","Done","Done") +self:AddTransition("*","Cancel","Done") +self:AddTransition("*","Success","Done") +self:AddTransition("*","ClientAborted","*") +self:AddTransition("*","Failed","Failed") +self:AddTransition("*","Status","*") +self:AddTransition("*","Stop","Stopped") +self:__Status(-5) +return self +end +function PLAYERTASK:NewFromTarget(Target,Repeat,Times,TTSType) +return PLAYERTASK:New(self:_GetTaskTypeForTarget(Target),Target,Repeat,Times,TTSType) +end +function PLAYERTASK:_GetTaskTypeForTarget(Target) +local group=nil +local auftrag=nil +if Target:IsInstanceOf("GROUP")then +group=Target +elseif Target:IsInstanceOf("SET_GROUP")then +group=Target:GetFirst() +elseif Target:IsInstanceOf("UNIT")then +group=Target:GetGroup() +elseif Target:IsInstanceOf("SET_UNIT")then +group=Target:GetFirst():GetGroup() +elseif Target:IsInstanceOf("AIRBASE")then +auftrag=AUFTRAG.Type.BOMBRUNWAY +elseif Target:IsInstanceOf("STATIC") +or Target:IsInstanceOf("SET_STATIC") +or Target:IsInstanceOf("SCENERY") +or Target:IsInstanceOf("SET_SCENERY")then +auftrag=AUFTRAG.Type.BOMBING +elseif Target:IsInstanceOf("OPSZONE") +or Target:IsInstanceOf("SET_OPSZONE")then +auftrag=AUFTRAG.Type.CAPTUREZONE +end +if group then +local category=group:GetCategory() +local attribute=group:GetAttribute() +if(category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER) +and group:InAir()then +auftrag=AUFTRAG.Type.INTERCEPT +elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then +if attribute==GROUP.Attribute.GROUND_SAM +or attribute==GROUP.Attribute.GROUND_EWR then +auftrag=AUFTRAG.Type.SEAD +elseif attribute==GROUP.Attribute.GROUND_AAA +or attribute==GROUP.Attribute.GROUND_APC +or attribute==GROUP.Attribute.GROUND_IFV +or attribute==GROUP.Attribute.GROUND_TRUCK +or attribute==GROUP.Attribute.GROUND_TRAIN then +auftrag=AUFTRAG.Type.BAI +elseif attribute==GROUP.Attribute.GROUND_INFANTRY +or attribute==GROUP.Attribute.GROUND_ARTILLERY +or attribute==GROUP.Attribute.GROUND_TANK then +auftrag=AUFTRAG.Type.CAS +else +auftrag=AUFTRAG.Type.BAI +end +elseif category==Group.Category.SHIP then +auftrag=AUFTRAG.Type.ANTISHIP +else +self:T(self.lid.."ERROR: Unknown Group category!") +end +end +return auftrag +end +function PLAYERTASK:_CheckCaptureOpsZoneSuccess(OpsZone,CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone) +local isClientInZone=true +if CheckClientInZone then +isClientInZone=false +for _,client in ipairs(self:GetClientObjects())do +local clientCoord=client:GetCoordinate() +if OpsZone.zone:IsCoordinateInZone(clientCoord)then +isClientInZone=true +break +end +end +end +local isCaptureGroupInZone=false +OpsZone:GetScannedGroupSet():ForEachGroup(function(group) +if string.find(group:GetName(),CaptureSquadGroupNamePrefix)then +isCaptureGroupInZone=true +end +end) +return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone +end +function PLAYERTASK:CanJoinTask(Group,Client) +return true +end +function PLAYERTASK:_SetController(Controller) +self:T(self.lid.."_SetController") +self.TaskController=Controller +return self +end +function PLAYERTASK:SetCoalition(Coalition) +self:T(self.lid.."SetCoalition") +self.coalition=Coalition or coalition.side.BLUE +return self +end +function PLAYERTASK:GetCoalition() +self:T(self.lid.."GetCoalition") +return self.coalition +end +function PLAYERTASK:GetTarget() +self:T(self.lid.."GetTarget") +return self.Target +end +function PLAYERTASK:AddFreetext(Text) +self:T(self.lid.."AddFreetext") +self.Freetext=Text +return self +end +function PLAYERTASK:HasFreetext() +self:T(self.lid.."HasFreetext") +return self.Freetext~=nil and true or false +end +function PLAYERTASK:HasFreetextTTS() +self:T(self.lid.."HasFreetextTTS") +return self.FreetextTTS~=nil and true or false +end +function PLAYERTASK:SetSubType(Type) +self:T(self.lid.."AddSubType") +self.TaskSubType=Type +return self +end +function PLAYERTASK:GetSubType() +self:T(self.lid.."GetSubType") +return self.TaskSubType +end +function PLAYERTASK:GetFreetext() +self:T(self.lid.."GetFreetext") +return self.Freetext or self.FreetextTTS or"No Details" +end +function PLAYERTASK:AddFreetextTTS(TextTTS) +self:T(self.lid.."AddFreetextTTS") +self.FreetextTTS=TextTTS +return self +end +function PLAYERTASK:GetFreetextTTS() +self:T(self.lid.."GetFreetextTTS") +return self.FreetextTTS or self.Freetext or"No Details" +end +function PLAYERTASK:SetMenuName(Text) +self:T(self.lid.."SetMenuName") +self.Target.name=Text +return self +end +function PLAYERTASK:AddStaticObjectSuccessCondition() +local task=self +task:AddConditionSuccess( +function(target) +if target==nil then return false end +local isDead=false +if target:IsInstanceOf("STATIC") +or target:IsInstanceOf("SCENERY") +or target:IsInstanceOf("SET_SCENERY")then +isDead=(not target)or target:GetLife()<1 or target:GetLife()<0.2*target:GetLife0() +elseif target:IsInstanceOf("SET_STATIC")then +local deadCount=0 +target:ForEachStatic(function(static) +if static:GetLife()<1 or static:GetLife()<0.2*static:GetLife0()then +deadCount=deadCount+1 +end +end) +if deadCount==target:Count()then +isDead=true +end +end +return isDead +end,task:GetTarget() +) +return self +end +function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone) +local task=self +task:AddConditionSuccess( +function(target) +if target:IsInstanceOf("OPSZONE")then +return task:_CheckCaptureOpsZoneSuccess(target,CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone or true) +elseif target:IsInstanceOf("SET_OPSZONE")then +local successes=0 +local isClientInZone=false +target:ForEachZone(function(opszone) +if task:_CheckCaptureOpsZoneSuccess(opszone,CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone or true)then +successes=successes+1 +end +for _,client in ipairs(task:GetClientObjects())do +local clientCoord=client:GetCoordinate() +if opszone.zone:IsCoordinateInZone(clientCoord)then +isClientInZone=true +break +end +end +end) +return successes==target:Count()and isClientInZone +end +return false +end,task:GetTarget() +) +return self +end +function PLAYERTASK:AddReconSuccessCondition(MinDistance) +local task=self +task:AddConditionSuccess( +function(target) +local targetLocation=target:GetCoordinate() +local minD=MinDistance or UTILS.NMToMeters(5) +for _,client in ipairs(task:GetClientObjects())do +local clientCoord=client:GetCoordinate() +local distance=clientCoord:Get2DDistance(targetLocation) +local isLos=land.isVisible(clientCoord:GetVec3(),targetLocation:GetVec3()) +if distance0 and timer.getTime()-task.StartTime>TimeLimit +end) +return self +end +function PLAYERTASK:AddNextTaskAfterSuccess(Task) +self:T(self.lid.."AddNextTaskAfterSuccess") +table.insert(self.NextTaskSuccess,Task) +return self +end +function PLAYERTASK:AddNextTaskAfterFailure(Task) +self:T(self.lid.."AddNextTaskAfterFailure") +table.insert(self.NextTaskFailure,Task) +return self +end +function PLAYERTASK:IsDone() +self:T(self.lid.."IsDone?") +local IsDone=false +local state=self:GetState() +if state=="Done"or state=="Stopped"then +IsDone=true +end +return IsDone +end +function PLAYERTASK:IsNotDone() +self:T(self.lid.."IsNotDone?") +local IsNotDone=not self:IsDone() +return IsNotDone +end +function PLAYERTASK:HasClients() +self:T(self.lid.."HasClients?") +local hasclients=self:CountClients()>0 and true or false +return hasclients +end +function PLAYERTASK:GetClients() +self:T(self.lid.."GetClients") +local clientlist=self.Clients:GetIDStackSorted()or{} +local count=self.Clients:Count() +return clientlist,count +end +function PLAYERTASK:GetClientObjects() +self:T(self.lid.."GetClientObjects") +local clientlist=self.Clients:GetDataTable()or{} +local count=self.Clients:Count() +return clientlist,count +end +function PLAYERTASK:CountClients() +self:T(self.lid.."CountClients") +return self.Clients:Count() +end +function PLAYERTASK:HasPlayerName(Name) +self:T(self.lid.."HasPlayerName?") +return self.Clients:HasUniqueID(Name) +end +function PLAYERTASK:AddClient(Client) +self:T(self.lid.."AddClient") +local name=Client:GetPlayerName() +if not self.Clients:HasUniqueID(name)then +self.Clients:Push(Client,name) +self:__ClientAdded(-2,Client) +end +if self.TaskController and self.TaskController.Scoring then +self.TaskController.Scoring:_AddPlayerFromUnit(Client) +end +return self +end +function PLAYERTASK:RemoveClient(Client,Name) +self:T(self.lid.."RemoveClient") +local name=Name or Client:GetPlayerName() +if self.Clients:HasUniqueID(name)then +self.Clients:PullByID(name) +if self.verbose then +self.Clients:Flush() +end +self:__ClientRemoved(-2,Client) +if self.Clients:Count()==0 then +self:__Failed(-1) +end +end +return self +end +function PLAYERTASK:ClientAbort(Client) +self:T(self.lid.."ClientAbort") +if Client and Client:IsAlive()then +self:RemoveClient(Client) +self:__ClientAborted(-1,Client) +return self +else +if self.Clients:Count()==0 then +self:__Failed(-1) +end +end +return self +end +function PLAYERTASK:MarkTargetOnF10Map(Text,Coalition,ReadOnly) +self:T(self.lid.."MarkTargetOnF10Map") +if self.Target then +local coordinate=self.Target:GetCoordinate() +if coordinate then +if self.TargetMarker then +self.TargetMarker:Remove() +end +local text=Text or("Target of "..self.lid) +self.TargetMarker=MARKER:New(coordinate,text) +if ReadOnly then +self.TargetMarker:ReadOnly() +end +if Coalition then +self.TargetMarker:ToCoalition(Coalition) +else +self.TargetMarker:ToAll() +end +end +end +return self +end +function PLAYERTASK:SmokeTarget(Color) +self:T(self.lid.."SmokeTarget") +local color=Color or SMOKECOLOR.Red +if not self.lastsmoketime then self.lastsmoketime=0 end +local TDiff=timer.getAbsTime()-self.lastsmoketime +if self.Target and TDiff>299 then +local coordinate=self.Target:GetAverageCoordinate() +if coordinate then +coordinate:Smoke(color) +self.lastsmoketime=timer.getAbsTime() +end +end +return self +end +function PLAYERTASK:FlareTarget(Color) +self:T(self.lid.."SmokeTarget") +local color=Color or FLARECOLOR.Red +if self.Target then +local coordinate=self.Target:GetAverageCoordinate() +if coordinate then +coordinate:Flare(color,0) +end +end +return self +end +function PLAYERTASK:IlluminateTarget(Power,Height) +self:T(self.lid.."IlluminateTarget") +local Power=Power or 1000 +local Height=Height or 150 +if self.Target then +local coordinate=self.Target:GetAverageCoordinate() +if coordinate then +local bcoord=COORDINATE:NewFromVec2(coordinate:GetVec2(),Height) +bcoord:IlluminationBomb(Power) +end +end +return self +end +function PLAYERTASK:AddConditionSuccess(ConditionFunction,...) +local condition={} +condition.func=ConditionFunction +condition.arg={} +if arg then +condition.arg=arg +end +table.insert(self.conditionSuccess,condition) +return self +end +function PLAYERTASK:AddConditionFailure(ConditionFunction,...) +local condition={} +condition.func=ConditionFunction +condition.arg={} +if arg then +condition.arg=arg +end +table.insert(self.conditionFailure,condition) +return self +end +function PLAYERTASK:_EvalConditionsAny(Conditions) +for _,_condition in pairs(Conditions or{})do +local condition=_condition +local istrue=condition.func(unpack(condition.arg)) +if istrue then +return true +end +end +return false +end +function PLAYERTASK:onafterStatus(From,Event,To) +self:T({From,Event,To}) +self:T(self.lid.."onafterStatus") +local status=self:GetState() +if status=="Stopped"then return self end +if self.TargetMarker then +local coordinate=self.Target:GetCoordinate() +self.TargetMarker:UpdateCoordinate(coordinate,0.5) +end +local targetdead=false +if self.Type~=AUFTRAG.Type.CTLD and self.Type~=AUFTRAG.Type.CSAR then +if self.Target:IsDead()or self.Target:IsDestroyed()or self.Target:CountTargets()==0 then +targetdead=true +self:__Success(-2) +status="Success" +return self +end +end +local clientsalive=false +if status=="Executing"then +local ClientTable=self.Clients:GetDataTable() +for _,_client in pairs(ClientTable)do +local client=_client +if client:IsAlive()then +clientsalive=true +end +end +if status=="Executing"and(not clientsalive)and(not targetdead)then +self:__Failed(-2) +status="Failed" +end +end +if status~="Done"and status~="Stopped"then +local successCondition=self:_EvalConditionsAny(self.conditionSuccess) +local failureCondition=self:_EvalConditionsAny(self.conditionFailure) +if failureCondition and status~="Failed"then +self:__Failed(-2) +status="Failed" +elseif successCondition then +self:__Success(-2) +status="Success" +end +if status~="Failed"and status~="Success"then +local targetcount=self.Target:CountTargets() +if targetcount0 then +for _,_client in pairs(clients)do +self.TaskController.Scoring:AddGoalScore(_client,self.Type,nil,10) +end +end +end +self.TaskController:__TaskProgress(-1,self,TargetCount) +end +return self +end +function PLAYERTASK:onafterPlanned(From,Event,To) +self:T({From,Event,To}) +self.timestamp=timer.getAbsTime() +return self +end +function PLAYERTASK:onafterRequested(From,Event,To) +self:T({From,Event,To}) +self.timestamp=timer.getAbsTime() +return self +end +function PLAYERTASK:onafterExecuting(From,Event,To) +self:T({From,Event,To}) +self.timestamp=timer.getAbsTime() +return self +end +function PLAYERTASK:onafterStop(From,Event,To) +self:T({From,Event,To}) +self.timestamp=timer.getAbsTime() +return self +end +function PLAYERTASK:onafterClientAdded(From,Event,To,Client) +self:T({From,Event,To}) +if Client and self.verbose then +local text=string.format("Player %s joined task %03d!",Client:GetPlayerName()or"Generic",self.PlayerTaskNr) +self:T(self.lid..text) +end +self.timestamp=timer.getAbsTime() +return self +end +function PLAYERTASK:onafterDone(From,Event,To) +self:T({From,Event,To}) +if self.TaskController then +self.TaskController:__TaskDone(-1,self) +end +self.timestamp=timer.getAbsTime() +self:__Stop(-1) +return self +end +function PLAYERTASK:onafterCancel(From,Event,To) +self:T({From,Event,To}) +if self.TaskController then +self.TaskController:__TaskCancelled(-1,self) +end +self.timestamp=timer.getAbsTime() +self.FinalState="Cancelled" +self:__Done(-1) +return self +end +function PLAYERTASK:onafterSuccess(From,Event,To) +self:T({From,Event,To}) +if self.TaskController then +self.TaskController:__TaskSuccess(-1,self) +end +if self.TargetMarker then +self.TargetMarker:Remove() +end +if self.TaskController and self.TaskController.Scoring then +local clients,count=self:GetClientObjects() +if count>0 then +for _,_client in pairs(clients)do +local auftrag=self:GetSubType() +self.TaskController.Scoring:AddGoalScore(_client,self.Type,nil,self.TaskController.Scores[self.Type]) +end +end +end +self.timestamp=timer.getAbsTime() +self.FinalState="Success" +self:__Done(-1) +return self +end +function PLAYERTASK:onafterFailed(From,Event,To) +self:T({From,Event,To}) +self.repeats=self.repeats+1 +if self.Repeat and(self.repeats<=self.RepeatNo)then +if self.TaskController then +self.TaskController:__TaskRepeatOnFailed(-1,self) +end +self:__Planned(-1) +return self +else +if self.TargetMarker then +self.TargetMarker:Remove() +end +self.FinalState="Failed" +if self.TaskController then +self.TaskController:__TaskFailed(-1,self) +end +self:__Done(-1.5) +end +if self.TaskController.Scoring then +local clients,count=self:GetClientObjects() +if count>0 then +for _,_client in pairs(clients)do +local auftrag=self:GetSubType() +self.TaskController.Scoring:AddGoalScore(_client,self.Type,nil,-self.TaskController.Scores[self.Type]) +end +end +end +self.timestamp=timer.getAbsTime() +return self +end +end +do +PLAYERTASKCONTROLLER={ +ClassName="PLAYERTASKCONTROLLER", +verbose=false, +lid=nil, +TargetQueue=nil, +ClientSet=nil, +UseGroupNames=true, +PlayerMenu={}, +usecluster=false, +MenuName=nil, +ClusterRadius=0.5, +NoScreenOutput=false, +TargetRadius=500, +UseWhiteList=false, +WhiteList={}, +gettext=nil, +locale="en", +precisionbombing=false, +taskinfomenu=false, +activehasinfomenu=false, +MarkerReadOnly=false, +customcallsigns={}, +ShortCallsign=true, +Keepnumber=false, +CallsignTranslations=nil, +PlayerFlashMenu={}, +PlayerJoinMenu={}, +PlayerInfoMenu={}, +PlayerMenuTag={}, +noflaresmokemenu=false, +illumenu=false, +TransmitOnlyWithPlayers=true, +buddylasing=false, +PlayerRecce=nil, +Coalition=nil, +MenuParent=nil, +ShowMagnetic=true, +InfoHasLLDDM=false, +InfoHasCoordinate=false, +UseTypeNames=false, +Scoring=nil, +MenuNoTask=nil, +InformationMenu=false, +TaskInfoDuration=30, +} +PLAYERTASKCONTROLLER.Type={ +A2A="Air-To-Air", +A2G="Air-To-Ground", +A2S="Air-To-Sea", +A2GS="Air-To-Ground-Sea", +} +AUFTRAG.Type.PRECISIONBOMBING="Precision Bombing" +AUFTRAG.Type.CTLD="Combat Transport" +AUFTRAG.Type.CSAR="Combat Rescue" +AUFTRAG.Type.CONQUER="Conquer" +PLAYERTASKCONTROLLER.Scores={ +[AUFTRAG.Type.PRECISIONBOMBING]=100, +[AUFTRAG.Type.CTLD]=100, +[AUFTRAG.Type.CSAR]=100, +[AUFTRAG.Type.INTERCEPT]=100, +[AUFTRAG.Type.ANTISHIP]=100, +[AUFTRAG.Type.CAS]=100, +[AUFTRAG.Type.BAI]=100, +[AUFTRAG.Type.SEAD]=100, +[AUFTRAG.Type.BOMBING]=100, +[AUFTRAG.Type.BOMBRUNWAY]=100, +[AUFTRAG.Type.CONQUER]=100, +[AUFTRAG.Type.RECON]=100, +[AUFTRAG.Type.ESCORT]=100, +[AUFTRAG.Type.CAP]=100, +[AUFTRAG.Type.CAPTUREZONE]=100, +} +PLAYERTASKCONTROLLER.SeadAttributes={ +SAM=GROUP.Attribute.GROUND_SAM, +AAA=GROUP.Attribute.GROUND_AAA, +EWR=GROUP.Attribute.GROUND_EWR, +} +PLAYERTASKCONTROLLER.Messages={ +EN={ +TASKABORT="Task aborted!", +NOACTIVETASK="No active task!", +FREQUENCIES="frequencies ", +FREQUENCY="frequency %.3f", +BROADCAST="%s, %s, switch to %s for task assignment!", +CASTTS="close air support", +SEADTTS="suppress air defense", +BOMBTTS="bombing", +PRECBOMBTTS="precision bombing", +BAITTS="battle field air interdiction", +ANTISHIPTTS="anti-ship", +INTERCEPTTS="intercept", +BOMBRUNWAYTTS="bomb runway", +HAVEACTIVETASK="You already have one active task! Complete it first!", +PILOTJOINEDTASK="%s, %s. You have been assigned %s task %03d", +TASKNAME="%s Task ID %03d", +TASKNAMETTS="%s Task ID %03d", +THREATHIGH="high", +THREATMEDIUM="medium", +THREATLOW="low", +THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +ELEVATION="\nTarget Elevation: %s %s", +METER="meter", +FEET="feet", +THREATTEXTTTS="%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.", +MARKTASK="%s, %s, copy, task %03d location marked on map!", +SMOKETASK="%s, %s, copy, task %03d location smoked!", +FLARETASK="%s, %s, copy, task %03d location illuminated!", +ABORTTASK="All stations, %s, %s has aborted %s task %03d!", +UNKNOWN="Unknown", +MENUTASKING=" Tasking ", +MENUACTIVE="Active Task", +MENUINFO="Info", +MENUMARK="Mark on map", +MENUSMOKE="Smoke", +MENUFLARE="Flare", +MENUILLU="Illuminate", +MENUABORT="Abort", +MENUJOIN="Join Task", +MENUTASKINFO="Task Info", +MENUTASKNO="TaskNo", +MENUNOTASKS="Currently no tasks available.", +TASKCANCELLED="Task #%03d %s is cancelled!", +TASKCANCELLEDTTS="%s, task %03d %s is cancelled!", +TASKSUCCESS="Task #%03d %s completed successfully!", +TASKSUCCESSTTS="%s, task %03d %s completed successfully!", +TASKFAILED="Task #%03d %s was a failure!", +TASKFAILEDTTS="%s, task %03d %s was a failure!", +TASKFAILEDREPLAN="Task #%03d %s available for reassignment!", +TASKFAILEDREPLANTTS="%s, task %03d %s vailable for reassignment!", +TASKADDED="%s has a new %s task available!", +PILOTS="\nPilot(s): ", +PILOTSTTS=". Pilot(s): ", +YES="Yes", +NO="No", +NONE="None", +POINTEROVERTARGET="%s, %s, pointer in reach for task %03d, lasing!", +POINTERTARGETREPORT="\nPointer in reach: %s\nLasing: %s", +RECCETARGETREPORT="\nRecce %s in reach: %s\nLasing: %s", +POINTERTARGETLASINGTTS=". Pointer in reach and lasing.", +TARGET="Target", +FLASHON="%s - Flashing directions is now ON!", +FLASHOFF="%s - Flashing directions is now OFF!", +FLASHMENU="Flash Directions Switch", +BRIEFING="Briefing", +TARGETLOCATION="Target location", +COORDINATE="Coordinate", +INFANTRY="Infantry", +TECHNICAL="Technical", +ARTILLERY="Artillery", +TANKS="Tanks", +AIRDEFENSE="Airdefense", +SAM="SAM", +GROUP="Group", +UNARMEDSHIP="Merchant", +LIGHTARMEDSHIP="Light Boat", +CORVETTE="Corvette", +FRIGATE="Frigate", +CRUISER="Cruiser", +DESTROYER="Destroyer", +CARRIER="Aircraft Carrier", +RADIOS="Radios", +}, +DE={ +TASKABORT="Auftrag abgebrochen!", +NOACTIVETASK="Kein aktiver Auftrag!", +FREQUENCIES="Frequenzen ", +FREQUENCY="Frequenz %.3f", +BROADCAST="%s, %s, Radio %s für Aufgabenzuteilung!", +CASTTS="Nahbereichsunterstützung", +SEADTTS="Luftabwehr ausschalten", +BOMBTTS="Bombardieren", +PRECBOMBTTS="Präzisionsbombardieren", +BAITTS="Luftunterstützung", +ANTISHIPTTS="Anti-Schiff", +INTERCEPTTS="Abfangen", +BOMBRUNWAYTTS="Startbahn Bombardieren", +HAVEACTIVETASK="Du hast einen aktiven Auftrag! Beende ihn zuerst!", +PILOTJOINEDTASK="%s, %s hat Auftrag %s %03d angenommen", +TASKNAME="%s Auftrag ID %03d", +TASKNAMETTS="%s Auftrag ID %03d", +THREATHIGH="hoch", +THREATMEDIUM="mittel", +THREATLOW="niedrig", +THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +ELEVATION="\nZiel Höhe: %s %s", +METER="Meter", +FEET="Fuss", +THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", +SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", +ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", +UNKNOWN="Unbekannt", +MENUTASKING=" Aufträge ", +MENUACTIVE="Aktiver Auftrag", +MENUINFO="Information", +MENUMARK="Kartenmarkierung", +MENUSMOKE="Rauchgranate", +MENUFLARE="Leuchtgranate", +MENUILLU="Feldbeleuchtung", +MENUABORT="Abbrechen", +MENUJOIN="Auftrag annehmen", +MENUTASKINFO="Auftrag Briefing", +MENUTASKNO="AuftragsNr", +MENUNOTASKS="Momentan keine Aufträge verfügbar.", +TASKCANCELLED="Auftrag #%03d %s wurde beendet!", +TASKCANCELLEDTTS="%s, Auftrag %03d %s wurde beendet!", +TASKSUCCESS="Auftrag #%03d %s erfolgreich!", +TASKSUCCESSTTS="%s, Auftrag %03d %s erfolgreich!", +TASKFAILED="Auftrag #%03d %s gescheitert!", +TASKFAILEDTTS="%s, Auftrag %03d %s gescheitert!", +TASKFAILEDREPLAN="Auftrag #%03d %s gescheitert! Neuplanung!", +TASKFAILEDREPLANTTS="%s, Auftrag %03d %s gescheitert! Neuplanung!", +TASKADDED="%s hat einen neuen Auftrag %s erstellt!", +PILOTS="\nPilot(en): ", +PILOTSTTS=". Pilot(en): ", +YES="Ja", +NO="Nein", +NONE="Keine", +POINTEROVERTARGET="%s, %s, Marker im Zielbereich für %03d, Laser an!", +POINTERTARGETREPORT="\nMarker im Zielbereich: %s\nLaser an: %s", +RECCETARGETREPORT="\nSpäher % im Zielbereich: %s\nLasing: %s", +POINTERTARGETLASINGTTS=". Marker im Zielbereich, Laser is an.", +TARGET="Ziel", +FLASHON="%s - Richtungsangaben einblenden ist EIN!", +FLASHOFF="%s - Richtungsangaben einblenden ist AUS!", +FLASHMENU="Richtungsangaben Schalter", +BRIEFING="Briefing", +TARGETLOCATION="Zielposition", +COORDINATE="Koordinate", +INFANTRY="Infantrie", +TECHNICAL="Technische", +ARTILLERY="Artillerie", +TANKS="Panzer", +AIRDEFENSE="Flak", +SAM="Luftabwehr", +GROUP="Einheit", +UNARMEDSHIP="Handelsschiff", +LIGHTARMEDSHIP="Tender", +CORVETTE="Korvette", +FRIGATE="Fregatte", +CRUISER="Kreuzer", +DESTROYER="Zerstörer", +CARRIER="Flugzeugträger", +RADIOS="Frequenzen", +}, +} +PLAYERTASKCONTROLLER.version="0.1.70" +function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) +local self=BASE:Inherit(self,FSM:New()) +self.Name=Name or"CentCom" +self.Coalition=Coalition or coalition.side.BLUE +self.CoalitionName=UTILS.GetCoalitionName(Coalition) +self.Type=Type or PLAYERTASKCONTROLLER.Type.A2G +self.usecluster=false +if self.Type==PLAYERTASKCONTROLLER.Type.A2A then +self.usecluster=true +end +self.ClusterRadius=0.5 +self.TargetRadius=500 +self.ClientFilter=ClientFilter +self.TargetQueue=FIFO:New() +self.TaskQueue=FIFO:New() +self.TasksPerPlayer=FIFO:New() +self.PrecisionTasks=FIFO:New() +self.LasingDroneSet=SET_OPSGROUP:New() +self.FlashPlayer={} +self.AllowFlash=false +self.lasttaskcount=0 +self.taskinfomenu=false +self.activehasinfomenu=false +self.MenuName=nil +self.menuitemlimit=6 +self.holdmenutime=30 +self.MarkerReadOnly=false +self.repeatonfailed=true +self.repeattimes=5 +self.UseGroupNames=true +self.customcallsigns={} +self.ShortCallsign=true +self.Keepnumber=false +self.CallsignTranslations=nil +self.noflaresmokemenu=false +self.illumenu=false +self.ShowMagnetic=true +self.UseTypeNames=false +self.InformationMenu=false +self.TaskInfoDuration=30 +self.IsClientSet=false +if ClientFilter and type(ClientFilter)=="table"and ClientFilter.ClassName and ClientFilter.ClassName=="SET_CLIENT"then +self.ClientSet=ClientFilter +self.IsClientSet=true +end +if ClientFilter and not self.IsClientSet then +self.ClientSet=SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterPrefixes(ClientFilter):FilterStart() +elseif not self.IsClientSet then +self.ClientSet=SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterStart() +end +self.ActiveClientSet=SET_CLIENT:New() +self.lid=string.format("PlayerTaskController %s %s | ",self.Name,tostring(self.Type)) +self:_InitLocalization() +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","TaskAdded","*") +self:AddTransition("*","TaskDone","*") +self:AddTransition("*","TaskCancelled","*") +self:AddTransition("*","TaskSuccess","*") +self:AddTransition("*","TaskFailed","*") +self:AddTransition("*","TaskProgress","*") +self:AddTransition("*","TaskTargetSmoked","*") +self:AddTransition("*","TaskTargetFlared","*") +self:AddTransition("*","TaskTargetIlluminated","*") +self:AddTransition("*","TaskRepeatOnFailed","*") +self:AddTransition("*","PlayerJoinedTask","*") +self:AddTransition("*","PlayerAbortedTask","*") +self:AddTransition("*","Stop","Stopped") +self:__Start(2) +local starttime=math.random(5,10) +self:__Status(starttime) +self:I(self.lid..self.version.." Started.") +return self +end +function PLAYERTASKCONTROLLER:EnableScoring(Scoring) +self.Scoring=Scoring or SCORING:New(self.Name) +return self +end +function PLAYERTASKCONTROLLER:DisableScoring() +self.Scoring=nil +return self +end +function PLAYERTASKCONTROLLER:_InitLocalization() +self:T(self.lid.."_InitLocalization") +self.gettext=TEXTANDSOUND:New("PLAYERTASKCONTROLLER","en") +self.locale="en" +for locale,table in pairs(self.Messages)do +local Locale=string.lower(tostring(locale)) +self:T("**** Adding locale: "..Locale) +for ID,Text in pairs(table)do +self:T(string.format('Adding ID %s',tostring(ID))) +self.gettext:AddEntry(Locale,tostring(ID),Text) +end +end +return self +end +function PLAYERTASKCONTROLLER:SetEnableUseTypeNames() +self:T(self.lid.."SetEnableUseTypeNames") +self.UseTypeNames=true +return self +end +function PLAYERTASKCONTROLLER:SetDisableUseTypeNames() +self:T(self.lid.."SetDisableUseTypeNames") +self.UseTypeNames=false +return self +end +function PLAYERTASKCONTROLLER:SetAllowFlashDirection(OnOff) +self:T(self.lid.."SetAllowFlashDirection") +self.AllowFlash=OnOff +return self +end +function PLAYERTASKCONTROLLER:SetShowRadioInfoMenu(OnOff) +self:T(self.lid.."SetAllowRadioInfoMenu") +self.InformationMenu=OnOff +return self +end +function PLAYERTASKCONTROLLER:SetDisableSmokeFlareTask() +self:T(self.lid.."SetDisableSmokeFlareTask") +self.noflaresmokemenu=true +return self +end +function PLAYERTASKCONTROLLER:SetTransmitOnlyWithPlayers(Switch) +self.TransmitOnlyWithPlayers=Switch +if self.SRSQueue then +self.SRSQueue:SetTransmitOnlyWithPlayers(Switch) +end +return self +end +function PLAYERTASKCONTROLLER:SetEnableSmokeFlareTask() +self:T(self.lid.."SetEnableSmokeFlareTask") +self.noflaresmokemenu=false +return self +end +function PLAYERTASKCONTROLLER:SetEnableIlluminateTask() +self:T(self.lid.."SetEnableSmokeFlareTask") +self.illumenu=true +return self +end +function PLAYERTASKCONTROLLER:SetDisableIlluminateTask() +self:T(self.lid.."SetDisableIlluminateTask") +self.illumenu=false +return self +end +function PLAYERTASKCONTROLLER:SetInfoShowsCoordinate(OnOff,LLDDM) +self:T(self.lid.."SetInfoShowsCoordinate") +self.InfoHasCoordinate=OnOff +self.InfoHasLLDDM=LLDDM +return self +end +function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) +if not ShortCallsign or ShortCallsign==false then +self.ShortCallsign=false +else +self.ShortCallsign=true +end +self.Keepnumber=Keepnumber or false +self.CallsignTranslations=CallsignTranslations +self.CallsignCustomFunc=CallsignCustomFunc +self.CallsignCustomArgs=arg or{} +return self +end +function PLAYERTASKCONTROLLER:_GetTextForSpeech(text) +self:T(self.lid.."_GetTextForSpeech") +text=string.gsub(text,"%d","%1 ") +text=string.gsub(text,"^%s*","") +text=string.gsub(text,"%s*$","") +text=string.gsub(text," "," ") +return text +end +function PLAYERTASKCONTROLLER:SetTaskRepetition(OnOff,Repeats) +self:T(self.lid.."SetTaskRepetition") +if OnOff then +self.repeatonfailed=true +self.repeattimes=Repeats or 5 +else +self.repeatonfailed=false +self.repeattimes=Repeats or 5 +end +return self +end +function PLAYERTASKCONTROLLER:SetBriefingDuration(Seconds) +self:T(self.lid.."SetBriefingDuration") +self.TaskInfoDuration=Seconds or 30 +return self +end +function PLAYERTASKCONTROLLER:_SendMessageToClients(Text,Seconds) +self:T(self.lid.."_SendMessageToClients") +local seconds=Seconds or 10 +self.ClientSet:ForEachClient( +function(Client) +if Client~=nil and Client:IsActive()then +local m=MESSAGE:New(Text,seconds,"Tasking"):ToClient(Client) +end +end +) +return self +end +function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) +self:T(self.lid.."EnablePrecisionBombing") +if not self.LasingDroneSet then +self.LasingDroneSet=SET_OPSGROUP:New() +end +local LasingDrone +if FlightGroup then +if FlightGroup.ClassName and(FlightGroup.ClassName=="FLIGHTGROUP"or FlightGroup.ClassName=="ARMYGROUP")then +LasingDrone=FlightGroup +self.precisionbombing=true +LasingDrone.playertask={} +LasingDrone.playertask.id=0 +LasingDrone.playertask.busy=false +LasingDrone.playertask.lasercode=LaserCode or 1688 +LasingDrone:SetLaser(LasingDrone.playertask.lasercode) +LasingDrone.playertask.template=LasingDrone:_GetTemplate(true) +LasingDrone.playertask.alt=Alt or 10000 +LasingDrone.playertask.speed=Speed or 120 +LasingDrone.playertask.maxtravel=UTILS.NMToMeters(MaxTravelDist or 50) +if LasingDrone:IsFlightgroup()then +local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.Coalition)) +if HoldingPoint then BullsCoordinate=HoldingPoint end +local Orbit=AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,Alt,Speed) +Orbit:SetMissionAltitude(Alt) +LasingDrone:AddMission(Orbit) +elseif LasingDrone:IsArmygroup()then +local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.Coalition)) +if HoldingPoint then BullsCoordinate=HoldingPoint end +local Orbit=AUFTRAG:NewONGUARD(BullsCoordinate) +LasingDrone:AddMission(Orbit) +end +self.LasingDroneSet:AddObject(FlightGroup) +elseif FlightGroup.ClassName and(FlightGroup.ClassName=="SET_OPSGROUP")then +FlightGroup:ForEachGroup( +function(group) +self:EnablePrecisionBombing(group,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) +end +) +else +self:E(self.lid.."No OPSGROUP/SET_OPSGROUP object passed or object is not alive!") +end +else +self.autolase=nil +self.precisionbombing=false +end +return self +end +function PLAYERTASKCONTROLLER:AddPrecisionBombingOpsGroup(FlightGroup,LaserCode,HoldingPoint,Alt,Speed) +self:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed) +return self +end +function PLAYERTASKCONTROLLER:EnableBuddyLasing(Recce) +self:T(self.lid.."EnableBuddyLasing") +self.buddylasing=true +self.PlayerRecce=Recce +return self +end +function PLAYERTASKCONTROLLER:DisableBuddyLasing() +self:T(self.lid.."DisableBuddyLasing") +self.buddylasing=false +return self +end +function PLAYERTASKCONTROLLER:EnableMarkerOps(Tag) +self:T(self.lid.."EnableMarkerOps") +local tag=Tag or"TASK" +local MarkerOps=MARKEROPS_BASE:New(tag,{"Name","Text"},true) +local function Handler(Keywords,Coord,Text) +if self.verbose then +local m=MESSAGE:New(string.format("Target added from marker at: %s",Coord:ToStringA2G(nil,nil,self.ShowMagnetic)),15,"INFO"):ToAll() +local m=MESSAGE:New(string.format("Text: %s",Text),15,"INFO"):ToAll() +end +local menuname=string.match(Text,"Name=(.+),") +local freetext=string.match(Text,"Text=(.+)") +if menuname then +Coord.menuname=menuname +if freetext then +Coord.freetext=freetext +end +end +self:AddTarget(Coord) +end +function MarkerOps:OnAfterMarkAdded(From,Event,To,Text,Keywords,Coord) +Handler(Keywords,Coord,Text) +end +function MarkerOps:OnAfterMarkChanged(From,Event,To,Text,Keywords,Coord) +Handler(Keywords,Coord,Text) +end +self.MarkerOps=MarkerOps +return self +end +function PLAYERTASKCONTROLLER:_GetPlayerName(Client) +self:T(self.lid.."_GetPlayerName") +local playername=Client:GetPlayerName() +local ttsplayername=nil +if not self.customcallsigns[playername]then +local playergroup=Client:GetGroup() +if playergroup~=nil then +ttsplayername=playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local newplayername=self:_GetTextForSpeech(ttsplayername) +self.customcallsigns[playername]=newplayername +ttsplayername=newplayername +end +else +ttsplayername=self.customcallsigns[playername] +end +return playername,ttsplayername +end +function PLAYERTASKCONTROLLER:DisablePrecisionBombing(FlightGroup,LaserCode) +self:T(self.lid.."DisablePrecisionBombing") +self.autolase=nil +self.precisionbombing=false +return self +end +function PLAYERTASKCONTROLLER:EnableTaskInfoMenu() +self:T(self.lid.."EnableTaskInfoMenu") +self.taskinfomenu=true +return self +end +function PLAYERTASKCONTROLLER:DisableTaskInfoMenu() +self:T(self.lid.."DisableTaskInfoMenu") +self.taskinfomenu=false +return self +end +function PLAYERTASKCONTROLLER:SetMenuOptions(InfoMenu,ItemLimit,HoldTime) +self:T(self.lid.."SetMenuOptions") +self.activehasinfomenu=InfoMenu or false +if self.activehasinfomenu then +self:EnableTaskInfoMenu() +end +self.menuitemlimit=ItemLimit+1 or 6 +self.holdmenutime=HoldTime or 30 +return self +end +function PLAYERTASKCONTROLLER:SetMarkerReadOnly() +self:T(self.lid.."SetMarkerReadOnly") +self.MarkerReadOnly=true +return self +end +function PLAYERTASKCONTROLLER:SetMarkerDeleteable() +self:T(self.lid.."SetMarkerDeleteable") +self.MarkerReadOnly=false +return self +end +function PLAYERTASKCONTROLLER:_EventHandler(EventData) +self:T(self.lid.."_EventHandler: "..EventData.id) +if EventData.id==EVENTS.UnitLost or EventData.id==EVENTS.PlayerLeaveUnit or EventData.id==EVENTS.Ejection or EventData.id==EVENTS.Crash or EventData.id==EVENTS.PilotDead then +if EventData.IniPlayerName then +self:T(self.lid.."Event for player: "..EventData.IniPlayerName) +local text="" +if self.TasksPerPlayer:HasUniqueID(EventData.IniPlayerName)then +local task=self.TasksPerPlayer:PullByID(EventData.IniPlayerName) +local Client=_DATABASE:FindClient(EventData.IniPlayerName) +if Client then +task:RemoveClient(Client) +text=self.gettext:GetEntry("TASKABORT",self.locale) +self.ActiveTaskMenuTemplate:ResetMenu(Client) +self.JoinTaskMenuTemplate:ResetMenu(Client) +else +task:RemoveClient(nil,EventData.IniPlayerName) +text=self.gettext:GetEntry("TASKABORT",self.locale) +end +else +text=self.gettext:GetEntry("NOACTIVETASK",self.locale) +end +self:T(self.lid..text) +end +elseif EventData.id==EVENTS.PlayerEnterAircraft and EventData.IniCoalition==self.Coalition then +if EventData.IniPlayerName and EventData.IniGroup then +if self.IsClientSet and(not self.ClientSet:IsIncludeObject(CLIENT:FindByName(EventData.IniUnitName)))then +self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName) +return self +end +self:T(self.lid.."Event for player: "..EventData.IniPlayerName) +if self.UseSRS then +local frequency=self.Frequency +local freqtext="" +if type(frequency)=="table"then +freqtext=self.gettext:GetEntry("FREQUENCIES",self.locale) +freqtext=freqtext..table.concat(frequency,", ") +else +local freqt=self.gettext:GetEntry("FREQUENCY",self.locale) +freqtext=string.format(freqt,frequency) +end +local modulation=self.Modulation +if type(modulation)=="table"then modulation=modulation[1]end +modulation=UTILS.GetModulationName(modulation) +local switchtext=self.gettext:GetEntry("BROADCAST",self.locale) +local playername=EventData.IniPlayerName +if EventData.IniGroup then +if self.customcallsigns[playername]then +self.customcallsigns[playername]=nil +end +playername=EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +end +playername=self:_GetTextForSpeech(playername) +local text=string.format(switchtext,playername,self.MenuName or self.Name,freqtext) +self.SRSQueue:NewTransmission(text,nil,self.SRS,timer.getAbsTime()+60,2,{EventData.IniGroup},text,30,self.BCFrequency,self.BCModulation) +end +if EventData.IniPlayerName then +local player=_DATABASE:FindClient(EventData.IniUnitName) +self:_SwitchMenuForClient(player,"Info") +end +end +end +return self +end +function PLAYERTASKCONTROLLER:SetLocale(Locale) +self:T(self.lid.."SetLocale") +self.locale=Locale or"en" +return self +end +function PLAYERTASKCONTROLLER:SuppressScreenOutput(OnOff) +self:T(self.lid.."SuppressScreenOutput") +self.NoScreenOutput=OnOff or false +return self +end +function PLAYERTASKCONTROLLER:SetTargetRadius(Radius) +self:T(self.lid.."SetTargetRadius") +self.TargetRadius=Radius or 500 +return self +end +function PLAYERTASKCONTROLLER:SetClusterRadius(Radius) +self:T(self.lid.."SetClusterRadius") +self.ClusterRadius=Radius or 0.5 +self.usecluster=true +return self +end +function PLAYERTASKCONTROLLER:CancelTask(Task) +self:T(self.lid.."CancelTask") +Task:__Cancel(-1) +return self +end +function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) +self:T(self.lid.."SwitchUseGroupNames") +if OnOff then +self.UseGroupNames=true +else +self.UseGroupNames=false +end +return self +end +function PLAYERTASKCONTROLLER:SwitchMagenticAngles(OnOff) +self:T(self.lid.."SwitchMagenticAngles") +if OnOff then +self.ShowMagnetic=true +else +self.ShowMagnetic=false +end +return self +end +function PLAYERTASKCONTROLLER:_GetAvailableTaskTypes() +self:T(self.lid.."_GetAvailableTaskTypes") +local tasktypes={} +self.TaskQueue:ForEach( +function(Task) +local task=Task +local type=Task.Type +tasktypes[type]={} +end +) +return tasktypes +end +function PLAYERTASKCONTROLLER:_GetTasksPerType() +self:T(self.lid.."_GetTasksPerType") +local tasktypes=self:_GetAvailableTaskTypes() +local datatable=self.TaskQueue:GetDataTable() +local threattable={} +for _,_task in pairs(datatable)do +local task=_task +local threat=task.Target:GetThreatLevelMax() +if not task:IsDone()then +threattable[#threattable+1]={task=task,threat=threat} +end +end +table.sort(threattable,function(k1,k2)return k1.threat>k2.threat end) +for _id,_data in pairs(threattable)do +local threat=_data.threat +local task=_data.task +local type=task.Type +local name=task.Target:GetName() +if not task:IsDone()then +table.insert(tasktypes[type],task) +end +end +return tasktypes +end +function PLAYERTASKCONTROLLER:_CheckTargetQueue() +self:T(self.lid.."_CheckTargetQueue") +if self.TargetQueue:Count()>0 then +local object=self.TargetQueue:Pull() +local target=TARGET:New(object) +if object.menuname then +target.menuname=object.menuname +if object.freetext then +target.freetext=object.freetext +end +end +if object:IsInstanceOf("UNIT")or object:IsInstanceOf("GROUP")then +if self.UseTypeNames and object:IsGround()then +local threat=object:GetThreatLevel() +local typekey="INFANTRY" +if threat==0 or threat==2 then +typekey="TECHNICAL" +elseif threat==3 then +typekey="ARTILLERY" +elseif threat==4 or threat==5 then +typekey="TANKS" +elseif threat==6 or threat==7 then +typekey="AIRDEFENSE" +elseif threat>=8 then +typekey="SAM" +end +local typename=self.gettext:GetEntry(typekey,self.locale) +local gname=self.gettext:GetEntry("GROUP",self.locale) +target.TypeName=string.format("%s %s",typename,gname) +end +if self.UseTypeNames and object:IsShip()then +local threat=object:GetThreatLevel() +local typekey="UNARMEDSHIP" +if threat==1 then +typekey="LIGHTARMEDSHIP" +elseif threat==2 then +typekey="CORVETTE" +elseif threat==3 or threat==4 then +typekey="FRIGATE" +elseif threat==5 or threat==6 then +typekey="CRUISER" +elseif threat==7 or threat==8 then +typekey="DESTROYER" +elseif threat>=9 then +typekey="CARRIER" +end +local typename=self.gettext:GetEntry(typekey,self.locale) +target.TypeName=typename +end +end +self:_AddTask(target) +end +return self +end +function PLAYERTASKCONTROLLER:_CheckTaskQueue() +self:T(self.lid.."_CheckTaskQueue") +if self.TaskQueue:Count()>0 then +local tasks=self.TaskQueue:GetIDStack() +for _id,_entry in pairs(tasks)do +local data=_entry.data +self:T("Looking at Task: "..data.PlayerTaskNr.." Type: "..data.Type.." State: "..data:GetState()) +if data:GetState()=="Done"or data:GetState()=="Stopped"then +local task=self.TaskQueue:ReadByID(_id) +local clientsattask=task.Clients:GetIDStackSorted() +for _,_id in pairs(clientsattask)do +self:T("*****Removing player ".._id) +self.TasksPerPlayer:PullByID(_id) +end +local clients=task:GetClientObjects() +for _,client in pairs(clients)do +self:_RemoveMenuEntriesForTask(task,client) +end +for _,client in pairs(clients)do +self:_SwitchMenuForClient(client,"Info",5) +end +local nexttasks={} +if task.FinalState=="Success"then +nexttasks=task.NextTaskSuccess +elseif task.FinalState=="Failed"then +nexttasks=task.NextTaskFailure +end +local clientlist,count=task:GetClientObjects() +if count>0 then +for _,_client in pairs(clientlist)do +local client=_client +local group=client:GetGroup() +for _,task in pairs(nexttasks)do +self:_JoinTask(task,true,group,client) +end +end +end +local TNow=timer.getAbsTime() +if TNow-task.timestamp>5 then +self:_RemoveMenuEntriesForTask(task) +local task=self.TaskQueue:PullByID(_id) +task=nil +end +end +end +end +return self +end +function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() +self:T(self.lid.."_CheckPrecisionTasks") +self:T({count=self.PrecisionTasks:Count(),enabled=self.precisionbombing}) +if self.PrecisionTasks:Count()>0 and self.precisionbombing then +self.LasingDroneSet:ForEachGroup( +function(LasingDrone) +if not LasingDrone or LasingDrone:IsDead()then +self:E(self.lid.."Lasing drone is dead ... creating a new one!") +if LasingDrone then +LasingDrone:_Respawn(1,nil,true) +else +end +end +end +) +local function SelectDrone(coord) +local selected=nil +local mindist=math.huge +local dist=math.huge +self.LasingDroneSet:ForEachGroup( +function(grp) +if grp.playertask and(not grp.playertask.busy)then +local gc=grp:GetCoordinate() +if coord and gc then +dist=coord:Get2DDistance(gc) +end +if dist2 and threat<7 then +type=AUFTRAG.Type.PRECISIONBOMBING +ttstype=self.gettext:GetEntry("PRECBOMBTTS",self.locale) +end +end +elseif cat==TARGET.Category.NAVAL then +type=AUFTRAG.Type.ANTISHIP +ttstype=self.gettext:GetEntry("ANTISHIPTTS",self.locale) +elseif cat==TARGET.Category.AIRCRAFT then +type=AUFTRAG.Type.INTERCEPT +ttstype=self.gettext:GetEntry("INTERCEPTTS",self.locale) +elseif cat==TARGET.Category.AIRBASE then +type=AUFTRAG.Type.BOMBRUNWAY +ttstype=self.gettext:GetEntry("BOMBRUNWAYTTS",self.locale) +elseif cat==TARGET.Category.COORDINATE or cat==TARGET.Category.ZONE then +local zone=Target:GetObject() +if cat==TARGET.Category.COORDINATE then +zone=ZONE_RADIUS:New("TargetZone-"..math.random(1,10000),Target:GetVec2(),self.TargetRadius) +end +local enemies=self.CoalitionName=="Blue"and"red"or"blue" +local enemysetg=SET_GROUP:New():FilterCoalitions(enemies):FilterCategoryGround():FilterActive(true):FilterZones({zone}):FilterOnce() +local enemysets=SET_STATIC:New():FilterCoalitions(enemies):FilterZones({zone}):FilterOnce() +local countg=enemysetg:Count() +local counts=enemysets:Count() +if countg>0 then +if Target.menuname then +enemysetg.menuname=Target.menuname +if Target.freetext then +enemysetg.freetext=Target.freetext +end +end +self:AddTarget(enemysetg) +end +if counts>0 then +if Target.menuname then +enemysets.menuname=Target.menuname +if Target.freetext then +enemysets.freetext=Target.freetext +end +end +self:AddTarget(enemysets) +end +return self +end +if self.UseWhiteList then +if not self:_CheckTaskTypeAllowed(type)then +return self +end +end +if self.UseBlackList then +if self:_CheckTaskTypeDisallowed(type)then +return self +end +end +local task=PLAYERTASK:New(type,Target,self.repeatonfailed,self.repeattimes,ttstype) +if Target.menuname then +task:SetMenuName(Target.menuname) +if Target.freetext then +task:AddFreetext(Target.freetext) +end +end +task.coalition=self.Coalition +task.TypeName=Target.TypeName +if type==AUFTRAG.Type.BOMBRUNWAY then +task:HandleEvent(EVENTS.Shot) +function task:OnEventShot(EventData) +local data=EventData +local wcat=Object.getCategory(data.Weapon) +local coord=data.IniUnit:GetCoordinate()or data.IniGroup:GetCoordinate() +local vec2=coord:GetVec2()or{x=0,y=0} +local coal=data.IniCoalition +local afbzone=AIRBASE:FindByName(Target:GetName()):GetZone() +local runways=AIRBASE:FindByName(Target:GetName()):GetRunways()or{} +local inrunwayzone=false +for _,_runway in pairs(runways)do +local runway=_runway +if runway.zone:IsVec2InZone(vec2)then +inrunwayzone=true +end +end +local inzone=afbzone:IsVec2InZone(vec2) +if coal==task.coalition and(wcat==2 or wcat==3)and(inrunwayzone or inzone)then +task:__Success(-20) +end +end +end +task:_SetController(self) +self.TaskQueue:Push(task) +self:__TaskAdded(10,task) +return self +end +function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter) +self:T(self.lid.."AddPlayerTaskToQueue") +if PlayerTask and PlayerTask.ClassName and PlayerTask.ClassName=="PLAYERTASK"then +if TaskFilter then +if self.UseWhiteList and(not self:_CheckTaskTypeAllowed(PlayerTask.Type))then +return self +end +if self.UseBlackList and self:_CheckTaskTypeDisallowed(PlayerTask.Type)then +return self +end +end +PlayerTask:_SetController(self) +PlayerTask:SetCoalition(self.Coalition) +self.TaskQueue:Push(PlayerTask) +if not Silent then +self:__TaskAdded(10,PlayerTask) +end +else +self:E(self.lid.."***** NO valid PAYERTASK object sent!") +end +return self +end +function PLAYERTASKCONTROLLER:CanJoinTask(Task,Group,Client) +return true +end +function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client) +self:T({Force,Group,Client}) +self:T(self.lid.."_JoinTask") +if not self:CanJoinTask(Task,Group,Client)then +return self +end +if not Task:CanJoinTask(Group,Client)then +return self +end +local force=false +if type(Force)=="boolean"then +force=Force +end +local playername,ttsplayername=self:_GetPlayerName(Client) +if self.TasksPerPlayer:HasUniqueID(playername)and not force then +if not self.NoScreenOutput then +local text=self.gettext:GetEntry("HAVEACTIVETASK",self.locale) +local m=MESSAGE:New(text,"10","Tasking"):ToClient(Client) +end +return self +end +local taskstate=Task:GetState() +if not Task:IsDone()then +if taskstate~="Executing"then +Task:__Requested(-1) +Task:__Executing(-2) +end +Task:AddClient(Client) +local joined=self.gettext:GetEntry("PILOTJOINEDTASK",self.locale) +local text=string.format(joined,ttsplayername,self.MenuName or self.Name,Task.TTSType,Task.PlayerTaskNr) +self:T(self.lid..text) +if not self.NoScreenOutput then +self:_SendMessageToClients(text) +end +if self.UseSRS then +self:T(self.lid..text) +self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) +end +self.TasksPerPlayer:Push(Task,playername) +self:__PlayerJoinedTask(1,Group,Client,Task) +self:_SwitchMenuForClient(Client,"Active",1) +end +if Task.Type==AUFTRAG.Type.PRECISIONBOMBING then +if not self.PrecisionTasks:HasUniqueID(Task.PlayerTaskNr)then +self.PrecisionTasks:Push(Task,Task.PlayerTaskNr) +end +end +return self +end +function PLAYERTASKCONTROLLER:_SwitchFlashing(Group,Client) +self:T(self.lid.."_SwitchFlashing") +local playername,ttsplayername=self:_GetPlayerName(Client) +if(not self.FlashPlayer[playername])or(self.FlashPlayer[playername]==false)then +self.FlashPlayer[playername]=Client +local flashtext=self.gettext:GetEntry("FLASHON",self.locale) +local text=string.format(flashtext,ttsplayername) +local m=MESSAGE:New(text,10,"Tasking"):ToClient(Client) +else +self.FlashPlayer[playername]=false +local flashtext=self.gettext:GetEntry("FLASHOFF",self.locale) +local text=string.format(flashtext,ttsplayername) +local m=MESSAGE:New(text,10,"Tasking"):ToClient(Client) +end +return self +end +function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group,Client) +self:T(self.lid.."_ShowRadioInfo") +local playername,ttsplayername=self:_GetPlayerName(Client) +if self.UseSRS then +local frequency=self.Frequency +local freqtext="" +if type(frequency)=="table"then +freqtext=self.gettext:GetEntry("FREQUENCIES",self.locale) +freqtext=freqtext..table.concat(frequency,", ") +else +local freqt=self.gettext:GetEntry("FREQUENCY",self.locale) +freqtext=string.format(freqt,frequency) +end +local switchtext=self.gettext:GetEntry("BROADCAST",self.locale) +playername=ttsplayername or self:_GetTextForSpeech(playername) +local text=string.format(switchtext,playername,self.MenuName or self.Name,freqtext) +self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2,{Group},text,30,self.BCFrequency,self.BCModulation) +end +return self +end +function PLAYERTASKCONTROLLER:_FlashInfo() +self:T(self.lid.."_FlashInfo") +for _playername,_client in pairs(self.FlashPlayer)do +if _client and _client:IsAlive()then +if self.TasksPerPlayer:HasUniqueID(_playername)then +local task=self.TasksPerPlayer:ReadByID(_playername) +local Coordinate=task.Target:GetCoordinate() +local CoordText="" +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then +CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic) +else +CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic) +end +local targettxt=self.gettext:GetEntry("TARGET",self.locale) +local text="Target: "..CoordText +local m=MESSAGE:New(text,10,"Tasking"):ToClient(_client) +end +end +end +return self +end +function PLAYERTASKCONTROLLER:_FindLasingDroneForTaskID(ID) +local drone=nil +self.LasingDroneSet:ForEachGroup( +function(grp) +if grp and grp:IsAlive()and grp.playertask and grp.playertask.id and grp.playertask.id==ID then +drone=grp +end +end +) +return drone +end +function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task,Group,Client) +self:T(self.lid.."_ActiveTaskInfo") +local playername,ttsplayername=self:_GetPlayerName(Client) +local text="" +local textTTS="" +local task=nil +if type(Task)~="string"then +task=Task +end +if self.TasksPerPlayer:HasUniqueID(playername)or task then +local task=task or self.TasksPerPlayer:ReadByID(playername) +local tname=self.gettext:GetEntry("TASKNAME",self.locale) +local ttsname=self.gettext:GetEntry("TASKNAMETTS",self.locale) +local taskname=string.format(tname,task.Type,task.PlayerTaskNr) +local ttstaskname=string.format(ttsname,task.TTSType,task.PlayerTaskNr) +local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0) +local Elevation=Coordinate:GetLandHeight()or 0 +local CoordText="" +local CoordTextLLDM=nil +local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr) +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then +CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) +else +CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) +end +local ThreatLevel=task.Target:GetThreatLevelMax() +local ThreatLevelText=self.gettext:GetEntry("THREATHIGH",self.locale) +if ThreatLevel>3 and ThreatLevel<8 then +ThreatLevelText=self.gettext:GetEntry("THREATMEDIUM",self.locale) +elseif ThreatLevel<=3 then +ThreatLevelText=self.gettext:GetEntry("THREATLOW",self.locale) +end +local targets=task.Target:CountTargets()or 0 +local clientlist,clientcount=task:GetClients() +local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel +local ThreatLocaleText=self.gettext:GetEntry("THREATTEXT",self.locale) +text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) +local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS +local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) +if settings:IsMetric()then +elevationmeasure=self.gettext:GetEntry("METER",self.locale) +else +Elevation=math.floor(UTILS.MetersToFeet(Elevation)) +end +local elev=self.gettext:GetEntry("ELEVATION",self.locale) +text=text..string.format(elev,tostring(math.floor(Elevation)),elevationmeasure) +if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then +if LasingDrone and LasingDrone.playertask then +local yes=self.gettext:GetEntry("YES",self.locale) +local no=self.gettext:GetEntry("NO",self.locale) +local inreach=LasingDrone.playertask.inreach==true and yes or no +local islasing=LasingDrone:IsLasing()==true and yes or no +local prectext=self.gettext:GetEntry("POINTERTARGETREPORT",self.locale) +prectext=string.format(prectext,inreach,islasing) +text=text..prectext.." ("..LasingDrone.playertask.lasercode..")" +end +end +if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.buddylasing then +if self.PlayerRecce then +local yes=self.gettext:GetEntry("YES",self.locale) +local no=self.gettext:GetEntry("NO",self.locale) +local reachdist=8000 +local inreach=false +local pset=self.PlayerRecce.PlayerSet:GetAliveSet() +for _,_player in pairs(pset)do +local player=_player +local pcoord=player:GetCoordinate() +if pcoord:Get2DDistance(Coordinate)<=reachdist then +inreach=true +local callsign=player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local playername=player:GetPlayerName() +local islasing=no +if self.PlayerRecce.CanLase[player:GetTypeName()]and self.PlayerRecce.AutoLase[playername]then +islasing=yes +end +local inrtext=inreach==true and yes or no +local prectext=self.gettext:GetEntry("RECCETARGETREPORT",self.locale) +prectext=string.format(prectext,callsign,inrtext,islasing) +text=text..prectext +end +end +end +elseif task.Type==AUFTRAG.Type.CTLD or task.Type==AUFTRAG.Type.CSAR then +text=taskname +textTTS=taskname +local detail=task:GetFreetext() +local detailTTS=task:GetFreetextTTS() +local brieftxt=self.gettext:GetEntry("BRIEFING",self.locale) +local locatxt=self.gettext:GetEntry("TARGETLOCATION",self.locale) +text=text..string.format("\n%s: %s\n%s %s",brieftxt,detail,locatxt,CoordText) +textTTS=textTTS..string.format("; %s: %s; %s %s",brieftxt,detailTTS,locatxt,CoordText) +end +local clienttxt=self.gettext:GetEntry("PILOTS",self.locale) +if clientcount>0 then +for _,_name in pairs(clientlist)do +if self.customcallsigns[_name]then +_name=self.customcallsigns[_name] +_name=string.gsub(_name,"(%d) ","%1") +end +clienttxt=clienttxt.._name..", " +end +clienttxt=string.gsub(clienttxt,", $",".") +else +local keine=self.gettext:GetEntry("NONE",self.locale) +clienttxt=clienttxt..keine +end +text=text..clienttxt +textTTS=textTTS..clienttxt +if self.InfoHasCoordinate then +if self.InfoHasLLDDM then +CoordTextLLDM=Coordinate:ToStringLLDDM() +else +CoordTextLLDM=Coordinate:ToStringLLDMS() +end +local locatxt=self.gettext:GetEntry("COORDINATE",self.locale) +text=string.format("%s\n%s: %s",text,locatxt,CoordTextLLDM) +end +if task:HasFreetext()and not(task.Type==AUFTRAG.Type.CTLD or task.Type==AUFTRAG.Type.CSAR)then +local brieftxt=self.gettext:GetEntry("BRIEFING",self.locale) +text=text..string.format("\n%s: ",brieftxt)..task:GetFreetext() +end +if self.UseSRS then +if string.find(CoordText," BR, ")then +CoordText=string.gsub(CoordText," BR, "," Bee, Arr; ") +end +if self.ShowMagnetic then +text=string.gsub(text,"°M|","° magnetic; ") +end +if string.find(CoordText,"MGRS")then +local Text=string.gsub(CoordText,"MGRS ","") +Text=string.gsub(Text,"%s+","") +Text=string.gsub(Text,"([%a%d])","%1;") +Text=string.gsub(Text,"0","zero") +Text=string.gsub(Text,"9","niner") +CoordText="MGRS;"..Text +if self.PathToGoogleKey then +end +end +local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) +local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then +if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then +local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) +ttstext=ttstext..lasingtext +end +elseif task.Type==AUFTRAG.Type.CTLD or task.Type==AUFTRAG.Type.CSAR then +ttstext=textTTS +if string.find(ttstext," BR, ")then +CoordText=string.gsub(ttstext," BR, "," Bee, Arr, ") +end +elseif task:HasFreetext()then +local brieftxt=self.gettext:GetEntry("BRIEFING",self.locale) +ttstext=ttstext..string.format("; %s: ",brieftxt)..task:GetFreetextTTS() +end +self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,2) +end +else +text=self.gettext:GetEntry("NOACTIVETASK",self.locale) +end +if not self.NoScreenOutput then +local m=MESSAGE:New(text,self.TaskInfoDuration or 30,"Tasking"):ToClient(Client) +end +return self +end +function PLAYERTASKCONTROLLER:_MarkTask(Group,Client) +self:T(self.lid.."_MarkTask") +local playername,ttsplayername=self:_GetPlayerName(Client) +local text="" +if self.TasksPerPlayer:HasUniqueID(playername)then +local task=self.TasksPerPlayer:ReadByID(playername) +text=string.format("Task ID #%03d | Type: %s | Threat: %d",task.PlayerTaskNr,task.Type,task.Target:GetThreatLevelMax()) +task:MarkTargetOnF10Map(text,self.Coalition,self.MarkerReadOnly) +local textmark=self.gettext:GetEntry("MARKTASK",self.locale) +text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) +self:T(self.lid..text) +if self.UseSRS then +self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) +end +else +text=self.gettext:GetEntry("NOACTIVETASK",self.locale) +end +if not self.NoScreenOutput then +local m=MESSAGE:New(text,"10","Tasking"):ToClient(Client) +end +return self +end +function PLAYERTASKCONTROLLER:_SmokeTask(Group,Client) +self:T(self.lid.."_SmokeTask") +local playername,ttsplayername=self:_GetPlayerName(Client) +local text="" +if self.TasksPerPlayer:HasUniqueID(playername)then +local task=self.TasksPerPlayer:ReadByID(playername) +task:SmokeTarget() +local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) +text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) +self:T(self.lid..text) +if self.UseSRS then +self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) +end +self:__TaskTargetSmoked(5,task) +else +text=self.gettext:GetEntry("NOACTIVETASK",self.locale) +end +if not self.NoScreenOutput then +local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) +end +return self +end +function PLAYERTASKCONTROLLER:_FlareTask(Group,Client) +self:T(self.lid.."_FlareTask") +local playername,ttsplayername=self:_GetPlayerName(Client) +local text="" +if self.TasksPerPlayer:HasUniqueID(playername)then +local task=self.TasksPerPlayer:ReadByID(playername) +task:FlareTarget() +local textmark=self.gettext:GetEntry("FLARETASK",self.locale) +text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) +self:T(self.lid..text) +if self.UseSRS then +self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) +end +self:__TaskTargetFlared(5,task) +else +text=self.gettext:GetEntry("NOACTIVETASK",self.locale) +end +if not self.NoScreenOutput then +local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) +end +return self +end +function PLAYERTASKCONTROLLER:_IlluminateTask(Group,Client) +self:T(self.lid.."_IlluminateTask") +local playername,ttsplayername=self:_GetPlayerName(Client) +local text="" +if self.TasksPerPlayer:HasUniqueID(playername)then +local task=self.TasksPerPlayer:ReadByID(playername) +task:FlareTarget() +local textmark=self.gettext:GetEntry("FLARETASK",self.locale) +text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) +self:T(self.lid..text) +if self.UseSRS then +self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) +end +self:__TaskTargetIlluminated(5,task) +else +text=self.gettext:GetEntry("NOACTIVETASK",self.locale) +end +if not self.NoScreenOutput then +local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) +end +return self +end +function PLAYERTASKCONTROLLER:_AbortTask(Group,Client) +self:T(self.lid.."_AbortTask") +local playername,ttsplayername=self:_GetPlayerName(Client) +local text="" +if self.TasksPerPlayer:HasUniqueID(playername)then +local task=self.TasksPerPlayer:PullByID(playername) +task:ClientAbort(Client) +local textmark=self.gettext:GetEntry("ABORTTASK",self.locale) +text=string.format(textmark,self.MenuName or self.Name,ttsplayername,task.TTSType,task.PlayerTaskNr) +self:T(self.lid..text) +if self.UseSRS then +self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) +end +self:__PlayerAbortedTask(1,Group,Client,task) +else +text=self.gettext:GetEntry("NOACTIVETASK",self.locale) +end +if not self.NoScreenOutput then +local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) +end +self:_SwitchMenuForClient(Client,"Info",1) +return self +end +function PLAYERTASKCONTROLLER:_UpdateJoinMenuTemplate() +self:T("_UpdateJoinMenuTemplate") +if self.TaskQueue:Count()>0 then +local taskpertype=self:_GetTasksPerType() +local JoinMenu=self.JoinMenu +local controller=self.JoinTaskMenuTemplate +local actcontroller=self.ActiveTaskMenuTemplate +local actinfomenu=self.ActiveInfoMenu +if self.TaskQueue:Count()==0 and self.MenuNoTask==nil then +local menunotasks=self.gettext:GetEntry("MENUNOTASKS",self.locale) +self.MenuNoTask=controller:NewEntry(menunotasks,self.JoinMenu) +controller:AddEntry(self.MenuNoTask) +end +if self.TaskQueue:Count()>0 and self.MenuNoTask~=nil then +controller:DeleteGenericEntry(self.MenuNoTask) +controller:DeleteF10Entry(self.MenuNoTask) +self.MenuNoTask=nil +end +local maxn=self.menuitemlimit +for _type,_ in pairs(taskpertype)do +local found=controller:FindEntriesByText(_type) +if#found==0 then +local newentry=controller:NewEntry(_type,JoinMenu) +controller:AddEntry(newentry) +if self.JoinInfoMenu then +local newentry=controller:NewEntry(_type,self.JoinInfoMenu) +controller:AddEntry(newentry) +end +if actinfomenu then +local newentry=actcontroller:NewEntry(_type,self.ActiveInfoMenu) +actcontroller:AddEntry(newentry) +end +end +end +local typelist=self:_GetAvailableTaskTypes() +for _tasktype,_data in pairs(typelist)do +self:T("**** Building for TaskType: ".._tasktype) +for _,_task in pairs(taskpertype[_tasktype])do +_task=_task +self:T("**** Building for Task: ".._task.Target:GetName()) +if _task.InMenu then +self:T("**** Task already in Menu ".._task.Target:GetName()) +else +local menutaskno=self.gettext:GetEntry("MENUTASKNO",self.locale) +local text=string.format("%s %03d",menutaskno,_task.PlayerTaskNr) +if self.UseGroupNames then +local name=_task.Target:GetName() +if name~="Unknown"then +text=string.format("%s (%03d)",name,_task.PlayerTaskNr) +end +end +local parenttable,number=controller:FindEntriesByText(_tasktype,JoinMenu) +if number>0 then +local Parent=parenttable[1] +local matches,count=controller:FindEntriesByParent(Parent) +self:T("***** Join Menu ".._tasktype.." # of entries: "..count) +if count0 then +local Parent=parenttable[1] +local matches,count=controller:FindEntriesByParent(Parent) +self:T("***** Join Info Menu ".._tasktype.." # of entries: "..count) +if count0 then +local Parent=parenttable[1] +local matches,count=actcontroller:FindEntriesByParent(Parent) +self:T("***** Active Info Menu ".._tasktype.." # of entries: "..count) +if count0 and self.MenuNoTask~=nil then +JoinTaskMenuTemplate:DeleteGenericEntry(self.MenuNoTask) +self.MenuNoTask=nil +end +if self.InformationMenu then +local radioinfo=self.gettext:GetEntry("RADIOS",self.locale) +JoinTaskMenuTemplate:NewEntry(radioinfo,self.JoinTopMenu,self._ShowRadioInfo,self) +end +self.JoinTaskMenuTemplate=JoinTaskMenuTemplate +return self +end +function PLAYERTASKCONTROLLER:_CreateActiveTaskMenuTemplate() +self:T("_CreateActiveTaskMenuTemplate") +local menuactive=self.gettext:GetEntry("MENUACTIVE",self.locale) +local menuinfo=self.gettext:GetEntry("MENUINFO",self.locale) +local menumark=self.gettext:GetEntry("MENUMARK",self.locale) +local menusmoke=self.gettext:GetEntry("MENUSMOKE",self.locale) +local menuflare=self.gettext:GetEntry("MENUFLARE",self.locale) +local menuillu=self.gettext:GetEntry("MENUILLU",self.locale) +local menuabort=self.gettext:GetEntry("MENUABORT",self.locale) +local ActiveTaskMenuTemplate=CLIENTMENUMANAGER:New(self.ActiveClientSet,"ActiveTask") +if not self.ActiveTopMenu then +local taskings=self.gettext:GetEntry("MENUTASKING",self.locale) +local longname=self.Name..taskings..self.Type +local menuname=self.MenuName or longname +self.ActiveTopMenu=ActiveTaskMenuTemplate:NewEntry(menuname,self.MenuParent) +end +if self.AllowFlash then +local flashtext=self.gettext:GetEntry("FLASHMENU",self.locale) +ActiveTaskMenuTemplate:NewEntry(flashtext,self.ActiveTopMenu,self._SwitchFlashing,self) +end +local active=ActiveTaskMenuTemplate:NewEntry(menuactive,self.ActiveTopMenu) +ActiveTaskMenuTemplate:NewEntry(menuinfo,active,self._ActiveTaskInfo,self,"NONE") +ActiveTaskMenuTemplate:NewEntry(menumark,active,self._MarkTask,self) +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and self.noflaresmokemenu~=true then +ActiveTaskMenuTemplate:NewEntry(menusmoke,active,self._SmokeTask,self) +ActiveTaskMenuTemplate:NewEntry(menuflare,active,self._FlareTask,self) +if self.illumenu then +ActiveTaskMenuTemplate:NewEntry(menuillu,active,self._IlluminateTask,self) +end +end +ActiveTaskMenuTemplate:NewEntry(menuabort,active,self._AbortTask,self) +self.ActiveTaskMenuTemplate=ActiveTaskMenuTemplate +if self.taskinfomenu and self.activehasinfomenu then +local menutaskinfo=self.gettext:GetEntry("MENUTASKINFO",self.locale) +self.ActiveInfoMenu=ActiveTaskMenuTemplate:NewEntry(menutaskinfo,self.ActiveTopMenu) +end +return self +end +function PLAYERTASKCONTROLLER:_SwitchMenuForClient(Client,MenuType,Delay) +self:T(self.lid.."_SwitchMenuForClient") +if Delay then +self:ScheduleOnce(Delay,self._SwitchMenuForClient,self,Client,MenuType) +return self +end +if MenuType=="Info"then +self.ClientSet:AddClientsByName(Client:GetName()) +self.ActiveClientSet:Remove(Client:GetName(),true) +self.ActiveTaskMenuTemplate:ResetMenu(Client) +self.JoinTaskMenuTemplate:ResetMenu(Client) +self.JoinTaskMenuTemplate:Propagate(Client) +elseif MenuType=="Active"then +self.ActiveClientSet:AddClientsByName(Client:GetName()) +self.ClientSet:Remove(Client:GetName(),true) +self.ActiveTaskMenuTemplate:ResetMenu(Client) +self.JoinTaskMenuTemplate:ResetMenu(Client) +self.ActiveTaskMenuTemplate:Propagate(Client) +else +self:E(self.lid.."Unknown menu type in _SwitchMenuForClient:"..tostring(MenuType)) +end +return self +end +function PLAYERTASKCONTROLLER:AddAgent(Recce) +self:T(self.lid.."AddAgent") +if self.Intel then +self.Intel:AddAgent(Recce) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddAgentSet(RecceSet) +self:T(self.lid.."AddAgentSet") +if self.Intel then +local Set=RecceSet:GetAliveSet() +for _,_Recce in pairs(Set)do +self.Intel:AddAgent(_Recce) +end +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SwitchDetectStatics(OnOff) +self:T(self.lid.."SwitchDetectStatics") +if self.Intel then +self.Intel:SetDetectStatics(OnOff) +else +self:E(self.lid.."***** NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddAcceptZone(AcceptZone) +self:T(self.lid.."AddAcceptZone") +if self.Intel then +self.Intel:AddAcceptZone(AcceptZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddAcceptZoneSet(AcceptZoneSet) +self:T(self.lid.."AddAcceptZoneSet") +if self.Intel then +self.Intel.acceptzoneset:AddSet(AcceptZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddRejectZone(RejectZone) +self:T(self.lid.."AddRejectZone") +if self.Intel then +self.Intel:AddRejectZone(RejectZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddRejectZoneSet(RejectZoneSet) +self:T(self.lid.."AddRejectZoneSet") +if self.Intel then +self.Intel.rejectzoneset:AddSet(RejectZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddConflictZone(ConflictZone) +self:T(self.lid.."AddConflictZone") +if self.Intel then +self.Intel:AddConflictZone(ConflictZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddConflictZoneSet(ConflictZoneSet) +self:T(self.lid.."AddConflictZoneSet") +if self.Intel then +self.Intel.conflictzoneset:AddSet(ConflictZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveAcceptZone(AcceptZone) +self:T(self.lid.."RemoveAcceptZone") +if self.Intel then +self.Intel:RemoveAcceptZone(AcceptZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveRejectZone(RejectZone) +self:T(self.lid.."RemoveRejectZone") +if self.Intel then +self.Intel:RemoveRejectZone(RejectZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone) +self:T(self.lid.."RemoveConflictZone") +if self.Intel then +self.Intel:RemoveConflictZone(ConflictZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetMenuName(Name) +self:T(self.lid.."SetMenuName: "..Name) +self.MenuName=Name +return self +end +function PLAYERTASKCONTROLLER:SetParentMenu(Menu) +self:T(self.lid.."SetParentMenu") +return self +end +function PLAYERTASKCONTROLLER:SetupIntel(RecceName) +self:T(self.lid.."SetupIntel") +self.RecceSet=SET_GROUP:New():FilterCoalitions(self.CoalitionName):FilterPrefixes(RecceName):FilterStart() +self.Intel=INTEL:New(self.RecceSet,self.Coalition,self.Name.."-Intel") +self.Intel:SetClusterAnalysis(true,false,false) +self.Intel:SetClusterRadius(self.ClusterRadius or 0.5) +self.Intel.statusupdate=25 +self.Intel:SetAcceptZones() +self.Intel:SetRejectZones() +if self.Type==PLAYERTASKCONTROLLER.Type.A2G or self.Type==PLAYERTASKCONTROLLER.Type.A2GS then +self.Intel:SetDetectStatics(true) +end +self.Intel:__Start(2) +local function NewCluster(Cluster) +if not self.usecluster then return self end +local cluster=Cluster +local type=cluster.ctype +self:T({type,self.Type}) +if(type==INTEL.Ctype.AIRCRAFT and self.Type==PLAYERTASKCONTROLLER.Type.A2A)or(type==INTEL.Ctype.NAVAL and(self.Type==PLAYERTASKCONTROLLER.Type.A2S or self.Type==PLAYERTASKCONTROLLER.Type.A2GS))then +self:T("A2A or A2S") +local contacts=cluster.Contacts +local targetset=SET_GROUP:New() +for _,_object in pairs(contacts)do +local contact=_object +self:T("Adding group: "..contact.groupname) +targetset:AddGroup(contact.group,true) +end +self:AddTarget(targetset) +elseif(type==INTEL.Ctype.GROUND or type==INTEL.Ctype.STRUCTURE)and(self.Type==PLAYERTASKCONTROLLER.Type.A2G or self.Type==PLAYERTASKCONTROLLER.Type.A2GS)then +self:T("A2G") +local contacts=cluster.Contacts +local targetset=nil +if type==INTEL.Ctype.GROUND then +targetset=SET_GROUP:New() +for _,_object in pairs(contacts)do +local contact=_object +self:T("Adding group: "..contact.groupname) +targetset:AddGroup(contact.group,true) +end +elseif type==INTEL.Ctype.STRUCTURE then +targetset=SET_STATIC:New() +for _,_object in pairs(contacts)do +local contact=_object +self:T("Adding static: "..contact.groupname) +targetset:AddStatic(contact.group) +end +end +if targetset then +self:AddTarget(targetset) +end +end +end +local function NewContact(Contact) +if self.usecluster then return self end +local contact=Contact +local type=contact.ctype +self:T({type,self.Type}) +if(type==INTEL.Ctype.AIRCRAFT and self.Type==PLAYERTASKCONTROLLER.Type.A2A)or(type==INTEL.Ctype.NAVAL and(self.Type==PLAYERTASKCONTROLLER.Type.A2S or self.Type==PLAYERTASKCONTROLLER.Type.A2GS))then +self:T("A2A or A2S") +self:T("Adding group: "..contact.groupname) +self:AddTarget(contact.group) +elseif(type==INTEL.Ctype.GROUND or type==INTEL.Ctype.STRUCTURE)and(self.Type==PLAYERTASKCONTROLLER.Type.A2G or self.Type==PLAYERTASKCONTROLLER.Type.A2GS)then +self:T("A2G") +self:T("Adding group: "..contact.groupname) +self:AddTarget(contact.group) +end +end +function self.Intel:OnAfterNewCluster(From,Event,To,Cluster) +NewCluster(Cluster) +end +function self.Intel:OnAfterNewContact(From,Event,To,Contact) +NewContact(Contact) +end +return self +end +function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend) +self:T(self.lid.."SetSRS") +self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +self.Gender=Gender or MSRS.gender or"male" +self.Culture=Culture or MSRS.culture or"en-US" +self.Port=Port or MSRS.port or 5002 +self.Voice=Voice or MSRS.voice +self.PathToGoogleKey=PathToGoogleKey +self.AccessKey=AccessKey +self.Volume=Volume or 1.0 +self.UseSRS=true +self.Frequency=Frequency or{127,251} +self.BCFrequency=self.Frequency +self.Modulation=Modulation or{radio.modulation.FM,radio.modulation.AM} +self.BCModulation=self.Modulation +self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,Backend) +self.SRS:SetCoalition(self.Coalition) +self.SRS:SetLabel(self.MenuName or self.Name) +self.SRS:SetGender(self.Gender) +self.SRS:SetCulture(self.Culture) +self.SRS:SetPort(self.Port) +self.SRS:SetVolume(self.Volume) +if self.PathToGoogleKey then +self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey) +self.SRS:SetProvider(MSRS.Provider.GOOGLE) +end +if(not PathToGoogleKey)and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then +self.PathToGoogleKey=MSRS.poptions.gcloud.credentials +self.Voice=Voice or MSRS.poptions.gcloud.voice +self.AccessKey=AccessKey or MSRS.poptions.gcloud.key +end +if Coordinate then +self.SRS:SetCoordinate(Coordinate) +end +self.SRS:SetVoice(self.Voice) +self.SRSQueue=MSRSQUEUE:New(self.MenuName or self.Name) +self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) +return self +end +function PLAYERTASKCONTROLLER:SetSRSBroadcast(Frequency,Modulation) +self:T(self.lid.."SetSRSBroadcast") +if self.SRS then +self.BCFrequency=Frequency +self.BCModulation=Modulation +end +return self +end +function PLAYERTASKCONTROLLER:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:T(self.lid.."onafterStart") +self:_CreateJoinMenuTemplate() +self:_CreateActiveTaskMenuTemplate() +self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler) +self:HandleEvent(EVENTS.Ejection,self._EventHandler) +self:HandleEvent(EVENTS.Crash,self._EventHandler) +self:HandleEvent(EVENTS.PilotDead,self._EventHandler) +self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) +self:HandleEvent(EVENTS.UnitLost,self._EventHandler) +self:SetEventPriority(5) +return self +end +function PLAYERTASKCONTROLLER:onafterStatus(From,Event,To) +self:T({From,Event,To}) +self:_CheckTargetQueue() +self:_CheckTaskQueue() +self:_CheckPrecisionTasks() +if self.AllowFlash then +self:_FlashInfo() +end +local targetcount=self.TargetQueue:Count() +local taskcount=self.TaskQueue:Count() +local playercount=self.ClientSet:CountAlive() +local assignedtasks=self.TasksPerPlayer:Count() +local enforcedmenu=false +if taskcount~=self.lasttaskcount then +self.lasttaskcount=taskcount +if taskcount12 then clock=clock-12 end +if clock==0 then clock=12 end +end +return clock +end +function PLAYERRECCE:SetLaserCodes(LaserCodes) +self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes} +return self +end +function PLAYERRECCE:SetReferencePoint(Coordinate,Name) +self.ReferencePoint=Coordinate +self.RPName=Name +if self.RPMarker then +self.RPMarker:Remove() +end +local llddm=Coordinate:ToStringLLDDM() +local lldms=Coordinate:ToStringLLDMS() +local mgrs=Coordinate:ToStringMGRS() +local text=string.format("%s RP %s\n%s\n%s\n%s",self.Name,Name,llddm,lldms,mgrs) +self.RPMarker=MARKER:New(Coordinate,text) +self.RPMarker:ReadOnly() +self.RPMarker:ToCoalition(self.Coalition) +return self +end +function PLAYERRECCE:SetPlayerTaskController(Controller) +self.UseController=true +self.Controller=Controller +return self +end +function PLAYERRECCE:SetAttackSet(AttackSet) +self.AttackSet=AttackSet +return self +end +function PLAYERRECCE:_CameraOn(client,playername) +local camera=true +local unit=client +if unit and unit:IsAlive()then +local typename=unit:GetTypeName() +if string.find(typename,"SA342")then +local dcsunit=Unit.getByName(client:GetName()) +local vivihorizontal=dcsunit:getDrawArgumentValue(215)or 0 +if vivihorizontal<-0.7 or vivihorizontal>0.7 then +camera=false +end +elseif string.find(typename,"OH58")then +local dcsunit=Unit.getByName(client:GetName()) +local vivihorizontal=dcsunit:getDrawArgumentValue(528)or 0 +if vivihorizontal<-0.527 or vivihorizontal>0.527 then +camera=false +end +elseif string.find(typename,"Ka-50")then +camera=true +end +end +return camera +end +function PLAYERRECCE:_GetKiowaMMSSight(Kiowa) +self:T(self.lid.."_GetKiowaMMSSight") +local unit=Kiowa +if unit and unit:IsAlive()then +local dcsunit=Unit.getByName(Kiowa:GetName()) +local mmshorizontal=dcsunit:getDrawArgumentValue(528)or 0 +local mmsvertical=dcsunit:getDrawArgumentValue(527)or 0 +self:T(string.format("Kiowa MMS Arguments Read: H %.3f V %.3f",mmshorizontal,mmsvertical)) +local mmson=true +if mmshorizontal<-0.527 or mmshorizontal>0.527 then mmson=false end +local horizontalview=mmshorizontal/0.527*190 +local heading=unit:GetHeading() +local mmsheading=(heading+horizontalview)%360 +local mmsyaw=math.atan(mmsvertical)*40 +local maxview=self:_GetActualMaxLOSight(unit,mmsheading,mmsyaw,not mmson) +if maxview>8000 then maxview=8000 end +self:T(string.format("Kiowa MMS Heading %d, Yaw %d, MaxView %dm MMS On %s",mmsheading,mmsyaw,maxview,tostring(mmson))) +return mmsheading,mmsyaw,maxview,mmson +end +return 0,0,0,false +end +function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle) +self:T(self.lid.."GetGazelleVivianneSight") +local unit=Gazelle +if unit and unit:IsAlive()then +local dcsunit=Unit.getByName(Gazelle:GetName()) +local vivihorizontal=dcsunit:getDrawArgumentValue(215)or 0 +local vivivertical=dcsunit:getDrawArgumentValue(216)or 0 +local vivioff=false +if vivihorizontal<-0.67 then +vivihorizontal=-0.67 +vivioff=false +elseif vivihorizontal>0.67 then +vivihorizontal=0.67 +vivioff=true +return 0,0,0,false +end +local horizontalview=vivihorizontal*-180 +local verticalview=math.atan(vivivertical) +local heading=unit:GetHeading() +local viviheading=(heading+horizontalview)%360 +local maxview=self:_GetActualMaxLOSight(unit,viviheading,verticalview,vivioff) +if maxview>8000 then maxview=8000 end +return viviheading,verticalview,maxview,not vivioff +end +return 0,0,0,false +end +function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading,vnod,vivoff) +self:T(self.lid.."_GetActualMaxLOSight") +if vivoff then return 0 end +local maxview=0 +if unit and unit:IsAlive()then +local typename=unit:GetTypeName() +maxview=self.MaxViewDistance[typename]or 8000 +local CamHeight=self.Cameraheight[typename]or 1 +if vnod<-2 then +local beta=90 +local gamma=90-math.abs(vnod) +local alpha=90-gamma +local a=unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight +local b=a/math.sin(math.rad(alpha)) +local c=b*math.sin(math.rad(gamma)) +maxview=c*1.2 +end +end +return math.ceil(math.abs(maxview)) +end +function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) +if not ShortCallsign or ShortCallsign==false then +self.ShortCallsign=false +else +self.ShortCallsign=true +end +self.Keepnumber=Keepnumber or false +self.CallsignTranslations=CallsignTranslations +self.CallsignCustomFunc=CallsignCustomFunc +self.CallsignCustomArgs=arg or{} +return self +end +function PLAYERRECCE:_GetViewZone(unit,vheading,minview,maxview,angle,camon,laser) +self:T(self.lid.."_GetViewZone") +local viewzone=nil +if not camon then return nil end +if unit and unit:IsAlive()then +local unitname=unit:GetName() +if not laser then +local startpos=unit:GetCoordinate() +local heading1=(vheading+angle)%360 +local heading2=(vheading-angle)%360 +local pos1=startpos:Translate(maxview,heading1) +local pos2=startpos:Translate(maxview,heading2) +local array={} +table.insert(array,startpos:GetVec2()) +table.insert(array,pos1:GetVec2()) +table.insert(array,pos2:GetVec2()) +viewzone=ZONE_POLYGON:NewFromPointsArray(unitname,array) +else +local startp=unit:GetCoordinate() +local heading1=(vheading+90)%360 +local heading2=(vheading-90)%360 +self:T({heading1,heading2}) +local startpos=startp:Translate(minview,vheading) +local pos1=startpos:Translate(12.5,heading1) +local pos2=startpos:Translate(12.5,heading2) +local pos3=pos1:Translate(maxview,vheading) +local pos4=pos2:Translate(maxview,vheading) +local array={} +table.insert(array,pos1:GetVec2()) +table.insert(array,pos2:GetVec2()) +table.insert(array,pos4:GetVec2()) +table.insert(array,pos3:GetVec2()) +viewzone=ZONE_POLYGON:NewFromPointsArray(unitname,array) +end +end +return viewzone +end +function PLAYERRECCE:_GetKnownTargets(client) +self:T(self.lid.."_GetKnownTargets") +local finaltargets=SET_UNIT:New() +local targets=self.TargetCache:GetDataTable() +local playername=client:GetPlayerName() +for _,_target in pairs(targets)do +local targetdata=_target.PlayerRecceDetected +if targetdata.playername==playername then +finaltargets:Add(_target:GetName(),_target) +end +end +return finaltargets,finaltargets:CountAlive() +end +function PLAYERRECCE:_CleanupTargetCache() +self:T(self.lid.."_CleanupTargetCache") +local cleancache=FIFO:New() +self.TargetCache:ForEach( +function(unit) +local pull=false +if unit and unit:IsAlive()and unit:GetLife()>1 then +if unit.PlayerRecceDetected and unit.PlayerRecceDetected.timestamp then +local TNow=timer.getTime() +if TNow-unit.PlayerRecceDetected.timestamp>self.TForget then +pull=true +unit.PlayerRecceDetected=nil +end +else +pull=true +end +else +pull=true +end +if not pull then +cleancache:Push(unit,unit:GetName()) +end +end +) +self.TargetCache=nil +self.TargetCache=cleancache +return self +end +function PLAYERRECCE:_GetTargetSet(unit,camera,laser) +self:T(self.lid.."_GetTargetSet") +local finaltargets=SET_UNIT:New() +local finalcount=0 +local minview=0 +local typename=unit:GetTypeName() +local playername=unit:GetPlayerName() +local maxview=self.MaxViewDistance[typename]or 8000 +local heading,nod,maxview,angle=0,30,8000,10 +local camon=false +local name=unit:GetName() +if string.find(typename,"SA342")and camera then +heading,nod,maxview,camon=self:_GetGazelleVivianneSight(unit) +angle=10 +maxview=self.MaxViewDistance[typename]or 8000 +elseif string.find(typename,"Ka-50")and camera then +heading=unit:GetHeading() +nod,maxview,camon=10,1000,true +angle=10 +maxview=self.MaxViewDistance[typename]or 8000 +elseif string.find(typename,"OH58")and camera then +nod,maxview,camon=0,8000,true +heading,nod,maxview,camon=self:_GetKiowaMMSSight(unit) +angle=8 +if maxview==0 then +maxview=self.MaxViewDistance[typename]or 8000 +end +else +heading=unit:GetHeading() +nod,maxview,camon=10,3000,true +maxview=self.MaxViewDistance[typename]or 3000 +angle=45 +end +if laser then +if not self.LaserFOV[playername]then +minview=100 +maxview=2000 +self.LaserFOV[playername]={ +min=100, +max=2000, +} +else +minview=self.LaserFOV[playername].min +maxview=self.LaserFOV[playername].max +end +end +local zone=self:_GetViewZone(unit,heading,minview,maxview,angle,camon,laser) +if zone then +local redcoalition="red" +if self.Coalition==coalition.side.RED then +redcoalition="blue" +end +local startpos=unit:GetCoordinate() +local targetset=SET_UNIT:New():FilterCategories("ground"):FilterActive(true):FilterZones({zone}):FilterCoalitions(redcoalition):FilterOnce() +self:T("Prefilter Target Count = "..targetset:CountAlive()) +targetset:ForEach( +function(_unit) +local _unit=_unit +local _unitpos=_unit:GetCoordinate() +if startpos:IsLOS(_unitpos)and _unit:IsAlive()and _unit:GetLife()>1 then +self:T("Adding to final targets: ".._unit:GetName()) +finaltargets:Add(_unit:GetName(),_unit) +end +end +) +finalcount=finaltargets:CountAlive() +self:T(string.format("%s Unit: %s | Targets in view %s",self.lid,name,finalcount)) +end +return finaltargets,finalcount,zone +end +function PLAYERRECCE:_GetHVTTarget(targetset) +self:T(self.lid.."_GetHVTTarget") +local unitsbythreat={} +local minthreat=self.minthreatlevel or 0 +for _,_unit in pairs(targetset.Set)do +local unit=_unit +if unit and unit:IsAlive()and unit:GetLife()>1 then +local threat=unit:GetThreatLevel() +if threat>=minthreat then +if unit:HasAttribute("RADAR_BAND1_FOR_ARM")or unit:HasAttribute("RADAR_BAND2_FOR_ARM")or unit:HasAttribute("Optical Tracker")then +threat=11 +end +table.insert(unitsbythreat,{unit,threat}) +end +end +end +table.sort(unitsbythreat,function(a,b) +local aNum=a[2] +local bNum=b[2] +return aNum>bNum +end) +if unitsbythreat[1]and unitsbythreat[1][1]then +return unitsbythreat[1][1] +else +return nil +end +end +function PLAYERRECCE:_LaseTarget(client,targetset) +self:T(self.lid.."_LaseTarget") +local target=self:_GetHVTTarget(targetset) +local playername=client:GetPlayerName() +local laser=nil +if not self.LaserSpots[playername]then +laser=SPOT:New(client) +if not self.UnitLaserCodes[playername]then +self.UnitLaserCodes[playername]=1688 +end +laser.LaserCode=self.UnitLaserCodes[playername]or 1688 +self.LaserSpots[playername]=laser +else +laser=self.LaserSpots[playername] +end +if self.LaserTarget[playername]then +local target=self.LaserTarget[playername] +local oldtarget=target:GetObject() +self:T("Targetstate: "..target:GetState()) +self:T("Laser State: "..tostring(laser:IsLasing())) +if(not oldtarget)or targetset:IsNotInSet(oldtarget)or target:IsDead()or target:IsDestroyed()then +laser:LaseOff() +self:T(self.lid.."Target Life Points: "..target:GetLife()or"none") +if target:IsDead()or target:IsDestroyed()or target:GetDamage()>79 or target:GetLife()<=1 then +self:__Shack(-1,client,oldtarget) +else +self:__TargetLOSLost(-1,client,oldtarget) +end +self.LaserTarget[playername]=nil +oldtarget=nil +self.LaserSpots[playername]=nil +elseif oldtarget and laser and(not laser:IsLasing())then +self:T("Switching laser back on ..") +local lasercode=self.UnitLaserCodes[playername]or laser.LaserCode or 1688 +local lasingtime=self.lasingtime or 60 +laser:LaseOn(oldtarget,lasercode,lasingtime) +else +self:T("Target alive and laser is on!") +end +elseif(not laser:IsLasing())and target then +local relativecam=self.LaserRelativePos[client:GetTypeName()] +laser:SetRelativeStartPosition(relativecam) +local lasercode=self.UnitLaserCodes[playername]or laser.LaserCode or 1688 +local lasingtime=self.lasingtime or 60 +laser:LaseOn(target,lasercode,lasingtime) +self.LaserTarget[playername]=TARGET:New(target) +self:__TargetLasing(-1,client,target,lasercode,lasingtime) +end +return self +end +function PLAYERRECCE:_SetClientLaserCode(client,group,playername,code) +self:T(self.lid.."_SetClientLaserCode") +self.UnitLaserCodes[playername]=code or 1688 +if self.ClientMenus[playername]then +self.ClientMenus[playername]:Remove() +self.ClientMenus[playername]=nil +end +self:_BuildMenus() +return self +end +function PLAYERRECCE:_SwitchOnStation(client,group,playername) +self:T(self.lid.."_SwitchOnStation") +if not self.OnStation[playername]then +self.OnStation[playername]=true +self:__RecceOnStation(-1,client,playername) +else +self.OnStation[playername]=false +self:__RecceOffStation(-1,client,playername) +end +if self.ClientMenus[playername]then +self.ClientMenus[playername]:Remove() +self.ClientMenus[playername]=nil +end +self:_BuildMenus(client) +return self +end +function PLAYERRECCE:_SwitchSmoke(client,group,playername) +self:T(self.lid.."_SwitchLasing") +if not self.SmokeOwn[playername]then +self.SmokeOwn[playername]=true +MESSAGE:New("Smoke self is now ON",10,self.Name or"FACA"):ToClient(client) +else +self.SmokeOwn[playername]=false +MESSAGE:New("Smoke self is now OFF",10,self.Name or"FACA"):ToClient(client) +end +if self.ClientMenus[playername]then +self.ClientMenus[playername]:Remove() +self.ClientMenus[playername]=nil +end +self:_BuildMenus(client) +return self +end +function PLAYERRECCE:_SwitchLasing(client,group,playername) +self:T(self.lid.."_SwitchLasing") +if not self.AutoLase[playername]then +self.AutoLase[playername]=true +MESSAGE:New("Lasing is now ON",10,self.Name or"FACA"):ToClient(client) +else +self.AutoLase[playername]=false +if self.LaserSpots[playername]then +local laser=self.LaserSpots[playername] +if laser:IsLasing()then +laser:LaseOff() +end +self.LaserSpots[playername]=nil +end +MESSAGE:New("Lasing is now OFF",10,self.Name or"FACA"):ToClient(client) +end +if self.ClientMenus[playername]then +self.ClientMenus[playername]:Remove() +self.ClientMenus[playername]=nil +end +self:_BuildMenus(client) +return self +end +function PLAYERRECCE:_SwitchLasingDist(client,group,playername,mindist,maxdist) +self:T(self.lid.."_SwitchLasingDist") +local mind=mindist or 100 +local maxd=maxdist or 2000 +if not self.LaserFOV[playername]then +self.LaserFOV[playername]={ +min=mind, +max=maxd, +} +else +self.LaserFOV[playername].min=mind +self.LaserFOV[playername].max=maxd +end +MESSAGE:New(string.format("Laser distance set to %d-%dm!",mindist,maxdist),10,"FACA"):ToClient(client) +if self.ClientMenus[playername]then +self.ClientMenus[playername]:Remove() +self.ClientMenus[playername]=nil +end +self:_BuildMenus(client) +return self +end +function PLAYERRECCE:_WIP(client,group,playername) +self:T(self.lid.."_WIP") +return self +end +function PLAYERRECCE:_SmokeTargets(client,group,playername) +self:T(self.lid.."_SmokeTargets") +local cameraset=self:_GetTargetSet(client,true) +local visualset=self:_GetTargetSet(client,false) +if cameraset:CountAlive()>0 or visualset:CountAlive()>0 then +self:__TargetsSmoked(-1,client,playername,cameraset) +else +return self +end +local highsmoke=self.SmokeColor.highsmoke +local medsmoke=self.SmokeColor.medsmoke +local lowsmoke=self.SmokeColor.lowsmoke +local lasersmoke=self.SmokeColor.lasersmoke +local laser=self.LaserSpots[playername] +if laser and laser.Target and laser.Target:IsAlive()then +laser.Target:GetCoordinate():Smoke(lasersmoke) +end +local coord=visualset:GetCoordinate() +if coord and self.smokeaveragetargetpos then +coord:SetAtLandheight() +coord:Smoke(medsmoke) +else +for _,_unit in pairs(visualset.Set)do +local unit=_unit +if unit and unit:IsAlive()then +local coord=unit:GetCoordinate() +local threat=unit:GetThreatLevel() +if coord then +local color=lowsmoke +if threat>7 then +color=highsmoke +elseif threat>2 then +color=medsmoke +end +coord:Smoke(color) +end +end +end +end +if self.SmokeOwn[playername]then +local cc=client:GetVec2() +local lc=COORDINATE:NewFromVec2(cc,1) +local color=self.SmokeColor.ownsmoke +lc:Smoke(color) +end +return self +end +function PLAYERRECCE:_FlareTargets(client,group,playername) +self:T(self.lid.."_FlareTargets") +local cameraset=self:_GetTargetSet(client,true) +local visualset=self:_GetTargetSet(client,false) +cameraset:AddSet(visualset) +if cameraset:CountAlive()>0 then +self:__TargetsFlared(-1,client,playername,cameraset) +end +local highsmoke=self.FlareColor.highflare +local medsmoke=self.FlareColor.medflare +local lowsmoke=self.FlareColor.lowflare +local lasersmoke=self.FlareColor.laserflare +local laser=self.LaserSpots[playername] +if laser and laser.Target and laser.Target:IsAlive()then +laser.Target:GetCoordinate():Flare(lasersmoke) +if cameraset:IsInSet(laser.Target)then +cameraset:Remove(laser.Target:GetName(),true) +end +end +for _,_unit in pairs(cameraset.Set)do +local unit=_unit +if unit and unit:IsAlive()then +local coord=unit:GetCoordinate() +local threat=unit:GetThreatLevel() +if coord then +local color=lowsmoke +if threat>7 then +color=highsmoke +elseif threat>2 then +color=medsmoke +end +coord:Flare(color) +end +end +end +return self +end +function PLAYERRECCE:_IlluTargets(client,group,playername) +self:T(self.lid.."_IlluTargets") +local totalset,count=self:_GetKnownTargets(client) +if count>0 then +local coord=totalset:GetCoordinate() +coord.y=coord.y+200 +coord:IlluminationBomb(nil,1) +self:__Illumination(1,client,playername,totalset) +end +return self +end +function PLAYERRECCE:_UploadTargets(client,group,playername) +self:T(self.lid.."_UploadTargets") +local totalset,count=self:_GetKnownTargets(client) +if count>0 then +self.Controller:AddTarget(totalset) +self:__TargetReportSent(1,client,playername,totalset) +end +return self +end +function PLAYERRECCE:_ReportLaserTargets(client,group,playername) +self:T(self.lid.."_ReportLaserTargets") +local targetset,number=self:_GetTargetSet(client,true,true) +if number>0 and self.AutoLase[playername]then +local Settings=(client and _DATABASE:GetPlayerSettings(playername))or _SETTINGS +local target=self:_GetHVTTarget(targetset) +local ThreatLevel=target:GetThreatLevel()or 1 +local ThreatLevelText="high" +if ThreatLevel>3 and ThreatLevel<8 then +ThreatLevelText="medium" +elseif ThreatLevel<=3 then +ThreatLevelText="low" +end +local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel +local report=REPORT:New("Lasing Report") +report:Add(string.rep("-",15)) +report:Add("Target type: "..target:GetTypeName()or"unknown") +report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") +if not self.ReferencePoint then +report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) +if self.reporttostringbullsonly~=true then +report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) +end +else +report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) +end +report:Add("Laser Code: "..self.UnitLaserCodes[playername]or 1688) +report:Add(string.rep("-",15)) +local text=report:Text() +self:__TargetReport(1,client,targetset,target,text) +else +local report=REPORT:New("Lasing Report") +report:Add(string.rep("-",15)) +report:Add("N O T A R G E T S") +report:Add(string.rep("-",15)) +local text=report:Text() +self:__TargetReport(1,client,nil,nil,text) +end +return self +end +function PLAYERRECCE:_ReportVisualTargets(client,group,playername) +self:T(self.lid.."_ReportVisualTargets") +local targetset,number=self:_GetKnownTargets(client) +if number>0 then +local Settings=(client and _DATABASE:GetPlayerSettings(playername))or _SETTINGS +local ThreatLevel=targetset:CalculateThreatLevelA2G() +local ThreatLevelText="high" +if ThreatLevel>3 and ThreatLevel<8 then +ThreatLevelText="medium" +elseif ThreatLevel<=3 then +ThreatLevelText="low" +end +local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel +local report=REPORT:New("Target Report") +report:Add(string.rep("-",15)) +report:Add("Target count: "..number) +report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") +if not self.ReferencePoint then +report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) +if self.reporttostringbullsonly~=true then +report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) +end +else +report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) +if self.reporttostringbullsonly~=true then +report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) +end +end +report:Add(string.rep("-",15)) +local text=report:Text() +self:__TargetReport(1,client,targetset,nil,text) +else +local report=REPORT:New("Target Report") +report:Add(string.rep("-",15)) +report:Add("N O T A R G E T S") +report:Add(string.rep("-",15)) +local text=report:Text() +self:__TargetReport(1,client,nil,nil,text) +end +return self +end +function PLAYERRECCE:_BuildMenus(Client) +self:T(self.lid.."_BuildMenus") +local clients=self.PlayerSet +local clientset=clients:GetSetObjects() +if Client then clientset={Client}end +for _,_client in pairs(clientset)do +local client=_client +if client and client:IsAlive()then +local playername=client:GetPlayerName() +self:T("Menu for "..playername) +if not self.UnitLaserCodes[playername]then +self:_SetClientLaserCode(nil,nil,playername,1688) +end +if self.SmokeOwn[playername]==nil then +self.SmokeOwn[playername]=self.smokeownposition +end +local group=client:GetGroup() +if not self.ClientMenus[playername]then +self:T("Start Menubuild for "..playername) +local canlase=self.CanLase[client:GetTypeName()] +self.ClientMenus[playername]=MENU_GROUP:New(group,self.MenuName or self.Name or"RECCE") +local txtonstation=self.OnStation[playername]and"ON"or"OFF" +local text=string.format("Switch On-Station (%s)",txtonstation) +local onstationmenu=MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchOnStation,self,client,group,playername) +if self.OnStation[playername]then +local smoketopmenu=MENU_GROUP:New(group,"Visual Markers",self.ClientMenus[playername]) +local smokemenu=MENU_GROUP_COMMAND:New(group,"Smoke Targets",smoketopmenu,self._SmokeTargets,self,client,group,playername) +local flaremenu=MENU_GROUP_COMMAND:New(group,"Flare Targets",smoketopmenu,self._FlareTargets,self,client,group,playername) +local illumenu=MENU_GROUP_COMMAND:New(group,"Illuminate Area",smoketopmenu,self._IlluTargets,self,client,group,playername) +local ownsm=self.SmokeOwn[playername]and"ON"or"OFF" +local owntxt=string.format("Switch smoke self (%s)",ownsm) +local ownsmoke=MENU_GROUP_COMMAND:New(group,owntxt,smoketopmenu,self._SwitchSmoke,self,client,group,playername) +if canlase then +local txtonstation=self.AutoLase[playername]and"ON"or"OFF" +local text=string.format("Switch Lasing (%s)",txtonstation) +local lasemenu=MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchLasing,self,client,group,playername) +local lasedist=MENU_GROUP:New(group,"Set Laser Distance",self.ClientMenus[playername]) +local mindist=100 +local maxdist=2000 +if self.LaserFOV[playername]and self.LaserFOV[playername].max then +maxdist=self.LaserFOV[playername].max +end +local laselist={} +for i=2,8 do +local dist1=(i*1000)-1000 +local dist2=i*1000 +dist1=dist1==1000 and 100 or dist1 +local text=string.format("%d-%dm",dist1,dist2) +if dist2==maxdist then +text=text.." (*)" +end +laselist[i]=MENU_GROUP_COMMAND:New(group,text,lasedist,self._SwitchLasingDist,self,client,group,playername,dist1,dist2) +end +end +local targetmenu=MENU_GROUP:New(group,"Target Report",self.ClientMenus[playername]) +if canlase then +local reportL=MENU_GROUP_COMMAND:New(group,"Laser Target",targetmenu,self._ReportLaserTargets,self,client,group,playername) +end +local reportV=MENU_GROUP_COMMAND:New(group,"Visual Targets",targetmenu,self._ReportVisualTargets,self,client,group,playername) +if self.UseController then +local text=string.format("Target Upload to %s",self.Controller.MenuName or self.Controller.Name) +local upload=MENU_GROUP_COMMAND:New(group,text,targetmenu,self._UploadTargets,self,client,group,playername) +end +if canlase then +local lasecodemenu=MENU_GROUP:New(group,"Set Laser Code",self.ClientMenus[playername]) +local codemenu={} +for _,_code in pairs(self.LaserCodes)do +if _code==self.UnitLaserCodes[playername]then +_code=tostring(_code).."(*)" +end +codemenu[playername.._code]=MENU_GROUP_COMMAND:New(group,tostring(_code),lasecodemenu,self._SetClientLaserCode,self,client,group,playername,_code) +end +end +end +end +end +end +return self +end +function PLAYERRECCE:_CheckNewTargets(targetset,client,playername) +self:T(self.lid.."_CheckNewTargets") +local tempset=SET_UNIT:New() +targetset:ForEach( +function(unit) +if unit and unit:IsAlive()then +self:T("Report unit: "..unit:GetName()) +if not unit.PlayerRecceDetected then +self:T("New unit: "..unit:GetName()) +unit.PlayerRecceDetected={ +detected=true, +recce=client, +playername=playername, +timestamp=timer.getTime() +} +tempset:Add(unit:GetName(),unit) +if not self.TargetCache:HasUniqueID(unit:GetName())then +self.TargetCache:Push(unit,unit:GetName()) +end +end +if unit.PlayerRecceDetected and unit.PlayerRecceDetected.timestamp then +local TNow=timer.getTime() +if TNow-unit.PlayerRecceDetected.timestamp>self.TForget then +unit.PlayerRecceDetected={ +detected=true, +recce=client, +playername=playername, +timestamp=timer.getTime() +} +if not self.TargetCache:HasUniqueID(unit:GetName())then +self.TargetCache:Push(unit,unit:GetName()) +end +tempset:Add(unit:GetName(),unit) +end +end +end +end +) +local targetsbyclock={} +for i=1,12 do +targetsbyclock[i]={} +end +tempset:ForEach( +function(object) +local obj=object +local clock=self:_GetClockDirection(client,obj) +table.insert(targetsbyclock[clock],obj) +end +) +self:T("Known target Count: "..self.TargetCache:Count()) +if tempset:CountAlive()>0 then +self:TargetDetected(targetsbyclock,client,playername) +end +return self +end +function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend) +self:T(self.lid.."SetSRS") +self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +self.Gender=Gender or MSRS.gender or"male" +self.Culture=Culture or MSRS.culture or"en-US" +self.Port=Port or MSRS.port or 5002 +self.Voice=Voice or MSRS.voice +self.PathToGoogleKey=PathToGoogleKey +self.Volume=Volume or 1.0 +self.UseSRS=true +self.Frequency=Frequency or{127,251} +self.BCFrequency=self.Frequency +self.Modulation=Modulation or{radio.modulation.FM,radio.modulation.AM} +self.BCModulation=self.Modulation +self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation) +self.SRS:SetCoalition(self.Coalition) +self.SRS:SetLabel(self.MenuName or self.Name) +self.SRS:SetGender(self.Gender) +self.SRS:SetCulture(self.Culture) +self.SRS:SetPort(self.Port) +self.SRS:SetVolume(self.Volume) +if Backend then +self.SRS:SetBackend(Backend) +end +if self.PathToGoogleKey then +self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey) +self.SRS:SetProvider(MSRS.Provider.GOOGLE) +end +if(not PathToGoogleKey)and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then +self.PathToGoogleKey=MSRS.poptions.gcloud.credentials +self.Voice=Voice or MSRS.poptions.gcloud.voice +end +self.SRS:SetVoice(self.Voice) +self.SRSQueue=MSRSQUEUE:New(self.MenuName or self.Name) +self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) +return self +end +function PLAYERRECCE:SetTransmitOnlyWithPlayers(Switch) +self.TransmitOnlyWithPlayers=Switch +if self.SRSQueue then +self.SRSQueue:SetTransmitOnlyWithPlayers(Switch) +end +return self +end +function PLAYERRECCE:SetMenuName(Name) +self:T(self.lid.."SetMenuName: "..Name) +self.MenuName=Name +return self +end +function PLAYERRECCE:SetReportBullsOnly(OnOff) +self:T(self.lid.."SetReportBullsOnly: "..tostring(OnOff)) +self.reporttostringbullsonly=OnOff +return self +end +function PLAYERRECCE:EnableSmokeOwnPosition() +self:T(self.lid.."EnableSmokeOwnPosition") +self.smokeownposition=true +return self +end +function PLAYERRECCE:EnableKiowaAutolase() +self:T(self.lid.."EnableKiowaAutolase") +self.CanLase.OH58D=true +return self +end +function PLAYERRECCE:DisableSmokeOwnPosition() +self:T(self.lid.."DisableSmokeOwnPosition") +self.smokeownposition=false +return self +end +function PLAYERRECCE:EnableSmokeAverageTargetPosition() +self:T(self.lid.."ENableSmokeOwnPosition") +self.smokeaveragetargetpos=true +return self +end +function PLAYERRECCE:DisableSmokeAverageTargetPosition() +self:T(self.lid.."DisableSmokeAverageTargetPosition") +self.smokeaveragetargetpos=false +return self +end +function PLAYERRECCE:_GetTextForSpeech(text) +text=string.gsub(text,"%d","%1 ") +text=string.gsub(text,"^%s*","") +text=string.gsub(text,"%s*$","") +text=string.gsub(text,"0","zero") +text=string.gsub(text,"9","niner") +text=string.gsub(text," "," ") +return text +end +function PLAYERRECCE:onafterStatus(From,Event,To) +self:T({From,Event,To}) +if not self.timestamp then +self.timestamp=timer.getTime() +else +local tNow=timer.getTime() +if tNow-self.timestamp>=60 then +self:_CleanupTargetCache() +self.timestamp=timer.getTime() +end +end +self:_BuildMenus() +self.PlayerSet:ForEachClient( +function(Client) +local client=Client +local playername=client:GetPlayerName() +local cameraison=self:_CameraOn(client,playername) +if client and client:IsAlive()and self.OnStation[playername]then +local targetset,targetcount,tzone=nil,0,nil +local laserset,lzone=nil,nil +local vistargetset,vistargetcount,viszone=nil,0,nil +if cameraison then +targetset,targetcount,tzone=self:_GetTargetSet(client,true) +if targetset then +if self.ViewZone[playername]then +self.ViewZone[playername]:UndrawZone() +end +if self.debug and tzone then +self.ViewZone[playername]=tzone:DrawZone(self.Coalition,{0,0,1},nil,nil,nil,1) +end +end +self:T({targetcount=targetcount}) +end +if self.AutoLase[playername]and cameraison then +laserset,targetcount,lzone=self:_GetTargetSet(client,true,true) +if targetcount>0 or self.LaserTarget[playername]then +if self.CanLase[client:GetTypeName()]then +self:_LaseTarget(client,laserset) +end +end +if lzone then +if self.ViewZoneLaser[playername]then +self.ViewZoneLaser[playername]:UndrawZone() +end +if self.debug and tzone then +self.ViewZoneLaser[playername]=lzone:DrawZone(self.Coalition,{0,1,0},nil,nil,nil,1) +end +end +self:T({lasercount=targetcount}) +end +vistargetset,vistargetcount,viszone=self:_GetTargetSet(client,false) +if vistargetset then +if self.ViewZoneVisual[playername]then +self.ViewZoneVisual[playername]:UndrawZone() +end +if self.debug and viszone then +self.ViewZoneVisual[playername]=viszone:DrawZone(self.Coalition,{1,0,0},nil,nil,nil,3) +end +end +self:T({visualtargetcount=vistargetcount}) +if targetset then +vistargetset:AddSet(targetset) +end +if laserset then +vistargetset:AddSet(laserset) +end +if not cameraison and self.debug then +if self.ViewZoneLaser[playername]then +self.ViewZoneLaser[playername]:UndrawZone() +end +if self.ViewZone[playername]then +self.ViewZone[playername]:UndrawZone() +end +end +self:_CheckNewTargets(vistargetset,client,playername) +end +end +) +self:__Status(-10) +return self +end +function PLAYERRECCE:onafterRecceOnStation(From,Event,To,Client,Playername) +self:T({From,Event,To}) +local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local coord=Client:GetCoordinate() +local coordtext=coord:ToStringBULLS(self.Coalition) +if self.ReferencePoint then +local Settings=Client and _DATABASE:GetPlayerSettings(Playername)or _SETTINGS +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) +end +local text1="Party time!" +local text2=string.format("All stations, FACA %s on station\nat %s!",callsign,coordtext) +local text2tts=string.format(" All stations, FACA %s on station at %s!",callsign,coordtext) +text2tts=self:_GetTextForSpeech(text2tts) +if self.debug then +self:T(text2.."\n"..text2tts) +end +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2) +self.SRSQueue:NewTransmission(text2tts,nil,self.SRS,nil,3) +MESSAGE:New(text2,10,self.Name or"FACA"):ToCoalition(self.Coalition) +else +MESSAGE:New(text1,10,self.Name or"FACA"):ToClient(Client) +MESSAGE:New(text2,10,self.Name or"FACA"):ToCoalition(self.Coalition) +end +return self +end +function PLAYERRECCE:onafterRecceOffStation(From,Event,To,Client,Playername) +self:T({From,Event,To}) +local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local coord=Client:GetCoordinate() +local coordtext=coord:ToStringBULLS(self.Coalition) +if self.ReferencePoint then +local Settings=Client and _DATABASE:GetPlayerSettings(Playername)or _SETTINGS +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) +end +local text=string.format("All stations, FACA %s leaving station\nat %s, good bye!",callsign,coordtext) +local texttts=string.format("All stations, FACA %s leaving station at %s, good bye!",callsign,coordtext) +texttts=self:_GetTextForSpeech(texttts) +if self.debug then +self:T(text.."\n"..texttts) +end +local text1="Going home!" +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2) +self.SRSQueue:NewTransmission(texttts,nil,self.SRS,nil,3) +MESSAGE:New(text,10,self.Name or"FACA"):ToCoalition(self.Coalition) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToCoalition(self.Coalition) +end +return self +end +function PLAYERRECCE:onafterTargetDetected(From,Event,To,Targetsbyclock,Client,Playername) +self:T({From,Event,To}) +local dunits="meters" +local Settings=Client and _DATABASE:GetPlayerSettings(Playername)or _SETTINGS +local clientcoord=Client:GetCoordinate() +for i=1,12 do +local targets=Targetsbyclock[i] +local targetno=#targets +if targetno==1 then +local targetdistance=clientcoord:Get2DDistance(targets[1]:GetCoordinate())or 100 +local Threatlvl=targets[1]:GetThreatLevel() +local ThreatTxt="Low" +if Threatlvl>=7 then +ThreatTxt="Medium" +elseif Threatlvl>=3 then +ThreatTxt="High" +end +if Settings:IsMetric()then +targetdistance=UTILS.Round(targetdistance,-2) +if targetdistance>=1000 then +targetdistance=UTILS.Round(targetdistance/1000,0) +dunits="kilometer" +end +else +if UTILS.MetersToNM(targetdistance)>=1 then +targetdistance=UTILS.Round(UTILS.MetersToNM(targetdistance),0) +dunits="miles" +else +targetdistance=UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) +dunits="feet" +end +end +local text=string.format("Target! %s! %s o\'clock, %d %s!",ThreatTxt,i,targetdistance,dunits) +local ttstext=string.format("Target! %s! %s oh clock, %d %s!",ThreatTxt,i,targetdistance,dunits) +if self.UseSRS then +local grp=Client:GetGroup() +if clientcoord then +self.SRS:SetCoordinate(clientcoord) +end +self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) +end +elseif targetno>1 then +local function GetNearest(TTable) +local distance=10000000 +for _,_unit in pairs(TTable)do +local dist=clientcoord:Get2DDistance(_unit:GetCoordinate())or 100 +if dist=1000 then +targetdistance=UTILS.Round(targetdistance/1000,0) +dunits="kilometer" +end +else +if UTILS.MetersToNM(targetdistance)>=1 then +targetdistance=UTILS.Round(UTILS.MetersToNM(targetdistance),0) +dunits="miles" +else +targetdistance=UTILS.Round(UTILS.MetersToFeet(targetdistance),-2) +dunits="feet" +end +end +local text=string.format(" %d targets! %s o\'clock, %d %s!",targetno,i,targetdistance,dunits) +local ttstext=string.format("%d targets! %s oh clock, %d %s!",targetno,i,targetdistance,dunits) +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) +end +end +end +return self +end +function PLAYERRECCE:onafterIllumination(From,Event,To,Client,Playername,TargetSet) +self:T({From,Event,To}) +local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local coord=Client:GetCoordinate() +local coordtext=coord:ToStringBULLS(self.Coalition) +if self.AttackSet then +for _,_client in pairs(self.AttackSet.Set)do +local client=_client +if client and client:IsAlive()then +local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS +local coordtext=coord:ToStringA2G(client,Settings) +if self.ReferencePoint then +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) +end +local text=string.format("All stations, %s fired illumination\nat %s!",callsign,coordtext) +MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) +end +end +end +local text="Sunshine!" +local ttstext="Sunshine!" +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) +end +return self +end +function PLAYERRECCE:onafterTargetsSmoked(From,Event,To,Client,Playername,TargetSet) +self:T({From,Event,To}) +local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local coord=Client:GetCoordinate() +local coordtext=coord:ToStringBULLS(self.Coalition) +if self.AttackSet then +for _,_client in pairs(self.AttackSet.Set)do +local client=_client +if client and client:IsAlive()then +local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS +local coordtext=coord:ToStringA2G(client,Settings) +if self.ReferencePoint then +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) +end +local text=string.format("All stations, %s smoked targets\nat %s!",callsign,coordtext) +MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) +end +end +end +local text="Smoke on!" +local ttstext="Smoke and Mirrors!" +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) +end +return self +end +function PLAYERRECCE:onafterTargetsFlared(From,Event,To,Client,Playername,TargetSet) +self:T({From,Event,To}) +local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local coord=Client:GetCoordinate() +local coordtext=coord:ToStringBULLS(self.Coalition) +if self.AttackSet then +for _,_client in pairs(self.AttackSet.Set)do +local client=_client +if client and client:IsAlive()then +local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS +if self.ReferencePoint then +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) +end +local coordtext=coord:ToStringA2G(client,Settings) +local text=string.format("All stations, %s flared targets\nat %s!",callsign,coordtext) +MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) +end +end +end +local text="Fireworks!" +local ttstext="Fire works!" +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) +end +return self +end +function PLAYERRECCE:onafterTargetLasing(From,Event,To,Client,Target,Lasercode,Lasingtime) +self:T({From,Event,To}) +local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS +local coord=Client:GetCoordinate() +local coordtext=coord:ToStringBULLS(self.Coalition,Settings) +if self.ReferencePoint then +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) +end +local targettype=Target:GetTypeName() +if self.AttackSet then +for _,_client in pairs(self.AttackSet.Set)do +local client=_client +if client and client:IsAlive()then +local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS +if self.ReferencePoint then +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) +end +local coordtext=coord:ToStringA2G(client,Settings) +local text=string.format("All stations, %s lasing %s\nat %s!\nCode %d, Duration %d plus seconds!",callsign,targettype,coordtext,Lasercode,Lasingtime) +MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) +end +end +end +local text="Lasing!" +local ttstext="Laser on!" +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) +end +return self +end +function PLAYERRECCE:onafterShack(From,Event,To,Client,Target) +self:T({From,Event,To}) +local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS +local coord=Client:GetCoordinate() +local coordtext=coord:ToStringBULLS(self.Coalition,Settings) +if self.ReferencePoint then +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) +end +local targettype="target" +if self.AttackSet then +for _,_client in pairs(self.AttackSet.Set)do +local client=_client +if client and client:IsAlive()then +local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS +if self.ReferencePoint then +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) +end +local coordtext=coord:ToStringA2G(client,Settings) +local text=string.format("All stations, %s good hit on %s\nat %s!",callsign,targettype,coordtext) +MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) +end +end +end +local text="Shack!" +local ttstext="Shack!" +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) +end +return self +end +function PLAYERRECCE:onafterTargetLOSLost(From,Event,To,Client,Target) +self:T({From,Event,To}) +local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) +local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS +local coord=Client:GetCoordinate() +local coordtext=coord:ToStringBULLS(self.Coalition,Settings) +if self.ReferencePoint then +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings) +end +local targettype="target" +if self.AttackSet then +for _,_client in pairs(self.AttackSet.Set)do +local client=_client +if client and client:IsAlive()then +local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS +if self.ReferencePoint then +coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings) +end +local coordtext=coord:ToStringA2G(client,Settings) +local text=string.format("All stations, %s lost sight of %s\nat %s!",callsign,targettype,coordtext) +MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client) +end +end +end +local text="Lost LOS!" +local ttstext="Lost L O S!" +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) +end +return self +end +function PLAYERRECCE:onafterTargetReport(From,Event,To,Client,TargetSet,Target,Text) +self:T({From,Event,To}) +MESSAGE:New(Text,45,self.Name or"FACA"):ToClient(Client) +if self.AttackSet then +for _,_client in pairs(self.AttackSet.Set)do +local client=_client +if client and client:IsAlive()and client~=Client then +MESSAGE:New(Text,45,self.Name or"FACA"):ToClient(client) +end +end +end +return self +end +function PLAYERRECCE:onafterTargetReportSent(From,Event,To,Client,Playername,TargetSet) +self:T({From,Event,To}) +local text="Upload completed!" +if self.UseSRS then +local grp=Client:GetGroup() +local coord=grp:GetCoordinate() +if coord then +self.SRS:SetCoordinate(coord) +end +self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,1,{grp},text,10) +else +MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client) +end +return self +end +function PLAYERRECCE:onafterStop(From,Event,To) +self:I({From,Event,To}) +self:UnHandleEvent(EVENTS.PlayerLeaveUnit) +self:UnHandleEvent(EVENTS.Ejection) +self:UnHandleEvent(EVENTS.Crash) +self:UnHandleEvent(EVENTS.PilotDead) +self:UnHandleEvent(EVENTS.PlayerEnterAircraft) +return self +end +SQUADRON={ +ClassName="SQUADRON", +verbose=0, +modex=nil, +modexcounter=0, +callsignName=nil, +callsigncounter=11, +tankerSystem=nil, +refuelSystem=nil, +} +SQUADRON.version="0.8.1" +function SQUADRON:New(TemplateGroupName,Ngroups,SquadronName) +local self=BASE:Inherit(self,COHORT:New(TemplateGroupName,Ngroups,SquadronName)) +self:AddMissionCapability(AUFTRAG.Type.ORBIT) +self.isAir=true +self.refuelSystem=select(2,self.templategroup:GetUnit(1):IsRefuelable()) +self.tankerSystem=select(2,self.templategroup:GetUnit(1):IsTanker()) +return self +end +function SQUADRON:SetGrouping(nunits) +self.ngrouping=nunits or 2 +if self.ngrouping<1 then self.ngrouping=1 end +if self.ngrouping>4 then self.ngrouping=4 end +return self +end +function SQUADRON:SetParkingIDs(ParkingIDs) +if type(ParkingIDs)~="table"then +ParkingIDs={ParkingIDs} +end +self.parkingIDs=ParkingIDs +return self +end +function SQUADRON:SetTakeoffType(TakeoffType) +TakeoffType=TakeoffType or"Cold" +if TakeoffType:lower()=="hot"then +self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot +elseif TakeoffType:lower()=="cold"then +self.takeoffType=COORDINATE.WaypointType.TakeOffParking +elseif TakeoffType:lower()=="air"then +self.takeoffType=COORDINATE.WaypointType.TurningPoint +else +self.takeoffType=COORDINATE.WaypointType.TakeOffParking +end +return self +end +function SQUADRON:SetTakeoffCold() +self:SetTakeoffType("Cold") +return self +end +function SQUADRON:SetTakeoffHot() +self:SetTakeoffType("Hot") +return self +end +function SQUADRON:SetTakeoffAir() +self:SetTakeoffType("Air") +return self +end +function SQUADRON:SetDespawnAfterLanding(Switch) +if Switch then +self.despawnAfterLanding=Switch +else +self.despawnAfterLanding=true +end +return self +end +function SQUADRON:SetDespawnAfterHolding(Switch) +if Switch then +self.despawnAfterHolding=Switch +else +self.despawnAfterHolding=true +end +return self +end +function SQUADRON:SetFuelLowThreshold(LowFuel) +self.fuellow=LowFuel or 25 +return self +end +function SQUADRON:SetFuelLowRefuel(switch) +if switch==false then +self.fuellowRefuel=false +else +self.fuellowRefuel=true +end +return self +end +function SQUADRON:SetAirwing(Airwing) +self.legion=Airwing +return self +end +function SQUADRON:GetAirwing(Airwing) +return self.legion +end +function SQUADRON:onafterStart(From,Event,To) +local text=string.format("Starting SQUADRON",self.name) +self:T(self.lid..text) +self:__Status(-1) +end +function SQUADRON:onafterStatus(From,Event,To) +if self.verbose>=1 then +local fsmstate=self:GetState() +local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName)or"N/A" +local modex=self.modex and self.modex or-1 +local skill=self.skill and tostring(self.skill)or"N/A" +local NassetsTot=#self.assets +local NassetsInS=self:CountAssets(true) +local NassetsQP=0;local NassetsP=0;local NassetsQ=0 +if self.legion then +NassetsQP,NassetsP,NassetsQ=self.legion:CountAssetsOnMission(nil,self) +end +local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]", +fsmstate,self.aircrafttype,callsign,modex,skill,NassetsTot,NassetsInS,NassetsQP,NassetsP,NassetsQ) +self:I(self.lid..text) +self:_CheckAssetStatus() +end +if not self:IsStopped()then +self:__Status(-60) +end +end +TARGET={ +ClassName="TARGET", +verbose=0, +lid=nil, +targets={}, +targetcounter=0, +life=0, +life0=0, +N0=0, +Ntargets0=0, +Ndestroyed=0, +Ndead=0, +elements={}, +casualties={}, +threatlevel0=0, +conditionStart={}, +TStatus=30, +} +TARGET.ObjectType={ +GROUP="Group", +UNIT="Unit", +STATIC="Static", +SCENERY="Scenery", +COORDINATE="Coordinate", +AIRBASE="Airbase", +ZONE="Zone", +OPSZONE="OpsZone" +} +TARGET.Category={ +AIRCRAFT="Aircraft", +GROUND="Ground", +NAVAL="Naval", +AIRBASE="Airbase", +COORDINATE="Coordinate", +ZONE="Zone", +} +TARGET.ObjectStatus={ +ALIVE="Alive", +DEAD="Dead", +DAMAGED="Damaged", +} +_TARGETID=0 +TARGET.version="0.7.1" +function TARGET:New(TargetObject) +local self=BASE:Inherit(self,FSM:New()) +_TARGETID=_TARGETID+1 +self.uid=_TARGETID +if TargetObject then +self:AddObject(TargetObject) +end +self:SetPriority() +self:SetImportance() +self.TStatus=30 +self.lid=string.format("TARGET #%03d | ",_TARGETID) +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Alive") +self:AddTransition("*","Status","*") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("*","ObjectDamaged","*") +self:AddTransition("*","ObjectDestroyed","*") +self:AddTransition("*","ObjectDead","*") +self:AddTransition("*","Damaged","Damaged") +self:AddTransition("*","Destroyed","Dead") +self:AddTransition("*","Dead","Dead") +self:__Start(-0.1) +return self +end +function TARGET:AddObject(Object) +if Object:IsInstanceOf("SET_GROUP")or +Object:IsInstanceOf("SET_UNIT")or +Object:IsInstanceOf("SET_STATIC")or +Object:IsInstanceOf("SET_SCENERY")or +Object:IsInstanceOf("SET_OPSGROUP")or +Object:IsInstanceOf("SET_OPSZONE")then +local set=Object +for _,object in pairs(set.Set)do +self:AddObject(object) +end +elseif Object:IsInstanceOf("SET_ZONE")then +local set=Object +set:SortByName() +for index,ZoneName in pairs(set.Index)do +local zone=set.Set[ZoneName] +self:_AddObject(zone) +end +else +if Object:IsInstanceOf("OPSGROUP")then +self:_AddObject(Object:GetGroup()) +else +self:_AddObject(Object) +end +end +return self +end +function TARGET:SetPriority(Priority) +self.prio=Priority or 50 +return self +end +function TARGET:SetImportance(Importance) +self.importance=Importance +return self +end +function TARGET:AddConditionStart(ConditionFunction,...) +local condition={} +condition.func=ConditionFunction +condition.arg={} +if arg then +condition.arg=arg +end +table.insert(self.conditionStart,condition) +return self +end +function TARGET:AddConditionStop(ConditionFunction,...) +local condition={} +condition.func=ConditionFunction +condition.arg={} +if arg then +condition.arg=arg +end +table.insert(self.conditionStop,condition) +return self +end +function TARGET:EvalConditionsAll(Conditions) +for _,_condition in pairs(Conditions or{})do +local condition=_condition +local istrue=condition.func(unpack(condition.arg)) +if not istrue then +return false +end +end +return true +end +function TARGET:EvalConditionsAny(Conditions) +for _,_condition in pairs(Conditions or{})do +local condition=_condition +local istrue=condition.func(unpack(condition.arg)) +if istrue then +return true +end +end +return false +end +function TARGET:AddResource(MissionType,Nmin,Nmax,Attributes,Properties) +if Attributes and type(Attributes)~="table"then +Attributes={Attributes} +end +if Properties and type(Properties)~="table"then +Properties={Properties} +end +local resource={} +resource.MissionType=MissionType +resource.Nmin=Nmin or 1 +resource.Nmax=Nmax or 1 +resource.Attributes=Attributes or{} +resource.Properties=Properties or{} +self.resources=self.resources or{} +table.insert(self.resources,resource) +if self.verbose>10 then +local text="Resource:" +for _,_r in pairs(self.resources)do +local r=_r +text=text..string.format("\nmission=%s, Nmin=%d, Nmax=%d, attribute=%s, properties=%s",r.MissionType,r.Nmin,r.Nmax,tostring(r.Attributes[1]),tostring(r.Properties[1])) +end +self:I(self.lid..text) +end +return resource +end +function TARGET:IsAlive() +for _,_target in pairs(self.targets)do +local target=_target +if target.Status~=TARGET.ObjectStatus.DEAD then +if self.isDestroyed then +self:E(self.lid..string.format("ERROR: target is DESTROYED but target object status is not DEAD but %s for object %s",target.Status,target.Name)) +elseif self:IsDead()then +self:E(self.lid..string.format("ERROR: target is DEAD but target object status is not DEAD but %s for object %s",target.Status,target.Name)) +end +return true +end +end +return false +end +function TARGET:IsDestroyed() +return self.isDestroyed +end +function TARGET:IsDead() +local is=self:Is("Dead") +return is +end +function TARGET:IsTargetDead(TargetObject) +local isDead=TargetObject.Status==TARGET.ObjectStatus.DEAD +return isDead +end +function TARGET:IsTargetAlive(TargetObject) +local isAlive=TargetObject.Status==TARGET.ObjectStatus.ALIVE +return isAlive +end +function TARGET:onafterStart(From,Event,To) +self:T({From,Event,To}) +local text=string.format("Starting Target") +self:T(self.lid..text) +self:HandleEvent(EVENTS.Dead,self.OnEventUnitDeadOrLost) +self:HandleEvent(EVENTS.UnitLost,self.OnEventUnitDeadOrLost) +self:HandleEvent(EVENTS.RemoveUnit,self.OnEventUnitDeadOrLost) +self:__Status(-1) +return self +end +function TARGET:onafterStatus(From,Event,To) +local fsmstate=self:GetState() +local damaged=false +for i,_target in pairs(self.targets)do +local target=_target +local life=target.Life +target.Life=self:GetTargetLife(target) +if target.Life>target.Life0 then +local delta=2*(target.Life-target.Life0) +target.Life0=target.Life0+delta +life=target.Life0 +self.life0=self.life0+delta +end +if target.Life object dead now for target object %s!",tostring(target.Name))) +self:ObjectDead(target) +damaged=true +end +end +if damaged then +self:Damaged() +end +if self.verbose>=1 then +local text=string.format("%s: Targets=%d/%d [%d, %d], Life=%.1f/%.1f, Damage=%.1f", +fsmstate,self:CountTargets(),self.N0,self.Ndestroyed,self.Ndead,self:GetLife(),self:GetLife0(),self:GetDamage()) +if self:CountTargets()==0 or self:GetDamage()>=100 then +text=text.." - Dead!" +elseif damaged then +text=text.." - Damaged!" +end +self:I(self.lid..text) +end +if self.verbose>=2 then +local text="Target:" +for i,_target in pairs(self.targets)do +local target=_target +local damage=(1-target.Life/target.Life0)*100 +text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f, N0=%d, Ndestroyed=%d, Ndead=%d", +i,target.Type,target.Name,target.Status,target.Life,target.Life0,damage,target.N0,target.Ndestroyed,target.Ndead) +end +self:I(self.lid..text) +end +if self:IsAlive()and(self:CountTargets()==0 or self:GetDamage()>=100)then +self:Dead() +end +for i,_target in pairs(self.targets)do +local target=_target +if target.Ndestroyed>target.N0 then +self:E(self.lid..string.format("ERROR: Number of destroyed target objects greater than number of initial target objects: %d>%d!",target.Ndestroyed,target.N0)) +end +if target.Ndestroyed>target.N0 then +self:E(self.lid..string.format("ERROR: Number of dead target objects greater than number of initial target objects: %d>%d!",target.Ndead,target.N0)) +end +end +if self:IsAlive()then +self:__Status(-self.TStatus) +else +self:I(self.lid..string.format("Target is not alive any more ==> no further status updates are carried out")) +end +return self +end +function TARGET:onafterObjectDamaged(From,Event,To,Target) +self:T({From,Event,To}) +self:T(self.lid..string.format("Object %s damaged",Target.Name)) +return self +end +function TARGET:onafterObjectDestroyed(From,Event,To,Target) +self:T({From,Event,To}) +self:T(self.lid..string.format("Object %s destroyed",Target.Name)) +self.Ndestroyed=self.Ndestroyed+1 +Target.Ndestroyed=Target.Ndestroyed+1 +Target.Life=0 +self:ObjectDead(Target) +return self +end +function TARGET:onafterObjectDead(From,Event,To,Target) +self:T({From,Event,To}) +self:T(self.lid..string.format("Object %s dead",Target.Name)) +Target.Status=TARGET.ObjectStatus.DEAD +Target.Ndead=Target.Ndead+1 +Target.Life=0 +self.Ndead=self.Ndead+1 +local dead=true +for _,_target in pairs(self.targets)do +local target=_target +if target.Status==TARGET.ObjectStatus.ALIVE then +dead=false +break +end +end +if dead then +if self.Ndestroyed==self.Ntargets0 then +self.isDestroyed=true +self:Destroyed() +else +self:Dead() +end +else +self:Damaged() +end +return self +end +function TARGET:onafterDamaged(From,Event,To) +self:T({From,Event,To}) +self:T(self.lid..string.format("TARGET damaged")) +return self +end +function TARGET:onafterDestroyed(From,Event,To) +self:T({From,Event,To}) +self:T(self.lid..string.format("TARGET destroyed")) +self:Dead() +return self +end +function TARGET:onafterDead(From,Event,To) +self:T({From,Event,To}) +self:T(self.lid..string.format("TARGET dead")) +return self +end +function TARGET:OnEventUnitDeadOrLost(EventData) +local Name=EventData and EventData.IniUnitName or nil +if self:IsElement(Name)and not self:IsCasualty(Name)then +self:T(self.lid..string.format("EVENT ID=%d: Unit %s dead or lost!",EventData.id,tostring(Name))) +table.insert(self.casualties,Name) +local target=self:GetTargetByName(EventData.IniGroupName) +if not target then +target=self:GetTargetByName(EventData.IniUnitName) +end +if target then +local Ndead=target.Ndead +local Ndestroyed=target.Ndestroyed +if EventData.id==EVENTS.RemoveUnit then +Ndead=Ndead+1 +else +Ndestroyed=Ndestroyed+1 +Ndead=Ndead+1 +end +if Ndead==target.N0 then +if Ndestroyed>=target.N0 then +self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed",EventData.id,tostring(target.Name))) +target.Life=0 +self:ObjectDestroyed(target) +else +self:T2(self.lid..string.format("EVENT ID=%d: target %s removed ==> dead",EventData.id,tostring(target.Name))) +target.Life=0 +self:ObjectDead(target) +end +end +end +end +return self +end +function TARGET:_AddObject(Object) +local target={} +target.N0=0 +target.Ndead=0 +target.Ndestroyed=0 +if Object:IsInstanceOf("GROUP")then +local group=Object +target.Type=TARGET.ObjectType.GROUP +target.Name=group:GetName() +target.Coordinate=group:GetCoordinate() +local units=group:GetUnits() +target.Life=0;target.Life0=0 +for _,_unit in pairs(units or{})do +local unit=_unit +local life=unit:GetLife() +target.Life=target.Life+life +target.Life0=target.Life0+math.max(unit:GetLife0(),life) +self.threatlevel0=self.threatlevel0+unit:GetThreatLevel() +table.insert(self.elements,unit:GetName()) +target.N0=target.N0+1 +end +elseif Object:IsInstanceOf("UNIT")then +local unit=Object +target.Type=TARGET.ObjectType.UNIT +target.Name=unit:GetName() +target.Coordinate=unit:GetCoordinate() +if unit then +target.Life=unit:GetLife() +target.Life0=math.max(unit:GetLife0(),target.Life) +self.threatlevel0=self.threatlevel0+unit:GetThreatLevel() +table.insert(self.elements,unit:GetName()) +target.N0=target.N0+1 +end +elseif Object:IsInstanceOf("STATIC")then +local static=Object +target.Type=TARGET.ObjectType.STATIC +target.Name=static:GetName() +target.Coordinate=static:GetCoordinate() +if static and static:IsAlive()then +target.Life0=static:GetLife0() +target.Life=static:GetLife() +target.N0=target.N0+1 +table.insert(self.elements,target.Name) +end +elseif Object:IsInstanceOf("SCENERY")then +local scenery=Object +target.Type=TARGET.ObjectType.SCENERY +target.Name=scenery:GetName() +target.Coordinate=scenery:GetCoordinate() +target.Life0=scenery:GetLife0() +if target.Life0==0 then target.Life0=1 end +target.Life=scenery:GetLife() +target.N0=target.N0+1 +table.insert(self.elements,target.Name) +elseif Object:IsInstanceOf("AIRBASE")then +local airbase=Object +target.Type=TARGET.ObjectType.AIRBASE +target.Name=airbase:GetName() +target.Coordinate=airbase:GetCoordinate() +target.Life0=1 +target.Life=1 +target.N0=target.N0+1 +table.insert(self.elements,target.Name) +elseif Object:IsInstanceOf("COORDINATE")then +local coord=UTILS.DeepCopy(Object) +target.Type=TARGET.ObjectType.COORDINATE +target.Name=coord:ToStringMGRS() +target.Coordinate=coord +target.Life0=1 +target.Life=1 +target.N0=target.N0+1 +elseif Object:IsInstanceOf("ZONE_BASE")then +local zone=Object +Object=zone +target.Type=TARGET.ObjectType.ZONE +target.Name=zone:GetName() +target.Coordinate=zone:GetCoordinate() +target.Life0=1 +target.Life=1 +target.N0=target.N0+1 +elseif Object:IsInstanceOf("OPSZONE")then +local zone=Object +Object=zone +target.Type=TARGET.ObjectType.OPSZONE +target.Name=zone:GetName() +target.Coordinate=zone:GetCoordinate() +target.N0=target.N0+1 +target.Life0=1 +target.Life=1 +else +self:E(self.lid.."ERROR: Unknown object type!") +return nil +end +self.life=self.life+target.Life +self.life0=self.life0+target.Life0 +self.N0=self.N0+target.N0 +self.Ntargets0=self.Ntargets0+1 +self.targetcounter=self.targetcounter+1 +target.ID=self.targetcounter +target.Status=TARGET.ObjectStatus.ALIVE +target.Object=Object +table.insert(self.targets,target) +if self.name==nil then +self.name=self:GetTargetName(target) +end +if self.category==nil then +self.category=self:GetTargetCategory(target) +end +return self +end +function TARGET:GetLife0() +return self.life0 +end +function TARGET:GetDamage() +local life=self:GetLife()/self:GetLife0() +local damage=1-life +return damage*100 +end +function TARGET:GetTargetLife(Target) +if Target.Type==TARGET.ObjectType.GROUP then +if Target.Object and Target.Object:IsAlive()then +local units=Target.Object:GetUnits() +local life=0 +for _,_unit in pairs(units or{})do +local unit=_unit +life=life+unit:GetLife() +end +return life +else +return 0 +end +elseif Target.Type==TARGET.ObjectType.UNIT then +local unit=Target.Object +if unit and unit:IsAlive()then +local life=unit:GetLife() +return life +else +return 0 +end +elseif Target.Type==TARGET.ObjectType.STATIC then +if Target.Object and Target.Object:IsAlive()then +local life=Target.Object:GetLife() +return life +else +return 0 +end +elseif Target.Type==TARGET.ObjectType.SCENERY then +if Target.Object and Target.Object:IsAlive(25)then +local life=Target.Object:GetLife() +return life +else +return 0 +end +elseif Target.Type==TARGET.ObjectType.AIRBASE then +if Target.Status==TARGET.ObjectStatus.ALIVE then +return 1 +else +return 0 +end +elseif Target.Type==TARGET.ObjectType.COORDINATE then +return 1 +elseif Target.Type==TARGET.ObjectType.ZONE or Target.Type==TARGET.ObjectType.OPSZONE then +return 1 +else +self:E("ERROR: unknown target object type in GetTargetLife!") +end +return self +end +function TARGET:GetLife() +local N=0 +for _,_target in pairs(self.targets)do +local Target=_target +N=N+self:GetTargetLife(Target) +end +return N +end +function TARGET:GetTargetThreatLevelMax(Target) +if Target.Type==TARGET.ObjectType.GROUP then +local group=Target.Object +if group and group:IsAlive()then +local tl=group:GetThreatLevel() +return tl +else +return 0 +end +elseif Target.Type==TARGET.ObjectType.UNIT then +local unit=Target.Object +if unit and unit:IsAlive()then +local life=unit:GetThreatLevel() +return life +else +return 0 +end +elseif Target.Type==TARGET.ObjectType.STATIC then +return 0 +elseif Target.Type==TARGET.ObjectType.SCENERY then +return 0 +elseif Target.Type==TARGET.ObjectType.AIRBASE then +return 0 +elseif Target.Type==TARGET.ObjectType.COORDINATE then +return 0 +elseif Target.Type==TARGET.ObjectType.ZONE then +local zone=Target.Object +local foundunits={} +if zone:IsInstanceOf("ZONE_RADIUS")or zone:IsInstanceOf("ZONE_POLYGON")then +zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}) +foundunits=zone:GetScannedSetUnit() +else +foundunits=SET_UNIT:New():FilterZones({zone}):FilterOnce() +end +local ThreatMax=foundunits:GetThreatLevelMax()or 0 +return ThreatMax +elseif Target.Type==TARGET.ObjectType.OPSZONE then +local unitset=Target.Object:GetScannedUnitSet() +local ThreatMax=unitset:GetThreatLevelMax() +return ThreatMax +else +self:E("ERROR: unknown target object type in GetTargetThreatLevel!") +return 0 +end +return self +end +function TARGET:GetThreatLevelMax() +local N=0 +for _,_target in pairs(self.targets)do +local Target=_target +local n=self:GetTargetThreatLevelMax(Target) +if n>N then +N=n +end +end +return N +end +function TARGET:GetTargetVec2(Target) +local vec3=self:GetTargetVec3(Target) +if vec3 then +return{x=vec3.x,y=vec3.z} +end +return nil +end +function TARGET:GetTargetVec3(Target,Average) +if Target.Type==TARGET.ObjectType.GROUP then +local object=Target.Object +if object and object:IsAlive()then +local vec3=object:GetVec3() +if Average then +vec3=object:GetAverageVec3() +end +if vec3 then +return vec3 +else +return nil +end +else +return nil +end +elseif Target.Type==TARGET.ObjectType.UNIT then +local object=Target.Object +if object and object:IsAlive()then +local vec3=object:GetVec3() +return vec3 +else +return nil +end +elseif Target.Type==TARGET.ObjectType.STATIC then +local object=Target.Object +if object and object:IsAlive()then +local vec3=object:GetVec3() +return vec3 +else +return nil +end +elseif Target.Type==TARGET.ObjectType.SCENERY then +local object=Target.Object +if object then +local vec3=object:GetVec3() +return vec3 +else +return nil +end +elseif Target.Type==TARGET.ObjectType.AIRBASE then +local object=Target.Object +local vec3=object:GetVec3() +return vec3 +elseif Target.Type==TARGET.ObjectType.COORDINATE then +local object=Target.Object +local vec3={x=object.x,y=object.y,z=object.z} +return vec3 +elseif Target.Type==TARGET.ObjectType.ZONE then +local object=Target.Object +local vec3=object:GetVec3() +return vec3 +elseif Target.Type==TARGET.ObjectType.OPSZONE then +local object=Target.Object +local vec3=object:GetZone():GetVec3() +return vec3 +end +self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get Vec3") +end +function TARGET:GetTargetHeading(Target) +if Target.Type==TARGET.ObjectType.GROUP then +local object=Target.Object +if object and object:IsAlive()then +local heading=object:GetHeading() +if heading then +return heading +else +return nil +end +else +return nil +end +elseif Target.Type==TARGET.ObjectType.UNIT then +local object=Target.Object +if object and object:IsAlive()then +local heading=object:GetHeading() +return heading +else +return nil +end +elseif Target.Type==TARGET.ObjectType.STATIC then +local object=Target.Object +if object and object:IsAlive()then +local heading=object:GetHeading() +return heading +else +return nil +end +elseif Target.Type==TARGET.ObjectType.SCENERY then +local object=Target.Object +if object then +local heading=object:GetHeading() +return heading +else +return nil +end +elseif Target.Type==TARGET.ObjectType.AIRBASE then +local object=Target.Object +return 0 +elseif Target.Type==TARGET.ObjectType.COORDINATE then +local object=Target.Object +return 0 +elseif Target.Type==TARGET.ObjectType.ZONE or Target.Type==TARGET.ObjectType.OPSZONE then +local object=Target.Object +return 0 +end +self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get heading") +end +function TARGET:GetTargetCoordinate(Target,Average) +if Target.Type==TARGET.ObjectType.COORDINATE then +return Target.Object +else +local vec3=self:GetTargetVec3(Target,Average) +if vec3 then +Target.Coordinate.x=vec3.x +Target.Coordinate.y=vec3.y +Target.Coordinate.z=vec3.z +end +return Target.Coordinate +end +return nil +end +function TARGET:GetTargetName(Target) +if Target.Type==TARGET.ObjectType.GROUP then +if Target.Object and Target.Object:IsAlive()then +return Target.Object:GetName() +end +elseif Target.Type==TARGET.ObjectType.UNIT then +if Target.Object and Target.Object:IsAlive()then +return Target.Object:GetName() +end +elseif Target.Type==TARGET.ObjectType.STATIC then +if Target.Object and Target.Object:IsAlive()then +return Target.Object:GetName() +end +elseif Target.Type==TARGET.ObjectType.AIRBASE then +if Target.Status==TARGET.ObjectStatus.ALIVE then +return Target.Object:GetName() +end +elseif Target.Type==TARGET.ObjectType.COORDINATE then +local coord=Target.Object +return coord:ToStringMGRS() +elseif Target.Type==TARGET.ObjectType.ZONE then +local Zone=Target.Object +return Zone:GetName() +elseif Target.Type==TARGET.ObjectType.SCENERY then +local Zone=Target.Object +return Zone:GetName() +end +return"Unknown" +end +function TARGET:GetName() +local name=self.name or"Unknown" +return name +end +function TARGET:GetVec2() +for _,_target in pairs(self.targets)do +local Target=_target +local coordinate=self:GetTargetVec2(Target) +if coordinate then +return coordinate +end +end +self:E(self.lid..string.format("ERROR: Cannot get Vec2 of target %s",self.name)) +return nil +end +function TARGET:GetVec3() +for _,_target in pairs(self.targets)do +local Target=_target +local coordinate=self:GetTargetVec3(Target) +if coordinate then +return coordinate +end +end +self:E(self.lid..string.format("ERROR: Cannot get Vec3 of target %s",self.name)) +return nil +end +function TARGET:GetCoordinate() +for _,_target in pairs(self.targets)do +local Target=_target +local coordinate=self:GetTargetCoordinate(Target) +if coordinate then +return coordinate +end +end +self:E(self.lid..string.format("ERROR: Cannot get coordinate of target %s",tostring(self.name))) +return nil +end +function TARGET:GetAverageCoordinate() +for _,_target in pairs(self.targets)do +local Target=_target +local coordinate=self:GetTargetCoordinate(Target,true) +if coordinate then +return coordinate +end +end +self:E(self.lid..string.format("ERROR: Cannot get average coordinate of target %s",tostring(self.name))) +return nil +end +function TARGET:GetCoordinates() +local coordinates={} +for _,_target in pairs(self.targets)do +local target=_target +local coordinate=self:GetTargetCoordinate(target) +if coordinate then +table.insert(coordinates,coordinate) +end +end +return coordinates +end +function TARGET:GetHeading() +for _,_target in pairs(self.targets)do +local Target=_target +local heading=self:GetTargetHeading(Target) +if heading then +return heading +end +end +self:E(self.lid..string.format("ERROR: Cannot get heading of target %s",tostring(self.name))) +return nil +end +function TARGET:GetCategory() +return self.category +end +function TARGET:GetTargetCategory(Target) +local category=nil +if Target.Type==TARGET.ObjectType.GROUP then +if Target.Object and Target.Object:IsAlive()~=nil then +local group=Target.Object +local cat=group:GetCategory() +if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then +category=TARGET.Category.AIRCRAFT +elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then +category=TARGET.Category.GROUND +elseif cat==Group.Category.SHIP then +category=TARGET.Category.NAVAL +end +end +elseif Target.Type==TARGET.ObjectType.UNIT then +if Target.Object and Target.Object:IsAlive()~=nil then +local unit=Target.Object +local group=unit:GetGroup() +local cat=group:GetCategory() +if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then +category=TARGET.Category.AIRCRAFT +elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then +category=TARGET.Category.GROUND +elseif cat==Group.Category.SHIP then +category=TARGET.Category.NAVAL +end +end +elseif Target.Type==TARGET.ObjectType.STATIC then +return TARGET.Category.GROUND +elseif Target.Type==TARGET.ObjectType.SCENERY then +return TARGET.Category.GROUND +elseif Target.Type==TARGET.ObjectType.AIRBASE then +return TARGET.Category.AIRBASE +elseif Target.Type==TARGET.ObjectType.COORDINATE then +return TARGET.Category.COORDINATE +elseif Target.Type==TARGET.ObjectType.ZONE then +return TARGET.Category.ZONE +elseif Target.Type==TARGET.ObjectType.OPSZONE then +return TARGET.Category.OPSZONE +else +self:E("ERROR: unknown target category!") +end +return category +end +function TARGET:GetTargetCoalition(Target) +local coal=coalition.side.NEUTRAL +if Target.Type==TARGET.ObjectType.GROUP then +if Target.Object and Target.Object:IsAlive()~=nil then +local object=Target.Object +coal=object:GetCoalition() +end +elseif Target.Type==TARGET.ObjectType.UNIT then +if Target.Object and Target.Object:IsAlive()~=nil then +local object=Target.Object +coal=object:GetCoalition() +end +elseif Target.Type==TARGET.ObjectType.STATIC then +local object=Target.Object +coal=object:GetCoalition() +elseif Target.Type==TARGET.ObjectType.SCENERY then +elseif Target.Type==TARGET.ObjectType.AIRBASE then +local object=Target.Object +coal=object:GetCoalition() +elseif Target.Type==TARGET.ObjectType.COORDINATE then +elseif Target.Type==TARGET.ObjectType.ZONE then +elseif Target.Type==TARGET.ObjectType.OPSZONE then +local object=Target.Object +coal=object:GetOwner() +else +self:E("ERROR: unknown target category!") +end +return coal +end +function TARGET:GetTargetByName(ObjectName) +for _,_target in pairs(self.targets)do +local target=_target +if ObjectName==target.Name then +return target +end +end +return nil +end +function TARGET:GetObjective(RefCoordinate,Coalitions) +if RefCoordinate then +local dmin=math.huge +local tmin=nil +for _,_target in pairs(self.targets)do +local target=_target +if target.Status~=TARGET.ObjectStatus.DEAD and(Coalitions==nil or UTILS.IsInTable(UTILS.EnsureTable(Coalitions),self:GetTargetCoalition(target)))then +local vec3=self:GetTargetVec3(target) +local d=UTILS.VecDist3D(vec3,RefCoordinate) +if d1 then +if Coalitions==nil or UTILS.IsInTable(UTILS.EnsureTable(Coalitions),unit:GetCoalition())then +N=N+1 +end +end +end +elseif Target.Type==TARGET.ObjectType.UNIT then +local target=Target.Object +if target and target:IsAlive()~=nil and target:GetLife()>1 then +if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetCoalition())then +N=N+1 +end +end +elseif Target.Type==TARGET.ObjectType.STATIC then +local target=Target.Object +if target and target:IsAlive()then +if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetCoalition())then +N=N+1 +end +end +elseif Target.Type==TARGET.ObjectType.SCENERY then +if Target.Status~=TARGET.ObjectStatus.DEAD then +N=N+1 +end +elseif Target.Type==TARGET.ObjectType.AIRBASE then +local target=Target.Object +if Target.Status==TARGET.ObjectStatus.ALIVE then +if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetCoalition())then +N=N+1 +end +end +elseif Target.Type==TARGET.ObjectType.COORDINATE then +N=N+1 +elseif Target.Type==TARGET.ObjectType.ZONE then +N=N+1 +elseif Target.Type==TARGET.ObjectType.OPSZONE then +local target=Target.Object +if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetOwner())then +N=N+1 +end +else +self:E(self.lid.."ERROR: Unknown target type! Cannot count targets") +end +return N +end +function TARGET:CountTargets(Coalitions) +local N=0 +for _,_target in pairs(self.targets)do +local Target=_target +N=N+self:CountObjectives(Target,Coalitions) +end +return N +end +function TARGET:IsElement(Name) +if Name==nil then +return false +end +for _,name in pairs(self.elements)do +if name==Name then +return true +end +end +return false +end +function TARGET:IsCasualty(Name) +if Name==nil then +return false +end +for _,name in pairs(self.casualties)do +if tostring(name)==tostring(Name)then +return true +end +end +return false +end +EASYGCICAP={ +ClassName="EASYGCICAP", +overhead=0.75, +capgrouping=2, +airbasename=nil, +airbase=nil, +coalition="blue", +alias=nil, +wings={}, +Intel=nil, +resurrection=900, +capspeed=300, +capalt=25000, +capdir=45, +capleg=15, +maxinterceptsize=2, +missionrange=100, +noalert5=4, +ManagedAW={}, +ManagedSQ={}, +ManagedCP={}, +ManagedTK={}, +ManagedEWR={}, +ManagedREC={}, +MaxAliveMissions=8, +debug=false, +engagerange=50, +repeatsonfailure=3, +GoZoneSet=nil, +NoGoZoneSet=nil, +ConflictZoneSet=nil, +Monitor=false, +TankerInvisible=true, +CapFormation=nil, +ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Air"}, +} +EASYGCICAP.version="0.1.30" +function EASYGCICAP:New(Alias,AirbaseName,Coalition,EWRName) +local self=BASE:Inherit(self,FSM:New()) +self.alias=Alias or AirbaseName.." CAP Wing" +self.coalitionname=string.lower(Coalition)or"blue" +self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED +self.wings={} +if type(EWRName)=="string"then EWRName={EWRName}end +self.EWRName=EWRName +self.airbasename=AirbaseName +self.airbase=AIRBASE:FindByName(self.airbasename) +self.GoZoneSet=SET_ZONE:New() +self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() +self.resurrection=900 +self.capspeed=300 +self.capalt=25000 +self.capdir=90 +self.capleg=15 +self.capgrouping=2 +self.missionrange=100 +self.noalert5=2 +self.MaxAliveMissions=8 +self.engagerange=50 +self.repeatsonfailure=3 +self.Monitor=false +self.TankerInvisible=true +self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Air"} +self.lid=string.format("EASYGCICAP %s | ",self.alias) +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("Running","Stop","Stopped") +self:AddTransition("*","Status","*") +self:AddAirwing(self.airbasename,self.alias,self.CapZoneName) +self:I(self.lid.."Created new instance (v"..self.version..")") +self:__Start(math.random(6,12)) +return self +end +function EASYGCICAP:GetAirwing(AirbaseName) +self:T(self.lid.."GetAirwing") +if self.wings[AirbaseName]then +return self.wings[AirbaseName][1] +end +return nil +end +function EASYGCICAP:GetAirwingTable() +self:T(self.lid.."GetAirwingTable") +local Wingtable={} +for _,_object in pairs(self.wings or{})do +table.insert(Wingtable,_object[1]) +end +return Wingtable +end +function EASYGCICAP:SetFuelLow(Percent) +self:T(self.lid.."SetFuelLow") +self.FuelLowThreshold=Percent or 25 +return self +end +function EASYGCICAP:ShowPatrolPointMarkers(onoff) +if onoff then +self.showpatrolpointmarks=true +else +self.showpatrolpointmarks=false +end +return self +end +function EASYGCICAP:SetFuelCritical(Percent) +self:T(self.lid.."SetFuelCritical") +self.FuelCriticalThreshold=Percent or 10 +return self +end +function EASYGCICAP:SetCAPFormation(Formation) +self:T(self.lid.."SetCAPFormation") +self.CapFormation=Formation +return self +end +function EASYGCICAP:SetTankerAndAWACSInvisible(Switch) +self:T(self.lid.."SetTankerAndAWACSInvisible") +self.TankerInvisible=Switch +return self +end +function EASYGCICAP:_CountAliveAuftrags() +local alive=0 +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +alive=alive+1 +end +end +return alive +end +function EASYGCICAP:SetMaxAliveMissions(Maxiumum) +self:T(self.lid.."SetMaxAliveMissions") +self.MaxAliveMissions=Maxiumum or 8 +return self +end +function EASYGCICAP:SetDefaultResurrection(Seconds) +self:T(self.lid.."SetDefaultResurrection") +self.resurrection=Seconds or 900 +return self +end +function EASYGCICAP:SetDefaultRepeatOnFailure(Retries) +self:T(self.lid.."SetDefaultRepeatOnFailure") +self.repeatsonfailure=Retries or 3 +return self +end +function EASYGCICAP:SetDefaultTakeOffType(Takeoff) +self:T(self.lid.."SetDefaultTakeOffType") +self.defaulttakeofftype=Takeoff or"hot" +return self +end +function EASYGCICAP:SetDefaultCAPSpeed(Speed) +self:T(self.lid.."SetDefaultSpeed") +self.capspeed=Speed or 300 +return self +end +function EASYGCICAP:SetDefaultCAPAlt(Altitude) +self:T(self.lid.."SetDefaultAltitude") +self.capalt=Altitude or 25000 +return self +end +function EASYGCICAP:SetDefaultCAPDirection(Direction) +self:T(self.lid.."SetDefaultDirection") +self.capdir=Direction or 90 +return self +end +function EASYGCICAP:SetDefaultCAPLeg(Leg) +self:T(self.lid.."SetDefaultLeg") +self.capleg=Leg or 15 +return self +end +function EASYGCICAP:SetDefaultCAPGrouping(Grouping) +self:T(self.lid.."SetDefaultCAPGrouping") +self.capgrouping=Grouping or 2 +return self +end +function EASYGCICAP:SetDefaultMissionRange(Range) +self:T(self.lid.."SetDefaultMissionRange") +self.missionrange=Range or 100 +return self +end +function EASYGCICAP:SetDefaultNumberAlert5Standby(Airframes) +self:T(self.lid.."SetDefaultNumberAlert5Standby") +self.noalert5=math.abs(Airframes)or 2 +return self +end +function EASYGCICAP:SetDefaultEngageRange(Range) +self:T(self.lid.."SetDefaultEngageRange") +self.engagerange=Range or 50 +return self +end +function EASYGCICAP:SetDefaultOverhead(Overhead) +self:T(self.lid.."SetDefaultOverhead") +self.overhead=Overhead or 0.75 +return self +end +function EASYGCICAP:SetDefaultDespawnAfterLanding() +self:T(self.lid.."SetDefaultDespawnAfterLanding") +self.DespawnAfterLanding=true +self.DespawnAfterHolding=false +return self +end +function EASYGCICAP:SetDefaultDespawnAfterHolding() +self:T(self.lid.."SetDefaultDespawnAfterLanding") +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +return self +end +function EASYGCICAP:SetCapStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function EASYGCICAP:SetCAPEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Air"} +return self +end +function EASYGCICAP:AddAirwing(Airbasename,Alias) +self:T(self.lid.."AddAirwing "..Airbasename) +local AWEntry={} +AWEntry.AirbaseName=Airbasename +AWEntry.Alias=Alias +self.ManagedAW[Airbasename]=AWEntry +return self +end +function EASYGCICAP:_CreateAirwings() +self:T(self.lid.."_CreateAirwings") +for airbase,data in pairs(self.ManagedAW)do +local wing=data +local afb=wing.AirbaseName +local alias=wing.Alias +self:_AddAirwing(airbase,alias) +end +return self +end +function EASYGCICAP:_AddAirwing(Airbasename,Alias) +self:T(self.lid.."_AddAirwing "..Airbasename) +local CapFormation=self.CapFormation +local DespawnAfterLanding=self.DespawnAfterLanding +local DespawnAfterHolding=self.DespawnAfterHolding +local check=STATIC:FindByName(Airbasename,false)or UNIT:FindByName(Airbasename) +if check==nil then +MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end +local CAP_Wing=AIRWING:New(Airbasename,Alias) +CAP_Wing:SetVerbosityLevel(0) +CAP_Wing:SetReportOff() +CAP_Wing:SetMarker(false) +CAP_Wing:SetAirbase(AIRBASE:FindByName(Airbasename)) +CAP_Wing:SetRespawnAfterDestroyed() +CAP_Wing:SetNumberCAP(self.capgrouping) +CAP_Wing:SetCapCloseRaceTrack(true) +if self.showpatrolpointmarks then +CAP_Wing:ShowPatrolPointMarkers(true) +end +if self.capOptionVaryStartTime then +CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime) +end +if CapFormation then +CAP_Wing:SetCAPFormation(CapFormation) +end +if#self.ManagedTK>0 then +CAP_Wing:SetNumberTankerBoom(1) +CAP_Wing:SetNumberTankerProbe(1) +end +if#self.ManagedEWR>0 then +CAP_Wing:SetNumberAWACS(1) +end +if#self.ManagedREC>0 then +CAP_Wing:SetNumberRecon(1) +end +CAP_Wing:SetTakeoffType(self.defaulttakeofftype) +CAP_Wing:SetLowFuelThreshold(0.3) +CAP_Wing.RandomAssetScore=math.random(50,100) +CAP_Wing:Start() +local Intel=self.Intel +local TankerInvisible=self.TankerInvisible +local engagerange=self.engagerange +local GoZoneSet=self.GoZoneSet +local NoGoZoneSet=self.NoGoZoneSet +local FuelLow=self.FuelLowThreshold or 25 +local FuelCritical=self.FuelCriticalThreshold or 10 +local EngageTypes=self.EngageTargetTypes or{"Air"} +function CAP_Wing:onbeforeFlightOnMission(From,Event,To,Flightgroup,Mission) +local flightgroup=Flightgroup +if DespawnAfterLanding then +flightgroup:SetDespawnAfterLanding() +elseif DespawnAfterHolding then +flightgroup:SetDespawnAfterHolding() +end +flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename)) +flightgroup:GetGroup():CommandEPLRS(true,5) +flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch() +flightgroup:GetGroup():SetOptionLandingOverheadBreak() +if Mission.type~=AUFTRAG.Type.TANKER and Mission.type~=AUFTRAG.Type.AWACS and Mission.type~=AUFTRAG.Type.RECON then +flightgroup:SetDetection(true) +flightgroup:SetEngageDetectedOn(engagerange,EngageTypes,GoZoneSet,NoGoZoneSet) +flightgroup:SetOutOfAAMRTB() +flightgroup:SetFuelLowRTB(true) +flightgroup:SetFuelLowThreshold(FuelLow) +flightgroup:SetFuelCriticalRTB(true) +flightgroup:SetFuelCriticalThreshold(FuelCritical) +if CapFormation then +flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation) +end +end +if Mission.type==AUFTRAG.Type.TANKER or Mission.type==AUFTRAG.Type.AWACS or Mission.type==AUFTRAG.Type.RECON then +if TankerInvisible then +flightgroup:GetGroup():SetCommandInvisible(true) +end +if Mission.type==AUFTRAG.Type.RECON then +flightgroup:SetDetection(true) +end +end +flightgroup:GetGroup():OptionROTEvadeFire() +flightgroup:SetFuelLowRTB(true) +Intel:AddAgent(flightgroup) +if DespawnAfterHolding then +function flightgroup:onbeforeHolding(From,Event,To) +self:Despawn(1,true) +end +end +end +if self.noalert5>0 then +local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +alert:SetRequiredAssets(self.noalert5) +alert:SetRepeat(99) +CAP_Wing:AddMission(alert) +table.insert(self.ListOfAuftrag,alert) +end +self.wings[Airbasename]={CAP_Wing,AIRBASE:FindByName(Airbasename):GetZone(),Airbasename} +return self +end +function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddPatrolPointCAP") +local coordinate=Coordinate +local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end +EntryCAP.AirbaseName=AirbaseName +EntryCAP.Coordinate=coordinate +EntryCAP.Altitude=Altitude or 25000 +EntryCAP.Speed=Speed or 300 +EntryCAP.Heading=Heading or 90 +EntryCAP.LegLength=LegLength or 15 +self.ManagedCP[#self.ManagedCP+1]=EntryCAP +if self.debug then +local mark=MARKER:New(coordinate,self.lid.."Patrol Point"):ToAll() +end +return self +end +function EASYGCICAP:AddPatrolPointRecon(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddPatrolPointRecon "..Coordinate:ToStringLLDDM()) +local EntryCAP={} +EntryCAP.AirbaseName=AirbaseName +EntryCAP.Coordinate=Coordinate +EntryCAP.Altitude=Altitude or 25000 +EntryCAP.Speed=Speed or 300 +EntryCAP.Heading=Heading or 90 +EntryCAP.LegLength=LegLength or 15 +self.ManagedREC[#self.ManagedREC+1]=EntryCAP +if self.debug then +local mark=MARKER:New(Coordinate,self.lid.."Patrol Point Recon"):ToAll() +end +return self +end +function EASYGCICAP:AddPatrolPointTanker(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddPatrolPointTanker "..Coordinate:ToStringLLDDM()) +local EntryCAP={} +EntryCAP.AirbaseName=AirbaseName +EntryCAP.Coordinate=Coordinate +EntryCAP.Altitude=Altitude or 25000 +EntryCAP.Speed=Speed or 300 +EntryCAP.Heading=Heading or 90 +EntryCAP.LegLength=LegLength or 15 +self.ManagedTK[#self.ManagedTK+1]=EntryCAP +if self.debug then +local mark=MARKER:New(Coordinate,self.lid.."Patrol Point Tanker"):ToAll() +end +return self +end +function EASYGCICAP:AddPatrolPointAwacs(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddPatrolPointAwacs "..Coordinate:ToStringLLDDM()) +local EntryCAP={} +EntryCAP.AirbaseName=AirbaseName +EntryCAP.Coordinate=Coordinate +EntryCAP.Altitude=Altitude or 25000 +EntryCAP.Speed=Speed or 300 +EntryCAP.Heading=Heading or 90 +EntryCAP.LegLength=LegLength or 15 +self.ManagedEWR[#self.ManagedEWR+1]=EntryCAP +if self.debug then +local mark=MARKER:New(Coordinate,self.lid.."Patrol Point AWACS"):ToAll() +end +return self +end +function EASYGCICAP:_SetTankerPatrolPoints() +self:T(self.lid.."_SetTankerPatrolPoints") +for _,_data in pairs(self.ManagedTK)do +local data=_data +self:T("Airbasename = "..data.AirbaseName) +if not self.wings[data.AirbaseName]then +MESSAGE:New(self.lid.."You are trying to create a TANKER point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end +local Wing=self.wings[data.AirbaseName][1] +local Coordinate=data.Coordinate +local Altitude=data.Altitude +local Speed=data.Speed +local Heading=data.Heading +local LegLength=data.LegLength +Wing:AddPatrolPointTANKER(Coordinate,Altitude,Speed,Heading,LegLength) +end +return self +end +function EASYGCICAP:_SetAwacsPatrolPoints() +self:T(self.lid.."_SetAwacsPatrolPoints") +for _,_data in pairs(self.ManagedEWR)do +local data=_data +self:T("Airbasename = "..data.AirbaseName) +if not self.wings[data.AirbaseName]then +MESSAGE:New(self.lid.."You are trying to create an AWACS point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end +local Wing=self.wings[data.AirbaseName][1] +local Coordinate=data.Coordinate +local Altitude=data.Altitude +local Speed=data.Speed +local Heading=data.Heading +local LegLength=data.LegLength +Wing:AddPatrolPointAWACS(Coordinate,Altitude,Speed,Heading,LegLength) +end +return self +end +function EASYGCICAP:_SetCAPPatrolPoints() +self:T(self.lid.."_SetCAPPatrolPoints") +for _,_data in pairs(self.ManagedCP)do +local data=_data +self:T("Airbasename = "..data.AirbaseName) +if not self.wings[data.AirbaseName]then +MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end +local Wing=self.wings[data.AirbaseName][1] +local Coordinate=data.Coordinate +local Altitude=data.Altitude +local Speed=data.Speed +local Heading=data.Heading +local LegLength=data.LegLength +local Zone=_data.Zone +if Zone then +Wing:AddPatrolPointCAP(Zone,Altitude,Speed,Heading,LegLength) +else +Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) +end +end +return self +end +function EASYGCICAP:_SetReconPatrolPoints() +self:T(self.lid.."_SetReconPatrolPoints") +for _,_data in pairs(self.ManagedREC)do +local data=_data +self:T("Airbasename = "..data.AirbaseName) +if not self.wings[data.AirbaseName]then +MESSAGE:New(self.lid.."You are trying to create a RECON point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end +local Wing=self.wings[data.AirbaseName][1] +local Coordinate=data.Coordinate +local Altitude=data.Altitude +local Speed=data.Speed +local Heading=data.Heading +local LegLength=data.LegLength +Wing:AddPatrolPointRecon(Coordinate,Altitude,Speed,Heading,LegLength) +end +return self +end +function EASYGCICAP:_CreateSquads() +self:T(self.lid.."_CreateSquads") +for name,data in pairs(self.ManagedSQ)do +local squad=data +local SquadName=name +local TemplateName=squad.TemplateName +local AirbaseName=squad.AirbaseName +local AirFrames=squad.AirFrames +local Skill=squad.Skill +local Modex=squad.Modex +local Livery=squad.Livery +local Frequeny=squad.Frequency +local Modulation=squad.Modulation +local TACAN=squad.TACAN +if squad.Tanker then +self:_AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequeny,Modulation,TACAN) +elseif squad.AWACS then +self:_AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequeny,Modulation) +elseif squad.RECON then +self:_AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) +else +self:_AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) +end +end +return self +end +function EASYGCICAP:AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) +self:T(self.lid.."AddSquadron "..SquadName) +local EntrySQ={} +EntrySQ.TemplateName=TemplateName +EntrySQ.SquadName=SquadName +EntrySQ.AirbaseName=AirbaseName +EntrySQ.AirFrames=AirFrames or 20 +EntrySQ.Skill=Skill or AI.Skill.AVERAGE +EntrySQ.Modex=Modex or 402 +EntrySQ.Livery=Livery +self.ManagedSQ[SquadName]=EntrySQ +return self +end +function EASYGCICAP:AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) +self:T(self.lid.."AddReconSquadron "..SquadName) +local EntrySQ={} +EntrySQ.TemplateName=TemplateName +EntrySQ.SquadName=SquadName +EntrySQ.AirbaseName=AirbaseName +EntrySQ.AirFrames=AirFrames or 20 +EntrySQ.Skill=Skill or AI.Skill.AVERAGE +EntrySQ.Modex=Modex or 402 +EntrySQ.Livery=Livery +EntrySQ.RECON=true +self.ManagedSQ[SquadName]=EntrySQ +return self +end +function EASYGCICAP:AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation,TACAN) +self:T(self.lid.."AddTankerSquadron "..SquadName) +local EntrySQ={} +EntrySQ.TemplateName=TemplateName +EntrySQ.SquadName=SquadName +EntrySQ.AirbaseName=AirbaseName +EntrySQ.AirFrames=AirFrames or 20 +EntrySQ.Skill=Skill or AI.Skill.AVERAGE +EntrySQ.Modex=Modex or 602 +EntrySQ.Livery=Livery +EntrySQ.Frequency=Frequency +EntrySQ.Modulation=Livery +EntrySQ.TACAN=TACAN +EntrySQ.Tanker=true +self.ManagedSQ[SquadName]=EntrySQ +return self +end +function EASYGCICAP:AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation) +self:T(self.lid.."AddAWACSSquadron "..SquadName) +local EntrySQ={} +EntrySQ.TemplateName=TemplateName +EntrySQ.SquadName=SquadName +EntrySQ.AirbaseName=AirbaseName +EntrySQ.AirFrames=AirFrames or 20 +EntrySQ.Skill=Skill or AI.Skill.AVERAGE +EntrySQ.Modex=Modex or 702 +EntrySQ.Livery=Livery +EntrySQ.Frequency=Frequency +EntrySQ.Modulation=Livery +EntrySQ.AWACS=true +self.ManagedSQ[SquadName]=EntrySQ +return self +end +function EASYGCICAP:_AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation) +self:T(self.lid.."_AddSquadron "..SquadName) +local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName) +Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP,AUFTRAG.Type.GCICAP,AUFTRAG.Type.INTERCEPT,AUFTRAG.Type.PATROLRACETRACK,AUFTRAG.Type.ALERT5}) +Squadron_One:SetFuelLowThreshold(0.3) +Squadron_One:SetTurnoverTime(10,20) +Squadron_One:SetModex(Modex) +Squadron_One:SetLivery(Livery) +Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) +Squadron_One:SetMissionRange(self.missionrange) +local wing=self.wings[AirbaseName][1] +wing:AddSquadron(Squadron_One) +wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAP,AUFTRAG.Type.GCICAP,AUFTRAG.Type.INTERCEPT,AUFTRAG.Type.PATROLRACETRACK,AUFTRAG.Type.ALERT5},75) +return self +end +function EASYGCICAP:_AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery) +self:T(self.lid.."_AddReconSquadron "..SquadName) +local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName) +Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON}) +Squadron_One:SetFuelLowThreshold(0.3) +Squadron_One:SetTurnoverTime(10,20) +Squadron_One:SetModex(Modex) +Squadron_One:SetLivery(Livery) +Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) +Squadron_One:SetMissionRange(self.missionrange) +local wing=self.wings[AirbaseName][1] +wing:AddSquadron(Squadron_One) +wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.RECON},75) +return self +end +function EASYGCICAP:_AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation,TACAN) +self:T(self.lid.."_AddTankerSquadron "..SquadName) +local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName) +Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER}) +Squadron_One:SetFuelLowThreshold(0.3) +Squadron_One:SetTurnoverTime(10,20) +Squadron_One:SetModex(Modex) +Squadron_One:SetLivery(Livery) +Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) +Squadron_One:SetMissionRange(self.missionrange) +Squadron_One:SetRadio(Frequency,Modulation) +if TACAN then +Squadron_One:AddTacanChannel(TACAN,TACAN) +end +local wing=self.wings[AirbaseName][1] +wing:AddSquadron(Squadron_One) +wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.TANKER},75) +return self +end +function EASYGCICAP:_AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation) +self:T(self.lid.."_AddAWACSSquadron "..SquadName) +local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName) +Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS}) +Squadron_One:SetFuelLowThreshold(0.3) +Squadron_One:SetTurnoverTime(10,20) +Squadron_One:SetModex(Modex) +Squadron_One:SetLivery(Livery) +Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) +Squadron_One:SetMissionRange(self.missionrange) +Squadron_One:SetRadio(Frequency,Modulation) +local wing=self.wings[AirbaseName][1] +wing:AddSquadron(Squadron_One) +wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.AWACS},75) +return self +end +function EASYGCICAP:AddAcceptZone(Zone) +self:T(self.lid.."AddAcceptZone") +self.GoZoneSet:AddZone(Zone) +return self +end +function EASYGCICAP:AddRejectZone(Zone) +self:T(self.lid.."AddRejectZone") +self.NoGoZoneSet:AddZone(Zone) +return self +end +function EASYGCICAP:AddConflictZone(Zone) +self:T(self.lid.."AddConflictZone") +self.ConflictZoneSet:AddZone(Zone) +self.GoZoneSet:AddZone(Zone) +return self +end +function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) +self:T("_TryAssignIntercept for size "..WingSize or 1) +local assigned=false +local wingsize=WingSize or 1 +local mindist=0 +local disttable={} +if Group and Group:IsAlive()then +local gcoord=Group:GetCoordinate()or COORDINATE:New(0,0,0) +self:T(self.lid..string.format("Assignment for %s",Group:GetName())) +for _name,_FG in pairs(ReadyFlightGroups or{})do +local FG=_FG +local fcoord=FG:GetCoordinate() +local dist=math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1)) +self:T(self.lid..string.format("FG %s Distance %dkm",_name,dist)) +disttable[#disttable+1]={FG=FG,dist=dist} +if dist>mindist then mindist=dist end +end +local function sortDistance(a,b) +return a.distmaxsize then wingsize=maxsize end +local retrymission=true +if Cluster.mission and(not Cluster.mission:IsOver())then +retrymission=false +end +if(retrymission)and(wingsize>=1)then +MESSAGE:New(string.format("**** %s Interceptors need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"CAPGCI"):ToAllIf(self.debug):ToLog() +for _,_data in pairs(wings)do +local airwing=_data[1] +local zone=_data[2] +local zonecoord=zone:GetCoordinate() +local name=_data[3] +local coa=AIRBASE:FindByName(name):GetCoalition() +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +for _,_data in pairs(ctlpts)do +local data=_data +local name=data.AirbaseName +local zonecoord=data.Coordinate +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end +local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +local text=string.format("Closest Airwing is %s",targetawname) +local m=MESSAGE:New(text,10,"CAPGCI"):ToAllIf(self.debug):ToLog() +if targetairwing then +local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) +local missioncount=self:_CountAliveAuftrags() +self:T(self.lid.." Assets on Mission "..AssetCount) +if missioncount0 then +InterceptAuftrag:AddConditionSuccess( +function(group,zoneset,conflictset) +local success=false +if group and group:IsAlive()then +local coord=group:GetCoordinate() +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then +success=true +end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +end +return success +end, +contact.group, +nogozoneset, +conflictzoneset +) +end +table.insert(self.ListOfAuftrag,InterceptAuftrag) +local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) +if not assigned then +InterceptAuftrag:SetRequiredAssets(rest) +targetairwing:AddMission(InterceptAuftrag) +end +Cluster.mission=InterceptAuftrag +end +else +MESSAGE:New("**** Not enough airframes available or max mission limit reached!",15,"CAPGCI"):ToAllIf(self.debug):ToLog() +end +end +end +function EASYGCICAP:_StartIntel() +self:T(self.lid.."_StartIntel") +local BlueAir_DetectionSetGroup=SET_GROUP:New() +BlueAir_DetectionSetGroup:FilterPrefixes(self.EWRName) +BlueAir_DetectionSetGroup:FilterStart() +local BlueIntel=INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname,self.alias) +BlueIntel:SetClusterAnalysis(true,false,false) +BlueIntel:SetForgetTime(300) +BlueIntel:SetAcceptZones(self.GoZoneSet) +BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) +BlueIntel:SetVerbosity(0) +BlueIntel:Start() +if self.debug then +BlueIntel.debug=true +end +local function AssignCluster(Cluster) +self:_AssignIntercept(Cluster) +end +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) +AssignCluster(Cluster) +end +self.Intel=BlueIntel +return self +end +function EASYGCICAP:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:_StartIntel() +self:_CreateAirwings() +self:_CreateSquads() +self:_SetCAPPatrolPoints() +self:_SetTankerPatrolPoints() +self:_SetAwacsPatrolPoints() +self:_SetReconPatrolPoints() +self:__Status(-10) +return self +end +function EASYGCICAP:onbeforeStatus(From,Event,To) +self:T({From,Event,To}) +if self:GetState()=="Stopped"then return false end +return self +end +function EASYGCICAP:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end +local function counttable(tbl) +local count=0 +for _,_data in pairs(tbl)do +count=count+1 +end +return count +end +local wings=counttable(self.ManagedAW) +local squads=counttable(self.ManagedSQ) +local caps=counttable(self.ManagedCP) +local assets=0 +local instock=0 +local capmission=0 +local interceptmission=0 +local reconmission=0 +local awacsmission=0 +local tankermission=0 +for _,_wing in pairs(self.wings)do +local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) +local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) +capmission=capmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) +interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.INTERCEPT}) +reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) +awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) +assets=assets+count +instock=instock+count2 +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) +self.ReadyFlightGroups=nil +self.ReadyFlightGroups={} +for _,_asset in pairs(assetsonmission or{})do +local asset=_asset +local FG=asset.flightgroup +if FG then +local name=FG:GetName() +local engage=FG:IsEngaging() +local hasmissiles=FG:IsOutOfMissiles()==nil and true or false +local ready=hasmissiles and FG:IsFuelGood()and FG:IsAirborne() +if ready then +self.ReadyFlightGroups[name]=FG +end +end +end +end +if self.Monitor then +local threatcount=#self.Intel.Clusters or 0 +local text="GCICAP "..self.alias +text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock +text=text.."\nThreats: "..threatcount +text=text.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission +text=text.."\n - CAP: "..capmission +text=text.."\n - AWACS: "..awacsmission +text=text.."\n - TANKER: "..tankermission +text=text.."\n - Recon: "..reconmission +text=text.."\nSelf managed Missions:" +text=text.."\n - Mission Limit: "..self.MaxAliveMissions +text=text.."\n - Alert5+Intercept "..self:_CountAliveAuftrags() +MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug) +end +self:__Status(30) +return self +end +function EASYGCICAP:onafterStop(From,Event,To) +self:T({From,Event,To}) +self.Intel:Stop() +for _,_wing in pairs(self.wings or{})do +_wing:Stop() +end +return self +end +AI_BALANCER={ +ClassName="AI_BALANCER", +PatrolZones={}, +AIGroups={}, +Earliest=5, +Latest=60, +} +function AI_BALANCER:New(SetClient,SpawnAI) +local self=BASE:Inherit(self,FSM_SET:New(SET_GROUP:New())) +self:SetStartState("None") +self:AddTransition("*","Monitor","Monitoring") +self:AddTransition("*","Spawn","Spawning") +self:AddTransition("Spawning","Spawned","Spawned") +self:AddTransition("*","Destroy","Destroying") +self:AddTransition("*","Return","Returning") +self.SetClient=SetClient +self.SetClient:FilterOnce() +self.SpawnAI=SpawnAI +self.SpawnQueue={} +self.ToNearestAirbase=false +self.ToHomeAirbase=false +self:__Monitor(1) +return self +end +function AI_BALANCER:InitSpawnInterval(Earliest,Latest) +self.Earliest=Earliest +self.Latest=Latest +return self +end +function AI_BALANCER:ReturnToNearestAirbases(ReturnThresholdRange,ReturnAirbaseSet) +self.ToNearestAirbase=true +self.ReturnThresholdRange=ReturnThresholdRange +self.ReturnAirbaseSet=ReturnAirbaseSet +end +function AI_BALANCER:ReturnToHomeAirbase(ReturnThresholdRange) +self.ToHomeAirbase=true +self.ReturnThresholdRange=ReturnThresholdRange +end +function AI_BALANCER:onenterSpawning(SetGroup,From,Event,To,ClientName) +local AIGroup=self.SpawnAI:Spawn() +if AIGroup then +AIGroup:T({"Spawning new AIGroup",ClientName=ClientName}) +SetGroup:Remove(ClientName) +SetGroup:Add(ClientName,AIGroup) +self.SpawnQueue[ClientName]=nil +self:Spawned(AIGroup) +end +end +function AI_BALANCER:onenterDestroying(SetGroup,From,Event,To,ClientName,AIGroup) +AIGroup:Destroy() +SetGroup:Flush(self) +SetGroup:Remove(ClientName) +SetGroup:Flush(self) +end +function AI_BALANCER:onenterReturning(SetGroup,From,Event,To,AIGroup) +local AIGroupTemplate=AIGroup:GetTemplate() +if self.ToHomeAirbase==true then +local WayPointCount=#AIGroupTemplate.route.points +local SwitchWayPointCommand=AIGroup:CommandSwitchWayPoint(1,WayPointCount,1) +AIGroup:SetCommand(SwitchWayPointCommand) +AIGroup:MessageToRed("Returning to home base ...",30) +else +local PointVec2=COORDINATE:New(AIGroup:GetVec2().x,0,AIGroup:GetVec2().y) +local ClosestAirbase=self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2(PointVec2) +self:T(ClosestAirbase.AirbaseName) +AIGroup:RouteRTB(ClosestAirbase) +end +end +function AI_BALANCER:onenterMonitoring(SetGroup) +self:T2({self.SetClient:Count()}) +self.SetClient:ForEachClient( +function(Client) +self:T3(Client.ClientName) +local AIGroup=self.Set:Get(Client.UnitName) +if AIGroup then self:T({AIGroup=AIGroup:GetName(),IsAlive=AIGroup:IsAlive()})end +if Client:IsAlive()==true then +if AIGroup and AIGroup:IsAlive()==true then +if self.ToNearestAirbase==false and self.ToHomeAirbase==false then +self:Destroy(Client.UnitName,AIGroup) +else +local PlayerInRange={Value=false} +local RangeZone=ZONE_RADIUS:New('RangeZone',AIGroup:GetVec2(),self.ReturnThresholdRange) +self:T2(RangeZone) +_DATABASE:ForEachPlayerUnit( +function(RangeTestUnit,RangeZone,AIGroup,PlayerInRange) +self:T2({PlayerInRange,RangeTestUnit.UnitName,RangeZone.ZoneName}) +if RangeTestUnit:IsInZone(RangeZone)==true then +self:T2("in zone") +if RangeTestUnit:GetCoalition()~=AIGroup:GetCoalition()then +self:T2("in range") +PlayerInRange.Value=true +end +end +end, +function(RangeZone,AIGroup,PlayerInRange) +if PlayerInRange.Value==false then +self:Return(AIGroup) +end +end +,RangeZone,AIGroup,PlayerInRange +) +end +self.Set:Remove(Client.UnitName) +end +else +if not AIGroup or not AIGroup:IsAlive()==true then +self:T("Client "..Client.UnitName.." not alive.") +self:T({Queue=self.SpawnQueue[Client.UnitName]}) +if not self.SpawnQueue[Client.UnitName]then +self:__Spawn(math.random(self.Earliest,self.Latest),Client.UnitName) +self.SpawnQueue[Client.UnitName]=true +self:T("New AI Spawned for Client "..Client.UnitName) +end +end +end +return true +end +) +self:__Monitor(10) +end +AI_AIR={ +ClassName="AI_AIR", +} +AI_AIR.TaskDelay=0.5 +function AI_AIR:New(AIGroup) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) +self:SetControllable(AIGroup) +self:SetStartState("Stopped") +self:AddTransition("*","Queue","Queued") +self:AddTransition("*","Start","Started") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("*","Status","*") +self:AddTransition("*","RTB","*") +self:AddTransition("Patrolling","Refuel","Refuelling") +self:AddTransition("*","Takeoff","Airborne") +self:AddTransition("*","Return","Returning") +self:AddTransition("*","Hold","Holding") +self:AddTransition("*","Home","Home") +self:AddTransition("*","LostControl","LostControl") +self:AddTransition("*","Fuel","Fuel") +self:AddTransition("*","Damaged","Damaged") +self:AddTransition("*","Eject","*") +self:AddTransition("*","Crash","Crashed") +self:AddTransition("*","PilotDead","*") +self.IdleCount=0 +self.RTBSpeedMaxFactor=0.6 +self.RTBSpeedMinFactor=0.5 +return self +end +function GROUP:OnEventTakeoff(EventData,Fsm) +Fsm:Takeoff() +self:UnHandleEvent(EVENTS.Takeoff) +end +function AI_AIR:SetDispatcher(Dispatcher) +self.Dispatcher=Dispatcher +end +function AI_AIR:GetDispatcher() +return self.Dispatcher +end +function AI_AIR:SetTargetDistance(Coordinate) +local CurrentCoord=self.Controllable:GetCoordinate() +self.TargetDistance=CurrentCoord:Get2DDistance(Coordinate) +self.ClosestTargetDistance=(not self.ClosestTargetDistance or self.ClosestTargetDistance>self.TargetDistance)and self.TargetDistance or self.ClosestTargetDistance +end +function AI_AIR:ClearTargetDistance() +self.TargetDistance=nil +self.ClosestTargetDistance=nil +end +function AI_AIR:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed) +self:F2({PatrolMinSpeed,PatrolMaxSpeed}) +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +end +function AI_AIR:SetRTBSpeed(RTBMinSpeed,RTBMaxSpeed) +self:F({RTBMinSpeed,RTBMaxSpeed}) +self.RTBMinSpeed=RTBMinSpeed +self.RTBMaxSpeed=RTBMaxSpeed +end +function AI_AIR:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude) +self:F2({PatrolFloorAltitude,PatrolCeilingAltitude}) +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +end +function AI_AIR:SetHomeAirbase(HomeAirbase) +self:F2({HomeAirbase}) +self.HomeAirbase=HomeAirbase +end +function AI_AIR:SetTanker(TankerName) +self:F2({TankerName}) +self.TankerName=TankerName +end +function AI_AIR:SetDisengageRadius(DisengageRadius) +self:F2({DisengageRadius}) +self.DisengageRadius=DisengageRadius +end +function AI_AIR:SetStatusOff() +self:F2() +self.CheckStatus=false +end +function AI_AIR:SetFuelThreshold(FuelThresholdPercentage,OutOfFuelOrbitTime) +self.FuelThresholdPercentage=FuelThresholdPercentage +self.OutOfFuelOrbitTime=OutOfFuelOrbitTime +self.Controllable:OptionRTBBingoFuel(false) +return self +end +function AI_AIR:SetDamageThreshold(PatrolDamageThreshold) +self.PatrolManageDamage=true +self.PatrolDamageThreshold=PatrolDamageThreshold +return self +end +function AI_AIR:onafterStart(Controllable,From,Event,To) +self:__Status(10) +self:HandleEvent(EVENTS.PilotDead,self.OnPilotDead) +self:HandleEvent(EVENTS.Crash,self.OnCrash) +self:HandleEvent(EVENTS.Ejection,self.OnEjection) +Controllable:OptionROEHoldFire() +Controllable:OptionROTVertical() +end +function AI_AIR:onafterReturn(Controllable,From,Event,To) +self:__RTB(self.TaskDelay) +end +function AI_AIR:onbeforeStatus() +return self.CheckStatus +end +function AI_AIR:onafterStatus() +if self.Controllable and self.Controllable:IsAlive()then +local RTB=false +local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate()) +if not self:Is("Holding")and not self:Is("Returning")then +local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate()) +if DistanceFromHomeBase>self.DisengageRadius then +self:T(self.Controllable:GetName().." is too far from home base, RTB!") +self:Hold(300) +RTB=false +end +end +if not self:Is("Fuel")and not self:Is("Home")and not self:is("Refuelling")then +local Fuel=self.Controllable:GetFuelMin() +if Fuel=10 then +if Damage~=InitialLife then +self:Damaged() +else +self:T(self.Controllable:GetName().." control lost! ") +self:LostControl() +end +else +self.IdleCount=self.IdleCount+1 +end +end +else +self.IdleCount=0 +end +if RTB==true then +self:__RTB(self.TaskDelay) +end +if not self:Is("Home")then +self:__Status(10) +end +end +end +function AI_AIR.RTBRoute(AIGroup,Fsm) +AIGroup:F({"AI_AIR.RTBRoute:",AIGroup:GetName()}) +if AIGroup:IsAlive()then +Fsm:RTB() +end +end +function AI_AIR.RTBHold(AIGroup,Fsm) +AIGroup:F({"AI_AIR.RTBHold:",AIGroup:GetName()}) +if AIGroup:IsAlive()then +Fsm:__RTB(Fsm.TaskDelay) +Fsm:Return() +local Task=AIGroup:TaskOrbitCircle(4000,400) +AIGroup:SetTask(Task) +end +end +function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor) +self.RTBSpeedMaxFactor=MaxFactor or 0.6 +self.RTBSpeedMinFactor=MinFactor or 0.5 +return self +end +function AI_AIR:onafterRTB(AIGroup,From,Event,To) +self:F({AIGroup,From,Event,To}) +if AIGroup and AIGroup:IsAlive()then +self:T("Group "..AIGroup:GetName().." ... RTB! ( "..self:GetState().." )") +self:ClearTargetDistance() +AIGroup:OptionProhibitAfterburner(true) +local EngageRoute={} +local FromCoord=AIGroup:GetCoordinate() +if not FromCoord then return end +local ToTargetCoord=self.HomeAirbase:GetCoordinate() +local ToTargetVec3=ToTargetCoord:GetVec3() +ToTargetVec3.y=ToTargetCoord:GetLandHeight()+3000 +local ToTargetCoord2=COORDINATE:NewFromVec3(ToTargetVec3) +if not self.RTBMinSpeed or not self.RTBMaxSpeed then +local RTBSpeedMax=AIGroup:GetSpeedMax() +local RTBSpeedMaxFactor=self.RTBSpeedMaxFactor or 0.6 +local RTBSpeedMinFactor=self.RTBSpeedMinFactor or 0.5 +self:SetRTBSpeed(RTBSpeedMax*RTBSpeedMinFactor,RTBSpeedMax*RTBSpeedMaxFactor) +end +local RTBSpeed=math.random(self.RTBMinSpeed,self.RTBMaxSpeed) +local Distance=FromCoord:Get2DDistance(ToTargetCoord2) +local ToAirbaseCoord=ToTargetCoord2 +if Distance<5000 then +self:T("RTB and near the airbase!") +self:Home() +return +end +if not AIGroup:InAir()==true then +self:T("Not anymore in the air, considered Home.") +self:Home() +return +end +local FromRTBRoutePoint=FromCoord:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +RTBSpeed, +true +) +local ToRTBRoutePoint=ToAirbaseCoord:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +RTBSpeed, +true +) +EngageRoute[#EngageRoute+1]=FromRTBRoutePoint +EngageRoute[#EngageRoute+1]=ToRTBRoutePoint +local Tasks={} +Tasks[#Tasks+1]=AIGroup:TaskFunction("AI_AIR.RTBRoute",self) +EngageRoute[#EngageRoute].task=AIGroup:TaskCombo(Tasks) +AIGroup:OptionROEHoldFire() +AIGroup:OptionROTEvadeFire() +AIGroup:Route(EngageRoute,self.TaskDelay) +end +end +function AI_AIR:onafterHome(AIGroup,From,Event,To) +self:F({AIGroup,From,Event,To}) +self:T("Group "..self.Controllable:GetName().." ... Home! ( "..self:GetState().." )") +if AIGroup and AIGroup:IsAlive()then +end +end +function AI_AIR:onafterHold(AIGroup,From,Event,To,HoldTime) +self:F({AIGroup,From,Event,To}) +self:T("Group "..self.Controllable:GetName().." ... Holding! ( "..self:GetState().." )") +if AIGroup and AIGroup:IsAlive()then +local Coordinate=AIGroup:GetCoordinate() +if Coordinate==nil then return end +local OrbitTask=AIGroup:TaskOrbitCircle(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude),self.PatrolMinSpeed,Coordinate) +local TimedOrbitTask=AIGroup:TaskControlled(OrbitTask,AIGroup:TaskCondition(nil,nil,nil,nil,HoldTime,nil)) +local RTBTask=AIGroup:TaskFunction("AI_AIR.RTBHold",self) +local OrbitHoldTask=AIGroup:TaskOrbitCircle(4000,self.PatrolMinSpeed) +AIGroup:SetTask(AIGroup:TaskCombo({TimedOrbitTask,RTBTask,OrbitHoldTask}),1) +end +end +function AI_AIR.Resume(AIGroup,Fsm) +AIGroup:T({"AI_AIR.Resume:",AIGroup:GetName()}) +if AIGroup:IsAlive()then +Fsm:__RTB(Fsm.TaskDelay) +end +end +function AI_AIR:onafterRefuel(AIGroup,From,Event,To) +self:F({AIGroup,From,Event,To}) +if AIGroup and AIGroup:IsAlive()then +local Tanker=GROUP:FindByName(self.TankerName) +if Tanker and Tanker:IsAlive()and Tanker:IsAirPlane()then +self:T("Group "..self.Controllable:GetName().." ... Refuelling! State="..self:GetState()..", Refuelling tanker "..self.TankerName) +local RefuelRoute={} +local FromRefuelCoord=AIGroup:GetCoordinate() +local ToRefuelCoord=Tanker:GetCoordinate() +local ToRefuelSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +local FromRefuelRoutePoint=FromRefuelCoord:WaypointAir(self.PatrolAltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToRefuelSpeed,true) +local ToRefuelRoutePoint=Tanker:GetCoordinate():WaypointAir(self.PatrolAltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToRefuelSpeed,true) +self:F({ToRefuelSpeed=ToRefuelSpeed}) +RefuelRoute[#RefuelRoute+1]=FromRefuelRoutePoint +RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint +AIGroup:OptionROEHoldFire() +AIGroup:OptionROTEvadeFire() +local classname=self:GetClassName() +if classname=="AI_A2A_CAP"then +classname="AI_AIR_PATROL" +end +env.info("FF refueling classname="..classname) +local Tasks={} +Tasks[#Tasks+1]=AIGroup:TaskRefueling() +Tasks[#Tasks+1]=AIGroup:TaskFunction(classname..".Resume",self) +RefuelRoute[#RefuelRoute].task=AIGroup:TaskCombo(Tasks) +AIGroup:Route(RefuelRoute,self.TaskDelay) +else +self:RTB() +end +end +end +function AI_AIR:onafterDead() +self:SetStatusOff() +end +function AI_AIR:OnCrash(EventData) +if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then +if#self.Controllable:GetUnits()==1 then +self:__Crash(self.TaskDelay,EventData) +end +end +end +function AI_AIR:OnEjection(EventData) +if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then +self:__Eject(self.TaskDelay,EventData) +end +end +function AI_AIR:OnPilotDead(EventData) +if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then +self:__PilotDead(self.TaskDelay,EventData) +end +end +AI_AIR_PATROL={ +ClassName="AI_AIR_PATROL", +} +function AI_AIR_PATROL:New(AI_Air,AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local self=BASE:Inherit(self,AI_Air) +local SpeedMax=AIGroup:GetSpeedMax() +self.PatrolZone=PatrolZone +self.PatrolFloorAltitude=PatrolFloorAltitude or 1000 +self.PatrolCeilingAltitude=PatrolCeilingAltitude or 1500 +self.PatrolMinSpeed=PatrolMinSpeed or SpeedMax*0.5 +self.PatrolMaxSpeed=PatrolMaxSpeed or SpeedMax*0.75 +self.PatrolAltType=PatrolAltType or"RADIO" +self:AddTransition({"Started","Airborne","Refuelling"},"Patrol","Patrolling") +self:AddTransition("Patrolling","PatrolRoute","Patrolling") +self:AddTransition("*","Reset","Patrolling") +return self +end +function AI_AIR_PATROL:SetEngageRange(EngageRange) +self:F2() +if EngageRange then +self.EngageRange=EngageRange +else +self.EngageRange=nil +end +end +function AI_AIR_PATROL:SetRaceTrackPattern(LegMin,LegMax,HeadingMin,HeadingMax,DurationMin,DurationMax,CapCoordinates) +self.racetrack=true +self.racetracklegmin=LegMin or 10000 +self.racetracklegmax=LegMax or 15000 +self.racetrackheadingmin=HeadingMin or 0 +self.racetrackheadingmax=HeadingMax or 180 +self.racetrackdurationmin=DurationMin +self.racetrackdurationmax=DurationMax +if self.racetrackdurationmax and not self.racetrackdurationmin then +self.racetrackdurationmin=self.racetrackdurationmax +end +self.racetrackcapcoordinates=CapCoordinates +end +function AI_AIR_PATROL:onafterPatrol(AIPatrol,From,Event,To) +self:F2() +self:ClearTargetDistance() +self:__PatrolRoute(self.TaskDelay) +AIPatrol:OnReSpawn( +function(PatrolGroup) +self:__Reset(self.TaskDelay) +self:__PatrolRoute(self.TaskDelay) +end +) +end +function AI_AIR_PATROL.___PatrolRoute(AIPatrol,Fsm) +AIPatrol:F({"AI_AIR_PATROL.___PatrolRoute:",AIPatrol:GetName()}) +if AIPatrol and AIPatrol:IsAlive()then +Fsm:PatrolRoute() +end +end +function AI_AIR_PATROL:onafterPatrolRoute(AIPatrol,From,Event,To) +self:F2() +if From=="RTB"then +return +end +if AIPatrol and AIPatrol:IsAlive()then +local PatrolRoute={} +local CurrentCoord=AIPatrol:GetCoordinate() +local altitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude) +local ToTargetCoord=self.PatrolZone:GetRandomPointVec2() +ToTargetCoord:SetAlt(altitude) +self:SetTargetDistance(ToTargetCoord) +local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +local speedkmh=ToTargetSpeed +local FromWP=CurrentCoord:WaypointAir(self.PatrolAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToTargetSpeed,true) +PatrolRoute[#PatrolRoute+1]=FromWP +if self.racetrack then +local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax) +local leg=math.random(self.racetracklegmin,self.racetracklegmax) +local duration=self.racetrackdurationmin +if self.racetrackdurationmax then +duration=math.random(self.racetrackdurationmin,self.racetrackdurationmax) +end +local c0=self.PatrolZone:GetRandomCoordinate() +if self.racetrackcapcoordinates and#self.racetrackcapcoordinates>0 then +c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] +end +local c1=c0:SetAltitude(altitude) +local c2=c1:Translate(leg,heading):SetAltitude(altitude) +self:SetTargetDistance(c0) +self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec",UTILS.KmphToKnots(speedkmh),UTILS.MetersToFeet(altitude),heading,leg,tostring(duration))) +local taskOrbit=AIPatrol:TaskOrbit(c1,altitude,UTILS.KmphToMps(speedkmh),c2) +local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self) +local taskCond=AIPatrol:TaskCondition(nil,nil,nil,nil,duration,nil) +local taskCont=AIPatrol:TaskControlled(taskOrbit,taskCond) +PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskCont,taskPatrol},"CAP Orbit") +else +local ToWP=ToTargetCoord:WaypointAir(self.PatrolAltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToTargetSpeed,true) +PatrolRoute[#PatrolRoute+1]=ToWP +local Tasks={} +Tasks[#Tasks+1]=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self) +PatrolRoute[#PatrolRoute].task=AIPatrol:TaskCombo(Tasks) +end +AIPatrol:OptionROEReturnFire() +AIPatrol:OptionROTEvadeFire() +AIPatrol:Route(PatrolRoute,self.TaskDelay) +end +end +function AI_AIR_PATROL.Resume(AIPatrol,Fsm) +AIPatrol:F({"AI_AIR_PATROL.Resume:",AIPatrol:GetName()}) +if AIPatrol and AIPatrol:IsAlive()then +Fsm:__Reset(Fsm.TaskDelay) +Fsm:__PatrolRoute(Fsm.TaskDelay) +end +end +AI_AIR_ENGAGE={ +ClassName="AI_AIR_ENGAGE", +} +function AI_AIR_ENGAGE:New(AI_Air,AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local self=BASE:Inherit(self,AI_Air) +self.Accomplished=false +self.Engaging=false +local SpeedMax=AIGroup:GetSpeedMax() +self.EngageMinSpeed=EngageMinSpeed or SpeedMax*0.5 +self.EngageMaxSpeed=EngageMaxSpeed or SpeedMax*0.75 +self.EngageFloorAltitude=EngageFloorAltitude or 1000 +self.EngageCeilingAltitude=EngageCeilingAltitude or 1500 +self.EngageAltType=EngageAltType or"RADIO" +self:AddTransition({"Started","Engaging","Returning","Airborne","Patrolling"},"EngageRoute","Engaging") +self:AddTransition({"Started","Engaging","Returning","Airborne","Patrolling"},"Engage","Engaging") +self:AddTransition("Engaging","Fired","Engaging") +self:AddTransition("*","Destroy","*") +self:AddTransition("Engaging","Abort","Patrolling") +self:AddTransition("Engaging","Accomplish","Patrolling") +self:AddTransition({"Patrolling","Engaging"},"Refuel","Refuelling") +return self +end +function AI_AIR_ENGAGE:onafterStart(AIGroup,From,Event,To) +self:GetParent(self,AI_AIR_ENGAGE).onafterStart(self,AIGroup,From,Event,To) +AIGroup:HandleEvent(EVENTS.Takeoff,nil,self) +end +function AI_AIR_ENGAGE:onafterEngage(AIGroup,From,Event,To) +self:HandleEvent(EVENTS.Dead) +end +function AI_AIR_ENGAGE:onbeforeEngage(AIGroup,From,Event,To) +if self.Accomplished==true then +return false +end +return true +end +function AI_AIR_ENGAGE:onafterAbort(AIGroup,From,Event,To) +AIGroup:ClearTasks() +self:Return() +end +function AI_AIR_ENGAGE:onafterAccomplish(AIGroup,From,Event,To) +self.Accomplished=true +end +function AI_AIR_ENGAGE:onafterDestroy(AIGroup,From,Event,To,EventData) +if EventData.IniUnit then +self.AttackUnits[EventData.IniUnit]=nil +end +end +function AI_AIR_ENGAGE:OnEventDead(EventData) +self:F({"EventDead",EventData}) +if EventData.IniDCSUnit then +if self.AttackUnits and self.AttackUnits[EventData.IniUnit]then +self:__Destroy(self.TaskDelay,EventData) +end +end +end +function AI_AIR_ENGAGE.___EngageRoute(AIGroup,Fsm,AttackSetUnit) +Fsm:T(string.format("AI_AIR_ENGAGE.___EngageRoute: %s",tostring(AIGroup:GetName()))) +if AIGroup and AIGroup:IsAlive()then +Fsm:__EngageRoute(Fsm.TaskDelay or 0.1,AttackSetUnit) +end +end +function AI_AIR_ENGAGE:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit) +self:T({DefenderGroup,From,Event,To,AttackSetUnit}) +local DefenderGroupName=DefenderGroup:GetName() +self.AttackSetUnit=AttackSetUnit +local AttackCount=AttackSetUnit:CountAlive() +if AttackCount>0 then +if DefenderGroup:IsAlive()then +local EngageAltitude=math.random(self.EngageFloorAltitude,self.EngageCeilingAltitude) +local EngageSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed) +local DefenderCoord=DefenderGroup:GetPointVec3() +DefenderCoord:SetY(EngageAltitude) +local TargetCoord=AttackSetUnit:GetRandomSurely():GetPointVec3() +if TargetCoord==nil then +self:Return() +return +end +TargetCoord:SetY(EngageAltitude) +local TargetDistance=DefenderCoord:Get2DDistance(TargetCoord) +local EngageDistance=(DefenderGroup:IsHelicopter()and 5000)or(DefenderGroup:IsAirPlane()and 10000) +if TargetDistance<=EngageDistance*9 then +self:__Engage(0.1,AttackSetUnit) +else +local EngageRoute={} +local AttackTasks={} +local FromWP=DefenderCoord:WaypointAir(self.PatrolAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true) +EngageRoute[#EngageRoute+1]=FromWP +self:SetTargetDistance(TargetCoord) +local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord)) +local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true) +local ToWP=ToCoord:WaypointAir(self.PatrolAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true) +EngageRoute[#EngageRoute+1]=ToWP +AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___EngageRoute",self,AttackSetUnit) +EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks) +DefenderGroup:OptionROEReturnFire() +DefenderGroup:OptionROTEvadeFire() +DefenderGroup:Route(EngageRoute,self.TaskDelay or 0.1) +end +end +else +self:T(DefenderGroupName..": No targets found -> Going RTB") +self:Return() +end +end +function AI_AIR_ENGAGE.___Engage(AIGroup,Fsm,AttackSetUnit) +Fsm:T(string.format("AI_AIR_ENGAGE.___Engage: %s",tostring(AIGroup:GetName()))) +if AIGroup and AIGroup:IsAlive()then +local delay=Fsm.TaskDelay or 0.1 +Fsm:__Engage(delay,AttackSetUnit) +end +end +function AI_AIR_ENGAGE:onafterEngage(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({DefenderGroup,From,Event,To,AttackSetUnit}) +local DefenderGroupName=DefenderGroup:GetName() +self.AttackSetUnit=AttackSetUnit +local AttackCount=AttackSetUnit:CountAlive() +self:T({AttackCount=AttackCount}) +if AttackCount>0 then +if DefenderGroup and DefenderGroup:IsAlive()then +local EngageAltitude=math.random(self.EngageFloorAltitude or 500,self.EngageCeilingAltitude or 1000) +local EngageSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed) +local DefenderCoord=DefenderGroup:GetPointVec3() +DefenderCoord:SetY(EngageAltitude) +local TargetCoord=AttackSetUnit:GetRandomSurely():GetPointVec3() +if not TargetCoord then +self:Return() +return +end +TargetCoord:SetY(EngageAltitude) +local TargetDistance=DefenderCoord:Get2DDistance(TargetCoord) +local EngageDistance=(DefenderGroup:IsHelicopter()and 5000)or(DefenderGroup:IsAirPlane()and 10000) +local EngageRoute={} +local AttackTasks={} +local FromWP=DefenderCoord:WaypointAir(self.EngageAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true) +EngageRoute[#EngageRoute+1]=FromWP +self:SetTargetDistance(TargetCoord) +local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord)) +local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true) +local ToWP=ToCoord:WaypointAir(self.EngageAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true) +EngageRoute[#EngageRoute+1]=ToWP +if TargetDistance<=EngageDistance*9 then +local AttackUnitTasks=self:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) +if#AttackUnitTasks==0 then +self:T(DefenderGroupName..": No valid targets found -> Going RTB") +self:Return() +return +else +local text=string.format("%s: Engaging targets at distance %.2f NM",DefenderGroupName,UTILS.MetersToNM(TargetDistance)) +self:T(text) +DefenderGroup:OptionROEOpenFire() +DefenderGroup:OptionROTEvadeFire() +DefenderGroup:OptionKeepWeaponsOnThreat() +AttackTasks[#AttackTasks+1]=DefenderGroup:TaskCombo(AttackUnitTasks) +end +end +AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___Engage",self,AttackSetUnit) +EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks) +DefenderGroup:Route(EngageRoute,self.TaskDelay or 0.1) +end +else +self:T(DefenderGroupName..": No targets found -> returning.") +self:Return() +return +end +end +function AI_AIR_ENGAGE.Resume(AIEngage,Fsm) +AIEngage:F({"Resume:",AIEngage:GetName()}) +if AIEngage and AIEngage:IsAlive()then +Fsm:__Reset(Fsm.TaskDelay or 0.1) +Fsm:__EngageRoute(Fsm.TaskDelay or 0.2,Fsm.AttackSetUnit) +end +end +AI_A2A_PATROL={ +ClassName="AI_A2A_PATROL", +} +function AI_A2A_PATROL:New(AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local AI_Air=AI_AIR:New(AIPatrol) +local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local self=BASE:Inherit(self,AI_Air_Patrol) +self:SetFuelThreshold(.2,60) +self:SetDamageThreshold(0.4) +self:SetDisengageRadius(70000) +self.PatrolZone=PatrolZone +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +self.PatrolAltType=PatrolAltType or"BARO" +self:AddTransition({"Started","Airborne","Refuelling"},"Patrol","Patrolling") +self:AddTransition("Patrolling","Route","Patrolling") +self:AddTransition("*","Reset","Patrolling") +return self +end +function AI_A2A_PATROL:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed) +self:F2({PatrolMinSpeed,PatrolMaxSpeed}) +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +end +function AI_A2A_PATROL:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude) +self:F2({PatrolFloorAltitude,PatrolCeilingAltitude}) +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +end +function AI_A2A_PATROL:onafterPatrol(AIPatrol,From,Event,To) +self:F2() +self:ClearTargetDistance() +self:__Route(1) +AIPatrol:OnReSpawn( +function(PatrolGroup) +self:__Reset(1) +self:__Route(5) +end +) +end +function AI_A2A_PATROL.PatrolRoute(AIPatrol,Fsm) +AIPatrol:F({"AI_A2A_PATROL.PatrolRoute:",AIPatrol:GetName()}) +if AIPatrol and AIPatrol:IsAlive()then +Fsm:Route() +end +end +function AI_A2A_PATROL:onafterRoute(AIPatrol,From,Event,To) +self:F2() +if From=="RTB"then +return +end +if AIPatrol and AIPatrol:IsAlive()then +local PatrolRoute={} +local CurrentCoord=AIPatrol:GetCoordinate() +local altitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude) +local speedkmh=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil,speedkmh,{},"Current") +if self.racetrack then +local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax) +local leg=math.random(self.racetracklegmin,self.racetracklegmax) +local duration=self.racetrackdurationmin +if self.racetrackdurationmax then +duration=math.random(self.racetrackdurationmin,self.racetrackdurationmax) +end +local c0=self.PatrolZone:GetRandomCoordinate() +if self.racetrackcapcoordinates and#self.racetrackcapcoordinates>0 then +c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] +end +local c1=c0:SetAltitude(altitude) +local c2=c1:Translate(leg,heading):SetAltitude(altitude) +self:SetTargetDistance(c0) +self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec",UTILS.KmphToKnots(speedkmh),UTILS.MetersToFeet(altitude),heading,leg,tostring(duration))) +local taskOrbit=AIPatrol:TaskOrbit(c1,altitude,UTILS.KmphToMps(speedkmh),c2) +local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self) +local taskCond=AIPatrol:TaskCondition(nil,nil,nil,nil,duration,nil) +local taskCont=AIPatrol:TaskControlled(taskOrbit,taskCond) +PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskCont,taskPatrol},"CAP Orbit") +else +local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() +ToTargetCoord:SetAltitude(altitude) +self:SetTargetDistance(ToTargetCoord) +local taskReRoute=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self) +PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskReRoute},"Patrol Point") +end +AIPatrol:OptionROEReturnFire() +AIPatrol:OptionROTEvadeFire() +AIPatrol:Route(PatrolRoute,0.5) +end +end +AI_A2A_CAP={ +ClassName="AI_A2A_CAP", +} +function AI_A2A_CAP:New2(AICap,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,PatrolZone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType) +local AI_Air=AI_AIR:New(AICap) +local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air_Patrol,AICap,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local self=BASE:Inherit(self,AI_Air_Engage) +self:SetFuelThreshold(.2,60) +self:SetDamageThreshold(0.4) +self:SetDisengageRadius(70000) +return self +end +function AI_A2A_CAP:New(AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,PatrolAltType) +return self:New2(AICap,EngageMinSpeed,EngageMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,PatrolZone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType) +end +function AI_A2A_CAP:onafterStart(AICap,From,Event,To) +self:GetParent(self,AI_A2A_CAP).onafterStart(self,AICap,From,Event,To) +AICap:HandleEvent(EVENTS.Takeoff,nil,self) +end +function AI_A2A_CAP:SetEngageZone(EngageZone) +self:F2() +if EngageZone then +self.EngageZone=EngageZone +else +self.EngageZone=nil +end +end +function AI_A2A_CAP:SetEngageRange(EngageRange) +self:F2() +if EngageRange then +self.EngageRange=EngageRange +else +self.EngageRange=nil +end +end +function AI_A2A_CAP:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) +local AttackUnitTasks={} +for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do +local AttackUnit=AttackUnit +if AttackUnit and AttackUnit:IsAlive()and AttackUnit:IsAir()then +self:T({"Attacking Task:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()}) +AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit) +end +end +return AttackUnitTasks +end +AI_A2A_GCI={ +ClassName="AI_A2A_GCI", +} +function AI_A2A_GCI:New2(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local AI_Air=AI_AIR:New(AIIntercept) +local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air,AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local self=BASE:Inherit(self,AI_Air_Engage) +self:SetFuelThreshold(.2,60) +self:SetDamageThreshold(0.4) +self:SetDisengageRadius(70000) +return self +end +function AI_A2A_GCI:New(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +return self:New2(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +end +function AI_A2A_GCI:onafterStart(AIIntercept,From,Event,To) +self:GetParent(self,AI_A2A_GCI).onafterStart(self,AIIntercept,From,Event,To) +end +function AI_A2A_GCI:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) +local AttackUnitTasks={} +for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do +local AttackUnit=AttackUnit +self:T({"Attacking Unit:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()}) +if AttackUnit:IsAlive()and AttackUnit:IsAir()then +AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit) +end +end +return AttackUnitTasks +end +do +AI_A2A_DISPATCHER={ +ClassName="AI_A2A_DISPATCHER", +Detection=nil, +} +AI_A2A_DISPATCHER.Takeoff=GROUP.Takeoff +AI_A2A_DISPATCHER.Landing={ +NearAirbase=1, +AtRunway=2, +AtEngineShutdown=3, +} +function AI_A2A_DISPATCHER:New(Detection) +local self=BASE:Inherit(self,DETECTION_MANAGER:New(nil,Detection)) +self.Detection=Detection +self.DefenderSquadrons={} +self.DefenderSpawns={} +self.DefenderTasks={} +self.DefenderDefault={} +self.SetSendPlayerMessages=false +self.Detection:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) +self.Detection:SetRefreshTimeInterval(30) +self:SetEngageRadius() +self:SetGciRadius() +self:SetIntercept(300) +self:SetDisengageRadius(300000) +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air) +self:SetDefaultTakeoffInAirAltitude(500) +self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase) +self:SetDefaultOverhead(1) +self:SetDefaultGrouping(1) +self:SetDefaultFuelThreshold(0.15,0) +self:SetDefaultDamageThreshold(0.4) +self:SetDefaultCapTimeInterval(180,600) +self:SetDefaultCapLimit(1) +self:AddTransition("Started","Assign","Started") +self:AddTransition("*","CAP","*") +self:AddTransition("*","GCI","*") +self:AddTransition("*","ENGAGE","*") +self:HandleEvent(EVENTS.Crash,self.OnEventCrashOrDead) +self:HandleEvent(EVENTS.Dead,self.OnEventCrashOrDead) +self:HandleEvent(EVENTS.Land) +self:HandleEvent(EVENTS.EngineShutdown) +self:HandleEvent(EVENTS.BaseCaptured) +self:SetTacticalDisplay(false) +self.DefenderCAPIndex=0 +self:__Start(5) +return self +end +function AI_A2A_DISPATCHER:onafterStart(From,Event,To) +self:GetParent(self,AI_A2A_DISPATCHER).onafterStart(self,From,Event,To) +for SquadronName,_DefenderSquadron in pairs(self.DefenderSquadrons)do +local DefenderSquadron=_DefenderSquadron +DefenderSquadron.Resources={} +if DefenderSquadron.ResourceCount then +for Resource=1,DefenderSquadron.ResourceCount do +self:ParkDefender(DefenderSquadron) +end +end +end +end +function AI_A2A_DISPATCHER:ParkDefender(DefenderSquadron) +local TemplateID=math.random(1,#DefenderSquadron.Spawn) +local Spawn=DefenderSquadron.Spawn[TemplateID] +Spawn:InitGrouping(1) +local SpawnGroup +if self:IsSquadronVisible(DefenderSquadron.Name)then +local Grouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping +Grouping=1 +Spawn:InitGrouping(Grouping) +SpawnGroup=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,SPAWN.Takeoff.Cold) +local GroupName=SpawnGroup:GetName() +DefenderSquadron.Resources=DefenderSquadron.Resources or{} +DefenderSquadron.Resources[TemplateID]=DefenderSquadron.Resources[TemplateID]or{} +DefenderSquadron.Resources[TemplateID][GroupName]={} +DefenderSquadron.Resources[TemplateID][GroupName]=SpawnGroup +self.uncontrolled=self.uncontrolled or{} +self.uncontrolled[DefenderSquadron.Name]=self.uncontrolled[DefenderSquadron.Name]or{} +table.insert(self.uncontrolled[DefenderSquadron.Name],{group=SpawnGroup,name=GroupName,grouping=Grouping}) +end +end +function AI_A2A_DISPATCHER:OnEventBaseCaptured(EventData) +local AirbaseName=EventData.PlaceName +self:T("Captured "..AirbaseName) +for SquadronName,Squadron in pairs(self.DefenderSquadrons)do +if Squadron.AirbaseName==AirbaseName then +Squadron.ResourceCount=-999 +Squadron.Captured=true +self:T("Squadron "..SquadronName.." captured.") +end +end +end +function AI_A2A_DISPATCHER:OnEventCrashOrDead(EventData) +self.Detection:ForgetDetectedUnit(EventData.IniUnitName) +end +function AI_A2A_DISPATCHER:OnEventLand(EventData) +self:F("Landed") +local DefenderUnit=EventData.IniUnit +local Defender=EventData.IniGroup +local Squadron=self:GetSquadronFromDefender(Defender) +if Squadron then +self:F({SquadronName=Squadron.Name}) +local LandingMethod=self:GetSquadronLanding(Squadron.Name) +if LandingMethod==AI_A2A_DISPATCHER.Landing.AtRunway then +local DefenderSize=Defender:GetSize() +if DefenderSize==1 then +self:RemoveDefenderFromSquadron(Squadron,Defender) +end +DefenderUnit:Destroy() +self:ParkDefender(Squadron) +return +end +if DefenderUnit:GetLife()~=DefenderUnit:GetLife0()then +DefenderUnit:Destroy() +return +end +end +end +function AI_A2A_DISPATCHER:OnEventEngineShutdown(EventData) +local DefenderUnit=EventData.IniUnit +local Defender=EventData.IniGroup +local Squadron=self:GetSquadronFromDefender(Defender) +if Squadron then +self:F({SquadronName=Squadron.Name}) +local LandingMethod=self:GetSquadronLanding(Squadron.Name) +if LandingMethod==AI_A2A_DISPATCHER.Landing.AtEngineShutdown and not DefenderUnit:InAir()then +local DefenderSize=Defender:GetSize() +if DefenderSize==1 then +self:RemoveDefenderFromSquadron(Squadron,Defender) +end +DefenderUnit:Destroy() +self:ParkDefender(Squadron) +end +end +end +function AI_A2A_DISPATCHER:SetEngageRadius(EngageRadius) +self.Detection:SetFriendliesRange(EngageRadius or 100000) +return self +end +function AI_A2A_DISPATCHER:SetDisengageRadius(DisengageRadius) +self.DisengageRadius=DisengageRadius or 300000 +return self +end +function AI_A2A_DISPATCHER:SetGciRadius(GciRadius) +self.GciRadius=GciRadius or 200000 +return self +end +function AI_A2A_DISPATCHER:SetBorderZone(BorderZone) +self.Detection:SetAcceptZones(BorderZone) +return self +end +function AI_A2A_DISPATCHER:SetTacticalDisplay(TacticalDisplay) +self.TacticalDisplay=TacticalDisplay +return self +end +function AI_A2A_DISPATCHER:SetDefaultDamageThreshold(DamageThreshold) +self.DefenderDefault.DamageThreshold=DamageThreshold +return self +end +function AI_A2A_DISPATCHER:SetDefaultCapTimeInterval(CapMinSeconds,CapMaxSeconds) +self.DefenderDefault.CapMinSeconds=CapMinSeconds +self.DefenderDefault.CapMaxSeconds=CapMaxSeconds +return self +end +function AI_A2A_DISPATCHER:SetDefaultCapLimit(CapLimit) +self.DefenderDefault.CapLimit=CapLimit +return self +end +function AI_A2A_DISPATCHER:SetIntercept(InterceptDelay) +self.DefenderDefault.InterceptDelay=InterceptDelay +local Detection=self.Detection +Detection:SetIntercept(true,InterceptDelay) +return self +end +function AI_A2A_DISPATCHER:GetAIFriendliesNearBy(DetectedItem) +local FriendliesNearBy=self.Detection:GetFriendliesDistance(DetectedItem) +return FriendliesNearBy +end +function AI_A2A_DISPATCHER:GetDefenderTasks() +return self.DefenderTasks or{} +end +function AI_A2A_DISPATCHER:GetDefenderTask(Defender) +return self.DefenderTasks[Defender] +end +function AI_A2A_DISPATCHER:GetDefenderTaskFsm(Defender) +return self:GetDefenderTask(Defender).Fsm +end +function AI_A2A_DISPATCHER:GetDefenderTaskTarget(Defender) +return self:GetDefenderTask(Defender).Target +end +function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName(Defender) +return self:GetDefenderTask(Defender).SquadronName +end +function AI_A2A_DISPATCHER:ClearDefenderTask(Defender) +if Defender and Defender:IsAlive()and self.DefenderTasks[Defender]then +local Target=self.DefenderTasks[Defender].Target +local Message="Clearing ("..self.DefenderTasks[Defender].Type..") " +Message=Message..Defender:GetName() +if Target then +Message=Message..(Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or"" +end +self:F({Target=Message}) +end +self.DefenderTasks[Defender]=nil +return self +end +function AI_A2A_DISPATCHER:ClearDefenderTaskTarget(Defender) +local DefenderTask=self:GetDefenderTask(Defender) +if Defender and Defender:IsAlive()and DefenderTask then +local Target=DefenderTask.Target +local Message="Clearing ("..DefenderTask.Type..") " +Message=Message..Defender:GetName() +if Target then +Message=Message..((Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or"") +end +self:F({Target=Message}) +end +if Defender and DefenderTask and DefenderTask.Target then +DefenderTask.Target=nil +end +return self +end +function AI_A2A_DISPATCHER:SetDefenderTask(SquadronName,Defender,Type,Fsm,Target) +self:F({SquadronName=SquadronName,Defender=Defender:GetName(),Type=Type,Target=Target}) +self.DefenderTasks[Defender]=self.DefenderTasks[Defender]or{} +self.DefenderTasks[Defender].Type=Type +self.DefenderTasks[Defender].Fsm=Fsm +self.DefenderTasks[Defender].SquadronName=SquadronName +if Target then +self:SetDefenderTaskTarget(Defender,Target) +end +return self +end +function AI_A2A_DISPATCHER:SetDefenderTaskTarget(Defender,AttackerDetection) +local Message="("..self.DefenderTasks[Defender].Type..") " +Message=Message..Defender:GetName() +Message=Message..((AttackerDetection and(" target "..AttackerDetection.Index.." ["..AttackerDetection.Set:Count().."]"))or"") +self:F({AttackerDetection=Message}) +if AttackerDetection then +self.DefenderTasks[Defender].Target=AttackerDetection +end +return self +end +function AI_A2A_DISPATCHER:SetSquadron(SquadronName,AirbaseName,TemplatePrefixes,ResourceCount) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +local DefenderSquadron=self.DefenderSquadrons[SquadronName] +DefenderSquadron.Name=SquadronName +DefenderSquadron.Airbase=AIRBASE:FindByName(AirbaseName) +DefenderSquadron.AirbaseName=DefenderSquadron.Airbase:GetName() +if not DefenderSquadron.Airbase then +error("Cannot find airbase with name:"..AirbaseName) +end +DefenderSquadron.Spawn={} +if type(TemplatePrefixes)=="string"then +local SpawnTemplate=TemplatePrefixes +self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate) +DefenderSquadron.Spawn[1]=self.DefenderSpawns[SpawnTemplate] +else +for TemplateID,SpawnTemplate in pairs(TemplatePrefixes)do +self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate) +DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1]=self.DefenderSpawns[SpawnTemplate] +end +end +DefenderSquadron.ResourceCount=ResourceCount +DefenderSquadron.TemplatePrefixes=TemplatePrefixes +DefenderSquadron.Captured=false +self:SetSquadronLanguage(SquadronName,"EN") +self:F({Squadron={SquadronName,AirbaseName,TemplatePrefixes,ResourceCount}}) +return self +end +function AI_A2A_DISPATCHER:GetSquadron(SquadronName) +local DefenderSquadron=self.DefenderSquadrons[SquadronName] +if not DefenderSquadron then +error("Unknown Squadron:"..SquadronName) +end +return DefenderSquadron +end +function AI_A2A_DISPATCHER:QuerySquadron(Squadron) +local Squadron=self:GetSquadron(Squadron) +if Squadron.ResourceCount then +self:T2(string.format("%s = %s",Squadron.Name,Squadron.ResourceCount)) +return Squadron.ResourceCount +end +self:F({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) +return nil +end +function AI_A2A_DISPATCHER:SetSquadronVisible(SquadronName) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Uncontrolled=true +DefenderSquadron.Grouping=1 +local nfreeparking=DefenderSquadron.Airbase:GetFreeParkingSpotsNumber(AIRBASE.TerminalType.FighterAircraft,true) +DefenderSquadron.ResourceCount=DefenderSquadron.ResourceCount or nfreeparking +DefenderSquadron.ResourceCount=math.min(DefenderSquadron.ResourceCount,nfreeparking) +for SpawnTemplate,_DefenderSpawn in pairs(self.DefenderSpawns)do +local DefenderSpawn=_DefenderSpawn +DefenderSpawn:InitUnControlled(true) +end +end +function AI_A2A_DISPATCHER:IsSquadronVisible(SquadronName) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +if DefenderSquadron then +return DefenderSquadron.Uncontrolled==true +end +return nil +end +function AI_A2A_DISPATCHER:SetSquadronCap2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +local Cap=self.DefenderSquadrons[SquadronName].Cap +Cap.Name=SquadronName +Cap.EngageMinSpeed=EngageMinSpeed +Cap.EngageMaxSpeed=EngageMaxSpeed +Cap.EngageFloorAltitude=EngageFloorAltitude +Cap.EngageCeilingAltitude=EngageCeilingAltitude +Cap.Zone=Zone +Cap.PatrolMinSpeed=PatrolMinSpeed +Cap.PatrolMaxSpeed=PatrolMaxSpeed +Cap.PatrolFloorAltitude=PatrolFloorAltitude +Cap.PatrolCeilingAltitude=PatrolCeilingAltitude +Cap.PatrolAltType=PatrolAltType +Cap.EngageAltType=EngageAltType +self:SetSquadronCapInterval(SquadronName,self.DefenderDefault.CapLimit,self.DefenderDefault.CapMinSeconds,self.DefenderDefault.CapMaxSeconds,1) +self:T({CAP={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageAltType}}) +local RecceSet=self.Detection:GetDetectionSet() +RecceSet:FilterPrefixes(DefenderSquadron.TemplatePrefixes) +RecceSet:FilterStart() +self.Detection:SetFriendlyPrefixes(DefenderSquadron.TemplatePrefixes) +return self +end +function AI_A2A_DISPATCHER:SetSquadronCap(SquadronName,Zone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) +return self:SetSquadronCap2(SquadronName,EngageMinSpeed,EngageMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,AltType,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,AltType) +end +function AI_A2A_DISPATCHER:SetSquadronCapInterval(SquadronName,CapLimit,LowInterval,HighInterval,Probability) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +local Cap=self.DefenderSquadrons[SquadronName].Cap +if Cap then +Cap.LowInterval=LowInterval or 180 +Cap.HighInterval=HighInterval or 600 +Cap.Probability=Probability or 1 +Cap.CapLimit=CapLimit or 1 +Cap.Scheduler=Cap.Scheduler or SCHEDULER:New(self) +local Scheduler=Cap.Scheduler +local ScheduleID=Cap.ScheduleID +local Variance=(Cap.HighInterval-Cap.LowInterval)/2 +local Repeat=Cap.LowInterval+Variance +local Randomization=Variance/Repeat +local Start=math.random(1,Cap.HighInterval) +if ScheduleID then +Scheduler:Stop(ScheduleID) +end +Cap.ScheduleID=Scheduler:Schedule(self,self.SchedulerCAP,{SquadronName},Start,Repeat,Randomization) +else +error("This squadron does not exist:"..SquadronName) +end +end +function AI_A2A_DISPATCHER:GetCAPDelay(SquadronName) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +local Cap=self.DefenderSquadrons[SquadronName].Cap +if Cap then +return math.random(Cap.LowInterval,Cap.HighInterval) +else +error("This squadron does not exist:"..SquadronName) +end +end +function AI_A2A_DISPATCHER:CanCAP(SquadronName) +self:F({SquadronName=SquadronName}) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +if DefenderSquadron.Captured==false then +if(not DefenderSquadron.ResourceCount)or(DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount>0)then +local Cap=DefenderSquadron.Cap +if Cap then +local CapCount=self:CountCapAirborne(SquadronName) +self:F({CapCount=CapCount}) +if CapCount0)then +local Gci=DefenderSquadron.Gci +if Gci then +return DefenderSquadron +end +end +end +return nil +end +function AI_A2A_DISPATCHER:SetSquadronGci2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{} +local Intercept=self.DefenderSquadrons[SquadronName].Gci +Intercept.Name=SquadronName +Intercept.EngageMinSpeed=EngageMinSpeed +Intercept.EngageMaxSpeed=EngageMaxSpeed +Intercept.EngageFloorAltitude=EngageFloorAltitude +Intercept.EngageCeilingAltitude=EngageCeilingAltitude +Intercept.EngageAltType=EngageAltType +self:T({GCI={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +end +function AI_A2A_DISPATCHER:SetSquadronGci(SquadronName,EngageMinSpeed,EngageMaxSpeed) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{} +local Intercept=self.DefenderSquadrons[SquadronName].Gci +Intercept.Name=SquadronName +Intercept.EngageMinSpeed=EngageMinSpeed +Intercept.EngageMaxSpeed=EngageMaxSpeed +self:F({GCI={SquadronName,EngageMinSpeed,EngageMaxSpeed}}) +end +function AI_A2A_DISPATCHER:SetDefaultOverhead(Overhead) +self.DefenderDefault.Overhead=Overhead +return self +end +function AI_A2A_DISPATCHER:SetSquadronOverhead(SquadronName,Overhead) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Overhead=Overhead +return self +end +function AI_A2A_DISPATCHER:SetDefaultGrouping(Grouping) +self.DefenderDefault.Grouping=Grouping +return self +end +function AI_A2A_DISPATCHER:SetSquadronGrouping(SquadronName,Grouping) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Grouping=Grouping +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoff(Takeoff) +self.DefenderDefault.Takeoff=Takeoff +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoff(SquadronName,Takeoff) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Takeoff=Takeoff +return self +end +function AI_A2A_DISPATCHER:GetDefaultTakeoff() +return self.DefenderDefault.Takeoff +end +function AI_A2A_DISPATCHER:GetSquadronTakeoff(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air) +return self +end +function AI_A2A_DISPATCHER:SetSendMessages(onoff) +self.SetSendPlayerMessages=onoff +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir(SquadronName,TakeoffAltitude) +self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Air) +if TakeoffAltitude then +self:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) +end +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Runway) +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Runway) +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Hot) +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Hot) +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Cold) +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Cold) +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude) +self.DefenderDefault.TakeoffAltitude=TakeoffAltitude +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.TakeoffAltitude=TakeoffAltitude +return self +end +function AI_A2A_DISPATCHER:SetDefaultLanding(Landing) +self.DefenderDefault.Landing=Landing +return self +end +function AI_A2A_DISPATCHER:SetSquadronLanding(SquadronName,Landing) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Landing=Landing +return self +end +function AI_A2A_DISPATCHER:GetDefaultLanding() +return self.DefenderDefault.Landing +end +function AI_A2A_DISPATCHER:GetSquadronLanding(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Landing or self.DefenderDefault.Landing +end +function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() +self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase) +return self +end +function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.NearAirbase) +return self +end +function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() +self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtRunway) +return self +end +function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtRunway) +return self +end +function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() +self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtEngineShutdown) +return self +end +function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtEngineShutdown) +return self +end +function AI_A2A_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold) +self.DefenderDefault.FuelThreshold=FuelThreshold +return self +end +function AI_A2A_DISPATCHER:SetSquadronFuelThreshold(SquadronName,FuelThreshold) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.FuelThreshold=FuelThreshold +return self +end +function AI_A2A_DISPATCHER:SetDefaultTanker(TankerName) +self.DefenderDefault.TankerName=TankerName +return self +end +function AI_A2A_DISPATCHER:SetSquadronTanker(SquadronName,TankerName) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.TankerName=TankerName +return self +end +function AI_A2A_DISPATCHER:SetSquadronLanguage(SquadronName,Language) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Language=Language +if DefenderSquadron.RadioQueue then +DefenderSquadron.RadioQueue:SetLanguage(Language) +end +return self +end +function AI_A2A_DISPATCHER:SetSquadronRadioFrequency(SquadronName,RadioFrequency,RadioModulation,RadioPower) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.RadioFrequency=RadioFrequency +DefenderSquadron.RadioModulation=RadioModulation or radio.modulation.AM +DefenderSquadron.RadioPower=RadioPower or 100 +if DefenderSquadron.RadioQueue then +DefenderSquadron.RadioQueue:Stop() +end +DefenderSquadron.RadioQueue=nil +DefenderSquadron.RadioQueue=RADIOSPEECH:New(DefenderSquadron.RadioFrequency,DefenderSquadron.RadioModulation) +DefenderSquadron.RadioQueue.power=DefenderSquadron.RadioPower +DefenderSquadron.RadioQueue:Start(0.5) +DefenderSquadron.RadioQueue:SetLanguage(DefenderSquadron.Language) +end +function AI_A2A_DISPATCHER:AddDefenderToSquadron(Squadron,Defender,Size) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +self.Defenders[DefenderName]=Squadron +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount-Size +end +self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount+Defender:GetSize() +end +self.Defenders[DefenderName]=nil +self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2A_DISPATCHER:GetSquadronFromDefender(Defender) +self.Defenders=self.Defenders or{} +if Defender~=nil then +local DefenderName=Defender:GetName() +self:F({DefenderName=DefenderName}) +return self.Defenders[DefenderName] +else +return nil +end +end +function AI_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +if DetectedItem.IsDetected==false then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function AI_A2A_DISPATCHER:CountCapAirborne(SquadronName) +local CapCount=0 +local DefenderSquadron=self.DefenderSquadrons[SquadronName] +if DefenderSquadron then +for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do +if DefenderTask.SquadronName==SquadronName then +if DefenderTask.Type=="CAP"then +if AIGroup and AIGroup:IsAlive()then +if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling")or DefenderTask.Fsm:Is("Started")then +CapCount=CapCount+1 +end +end +end +end +end +end +return CapCount +end +function AI_A2A_DISPATCHER:CountDefendersEngaged(AttackerDetection) +local DefenderCount=0 +local DetectedSet=AttackerDetection.Set +local DefenderTasks=self:GetDefenderTasks() +for DefenderGroup,DefenderTask in pairs(DefenderTasks)do +local Defender=DefenderGroup +local DefenderTaskTarget=DefenderTask.Target +local DefenderSquadronName=DefenderTask.SquadronName +if DefenderTaskTarget and DefenderTaskTarget.Index==AttackerDetection.Index then +local Squadron=self:GetSquadron(DefenderSquadronName) +local SquadronOverhead=Squadron.Overhead or self.DefenderDefault.Overhead +local DefenderSize=Defender:GetInitialSize() +if DefenderSize then +DefenderCount=DefenderCount+DefenderSize/SquadronOverhead +self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize) +else +DefenderCount=0 +end +end +end +self:F({DefenderCount=DefenderCount}) +return DefenderCount +end +function AI_A2A_DISPATCHER:CountDefendersToBeEngaged(AttackerDetection,DefenderCount) +local Friendlies=nil +local AttackerSet=AttackerDetection.Set +local AttackerCount=AttackerSet:Count() +local DefenderFriendlies=self:GetAIFriendliesNearBy(AttackerDetection) +for FriendlyDistance,AIFriendly in UTILS.spairs(DefenderFriendlies or{})do +if AttackerCount>DefenderCount then +if AIFriendly then +local classname=AIFriendly.ClassName or"No Class Name" +local unitname=AIFriendly.IdentifiableName or"No Unit Name" +end +local Friendly=nil +if AIFriendly and AIFriendly:IsAlive()then +Friendly=AIFriendly:GetGroup() +end +if Friendly and Friendly:IsAlive()then +local DefenderTask=self:GetDefenderTask(Friendly) +if DefenderTask then +if DefenderTask.Type=="CAP"or DefenderTask.Type=="GCI"then +if DefenderTask.Target==nil then +if DefenderTask.Fsm:Is("Returning")or DefenderTask.Fsm:Is("Patrolling")then +Friendlies=Friendlies or{} +Friendlies[Friendly]=Friendly +DefenderCount=DefenderCount+Friendly:GetSize() +self:F({Friendly=Friendly:GetName(),FriendlyDistance=FriendlyDistance}) +end +end +end +end +end +else +break +end +end +return Friendlies +end +function AI_A2A_DISPATCHER:ResourceActivate(DefenderSquadron,DefendersNeeded) +local SquadronName=DefenderSquadron.Name +DefendersNeeded=DefendersNeeded or 4 +local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping +DefenderGrouping=(DefenderGrouping0 then +local id=math.random(n) +local Defender=self.uncontrolled[SquadronName][id].group +Defender:StartUncontrolled() +DefenderGrouping=self.uncontrolled[SquadronName][id].grouping +self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) +table.remove(self.uncontrolled[SquadronName],id) +return Defender,DefenderGrouping +else +return nil,0 +end +local TemplateID=math.random(1,#DefenderSquadron.Spawn) +else +local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)] +if DefenderGrouping then +Spawn:InitGrouping(DefenderGrouping) +else +Spawn:InitGrouping() +end +local TakeoffMethod=self:GetSquadronTakeoff(SquadronName) +local Defender=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude) +self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) +return Defender,DefenderGrouping +end +return nil,nil +end +function AI_A2A_DISPATCHER:onafterCAP(From,Event,To,SquadronName) +self:F({SquadronName=SquadronName}) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:CanCAP(SquadronName) +if DefenderSquadron then +local Cap=DefenderSquadron.Cap +if Cap then +local DefenderCAP,DefenderGrouping=self:ResourceActivate(DefenderSquadron) +if DefenderCAP then +local AI_A2A_Fsm=AI_A2A_CAP:New2(DefenderCAP,Cap.EngageMinSpeed,Cap.EngageMaxSpeed,Cap.EngageFloorAltitude,Cap.EngageCeilingAltitude,Cap.EngageAltType,Cap.Zone,Cap.PatrolMinSpeed,Cap.PatrolMaxSpeed,Cap.PatrolFloorAltitude,Cap.PatrolCeilingAltitude,Cap.PatrolAltType) +AI_A2A_Fsm:SetDispatcher(self) +AI_A2A_Fsm:SetHomeAirbase(DefenderSquadron.Airbase) +AI_A2A_Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60) +AI_A2A_Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold) +AI_A2A_Fsm:SetDisengageRadius(self.DisengageRadius) +AI_A2A_Fsm:SetTanker(DefenderSquadron.TankerName or self.DefenderDefault.TankerName) +if DefenderSquadron.Racetrack or self.DefenderDefault.Racetrack then +AI_A2A_Fsm:SetRaceTrackPattern(DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, +DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax, +DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, +DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, +DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin, +DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax, +DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates) +end +AI_A2A_Fsm:Start() +self:SetDefenderTask(SquadronName,DefenderCAP,"CAP",AI_A2A_Fsm) +function AI_A2A_Fsm:onafterTakeoff(DefenderGroup,From,Event,To) +if DefenderGroup and DefenderGroup:IsAlive()then +self:F({"CAP Takeoff",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2A_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron then +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." Wheels up.",DefenderGroup) +end +AI_A2A_Fsm:__Patrol(2) +end +end +end +function AI_A2A_Fsm:onafterPatrolRoute(DefenderGroup,From,Event,To) +if DefenderGroup and DefenderGroup:IsAlive()then +self:F({"CAP PatrolRoute",DefenderGroup:GetName()}) +self:GetParent(self).onafterPatrolRoute(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", patrolling.",DefenderGroup) +end +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +end +function AI_A2A_Fsm:onafterRTB(DefenderGroup,From,Event,To) +if DefenderGroup and DefenderGroup:IsAlive()then +self:F({"CAP RTB",DefenderGroup:GetName()}) +self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." returning to base.",DefenderGroup) +end +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +end +function AI_A2A_Fsm:onafterHome(Defender,From,Event,To,Action) +if Defender and Defender:IsAlive()then +self:F({"CAP Home",Defender:GetName()}) +self:GetParent(self).onafterHome(self,Defender,From,Event,To) +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(Defender) +if Action and Action=="Destroy"then +Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender) +Defender:Destroy() +end +if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2A_DISPATCHER.Landing.NearAirbase then +Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender) +Defender:Destroy() +Dispatcher:ParkDefender(Squadron) +end +end +end +end +end +end +end +function AI_A2A_DISPATCHER:onafterENGAGE(From,Event,To,AttackerDetection,Defenders) +self:F("ENGAGING Detection ID="..tostring(AttackerDetection.ID)) +if Defenders then +for DefenderID,Defender in pairs(Defenders)do +local Fsm=self:GetDefenderTaskFsm(Defender) +Fsm:EngageRoute(AttackerDetection.Set) +self:SetDefenderTaskTarget(Defender,AttackerDetection) +end +end +end +function AI_A2A_DISPATCHER:onafterGCI(From,Event,To,AttackerDetection,DefendersMissing,DefenderFriendlies) +self:F("GCI Detection ID="..tostring(AttackerDetection.ID)) +self:F({From,Event,To,AttackerDetection.Index,DefendersMissing,DefenderFriendlies}) +local AttackerSet=AttackerDetection.Set +local AttackerUnit=AttackerSet:GetFirst() +if AttackerUnit and AttackerUnit:IsAlive()then +local AttackerCount=AttackerSet:Count() +local DefenderCount=0 +for DefenderID,DefenderGroup in pairs(DefenderFriendlies or{})do +local Fsm=self:GetDefenderTaskFsm(DefenderGroup) +Fsm:__EngageRoute(0.1,AttackerSet) +self:SetDefenderTaskTarget(DefenderGroup,AttackerDetection) +DefenderCount=DefenderCount+DefenderGroup:GetSize() +end +self:F({DefenderCount=DefenderCount,DefendersMissing=DefendersMissing}) +DefenderCount=DefendersMissing +local ClosestDistance=0 +local ClosestDefenderSquadronName=nil +local BreakLoop=false +while(DefenderCount>0 and not BreakLoop)do +self:F({DefenderSquadrons=self.DefenderSquadrons}) +for SquadronName,DefenderSquadron in pairs(self.DefenderSquadrons or{})do +self:F({GCI=DefenderSquadron.Gci}) +for InterceptID,Intercept in pairs(DefenderSquadron.Gci or{})do +self:F({DefenderSquadron}) +local SpawnCoord=DefenderSquadron.Airbase:GetCoordinate() +local AttackerCoord=AttackerUnit:GetCoordinate() +local InterceptCoord=AttackerDetection.InterceptCoord +self:F({InterceptCoord=InterceptCoord}) +if InterceptCoord then +local InterceptDistance=SpawnCoord:Get2DDistance(InterceptCoord) +local AirbaseDistance=SpawnCoord:Get2DDistance(AttackerCoord) +self:F({InterceptDistance=InterceptDistance,AirbaseDistance=AirbaseDistance,InterceptCoord=InterceptCoord}) +if ClosestDistance==0 or InterceptDistanceDefenderSquadron.ResourceCount then +DefendersNeeded=DefenderSquadron.ResourceCount +BreakLoop=true +end +while(DefendersNeeded>0)do +local DefenderGCI,DefenderGrouping=self:ResourceActivate(DefenderSquadron,DefendersNeeded) +DefendersNeeded=DefendersNeeded-DefenderGrouping +if DefenderGCI then +DefenderCount=DefenderCount-DefenderGrouping/DefenderOverhead +local Fsm=AI_A2A_GCI:New2(DefenderGCI,Gci.EngageMinSpeed,Gci.EngageMaxSpeed,Gci.EngageFloorAltitude,Gci.EngageCeilingAltitude,Gci.EngageAltType) +Fsm:SetDispatcher(self) +Fsm:SetHomeAirbase(DefenderSquadron.Airbase) +Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60) +Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold) +Fsm:SetDisengageRadius(self.DisengageRadius) +Fsm:Start() +self:SetDefenderTask(ClosestDefenderSquadronName,DefenderGCI,"GCI",Fsm,AttackerDetection) +function Fsm:onafterTakeoff(DefenderGroup,From,Event,To) +self:F({"GCI Birth",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +local DefenderTarget=Dispatcher:GetDefenderTaskTarget(DefenderGroup) +if DefenderTarget then +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." wheels up.",DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." колёса вверх.",DefenderGroup) +end +Fsm:EngageRoute(DefenderTarget.Set) +end +end +function Fsm:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"GCI Route",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron and AttackSetUnit:Count()>0 then +local FirstUnit=AttackSetUnit:GetFirst() +local Coordinate=FirstUnit:GetCoordinate() +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", intercepting bogeys at "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", перехватывая боги в "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +elseif Squadron.Language=="DE"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", Eindringlinge abfangen bei"..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +end +end +self:GetParent(Fsm).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit) +end +function Fsm:onafterEngage(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"GCI Engage",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron and AttackSetUnit:Count()>0 then +local FirstUnit=AttackSetUnit:GetFirst() +local Coordinate=FirstUnit:GetCoordinate() +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging bogeys at "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", задействуя боги в "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +end +end +self:GetParent(Fsm).onafterEngage(self,DefenderGroup,From,Event,To,AttackSetUnit) +end +function Fsm:onafterRTB(DefenderGroup,From,Event,To) +self:F({"GCI RTB",DefenderGroup:GetName()}) +self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron then +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." returning to base.",DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", возвращение на базу.",DefenderGroup) +end +end +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +function Fsm:onafterLostControl(Defender,From,Event,To) +self:F({"GCI LostControl",Defender:GetName()}) +self:GetParent(self).onafterHome(self,Defender,From,Event,To) +local Dispatcher=Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(Defender) +if Defender:IsAboveRunway()then +Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender) +Defender:Destroy() +end +end +function Fsm:onafterHome(DefenderGroup,From,Event,To,Action) +self:F({"GCI Home",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." landing at base.",DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", посадка на базу.",DefenderGroup) +end +if Action and Action=="Destroy"then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2A_DISPATCHER.Landing.NearAirbase then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +Dispatcher:ParkDefender(Squadron) +end +end +end +end +end +else +BreakLoop=true +break +end +else +break +end +end +end +end +function AI_A2A_DISPATCHER:EvaluateENGAGE(DetectedItem) +self:F({DetectedItem.ItemID}) +local DefenderCount=self:CountDefendersEngaged(DetectedItem) +local DefenderGroups=self:CountDefendersToBeEngaged(DetectedItem,DefenderCount) +self:F({DefenderCount=DefenderCount}) +if DefenderGroups and DetectedItem.IsDetected==true then +return DefenderGroups +end +return nil +end +function AI_A2A_DISPATCHER:EvaluateGCI(DetectedItem) +self:F({DetectedItem.ItemID}) +local AttackerSet=DetectedItem.Set +local AttackerCount=AttackerSet:Count() +local DefenderCount=self:CountDefendersEngaged(DetectedItem) +local DefendersMissing=AttackerCount-DefenderCount +self:F({AttackerCount=AttackerCount,DefenderCount=DefenderCount,DefendersMissing=DefendersMissing}) +local Friendlies=self:CountDefendersToBeEngaged(DetectedItem,DefenderCount) +if DetectedItem.IsDetected==true then +return DefendersMissing,Friendlies +end +return nil,nil +end +function AI_A2A_DISPATCHER:Order(DetectedItem) +local detection=self.Detection +local ShortestDistance=999999999 +local AttackCoordinate=detection:GetDetectedItemCoordinate(DetectedItem) +if AttackCoordinate then +for DefenderSquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do +self:T({DefenderSquadron=DefenderSquadron.Name}) +local Airbase=DefenderSquadron.Airbase +local AirbaseCoordinate=Airbase:GetCoordinate() +local EvaluateDistance=AttackCoordinate:Get2DDistance(AirbaseCoordinate) +if EvaluateDistance<=ShortestDistance then +ShortestDistance=EvaluateDistance +end +end +end +return ShortestDistance +end +function AI_A2A_DISPATCHER:ShowTacticalDisplay(Detection) +local AreaMsg={} +local TaskMsg={} +local ChangeMsg={} +local TaskReport=REPORT:New() +local Report=REPORT:New("Tactical Overview:") +local DefenderGroupCount=0 +for DetectedItemID,DetectedItem in UTILS.spairs(Detection:GetDetectedItems(),function(t,a,b) +return self:Order(t[a])0 then +self:F({DefendersMissing=DefendersMissing}) +self:GCI(DetectedItem,DefendersMissing,Friendlies) +end +end +end +if self.TacticalDisplay then +self:ShowTacticalDisplay(Detection) +end +return true +end +end +do +function AI_A2A_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem) +local PlayerTypes={} +local PlayersCount=0 +if PlayersNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do +local PlayerUnit=PlayerUnitData +local PlayerName=PlayerUnit:GetPlayerName() +if PlayerUnit:IsAirPlane()and PlayerName~=nil then +local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel() +PlayersCount=PlayersCount+1 +local PlayerType=PlayerUnit:GetTypeName() +PlayerTypes[PlayerName]=PlayerType +if DetectedTreatLevel0 then +for PlayerName,PlayerType in pairs(PlayerTypes)do +PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType)) +end +else +PlayerTypesReport:Add("-") +end +return PlayersCount,PlayerTypesReport +end +function AI_A2A_DISPATCHER:GetFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem) +local FriendlyTypes={} +local FriendliesCount=0 +if FriendlyUnitsNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do +local FriendlyUnit=FriendlyUnitData +if FriendlyUnit:IsAirPlane()then +local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel() +FriendliesCount=FriendliesCount+1 +local FriendlyType=FriendlyUnit:GetTypeName() +FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1 +if DetectedTreatLevel0 then +for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do +FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType)) +end +else +FriendlyTypesReport:Add("-") +end +return FriendliesCount,FriendlyTypesReport +end +function AI_A2A_DISPATCHER:SchedulerCAP(SquadronName) +self:CAP(SquadronName) +end +function AI_A2A_DISPATCHER:AddToSquadron(Squadron,Amount) +local Squadron=self:GetSquadron(Squadron) +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount+Amount +end +self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2A_DISPATCHER:RemoveFromSquadron(Squadron,Amount) +local Squadron=self:GetSquadron(Squadron) +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount-Amount +end +self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) +end +end +do +AI_A2A_GCICAP={ +ClassName="AI_A2A_GCICAP", +Detection=nil, +} +function AI_A2A_GCICAP:New(EWRPrefixes,TemplatePrefixes,CapPrefixes,CapLimit,GroupingRadius,EngageRadius,GciRadius,ResourceCount) +local EWRSetGroup=SET_GROUP:New() +EWRSetGroup:FilterPrefixes(EWRPrefixes) +EWRSetGroup:FilterStart() +local Detection=DETECTION_AREAS:New(EWRSetGroup,GroupingRadius or 30000) +local self=BASE:Inherit(self,AI_A2A_DISPATCHER:New(Detection)) +self:SetEngageRadius(EngageRadius) +self:SetGciRadius(GciRadius) +local EWRFirst=EWRSetGroup:GetFirst() +local EWRCoalition=EWRFirst:GetCoalition() +local AirbaseNames={} +for AirbaseID,AirbaseData in pairs(_DATABASE.AIRBASES)do +local Airbase=AirbaseData +local AirbaseName=Airbase:GetName() +if Airbase:GetCoalition()==EWRCoalition then +table.insert(AirbaseNames,AirbaseName) +end +end +self.Templates=SET_GROUP:New():FilterPrefixes(TemplatePrefixes):FilterOnce() +self:T({Airbases=AirbaseNames}) +self:T("Defining Templates for Airbases ...") +for AirbaseID,AirbaseName in pairs(AirbaseNames)do +local Airbase=_DATABASE:FindAirbase(AirbaseName) +local AirbaseName=Airbase:GetName() +local AirbaseCoord=Airbase:GetCoordinate() +local AirbaseZone=ZONE_RADIUS:New("Airbase",AirbaseCoord:GetVec2(),3000) +local Templates=nil +self:T({Airbase=AirbaseName}) +for TemplateID,Template in pairs(self.Templates:GetSet())do +local Template=Template +local TemplateCoord=Template:GetCoordinate() +if AirbaseZone:IsVec2InZone(TemplateCoord:GetVec2())then +Templates=Templates or{} +table.insert(Templates,Template:GetName()) +self:T({Template=Template:GetName()}) +end +end +if Templates then +self:SetSquadron(AirbaseName,AirbaseName,Templates,ResourceCount) +end +end +self.CAPTemplates=SET_GROUP:New() +self.CAPTemplates:FilterPrefixes(CapPrefixes) +self.CAPTemplates:FilterOnce() +self:T("Setting up CAP ...") +for CAPID,CAPTemplate in pairs(self.CAPTemplates:GetSet())do +local CAPZone=ZONE_POLYGON:New(CAPTemplate:GetName(),CAPTemplate) +local AirbaseDistance=99999999 +local AirbaseClosest=nil +self:T({CAPZoneGroup=CAPID}) +for AirbaseID,AirbaseName in pairs(AirbaseNames)do +local Airbase=_DATABASE:FindAirbase(AirbaseName) +local AirbaseName=Airbase:GetName() +local AirbaseCoord=Airbase:GetCoordinate() +local Squadron=self.DefenderSquadrons[AirbaseName] +if Squadron then +local Distance=AirbaseCoord:Get2DDistance(CAPZone:GetCoordinate()) +self:T({AirbaseDistance=Distance}) +if Distance0)then +local Patrol=DefenderSquadron[DefenseTaskType] +if Patrol and Patrol.Patrol==true then +local PatrolCount=self:CountPatrolAirborne(SquadronName,DefenseTaskType) +self:F({PatrolCount=PatrolCount,PatrolLimit=Patrol.PatrolLimit,PatrolProbability=Patrol.Probability}) +if PatrolCount0)then +if DefenderSquadron[DefenseTaskType]and(DefenderSquadron[DefenseTaskType].Defend==true)then +return DefenderSquadron,DefenderSquadron[DefenseTaskType] +end +end +end +return nil +end +function AI_A2G_DISPATCHER:SetSquadronEngageLimit(SquadronName,EngageLimit,DefenseTaskType) +local DefenderSquadron=self:GetSquadron(SquadronName) +local Defense=DefenderSquadron[DefenseTaskType] +if Defense then +Defense.EngageLimit=EngageLimit or 1 +else +error("This squadron does not exist:"..SquadronName) +end +end +function AI_A2G_DISPATCHER:SetSquadronSead2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.SEAD=DefenderSquadron.SEAD or{} +local Sead=DefenderSquadron.SEAD +Sead.Name=SquadronName +Sead.EngageMinSpeed=EngageMinSpeed +Sead.EngageMaxSpeed=EngageMaxSpeed +Sead.EngageFloorAltitude=EngageFloorAltitude or 500 +Sead.EngageCeilingAltitude=EngageCeilingAltitude or 1000 +Sead.EngageAltType=EngageAltType +Sead.Defend=true +self:T({SEAD={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +return self +end +function AI_A2G_DISPATCHER:SetSquadronSead(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude) +return self:SetSquadronSead2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO") +end +function AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit(SquadronName,EngageLimit) +self:SetSquadronEngageLimit(SquadronName,EngageLimit,"SEAD") +end +function AI_A2G_DISPATCHER:SetSquadronSeadPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.SEAD=DefenderSquadron.SEAD or{} +local SeadPatrol=DefenderSquadron.SEAD +SeadPatrol.Name=SquadronName +SeadPatrol.Zone=Zone +SeadPatrol.PatrolFloorAltitude=PatrolFloorAltitude +SeadPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude +SeadPatrol.EngageFloorAltitude=EngageFloorAltitude +SeadPatrol.EngageCeilingAltitude=EngageCeilingAltitude +SeadPatrol.PatrolMinSpeed=PatrolMinSpeed +SeadPatrol.PatrolMaxSpeed=PatrolMaxSpeed +SeadPatrol.EngageMinSpeed=EngageMinSpeed +SeadPatrol.EngageMaxSpeed=EngageMaxSpeed +SeadPatrol.PatrolAltType=PatrolAltType +SeadPatrol.EngageAltType=EngageAltType +SeadPatrol.Patrol=true +self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"SEAD") +self:T({SEAD={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +end +function AI_A2G_DISPATCHER:SetSquadronSeadPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) +self:SetSquadronSeadPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType) +end +function AI_A2G_DISPATCHER:SetSquadronCas2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.CAS=DefenderSquadron.CAS or{} +local Cas=DefenderSquadron.CAS +Cas.Name=SquadronName +Cas.EngageMinSpeed=EngageMinSpeed +Cas.EngageMaxSpeed=EngageMaxSpeed +Cas.EngageFloorAltitude=EngageFloorAltitude or 500 +Cas.EngageCeilingAltitude=EngageCeilingAltitude or 1000 +Cas.EngageAltType=EngageAltType +Cas.Defend=true +self:T({CAS={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +return self +end +function AI_A2G_DISPATCHER:SetSquadronCas(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude) +return self:SetSquadronCas2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO") +end +function AI_A2G_DISPATCHER:SetSquadronCasEngageLimit(SquadronName,EngageLimit) +self:SetSquadronEngageLimit(SquadronName,EngageLimit,"CAS") +end +function AI_A2G_DISPATCHER:SetSquadronCasPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.CAS=DefenderSquadron.CAS or{} +local CasPatrol=DefenderSquadron.CAS +CasPatrol.Name=SquadronName +CasPatrol.Zone=Zone +CasPatrol.PatrolFloorAltitude=PatrolFloorAltitude +CasPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude +CasPatrol.EngageFloorAltitude=EngageFloorAltitude +CasPatrol.EngageCeilingAltitude=EngageCeilingAltitude +CasPatrol.PatrolMinSpeed=PatrolMinSpeed +CasPatrol.PatrolMaxSpeed=PatrolMaxSpeed +CasPatrol.EngageMinSpeed=EngageMinSpeed +CasPatrol.EngageMaxSpeed=EngageMaxSpeed +CasPatrol.PatrolAltType=PatrolAltType +CasPatrol.EngageAltType=EngageAltType +CasPatrol.Patrol=true +self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"CAS") +self:T({CAS={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +end +function AI_A2G_DISPATCHER:SetSquadronCasPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) +self:SetSquadronCasPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType) +end +function AI_A2G_DISPATCHER:SetSquadronBai2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.BAI=DefenderSquadron.BAI or{} +local Bai=DefenderSquadron.BAI +Bai.Name=SquadronName +Bai.EngageMinSpeed=EngageMinSpeed +Bai.EngageMaxSpeed=EngageMaxSpeed +Bai.EngageFloorAltitude=EngageFloorAltitude or 500 +Bai.EngageCeilingAltitude=EngageCeilingAltitude or 1000 +Bai.EngageAltType=EngageAltType +Bai.Defend=true +self:T({BAI={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +return self +end +function AI_A2G_DISPATCHER:SetSquadronBai(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude) +return self:SetSquadronBai2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO") +end +function AI_A2G_DISPATCHER:SetSquadronBaiEngageLimit(SquadronName,EngageLimit) +self:SetSquadronEngageLimit(SquadronName,EngageLimit,"BAI") +end +function AI_A2G_DISPATCHER:SetSquadronBaiPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.BAI=DefenderSquadron.BAI or{} +local BaiPatrol=DefenderSquadron.BAI +BaiPatrol.Name=SquadronName +BaiPatrol.Zone=Zone +BaiPatrol.PatrolFloorAltitude=PatrolFloorAltitude +BaiPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude +BaiPatrol.EngageFloorAltitude=EngageFloorAltitude +BaiPatrol.EngageCeilingAltitude=EngageCeilingAltitude +BaiPatrol.PatrolMinSpeed=PatrolMinSpeed +BaiPatrol.PatrolMaxSpeed=PatrolMaxSpeed +BaiPatrol.EngageMinSpeed=EngageMinSpeed +BaiPatrol.EngageMaxSpeed=EngageMaxSpeed +BaiPatrol.PatrolAltType=PatrolAltType +BaiPatrol.EngageAltType=EngageAltType +BaiPatrol.Patrol=true +self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"BAI") +self:T({BAI={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +end +function AI_A2G_DISPATCHER:SetSquadronBaiPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) +self:SetSquadronBaiPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType) +end +function AI_A2G_DISPATCHER:SetDefaultOverhead(Overhead) +self.DefenderDefault.Overhead=Overhead +return self +end +function AI_A2G_DISPATCHER:SetSquadronOverhead(SquadronName,Overhead) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Overhead=Overhead +return self +end +function AI_A2G_DISPATCHER:GetSquadronOverhead(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Overhead or self.DefenderDefault.Overhead +end +function AI_A2G_DISPATCHER:SetDefaultGrouping(Grouping) +self.DefenderDefault.Grouping=Grouping +return self +end +function AI_A2G_DISPATCHER:SetSquadronGrouping(SquadronName,Grouping) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Grouping=Grouping +return self +end +function AI_A2G_DISPATCHER:SetSquadronEngageProbability(SquadronName,EngageProbability) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.EngageProbability=EngageProbability +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoff(Takeoff) +self.DefenderDefault.Takeoff=Takeoff +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoff(SquadronName,Takeoff) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Takeoff=Takeoff +return self +end +function AI_A2G_DISPATCHER:GetDefaultTakeoff() +return self.DefenderDefault.Takeoff +end +function AI_A2G_DISPATCHER:GetSquadronTakeoff(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffInAir() +self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Air) +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffInAir(SquadronName,TakeoffAltitude) +self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Air) +if TakeoffAltitude then +self:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) +end +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffFromRunway() +self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Runway) +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffFromRunway(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Runway) +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingHot() +self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Hot) +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingHot(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Hot) +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingCold() +self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Cold) +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingCold(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Cold) +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude) +self.DefenderDefault.TakeoffAltitude=TakeoffAltitude +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.TakeoffAltitude=TakeoffAltitude +return self +end +function AI_A2G_DISPATCHER:SetDefaultLanding(Landing) +self.DefenderDefault.Landing=Landing +return self +end +function AI_A2G_DISPATCHER:SetSquadronLanding(SquadronName,Landing) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Landing=Landing +return self +end +function AI_A2G_DISPATCHER:GetDefaultLanding() +return self.DefenderDefault.Landing +end +function AI_A2G_DISPATCHER:GetSquadronLanding(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Landing or self.DefenderDefault.Landing +end +function AI_A2G_DISPATCHER:SetDefaultLandingNearAirbase() +self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.NearAirbase) +return self +end +function AI_A2G_DISPATCHER:SetSquadronLandingNearAirbase(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.NearAirbase) +return self +end +function AI_A2G_DISPATCHER:SetDefaultLandingAtRunway() +self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.AtRunway) +return self +end +function AI_A2G_DISPATCHER:SetSquadronLandingAtRunway(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.AtRunway) +return self +end +function AI_A2G_DISPATCHER:SetDefaultLandingAtEngineShutdown() +self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.AtEngineShutdown) +return self +end +function AI_A2G_DISPATCHER:SetSquadronLandingAtEngineShutdown(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.AtEngineShutdown) +return self +end +function AI_A2G_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold) +self.DefenderDefault.FuelThreshold=FuelThreshold +return self +end +function AI_A2G_DISPATCHER:SetSquadronFuelThreshold(SquadronName,FuelThreshold) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.FuelThreshold=FuelThreshold +return self +end +function AI_A2G_DISPATCHER:SetDefaultTanker(TankerName) +self.DefenderDefault.TankerName=TankerName +return self +end +function AI_A2G_DISPATCHER:SetSquadronTanker(SquadronName,TankerName) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.TankerName=TankerName +return self +end +function AI_A2G_DISPATCHER:SetSquadronRadioFrequency(SquadronName,RadioFrequency,RadioModulation,RadioPower) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.RadioFrequency=RadioFrequency +DefenderSquadron.RadioModulation=RadioModulation or radio.modulation.AM +DefenderSquadron.RadioPower=RadioPower or 100 +if DefenderSquadron.RadioQueue then +DefenderSquadron.RadioQueue:Stop() +end +DefenderSquadron.RadioQueue=nil +DefenderSquadron.RadioQueue=RADIOSPEECH:New(DefenderSquadron.RadioFrequency,DefenderSquadron.RadioModulation) +DefenderSquadron.RadioQueue.power=DefenderSquadron.RadioPower +DefenderSquadron.RadioQueue:Start(0.5) +DefenderSquadron.RadioQueue:SetLanguage(DefenderSquadron.Language) +end +function AI_A2G_DISPATCHER:AddDefenderToSquadron(Squadron,Defender,Size) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +self.Defenders[DefenderName]=Squadron +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount-Size +end +self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2G_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount+Defender:GetSize() +end +self.Defenders[DefenderName]=nil +self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2G_DISPATCHER:GetSquadronFromDefender(Defender) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +self:F({DefenderName=DefenderName}) +return self.Defenders[DefenderName] +end +function AI_A2G_DISPATCHER:CountPatrolAirborne(SquadronName,DefenseTaskType) +local PatrolCount=0 +local DefenderSquadron=self.DefenderSquadrons[SquadronName] +if DefenderSquadron then +for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do +if DefenderTask.SquadronName==SquadronName then +if DefenderTask.Type==DefenseTaskType then +if AIGroup:IsAlive()then +if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling") +or DefenderTask.Fsm:Is("Started")then +PatrolCount=PatrolCount+1 +end +end +end +end +end +end +return PatrolCount +end +function AI_A2G_DISPATCHER:CountDefendersEngaged(AttackerDetection,AttackerCount) +local DefendersEngaged=0 +local DefendersTotal=0 +local AttackerSet=AttackerDetection.Set +local DefendersMissing=AttackerCount +local DefenderTasks=self:GetDefenderTasks() +for DefenderGroup,DefenderTask in pairs(DefenderTasks)do +local Defender=DefenderGroup +local DefenderTaskTarget=DefenderTask.Target +local DefenderSquadronName=DefenderTask.SquadronName +local DefenderSize=DefenderTask.Size +if DefenderTask.Target then +self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize) +DefendersTotal=DefendersTotal+DefenderSize +if DefenderTaskTarget and DefenderTaskTarget.Index==AttackerDetection.Index then +local SquadronOverhead=self:GetSquadronOverhead(DefenderSquadronName) +self:F({SquadronOverhead=SquadronOverhead}) +if DefenderSize then +DefendersEngaged=DefendersEngaged+DefenderSize +DefendersMissing=DefendersMissing-DefenderSize/SquadronOverhead +self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize) +else +DefendersEngaged=0 +end +end +end +end +for QueueID,QueueItem in pairs(self.DefenseQueue)do +local QueueItem=QueueItem +if QueueItem.AttackerDetection and QueueItem.AttackerDetection.ItemID==AttackerDetection.ItemID then +DefendersMissing=DefendersMissing-QueueItem.DefendersNeeded/QueueItem.DefenderSquadron.Overhead +self:F({QueueItemName=QueueItem.Defense,QueueItem_ItemID=QueueItem.AttackerDetection.ItemID,DetectedItem=AttackerDetection.ItemID,DefendersMissing=DefendersMissing}) +end +end +self:F({DefenderCount=DefendersEngaged}) +return DefendersTotal,DefendersEngaged,DefendersMissing +end +function AI_A2G_DISPATCHER:CountDefenders(AttackerDetection,DefenderCount,DefenderTaskType) +local Friendlies=nil +local AttackerSet=AttackerDetection.Set +local AttackerCount=AttackerSet:Count() +local DefenderFriendlies=self:GetDefenderFriendliesNearBy(AttackerDetection) +for FriendlyDistance,DefenderFriendlyUnit in UTILS.spairs(DefenderFriendlies or{})do +if AttackerCount>DefenderCount then +local FriendlyGroup=DefenderFriendlyUnit:GetGroup() +if FriendlyGroup and FriendlyGroup:IsAlive()then +local DefenderTask=self:GetDefenderTask(FriendlyGroup) +if DefenderTask then +if DefenderTaskType==DefenderTask.Type then +if DefenderTask.Target==nil then +if DefenderTask.Fsm:Is("Returning") +or DefenderTask.Fsm:Is("Patrolling")then +Friendlies=Friendlies or{} +Friendlies[FriendlyGroup]=FriendlyGroup +DefenderCount=DefenderCount+FriendlyGroup:GetSize() +self:F({Friendly=FriendlyGroup:GetName(),FriendlyDistance=FriendlyDistance}) +end +end +end +end +end +else +break +end +end +return Friendlies +end +function AI_A2G_DISPATCHER:ResourceActivate(DefenderSquadron,DefendersNeeded) +local SquadronName=DefenderSquadron.Name +DefendersNeeded=DefendersNeeded or 4 +local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping +DefenderGrouping=(DefenderGroupingDefenderGrouping then +break +end +end +if DefenderPatrolTemplate then +local TakeoffMethod=self:GetSquadronTakeoff(SquadronName) +local SpawnGroup=GROUP:Register(DefenderName) +DefenderPatrolTemplate.lateActivation=nil +DefenderPatrolTemplate.uncontrolled=nil +local Takeoff=self:GetSquadronTakeoff(SquadronName) +DefenderPatrolTemplate.route.points[1].type=GROUPTEMPLATE.Takeoff[Takeoff][1] +DefenderPatrolTemplate.route.points[1].action=GROUPTEMPLATE.Takeoff[Takeoff][2] +local Defender=_DATABASE:Spawn(DefenderPatrolTemplate) +self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) +Defender:Activate() +return Defender,DefenderGrouping +end +else +local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)] +if DefenderGrouping then +Spawn:InitGrouping(DefenderGrouping) +else +Spawn:InitGrouping() +end +local TakeoffMethod=self:GetSquadronTakeoff(SquadronName) +local Defender=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude) +self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) +return Defender,DefenderGrouping +end +return nil,nil +end +function AI_A2G_DISPATCHER:onafterPatrol(From,Event,To,SquadronName,DefenseTaskType) +local DefenderSquadron,Patrol=self:CanPatrol(SquadronName,DefenseTaskType) +if DefenderSquadron then +local DefendersNeeded +local DefendersGrouping=(DefenderSquadron.Grouping or self.DefenderDefault.Grouping) +if DefenderSquadron.ResourceCount==nil then +DefendersNeeded=DefendersGrouping +else +if DefenderSquadron.ResourceCount>=DefendersGrouping then +DefendersNeeded=DefendersGrouping +else +DefendersNeeded=DefenderSquadron.ResourceCount +end +end +if Patrol then +self:ResourceQueue(true,DefenderSquadron,DefendersNeeded,Patrol,DefenseTaskType,nil,SquadronName) +end +end +end +function AI_A2G_DISPATCHER:ResourceQueue(Patrol,DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName) +self:F({DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName}) +local DefenseQueueItem={} +DefenseQueueItem.Patrol=Patrol +DefenseQueueItem.DefenderSquadron=DefenderSquadron +DefenseQueueItem.DefendersNeeded=DefendersNeeded +DefenseQueueItem.Defense=Defense +DefenseQueueItem.DefenseTaskType=DefenseTaskType +DefenseQueueItem.AttackerDetection=AttackerDetection +DefenseQueueItem.SquadronName=SquadronName +table.insert(self.DefenseQueue,DefenseQueueItem) +self:F({QueueItems=#self.DefenseQueue}) +end +function AI_A2G_DISPATCHER:ResourceTakeoff() +for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do +self:F({DefenseQueueID}) +end +for SquadronName,Squadron in pairs(self.DefenderSquadrons)do +if#self.DefenseQueue>0 then +self:F({SquadronName,Squadron.Name,Squadron.TakeoffTime,Squadron.TakeoffInterval,timer.getTime()}) +local DefenseQueueItem=self.DefenseQueue[1] +self:F({DefenderSquadron=DefenseQueueItem.DefenderSquadron}) +if DefenseQueueItem.SquadronName==SquadronName then +if Squadron.TakeoffTime+Squadron.TakeoffInterval0 then +local FirstUnit=AttackSetUnit:GetFirst() +local Coordinate=FirstUnit:GetCoordinate() +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", moving on to ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) +end +end +end +function AI_A2G_Fsm:OnAfterEngage(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"Engage Route",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +local FirstUnit=AttackSetUnit:GetFirst() +if FirstUnit then +local Coordinate=FirstUnit:GetCoordinate() +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) +end +end +end +function AI_A2G_Fsm:onafterRTB(DefenderGroup,From,Event,To) +self:F({"RTB",DefenderGroup:GetName()}) +self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", returning to base.",DefenderGroup) +end +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +function AI_A2G_Fsm:onafterLostControl(DefenderGroup,From,Event,To) +self:F({"LostControl",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", lost control.") +end +if DefenderGroup:IsAboveRunway()then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +end +function AI_A2G_Fsm:onafterHome(DefenderGroup,From,Event,To,Action) +self:F({"Home",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", landing at base.",DefenderGroup) +end +if Action and Action=="Destroy"then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2G_DISPATCHER.Landing.NearAirbase then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +Dispatcher:ResourcePark(Squadron,DefenderGroup) +end +end +end +end +function AI_A2G_DISPATCHER:ResourceEngage(DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName) +self:F({DefenderSquadron=DefenderSquadron}) +self:F({DefendersNeeded=DefendersNeeded}) +self:F({Defense=Defense}) +self:F({DefenseTaskType=DefenseTaskType}) +self:F({AttackerDetection=AttackerDetection}) +self:F({SquadronName=SquadronName}) +local DefenderGroup,DefenderGrouping=self:ResourceActivate(DefenderSquadron,DefendersNeeded) +if DefenderGroup then +local AI_A2G_ENGAGE={SEAD=AI_A2G_SEAD,BAI=AI_A2G_BAI,CAS=AI_A2G_CAS} +local AI_A2G_Fsm=AI_A2G_ENGAGE[DefenseTaskType]:New(DefenderGroup,Defense.EngageMinSpeed,Defense.EngageMaxSpeed,Defense.EngageFloorAltitude,Defense.EngageCeilingAltitude,Defense.EngageAltType) +AI_A2G_Fsm:SetDispatcher(self) +AI_A2G_Fsm:SetHomeAirbase(DefenderSquadron.Airbase) +AI_A2G_Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60) +AI_A2G_Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold) +AI_A2G_Fsm:SetDisengageRadius(self.DisengageRadius) +AI_A2G_Fsm:Start() +self:SetDefenderTask(SquadronName,DefenderGroup,DefenseTaskType,AI_A2G_Fsm,AttackerDetection,DefenderGrouping) +function AI_A2G_Fsm:onafterTakeoff(DefenderGroup,From,Event,To) +self:F({"Defender Birth",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +local DefenderTarget=Dispatcher:GetDefenderTaskTarget(DefenderGroup) +self:F({DefenderTarget=DefenderTarget}) +if DefenderTarget then +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", wheels up.",DefenderGroup) +end +AI_A2G_Fsm:EngageRoute(DefenderTarget.Set) +end +end +function AI_A2G_Fsm:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"Engage Route",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron then +local FirstUnit=AttackSetUnit:GetRandomSurely() +if FirstUnit then +local Coordinate=FirstUnit:GetCoordinate() +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", on route to ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) +end +else +return +end +end +self:GetParent(self).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit) +end +function AI_A2G_Fsm:OnAfterEngage(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"Engage Route",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +local FirstUnit=AttackSetUnit:GetFirst() +if FirstUnit then +local Coordinate=FirstUnit:GetCoordinate() +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) +end +end +end +function AI_A2G_Fsm:onafterRTB(DefenderGroup,From,Event,To) +self:F({"Defender RTB",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", returning to base.",DefenderGroup) +end +self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +function AI_A2G_Fsm:onafterLostControl(DefenderGroup,From,Event,To) +self:F({"Defender LostControl",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,"Squadron "..Squadron.Name..", "..DefenderName.." lost control.") +end +if DefenderGroup:IsAboveRunway()then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +end +function AI_A2G_Fsm:onafterHome(DefenderGroup,From,Event,To,Action) +self:F({"Defender Home",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", landing at base.",DefenderGroup) +end +if Action and Action=="Destroy"then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2G_DISPATCHER.Landing.NearAirbase then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +Dispatcher:ResourcePark(Squadron,DefenderGroup) +end +end +end +end +function AI_A2G_DISPATCHER:onafterEngage(From,Event,To,AttackerDetection,Defenders) +if Defenders then +for DefenderID,Defender in pairs(Defenders or{})do +local Fsm=self:GetDefenderTaskFsm(Defender) +Fsm:Engage(AttackerDetection.Set) +self:SetDefenderTaskTarget(Defender,AttackerDetection) +end +end +end +function AI_A2G_DISPATCHER:HasDefenseLine(DefenseCoordinate,DetectedItem) +local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) +local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate) +local c1=DefenseCoordinate +local c2=AttackCoordinate +local a=c1.z-c2.z +local b=c2.x-c1.x +local c=c1.x*c2.z-c2.x*c1.z +local ok=true +for AttackItemID,CheckAttackItem in pairs(self.Detection:GetDetectedItems())do +if AttackItemID~=DetectedItem.ID then +local CheckAttackCoordinate=self.Detection:GetDetectedItemCoordinate(CheckAttackItem) +local x=CheckAttackCoordinate.x +local y=CheckAttackCoordinate.z +local r=5000 +local IntersectDistance=(math.abs(a*x+b*y+c))/math.sqrt(a*a+b*b) +self:F({IntersectDistance=IntersectDistance,x=x,y=y}) +local IntersectAttackDistance=CheckAttackCoordinate:Get2DDistance(DefenseCoordinate) +self:F({IntersectAttackDistance=IntersectAttackDistance,EvaluateDistance=EvaluateDistance}) +if IntersectDistance0 and not BreakLoop)do +self:F({DefenderSquadrons=self.DefenderSquadrons}) +for SquadronName,DefenderSquadron in UTILS.rpairs(self.DefenderSquadrons or{})do +if DefenderSquadron[DefenseTaskType]then +local AirbaseCoordinate=DefenderSquadron.Airbase:GetCoordinate() +local AttackerCoord=AttackerUnit:GetCoordinate() +local InterceptCoord=DetectedItem.InterceptCoord +self:F({InterceptCoord=InterceptCoord}) +if InterceptCoord then +local InterceptDistance=AirbaseCoordinate:Get2DDistance(InterceptCoord) +local AirbaseDistance=AirbaseCoordinate:Get2DDistance(AttackerCoord) +self:F({InterceptDistance=InterceptDistance,AirbaseDistance=AirbaseDistance,InterceptCoord=InterceptCoord}) +if AirbaseDistance<=self.DefenseRadius then +local HasDefenseLine=self:HasDefenseLine(AirbaseCoordinate,DetectedItem) +if HasDefenseLine==true then +local EngageProbability=(DefenderSquadron.EngageProbability or 1) +local Probability=math.random() +if Probability=DefendersLimit then +DefendersNeeded=0 +BreakLoop=true +else +if DefendersTotal+DefendersNeeded>DefendersLimit then +DefendersNeeded=DefendersLimit-DefendersTotal +end +end +end +if DefenderSquadron.ResourceCount and DefendersNeeded>DefenderSquadron.ResourceCount then +DefendersNeeded=DefenderSquadron.ResourceCount +BreakLoop=true +end +while(DefendersNeeded>0)do +self:ResourceQueue(false,DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,DetectedItem,EngageSquadronName) +DefendersNeeded=DefendersNeeded-DefenderGrouping +DefenderCount=DefenderCount-DefenderGrouping/DefenderOverhead +end +else +BreakLoop=true +break +end +else +break +end +end +end +end +function AI_A2G_DISPATCHER:Evaluate_SEAD(DetectedItem) +self:F({DetectedItem.ItemID}) +local AttackerSet=DetectedItem.Set +local AttackerCount=AttackerSet:HasSEAD() +if(AttackerCount>0)then +local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount) +self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"SEAD") +if DetectedItem.IsDetected==true then +return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups +end +end +return 0,0,0 +end +function AI_A2G_DISPATCHER:Evaluate_CAS(DetectedItem) +self:F({DetectedItem.ItemID}) +local AttackerSet=DetectedItem.Set +local AttackerCount=AttackerSet:Count() +local AttackerRadarCount=AttackerSet:HasSEAD() +local IsFriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +local IsCas=(AttackerRadarCount==0)and(IsFriendliesNearBy==true) +if IsCas==true then +local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount) +self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"CAS") +if DetectedItem.IsDetected==true then +return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups +end +end +return 0,0,0 +end +function AI_A2G_DISPATCHER:Evaluate_BAI(DetectedItem) +self:F({DetectedItem.ItemID}) +local AttackerSet=DetectedItem.Set +local AttackerCount=AttackerSet:Count() +local AttackerRadarCount=AttackerSet:HasSEAD() +local IsFriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +local IsBai=(AttackerRadarCount==0)and(IsFriendliesNearBy==false) +if IsBai==true then +local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount) +self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"BAI") +if DetectedItem.IsDetected==true then +return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups +end +end +return 0,0,0 +end +function AI_A2G_DISPATCHER:Keys(DetectedItem) +self:F({DetectedItem=DetectedItem}) +local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) +local ShortestDistance=999999999 +for DefenseCoordinateName,DefenseCoordinate in pairs(self.DefenseCoordinates)do +local DefenseCoordinate=DefenseCoordinate +local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate) +if EvaluateDistance<=ShortestDistance then +ShortestDistance=EvaluateDistance +end +end +return ShortestDistance +end +function AI_A2G_DISPATCHER:Order(DetectedItem) +local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) +local ShortestDistance=999999999 +for DefenseCoordinateName,DefenseCoordinate in pairs(self.DefenseCoordinates)do +local DefenseCoordinate=DefenseCoordinate +local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate) +if EvaluateDistance<=ShortestDistance then +ShortestDistance=EvaluateDistance +end +end +return ShortestDistance +end +function AI_A2G_DISPATCHER:ShowTacticalDisplay(Detection) +local AreaMsg={} +local TaskMsg={} +local ChangeMsg={} +local TaskReport=REPORT:New() +local DefenseTotal=0 +local Report=REPORT:New("\nTactical Overview") +local DefenderGroupCount=0 +local DefendersTotal=0 +for DetectedItemID,DetectedItem in UTILS.spairs(Detection:GetDetectedItems(),function(t,a,b)return self:Order(t[a])0 then +self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"SEAD") +end +end +do +local DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies=self:Evaluate_CAS(DetectedItem) +if DefendersMissing>0 then +self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"CAS") +end +end +do +local DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies=self:Evaluate_BAI(DetectedItem) +if DefendersMissing>0 then +self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"BAI") +end +end +end +for Defender,DefenderTask in pairs(self:GetDefenderTasks())do +local Defender=Defender +if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then +DefenseTotal=DefenseTotal+1 +end +end +for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do +local DefenseQueueItem=DefenseQueueItem +if DefenseQueueItem.AttackerDetection and DefenseQueueItem.AttackerDetection.Index and DefenseQueueItem.AttackerDetection.Index==DetectedItem.Index then +DefenseTotal=DefenseTotal+1 +end +end +if self.TacticalDisplay then +local ThreatLevel=DetectedItem.Set:CalculateThreatLevelA2G() +Report:Add(string.format(" - %1s%s ( %4s ): ( #%d - %4s ) %s",(DetectedItem.IsDetected==true)and"!"or" ",DetectedItem.ItemID,DetectedItem.Index,DetectedItem.Set:Count(),DetectedItem.Type or" --- ",string.rep("■",ThreatLevel))) +for Defender,DefenderTask in pairs(self:GetDefenderTasks())do +local Defender=Defender +if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then +if Defender:IsAlive()then +DefenderGroupCount=DefenderGroupCount+1 +local Fuel=Defender:GetFuelMin()*100 +local Damage=Defender:GetLife()/Defender:GetLife0()*100 +Report:Add(string.format(" - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", +Defender:GetName(), +DefenderTask.Type, +DefenderTask.Fsm:GetState(), +Defender:GetSize(), +Fuel, +Damage, +Defender:HasTask()==true and"Executing"or"Idle")) +end +end +end +end +end +end +if self.TacticalDisplay then +Report:Add("\n - No Targets:") +local TaskCount=0 +for Defender,DefenderTask in pairs(self:GetDefenderTasks())do +TaskCount=TaskCount+1 +local Defender=Defender +if not DefenderTask.Target then +if Defender:IsAlive()then +local DefenderHasTask=Defender:HasTask() +local Fuel=Defender:GetFuelMin()*100 +local Damage=Defender:GetLife()/Defender:GetLife0()*100 +DefenderGroupCount=DefenderGroupCount+1 +Report:Add(string.format(" - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", +Defender:GetName(), +DefenderTask.Type, +DefenderTask.Fsm:GetState(), +Defender:GetSize(), +Fuel, +Damage, +Defender:HasTask()==true and"Executing"or"Idle")) +end +end +end +Report:Add(string.format("\n - %d Tasks - %d Defender Groups",TaskCount,DefenderGroupCount)) +Report:Add(string.format("\n - %d Queued Aircraft Launches",#self.DefenseQueue)) +for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do +local DefenseQueueItem=DefenseQueueItem +Report:Add(string.format(" - %s - %s",DefenseQueueItem.SquadronName,DefenseQueueItem.DefenderSquadron.TakeoffTime,DefenseQueueItem.DefenderSquadron.TakeoffInterval)) +end +Report:Add(string.format("\n - Squadron Resources: ",#self.DefenseQueue)) +for DefenderSquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do +Report:Add(string.format(" - %s - %s",DefenderSquadronName,DefenderSquadron.ResourceCount and tostring(DefenderSquadron.ResourceCount)or"n/a")) +end +self:F(Report:Text("\n")) +trigger.action.outText(Report:Text("\n"),25) +end +return true +end +end +do +function AI_A2G_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem) +local PlayerTypes={} +local PlayersCount=0 +if PlayersNearBy then +local DetectedThreatLevel=DetectedSet:CalculateThreatLevelA2G() +for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do +local PlayerUnit=PlayerUnitData +local PlayerName=PlayerUnit:GetPlayerName() +if PlayerUnit:IsAirPlane()and PlayerName~=nil then +local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel() +PlayersCount=PlayersCount+1 +local PlayerType=PlayerUnit:GetTypeName() +PlayerTypes[PlayerName]=PlayerType +if DetectedThreatLevel0 then +for PlayerName,PlayerType in pairs(PlayerTypes)do +PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType)) +end +else +PlayerTypesReport:Add("-") +end +return PlayersCount,PlayerTypesReport +end +function AI_A2G_DISPATCHER:GetFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem) +local FriendlyTypes={} +local FriendliesCount=0 +if FriendlyUnitsNearBy then +local DetectedThreatLevel=DetectedSet:CalculateThreatLevelA2G() +for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do +local FriendlyUnit=FriendlyUnitData +if FriendlyUnit:IsAirPlane()then +local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel() +FriendliesCount=FriendliesCount+1 +local FriendlyType=FriendlyUnit:GetTypeName() +FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1 +if DetectedThreatLevel0 then +for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do +FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType)) +end +else +FriendlyTypesReport:Add("-") +end +return FriendliesCount,FriendlyTypesReport +end +function AI_A2G_DISPATCHER:SchedulerPatrol(SquadronName) +local PatrolTaskTypes={"SEAD","CAS","BAI"} +local PatrolTaskType=PatrolTaskTypes[math.random(1,3)] +self:Patrol(SquadronName,PatrolTaskType) +end +function AI_A2G_DISPATCHER:SetSendMessages(onoff) +self.SetSendPlayerMessages=onoff +end +end +function AI_A2G_DISPATCHER:AddToSquadron(Squadron,Amount) +local Squadron=self:GetSquadron(Squadron) +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount+Amount +end +self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2G_DISPATCHER:RemoveFromSquadron(Squadron,Amount) +local Squadron=self:GetSquadron(Squadron) +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount-Amount +end +self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount}) +end +AI_PATROL_ZONE={ +ClassName="AI_PATROL_ZONE", +} +function AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) +self.PatrolZone=PatrolZone +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +self.PatrolAltType=PatrolAltType or"BARO" +self:SetRefreshTimeInterval(30) +self.CheckStatus=true +self:ManageFuel(.2,60) +self:ManageDamage(1) +self.DetectedUnits={} +self:SetStartState("None") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("None","Start","Patrolling") +self:AddTransition("Patrolling","Route","Patrolling") +self:AddTransition("*","Status","*") +self:AddTransition("*","Detect","*") +self:AddTransition("*","Detected","*") +self:AddTransition("*","RTB","Returning") +self:AddTransition("*","Reset","Patrolling") +self:AddTransition("*","Eject","*") +self:AddTransition("*","Crash","Crashed") +self:AddTransition("*","PilotDead","*") +return self +end +function AI_PATROL_ZONE:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed) +self:F2({PatrolMinSpeed,PatrolMaxSpeed}) +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +end +function AI_PATROL_ZONE:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude) +self:F2({PatrolFloorAltitude,PatrolCeilingAltitude}) +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +end +function AI_PATROL_ZONE:SetDetectionOn() +self:F2() +self.DetectOn=true +end +function AI_PATROL_ZONE:SetDetectionOff() +self:F2() +self.DetectOn=false +end +function AI_PATROL_ZONE:SetStatusOff() +self:F2() +self.CheckStatus=false +end +function AI_PATROL_ZONE:SetDetectionActivated() +self:F2() +self:ClearDetectedUnits() +self.DetectActivated=true +self:__Detect(-self.DetectInterval) +end +function AI_PATROL_ZONE:SetDetectionDeactivated() +self:F2() +self:ClearDetectedUnits() +self.DetectActivated=false +end +function AI_PATROL_ZONE:SetRefreshTimeInterval(Seconds) +self:F2() +if Seconds then +self.DetectInterval=Seconds +else +self.DetectInterval=30 +end +end +function AI_PATROL_ZONE:SetDetectionZone(DetectionZone) +self:F2() +if DetectionZone then +self.DetectZone=DetectionZone +else +self.DetectZone=nil +end +end +function AI_PATROL_ZONE:GetDetectedUnits() +self:F2() +return self.DetectedUnits +end +function AI_PATROL_ZONE:ClearDetectedUnits() +self:F2() +self.DetectedUnits={} +end +function AI_PATROL_ZONE:ManageFuel(PatrolFuelThresholdPercentage,PatrolOutOfFuelOrbitTime) +self.PatrolFuelThresholdPercentage=PatrolFuelThresholdPercentage +self.PatrolOutOfFuelOrbitTime=PatrolOutOfFuelOrbitTime +return self +end +function AI_PATROL_ZONE:ManageDamage(PatrolDamageThreshold) +self.PatrolManageDamage=true +self.PatrolDamageThreshold=PatrolDamageThreshold +return self +end +function AI_PATROL_ZONE:onafterStart(Controllable,From,Event,To) +self:F2() +self:__Route(1) +self:__Status(60) +self:SetDetectionActivated() +self:HandleEvent(EVENTS.PilotDead,self.OnPilotDead) +self:HandleEvent(EVENTS.Crash,self.OnCrash) +self:HandleEvent(EVENTS.Ejection,self.OnEjection) +Controllable:OptionROEHoldFire() +Controllable:OptionROTVertical() +self.Controllable:OnReSpawn( +function(PatrolGroup) +self:T("ReSpawn") +self:__Reset(1) +self:__Route(5) +end +) +self:SetDetectionOn() +end +function AI_PATROL_ZONE:onbeforeDetect(Controllable,From,Event,To) +return self.DetectOn and self.DetectActivated +end +function AI_PATROL_ZONE:onafterDetect(Controllable,From,Event,To) +local Detected=false +local DetectedTargets=Controllable:GetDetectedTargets() +for TargetID,Target in pairs(DetectedTargets or{})do +local TargetObject=Target.object +if TargetObject and TargetObject:isExist()and TargetObject.id_<50000000 then +local TargetUnit=UNIT:Find(TargetObject) +if TargetUnit and TargetUnit:IsAlive()then +local TargetUnitName=TargetUnit:GetName() +if self.DetectionZone then +if TargetUnit:IsInZone(self.DetectionZone)then +self:T({"Detected ",TargetUnit}) +if self.DetectedUnits[TargetUnit]==nil then +self.DetectedUnits[TargetUnit]=true +end +Detected=true +end +else +if self.DetectedUnits[TargetUnit]==nil then +self.DetectedUnits[TargetUnit]=true +end +Detected=true +end +end +end +end +self:__Detect(-self.DetectInterval) +if Detected==true then +self:__Detected(1.5) +end +end +function AI_PATROL_ZONE:_NewPatrolRoute(AIControllable) +local PatrolZone=AIControllable:GetState(AIControllable,"PatrolZone") +PatrolZone:__Route(1) +end +function AI_PATROL_ZONE:onafterRoute(Controllable,From,Event,To) +self:F2() +if From=="RTB"then +return +end +local life=self.Controllable:GetLife()or 0 +if self.Controllable:IsAlive()and life>1 then +local PatrolRoute={} +if self.Controllable:InAir()==false then +self:T("Not in the air, finding route path within PatrolZone") +local CurrentVec2=self.Controllable:GetVec2() +if not CurrentVec2 then return end +local CurrentAltitude=self.Controllable:GetAltitude() +local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToPatrolZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TakeOffParking, +COORDINATE.WaypointAction.FromParkingArea, +ToPatrolZoneSpeed, +true +) +PatrolRoute[#PatrolRoute+1]=CurrentRoutePoint +else +self:T("In the air, finding route path within PatrolZone") +local CurrentVec2=self.Controllable:GetVec2() +if not CurrentVec2 then return end +local CurrentAltitude=self.Controllable:GetAltitude() +local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToPatrolZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +ToPatrolZoneSpeed, +true +) +PatrolRoute[#PatrolRoute+1]=CurrentRoutePoint +end +local ToTargetVec2=self.PatrolZone:GetRandomVec2() +self:T2(ToTargetVec2) +local ToTargetAltitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude) +local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed}) +local ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y) +local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +ToTargetSpeed, +true +) +PatrolRoute[#PatrolRoute+1]=ToTargetRoutePoint +self.Controllable:WayPointInitialize(PatrolRoute) +self.Controllable:SetState(self.Controllable,"PatrolZone",self) +self.Controllable:WayPointFunction(#PatrolRoute,1,"AI_PATROL_ZONE:_NewPatrolRoute") +self.Controllable:WayPointExecute(1,2) +end +end +function AI_PATROL_ZONE:onbeforeStatus() +return self.CheckStatus +end +function AI_PATROL_ZONE:onafterStatus() +self:F2() +if self.Controllable and self.Controllable:IsAlive()then +local RTB=false +local Fuel=self.Controllable:GetFuelMin() +if Fuel Engaging') +self:__Engage(1) +end +end +end +function AI_CAP_ZONE:onafterAbort(Controllable,From,Event,To) +Controllable:ClearTasks() +self:__Route(1) +end +function AI_CAP_ZONE:onafterEngage(Controllable,From,Event,To) +if Controllable and Controllable:IsAlive()then +local EngageRoute={} +local CurrentVec2=self.Controllable:GetVec2() +if not CurrentVec2 then return self end +local CurrentAltitude=self.Controllable:GetAltitude() +local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToEngageZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +ToEngageZoneSpeed, +true +) +EngageRoute[#EngageRoute+1]=CurrentRoutePoint +local ToTargetVec2=self.PatrolZone:GetRandomVec2() +self:T2(ToTargetVec2) +local ToTargetAltitude=math.random(self.EngageFloorAltitude,self.EngageCeilingAltitude) +local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed}) +local ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y) +local ToPatrolRoutePoint=ToTargetPointVec3:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +ToTargetSpeed, +true +) +EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint +Controllable:OptionROEOpenFire() +Controllable:OptionROTEvadeFire() +local AttackTasks={} +for DetectedUnit,Detected in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnit +self:T({DetectedUnit,DetectedUnit:IsAlive(),DetectedUnit:IsAir()}) +if DetectedUnit:IsAlive()and DetectedUnit:IsAir()then +if self.EngageZone then +if DetectedUnit:IsInZone(self.EngageZone)then +self:F({"Within Zone and Engaging ",DetectedUnit}) +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) +end +else +if self.EngageRange then +if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3())<=self.EngageRange then +self:F({"Within Range and Engaging",DetectedUnit}) +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) +end +else +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) +end +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +if#AttackTasks==0 then +self:F("No targets found -> Going back to Patrolling") +self:__Abort(1) +self:__Route(1) +self:SetDetectionActivated() +else +AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAP_ZONE.EngageRoute",self) +EngageRoute[1].task=Controllable:TaskCombo(AttackTasks) +self:SetDetectionDeactivated() +end +Controllable:Route(EngageRoute,0.5) +end +end +function AI_CAP_ZONE:onafterAccomplish(Controllable,From,Event,To) +self.Accomplished=true +self:SetDetectionOff() +end +function AI_CAP_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) +if EventData.IniUnit then +self.DetectedUnits[EventData.IniUnit]=nil +end +end +function AI_CAP_ZONE:OnEventDead(EventData) +self:F({"EventDead",EventData}) +if EventData.IniDCSUnit then +if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then +self:__Destroy(1,EventData) +end +end +end +AI_CAS_ZONE={ +ClassName="AI_CAS_ZONE", +} +function AI_CAS_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType) +local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)) +self.EngageZone=EngageZone +self.Accomplished=false +self:SetDetectionZone(self.EngageZone) +self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging") +self:AddTransition("Engaging","Target","Engaging") +self:AddTransition("Engaging","Fired","Engaging") +self:AddTransition("*","Destroy","*") +self:AddTransition("Engaging","Abort","Patrolling") +self:AddTransition("Engaging","Accomplish","Patrolling") +return self +end +function AI_CAS_ZONE:SetEngageZone(EngageZone) +self:F2() +if EngageZone then +self.EngageZone=EngageZone +else +self.EngageZone=nil +end +end +function AI_CAS_ZONE:onafterStart(Controllable,From,Event,To) +self:GetParent(self).onafterStart(self,Controllable,From,Event,To) +self:HandleEvent(EVENTS.Dead) +self:SetDetectionDeactivated() +end +function AI_CAS_ZONE.EngageRoute(EngageGroup,Fsm) +EngageGroup:F({"AI_CAS_ZONE.EngageRoute:",EngageGroup:GetName()}) +if EngageGroup:IsAlive()then +Fsm:__Engage(1,Fsm.EngageSpeed,Fsm.EngageAltitude,Fsm.EngageWeaponExpend,Fsm.EngageAttackQty,Fsm.EngageDirection) +end +end +function AI_CAS_ZONE:onbeforeEngage(Controllable,From,Event,To) +if self.Accomplished==true then +return false +end +end +function AI_CAS_ZONE:onafterTarget(Controllable,From,Event,To) +if Controllable:IsAlive()then +local AttackTasks={} +for DetectedUnit,Detected in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnit +if DetectedUnit:IsAlive()then +if DetectedUnit:IsInZone(self.EngageZone)then +if Detected==true then +self:F({"Target: ",DetectedUnit}) +self.DetectedUnits[DetectedUnit]=false +local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil) +self.Controllable:PushTask(AttackTask,1) +end +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +self:__Target(-10) +end +end +function AI_CAS_ZONE:onafterAbort(Controllable,From,Event,To) +Controllable:ClearTasks() +self:__Route(1) +end +function AI_CAS_ZONE:onafterEngage(Controllable,From,Event,To, +EngageSpeed, +EngageAltitude, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection) +self:F("onafterEngage") +self.EngageSpeed=EngageSpeed or 400 +self.EngageAltitude=EngageAltitude or 2000 +self.EngageWeaponExpend=EngageWeaponExpend +self.EngageAttackQty=EngageAttackQty +self.EngageDirection=EngageDirection +if Controllable:IsAlive()then +Controllable:OptionROEOpenFire() +Controllable:OptionROTVertical() +local EngageRoute={} +local CurrentVec2=self.Controllable:GetVec2() +local CurrentAltitude=self.Controllable:GetAltitude() +local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToEngageZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +self.EngageSpeed, +true +) +EngageRoute[#EngageRoute+1]=CurrentRoutePoint +local AttackTasks={} +for DetectedUnit,Detected in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnit +self:T(DetectedUnit) +if DetectedUnit:IsAlive()then +if DetectedUnit:IsInZone(self.EngageZone)then +self:F({"Engaging ",DetectedUnit}) +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit, +true, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection +) +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAS_ZONE.EngageRoute",self) +EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks) +local ToTargetVec2=self.EngageZone:GetRandomVec2() +self:T2(ToTargetVec2) +local ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y) +local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +self.EngageSpeed, +true +) +EngageRoute[#EngageRoute+1]=ToTargetRoutePoint +Controllable:Route(EngageRoute,0.5) +self:SetRefreshTimeInterval(2) +self:SetDetectionActivated() +self:__Target(-2) +end +end +function AI_CAS_ZONE:onafterAccomplish(Controllable,From,Event,To) +self.Accomplished=true +self:SetDetectionDeactivated() +end +function AI_CAS_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) +if EventData.IniUnit then +self.DetectedUnits[EventData.IniUnit]=nil +end +end +function AI_CAS_ZONE:OnEventDead(EventData) +self:F({"EventDead",EventData}) +if EventData.IniDCSUnit then +if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then +self:__Destroy(1,EventData) +end +end +end +AI_BAI_ZONE={ +ClassName="AI_BAI_ZONE", +} +function AI_BAI_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType) +local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)) +self.EngageZone=EngageZone +self.Accomplished=false +self:SetDetectionZone(self.EngageZone) +self:SearchOn() +self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging") +self:AddTransition("Engaging","Target","Engaging") +self:AddTransition("Engaging","Fired","Engaging") +self:AddTransition("*","Destroy","*") +self:AddTransition("Engaging","Abort","Patrolling") +self:AddTransition("Engaging","Accomplish","Patrolling") +return self +end +function AI_BAI_ZONE:SetEngageZone(EngageZone) +self:F2() +if EngageZone then +self.EngageZone=EngageZone +else +self.EngageZone=nil +end +end +function AI_BAI_ZONE:SearchOnOff(Search) +self.Search=Search +return self +end +function AI_BAI_ZONE:SearchOff() +self:SearchOnOff(false) +return self +end +function AI_BAI_ZONE:SearchOn() +self:SearchOnOff(true) +return self +end +function AI_BAI_ZONE:onafterStart(Controllable,From,Event,To) +self:GetParent(self).onafterStart(self,Controllable,From,Event,To) +self:HandleEvent(EVENTS.Dead) +self:SetDetectionDeactivated() +end +function _NewEngageRoute(AIControllable) +AIControllable:T("NewEngageRoute") +local EngageZone=AIControllable:GetState(AIControllable,"EngageZone") +EngageZone:__Engage(1,EngageZone.EngageSpeed,EngageZone.EngageAltitude,EngageZone.EngageWeaponExpend,EngageZone.EngageAttackQty,EngageZone.EngageDirection) +end +function AI_BAI_ZONE:onbeforeEngage(Controllable,From,Event,To) +if self.Accomplished==true then +return false +end +end +function AI_BAI_ZONE:onafterTarget(Controllable,From,Event,To) +self:F({"onafterTarget",self.Search,Controllable:IsAlive()}) +if Controllable:IsAlive()then +local AttackTasks={} +if self.Search==true then +for DetectedUnit,Detected in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnit +if DetectedUnit:IsAlive()then +if DetectedUnit:IsInZone(self.EngageZone)then +if Detected==true then +self:F({"Target: ",DetectedUnit}) +self.DetectedUnits[DetectedUnit]=false +local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil) +self.Controllable:PushTask(AttackTask,1) +end +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +else +self:F("Attack zone") +local AttackTask=Controllable:TaskAttackMapObject( +self.EngageZone:GetPointVec2():GetVec2(), +true, +self.EngageWeaponExpend, +self.EngageAttackQty, +self.EngageDirection, +self.EngageAltitude +) +self.Controllable:PushTask(AttackTask,1) +end +self:__Target(-10) +end +end +function AI_BAI_ZONE:onafterAbort(Controllable,From,Event,To) +Controllable:ClearTasks() +self:__Route(1) +end +function AI_BAI_ZONE:onafterEngage(Controllable,From,Event,To, +EngageSpeed, +EngageAltitude, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection) +self:F("onafterEngage") +self.EngageSpeed=EngageSpeed or 400 +self.EngageAltitude=EngageAltitude or 2000 +self.EngageWeaponExpend=EngageWeaponExpend +self.EngageAttackQty=EngageAttackQty +self.EngageDirection=EngageDirection +if Controllable:IsAlive()then +local EngageRoute={} +local CurrentVec2=self.Controllable:GetVec2() +local CurrentAltitude=self.Controllable:GetAltitude() +local CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToEngageZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +self.EngageSpeed, +true +) +EngageRoute[#EngageRoute+1]=CurrentRoutePoint +local AttackTasks={} +if self.Search==true then +for DetectedUnitID,DetectedUnitData in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnitData +self:T(DetectedUnit) +if DetectedUnit:IsAlive()then +if DetectedUnit:IsInZone(self.EngageZone)then +self:F({"Engaging ",DetectedUnit}) +AttackTasks[#AttackTasks+1]=Controllable:TaskBombing( +DetectedUnit:GetPointVec2():GetVec2(), +true, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection, +EngageAltitude +) +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +else +self:F("Attack zone") +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackMapObject( +self.EngageZone:GetPointVec2():GetVec2(), +true, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection, +EngageAltitude +) +end +EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks) +local ToTargetVec2=self.EngageZone:GetRandomVec2() +self:T2(ToTargetVec2) +local ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y) +local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( +self.PatrolAltType, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +self.EngageSpeed, +true +) +EngageRoute[#EngageRoute+1]=ToTargetRoutePoint +Controllable:OptionROEOpenFire() +Controllable:OptionROTVertical() +Controllable:WayPointInitialize(EngageRoute) +Controllable:SetState(Controllable,"EngageZone",self) +Controllable:WayPointFunction(#EngageRoute,1,"_NewEngageRoute") +Controllable:WayPointExecute(1) +self:SetRefreshTimeInterval(2) +self:SetDetectionActivated() +self:__Target(-2) +end +end +function AI_BAI_ZONE:onafterAccomplish(Controllable,From,Event,To) +self.Accomplished=true +self:SetDetectionDeactivated() +end +function AI_BAI_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) +if EventData.IniUnit then +self.DetectedUnits[EventData.IniUnit]=nil +end +end +function AI_BAI_ZONE:OnEventDead(EventData) +self:F({"EventDead",EventData}) +if EventData.IniDCSUnit then +if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then +self:__Destroy(1,EventData) +end +end +end +AI_FORMATION={ +ClassName="AI_FORMATION", +FollowName=nil, +FollowUnit=nil, +FollowGroupSet=nil, +FollowMode=1, +MODE={ +FOLLOW=1, +MISSION=2, +}, +FollowScheduler=nil, +OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE, +OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, +dtFollow=0.5, +} +AI_FORMATION.__Enum={} +AI_FORMATION.__Enum.Formation={ +None=0, +Mission=1, +Line=2, +Trail=3, +Stack=4, +LeftLine=5, +RightLine=6, +LeftWing=7, +RightWing=8, +Vic=9, +Box=10, +} +AI_FORMATION.__Enum.Mode={ +Mission="M", +Formation="F", +Attack="A", +Reconnaissance="R", +} +AI_FORMATION.__Enum.ReportType={ +All="*", +Airborne="A", +GroundRadar="R", +Ground="G", +} +function AI_FORMATION:New(FollowUnit,FollowGroupSet,FollowName,FollowBriefing) +local self=BASE:Inherit(self,FSM_SET:New(FollowGroupSet)) +self:F({FollowUnit,FollowGroupSet,FollowName}) +self.FollowUnit=FollowUnit +self.FollowGroupSet=FollowGroupSet +self.FollowGroupSet:ForEachGroup( +function(FollowGroup) +FollowGroup:SetState(self,"Mode",self.__Enum.Mode.Formation) +end +) +self:SetFlightModeFormation() +self:SetFlightRandomization(2) +self:SetStartState("None") +self:AddTransition("*","Stop","Stopped") +self:AddTransition({"None","Stopped"},"Start","Following") +self:AddTransition("*","FormationLine","*") +self:AddTransition("*","FormationTrail","*") +self:AddTransition("*","FormationStack","*") +self:AddTransition("*","FormationLeftLine","*") +self:AddTransition("*","FormationRightLine","*") +self:AddTransition("*","FormationLeftWing","*") +self:AddTransition("*","FormationRightWing","*") +self:AddTransition("*","FormationCenterWing","*") +self:AddTransition("*","FormationVic","*") +self:AddTransition("*","FormationBox","*") +self:AddTransition("*","Follow","Following") +self:FormationLeftLine(500,0,250,250) +self.FollowName=FollowName +self.FollowBriefing=FollowBriefing +self.CT1=0 +self.GT1=0 +self.FollowMode=AI_FORMATION.MODE.MISSION +return self +end +function AI_FORMATION:SetFollowTimeInterval(dt) +self.dtFollow=dt or 0.5 +return self +end +function AI_FORMATION:TestSmokeDirectionVector(SmokeDirection) +self.SmokeDirectionVector=(SmokeDirection==true)and true or false +return self +end +function AI_FORMATION:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation) +self:F({FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation}) +XStart=XStart or self.XStart +XSpace=XSpace or self.XSpace +YStart=YStart or self.YStart +YSpace=YSpace or self.YSpace +ZStart=ZStart or self.ZStart +ZSpace=ZSpace or self.ZSpace +FollowGroupSet:Flush(self) +local FollowSet=FollowGroupSet:GetSet() +local i=1 +for FollowID,FollowGroup in pairs(FollowSet)do +local PointVec3=COORDINATE:New() +PointVec3:SetX(XStart+i*XSpace) +PointVec3:SetY(YStart+i*YSpace) +PointVec3:SetZ(ZStart+i*ZSpace) +local Vec3=PointVec3:GetVec3() +FollowGroup:SetState(self,"FormationVec3",Vec3) +i=i+1 +FollowGroup:SetState(FollowGroup,"Formation",Formation) +end +return self +end +function AI_FORMATION:onafterFormationTrail(FollowGroupSet,From,Event,To,XStart,XSpace,YStart) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0,self.__Enum.Formation.Trail) +return self +end +function AI_FORMATION:onafterFormationStack(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0,self.__Enum.Formation.Stack) +return self +end +function AI_FORMATION:onafterFormationLeftLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftLine) +return self +end +function AI_FORMATION:onafterFormationRightLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightLine) +return self +end +function AI_FORMATION:onafterFormationLeftWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftWing) +return self +end +function AI_FORMATION:onafterFormationRightWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightWing) +return self +end +function AI_FORMATION:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +local FollowSet=FollowGroupSet:GetSet() +local i=0 +for FollowID,FollowGroup in pairs(FollowSet)do +local PointVec3=COORDINATE:New() +local Side=(i%2==0)and 1 or-1 +local Row=i/2+1 +PointVec3:SetX(XStart+Row*XSpace) +PointVec3:SetY(YStart) +PointVec3:SetZ(Side*(ZStart+i*ZSpace)) +local Vec3=PointVec3:GetVec3() +FollowGroup:SetState(self,"FormationVec3",Vec3) +i=i+1 +FollowGroup:SetState(FollowGroup,"Formation",self.__Enum.Formation.Vic) +end +return self +end +function AI_FORMATION:onafterFormationVic(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +return self +end +function AI_FORMATION:onafterFormationBox(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +local FollowSet=FollowGroupSet:GetSet() +local i=0 +for FollowID,FollowGroup in pairs(FollowSet)do +local PointVec3=COORDINATE:New() +local ZIndex=i%ZLevels +local XIndex=math.floor(i/ZLevels) +local YIndex=math.floor(i/ZLevels) +PointVec3:SetX(XStart+XIndex*XSpace) +PointVec3:SetY(YStart+YIndex*YSpace) +PointVec3:SetZ(-ZStart-(ZSpace*ZLevels/2)+ZSpace*ZIndex) +local Vec3=PointVec3:GetVec3() +FollowGroup:SetState(self,"FormationVec3",Vec3) +i=i+1 +FollowGroup:SetState(FollowGroup,"Formation",self.__Enum.Formation.Box) +end +return self +end +function AI_FORMATION:SetFlightRandomization(FlightRandomization) +self.FlightRandomization=FlightRandomization +return self +end +function AI_FORMATION:GetFlightMode(FollowGroup) +if FollowGroup then +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission) +end +return FollowGroup:GetState(FollowGroup,"Mode") +end +function AI_FORMATION:SetFlightModeMission(FollowGroup) +if FollowGroup then +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission) +else +self.FollowGroupSet:ForSomeGroupAlive( +function(FollowGroup) +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission) +end +) +end +return self +end +function AI_FORMATION:SetFlightModeAttack(FollowGroup) +if FollowGroup then +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Attack) +else +self.FollowGroupSet:ForSomeGroupAlive( +function(FollowGroup) +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Attack) +end +) +end +return self +end +function AI_FORMATION:SetFlightModeFormation(FollowGroup) +if FollowGroup then +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Formation) +else +self.FollowGroupSet:ForSomeGroupAlive( +function(FollowGroup) +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Formation) +end +) +end +return self +end +function AI_FORMATION:onafterStop(FollowGroupSet,From,Event,To) +self:E("Stopping formation.") +end +function AI_FORMATION:onbeforeFollow(FollowGroupSet,From,Event,To) +if From=="Stopped"then +return false +end +return true +end +function AI_FORMATION:onenterFollowing(FollowGroupSet) +if self.FollowUnit:IsAlive()then +local ClientUnit=self.FollowUnit +local CT1,CT2,CV1,CV2 +CT1=ClientUnit:GetState(self,"CT1") +local CuVec3=ClientUnit:GetVec3() +if CT1==nil or CT1==0 then +ClientUnit:SetState(self,"CV1",CuVec3) +ClientUnit:SetState(self,"CT1",timer.getTime()) +else +CT1=ClientUnit:GetState(self,"CT1") +CT2=timer.getTime() +CV1=ClientUnit:GetState(self,"CV1") +CV2=CuVec3 +ClientUnit:SetState(self,"CT1",CT2) +ClientUnit:SetState(self,"CV1",CV2) +end +for _,_group in pairs(FollowGroupSet:GetSet())do +local group=_group +if group and group:IsAlive()then +self:FollowMe(group,ClientUnit,CT1,CV1,CT2,CV2) +end +end +self:__Follow(-self.dtFollow) +end +end +function AI_FORMATION:FollowMe(FollowGroup,ClientUnit,CT1,CV1,CT2,CV2) +if FollowGroup:GetState(FollowGroup,"Mode")==self.__Enum.Mode.Formation and not self:Is("Stopped")then +self:T({Mode=FollowGroup:GetState(FollowGroup,"Mode")}) +FollowGroup:OptionROTEvadeFire() +FollowGroup:OptionROEReturnFire() +local GroupUnit=FollowGroup:GetUnit(1) +local GuVec3=GroupUnit:GetVec3() +local FollowFormation=FollowGroup:GetState(self,"FormationVec3") +if FollowFormation then +local FollowDistance=FollowFormation.x +local GT1=GroupUnit:GetState(self,"GT1") +if CT1==nil or CT1==0 or GT1==nil or GT1==0 then +GroupUnit:SetState(self,"GV1",GuVec3) +GroupUnit:SetState(self,"GT1",timer.getTime()) +else +local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5 +local CT=CT2-CT1 +local CS=(3600/CT)*(CD/1000)/3.6 +local CDv={x=CV2.x-CV1.x,y=CV2.y-CV1.y,z=CV2.z-CV1.z} +local Ca=math.atan2(CDv.x,CDv.z) +local GT1=GroupUnit:GetState(self,"GT1") +local GT2=timer.getTime() +local GV1=GroupUnit:GetState(self,"GV1") +local GV2=GuVec3 +GV2.x=GV2.x+math.random(-self.FlightRandomization/2,self.FlightRandomization/2) +GV2.y=GV2.y+math.random(-self.FlightRandomization/2,self.FlightRandomization/2) +GV2.z=GV2.z+math.random(-self.FlightRandomization/2,self.FlightRandomization/2) +GroupUnit:SetState(self,"GT1",GT2) +GroupUnit:SetState(self,"GV1",GV2) +local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5 +local GT=GT2-GT1 +local GDv={x=GV2.x-CV1.x,y=GV2.y-CV1.y,z=GV2.z-CV1.z} +local Alpha_T=math.atan2(GDv.x,GDv.z)-math.atan2(CDv.x,CDv.z) +local Alpha_R=(Alpha_T<0)and Alpha_T+2*math.pi or Alpha_T +local Position=math.cos(Alpha_R) +local GD=((GDv.x)^2+(GDv.z)^2)^0.5 +local Distance=GD*Position+-CS*0.5 +local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z} +local GH2={x=GV2.x,y=CV2.y+FollowFormation.y,z=GV2.z} +local alpha=math.atan2(GV.x,GV.z) +local GVx=FollowFormation.z*math.cos(Ca)+FollowFormation.x*math.sin(Ca) +local GVz=FollowFormation.x*math.cos(Ca)-FollowFormation.z*math.sin(Ca) +local Inclination=(Distance+FollowFormation.x)/10 +if Inclination<-30 then +Inclination=-30 +end +local CVI={ +x=CV2.x+CS*10*math.sin(Ca), +y=GH2.y+Inclination, +z=CV2.z+CS*10*math.cos(Ca), +} +local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z} +local DVu={x=DV.x/FollowDistance,y=DV.y,z=DV.z/FollowDistance} +local GDV={x=CVI.x,y=CVI.y,z=CVI.z} +local ADDx=FollowFormation.x*math.cos(alpha)-FollowFormation.z*math.sin(alpha) +local ADDz=FollowFormation.z*math.cos(alpha)+FollowFormation.x*math.sin(alpha) +local GDV_Formation={ +x=GDV.x-GVx, +y=GDV.y, +z=GDV.z-GVz +} +if self.SmokeDirectionVector==true then +trigger.action.smoke(GDV,trigger.smokeColor.Green) +trigger.action.smoke(GDV_Formation,trigger.smokeColor.White) +end +local Time=120 +local Speed=-(Distance+FollowFormation.x)/Time +if Distance>-10000 then +Speed=-(Distance+FollowFormation.x)/60 +end +if Distance>-2500 then +Speed=-(Distance+FollowFormation.x)/20 +end +local GS=Speed+CS +FollowGroup:RouteToVec3(GDV_Formation,GS) +end +end +end +end +AI_ESCORT={ +ClassName="AI_ESCORT", +EscortName=nil, +EscortUnit=nil, +EscortGroup=nil, +EscortMode=1, +Targets={}, +FollowScheduler=nil, +ReportTargets=true, +OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE, +OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, +SmokeDirectionVector=false, +TaskPoints={} +} +AI_ESCORT.Detection=nil +function AI_ESCORT:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing) +local self=BASE:Inherit(self,AI_FORMATION:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing)) +self:F({EscortUnit,EscortGroupSet}) +self.PlayerUnit=self.FollowUnit +self.PlayerGroup=self.FollowUnit:GetGroup() +self.EscortName=EscortName +self.EscortGroupSet=EscortGroupSet +self.EscortGroupSet:SetSomeIteratorLimit(8) +self.EscortBriefing=EscortBriefing +self.Menu={} +self.Menu.HoldAtEscortPosition=self.Menu.HoldAtEscortPosition or{} +self.Menu.HoldAtLeaderPosition=self.Menu.HoldAtLeaderPosition or{} +self.Menu.Flare=self.Menu.Flare or{} +self.Menu.Smoke=self.Menu.Smoke or{} +self.Menu.Targets=self.Menu.Targets or{} +self.Menu.ROE=self.Menu.ROE or{} +self.Menu.ROT=self.Menu.ROT or{} +self.FollowDistance=100 +self.CT1=0 +self.GT1=0 +EscortGroupSet:ForEachGroup( +function(EscortGroup) +if not self.PlayerUnit._EscortGroups then +self.PlayerUnit._EscortGroups={} +end +if not self.PlayerUnit._EscortGroups[EscortGroup:GetName()]then +self.PlayerUnit._EscortGroups[EscortGroup:GetName()]={} +self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortGroup=EscortGroup +self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortName=self.EscortName +self.PlayerUnit._EscortGroups[EscortGroup:GetName()].Detection=self.Detection +end +end +) +self:SetFlightReportType(self.__Enum.ReportType.All) +return self +end +function AI_ESCORT:_InitFlightMenus() +self:SetFlightMenuJoinUp() +self:SetFlightMenuFormation("Trail") +self:SetFlightMenuFormation("Stack") +self:SetFlightMenuFormation("LeftLine") +self:SetFlightMenuFormation("RightLine") +self:SetFlightMenuFormation("LeftWing") +self:SetFlightMenuFormation("RightWing") +self:SetFlightMenuFormation("Vic") +self:SetFlightMenuFormation("Box") +self:SetFlightMenuHoldAtEscortPosition() +self:SetFlightMenuHoldAtLeaderPosition() +self:SetFlightMenuFlare() +self:SetFlightMenuSmoke() +self:SetFlightMenuROE() +self:SetFlightMenuROT() +self:SetFlightMenuTargets() +self:SetFlightMenuReportType() +end +function AI_ESCORT:_InitEscortMenus(EscortGroup) +EscortGroup.EscortMenu=MENU_GROUP:New(self.PlayerGroup,EscortGroup:GetCallsign(),self.MainMenu) +self:SetEscortMenuJoinUp(EscortGroup) +self:SetEscortMenuResumeMission(EscortGroup) +self:SetEscortMenuHoldAtEscortPosition(EscortGroup) +self:SetEscortMenuHoldAtLeaderPosition(EscortGroup) +self:SetEscortMenuFlare(EscortGroup) +self:SetEscortMenuSmoke(EscortGroup) +self:SetEscortMenuROE(EscortGroup) +self:SetEscortMenuROT(EscortGroup) +self:SetEscortMenuTargets(EscortGroup) +end +function AI_ESCORT:_InitEscortRoute(EscortGroup) +EscortGroup.MissionRoute=EscortGroup:GetTaskRoute() +end +function AI_ESCORT:onafterStart(EscortGroupSet) +self:F() +EscortGroupSet:ForEachGroup( +function(EscortGroup) +EscortGroup:WayPointInitialize() +EscortGroup:OptionROTVertical() +EscortGroup:OptionROEOpenFire() +end +) +local LeaderEscort=EscortGroupSet:GetFirst() +if LeaderEscort then +local Report=REPORT:New("Escort reporting:") +Report:Add("Joining Up "..EscortGroupSet:GetUnitTypeNames():Text(", ").." from "..LeaderEscort:GetCoordinate():ToString(self.PlayerUnit)) +LeaderEscort:MessageTypeToGroup(Report:Text(),MESSAGE.Type.Information,self.PlayerUnit) +end +self.Detection=DETECTION_AREAS:New(EscortGroupSet,5000) +self.Detection:InitDetectVisual(true) +self.Detection:InitDetectIRST(true) +self.Detection:InitDetectOptical(true) +self.Detection:InitDetectRadar(true) +self.Detection:InitDetectRWR(true) +self.Detection:SetAcceptRange(100000) +self.Detection:__Start(30) +self.MainMenu=MENU_GROUP:New(self.PlayerGroup,self.EscortName) +self.FlightMenu=MENU_GROUP:New(self.PlayerGroup,"Flight",self.MainMenu) +self:_InitFlightMenus() +self.EscortGroupSet:ForSomeGroupAlive( +function(EscortGroup) +self:_InitEscortMenus(EscortGroup) +self:_InitEscortRoute(EscortGroup) +self:SetFlightModeFormation(EscortGroup) +function EscortGroup:OnEventDeadOrCrash(EventData) +self:F({"EventDead",EventData}) +self.EscortMenu:Remove() +end +EscortGroup:HandleEvent(EVENTS.Dead,EscortGroup.OnEventDeadOrCrash) +EscortGroup:HandleEvent(EVENTS.Crash,EscortGroup.OnEventDeadOrCrash) +end +) +end +function AI_ESCORT:onafterStop(EscortGroupSet) +self:F() +EscortGroupSet:ForEachGroup( +function(EscortGroup) +EscortGroup:WayPointInitialize() +EscortGroup:OptionROTVertical() +EscortGroup:OptionROEOpenFire() +end +) +self.Detection:Stop() +self.MainMenu:Remove() +end +function AI_ESCORT:SetDetection(Detection) +self.Detection=Detection +self.EscortGroup.Detection=self.Detection +self.PlayerUnit._EscortGroups[self.EscortGroup:GetName()].Detection=self.EscortGroup.Detection +Detection:__Start(1) +end +function AI_ESCORT:TestSmokeDirectionVector(SmokeDirection) +self.SmokeDirectionVector=(SmokeDirection==true)and true or false +end +function AI_ESCORT:MenusHelicopters(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +self:F() +self.XStart=XStart or 50 +self.XSpace=XSpace or 50 +self.YStart=YStart or 50 +self.YSpace=YSpace or 50 +self.ZStart=ZStart or 50 +self.ZSpace=ZSpace or 50 +self.ZLevels=ZLevels or 10 +self:MenuJoinUp() +self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) +self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) +self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) +self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) +self:MenuHoldAtEscortPosition(30) +self:MenuHoldAtEscortPosition(100) +self:MenuHoldAtEscortPosition(500) +self:MenuHoldAtLeaderPosition(30,500) +self:MenuFlare() +self:MenuSmoke() +self:MenuTargets(60) +self:MenuAssistedAttack() +self:MenuROE() +self:MenuROT() +return self +end +function AI_ESCORT:MenusAirplanes(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +self:F() +self.XStart=XStart or 50 +self.XSpace=XSpace or 50 +self.YStart=YStart or 50 +self.YSpace=YSpace or 50 +self.ZStart=ZStart or 50 +self.ZSpace=ZSpace or 50 +self.ZLevels=ZLevels or 10 +self:MenuJoinUp() +self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) +self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) +self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) +self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) +self:MenuHoldAtEscortPosition(1000,500) +self:MenuHoldAtLeaderPosition(1000,500) +self:MenuFlare() +self:MenuSmoke() +self:MenuTargets(60) +self:MenuAssistedAttack() +self:MenuROE() +self:MenuROT() +return self +end +function AI_ESCORT:SetFlightMenuFormation(Formation) +local FormationID="Formation"..Formation +local MenuFormation=self.Menu[FormationID] +if MenuFormation then +local Arguments=MenuFormation.Arguments +local FlightMenuFormation=MENU_GROUP:New(self.PlayerGroup,"Formation",self.MainMenu) +local MenuFlightFormationID=MENU_GROUP_COMMAND:New(self.PlayerGroup,Formation,FlightMenuFormation, +function(self,Formation,...) +self.EscortGroupSet:ForSomeGroupAlive( +function(EscortGroup,self,Formation,Arguments) +if EscortGroup:IsAir()then +self:E({FormationID=FormationID}) +self[FormationID](self,unpack(Arguments)) +end +end,self,Formation,Arguments +) +end,self,Formation,Arguments +) +end +return self +end +function AI_ESCORT:MenuFormation(Formation,...) +local FormationID="Formation"..Formation +self.Menu[FormationID]=self.Menu[FormationID]or{} +self.Menu[FormationID].Arguments=arg +end +function AI_ESCORT:MenuFormationTrail(XStart,XSpace,YStart) +self:MenuFormation("Trail",XStart,XSpace,YStart) +return self +end +function AI_ESCORT:MenuFormationStack(XStart,XSpace,YStart,YSpace) +self:MenuFormation("Stack",XStart,XSpace,YStart,YSpace) +return self +end +function AI_ESCORT:MenuFormationLeftLine(XStart,YStart,ZStart,ZSpace) +self:MenuFormation("LeftLine",XStart,YStart,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationRightLine(XStart,YStart,ZStart,ZSpace) +self:MenuFormation("RightLine",XStart,YStart,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationLeftWing(XStart,XSpace,YStart,ZStart,ZSpace) +self:MenuFormation("LeftWing",XStart,XSpace,YStart,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationRightWing(XStart,XSpace,YStart,ZStart,ZSpace) +self:MenuFormation("RightWing",XStart,XSpace,YStart,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationCenterWing(XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +self:MenuFormation("CenterWing",XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationVic(XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +self:MenuFormation("Vic",XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationBox(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +self:MenuFormation("Box",XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +return self +end +function AI_ESCORT:SetFlightMenuJoinUp() +if self.Menu.JoinUp==true then +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuJoinUp=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Join Up",FlightMenuReportNavigation,AI_ESCORT._FlightJoinUp,self) +end +end +function AI_ESCORT:SetEscortMenuJoinUp(EscortGroup) +if self.Menu.JoinUp==true then +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuJoinUp=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Join Up",EscortMenuReportNavigation,AI_ESCORT._JoinUp,self,EscortGroup) +end +end +end +function AI_ESCORT:MenuJoinUp() +self.Menu.JoinUp=true +return self +end +function AI_ESCORT:SetFlightMenuHoldAtEscortPosition() +for _,MenuHoldAtEscortPosition in pairs(self.Menu.HoldAtEscortPosition or{})do +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuHoldPosition=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +MenuHoldAtEscortPosition.MenuText, +FlightMenuReportNavigation, +AI_ESCORT._FlightHoldPosition, +self, +nil, +MenuHoldAtEscortPosition.Height, +MenuHoldAtEscortPosition.Speed +) +end +return self +end +function AI_ESCORT:SetEscortMenuHoldAtEscortPosition(EscortGroup) +for _,HoldAtEscortPosition in pairs(self.Menu.HoldAtEscortPosition or{})do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuHoldPosition=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +HoldAtEscortPosition.MenuText, +EscortMenuReportNavigation, +AI_ESCORT._HoldPosition, +self, +EscortGroup, +EscortGroup, +HoldAtEscortPosition.Height, +HoldAtEscortPosition.Speed +) +end +end +return self +end +function AI_ESCORT:MenuHoldAtEscortPosition(Height,Speed,MenuTextFormat) +self:F({Height,Speed,MenuTextFormat}) +if not Height then +Height=30 +end +if not Speed then +Speed=0 +end +local MenuText="" +if not MenuTextFormat then +if Speed==0 then +MenuText=string.format("Hold at %d meter",Height) +else +MenuText=string.format("Hold at %d meter at %d",Height,Speed) +end +else +if Speed==0 then +MenuText=string.format(MenuTextFormat,Height) +else +MenuText=string.format(MenuTextFormat,Height,Speed) +end +end +self.Menu.HoldAtEscortPosition=self.Menu.HoldAtEscortPosition or{} +self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition+1]={} +self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Height=Height +self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Speed=Speed +self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].MenuText=MenuText +return self +end +function AI_ESCORT:SetFlightMenuHoldAtLeaderPosition() +for _,MenuHoldAtLeaderPosition in pairs(self.Menu.HoldAtLeaderPosition or{})do +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuHoldAtLeaderPosition=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +MenuHoldAtLeaderPosition.MenuText, +FlightMenuReportNavigation, +AI_ESCORT._FlightHoldPosition, +self, +self.PlayerGroup, +MenuHoldAtLeaderPosition.Height, +MenuHoldAtLeaderPosition.Speed +) +end +return self +end +function AI_ESCORT:SetEscortMenuHoldAtLeaderPosition(EscortGroup) +for _,HoldAtLeaderPosition in pairs(self.Menu.HoldAtLeaderPosition or{})do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuHoldAtLeaderPosition=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +HoldAtLeaderPosition.MenuText, +EscortMenuReportNavigation, +AI_ESCORT._HoldPosition, +self, +self.PlayerGroup, +EscortGroup, +HoldAtLeaderPosition.Height, +HoldAtLeaderPosition.Speed +) +end +end +return self +end +function AI_ESCORT:MenuHoldAtLeaderPosition(Height,Speed,MenuTextFormat) +self:F({Height,Speed,MenuTextFormat}) +if not Height then +Height=30 +end +if not Speed then +Speed=0 +end +local MenuText="" +if not MenuTextFormat then +if Speed==0 then +MenuText=string.format("Rejoin and hold at %d meter",Height) +else +MenuText=string.format("Rejoin and hold at %d meter at %d",Height,Speed) +end +else +if Speed==0 then +MenuText=string.format(MenuTextFormat,Height) +else +MenuText=string.format(MenuTextFormat,Height,Speed) +end +end +self.Menu.HoldAtLeaderPosition=self.Menu.HoldAtLeaderPosition or{} +self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition+1]={} +self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Height=Height +self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Speed=Speed +self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].MenuText=MenuText +return self +end +function AI_ESCORT:MenuScanForTargets(Height,Seconds,MenuTextFormat) +self:F({Height,Seconds,MenuTextFormat}) +if self.EscortGroup:IsAir()then +if not self.EscortMenuScan then +self.EscortMenuScan=MENU_GROUP:New(self.PlayerGroup,"Scan for targets",self.EscortMenu) +end +if not Height then +Height=100 +end +if not Seconds then +Seconds=30 +end +local MenuText="" +if not MenuTextFormat then +if Seconds==0 then +MenuText=string.format("At %d meter",Height) +else +MenuText=string.format("At %d meter for %d seconds",Height,Seconds) +end +else +if Seconds==0 then +MenuText=string.format(MenuTextFormat,Height) +else +MenuText=string.format(MenuTextFormat,Height,Seconds) +end +end +if not self.EscortMenuScanForTargets then +self.EscortMenuScanForTargets={} +end +self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1]=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +MenuText, +self.EscortMenuScan, +AI_ESCORT._ScanTargets, +self, +30 +) +end +return self +end +function AI_ESCORT:SetFlightMenuFlare() +for _,MenuFlare in pairs(self.Menu.Flare or{})do +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuFlare=MENU_GROUP:New(self.PlayerGroup,MenuFlare.MenuText,FlightMenuReportNavigation) +local FlightMenuFlareGreenFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Green,"Released a green flare!") +local FlightMenuFlareRedFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Red,"Released a red flare!") +local FlightMenuFlareWhiteFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.White,"Released a white flare!") +local FlightMenuFlareYellowFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release yellow flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Yellow,"Released a yellow flare!") +end +return self +end +function AI_ESCORT:SetEscortMenuFlare(EscortGroup) +for _,MenuFlare in pairs(self.Menu.Flare or{})do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuFlare=MENU_GROUP:New(self.PlayerGroup,MenuFlare.MenuText,EscortMenuReportNavigation) +local EscortMenuFlareGreen=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Green,"Released a green flare!") +local EscortMenuFlareRed=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Red,"Released a red flare!") +local EscortMenuFlareWhite=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.White,"Released a white flare!") +local EscortMenuFlareYellow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release yellow flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Yellow,"Released a yellow flare!") +end +end +return self +end +function AI_ESCORT:MenuFlare(MenuTextFormat) +self:F() +local MenuText="" +if not MenuTextFormat then +MenuText="Flare" +else +MenuText=MenuTextFormat +end +self.Menu.Flare=self.Menu.Flare or{} +self.Menu.Flare[#self.Menu.Flare+1]={} +self.Menu.Flare[#self.Menu.Flare].MenuText=MenuText +return self +end +function AI_ESCORT:SetFlightMenuSmoke() +for _,MenuSmoke in pairs(self.Menu.Smoke or{})do +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuSmoke=MENU_GROUP:New(self.PlayerGroup,MenuSmoke.MenuText,FlightMenuReportNavigation) +local FlightMenuSmokeGreenFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Green,"Releasing green smoke!") +local FlightMenuSmokeRedFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Red,"Releasing red smoke!") +local FlightMenuSmokeWhiteFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.White,"Releasing white smoke!") +local FlightMenuSmokeOrangeFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release orange smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!") +local FlightMenuSmokeBlueFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release blue smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Blue,"Releasing blue smoke!") +end +return self +end +function AI_ESCORT:SetEscortMenuSmoke(EscortGroup) +for _,MenuSmoke in pairs(self.Menu.Smoke or{})do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuSmoke=MENU_GROUP:New(self.PlayerGroup,MenuSmoke.MenuText,EscortMenuReportNavigation) +local EscortMenuSmokeGreen=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Green,"Releasing green smoke!") +local EscortMenuSmokeRed=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Red,"Releasing red smoke!") +local EscortMenuSmokeWhite=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.White,"Releasing white smoke!") +local EscortMenuSmokeOrange=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release orange smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Orange,"Releasing orange smoke!") +local EscortMenuSmokeBlue=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release blue smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Blue,"Releasing blue smoke!") +end +end +return self +end +function AI_ESCORT:MenuSmoke(MenuTextFormat) +self:F() +local MenuText="" +if not MenuTextFormat then +MenuText="Smoke" +else +MenuText=MenuTextFormat +end +self.Menu.Smoke=self.Menu.Smoke or{} +self.Menu.Smoke[#self.Menu.Smoke+1]={} +self.Menu.Smoke[#self.Menu.Smoke].MenuText=MenuText +return self +end +function AI_ESCORT:SetFlightMenuReportType() +local FlightMenuReportTargets=MENU_GROUP:New(self.PlayerGroup,"Report targets",self.FlightMenu) +local MenuStamp=FlightMenuReportTargets:GetStamp() +local FlightReportType=self:GetFlightReportType() +if FlightReportType~=self.__Enum.ReportType.All then +local FlightMenuReportTargetsAll=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report all targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeAll,self) +:SetTag("ReportType") +:SetStamp(MenuStamp) +end +if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.Airborne then +local FlightMenuReportTargetsAirborne=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report airborne targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeAirborne,self) +:SetTag("ReportType") +:SetStamp(MenuStamp) +end +if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.GroundRadar then +local FlightMenuReportTargetsGroundRadar=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report gound radar targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeGroundRadar,self) +:SetTag("ReportType") +:SetStamp(MenuStamp) +end +if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.Ground then +local FlightMenuReportTargetsGround=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report ground targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeGround,self) +:SetTag("ReportType") +:SetStamp(MenuStamp) +end +FlightMenuReportTargets:RemoveSubMenus(MenuStamp,"ReportType") +end +function AI_ESCORT:SetFlightMenuTargets() +local FlightMenuReportTargets=MENU_GROUP:New(self.PlayerGroup,"Report targets",self.FlightMenu) +local FlightMenuReportTargetsNow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets now!",FlightMenuReportTargets,AI_ESCORT._FlightReportNearbyTargetsNow,self) +local FlightMenuReportTargetsOn=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets on",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportNearbyTargets,self,true) +local FlightMenuReportTargetsOff=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets off",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportNearbyTargets,self,false) +self.FlightMenuAttack=MENU_GROUP:New(self.PlayerGroup,"Attack targets",self.FlightMenu) +local FlightMenuAttackNearby=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self):SetTag("Attack") +local FlightMenuAttackNearbyAir=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest airborne targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self,self.__Enum.ReportType.Air):SetTag("Attack") +local FlightMenuAttackNearbyGround=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest ground targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self,self.__Enum.ReportType.Ground):SetTag("Attack") +for _,MenuTargets in pairs(self.Menu.Targets or{})do +MenuTargets.FlightReportTargetsScheduler=SCHEDULER:New(self,self._FlightReportTargetsScheduler,{},MenuTargets.Interval,MenuTargets.Interval) +end +return self +end +function AI_ESCORT:SetEscortMenuTargets(EscortGroup) +for _,MenuTargets in pairs(self.Menu.Targets or{}or{})do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +EscortGroup.EscortMenuReportNearbyTargetsNow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets",EscortGroup.EscortMenu,AI_ESCORT._ReportNearbyTargetsNow,self,EscortGroup,true) +EscortGroup.ReportTargetsScheduler=SCHEDULER:New(self,self._ReportTargetsScheduler,{EscortGroup},1,MenuTargets.Interval) +EscortGroup.ResumeScheduler=SCHEDULER:New(self,self._ResumeScheduler,{EscortGroup},1,60) +end +end +return self +end +function AI_ESCORT:MenuTargets(Seconds) +self:F({Seconds}) +if not Seconds then +Seconds=30 +end +self.Menu.Targets=self.Menu.Targets or{} +self.Menu.Targets[#self.Menu.Targets+1]={} +self.Menu.Targets[#self.Menu.Targets].Interval=Seconds +return self +end +function AI_ESCORT:MenuAssistedAttack() +self:F() +self.EscortGroupSet:ForSomeGroupAlive( +function(EscortGroup) +if not EscortGroup:IsAir()then +self.EscortMenuTargetAssistance=MENU_GROUP:New(self.PlayerGroup,"Request assistance from",EscortGroup.EscortMenu) +end +end +) +return self +end +function AI_ESCORT:SetFlightMenuROE() +for _,MenuROE in pairs(self.Menu.ROE or{})do +local FlightMenuROE=MENU_GROUP:New(self.PlayerGroup,"Rule Of Engagement",self.FlightMenu) +local FlightMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Hold fire",FlightMenuROE,AI_ESCORT._FlightROEHoldFire,self,"Holding weapons!") +local FlightMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Return fire",FlightMenuROE,AI_ESCORT._FlightROEReturnFire,self,"Returning fire!") +local FlightMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open Fire",FlightMenuROE,AI_ESCORT._FlightROEOpenFire,self,"Open fire at designated targets!") +local FlightMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Engage all targets",FlightMenuROE,AI_ESCORT._FlightROEWeaponFree,self,"Engaging all targets!") +end +return self +end +function AI_ESCORT:SetEscortMenuROE(EscortGroup) +for _,MenuROE in pairs(self.Menu.ROE or{})do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuROE=MENU_GROUP:New(self.PlayerGroup,"Rule Of Engagement",EscortGroup.EscortMenu) +if EscortGroup:OptionROEHoldFirePossible()then +local EscortMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Hold fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEHoldFire,"Holding weapons!") +end +if EscortGroup:OptionROEReturnFirePossible()then +local EscortMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Return fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEReturnFire,"Returning fire!") +end +if EscortGroup:OptionROEOpenFirePossible()then +EscortGroup.EscortMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open Fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEOpenFire,"Opening fire on designated targets!!") +end +if EscortGroup:OptionROEWeaponFreePossible()then +EscortGroup.EscortMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Engage all targets",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEWeaponFree,"Opening fire on targets of opportunity!") +end +end +end +return self +end +function AI_ESCORT:MenuROE() +self:F() +self.Menu.ROE=self.Menu.ROE or{} +self.Menu.ROE[#self.Menu.ROE+1]={} +return self +end +function AI_ESCORT:SetFlightMenuROT() +for _,MenuROT in pairs(self.Menu.ROT or{})do +local FlightMenuROT=MENU_GROUP:New(self.PlayerGroup,"Reaction On Threat",self.FlightMenu) +local FlightMenuROTNoReaction=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Fight until death",FlightMenuROT,AI_ESCORT._FlightROTNoReaction,self,"Fighting until death!") +local FlightMenuROTPassiveDefense=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Use flares, chaff and jammers",FlightMenuROT,AI_ESCORT._FlightROTPassiveDefense,self,"Defending using jammers, chaff and flares!") +local FlightMenuROTEvadeFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open fire",FlightMenuROT,AI_ESCORT._FlightROTEvadeFire,self,"Evading on enemy fire!") +local FlightMenuROTVertical=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Avoid radar and evade fire",FlightMenuROT,AI_ESCORT._FlightROTVertical,self,"Evading on enemy fire with vertical manoeuvres!") +end +return self +end +function AI_ESCORT:SetEscortMenuROT(EscortGroup) +for _,MenuROT in pairs(self.Menu.ROT or{})do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuROT=MENU_GROUP:New(self.PlayerGroup,"Reaction On Threat",EscortGroup.EscortMenu) +if not EscortGroup.EscortMenuEvasion then +if EscortGroup:OptionROTNoReactionPossible()then +local EscortMenuEvasionNoReaction=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Fight until death",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTNoReaction,"Fighting until death!") +end +if EscortGroup:OptionROTPassiveDefensePossible()then +local EscortMenuEvasionPassiveDefense=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Use flares, chaff and jammers",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTPassiveDefense,"Defending using jammers, chaff and flares!") +end +if EscortGroup:OptionROTEvadeFirePossible()then +local EscortMenuEvasionEvadeFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open fire",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTEvadeFire,"Evading on enemy fire!") +end +if EscortGroup:OptionROTVerticalPossible()then +local EscortMenuOptionEvasionVertical=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Avoid radar and evade fire",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTVertical,"Evading on enemy fire with vertical manoeuvres!") +end +end +end +end +return self +end +function AI_ESCORT:MenuROT(MenuTextFormat) +self:F(MenuTextFormat) +self.Menu.ROT=self.Menu.ROT or{} +self.Menu.ROT[#self.Menu.ROT+1]={} +return self +end +function AI_ESCORT:SetEscortMenuResumeMission(EscortGroup) +self:F() +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +EscortGroup.EscortMenuResumeMission=MENU_GROUP:New(self.PlayerGroup,"Resume from",EscortGroup.EscortMenu) +end +return self +end +function AI_ESCORT:_HoldPosition(OrbitGroup,EscortGroup,OrbitHeight,OrbitSeconds) +local EscortUnit=self.PlayerUnit +local OrbitUnit=OrbitGroup:GetUnit(1) +self:SetFlightModeMission(EscortGroup) +local PointFrom={} +local GroupVec3=EscortGroup:GetUnit(1):GetVec3() +PointFrom={} +PointFrom.x=GroupVec3.x +PointFrom.y=GroupVec3.z +PointFrom.speed=250 +PointFrom.type=AI.Task.WaypointType.TURNING_POINT +PointFrom.alt=GroupVec3.y +PointFrom.alt_type=AI.Task.AltitudeType.BARO +local OrbitPoint=OrbitUnit:GetVec2() +local PointTo={} +PointTo.x=OrbitPoint.x +PointTo.y=OrbitPoint.y +PointTo.speed=250 +PointTo.type=AI.Task.WaypointType.TURNING_POINT +PointTo.alt=OrbitHeight +PointTo.alt_type=AI.Task.AltitudeType.BARO +PointTo.task=EscortGroup:TaskOrbitCircleAtVec2(OrbitPoint,OrbitHeight,0) +local Points={PointFrom,PointTo} +EscortGroup:OptionROEHoldFire() +EscortGroup:OptionROTPassiveDefense() +EscortGroup:SetTask(EscortGroup:TaskRoute(Points),1) +EscortGroup:MessageTypeToGroup("Orbiting at current location.",MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_FlightHoldPosition(OrbitGroup,OrbitHeight,OrbitSeconds) +local EscortUnit=self.PlayerUnit +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup,OrbitGroup) +if EscortGroup:IsAir()then +if OrbitGroup==nil then +OrbitGroup=EscortGroup +end +self:_HoldPosition(OrbitGroup,EscortGroup,OrbitHeight,OrbitSeconds) +end +end,OrbitGroup +) +end +function AI_ESCORT:_JoinUp(EscortGroup) +local EscortUnit=self.PlayerUnit +self:SetFlightModeFormation(EscortGroup) +EscortGroup:MessageTypeToGroup("Joining up!",MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_FlightJoinUp() +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_JoinUp(EscortGroup) +end +end +) +end +function AI_ESCORT:_EscortFormationTrail(EscortGroup,XStart,XSpace,YStart) +self:FormationTrail(XStart,XSpace,YStart) +end +function AI_ESCORT:_FlightFormationTrail(XStart,XSpace,YStart) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_EscortFormationTrail(EscortGroup,XStart,XSpace,YStart) +end +end +) +end +function AI_ESCORT:_EscortFormationStack(EscortGroup,XStart,XSpace,YStart,YSpace) +self:FormationStack(XStart,XSpace,YStart,YSpace) +end +function AI_ESCORT:_FlightFormationStack(XStart,XSpace,YStart,YSpace) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_EscortFormationStack(EscortGroup,XStart,XSpace,YStart,YSpace) +end +end +) +end +function AI_ESCORT:_Flare(EscortGroup,Color,Message) +local EscortUnit=self.PlayerUnit +EscortGroup:GetUnit(1):Flare(Color) +EscortGroup:MessageTypeToGroup(Message,MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_FlightFlare(Color,Message) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_Flare(EscortGroup,Color,Message) +end +end +) +end +function AI_ESCORT:_Smoke(EscortGroup,Color,Message) +local EscortUnit=self.PlayerUnit +EscortGroup:GetUnit(1):Smoke(Color) +EscortGroup:MessageTypeToGroup(Message,MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_FlightSmoke(Color,Message) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_Smoke(EscortGroup,Color,Message) +end +end +) +end +function AI_ESCORT:_ReportNearbyTargetsNow(EscortGroup) +local EscortUnit=self.PlayerUnit +self:_ReportTargetsScheduler(EscortGroup) +end +function AI_ESCORT:_FlightReportNearbyTargetsNow() +self:_FlightReportTargetsScheduler() +end +function AI_ESCORT:_FlightSwitchReportNearbyTargets(ReportTargets) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_EscortSwitchReportNearbyTargets(EscortGroup,ReportTargets) +end +end +) +end +function AI_ESCORT:SetFlightReportType(ReportType) +self.FlightReportType=ReportType +end +function AI_ESCORT:GetFlightReportType() +return self.FlightReportType +end +function AI_ESCORT:_FlightSwitchReportTypeAll() +self:SetFlightReportType(self.__Enum.ReportType.All) +self:SetFlightMenuReportType() +local EscortGroup=self.EscortGroupSet:GetFirst() +EscortGroup:MessageTypeToGroup("Reporting all targets.",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightSwitchReportTypeAirborne() +self:SetFlightReportType(self.__Enum.ReportType.Airborne) +self:SetFlightMenuReportType() +local EscortGroup=self.EscortGroupSet:GetFirst() +EscortGroup:MessageTypeToGroup("Reporting airborne targets.",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightSwitchReportTypeGroundRadar() +self:SetFlightReportType(self.__Enum.ReportType.Ground) +self:SetFlightMenuReportType() +local EscortGroup=self.EscortGroupSet:GetFirst() +EscortGroup:MessageTypeToGroup("Reporting ground radar targets.",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightSwitchReportTypeGround() +self:SetFlightReportType(self.__Enum.ReportType.Ground) +self:SetFlightMenuReportType() +local EscortGroup=self.EscortGroupSet:GetFirst() +EscortGroup:MessageTypeToGroup("Reporting ground targets.",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_ScanTargets(ScanDuration) +local EscortGroup=self.EscortGroup +local EscortUnit=self.PlayerUnit +self.FollowScheduler:Stop(self.FollowSchedule) +if EscortGroup:IsHelicopter()then +EscortGroup:PushTask( +EscortGroup:TaskControlled( +EscortGroup:TaskOrbitCircle(200,20), +EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) +),1) +elseif EscortGroup:IsAirPlane()then +EscortGroup:PushTask( +EscortGroup:TaskControlled( +EscortGroup:TaskOrbitCircle(1000,500), +EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) +),1) +end +EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortUnit) +if self.EscortMode==AI_ESCORT.MODE.FOLLOW then +self.FollowScheduler:Start(self.FollowSchedule) +end +end +function AI_ESCORT.___Resume(EscortGroup,self) +self:F({self=self}) +local PlayerGroup=self.PlayerGroup +EscortGroup:OptionROEHoldFire() +EscortGroup:OptionROTVertical() +EscortGroup:SetState(EscortGroup,"Mode",EscortGroup:GetState(EscortGroup,"PreviousMode")) +if EscortGroup:GetState(EscortGroup,"Mode")==self.__Enum.Mode.Mission then +EscortGroup:MessageTypeToGroup("Resuming route.",MESSAGE.Type.Information,PlayerGroup) +else +EscortGroup:MessageTypeToGroup("Rejoining formation.",MESSAGE.Type.Information,PlayerGroup) +end +end +function AI_ESCORT:_ResumeMission(EscortGroup,WayPoint) +self:SetFlightModeMission(EscortGroup) +local WayPoints=EscortGroup.MissionRoute +self:T(WayPoint,WayPoints) +for WayPointIgnore=1,WayPoint do +table.remove(WayPoints,1) +end +EscortGroup:SetTask(EscortGroup:TaskRoute(WayPoints),1) +EscortGroup:MessageTypeToGroup("Resuming mission from waypoint ",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_AttackTarget(EscortGroup,DetectedItem) +self:F(EscortGroup) +self:SetFlightModeAttack(EscortGroup) +if EscortGroup:IsAir()then +EscortGroup:OptionROEOpenFire() +EscortGroup:OptionROTVertical() +EscortGroup:SetState(EscortGroup,"Escort",self) +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +local AttackUnitTasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +AttackUnitTasks[#AttackUnitTasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit) +end +end,Tasks +) +Tasks[#Tasks+1]=EscortGroup:TaskCombo(AttackUnitTasks) +Tasks[#Tasks+1]=EscortGroup:TaskFunction("AI_ESCORT.___Resume",self) +EscortGroup:PushTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +else +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50) +end +end,Tasks +) +EscortGroup:PushTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +end +local DetectedTargetsReport=REPORT:New("Engaging target:\n") +local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) +local ReportSummary=DetectedItemReportSummary:Text(", ") +DetectedTargetsReport:AddIndent(ReportSummary,"-") +EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text(),MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightAttackTarget(DetectedItem) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup,DetectedItem) +if EscortGroup:IsAir()then +self:_AttackTarget(EscortGroup,DetectedItem) +end +end,DetectedItem +) +end +function AI_ESCORT:_FlightAttackNearestTarget(TargetType) +self.Detection:Detect() +self:_FlightReportTargetsScheduler() +local EscortGroup=self.EscortGroupSet:GetFirst() +local AttackDetectedItem=nil +local DetectedItems=self.Detection:GetDetectedItems() +for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])0 +local HasAir=DetectedItemSet:HasAirUnits()>0 +local FlightReportType=self:GetFlightReportType() +if(TargetType and TargetType==self.__Enum.ReportType.Ground and HasGround)or +(TargetType and TargetType==self.__Enum.ReportType.Air and HasAir)or +(TargetType==nil)then +AttackDetectedItem=DetectedItem +break +end +end +if AttackDetectedItem then +self:_FlightAttackTarget(AttackDetectedItem) +else +EscortGroup:MessageTypeToGroup("Nothing to attack!",MESSAGE.Type.Information,self.PlayerGroup) +end +end +function AI_ESCORT:_AssistTarget(EscortGroup,DetectedItem) +local EscortUnit=self.PlayerUnit +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50) +end +end,Tasks +) +EscortGroup:SetTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +EscortGroup:MessageTypeToGroup("Assisting attack!",MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_ROE(EscortGroup,EscortROEFunction,EscortROEMessage) +pcall(function()EscortROEFunction(EscortGroup)end) +EscortGroup:MessageTypeToGroup(EscortROEMessage,MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightROEHoldFire(EscortROEMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROE(EscortGroup,EscortGroup.OptionROEHoldFire,EscortROEMessage) +end +) +end +function AI_ESCORT:_FlightROEOpenFire(EscortROEMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROE(EscortGroup,EscortGroup.OptionROEOpenFire,EscortROEMessage) +end +) +end +function AI_ESCORT:_FlightROEReturnFire(EscortROEMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROE(EscortGroup,EscortGroup.OptionROEReturnFire,EscortROEMessage) +end +) +end +function AI_ESCORT:_FlightROEWeaponFree(EscortROEMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROE(EscortGroup,EscortGroup.OptionROEWeaponFree,EscortROEMessage) +end +) +end +function AI_ESCORT:_ROT(EscortGroup,EscortROTFunction,EscortROTMessage) +pcall(function()EscortROTFunction(EscortGroup)end) +EscortGroup:MessageTypeToGroup(EscortROTMessage,MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightROTNoReaction(EscortROTMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROT(EscortGroup,EscortGroup.OptionROTNoReaction,EscortROTMessage) +end +) +end +function AI_ESCORT:_FlightROTPassiveDefense(EscortROTMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROT(EscortGroup,EscortGroup.OptionROTPassiveDefense,EscortROTMessage) +end +) +end +function AI_ESCORT:_FlightROTEvadeFire(EscortROTMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROT(EscortGroup,EscortGroup.OptionROTEvadeFire,EscortROTMessage) +end +) +end +function AI_ESCORT:_FlightROTVertical(EscortROTMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROT(EscortGroup,EscortGroup.OptionROTVertical,EscortROTMessage) +end +) +end +function AI_ESCORT:RegisterRoute() +self:F() +local EscortGroup=self.EscortGroup +local TaskPoints=EscortGroup:GetTaskRoute() +self:T(TaskPoints) +return TaskPoints +end +function AI_ESCORT:_ResumeScheduler(EscortGroup) +self:F(EscortGroup:GetName()) +if EscortGroup:IsAlive()and self.PlayerUnit:IsAlive()then +local EscortGroupName=EscortGroup:GetCallsign() +if EscortGroup.EscortMenuResumeMission then +EscortGroup.EscortMenuResumeMission:RemoveSubMenus() +local TaskPoints=EscortGroup.MissionRoute +for WayPointID,WayPoint in pairs(TaskPoints)do +local EscortVec3=EscortGroup:GetVec3() +local Distance=((WayPoint.x-EscortVec3.x)^2+ +(WayPoint.y-EscortVec3.z)^2 +)^0.5/1000 +MENU_GROUP_COMMAND:New(self.PlayerGroup,"Waypoint "..WayPointID.." at "..string.format("%.2f",Distance).."km",EscortGroup.EscortMenuResumeMission,AI_ESCORT._ResumeMission,self,EscortGroup,WayPointID) +end +end +end +end +function AI_ESCORT:Distance(PlayerUnit,DetectedItem) +local DetectedCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) +local PlayerCoordinate=PlayerUnit:GetCoordinate() +return DetectedCoordinate:Get3DDistance(PlayerCoordinate) +end +function AI_ESCORT:_ReportTargetsScheduler(EscortGroup,Report) +self:F(EscortGroup:GetName()) +if EscortGroup:IsAlive()and self.PlayerUnit:IsAlive()then +local EscortGroupName=EscortGroup:GetCallsign() +local DetectedTargetsReport=REPORT:New("Reporting targets:\n") +if EscortGroup.EscortMenuTargetAssistance then +EscortGroup.EscortMenuTargetAssistance:RemoveSubMenus() +end +local DetectedItems=self.Detection:GetDetectedItems() +local ClientEscortTargets=self.Detection +local TimeUpdate=timer.getTime() +local EscortMenuAttackTargets=MENU_GROUP:New(self.PlayerGroup,"Attack targets",EscortGroup.EscortMenu) +local DetectedTargets=false +for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])0 +local HasGroundRadar=HasGround and DetectedItemSet:HasRadar()>0 +local HasAir=DetectedItemSet:HasAirUnits()>0 +local FlightReportType=self:GetFlightReportType() +if(FlightReportType==self.__Enum.ReportType.All)or +(FlightReportType==self.__Enum.ReportType.Airborne and HasAir)or +(FlightReportType==self.__Enum.ReportType.Ground and HasGround)or +(FlightReportType==self.__Enum.ReportType.GroundRadar and HasGroundRadar)then +DetectedTargets=true +local DetectedMenu=self.Detection:DetectedItemReportMenu(DetectedItem,EscortGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())):Text("\n") +local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,EscortGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) +local ReportSummary=DetectedItemReportSummary:Text(", ") +DetectedTargetsReport:AddIndent(ReportSummary,"-") +if EscortGroup:IsAir()then +MENU_GROUP_COMMAND:New(self.PlayerGroup, +DetectedMenu, +EscortMenuAttackTargets, +AI_ESCORT._AttackTarget, +self, +EscortGroup, +DetectedItem +):SetTag("Escort"):SetTime(TimeUpdate) +else +if self.EscortMenuTargetAssistance then +local MenuTargetAssistance=MENU_GROUP:New(self.PlayerGroup,EscortGroupName,EscortGroup.EscortMenuTargetAssistance) +MENU_GROUP_COMMAND:New(self.PlayerGroup, +DetectedMenu, +MenuTargetAssistance, +AI_ESCORT._AssistTarget, +self, +EscortGroup, +DetectedItem +) +end +end +end +end +EscortMenuAttackTargets:RemoveSubMenus(TimeUpdate,"Escort") +if Report then +if DetectedTargets then +EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text("\n"),MESSAGE.Type.Information,self.PlayerGroup) +else +EscortGroup:MessageTypeToGroup("No targets detected.",MESSAGE.Type.Information,self.PlayerGroup) +end +end +return true +end +return false +end +function AI_ESCORT:_FlightReportTargetsScheduler() +self:F("FlightReportTargetScheduler") +local EscortGroup=self.EscortGroupSet:GetFirst() +local DetectedTargetsReport=REPORT:New("Reporting your targets:\n") +if EscortGroup and(self.PlayerUnit:IsAlive()and EscortGroup:IsAlive())then +local TimeUpdate=timer.getTime() +local DetectedItems=self.Detection:GetDetectedItems() +local DetectedTargets=false +local ClientEscortTargets=self.Detection +for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])0 +local HasGroundRadar=HasGround and DetectedItemSet:HasRadar()>0 +local HasAir=DetectedItemSet:HasAirUnits()>0 +local FlightReportType=self:GetFlightReportType() +if(FlightReportType==self.__Enum.ReportType.All)or +(FlightReportType==self.__Enum.ReportType.Airborne and HasAir)or +(FlightReportType==self.__Enum.ReportType.Ground and HasGround)or +(FlightReportType==self.__Enum.ReportType.GroundRadar and HasGroundRadar)then +DetectedTargets=true +local DetectedItemReportMenu=self.Detection:DetectedItemReportMenu(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) +local ReportMenuText=DetectedItemReportMenu:Text(", ") +MENU_GROUP_COMMAND:New(self.PlayerGroup, +ReportMenuText, +self.FlightMenuAttack, +AI_ESCORT._FlightAttackTarget, +self, +DetectedItem +):SetTag("Flight"):SetTime(TimeUpdate) +local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) +local ReportSummary=DetectedItemReportSummary:Text(", ") +DetectedTargetsReport:AddIndent(ReportSummary,"-") +end +end +self.FlightMenuAttack:RemoveSubMenus(TimeUpdate,"Flight") +if DetectedTargets then +EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text("\n"),MESSAGE.Type.Information,self.PlayerGroup) +end +return true +end +return false +end +AI_ESCORT_REQUEST={ +ClassName="AI_ESCORT_REQUEST", +} +function AI_ESCORT_REQUEST:New(EscortUnit,EscortSpawn,EscortAirbase,EscortName,EscortBriefing) +local EscortGroupSet=SET_GROUP:New():FilterDeads():FilterCrashes() +local self=BASE:Inherit(self,AI_ESCORT:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing)) +self.EscortGroupSet=EscortGroupSet +self.EscortSpawn=EscortSpawn +self.EscortAirbase=EscortAirbase +self.LeaderGroup=self.PlayerUnit:GetGroup() +self.Detection=DETECTION_AREAS:New(self.EscortGroupSet,5000) +self.Detection:__Start(30) +self.SpawnMode=self.__Enum.Mode.Mission +return self +end +function AI_ESCORT_REQUEST:SpawnEscort() +local EscortGroup=self.EscortSpawn:SpawnAtAirbase(self.EscortAirbase,SPAWN.Takeoff.Hot) +self:ScheduleOnce(0.1, +function(EscortGroup) +EscortGroup:OptionROTVertical() +EscortGroup:OptionROEHoldFire() +self.EscortGroupSet:AddGroup(EscortGroup) +local LeaderEscort=self.EscortGroupSet:GetFirst() +local Report=REPORT:New() +Report:Add("Joining Up "..self.EscortGroupSet:GetUnitTypeNames():Text(", ").." from "..LeaderEscort:GetCoordinate():ToString(self.EscortUnit)) +LeaderEscort:MessageTypeToGroup(Report:Text(),MESSAGE.Type.Information,self.PlayerUnit) +self:SetFlightModeFormation(EscortGroup) +self:FormationTrail() +self:_InitFlightMenus() +self:_InitEscortMenus(EscortGroup) +self:_InitEscortRoute(EscortGroup) +function EscortGroup:OnEventDeadOrCrash(EventData) +self:F({"EventDead",EventData}) +self.EscortMenu:Remove() +end +EscortGroup:HandleEvent(EVENTS.Dead,EscortGroup.OnEventDeadOrCrash) +EscortGroup:HandleEvent(EVENTS.Crash,EscortGroup.OnEventDeadOrCrash) +end,EscortGroup +) +end +function AI_ESCORT_REQUEST:onafterStart(EscortGroupSet) +self:F() +if not self.MenuRequestEscort then +self.MainMenu=MENU_GROUP:New(self.PlayerGroup,self.EscortName) +self.MenuRequestEscort=MENU_GROUP_COMMAND:New(self.LeaderGroup,"Request new escort ",self.MainMenu, +function() +self:SpawnEscort() +end +) +end +self:GetParent(self).onafterStart(self,EscortGroupSet) +self:HandleEvent(EVENTS.Dead,self.OnEventDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self.OnEventDeadOrCrash) +end +function AI_ESCORT_REQUEST:onafterStop(EscortGroupSet) +self:F() +EscortGroupSet:ForEachGroup( +function(EscortGroup) +EscortGroup:WayPointInitialize() +EscortGroup:OptionROTVertical() +EscortGroup:OptionROEOpenFire() +end +) +self.Detection:Stop() +self.MainMenu:Remove() +end +function AI_ESCORT_REQUEST:SetEscortSpawnMission() +self.SpawnMode=self.__Enum.Mode.Mission +end +AI_ESCORT_DISPATCHER={ +ClassName="AI_ESCORT_DISPATCHER", +} +AI_ESCORT_DISPATCHER.AI_Escorts={} +function AI_ESCORT_DISPATCHER:New(CarrierSet,EscortSpawn,EscortAirbase,EscortName,EscortBriefing) +local self=BASE:Inherit(self,FSM:New()) +self.CarrierSet=CarrierSet +self.EscortSpawn=EscortSpawn +self.EscortAirbase=EscortAirbase +self.EscortName=EscortName +self.EscortBriefing=EscortBriefing +self:SetStartState("Idle") +self:AddTransition("Monitoring","Monitor","Monitoring") +self:AddTransition("Idle","Start","Monitoring") +self:AddTransition("Monitoring","Stop","Idle") +function self.CarrierSet.OnAfterRemoved(CarrierSet,From,Event,To,CarrierName,Carrier) +self:F({Carrier=Carrier:GetName()}) +end +return self +end +function AI_ESCORT_DISPATCHER:onafterStart(From,Event,To) +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventExit) +self:HandleEvent(EVENTS.Crash,self.OnEventExit) +self:HandleEvent(EVENTS.Dead,self.OnEventExit) +end +function AI_ESCORT_DISPATCHER:OnEventExit(EventData) +local PlayerGroupName=EventData.IniGroupName +local PlayerGroup=EventData.IniGroup +local PlayerUnit=EventData.IniUnit +self:T({EscortAirbase=self.EscortAirbase}) +self:T({PlayerGroupName=PlayerGroupName}) +self:T({PlayerGroup=PlayerGroup}) +self:T({FirstGroup=self.CarrierSet:GetFirst()}) +self:T({FindGroup=self.CarrierSet:FindGroup(PlayerGroupName)}) +if self.CarrierSet:FindGroup(PlayerGroupName)then +if self.AI_Escorts[PlayerGroupName]then +self.AI_Escorts[PlayerGroupName]:Stop() +self.AI_Escorts[PlayerGroupName]=nil +end +end +end +function AI_ESCORT_DISPATCHER:OnEventBirth(EventData) +local PlayerGroupName=EventData.IniGroupName +local PlayerGroup=EventData.IniGroup +local PlayerUnit=EventData.IniUnit +self:T({EscortAirbase=self.EscortAirbase}) +self:T({PlayerGroupName=PlayerGroupName}) +self:T({PlayerGroup=PlayerGroup}) +self:T({FirstGroup=self.CarrierSet:GetFirst()}) +self:T({FindGroup=self.CarrierSet:FindGroup(PlayerGroupName)}) +if self.CarrierSet:FindGroup(PlayerGroupName)then +if not self.AI_Escorts[PlayerGroupName]then +local LeaderUnit=PlayerUnit +local EscortGroup=self.EscortSpawn:SpawnAtAirbase(self.EscortAirbase,SPAWN.Takeoff.Hot) +self:T({EscortGroup=EscortGroup}) +self:ScheduleOnce(1, +function(EscortGroup) +local EscortSet=SET_GROUP:New() +EscortSet:AddGroup(EscortGroup) +self.AI_Escorts[PlayerGroupName]=AI_ESCORT:New(LeaderUnit,EscortSet,self.EscortName,self.EscortBriefing) +self.AI_Escorts[PlayerGroupName]:FormationTrail(0,100,0) +if EscortGroup:IsHelicopter()then +self.AI_Escorts[PlayerGroupName]:MenusHelicopters() +else +self.AI_Escorts[PlayerGroupName]:MenusAirplanes() +end +self.AI_Escorts[PlayerGroupName]:__Start(0.1) +end,EscortGroup +) +end +end +end +AI_ESCORT_DISPATCHER_REQUEST={ +ClassName="AI_ESCORT_DISPATCHER_REQUEST", +} +AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts={} +function AI_ESCORT_DISPATCHER_REQUEST:New(CarrierSet,EscortSpawn,EscortAirbase,EscortName,EscortBriefing) +local self=BASE:Inherit(self,FSM:New()) +self.CarrierSet=CarrierSet +self.EscortSpawn=EscortSpawn +self.EscortAirbase=EscortAirbase +self.EscortName=EscortName +self.EscortBriefing=EscortBriefing +self:SetStartState("Idle") +self:AddTransition("Monitoring","Monitor","Monitoring") +self:AddTransition("Idle","Start","Monitoring") +self:AddTransition("Monitoring","Stop","Idle") +function self.CarrierSet.OnAfterRemoved(CarrierSet,From,Event,To,CarrierName,Carrier) +self:F({Carrier=Carrier:GetName()}) +end +return self +end +function AI_ESCORT_DISPATCHER_REQUEST:onafterStart(From,Event,To) +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventExit) +self:HandleEvent(EVENTS.Crash,self.OnEventExit) +self:HandleEvent(EVENTS.Dead,self.OnEventExit) +end +function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit(EventData) +local PlayerGroupName=EventData.IniGroupName +local PlayerGroup=EventData.IniGroup +local PlayerUnit=EventData.IniUnit +if self.CarrierSet:FindGroup(PlayerGroupName)then +if self.AI_Escorts[PlayerGroupName]then +self.AI_Escorts[PlayerGroupName]:Stop() +self.AI_Escorts[PlayerGroupName]=nil +end +end +end +function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth(EventData) +local PlayerGroupName=EventData.IniGroupName +local PlayerGroup=EventData.IniGroup +local PlayerUnit=EventData.IniUnit +if self.CarrierSet:FindGroup(PlayerGroupName)then +if not self.AI_Escorts[PlayerGroupName]then +local LeaderUnit=PlayerUnit +self:ScheduleOnce(0.1, +function() +self.AI_Escorts[PlayerGroupName]=AI_ESCORT_REQUEST:New(LeaderUnit,self.EscortSpawn,self.EscortAirbase,self.EscortName,self.EscortBriefing) +self.AI_Escorts[PlayerGroupName]:FormationTrail(0,100,0) +if PlayerGroup:IsHelicopter()then +self.AI_Escorts[PlayerGroupName]:MenusHelicopters() +else +self.AI_Escorts[PlayerGroupName]:MenusAirplanes() +end +self.AI_Escorts[PlayerGroupName]:__Start(0.1) +end +) +end +end +end +AI_CARGO={ +ClassName="AI_CARGO", +Coordinate=nil, +Carrier_Cargo={}, +} +function AI_CARGO:New(Carrier,CargoSet) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New(Carrier)) +self.CargoSet=CargoSet +self.CargoCarrier=Carrier +self:SetStartState("Unloaded") +self:AddTransition("Unloaded","Pickup","Unloaded") +self:AddTransition("*","Load","*") +self:AddTransition("*","Reload","*") +self:AddTransition("*","Board","*") +self:AddTransition("*","Loaded","Loaded") +self:AddTransition("Loaded","PickedUp","Loaded") +self:AddTransition("Loaded","Deploy","*") +self:AddTransition("*","Unload","*") +self:AddTransition("*","Unboard","*") +self:AddTransition("*","Unloaded","Unloaded") +self:AddTransition("Unloaded","Deployed","Unloaded") +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +CarrierUnit:SetCargoBayWeightLimit() +end +self.Transporting=false +self.Relocating=false +return self +end +function AI_CARGO:IsTransporting() +return self.Transporting==true +end +function AI_CARGO:IsRelocating() +return self.Relocating==true +end +function AI_CARGO:onafterPickup(APC,From,Event,To,Coordinate,Speed,Height,PickupZone) +self.Transporting=false +self.Relocating=true +end +function AI_CARGO:onafterDeploy(APC,From,Event,To,Coordinate,Speed,Height,DeployZone) +self.Relocating=false +self.Transporting=true +end +function AI_CARGO:onbeforeLoad(Carrier,From,Event,To,PickupZone) +self:F({Carrier,From,Event,To}) +local Boarding=false +local LoadInterval=2 +local LoadDelay=1 +local Carrier_List={} +local Carrier_Weight={} +if Carrier and Carrier:IsAlive()then +self.Carrier_Cargo={} +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +local CargoBayFreeWeight=CarrierUnit:GetCargoBayFreeWeight() +self:F({CargoBayFreeWeight=CargoBayFreeWeight}) +Carrier_List[#Carrier_List+1]=CarrierUnit +Carrier_Weight[CarrierUnit]=CargoBayFreeWeight +end +local Carrier_Count=#Carrier_List +local Carrier_Index=1 +local Loaded=false +for _,Cargo in UTILS.spairs(self.CargoSet:GetSet(),function(t,a,b)return t[a]:GetWeight()>t[b]:GetWeight()end)do +local Cargo=Cargo +self:F({IsUnLoaded=Cargo:IsUnLoaded(),IsDeployed=Cargo:IsDeployed(),Cargo:GetName(),Carrier:GetName()}) +for Carrier_Loop=1,#Carrier_List do +local CarrierUnit=Carrier_List[Carrier_Index] +Carrier_Index=Carrier_Index+1 +if Carrier_Index>Carrier_Count then +Carrier_Index=1 +end +if Cargo:IsUnLoaded()and not Cargo:IsDeployed()then +if Cargo:IsInLoadRadius(CarrierUnit:GetCoordinate())then +self:F({"In radius",CarrierUnit:GetName()}) +local CargoWeight=Cargo:GetWeight() +local CarrierSpace=Carrier_Weight[CarrierUnit] +if CarrierSpace>CargoWeight then +Carrier:RouteStop() +Cargo:__Board(-LoadDelay,CarrierUnit) +self:__Board(LoadDelay,Cargo,CarrierUnit,PickupZone) +LoadDelay=LoadDelay+Cargo:GetCount()*LoadInterval +self.Carrier_Cargo[Cargo]=CarrierUnit +Boarding=true +Carrier_Weight[CarrierUnit]=Carrier_Weight[CarrierUnit]-CargoWeight +Loaded=true +break +else +self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space",tostring(CarrierUnit:GetName()),CargoWeight,CarrierSpace)) +end +end +end +end +end +if not Loaded==true then +self.Relocating=false +end +end +return Boarding +end +function AI_CARGO:onbeforeReload(Carrier,From,Event,To) +self:F({Carrier,From,Event,To}) +local Boarding=false +local LoadInterval=2 +local LoadDelay=1 +local Carrier_List={} +local Carrier_Weight={} +if Carrier and Carrier:IsAlive()then +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +Carrier_List[#Carrier_List+1]=CarrierUnit +end +local Carrier_Count=#Carrier_List +local Carrier_Index=1 +local Loaded=false +for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +self:F({IsUnLoaded=Cargo:IsUnLoaded(),IsDeployed=Cargo:IsDeployed(),Cargo:GetName(),Carrier:GetName()}) +for Carrier_Loop=1,#Carrier_List do +local CarrierUnit=Carrier_List[Carrier_Index] +Carrier_Index=Carrier_Index+1 +if Carrier_Index>Carrier_Count then +Carrier_Index=1 +end +if Cargo:IsUnLoaded()and not Cargo:IsDeployed()then +Carrier:RouteStop() +Cargo:__Board(-LoadDelay,CarrierUnit) +self:__Board(LoadDelay,Cargo,CarrierUnit) +LoadDelay=LoadDelay+Cargo:GetCount()*LoadInterval +self.Carrier_Cargo[Cargo]=CarrierUnit +Boarding=true +Loaded=true +end +end +end +if not Loaded==true then +self.Relocating=false +end +end +return Boarding +end +function AI_CARGO:onafterBoard(Carrier,From,Event,To,Cargo,CarrierUnit,PickupZone) +self:F({Carrier,From,Event,To,Cargo,CarrierUnit:GetName()}) +if Carrier and Carrier:IsAlive()then +self:F({IsLoaded=Cargo:IsLoaded(),Cargo:GetName(),Carrier:GetName()}) +if not Cargo:IsLoaded()and not Cargo:IsDestroyed()then +self:__Board(-10,Cargo,CarrierUnit,PickupZone) +return +end +end +self:__Loaded(0.1,Cargo,CarrierUnit,PickupZone) +end +function AI_CARGO:onafterLoaded(Carrier,From,Event,To,Cargo,PickupZone) +self:F({Carrier,From,Event,To}) +local Loaded=true +if Carrier and Carrier:IsAlive()then +for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +self:F({IsLoaded=Cargo:IsLoaded(),IsDestroyed=Cargo:IsDestroyed(),Cargo:GetName(),Carrier:GetName()}) +if not Cargo:IsLoaded()and not Cargo:IsDestroyed()then +Loaded=false +end +end +end +if Loaded then +self:__PickedUp(0.1,PickupZone) +end +end +function AI_CARGO:onafterPickedUp(Carrier,From,Event,To,PickupZone) +self:F({Carrier,From,Event,To}) +Carrier:RouteResume() +local HasCargo=false +if Carrier and Carrier:IsAlive()then +for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do +HasCargo=true +break +end +end +self.Relocating=false +if HasCargo then +self:F("Transporting") +self.Transporting=true +end +end +function AI_CARGO:onafterUnload(Carrier,From,Event,To,DeployZone,Defend) +self:F({Carrier,From,Event,To,DeployZone,Defend=Defend}) +local UnboardInterval=5 +local UnboardDelay=5 +if Carrier and Carrier:IsAlive()then +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +Carrier:RouteStop() +for _,Cargo in pairs(CarrierUnit:GetCargo())do +self:F({Cargo=Cargo:GetName(),Isloaded=Cargo:IsLoaded()}) +if Cargo:IsLoaded()then +Cargo:__UnBoard(UnboardDelay) +UnboardDelay=UnboardDelay+Cargo:GetCount()*UnboardInterval +self:__Unboard(UnboardDelay,Cargo,CarrierUnit,DeployZone,Defend) +if not Defend==true then +Cargo:SetDeployed(true) +end +end +end +end +end +end +function AI_CARGO:onafterUnboard(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) +self:F({Carrier,From,Event,To,Cargo:GetName(),DeployZone=DeployZone,Defend=Defend}) +if Carrier and Carrier:IsAlive()then +if not Cargo:IsUnLoaded()then +self:__Unboard(10,Cargo,CarrierUnit,DeployZone,Defend) +return +end +end +self:Unloaded(Cargo,CarrierUnit,DeployZone,Defend) +end +function AI_CARGO:onafterUnloaded(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) +self:F({Carrier,From,Event,To,Cargo:GetName(),DeployZone=DeployZone,Defend=Defend}) +local AllUnloaded=true +if Carrier and Carrier:IsAlive()then +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +local IsEmpty=CarrierUnit:IsCargoEmpty() +self:T({IsEmpty=IsEmpty}) +if not IsEmpty then +AllUnloaded=false +break +end +end +if AllUnloaded==true then +if DeployZone==true then +self.Carrier_Cargo={} +end +self.CargoCarrier=Carrier +end +end +if AllUnloaded==true then +self:__Deployed(5,DeployZone,Defend) +end +end +function AI_CARGO:onafterDeployed(Carrier,From,Event,To,DeployZone,Defend) +self:F({Carrier,From,Event,To,DeployZone=DeployZone,Defend=Defend}) +if not Defend==true then +self.Transporting=false +else +self:F("Defending") +end +end +AI_CARGO_APC={ +ClassName="AI_CARGO_APC", +Coordinate=nil, +} +function AI_CARGO_APC:New(APC,CargoSet,CombatRadius) +local self=BASE:Inherit(self,AI_CARGO:New(APC,CargoSet)) +self:AddTransition("*","Monitor","*") +self:AddTransition("*","Follow","Following") +self:AddTransition("*","Guard","Unloaded") +self:AddTransition("*","Home","*") +self:AddTransition("*","Reload","Boarding") +self:AddTransition("*","Deployed","*") +self:AddTransition("*","PickedUp","*") +self:AddTransition("*","Destroyed","Destroyed") +self:SetCombatRadius(CombatRadius) +self:SetCarrier(APC) +return self +end +function AI_CARGO_APC:SetCarrier(CargoCarrier) +self.CargoCarrier=CargoCarrier +self.CargoCarrier:SetState(self.CargoCarrier,"AI_CARGO_APC",self) +CargoCarrier:HandleEvent(EVENTS.Dead) +function CargoCarrier:OnEventDead(EventData) +self:F({"dead"}) +local AICargoTroops=self:GetState(self,"AI_CARGO_APC") +self:F({AICargoTroops=AICargoTroops}) +if AICargoTroops then +self:F({}) +if not AICargoTroops:Is("Loaded")then +AICargoTroops:Destroyed() +end +end +end +self.Zone=ZONE_UNIT:New(self.CargoCarrier:GetName().."-Zone",self.CargoCarrier,self.CombatRadius) +self.Coalition=self.CargoCarrier:GetCoalition() +self:SetControllable(CargoCarrier) +self:Guard() +return self +end +function AI_CARGO_APC:SetOffRoad(Offroad,Formation) +self:SetPickupOffRoad(Offroad,Formation) +self:SetDeployOffRoad(Offroad,Formation) +return self +end +function AI_CARGO_APC:SetPickupOffRoad(Offroad,Formation) +self.pickupOffroad=Offroad +self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad +return self +end +function AI_CARGO_APC:SetDeployOffRoad(Offroad,Formation) +self.deployOffroad=Offroad +self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad +return self +end +function AI_CARGO_APC:FindCarrier(Coordinate,Radius) +local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius) +CoordinateZone:Scan({Object.Category.UNIT}) +for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do +local NearUnit=UNIT:Find(DCSUnit) +self:F({NearUnit=NearUnit}) +if not NearUnit:GetState(NearUnit,"AI_CARGO_APC")then +local Attributes=NearUnit:GetDesc() +self:F({Desc=Attributes}) +if NearUnit:HasAttribute("Trucks")then +return NearUnit:GetGroup() +end +end +end +return nil +end +function AI_CARGO_APC:SetCombatRadius(CombatRadius) +self.CombatRadius=CombatRadius or 0 +if self.CombatRadius>0 then +self:__Monitor(-5) +end +return self +end +function AI_CARGO_APC:FollowToCarrier(Me,APCUnit,CargoGroup) +local InfantryGroup=CargoGroup:GetGroup() +self:F({self=self:GetClassNameAndID(),InfantryGroup=InfantryGroup:GetName()}) +if APCUnit:IsAlive()then +if InfantryGroup:IsPartlyInZone(ZONE_UNIT:New("Radius",APCUnit,25))then +Me:Guard() +else +self:F({InfantryGroup=InfantryGroup:GetName()}) +if InfantryGroup:IsAlive()then +self:F({InfantryGroup=InfantryGroup:GetName()}) +local Waypoints={} +local FromCoord=InfantryGroup:GetCoordinate() +local FromGround=FromCoord:WaypointGround(10,"Diamond") +self:F({FromGround=FromGround}) +table.insert(Waypoints,FromGround) +local ToCoord=APCUnit:GetCoordinate():GetRandomCoordinateInRadius(10,5) +local ToGround=ToCoord:WaypointGround(10,"Diamond") +self:F({ToGround=ToGround}) +table.insert(Waypoints,ToGround) +local TaskRoute=InfantryGroup:TaskFunction("AI_CARGO_APC.FollowToCarrier",Me,APCUnit,CargoGroup) +self:F({Waypoints=Waypoints}) +local Waypoint=Waypoints[#Waypoints] +InfantryGroup:SetTaskWaypoint(Waypoint,TaskRoute) +InfantryGroup:Route(Waypoints,1) +end +end +end +end +function AI_CARGO_APC:onafterMonitor(APC,From,Event,To) +self:F({APC,From,Event,To,IsTransporting=self:IsTransporting()}) +if self.CombatRadius>0 then +if APC and APC:IsAlive()then +if self.CarrierCoordinate then +if self:IsTransporting()==true then +local Coordinate=APC:GetCoordinate() +if self:Is("Unloaded")or self:Is("Loaded")then +self.Zone:Scan({Object.Category.UNIT}) +if self.Zone:IsAllInZoneOfCoalition(self.Coalition)then +if self:Is("Unloaded")then +self:Reload() +end +else +if self:Is("Loaded")then +self:__Unload(1,nil,true) +else +if self:Is("Unloaded")then +end +self:F("I am here"..self:GetCurrentState()) +if self:Is("Following")then +for Cargo,APCUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +local APCUnit=APCUnit +if Cargo:IsAlive()then +if not Cargo:IsNear(APCUnit,40)then +APCUnit:RouteStop() +self.CarrierStopped=true +else +if self.CarrierStopped then +if Cargo:IsNear(APCUnit,25)then +APCUnit:RouteResume() +self.CarrierStopped=nil +end +end +end +end +end +end +end +end +end +end +end +self.CarrierCoordinate=APC:GetCoordinate() +end +self:__Monitor(-5) +end +end +function AI_CARGO_APC:onafterFollow(APC,From,Event,To) +self:F({APC,From,Event,To}) +self:F("Follow") +if APC and APC:IsAlive()then +for Cargo,APCUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +if Cargo:IsUnLoaded()then +self:FollowToCarrier(self,APCUnit,Cargo) +APCUnit:RouteResume() +end +end +end +end +function AI_CARGO_APC._Pickup(APC,self,Coordinate,Speed,PickupZone) +APC:F({"AI_CARGO_APC._Pickup:",APC:GetName()}) +if APC:IsAlive()then +self:Load(PickupZone) +end +end +function AI_CARGO_APC._Deploy(APC,self,Coordinate,DeployZone) +APC:F({"AI_CARGO_APC._Deploy:",APC}) +if APC:IsAlive()then +self:Unload(DeployZone) +end +end +function AI_CARGO_APC:onafterPickup(APC,From,Event,To,Coordinate,Speed,Height,PickupZone) +if APC and APC:IsAlive()then +if Coordinate then +self.RoutePickup=true +local _speed=Speed or APC:GetSpeedMax()*0.5 +local Waypoints={} +if self.pickupOffroad then +Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed,self.pickupFormation) +Waypoints[2]=Coordinate:WaypointGround(_speed,self.pickupFormation,DCSTasks) +else +Waypoints=APC:TaskGroundOnRoad(Coordinate,_speed,ENUMS.Formation.Vehicle.OffRoad,true) +end +local TaskFunction=APC:TaskFunction("AI_CARGO_APC._Pickup",self,Coordinate,Speed,PickupZone) +local Waypoint=Waypoints[#Waypoints] +APC:SetTaskWaypoint(Waypoint,TaskFunction) +APC:Route(Waypoints,1) +else +AI_CARGO_APC._Pickup(APC,self,Coordinate,Speed,PickupZone) +end +self:GetParent(self,AI_CARGO_APC).onafterPickup(self,APC,From,Event,To,Coordinate,Speed,Height,PickupZone) +end +end +function AI_CARGO_APC:onafterDeploy(APC,From,Event,To,Coordinate,Speed,Height,DeployZone) +if APC and APC:IsAlive()then +self.RouteDeploy=true +local speedmax=APC:GetSpeedMax() +local _speed=Speed or speedmax*0.5 +_speed=math.min(_speed,speedmax) +local Waypoints={} +if self.deployOffroad then +Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed,self.deployFormation) +Waypoints[2]=Coordinate:WaypointGround(_speed,self.deployFormation,DCSTasks) +else +Waypoints=APC:TaskGroundOnRoad(Coordinate,_speed,ENUMS.Formation.Vehicle.OffRoad,true) +end +local TaskFunction=APC:TaskFunction("AI_CARGO_APC._Deploy",self,Coordinate,DeployZone) +local Waypoint=Waypoints[#Waypoints] +APC:SetTaskWaypoint(Waypoint,TaskFunction) +APC:Route(Waypoints,1) +self:GetParent(self,AI_CARGO_APC).onafterDeploy(self,APC,From,Event,To,Coordinate,Speed,Height,DeployZone) +end +end +function AI_CARGO_APC:onafterUnloaded(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) +self:F({Carrier,From,Event,To,DeployZone=DeployZone,Defend=Defend}) +self:GetParent(self,AI_CARGO_APC).onafterUnloaded(self,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) +if Defend==true then +self.Zone:Scan({Object.Category.UNIT}) +if not self.Zone:IsAllInZoneOfCoalition(self.Coalition)then +local AttackUnits=self.Zone:GetScannedUnits() +local Move={} +local CargoGroup=Cargo.CargoObject +Move[#Move+1]=CargoGroup:GetCoordinate():WaypointGround(70,"Custom") +for UnitId,AttackUnit in pairs(AttackUnits)do +local MooseUnit=UNIT:Find(AttackUnit) +if MooseUnit:GetCoalition()~=CargoGroup:GetCoalition()then +Move[#Move+1]=MooseUnit:GetCoordinate():WaypointGround(70,"Line abreast") +self:F({MooseUnit=MooseUnit:GetName(),CargoGroup=CargoGroup:GetName()}) +end +end +CargoGroup:RoutePush(Move,0.1) +end +end +end +function AI_CARGO_APC:onafterDeployed(APC,From,Event,To,DeployZone,Defend) +self:F({APC,From,Event,To,DeployZone=DeployZone,Defend=Defend}) +self:__Guard(0.1) +self:GetParent(self,AI_CARGO_APC).onafterDeployed(self,APC,From,Event,To,DeployZone,Defend) +end +function AI_CARGO_APC:onafterHome(APC,From,Event,To,Coordinate,Speed,Height,HomeZone) +if APC and APC:IsAlive()~=nil then +self.RouteHome=true +Speed=Speed or APC:GetSpeedMax()*0.5 +local Waypoints=APC:TaskGroundOnRoad(Coordinate,Speed,"Line abreast",true) +self:F({Waypoints=Waypoints}) +local Waypoint=Waypoints[#Waypoints] +APC:Route(Waypoints,1) +end +end +AI_CARGO_HELICOPTER={ +ClassName="AI_CARGO_HELICOPTER", +Coordinate=nil, +} +AI_CARGO_QUEUE={} +function AI_CARGO_HELICOPTER:New(Helicopter,CargoSet) +local self=BASE:Inherit(self,AI_CARGO:New(Helicopter,CargoSet)) +self.Zone=ZONE_GROUP:New(Helicopter:GetName(),Helicopter,300) +self:SetStartState("Unloaded") +self:AddTransition("Unloaded","Pickup","Unloaded") +self:AddTransition("*","Landed","*") +self:AddTransition("*","Load","*") +self:AddTransition("*","Loaded","Loaded") +self:AddTransition("Loaded","PickedUp","Loaded") +self:AddTransition("Loaded","Deploy","*") +self:AddTransition("*","Queue","*") +self:AddTransition("*","Orbit","*") +self:AddTransition("*","Destroyed","*") +self:AddTransition("*","Unload","*") +self:AddTransition("*","Unloaded","Unloaded") +self:AddTransition("Unloaded","Deployed","Unloaded") +self:AddTransition("*","Home","*") +Helicopter:HandleEvent(EVENTS.Crash, +function(Helicopter,EventData) +AI_CARGO_QUEUE[Helicopter]=nil +end +) +Helicopter:HandleEvent(EVENTS.Land, +function(Helicopter,EventData) +self:ScheduleOnce(60, +function(Helicopter) +AI_CARGO_QUEUE[Helicopter]=nil +end,Helicopter +) +end +) +self:SetCarrier(Helicopter) +self.landingspeed=15 +self.landingheight=5.5 +return self +end +function AI_CARGO_HELICOPTER:SetCarrier(Helicopter) +local AICargo=self +self.Helicopter=Helicopter +self.Helicopter:SetState(self.Helicopter,"AI_CARGO_HELICOPTER",self) +self.RoutePickup=false +self.RouteDeploy=false +Helicopter:HandleEvent(EVENTS.Dead) +Helicopter:HandleEvent(EVENTS.Hit) +Helicopter:HandleEvent(EVENTS.Land) +function Helicopter:OnEventDead(EventData) +local AICargoTroops=self:GetState(self,"AI_CARGO_HELICOPTER") +self:F({AICargoTroops=AICargoTroops}) +if AICargoTroops then +self:F({}) +if not AICargoTroops:Is("Loaded")then +AICargoTroops:Destroyed() +end +end +end +function Helicopter:OnEventLand(EventData) +AICargo:Landed() +end +self.Coalition=self.Helicopter:GetCoalition() +self:SetControllable(Helicopter) +return self +end +function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed,height) +local _speed=speed or 15 +local _height=height or 5.5 +self.landingheight=_height +self.landingspeed=_speed +return self +end +function AI_CARGO_HELICOPTER:onafterLanded(Helicopter,From,Event,To) +self:F({From,Event,To}) +Helicopter:F({Name=Helicopter:GetName()}) +if Helicopter and Helicopter:IsAlive()then +self:T({Helicopter:GetName(),Height=Helicopter:GetHeight(true),Velocity=Helicopter:GetVelocityKMH()}) +if self.RoutePickup==true then +if Helicopter:GetHeight(true)<=self.landingheight then +self:Load(self.PickupZone) +self.RoutePickup=false +end +end +if self.RouteDeploy==true then +if Helicopter:GetHeight(true)<=self.landingheight then +self:Unload(self.DeployZone) +self.RouteDeploy=false +end +end +end +end +function AI_CARGO_HELICOPTER:onafterQueue(Helicopter,From,Event,To,Coordinate,Speed,DeployZone) +self:F({From,Event,To,Coordinate,Speed,DeployZone}) +local HelicopterInZone=false +if Helicopter and Helicopter:IsAlive()==true then +local Distance=Coordinate:DistanceFromPointVec2(Helicopter:GetCoordinate()) +if Distance>2000 then +self:__Queue(-10,Coordinate,Speed,DeployZone) +else +local ZoneFree=true +for Helicopter,ZoneQueue in pairs(AI_CARGO_QUEUE)do +local ZoneQueue=ZoneQueue +if ZoneQueue:IsCoordinateInZone(Coordinate)then +ZoneFree=false +end +end +self:F({ZoneFree=ZoneFree}) +if ZoneFree==true then +local ZoneQueue=ZONE_RADIUS:New(Helicopter:GetName(),Coordinate:GetVec2(),100) +AI_CARGO_QUEUE[Helicopter]=ZoneQueue +local Route={} +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+50 +local WaypointTo=CoordinateTo:WaypointAir( +"RADIO", +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, +50, +true +) +Route[#Route+1]=WaypointTo +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2()) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,0) +self.DeployZone=DeployZone +else +self:__Queue(-10,Coordinate,Speed,DeployZone) +end +end +else +AI_CARGO_QUEUE[Helicopter]=nil +end +end +function AI_CARGO_HELICOPTER:onafterOrbit(Helicopter,From,Event,To,Coordinate) +self:F({From,Event,To,Coordinate}) +if Helicopter and Helicopter:IsAlive()then +local Route={} +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+50 +local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,50,true) +Route[#Route+1]=WaypointTo +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,80),150,CoordinateTo:GetRandomCoordinateInRadius(800,500)) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,0) +end +end +function AI_CARGO_HELICOPTER:onafterDeployed(Helicopter,From,Event,To,DeployZone) +self:F({From,Event,To,DeployZone=DeployZone}) +self:Orbit(Helicopter:GetCoordinate(),50) +self:ScheduleOnce(30, +function(Helicopter) +AI_CARGO_QUEUE[Helicopter]=nil +end,Helicopter +) +self:GetParent(self,AI_CARGO_HELICOPTER).onafterDeployed(self,Helicopter,From,Event,To,DeployZone) +end +function AI_CARGO_HELICOPTER:onafterPickup(Helicopter,From,Event,To,Coordinate,Speed,Height,PickupZone) +self:F({Coordinate,Speed,Height,PickupZone}) +if Helicopter and Helicopter:IsAlive()~=nil then +Helicopter:Activate() +self.RoutePickup=true +Coordinate.y=Height +local _speed=Speed or Helicopter:GetSpeedMax()*0.5 +local Route={} +local CoordinateFrom=Helicopter:GetCoordinate() +local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true) +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+50 +local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true) +Route[#Route+1]=WaypointFrom +Route[#Route+1]=WaypointTo +Helicopter:WayPointInitialize(Route) +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2()) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,1) +self.PickupZone=PickupZone +self:GetParent(self,AI_CARGO_HELICOPTER).onafterPickup(self,Helicopter,From,Event,To,Coordinate,Speed,Height,PickupZone) +end +end +function AI_CARGO_HELICOPTER:_Deploy(AICargoHelicopter,Coordinate,DeployZone) +AICargoHelicopter:__Queue(-10,Coordinate,100,DeployZone) +end +function AI_CARGO_HELICOPTER:onafterDeploy(Helicopter,From,Event,To,Coordinate,Speed,Height,DeployZone) +self:F({From,Event,To,Coordinate,Speed,Height,DeployZone}) +if Helicopter and Helicopter:IsAlive()~=nil then +self.RouteDeploy=true +local Route={} +Coordinate.y=Height +local _speed=Speed or Helicopter:GetSpeedMax()*0.5 +local CoordinateFrom=Helicopter:GetCoordinate() +local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true) +Route[#Route+1]=WaypointFrom +Route[#Route+1]=WaypointFrom +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+50 +local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true) +Route[#Route+1]=WaypointTo +Route[#Route+1]=WaypointTo +Helicopter:WayPointInitialize(Route) +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskFunction("AI_CARGO_HELICOPTER._Deploy",self,Coordinate,DeployZone) +Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,100),_speed,CoordinateTo:GetRandomCoordinateInRadius(800,500)) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,0) +self:GetParent(self,AI_CARGO_HELICOPTER).onafterDeploy(self,Helicopter,From,Event,To,Coordinate,Speed,Height,DeployZone) +end +end +function AI_CARGO_HELICOPTER:onafterHome(Helicopter,From,Event,To,Coordinate,Speed,Height,HomeZone) +self:F({From,Event,To,Coordinate,Speed,Height}) +if Helicopter and Helicopter:IsAlive()~=nil then +self.RouteHome=true +local Route={} +Height=Height or 50 +Speed=Speed or Helicopter:GetSpeedMax()*0.5 +local CoordinateFrom=Helicopter:GetCoordinate() +local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true) +Route[#Route+1]=WaypointFrom +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+Height +local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true) +Route[#Route+1]=WaypointTo +Helicopter:WayPointInitialize(Route) +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2()) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,0) +end +end +AI_CARGO_AIRPLANE={ +ClassName="AI_CARGO_AIRPLANE", +Coordinate=nil, +} +function AI_CARGO_AIRPLANE:New(Airplane,CargoSet) +local self=BASE:Inherit(self,AI_CARGO:New(Airplane,CargoSet)) +self:AddTransition("*","Landed","*") +self:AddTransition("*","Home","*") +self:AddTransition("*","Destroyed","Destroyed") +self:SetCarrier(Airplane) +return self +end +function AI_CARGO_AIRPLANE:SetCarrier(Airplane) +local AICargo=self +self.Airplane=Airplane +self.Airplane:SetState(self.Airplane,"AI_CARGO_AIRPLANE",self) +self.RoutePickup=false +self.RouteDeploy=false +Airplane:HandleEvent(EVENTS.Dead) +Airplane:HandleEvent(EVENTS.Hit) +Airplane:HandleEvent(EVENTS.EngineShutdown) +function Airplane:OnEventDead(EventData) +local AICargoTroops=self:GetState(self,"AI_CARGO_AIRPLANE") +self:F({AICargoTroops=AICargoTroops}) +if AICargoTroops then +self:F({}) +if not AICargoTroops:Is("Loaded")then +AICargoTroops:Destroyed() +end +end +end +function Airplane:OnEventHit(EventData) +local AICargoTroops=self:GetState(self,"AI_CARGO_AIRPLANE") +if AICargoTroops then +self:F({OnHitLoaded=AICargoTroops:Is("Loaded")}) +if AICargoTroops:Is("Loaded")or AICargoTroops:Is("Boarding")then +AICargoTroops:Unload() +end +end +end +function Airplane:OnEventEngineShutdown(EventData) +AICargo.Relocating=false +AICargo:Landed(self.Airplane) +end +self.Coalition=self.Airplane:GetCoalition() +self:SetControllable(Airplane) +return self +end +function AI_CARGO_AIRPLANE:FindCarrier(Coordinate,Radius) +local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius) +CoordinateZone:Scan({Object.Category.UNIT}) +for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do +local NearUnit=UNIT:Find(DCSUnit) +self:F({NearUnit=NearUnit}) +if not NearUnit:GetState(NearUnit,"AI_CARGO_AIRPLANE")then +local Attributes=NearUnit:GetDesc() +self:F({Desc=Attributes}) +if NearUnit:HasAttribute("Trucks")then +self:SetCarrier(NearUnit) +break +end +end +end +end +function AI_CARGO_AIRPLANE:onafterLanded(Airplane,From,Event,To) +self:F({Airplane,From,Event,To}) +if Airplane and Airplane:IsAlive()~=nil then +if self.RoutePickup==true then +self:Load(self.PickupZone) +end +if self.RouteDeploy==true then +self:Unload() +self.RouteDeploy=false +end +end +end +function AI_CARGO_AIRPLANE:onafterPickup(Airplane,From,Event,To,Coordinate,Speed,Height,PickupZone) +if Airplane and Airplane:IsAlive()then +local airbasepickup=Coordinate:GetClosestAirbase() +self.PickupZone=PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName()) +local ClosestAirbase,DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase() +if Airplane:InAir()then +self.Airbase=nil +else +self.Airbase=ClosestAirbase +end +local Airbase=self.PickupZone:GetAirbase() +local Dist=Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate()) +if Airplane:InAir()or Dist>500 then +self:Route(Airplane,Airbase,Speed,Height) +self.Airbase=Airbase +self.RoutePickup=true +else +self.RoutePickup=true +self:Landed() +end +self:GetParent(self,AI_CARGO_AIRPLANE).onafterPickup(self,Airplane,From,Event,To,Coordinate,Speed,Height,self.PickupZone) +end +end +function AI_CARGO_AIRPLANE:onafterDeploy(Airplane,From,Event,To,Coordinate,Speed,Height,DeployZone) +if Airplane and Airplane:IsAlive()~=nil then +local Airbase=Coordinate:GetClosestAirbase() +if DeployZone then +Airbase=DeployZone:GetAirbase() +end +if Airplane:IsAlive()==false then +Airplane:SetCommand({id='Start',params={}}) +end +self:Route(Airplane,Airbase,Speed,Height) +self.RouteDeploy=true +self.Airbase=Airbase +self:GetParent(self,AI_CARGO_AIRPLANE).onafterDeploy(self,Airplane,From,Event,To,Coordinate,Speed,Height,DeployZone) +end +end +function AI_CARGO_AIRPLANE:onafterUnload(Airplane,From,Event,To,DeployZone) +local UnboardInterval=10 +local UnboardDelay=10 +if Airplane and Airplane:IsAlive()then +for _,AirplaneUnit in pairs(Airplane:GetUnits())do +local Cargos=AirplaneUnit:GetCargo() +for CargoID,Cargo in pairs(Cargos)do +local Angle=180 +local CargoCarrierHeading=Airplane:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +self:T({CargoCarrierHeading,CargoDeployHeading}) +local CargoDeployCoordinate=Airplane:GetPointVec2():Translate(150,CargoDeployHeading) +Cargo:__UnBoard(UnboardDelay,CargoDeployCoordinate) +UnboardDelay=UnboardDelay+UnboardInterval +Cargo:SetDeployed(true) +self:__Unboard(UnboardDelay,Cargo,AirplaneUnit,DeployZone) +end +end +end +end +function AI_CARGO_AIRPLANE:Route(Airplane,Airbase,Speed,Height,Uncontrolled) +if Airplane and Airplane:IsAlive()then +local Takeoff=SPAWN.Takeoff.Cold +local Template=Airplane:GetTemplate() +if Template==nil then +return +end +local Points={} +local AirbasePointVec2=Airbase:GetPointVec2() +local ToWaypoint=AirbasePointVec2:WaypointAir(COORDINATE.WaypointAltType.BARO,"Land","Landing",Speed or Airplane:GetSpeedMax()*0.8,true,Airbase) +if self.Airbase then +Template.route.points[2]=ToWaypoint +Airplane:RespawnAtCurrentAirbase(Template,Takeoff,Uncontrolled) +else +local GroupPoint=Airplane:GetVec2() +local FromWaypoint={} +FromWaypoint.x=GroupPoint.x +FromWaypoint.y=GroupPoint.y +FromWaypoint.type="Turning Point" +FromWaypoint.action="Turning Point" +FromWaypoint.speed=Airplane:GetSpeedMax()*0.8 +Points[1]=FromWaypoint +Points[2]=ToWaypoint +local PointVec3=Airplane:GetPointVec3() +Template.x=PointVec3.x +Template.y=PointVec3.z +Template.route.points=Points +local GroupSpawned=Airplane:Respawn(Template) +end +end +end +function AI_CARGO_AIRPLANE:onafterHome(Airplane,From,Event,To,Coordinate,Speed,Height,HomeZone) +if Airplane and Airplane:IsAlive()then +self.RouteHome=true +local HomeBase=HomeZone:GetAirbase() +self.Airbase=HomeBase +self:Route(Airplane,HomeBase,Speed,Height) +end +end +AI_CARGO_SHIP={ +ClassName="AI_CARGO_SHIP", +Coordinate=nil +} +function AI_CARGO_SHIP:New(Ship,CargoSet,CombatRadius,ShippingLane) +local self=BASE:Inherit(self,AI_CARGO:New(Ship,CargoSet)) +self:AddTransition("*","Monitor","*") +self:AddTransition("*","Destroyed","Destroyed") +self:AddTransition("*","Home","*") +self:SetCombatRadius(0) +self:SetShippingLane(ShippingLane) +self:SetCarrier(Ship) +return self +end +function AI_CARGO_SHIP:SetCarrier(CargoCarrier) +self.CargoCarrier=CargoCarrier +self.CargoCarrier:SetState(self.CargoCarrier,"AI_CARGO_SHIP",self) +CargoCarrier:HandleEvent(EVENTS.Dead) +function CargoCarrier:OnEventDead(EventData) +self:F({"dead"}) +local AICargoTroops=self:GetState(self,"AI_CARGO_SHIP") +self:F({AICargoTroops=AICargoTroops}) +if AICargoTroops then +self:F({}) +if not AICargoTroops:Is("Loaded")then +AICargoTroops:Destroyed() +end +end +end +self.Zone=ZONE_UNIT:New(self.CargoCarrier:GetName().."-Zone",self.CargoCarrier,self.CombatRadius) +self.Coalition=self.CargoCarrier:GetCoalition() +self:SetControllable(CargoCarrier) +return self +end +function AI_CARGO_SHIP:FindCarrier(Coordinate,Radius) +local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius) +CoordinateZone:Scan({Object.Category.UNIT}) +for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do +local NearUnit=UNIT:Find(DCSUnit) +self:F({NearUnit=NearUnit}) +if not NearUnit:GetState(NearUnit,"AI_CARGO_SHIP")then +local Attributes=NearUnit:GetDesc() +self:F({Desc=Attributes}) +if NearUnit:HasAttributes("Trucks")then +return NearUnit:GetGroup() +end +end +end +return nil +end +function AI_CARGO_SHIP:SetShippingLane(ShippingLane) +self.ShippingLane=ShippingLane +return self +end +function AI_CARGO_SHIP:SetCombatRadius(CombatRadius) +self.CombatRadius=CombatRadius or 0 +return self +end +function AI_CARGO_SHIP:FollowToCarrier(Me,ShipUnit,CargoGroup) +local InfantryGroup=CargoGroup:GetGroup() +self:F({self=self:GetClassNameAndID(),InfantryGroup=InfantryGroup:GetName()}) +if ShipUnit:IsAlive()then +if InfantryGroup:IsPartlyInZone(ZONE_UNIT:New("Radius",ShipUnit,1000))then +Me:Guard() +else +self:F({InfantryGroup=InfantryGroup:GetName()}) +if InfantryGroup:IsAlive()then +self:F({InfantryGroup=InfantryGroup:GetName()}) +local Waypoints={} +local FromCoord=InfantryGroup:GetCoordinate() +local FromGround=FromCoord:WaypointGround(10,"Diamond") +self:F({FromGround=FromGround}) +table.insert(Waypoints,FromGround) +local ToCoord=ShipUnit:GetCoordinate():GetRandomCoordinateInRadius(10,5) +local ToGround=ToCoord:WaypointGround(10,"Diamond") +self:F({ToGround=ToGround}) +table.insert(Waypoints,ToGround) +local TaskRoute=InfantryGroup:TaskFunction("AI_CARGO_SHIP.FollowToCarrier",Me,ShipUnit,CargoGroup) +self:F({Waypoints=Waypoints}) +local Waypoint=Waypoints[#Waypoints] +InfantryGroup:SetTaskWaypoint(Waypoint,TaskRoute) +InfantryGroup:Route(Waypoints,1) +end +end +end +end +function AI_CARGO_SHIP:onafterMonitor(Ship,From,Event,To) +self:F({Ship,From,Event,To,IsTransporting=self:IsTransporting()}) +if self.CombatRadius>0 then +if Ship and Ship:IsAlive()then +if self.CarrierCoordinate then +if self:IsTransporting()==true then +local Coordinate=Ship:GetCoordinate() +if self:Is("Unloaded")or self:Is("Loaded")then +self.Zone:Scan({Object.Category.UNIT}) +if self.Zone:IsAllInZoneOfCoalition(self.Coalition)then +if self:Is("Unloaded")then +self:Reload() +end +else +if self:Is("Loaded")then +self:__Unload(1,nil,true) +else +if self:Is("Unloaded")then +end +self:F("I am here"..self:GetCurrentState()) +if self:Is("Following")then +for Cargo,ShipUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +local ShipUnit=ShipUnit +if Cargo:IsAlive()then +if not Cargo:IsNear(ShipUnit,40)then +ShipUnit:RouteStop() +self.CarrierStopped=true +else +if self.CarrierStopped then +if Cargo:IsNear(ShipUnit,25)then +ShipUnit:RouteResume() +self.CarrierStopped=nil +end +end +end +end +end +end +end +end +end +end +end +self.CarrierCoordinate=Ship:GetCoordinate() +end +self:__Monitor(-5) +end +end +function AI_CARGO_SHIP._Pickup(Ship,self,Coordinate,Speed,PickupZone) +Ship:F({"AI_CARGO_Ship._Pickup:",Ship:GetName()}) +if Ship:IsAlive()then +self:Load(PickupZone) +end +end +function AI_CARGO_SHIP._Deploy(Ship,self,Coordinate,DeployZone) +Ship:F({"AI_CARGO_Ship._Deploy:",Ship}) +if Ship:IsAlive()then +self:Unload(DeployZone) +end +end +function AI_CARGO_SHIP:onafterPickup(Ship,From,Event,To,Coordinate,Speed,Height,PickupZone) +if Ship and Ship:IsAlive()then +AI_CARGO_SHIP._Pickup(Ship,self,Coordinate,Speed,PickupZone) +self:GetParent(self,AI_CARGO_SHIP).onafterPickup(self,Ship,From,Event,To,Coordinate,Speed,Height,PickupZone) +end +end +function AI_CARGO_SHIP:onafterDeploy(Ship,From,Event,To,Coordinate,Speed,Height,DeployZone) +if Ship and Ship:IsAlive()then +Speed=Speed or Ship:GetSpeedMax()*0.8 +local lane=self.ShippingLane +if lane then +local Waypoints={} +for i=1,#lane do +local coord=lane[i] +local Waypoint=coord:WaypointGround(_speed) +table.insert(Waypoints,Waypoint) +end +local TaskFunction=Ship:TaskFunction("AI_CARGO_SHIP._Deploy",self,Coordinate,DeployZone) +local Waypoint=Waypoints[#Waypoints] +Ship:SetTaskWaypoint(Waypoint,TaskFunction) +Ship:Route(Waypoints,1) +self:GetParent(self,AI_CARGO_SHIP).onafterDeploy(self,Ship,From,Event,To,Coordinate,Speed,Height,DeployZone) +else +self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") +end +end +end +function AI_CARGO_SHIP:onafterUnload(Ship,From,Event,To,DeployZone,Defend) +self:F({Ship,From,Event,To,DeployZone,Defend=Defend}) +local UnboardInterval=5 +local UnboardDelay=5 +if Ship and Ship:IsAlive()then +for _,ShipUnit in pairs(Ship:GetUnits())do +local ShipUnit=ShipUnit +Ship:RouteStop() +for _,Cargo in pairs(ShipUnit:GetCargo())do +self:F({Cargo=Cargo:GetName(),Isloaded=Cargo:IsLoaded()}) +if Cargo:IsLoaded()then +local unboardCoord=DeployZone:GetRandomPointVec2() +Cargo:__UnBoard(UnboardDelay,unboardCoord,1000) +UnboardDelay=UnboardDelay+Cargo:GetCount()*UnboardInterval +self:__Unboard(UnboardDelay,Cargo,ShipUnit,DeployZone,Defend) +if not Defend==true then +Cargo:SetDeployed(true) +end +end +end +end +end +end +function AI_CARGO_SHIP:onafterHome(Ship,From,Event,To,Coordinate,Speed,Height,HomeZone) +if Ship and Ship:IsAlive()then +self.RouteHome=true +Speed=Speed or Ship:GetSpeedMax()*0.8 +local lane=self.ShippingLane +if lane then +local Waypoints={} +for i=#lane,1,-1 do +local coord=lane[i] +local Waypoint=coord:WaypointGround(_speed) +table.insert(Waypoints,Waypoint) +end +local Waypoint=Waypoints[#Waypoints] +Ship:Route(Waypoints,1) +else +self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") +end +end +end +AI_CARGO_DISPATCHER={ +ClassName="AI_CARGO_DISPATCHER", +AI_Cargo={}, +PickupCargo={} +} +AI_CARGO_DISPATCHER.AI_Cargo={} +AI_CARGO_DISPATCHER.PickupCargo={} +function AI_CARGO_DISPATCHER:New(CarrierSet,CargoSet,PickupZoneSet,DeployZoneSet) +local self=BASE:Inherit(self,FSM:New()) +self.SetCarrier=CarrierSet +self.SetCargo=CargoSet +self.PickupZoneSet=PickupZoneSet +self.DeployZoneSet=DeployZoneSet +self:SetStartState("Idle") +self:AddTransition("Monitoring","Monitor","Monitoring") +self:AddTransition("Idle","Start","Monitoring") +self:AddTransition("Monitoring","Stop","Idle") +self:AddTransition("Monitoring","Pickup","Monitoring") +self:AddTransition("Monitoring","Load","Monitoring") +self:AddTransition("Monitoring","Loading","Monitoring") +self:AddTransition("Monitoring","Loaded","Monitoring") +self:AddTransition("Monitoring","PickedUp","Monitoring") +self:AddTransition("Monitoring","Transport","Monitoring") +self:AddTransition("Monitoring","Deploy","Monitoring") +self:AddTransition("Monitoring","Unload","Monitoring") +self:AddTransition("Monitoring","Unloading","Monitoring") +self:AddTransition("Monitoring","Unloaded","Monitoring") +self:AddTransition("Monitoring","Deployed","Monitoring") +self:AddTransition("Monitoring","Home","Monitoring") +self:SetMonitorTimeInterval(30) +self:SetDeployRadius(500,200) +self.PickupCargo={} +self.CarrierHome={} +function self.SetCarrier.OnAfterRemoved(SetCarrier,From,Event,To,CarrierName,Carrier) +self:F({Carrier=Carrier:GetName()}) +self.PickupCargo[Carrier]=nil +self.CarrierHome[Carrier]=nil +end +return self +end +function AI_CARGO_DISPATCHER:SetMonitorTimeInterval(MonitorTimeInterval) +self.MonitorTimeInterval=MonitorTimeInterval +return self +end +function AI_CARGO_DISPATCHER:SetHomeZone(HomeZone) +self.HomeZone=HomeZone +return self +end +function AI_CARGO_DISPATCHER:SetPickupRadius(OuterRadius,InnerRadius) +OuterRadius=OuterRadius or 0 +InnerRadius=InnerRadius or OuterRadius +self.PickupOuterRadius=OuterRadius +self.PickupInnerRadius=InnerRadius +return self +end +function AI_CARGO_DISPATCHER:SetPickupSpeed(MaxSpeed,MinSpeed) +MaxSpeed=MaxSpeed or 999 +MinSpeed=MinSpeed or MaxSpeed +self.PickupMinSpeed=MinSpeed +self.PickupMaxSpeed=MaxSpeed +return self +end +function AI_CARGO_DISPATCHER:SetDeployRadius(OuterRadius,InnerRadius) +OuterRadius=OuterRadius or 0 +InnerRadius=InnerRadius or OuterRadius +self.DeployOuterRadius=OuterRadius +self.DeployInnerRadius=InnerRadius +return self +end +function AI_CARGO_DISPATCHER:SetDeploySpeed(MaxSpeed,MinSpeed) +MaxSpeed=MaxSpeed or 999 +MinSpeed=MinSpeed or MaxSpeed +self.DeployMinSpeed=MinSpeed +self.DeployMaxSpeed=MaxSpeed +return self +end +function AI_CARGO_DISPATCHER:SetPickupHeight(MaxHeight,MinHeight) +MaxHeight=MaxHeight or 200 +MinHeight=MinHeight or MaxHeight +self.PickupMinHeight=MinHeight +self.PickupMaxHeight=MaxHeight +return self +end +function AI_CARGO_DISPATCHER:SetDeployHeight(MaxHeight,MinHeight) +MaxHeight=MaxHeight or 200 +MinHeight=MinHeight or MaxHeight +self.DeployMinHeight=MinHeight +self.DeployMaxHeight=MaxHeight +return self +end +function AI_CARGO_DISPATCHER:onafterMonitor() +self:F("Carriers") +self.SetCarrier:Flush() +for CarrierGroupName,Carrier in pairs(self.SetCarrier:GetSet())do +local Carrier=Carrier +if Carrier:IsAlive()~=nil then +local AI_Cargo=self.AI_Cargo[Carrier] +if not AI_Cargo then +self.AI_Cargo[Carrier]=self:AICargo(Carrier,self.SetCargo,self.CombatRadius) +AI_Cargo=self.AI_Cargo[Carrier] +function AI_Cargo.OnAfterPickup(AI_Cargo,CarrierGroup,From,Event,To,Coordinate,Speed,Height,PickupZone) +self:Pickup(CarrierGroup,Coordinate,Speed,Height,PickupZone) +end +function AI_Cargo.OnAfterLoad(AI_Cargo,CarrierGroup,From,Event,To,PickupZone) +self:Load(CarrierGroup,PickupZone) +end +function AI_Cargo.OnAfterBoard(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,PickupZone) +self:Loading(CarrierGroup,Cargo,CarrierUnit,PickupZone) +end +function AI_Cargo.OnAfterLoaded(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,PickupZone) +self:Loaded(CarrierGroup,Cargo,CarrierUnit,PickupZone) +end +function AI_Cargo.OnAfterPickedUp(AI_Cargo,CarrierGroup,From,Event,To,PickupZone) +self:PickedUp(CarrierGroup,PickupZone) +self:Transport(CarrierGroup) +end +function AI_Cargo.OnAfterDeploy(AI_Cargo,CarrierGroup,From,Event,To,Coordinate,Speed,Height,DeployZone) +self:Deploy(CarrierGroup,Coordinate,Speed,Height,DeployZone) +end +function AI_Cargo.OnAfterUnload(AI_Cargo,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone) +self:Unloading(Carrier,Cargo,CarrierUnit,DeployZone) +end +function AI_Cargo.OnAfterUnboard(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,DeployZone) +self:Unloading(CarrierGroup,Cargo,CarrierUnit,DeployZone) +end +function AI_Cargo.OnAfterUnloaded(AI_Cargo,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone) +self:Unloaded(Carrier,Cargo,CarrierUnit,DeployZone) +end +function AI_Cargo.OnAfterDeployed(AI_Cargo,Carrier,From,Event,To,DeployZone) +self:Deployed(Carrier,DeployZone) +end +function AI_Cargo.OnAfterHome(AI_Cargo,Carrier,From,Event,To,Coordinate,Speed,Height,HomeZone) +self:Home(Carrier,Coordinate,Speed,Height,HomeZone) +end +end +self:T({Carrier=CarrierGroupName,IsRelocating=AI_Cargo:IsRelocating(),IsTransporting=AI_Cargo:IsTransporting()}) +if AI_Cargo:IsRelocating()==false and AI_Cargo:IsTransporting()==false then +local PickupCargo=nil +local PickupZone=nil +self.SetCargo:Flush() +for CargoName,Cargo in UTILS.spairs(self.SetCargo:GetSet(),function(t,a,b)return t[a]:GetWeight()=Cargo:GetWeight()then +self.PickupCargo[Carrier]=CargoCoordinate +PickupCargo=Cargo +break +else +local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.", +tostring(Cargo:GetName()),Cargo:GetWeight(),LargestLoadCapacity,tostring(Carrier:GetName())) +self:T(text) +end +end +end +end +end +if PickupCargo then +self.CarrierHome[Carrier]=nil +local PickupCoordinate=PickupCargo:GetCoordinate():GetRandomCoordinateInRadius(self.PickupOuterRadius,self.PickupInnerRadius) +AI_Cargo:Pickup(PickupCoordinate,math.random(self.PickupMinSpeed,self.PickupMaxSpeed),math.random(self.PickupMinHeight,self.PickupMaxHeight),PickupZone) +break +else +if self.HomeZone then +if not self.CarrierHome[Carrier]then +self.CarrierHome[Carrier]=true +AI_Cargo:Home(self.HomeZone:GetRandomPointVec2(),math.random(self.PickupMinSpeed,self.PickupMaxSpeed),math.random(self.PickupMinHeight,self.PickupMaxHeight),self.HomeZone) +end +end +end +end +end +end +self:__Monitor(self.MonitorTimeInterval) +end +function AI_CARGO_DISPATCHER:onafterStart(From,Event,To) +self:__Monitor(-1) +end +function AI_CARGO_DISPATCHER:onafterTransport(From,Event,To,Carrier,Cargo) +if self.DeployZoneSet then +if self.AI_Cargo[Carrier]:IsTransporting()==true then +local DeployZone=self.DeployZoneSet:GetRandomZone() +local DeployCoordinate=DeployZone:GetCoordinate():GetRandomCoordinateInRadius(self.DeployOuterRadius,self.DeployInnerRadius) +self.AI_Cargo[Carrier]:__Deploy(0.1,DeployCoordinate,math.random(self.DeployMinSpeed,self.DeployMaxSpeed),math.random(self.DeployMinHeight,self.DeployMaxHeight),DeployZone) +end +end +self:F({Carrier=Carrier:GetName(),PickupCargo=self.PickupCargo}) +self.PickupCargo[Carrier]=nil +end +AI_CARGO_DISPATCHER_APC={ +ClassName="AI_CARGO_DISPATCHER_APC", +} +function AI_CARGO_DISPATCHER_APC:New(APCSet,CargoSet,PickupZoneSet,DeployZoneSet,CombatRadius) +local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(APCSet,CargoSet,PickupZoneSet,DeployZoneSet)) +self:SetDeploySpeed(120,70) +self:SetPickupSpeed(120,70) +self:SetPickupRadius(0,0) +self:SetDeployRadius(0,0) +self:SetPickupHeight() +self:SetDeployHeight() +self:SetCombatRadius(CombatRadius) +return self +end +function AI_CARGO_DISPATCHER_APC:AICargo(APC,CargoSet) +local aicargoapc=AI_CARGO_APC:New(APC,CargoSet,self.CombatRadius) +aicargoapc:SetDeployOffRoad(self.deployOffroad,self.deployFormation) +aicargoapc:SetPickupOffRoad(self.pickupOffroad,self.pickupFormation) +return aicargoapc +end +function AI_CARGO_DISPATCHER_APC:SetCombatRadius(CombatRadius) +self.CombatRadius=CombatRadius or 0 +return self +end +function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad,Formation) +self:SetPickupOffRoad(Offroad,Formation) +self:SetDeployOffRoad(Offroad,Formation) +return self +end +function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad,Formation) +self.pickupOffroad=Offroad +self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad +return self +end +function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad,Formation) +self.deployOffroad=Offroad +self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad +return self +end +AI_CARGO_DISPATCHER_HELICOPTER={ +ClassName="AI_CARGO_DISPATCHER_HELICOPTER", +} +function AI_CARGO_DISPATCHER_HELICOPTER:New(HelicopterSet,CargoSet,PickupZoneSet,DeployZoneSet) +local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(HelicopterSet,CargoSet,PickupZoneSet,DeployZoneSet)) +self:SetPickupSpeed(350,150) +self:SetDeploySpeed(350,150) +self:SetPickupRadius(40,12) +self:SetDeployRadius(40,12) +self:SetPickupHeight(500,200) +self:SetDeployHeight(500,200) +return self +end +function AI_CARGO_DISPATCHER_HELICOPTER:AICargo(Helicopter,CargoSet) +local dispatcher=AI_CARGO_HELICOPTER:New(Helicopter,CargoSet) +dispatcher:SetLandingSpeedAndHeight(27,6) +return dispatcher +end +AI_CARGO_DISPATCHER_AIRPLANE={ +ClassName="AI_CARGO_DISPATCHER_AIRPLANE", +} +function AI_CARGO_DISPATCHER_AIRPLANE:New(AirplaneSet,CargoSet,PickupZoneSet,DeployZoneSet) +local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(AirplaneSet,CargoSet,PickupZoneSet,DeployZoneSet)) +self:SetPickupSpeed(1200,600) +self:SetDeploySpeed(1200,600) +self:SetPickupRadius(0,0) +self:SetDeployRadius(0,0) +self:SetPickupHeight(8000,6000) +self:SetDeployHeight(8000,6000) +self:SetMonitorTimeInterval(600) +return self +end +function AI_CARGO_DISPATCHER_AIRPLANE:AICargo(Airplane,CargoSet) +return AI_CARGO_AIRPLANE:New(Airplane,CargoSet) +end +AI_CARGO_DISPATCHER_SHIP={ +ClassName="AI_CARGO_DISPATCHER_SHIP" +} +function AI_CARGO_DISPATCHER_SHIP:New(ShipSet,CargoSet,PickupZoneSet,DeployZoneSet,ShippingLane) +local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(ShipSet,CargoSet,PickupZoneSet,DeployZoneSet)) +self:SetPickupSpeed(60,10) +self:SetDeploySpeed(60,10) +self:SetPickupRadius(500,6000) +self:SetDeployRadius(500,6000) +self:SetPickupHeight(0,0) +self:SetDeployHeight(0,0) +self:SetShippingLane(ShippingLane) +self:SetMonitorTimeInterval(600) +return self +end +function AI_CARGO_DISPATCHER_SHIP:SetShippingLane(ShippingLane) +self.ShippingLane=ShippingLane +return self +end +function AI_CARGO_DISPATCHER_SHIP:AICargo(Ship,CargoSet) +return AI_CARGO_SHIP:New(Ship,CargoSet,0,self.ShippingLane) +end +do +ACT_ASSIGN={ +ClassName="ACT_ASSIGN", +} +function ACT_ASSIGN:New() +local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIGN")) +self:AddTransition("UnAssigned","Start","Waiting") +self:AddTransition("Waiting","Assign","Assigned") +self:AddTransition("Waiting","Reject","Rejected") +self:AddTransition("*","Fail","Failed") +self:AddEndState("Assigned") +self:AddEndState("Rejected") +self:AddEndState("Failed") +self:SetStartState("UnAssigned") +return self +end +end +do +ACT_ASSIGN_ACCEPT={ +ClassName="ACT_ASSIGN_ACCEPT", +} +function ACT_ASSIGN_ACCEPT:New(TaskBriefing) +local self=BASE:Inherit(self,ACT_ASSIGN:New()) +self.TaskBriefing=TaskBriefing +return self +end +function ACT_ASSIGN_ACCEPT:Init(FsmAssign) +self.TaskBriefing=FsmAssign.TaskBriefing +end +function ACT_ASSIGN_ACCEPT:onafterStart(ProcessUnit,Task,From,Event,To) +self:__Assign(1) +end +function ACT_ASSIGN_ACCEPT:onenterAssigned(ProcessUnit,Task,From,Event,To,TaskGroup) +self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName()) +end +end +do +ACT_ASSIGN_MENU_ACCEPT={ +ClassName="ACT_ASSIGN_MENU_ACCEPT", +} +function ACT_ASSIGN_MENU_ACCEPT:New(TaskBriefing) +local self=BASE:Inherit(self,ACT_ASSIGN:New()) +self.TaskBriefing=TaskBriefing +return self +end +function ACT_ASSIGN_MENU_ACCEPT:Init(TaskBriefing) +self.TaskBriefing=TaskBriefing +return self +end +function ACT_ASSIGN_MENU_ACCEPT:onafterStart(ProcessUnit,Task,From,Event,To) +self:GetCommandCenter():MessageToGroup("Task "..self.Task:GetName().." has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!",ProcessUnit:GetGroup(),120) +local TaskGroup=ProcessUnit:GetGroup() +self.Menu=MENU_GROUP:New(TaskGroup,"Task "..self.Task:GetName().." CONFIRMATION") +self.MenuAcceptTask=MENU_GROUP_COMMAND:New(TaskGroup,"Accept task "..self.Task:GetName(),self.Menu,self.MenuAssign,self,TaskGroup) +self.MenuRejectTask=MENU_GROUP_COMMAND:New(TaskGroup,"Reject task "..self.Task:GetName(),self.Menu,self.MenuReject,self,TaskGroup) +self:__Reject(120,TaskGroup) +end +function ACT_ASSIGN_MENU_ACCEPT:MenuAssign(TaskGroup) +self:__Assign(-1,TaskGroup) +end +function ACT_ASSIGN_MENU_ACCEPT:MenuReject(TaskGroup) +self:__Reject(-1,TaskGroup) +end +function ACT_ASSIGN_MENU_ACCEPT:onafterAssign(ProcessUnit,Task,From,Event,To,TaskGroup) +self.Menu:Remove() +end +function ACT_ASSIGN_MENU_ACCEPT:onafterReject(ProcessUnit,Task,From,Event,To,TaskGroup) +self:F({TaskGroup=TaskGroup}) +self.Menu:Remove() +self.Task:RejectGroup(TaskGroup) +end +function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned(ProcessUnit,Task,From,Event,To,TaskGroup) +self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName()) +end +end +do +ACT_ROUTE={ +ClassName="ACT_ROUTE", +} +function ACT_ROUTE:New() +local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ROUTE")) +self:AddTransition("*","Reset","None") +self:AddTransition("None","Start","Routing") +self:AddTransition("*","Report","*") +self:AddTransition("Routing","Route","Routing") +self:AddTransition("Routing","Pause","Pausing") +self:AddTransition("Routing","Arrive","Arrived") +self:AddTransition("*","Cancel","Cancelled") +self:AddTransition("Arrived","Success","Success") +self:AddTransition("*","Fail","Failed") +self:AddTransition("","","") +self:AddTransition("","","") +self:AddEndState("Arrived") +self:AddEndState("Failed") +self:AddEndState("Cancelled") +self:SetStartState("None") +self:SetRouteMode("C") +return self +end +function ACT_ROUTE:SetMenuCancel(MenuGroup,MenuText,ParentMenu,MenuTime,MenuTag) +self.CancelMenuGroupCommand=MENU_GROUP_COMMAND:New( +MenuGroup, +MenuText, +ParentMenu, +self.MenuCancel, +self +):SetTime(MenuTime):SetTag(MenuTag) +ParentMenu:SetTime(MenuTime) +ParentMenu:Remove(MenuTime,MenuTag) +return self +end +function ACT_ROUTE:SetRouteMode(RouteMode) +self.RouteMode=RouteMode +return self +end +function ACT_ROUTE:GetRouteText(Controllable) +local RouteText="" +local Coordinate=nil +if self.Coordinate then +Coordinate=self.Coordinate +end +if self.Zone then +Coordinate=self.Zone:GetPointVec3(self.Altitude) +Coordinate:SetHeading(self.Heading) +end +local Task=self:GetTask() +local CC=self:GetTask():GetMission():GetCommandCenter() +if CC then +if CC:IsModeWWII()then +local ShortestDistance=0 +local ShortestReferencePoint=nil +local ShortestReferenceName="" +self:F({CC.ReferencePoints}) +for ZoneName,Zone in pairs(CC.ReferencePoints)do +self:F({ZoneName=ZoneName}) +local Zone=Zone +local ZoneCoord=Zone:GetCoordinate() +local ZoneDistance=ZoneCoord:Get2DDistance(Coordinate) +self:F({ShortestDistance,ShortestReferenceName}) +if ShortestDistance==0 or ZoneDistance=self.DisplayInterval then +self:T({HasArrived=HasArrived}) +if not HasArrived then +self:Report() +end +self.DisplayCount=1 +else +self.DisplayCount=self.DisplayCount+1 +end +if HasArrived then +self:__Arrive(1) +else +self:__Route(1) +end +return HasArrived +end +return false +end +end +do +ACT_ROUTE_POINT={ +ClassName="ACT_ROUTE_POINT", +} +function ACT_ROUTE_POINT:New(Coordinate,Range) +local self=BASE:Inherit(self,ACT_ROUTE:New()) +self.Coordinate=Coordinate +self.Range=Range or 0 +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +return self +end +function ACT_ROUTE_POINT:Init(FsmRoute) +self.Coordinate=FsmRoute.Coordinate +self.Range=FsmRoute.Range or 0 +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +self:SetStartState("None") +end +function ACT_ROUTE_POINT:SetCoordinate(Coordinate) +self:F2({Coordinate}) +self.Coordinate=Coordinate +end +function ACT_ROUTE_POINT:GetCoordinate() +self:F2({self.Coordinate}) +return self.Coordinate +end +function ACT_ROUTE_POINT:SetRange(Range) +self:F2({Range}) +self.Range=Range or 10000 +end +function ACT_ROUTE_POINT:GetRange() +self:F2({self.Range}) +return self.Range +end +function ACT_ROUTE_POINT:onfuncHasArrived(ProcessUnit) +if ProcessUnit:IsAlive()then +local Distance=self.Coordinate:Get2DDistance(ProcessUnit:GetCoordinate()) +if Distance<=self.Range then +local RouteText="Task \""..self:GetTask():GetName().."\", you have arrived." +self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +return true +end +end +return false +end +function ACT_ROUTE_POINT:onafterReport(ProcessUnit,From,Event,To) +local RouteText="Task \""..self:GetTask():GetName().."\", "..self:GetRouteText(ProcessUnit) +self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update) +end +end +do +ACT_ROUTE_ZONE={ +ClassName="ACT_ROUTE_ZONE", +} +function ACT_ROUTE_ZONE:New(Zone) +local self=BASE:Inherit(self,ACT_ROUTE:New()) +self.Zone=Zone +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +return self +end +function ACT_ROUTE_ZONE:Init(FsmRoute) +self.Zone=FsmRoute.Zone +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +end +function ACT_ROUTE_ZONE:SetZone(Zone,Altitude,Heading) +self.Zone=Zone +self.Altitude=Altitude +self.Heading=Heading +end +function ACT_ROUTE_ZONE:GetZone() +return self.Zone +end +function ACT_ROUTE_ZONE:onfuncHasArrived(ProcessUnit) +if ProcessUnit:IsInZone(self.Zone)then +local RouteText="Task \""..self:GetTask():GetName().."\", you have arrived within the zone." +self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +end +return ProcessUnit:IsInZone(self.Zone) +end +function ACT_ROUTE_ZONE:onafterReport(ProcessUnit,From,Event,To) +self:F({ProcessUnit=ProcessUnit}) +local RouteText="Task \""..self:GetTask():GetName().."\", "..self:GetRouteText(ProcessUnit) +self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update) +end +end +do +ACT_ACCOUNT={ +ClassName="ACT_ACCOUNT", +TargetSetUnit=nil, +} +function ACT_ACCOUNT:New() +local self=BASE:Inherit(self,FSM_PROCESS:New()) +self:AddTransition("Assigned","Start","Waiting") +self:AddTransition("*","Wait","Waiting") +self:AddTransition("*","Report","Report") +self:AddTransition("*","Event","Account") +self:AddTransition("Account","Player","AccountForPlayer") +self:AddTransition("Account","Other","AccountForOther") +self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"More","Wait") +self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"NoMore","Accounted") +self:AddTransition("*","Fail","Failed") +self:AddEndState("Failed") +self:SetStartState("Assigned") +return self +end +function ACT_ACCOUNT:onafterStart(ProcessUnit,From,Event,To) +self:HandleEvent(EVENTS.Dead,self.onfuncEventDead) +self:HandleEvent(EVENTS.Crash,self.onfuncEventCrash) +self:HandleEvent(EVENTS.Hit) +self:__Wait(1) +end +function ACT_ACCOUNT:onenterWaiting(ProcessUnit,From,Event,To) +if self.DisplayCount>=self.DisplayInterval then +self:Report() +self.DisplayCount=1 +else +self.DisplayCount=self.DisplayCount+1 +end +return true +end +function ACT_ACCOUNT:onafterEvent(ProcessUnit,From,Event,To) +self:__NoMore(1) +end +end +do +ACT_ACCOUNT_DEADS={ +ClassName="ACT_ACCOUNT_DEADS", +} +function ACT_ACCOUNT_DEADS:New() +local self=BASE:Inherit(self,ACT_ACCOUNT:New()) +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +self.DisplayCategory="HQ" +return self +end +function ACT_ACCOUNT_DEADS:Init(FsmAccount) +self.Task=self:GetTask() +self.TaskName=self.Task:GetName() +end +function ACT_ACCOUNT_DEADS:onenterReport(ProcessUnit,Task,From,Event,To) +local MessageText="Your group with assigned "..self.TaskName.." task has "..Task.TargetSetUnit:GetUnitTypesText().." targets left to be destroyed." +self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +end +function ACT_ACCOUNT_DEADS:onafterEvent(ProcessUnit,Task,From,Event,To,EventData) +self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData}) +if Task.TargetSetUnit:FindUnit(EventData.IniUnitName)then +local PlayerName=ProcessUnit:GetPlayerName() +local PlayerHit=self.PlayerHits and self.PlayerHits[EventData.IniUnitName] +if PlayerHit==PlayerName then +self:Player(EventData) +else +self:Other(EventData) +end +end +end +function ACT_ACCOUNT_DEADS:onenterAccountForPlayer(ProcessUnit,Task,From,Event,To,EventData) +self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData}) +local TaskGroup=ProcessUnit:GetGroup() +Task.TargetSetUnit:Remove(EventData.IniUnitName) +local MessageText="You have destroyed a target.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed." +self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +local PlayerName=ProcessUnit:GetPlayerName() +Task:AddProgress(PlayerName,"Destroyed "..EventData.IniTypeName,timer.getTime(),1) +if Task.TargetSetUnit:Count()>0 then +self:__More(1) +else +self:__NoMore(1) +end +end +function ACT_ACCOUNT_DEADS:onenterAccountForOther(ProcessUnit,Task,From,Event,To,EventData) +self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData}) +local TaskGroup=ProcessUnit:GetGroup() +Task.TargetSetUnit:Remove(EventData.IniUnitName) +local MessageText="One of the task targets has been destroyed.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed." +self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +if Task.TargetSetUnit:Count()>0 then +self:__More(1) +else +self:__NoMore(1) +end +end +function ACT_ACCOUNT_DEADS:OnEventHit(EventData) +self:T({"EventDead",EventData}) +if EventData.IniPlayerName and EventData.TgtDCSUnitName then +self.PlayerHits=self.PlayerHits or{} +self.PlayerHits[EventData.TgtDCSUnitName]=EventData.IniPlayerName +end +end +function ACT_ACCOUNT_DEADS:onfuncEventDead(EventData) +self:T({"EventDead",EventData}) +if EventData.IniDCSUnit then +self:Event(EventData) +end +end +function ACT_ACCOUNT_DEADS:onfuncEventCrash(EventData) +self:T({"EventDead",EventData}) +if EventData.IniDCSUnit then +self:Event(EventData) +end +end +end +do +ACT_ASSIST={ +ClassName="ACT_ASSIST", +} +function ACT_ASSIST:New() +local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIST")) +self:AddTransition("None","Start","AwaitSmoke") +self:AddTransition("AwaitSmoke","Next","Smoking") +self:AddTransition("Smoking","Next","AwaitSmoke") +self:AddTransition("*","Stop","Success") +self:AddTransition("*","Fail","Failed") +self:AddEndState("Failed") +self:AddEndState("Success") +self:SetStartState("None") +return self +end +function ACT_ASSIST:onafterStart(ProcessUnit,From,Event,To) +local ProcessGroup=ProcessUnit:GetGroup() +local MissionMenu=self:GetMission():GetMenu(ProcessGroup) +local function MenuSmoke(MenuParam) +local self=MenuParam.self +local SmokeColor=MenuParam.SmokeColor +self.SmokeColor=SmokeColor +self:__Next(1) +end +self.Menu=MENU_GROUP:New(ProcessGroup,"Target acquisition",MissionMenu) +self.MenuSmokeBlue=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop blue smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Blue}) +self.MenuSmokeGreen=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop green smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Green}) +self.MenuSmokeOrange=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Orange smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Orange}) +self.MenuSmokeRed=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Red smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Red}) +self.MenuSmokeWhite=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop White smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.White}) +end +function ACT_ASSIST:onafterStop(ProcessUnit,From,Event,To) +self.Menu:Remove() +end +end +do +ACT_ASSIST_SMOKE_TARGETS_ZONE={ +ClassName="ACT_ASSIST_SMOKE_TARGETS_ZONE", +} +function ACT_ASSIST_SMOKE_TARGETS_ZONE:New(TargetSetUnit,TargetZone) +local self=BASE:Inherit(self,ACT_ASSIST:New()) +self.TargetSetUnit=TargetSetUnit +self.TargetZone=TargetZone +return self +end +function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(FsmSmoke) +self.TargetSetUnit=FsmSmoke.TargetSetUnit +self.TargetZone=FsmSmoke.TargetZone +end +function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(TargetSetUnit,TargetZone) +self.TargetSetUnit=TargetSetUnit +self.TargetZone=TargetZone +return self +end +function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking(ProcessUnit,From,Event,To) +self.TargetSetUnit:ForEachUnit( +function(SmokeUnit) +if math.random(1,(100*self.TargetSetUnit:Count())/4)<=100 then +SCHEDULER:New(self, +function() +if SmokeUnit:IsAlive()then +SmokeUnit:Smoke(self.SmokeColor,150) +end +end,{},math.random(10,60) +) +end +end +) +end +end +SHAPE_BASE={ +ClassName="SHAPE_BASE", +Name="", +CenterVec2=nil, +Points={}, +Coords={}, +MarkIDs={}, +ColorString="", +ColorRGBA={} +} +function SHAPE_BASE:New() +local self=BASE:Inherit(self,BASE:New()) +return self +end +function SHAPE_BASE:FindOnMap(shape_name) +local self=BASE:Inherit(self,BASE:New()) +local found=false +for _,layer in pairs(env.mission.drawings.layers)do +for _,object in pairs(layer["objects"])do +if object["name"]==shape_name then +self.Name=object["name"] +self.CenterVec2={x=object["mapX"],y=object["mapY"]} +self.ColorString=object["colorString"] +self.ColorRGBA=UTILS.HexToRGBA(self.ColorString) +found=true +end +end +end +if not found then +self:E("Can't find a shape with name "..shape_name) +end +return self +end +function SHAPE_BASE:GetAllShapes(filter) +filter=filter or"" +local return_shapes={} +for _,layer in pairs(env.mission.drawings.layers)do +for _,object in pairs(layer["objects"])do +if string.contains(object["name"],filter)then +table.add(return_shapes,object) +end +end +end +return return_shapes +end +function SHAPE_BASE:Offset(new_vec2) +local offset_vec2=UTILS.Vec2Subtract(new_vec2,self.CenterVec2) +self.CenterVec2=new_vec2 +if self.ClassName=="POLYGON"then +for _,point in pairs(self.Points)do +point.x=point.x+offset_vec2.x +point.y=point.y+offset_vec2.y +end +end +end +function SHAPE_BASE:GetName() +return self.Name +end +function SHAPE_BASE:GetColorString() +return self.ColorString +end +function SHAPE_BASE:GetColorRGBA() +return self.ColorRGBA +end +function SHAPE_BASE:GetColorRed() +return self.ColorRGBA.R +end +function SHAPE_BASE:GetColorGreen() +return self.ColorRGBA.G +end +function SHAPE_BASE:GetColorBlue() +return self.ColorRGBA.B +end +function SHAPE_BASE:GetColorAlpha() +return self.ColorRGBA.A +end +function SHAPE_BASE:GetCenterVec2() +return self.CenterVec2 +end +function SHAPE_BASE:GetCenterCoordinate() +return COORDINATE:NewFromVec2(self.CenterVec2) +end +function SHAPE_BASE:GetCoordinate() +return self:GetCenterCoordinate() +end +function SHAPE_BASE:ContainsPoint(_) +self:E("This needs to be set in the derived class") +end +function SHAPE_BASE:ContainsUnit(unit_name) +local unit=UNIT:FindByName(unit_name) +if unit==nil or not unit:IsAlive()then +return false +end +if self:ContainsPoint(unit:GetVec2())then +return true +end +return false +end +function SHAPE_BASE:ContainsAnyOfGroup(group_name) +local group=GROUP:FindByName(group_name) +if group==nil or not group:IsAlive()then +return false +end +for _,unit in pairs(group:GetUnits())do +if self:ContainsPoint(unit:GetVec2())then +return true +end +end +return false +end +function SHAPE_BASE:ContainsAllOfGroup(group_name) +local group=GROUP:FindByName(group_name) +if group==nil or not group:IsAlive()then +return false +end +for _,unit in pairs(group:GetUnits())do +if not self:ContainsPoint(unit:GetVec2())then +return false +end +end +return true +end +CIRCLE={ +ClassName="CIRCLE", +Radius=nil, +} +function CIRCLE:FindOnMap(shape_name) +local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(shape_name)) +for _,layer in pairs(env.mission.drawings.layers)do +for _,object in pairs(layer["objects"])do +if string.find(object["name"],shape_name,1,true)then +if object["polygonMode"]=="circle"then +self.Radius=object["radius"] +end +end +end +end +return self +end +function CIRCLE:Find(shape_name) +return _DATABASE:FindShape(shape_name) +end +function CIRCLE:New(vec2,radius) +local self=BASE:Inherit(self,SHAPE_BASE:New()) +self.CenterVec2=vec2 +self.Radius=radius +return self +end +function CIRCLE:GetRadius() +return self.Radius +end +function CIRCLE:ContainsPoint(point) +if((point.x-self.CenterVec2.x)^2+(point.y-self.CenterVec2.y)^2)^0.5<=self.Radius then +return true +end +return false +end +function CIRCLE:PointInSector(point,sector_start,sector_end,center,radius) +center=center or self.CenterVec2 +radius=radius or self.Radius +local function are_clockwise(v1,v2) +return-v1.x*v2.y+v1.y*v2.x>0 +end +local function is_in_radius(rp) +return rp.x*rp.x+rp.y*rp.y<=radius^2 +end +local rel_pt={ +x=point.x-center.x, +y=point.y-center.y +} +local rel_sector_start={ +x=sector_start.x-center.x, +y=sector_start.y-center.y, +} +local rel_sector_end={ +x=sector_end.x-center.x, +y=sector_end.y-center.y, +} +return not are_clockwise(rel_sector_start,rel_pt)and +are_clockwise(rel_sector_end,rel_pt)and +is_in_radius(rel_pt,radius) +end +function CIRCLE:UnitInSector(unit_name,sector_start,sector_end,center,radius) +center=center or self.CenterVec2 +radius=radius or self.Radius +if self:PointInSector(UNIT:FindByName(unit_name):GetVec2(),sector_start,sector_end,center,radius)then +return true +end +return false +end +function CIRCLE:AnyOfGroupInSector(group_name,sector_start,sector_end,center,radius) +center=center or self.CenterVec2 +radius=radius or self.Radius +for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do +if self:PointInSector(unit:GetVec2(),sector_start,sector_end,center,radius)then +return true +end +end +return false +end +function CIRCLE:AllOfGroupInSector(group_name,sector_start,sector_end,center,radius) +center=center or self.CenterVec2 +radius=radius or self.Radius +for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do +if not self:PointInSector(unit:GetVec2(),sector_start,sector_end,center,radius)then +return false +end +end +return true +end +function CIRCLE:UnitInRadius(unit_name,center,radius) +center=center or self.CenterVec2 +radius=radius or self.Radius +if UTILS.IsInRadius(center,UNIT:FindByName(unit_name):GetVec2(),radius)then +return true +end +return false +end +function CIRCLE:AnyOfGroupInRadius(group_name,center,radius) +center=center or self.CenterVec2 +radius=radius or self.Radius +for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do +if UTILS.IsInRadius(center,unit:GetVec2(),radius)then +return true +end +end +return false +end +function CIRCLE:AllOfGroupInRadius(group_name,center,radius) +center=center or self.CenterVec2 +radius=radius or self.Radius +for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do +if not UTILS.IsInRadius(center,unit:GetVec2(),radius)then +return false +end +end +return true +end +function CIRCLE:GetRandomVec2() +math.random() +math.random() +math.random() +local angle=math.random()*2*math.pi +local rx=math.random(0,self.Radius)*math.cos(angle)+self.CenterVec2.x +local ry=math.random(0,self.Radius)*math.sin(angle)+self.CenterVec2.y +return{x=rx,y=ry} +end +function CIRCLE:GetRandomVec2OnBorder() +math.random() +math.random() +math.random() +local angle=math.random()*2*math.pi +local rx=self.Radius*math.cos(angle)+self.CenterVec2.x +local ry=self.Radius*math.sin(angle)+self.CenterVec2.y +return{x=rx,y=ry} +end +function CIRCLE:GetBoundingBox() +local min_x=self.CenterVec2.x-self.Radius +local min_y=self.CenterVec2.y-self.Radius +local max_x=self.CenterVec2.x+self.Radius +local max_y=self.CenterVec2.y+self.Radius +return{ +{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y} +} +end +CUBE={ +ClassName="CUBE", +Points={}, +Coords={} +} +function CUBE:New(p1,p2,p3,p4,p5,p6,p7,p8) +local self=BASE:Inherit(self,SHAPE_BASE) +self.Points={p1,p2,p3,p4,p5,p6,p7,p8} +for _,point in spairs(self.Points)do +table.insert(self.Coords,COORDINATE:NewFromVec3(point)) +end +return self +end +function CUBE:GetCenter() +local center={x=0,y=0,z=0} +for _,point in pairs(self.Points)do +center.x=center.x+point.x +center.y=center.y+point.y +center.z=center.z+point.z +end +center.x=center.x/8 +center.y=center.y/8 +center.z=center.z/8 +return center +end +function CUBE:ContainsPoint(point,cube_points) +cube_points=cube_points or self.Points +local min_x,min_y,min_z=math.huge,math.huge,math.huge +local max_x,max_y,max_z=-math.huge,-math.huge,-math.huge +for _,p in ipairs(cube_points)do +if p.xmax_x then max_x=p.x end +if p.y>max_y then max_y=p.y end +if p.z>max_z then max_z=p.z end +end +return point.x>=min_x and point.x<=max_x and point.y>=min_y and point.y<=max_y and point.z>=min_z and point.z<=max_z +end +LINE={ +ClassName="LINE", +Points={}, +Coords={}, +} +function LINE:FindOnMap(line_name) +local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(line_name)) +for _,layer in pairs(env.mission.drawings.layers)do +for _,object in pairs(layer["objects"])do +if object["name"]==line_name then +if object["primitiveType"]=="Line"then +for _,point in UTILS.spairs(object["points"])do +local p={x=object["mapX"]+point["x"], +y=object["mapY"]+point["y"]} +local coord=COORDINATE:NewFromVec2(p) +table.insert(self.Points,p) +table.insert(self.Coords,coord) +end +end +end +end +end +self:I(#self.Points) +if#self.Points==0 then +return nil +end +self.MarkIDs={} +return self +end +function LINE:Find(shape_name) +return _DATABASE:FindShape(shape_name) +end +function LINE:New(...) +local self=BASE:Inherit(self,SHAPE_BASE:New()) +self.Points={...} +self:I(self.Points) +for _,point in UTILS.spairs(self.Points)do +table.insert(self.Coords,COORDINATE:NewFromVec2(point)) +end +return self +end +function LINE:NewFromCircle(center_point,radius,angle_degrees) +local self=BASE:Inherit(self,SHAPE_BASE:New()) +self.CenterVec2=center_point +local angleRadians=math.rad(angle_degrees) +local point1={ +x=center_point.x+radius*math.cos(angleRadians), +y=center_point.y+radius*math.sin(angleRadians) +} +local point2={ +x=center_point.x+radius*math.cos(angleRadians+math.pi), +y=center_point.y+radius*math.sin(angleRadians+math.pi) +} +for _,point in pairs{point1,point2}do +table.insert(self.Points,point) +table.insert(self.Coords,COORDINATE:NewFromVec2(point)) +end +return self +end +function LINE:Coordinates() +return self.Coords +end +function LINE:GetStartCoordinate() +return self.Coords[1] +end +function LINE:GetEndCoordinate() +return self.Coords[#self.Coords] +end +function LINE:GetStartPoint() +return self.Points[1] +end +function LINE:GetEndPoint() +return self.Points[#self.Points] +end +function LINE:GetLength() +local total_length=0 +for i=1,#self.Points-1 do +local x1,y1=self.Points[i]["x"],self.Points[i]["y"] +local x2,y2=self.Points[i+1]["x"],self.Points[i+1]["y"] +local segment_length=math.sqrt((x2-x1)^2+(y2-y1)^2) +total_length=total_length+segment_length +end +return total_length +end +function LINE:GetRandomPoint(points) +points=points or self.Points +local rand=math.random() +local random_x=points[1].x+rand*(points[2].x-points[1].x) +local random_y=points[1].y+rand*(points[2].y-points[1].y) +return{x=random_x,y=random_y} +end +function LINE:GetHeading(points) +points=points or self.Points +local angle=math.atan2(points[2].y-points[1].y,points[2].x-points[1].x) +angle=math.deg(angle) +if angle<0 then +angle=angle+360 +end +return angle +end +function LINE:GetIndividualParts() +local parts={} +if#self.Points==2 then +parts={self} +end +for i=1,#self.Points-1 do +local p1=self.Points[i] +local p2=self.Points[i%#self.Points+1] +table.add(parts,LINE:New(p1,p2)) +end +return parts +end +function LINE:GetPointsInbetween(amount,start_point,end_point) +start_point=start_point or self:GetStartPoint() +end_point=end_point or self:GetEndPoint() +if amount==0 then return{start_point,end_point}end +amount=amount+1 +local points={} +local difference={x=end_point.x-start_point.x,y=end_point.y-start_point.y} +local divided={x=difference.x/amount,y=difference.y/amount} +for j=0,amount do +local part_pos={x=divided.x*j,y=divided.y*j} +local point={x=start_point.x+part_pos.x,y=start_point.y+part_pos.y} +table.insert(points,point) +end +return points +end +function LINE:GetCoordinatesInBetween(amount,start_point,end_point) +local coords={} +for _,pt in pairs(self:GetPointsInbetween(amount,start_point,end_point))do +table.add(coords,COORDINATE:NewFromVec2(pt)) +end +return coords +end +function LINE:GetRandomPoint(start_point,end_point) +start_point=start_point or self:GetStartPoint() +end_point=end_point or self:GetEndPoint() +local fraction=math.random() +local difference={x=end_point.x-start_point.x,y=end_point.y-start_point.y} +local part_pos={x=difference.x*fraction,y=difference.y*fraction} +local random_point={x=start_point.x+part_pos.x,y=start_point.y+part_pos.y} +return random_point +end +function LINE:GetRandomCoordinate(start_point,end_point) +start_point=start_point or self:GetStartPoint() +end_point=end_point or self:GetEndPoint() +return COORDINATE:NewFromVec2(self:GetRandomPoint(start_point,end_point)) +end +function LINE:GetPointsBetweenAsSineWave(amount,start_point,end_point,frequency,phase,amplitude) +amount=amount or 20 +start_point=start_point or self:GetStartPoint() +end_point=end_point or self:GetEndPoint() +frequency=frequency or 1 +phase=phase or 0 +amplitude=amplitude or 100 +local points={} +local function sine_wave(x) +return amplitude*math.sin(2*math.pi*frequency*(x-start_point.x)+phase) +end +local x=start_point.x +local step=(end_point.x-start_point.x)/20 +for _=1,amount do +local y=sine_wave(x) +x=x+step +table.add(points,{x=x,y=y}) +end +return points +end +function LINE:GetBoundingBox() +local min_x,min_y,max_x,max_y=self.Points[1].x,self.Points[1].y,self.Points[2].x,self.Points[2].y +for i=2,#self.Points do +local x,y=self.Points[i].x,self.Points[i].y +if xmax_x then +max_x=x +end +if y>max_y then +max_y=y +end +end +return{ +{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y} +} +end +function LINE:Draw() +for i=1,#self.Coords-1 do +local c1=self.Coords[i] +local c2=self.Coords[i%#self.Coords+1] +table.add(self.MarkIDs,c1:LineToAll(c2)) +end +end +function LINE:RemoveDraw() +for _,mark_id in pairs(self.MarkIDs)do +UTILS.RemoveMark(mark_id) +end +end +OVAL={ +ClassName="OVAL", +MajorAxis=nil, +MinorAxis=nil, +Angle=0, +DrawPoly=nil +} +function OVAL:FindOnMap(shape_name) +local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(shape_name)) +for _,layer in pairs(env.mission.drawings.layers)do +for _,object in pairs(layer["objects"])do +if string.find(object["name"],shape_name,1,true)then +if object["polygonMode"]=="oval"then +self.CenterVec2={x=object["mapX"],y=object["mapY"]} +self.MajorAxis=object["r1"] +self.MinorAxis=object["r2"] +self.Angle=object["angle"] +end +end +end +end +return self +end +function OVAL:Find(shape_name) +return _DATABASE:FindShape(shape_name) +end +function OVAL:New(vec2,major_axis,minor_axis,angle) +local self=BASE:Inherit(self,SHAPE_BASE:New()) +self.CenterVec2=vec2 +self.MajorAxis=major_axis +self.MinorAxis=minor_axis +self.Angle=angle or 0 +return self +end +function OVAL:GetMajorAxis() +return self.MajorAxis +end +function OVAL:GetMinorAxis() +return self.MinorAxis +end +function OVAL:GetAngle() +return self.Angle +end +function OVAL:SetMajorAxis(value) +self.MajorAxis=value +end +function OVAL:SetMinorAxis(value) +self.MinorAxis=value +end +function OVAL:SetAngle(value) +self.Angle=value +end +function OVAL:ContainsPoint(point) +local cos,sin=math.cos,math.sin +local dx=point.x-self.CenterVec2.x +local dy=point.y-self.CenterVec2.y +local rx=dx*cos(self.Angle)+dy*sin(self.Angle) +local ry=-dx*sin(self.Angle)+dy*cos(self.Angle) +return rx*rx/(self.MajorAxis*self.MajorAxis)+ry*ry/(self.MinorAxis*self.MinorAxis)<=1 +end +function OVAL:GetRandomVec2() +local theta=math.rad(self.Angle) +local random_point=math.sqrt(math.random()) +local phi=math.random()*2*math.pi +local x_c=random_point*math.cos(phi) +local y_c=random_point*math.sin(phi) +local x_e=x_c*self.MajorAxis +local y_e=y_c*self.MinorAxis +local rx=(x_e*math.cos(theta)-y_e*math.sin(theta))+self.CenterVec2.x +local ry=(x_e*math.sin(theta)+y_e*math.cos(theta))+self.CenterVec2.y +return{x=rx,y=ry} +end +function OVAL:GetBoundingBox() +local min_x=self.CenterVec2.x-self.MajorAxis +local min_y=self.CenterVec2.y-self.MinorAxis +local max_x=self.CenterVec2.x+self.MajorAxis +local max_y=self.CenterVec2.y+self.MinorAxis +return{ +{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y} +} +end +function OVAL:Draw() +self.DrawPoly=POLYGON:NewFromPoints(self:PointsOnEdge(20)) +self.DrawPoly:Draw(true) +end +function OVAL:RemoveDraw() +self.DrawPoly:RemoveDraw() +end +function OVAL:PointsOnEdge(num_points) +num_points=num_points or 20 +local points={} +local dtheta=2*math.pi/num_points +for i=0,num_points-1 do +local theta=i*dtheta +local x=self.CenterVec2.x+self.MajorAxis*math.cos(theta)*math.cos(self.Angle)-self.MinorAxis*math.sin(theta)*math.sin(self.Angle) +local y=self.CenterVec2.y+self.MajorAxis*math.cos(theta)*math.sin(self.Angle)+self.MinorAxis*math.sin(theta)*math.cos(self.Angle) +table.insert(points,{x=x,y=y}) +end +return points +end +POLYGON={ +ClassName="POLYGON", +Points={}, +Coords={}, +Triangles={}, +SurfaceArea=0, +TriangleMarkIDs={}, +OutlineMarkIDs={}, +Angle=nil, +Heading=nil +} +function POLYGON:FindOnMap(shape_name) +local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(shape_name)) +for _,layer in pairs(env.mission.drawings.layers)do +for _,object in pairs(layer["objects"])do +if object["name"]==shape_name then +if(object["primitiveType"]=="Line"and object["closed"]==true)or(object["polygonMode"]=="free")then +for _,point in UTILS.spairs(object["points"])do +local p={x=object["mapX"]+point["x"], +y=object["mapY"]+point["y"]} +local coord=COORDINATE:NewFromVec2(p) +self.Points[#self.Points+1]=p +self.Coords[#self.Coords+1]=coord +end +elseif object["polygonMode"]=="rect"then +local angle=object["angle"] +local half_width=object["width"]/2 +local half_height=object["height"]/2 +local p1=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x-half_height,y=self.CenterVec2.y+half_width},self.CenterVec2,angle) +local p2=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x+half_height,y=self.CenterVec2.y+half_width},self.CenterVec2,angle) +local p3=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x+half_height,y=self.CenterVec2.y-half_width},self.CenterVec2,angle) +local p4=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x-half_height,y=self.CenterVec2.y-half_width},self.CenterVec2,angle) +self.Points={p1,p2,p3,p4} +for _,point in pairs(self.Points)do +self.Coords[#self.Coords+1]=COORDINATE:NewFromVec2(point) +end +elseif object["polygonMode"]=="arrow"then +for _,point in UTILS.spairs(object["points"])do +local p={x=object["mapX"]+point["x"], +y=object["mapY"]+point["y"]} +local coord=COORDINATE:NewFromVec2(p) +self.Points[#self.Points+1]=p +self.Coords[#self.Coords+1]=coord +end +self.Angle=object["angle"] +self.Heading=UTILS.ClampAngle(self.Angle+90) +end +end +end +end +if#self.Points==0 then +return nil +end +self.CenterVec2=self:GetCentroid() +self.Triangles=self:Triangulate() +self.SurfaceArea=self:__CalculateSurfaceArea() +self.TriangleMarkIDs={} +self.OutlineMarkIDs={} +return self +end +function POLYGON:FromZone(zone_name) +for _,zone in pairs(env.mission.triggers.zones)do +if zone["name"]==zone_name then +return POLYGON:New(unpack(zone["verticies"]or{})) +end +end +end +function POLYGON:Find(shape_name) +return _DATABASE:FindShape(shape_name) +end +function POLYGON:New(...) +local self=BASE:Inherit(self,SHAPE_BASE:New()) +self.Points={...} +self.Coords={} +for _,point in UTILS.spairs(self.Points)do +table.insert(self.Coords,COORDINATE:NewFromVec2(point)) +end +self.Triangles=self:Triangulate() +self.SurfaceArea=self:__CalculateSurfaceArea() +return self +end +function POLYGON:GetCentroid() +local function sum(t) +local total=0 +for _,value in pairs(t)do +total=total+value +end +return total +end +local x_values={} +local y_values={} +local length=table.length(self.Points) +for _,point in pairs(self.Points)do +table.insert(x_values,point.x) +table.insert(y_values,point.y) +end +local x=sum(x_values)/length +local y=sum(y_values)/length +return{ +["x"]=x, +["y"]=y +} +end +function POLYGON:GetCoordinates() +return self.Coords +end +function POLYGON:GetStartCoordinate() +return self.Coords[1] +end +function POLYGON:GetEndCoordinate() +return self.Coords[#self.Coords] +end +function POLYGON:GetStartPoint() +return self.Points[1] +end +function POLYGON:GetEndPoint() +return self.Points[#self.Points] +end +function POLYGON:GetPoints() +return self.Points +end +function POLYGON:GetSurfaceArea() +return self.SurfaceArea +end +function POLYGON:GetBoundingBox() +local min_x,min_y,max_x,max_y=self.Points[1].x,self.Points[1].y,self.Points[1].x,self.Points[1].y +for i=2,#self.Points do +local x,y=self.Points[i].x,self.Points[i].y +if xmax_x then +max_x=x +end +if y>max_y then +max_y=y +end +end +return{ +{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y} +} +end +function POLYGON:Triangulate(points) +points=points or self.Points +local triangles={} +local function get_orientation(shape_points) +local sum=0 +for i=1,#shape_points do +local j=i%#shape_points+1 +sum=sum+(shape_points[j].x-shape_points[i].x)*(shape_points[j].y+shape_points[i].y) +end +return sum>=0 and"clockwise"or"counter-clockwise" +end +local function ensure_clockwise(shape_points) +local orientation=get_orientation(shape_points) +if orientation=="counter-clockwise"then +local reversed={} +for i=#shape_points,1,-1 do +table.insert(reversed,shape_points[i]) +end +return reversed +end +return shape_points +end +local function is_clockwise(p1,p2,p3) +local cross_product=(p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x) +return cross_product<0 +end +local function divide_recursively(shape_points) +if#shape_points==3 then +table.insert(triangles,TRIANGLE:New(shape_points[1],shape_points[2],shape_points[3])) +elseif#shape_points>3 then +for i,p1 in ipairs(shape_points)do +local p2=shape_points[(i%#shape_points)+1] +local p3=shape_points[(i+1)%#shape_points+1] +local triangle=TRIANGLE:New(p1,p2,p3) +local is_ear=true +if not is_clockwise(p1,p2,p3)then +is_ear=false +else +for _,point in ipairs(shape_points)do +if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then +is_ear=false +break +end +end +end +if is_ear then +local is_valid_triangle=true +for _,point in ipairs(points)do +if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then +is_valid_triangle=false +break +end +end +if is_valid_triangle then +table.insert(triangles,triangle) +local remaining_points={} +for j,point in ipairs(shape_points)do +if point~=p2 then +table.insert(remaining_points,point) +end +end +divide_recursively(remaining_points) +break +end +end +end +end +end +points=ensure_clockwise(points) +divide_recursively(points) +return triangles +end +function POLYGON:CovarianceMatrix() +local cx,cy=self:GetCentroid() +local covXX,covYY,covXY=0,0,0 +for _,p in ipairs(self.points)do +covXX=covXX+(p.x-cx)^2 +covYY=covYY+(p.y-cy)^2 +covXY=covXY+(p.x-cx)*(p.y-cy) +end +covXX=covXX/(#self.points-1) +covYY=covYY/(#self.points-1) +covXY=covXY/(#self.points-1) +return covXX,covYY,covXY +end +function POLYGON:Direction() +local covXX,covYY,covXY=self:CovarianceMatrix() +local theta=0.5*math.atan2(2*covXY,covXX-covYY) +return math.cos(theta),math.sin(theta) +end +function POLYGON:GetRandomVec2() +local weights={} +for _,triangle in pairs(self.Triangles)do +weights[triangle]=triangle.SurfaceArea/self.SurfaceArea +end +local random_weight=math.random() +local accumulated_weight=0 +for triangle,weight in pairs(weights)do +accumulated_weight=accumulated_weight+weight +if accumulated_weight>=random_weight then +return triangle:GetRandomVec2() +end +end +end +function POLYGON:GetRandomNonWeightedVec2() +return self.Triangles[math.random(1,#self.Triangles)]:GetRandomVec2() +end +function POLYGON:ContainsPoint(point,polygon_points) +local x=point.x +local y=point.y +polygon_points=polygon_points or self.Points +local counter=0 +local num_points=#polygon_points +for current_index=1,num_points do +local next_index=(current_index%num_points)+1 +local current_x,current_y=polygon_points[current_index].x,polygon_points[current_index].y +local next_x,next_y=polygon_points[next_index].x,polygon_points[next_index].y +if((current_y>y)~=(next_y>y))and(x<(next_x-current_x)*(y-current_y)/(next_y-current_y)+current_x)then +counter=counter+1 +end +end +return counter%2==1 +end +function POLYGON:Draw(include_inner_triangles) +include_inner_triangles=include_inner_triangles or false +for i=1,#self.Coords do +local c1=self.Coords[i] +local c2=self.Coords[i%#self.Coords+1] +table.add(self.OutlineMarkIDs,c1:LineToAll(c2)) +end +if include_inner_triangles then +for _,triangle in ipairs(self.Triangles)do +triangle:Draw() +end +end +end +function POLYGON:RemoveDraw() +for _,triangle in pairs(self.Triangles)do +triangle:RemoveDraw() +end +for _,mark_id in pairs(self.OutlineMarkIDs)do +UTILS.RemoveMark(mark_id) +end +end +function POLYGON:__CalculateSurfaceArea() +local area=0 +for _,triangle in pairs(self.Triangles)do +area=area+triangle.SurfaceArea +end +return area +end +TRIANGLE={ +ClassName="TRIANGLE", +Points={}, +Coords={}, +SurfaceArea=0 +} +function TRIANGLE:New(p1,p2,p3) +local self=BASE:Inherit(self,SHAPE_BASE:New()) +self.Points={p1,p2,p3} +local center_x=(p1.x+p2.x+p3.x)/3 +local center_y=(p1.y+p2.y+p3.y)/3 +self.CenterVec2={x=center_x,y=center_y} +for _,pt in pairs({p1,p2,p3})do +table.add(self.Coords,COORDINATE:NewFromVec2(pt)) +end +self.SurfaceArea=math.abs((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))*0.5 +self.MarkIDs={} +return self +end +function TRIANGLE:ContainsPoint(pt,points) +points=points or self.Points +local function sign(p1,p2,p3) +return(p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y) +end +local d1=sign(pt,self.Points[1],self.Points[2]) +local d2=sign(pt,self.Points[2],self.Points[3]) +local d3=sign(pt,self.Points[3],self.Points[1]) +local has_neg=(d1<0)or(d2<0)or(d3<0) +local has_pos=(d1>0)or(d2>0)or(d3>0) +return not(has_neg and has_pos) +end +function TRIANGLE:GetRandomVec2(points) +math.random() +math.random() +math.random() +points=points or self.Points +local pt={math.random(),math.random()} +table.sort(pt) +local s=pt[1] +local t=pt[2]-pt[1] +local u=1-pt[2] +return{x=s*points[1].x+t*points[2].x+u*points[3].x, +y=s*points[1].y+t*points[2].y+u*points[3].y} +end +function TRIANGLE:Draw() +for i=1,#self.Coords do +local c1=self.Coords[i] +local c2=self.Coords[i%#self.Coords+1] +table.add(self.MarkIDs,c1:LineToAll(c2)) +end +end +function TRIANGLE:RemoveDraw() +for _,mark_id in pairs(self.MarkIDs)do +UTILS.RemoveMark(mark_id) +end +end +do +USERSOUND={ +ClassName="USERSOUND", +} +function USERSOUND:New(UserSoundFileName) +local self=BASE:Inherit(self,BASE:New()) +self.UserSoundFileName=UserSoundFileName +return self +end +function USERSOUND:SetFileName(UserSoundFileName) +self.UserSoundFileName=UserSoundFileName +return self +end +function USERSOUND:ToAll() +trigger.action.outSound(self.UserSoundFileName) +return self +end +function USERSOUND:ToCoalition(Coalition) +trigger.action.outSoundForCoalition(Coalition,self.UserSoundFileName) +return self +end +function USERSOUND:ToCountry(Country) +trigger.action.outSoundForCountry(Country,self.UserSoundFileName) +return self +end +function USERSOUND:ToGroup(Group,Delay) +Delay=Delay or 0 +if Delay>0 then +SCHEDULER:New(nil,USERSOUND.ToGroup,{self,Group},Delay) +else +trigger.action.outSoundForGroup(Group:GetID(),self.UserSoundFileName) +end +return self +end +function USERSOUND:ToUnit(Unit,Delay) +Delay=Delay or 0 +if Delay>0 then +SCHEDULER:New(nil,USERSOUND.ToUnit,{self,Unit},Delay) +else +trigger.action.outSoundForUnit(Unit:GetID(),self.UserSoundFileName) +end +return self +end +function USERSOUND:ToClient(Client,Delay) +Delay=Delay or 0 +if Delay>0 then +SCHEDULER:New(nil,USERSOUND.ToClient,{self,Client},Delay) +else +trigger.action.outSoundForUnit(Client:GetID(),self.UserSoundFileName) +end +return self +end +end +do +SOUNDBASE={ +ClassName="SOUNDBASE", +} +function SOUNDBASE:New() +local self=BASE:Inherit(self,BASE:New()) +return self +end +function SOUNDBASE:GetSpeechTime(length,speed,isGoogle) +local maxRateRatio=3 +speed=speed or 1.0 +isGoogle=isGoogle or false +local speedFactor=1.0 +if isGoogle then +speedFactor=speed +else +if speed~=0 then +speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1 +end +if speed<0 then +speedFactor=1/speedFactor +end +end +local wpm=math.ceil(100*speedFactor) +local cps=math.floor((wpm*5)/60) +if type(length)=="string"then +length=string.len(length) +end +return math.ceil(length/cps) +end +end +do +SOUNDFILE={ +ClassName="SOUNDFILE", +filename=nil, +path="l10n/DEFAULT/", +duration=3, +subtitle=nil, +subduration=0, +useSRS=false, +} +function SOUNDFILE:New(FileName,Path,Duration,UseSrs) +local self=BASE:Inherit(self,BASE:New()) +self:F({FileName,Path,Duration,UseSrs}) +self:SetFileName(FileName) +self:SetPlayWithSRS(UseSrs or false) +self:SetPath(Path) +self:SetDuration(Duration) +return self +end +function SOUNDFILE:SetPath(Path) +self:F({Path}) +if not Path then +if self.useSRS then +self.path=lfs.tempdir().."Mission\\l10n\\DEFAULT" +else +self.path="l10n/DEFAULT/" +end +else +self.path=Path +end +local nmax=1000;local n=1 +while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do +self.path=self.path:sub(1,#self.path-1) +n=n+1 +end +self.path=self.path.."/" +self:T("self.path="..self.path) +return self +end +function SOUNDFILE:GetPath() +local path=self.path or"l10n/DEFAULT/" +return path +end +function SOUNDFILE:SetFileName(FileName) +self.filename=FileName or"Hello World.mp3" +return self +end +function SOUNDFILE:GetFileName() +return self.filename +end +function SOUNDFILE:SetDuration(Duration) +if Duration and type(Duration)=="string"then +Duration=tonumber(Duration) +end +self.duration=Duration or 3 +return self +end +function SOUNDFILE:GetDuration() +return self.duration or 3 +end +function SOUNDFILE:GetName() +local path=self:GetPath() +local filename=self:GetFileName() +local name=string.format("%s%s",path,filename) +return name +end +function SOUNDFILE:SetPlayWithSRS(Switch) +self:F({Switch}) +if Switch==true or Switch==nil then +self.useSRS=true +else +self.useSRS=false +end +self:T("self.useSRS="..tostring(self.useSRS)) +return self +end +end +do +SOUNDTEXT={ +ClassName="SOUNDTEXT", +} +function SOUNDTEXT:New(Text,Duration) +local self=BASE:Inherit(self,BASE:New()) +self:SetText(Text) +self:SetDuration(Duration or MSRS.getSpeechTime(Text)) +self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec",self.text,self.duration)) +return self +end +function SOUNDTEXT:SetText(Text) +self.text=Text or"Hello World!" +return self +end +function SOUNDTEXT:SetDuration(Duration) +self.duration=Duration or 3 +return self +end +function SOUNDTEXT:SetGender(Gender) +self.gender=Gender or"female" +return self +end +function SOUNDTEXT:SetCulture(Culture) +self.culture=Culture or"en-GB" +return self +end +function SOUNDTEXT:SetVoice(VoiceName) +self.voice=VoiceName +return self +end +end +RADIO={ +ClassName="RADIO", +FileName="", +Frequency=0, +Modulation=radio.modulation.AM, +Subtitle="", +SubtitleDuration=0, +Power=100, +Loop=false, +alias=nil, +moduhasbeenset=false, +} +function RADIO:New(Positionable) +local self=BASE:Inherit(self,BASE:New()) +self:F(Positionable) +if Positionable:GetPointVec2()then +self.Positionable=Positionable +return self +end +self:E({error="The passed positionable is invalid, no RADIO created!",positionable=Positionable}) +return nil +end +function RADIO:SetAlias(alias) +self.alias=tostring(alias) +return self +end +function RADIO:GetAlias() +return tostring(self.alias) +end +function RADIO:SetFileName(FileName) +self:F2(FileName) +if type(FileName)=="string"then +if FileName:find(".ogg")or FileName:find(".wav")then +if not FileName:find("l10n/DEFAULT/")then +FileName="l10n/DEFAULT/"..FileName +end +self.FileName=FileName +return self +end +end +self:E({"File name invalid. Maybe something wrong with the extension?",FileName}) +return self +end +function RADIO:SetFrequency(Frequency) +self:F2(Frequency) +if type(Frequency)=="number"then +self.Frequency=Frequency +self.HertzFrequency=Frequency*1000000 +if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then +local commandSetFrequency={ +id="SetFrequency", +params={ +frequency=self.HertzFrequency, +modulation=self.Modulation, +} +} +self:T2(commandSetFrequency) +self.Positionable:SetCommand(commandSetFrequency) +end +return self +end +self:E({"Frequency is not a number. Frequency unchanged.",Frequency}) +return self +end +function RADIO:SetModulation(Modulation) +self:F2(Modulation) +if type(Modulation)=="number"then +if Modulation==radio.modulation.AM or Modulation==radio.modulation.FM then +self.Modulation=Modulation +if self.moduhasbeenset==false and Modulation==radio.modulation.FM then +self:SetFrequency(self.Frequency) +end +self.moduhasbeenset=true +return self +end +end +self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.",self.Modulation}) +return self +end +function RADIO:SetPower(Power) +self:F2(Power) +if type(Power)=="number"then +self.Power=math.floor(math.abs(Power)) +else +self:E({"Power is invalid. Power unchanged.",self.Power}) +end +return self +end +function RADIO:SetLoop(Loop) +self:F2(Loop) +if type(Loop)=="boolean"then +self.Loop=Loop +return self +end +self:E({"Loop is invalid. Loop unchanged.",self.Loop}) +return self +end +function RADIO:SetSubtitle(Subtitle,SubtitleDuration) +self:F2({Subtitle,SubtitleDuration}) +if type(Subtitle)=="string"then +self.Subtitle=Subtitle +else +self.Subtitle="" +self:E({"Subtitle is invalid. Subtitle reset.",self.Subtitle}) +end +if type(SubtitleDuration)=="number"then +self.SubtitleDuration=SubtitleDuration +else +self.SubtitleDuration=0 +self:E({"SubtitleDuration is invalid. SubtitleDuration reset.",self.SubtitleDuration}) +end +return self +end +function RADIO:NewGenericTransmission(FileName,Frequency,Modulation,Power,Loop) +self:F({FileName,Frequency,Modulation,Power}) +self:SetFileName(FileName) +if Frequency then self:SetFrequency(Frequency)end +if Modulation then self:SetModulation(Modulation)end +if Power then self:SetPower(Power)end +if Loop then self:SetLoop(Loop)end +return self +end +function RADIO:NewUnitTransmission(FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop) +self:F({FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop}) +self:SetFileName(FileName) +if Modulation then +self:SetModulation(Modulation) +end +if Frequency then +self:SetFrequency(Frequency) +end +if Subtitle then +self:SetSubtitle(Subtitle,SubtitleDuration or 0) +end +if Loop then +self:SetLoop(Loop) +end +return self +end +function RADIO:Broadcast(viatrigger) +self:F({viatrigger=viatrigger}) +if(self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP")and(not viatrigger)then +self:T("Broadcasting from a UNIT or a GROUP") +local commandTransmitMessage={ +id="TransmitMessage", +params={ +file=self.FileName, +duration=self.SubtitleDuration, +subtitle=self.Subtitle, +loop=self.Loop, +}} +self:T3(commandTransmitMessage) +self.Positionable:SetCommand(commandTransmitMessage) +else +self:T("Broadcasting from a POSITIONABLE") +trigger.action.radioTransmission(self.FileName,self.Positionable:GetPositionVec3(),self.Modulation,self.Loop,self.Frequency,self.Power,tostring(self.ID)) +end +return self +end +function RADIO:StopBroadcast() +self:F() +if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then +local commandStopTransmission={id="StopTransmission",params={}} +self.Positionable:SetCommand(commandStopTransmission) +else +trigger.action.stopRadioTransmission(tostring(self.ID)) +end +return self +end +RADIOQUEUE={ +ClassName="RADIOQUEUE", +Debugmode=nil, +lid=nil, +frequency=nil, +modulation=nil, +scheduler=nil, +RQid=nil, +queue={}, +alias=nil, +dt=nil, +delay=nil, +Tlast=nil, +sendercoord=nil, +sendername=nil, +senderinit=nil, +power=nil, +numbers={}, +checking=nil, +schedonce=false, +} +function RADIOQUEUE:New(frequency,modulation,alias) +local self=BASE:Inherit(self,BASE:New()) +self.alias=alias or"My Radio" +self.lid=string.format("RADIOQUEUE %s | ",self.alias) +if frequency==nil then +self:E(self.lid.."ERROR: No frequency specified as first parameter!") +return nil +end +self.frequency=frequency*1000000 +self.modulation=modulation or radio.modulation.AM +self:SetRadioPower() +self.scheduler=SCHEDULER:New() +self.scheduler:NoTrace() +return self +end +function RADIOQUEUE:Start(delay,dt) +self.delay=delay or 1 +self.dt=dt or 0.01 +self:I(self.lid..string.format("Starting RADIOQUEUE %s on Frequency %.2f MHz [modulation=%d] in %.1f seconds (dt=%.3f sec)",self.alias,self.frequency/1000000,self.modulation,self.delay,self.dt)) +if self.schedonce then +self:_CheckRadioQueueDelayed(self.delay) +else +self.RQid=self.scheduler:Schedule(nil,RADIOQUEUE._CheckRadioQueue,{self},self.delay,self.dt) +end +return self +end +function RADIOQUEUE:Stop() +self:I(self.lid.."Stopping RADIOQUEUE.") +self.scheduler:Stop(self.RQid) +self.queue={} +return self +end +function RADIOQUEUE:SetSenderCoordinate(coordinate) +self.sendercoord=coordinate +return self +end +function RADIOQUEUE:SetSenderUnitName(name) +self.sendername=name +return self +end +function RADIOQUEUE:SetRadioPower(power) +self.power=power or 100 +return self +end +function RADIOQUEUE:SetSRS(PathToSRS,Port) +local path=PathToSRS or MSRS.path +local port=Port or MSRS.port +self.msrs=MSRS:New(path,self.frequency/1000000,self.modulation) +self.msrs:SetPort(port) +return self +end +function RADIOQUEUE:SetDigit(digit,filename,duration,path,subtitle,subduration) +local transmission={} +transmission.filename=filename +transmission.duration=duration +transmission.path=path or"l10n/DEFAULT/" +transmission.subtitle=nil +transmission.subduration=nil +if type(digit)=="number"then +digit=tostring(digit) +end +self.numbers[digit]=transmission +return self +end +function RADIOQUEUE:AddTransmission(transmission) +self:F({transmission=transmission}) +transmission.isplaying=false +transmission.Tstarted=nil +table.insert(self.queue,transmission) +if self.schedonce and not self.checking then +self:_CheckRadioQueueDelayed() +end +return self +end +function RADIOQUEUE:NewTransmission(filename,duration,path,tstart,interval,subtitle,subduration) +if not filename then +self:E(self.lid.."ERROR: No filename specified.") +return nil +end +if type(filename)~="string"then +self:E(self.lid.."ERROR: Filename specified is NOT a string.") +return nil +end +if not duration then +self:E(self.lid.."ERROR: No duration of transmission specified.") +return nil +end +if type(duration)~="number"then +self:E(self.lid..string.format("ERROR: Duration specified is NOT a number but type=%s. Filename=%s, duration=%s",type(duration),tostring(filename),tostring(duration))) +return nil +end +local transmission={} +transmission.filename=filename +transmission.duration=duration +transmission.path=path or"l10n/DEFAULT/" +transmission.Tplay=tstart or timer.getAbsTime() +transmission.subtitle=subtitle +transmission.interval=interval or 0 +if transmission.subtitle then +transmission.subduration=subduration or 5 +else +transmission.subduration=nil +end +self:AddTransmission(transmission) +return transmission +end +function RADIOQUEUE:AddSoundFile(soundfile,tstart,interval) +local transmission=self:NewTransmission(soundfile:GetFileName(),soundfile.duration,soundfile:GetPath(),tstart,interval,soundfile.subtitle,soundfile.subduration) +transmission.soundfile=soundfile +return self +end +function RADIOQUEUE:AddSoundText(soundtext,tstart,interval) +local transmission=self:NewTransmission("SoundText.ogg",soundtext.duration,nil,tstart,interval,soundtext.subtitle,soundtext.subduration) +transmission.soundtext=soundtext +return self +end +function RADIOQUEUE:Number2Transmission(number,delay,interval) +local numbers=UTILS.GetCharacters(number) +local wait=0 +for i=1,#numbers do +local n=numbers[i] +local transmission=UTILS.DeepCopy(self.numbers[n]) +transmission.Tplay=timer.getAbsTime()+(delay or 0) +if interval and i==1 then +transmission.interval=interval +end +self:AddTransmission(transmission) +wait=wait+transmission.duration +end +return wait +end +function RADIOQUEUE:Broadcast(transmission) +self:T("Broadcast") +if((transmission.soundfile and transmission.soundfile.useSRS)or transmission.soundtext)and self.msrs then +self:_BroadcastSRS(transmission) +return +end +local sender=self:_GetRadioSender() +local filename=string.format("%s%s",transmission.path,transmission.filename) +if sender then +self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName())) +local commandFrequency={ +id="SetFrequency", +params={ +frequency=self.frequency, +modulation=self.modulation, +}} +sender:SetCommand(commandFrequency) +self.senderinit=true +local subtitle=nil +local duration=nil +if transmission.subtitle and transmission.subduration and transmission.subduration>0 then +subtitle=transmission.subtitle +duration=transmission.subduration +end +local commandTransmit={ +id="TransmitMessage", +params={ +file=filename, +duration=duration, +subtitle=subtitle, +loop=false, +}} +sender:SetCommand(commandTransmit) +if self.Debugmode then +local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"") +MESSAGE:New(text,2,"RADIOQUEUE "..self.alias):ToAll() +end +else +self:T(self.lid..string.format("Broadcasting via trigger.action.radioTransmission()")) +local vec3=nil +if self.sendername then +vec3=self:_GetRadioSenderCoord() +end +if self.sendercoord and not vec3 then +vec3=self.sendercoord:GetVec3() +end +if vec3 then +self:T("Sending") +self:T({filename=filename,vec3=vec3,modulation=self.modulation,frequency=self.frequency,power=self.power}) +trigger.action.radioTransmission(filename,vec3,self.modulation,false,self.frequency,self.power) +if self.Debugmode then +local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"") +MESSAGE:New(string.format(text,filename,transmission.duration,transmission.subtitle or""),5,"RADIOQUEUE "..self.alias):ToAll() +end +else +self:E("ERROR: Could not get vec3 to determine transmission origin! Did you specify a sender and is it still alive?") +end +end +end +function RADIOQUEUE:_BroadcastSRS(transmission) +if transmission.soundfile and transmission.soundfile.useSRS then +self.msrs:PlaySoundFile(transmission.soundfile) +elseif transmission.soundtext then +self.msrs:PlaySoundText(transmission.soundtext) +end +end +function RADIOQUEUE:_CheckRadioQueueDelayed(delay) +self.checking=true +self:ScheduleOnce(delay or self.dt,RADIOQUEUE._CheckRadioQueue,self) +end +function RADIOQUEUE:_CheckRadioQueue() +if#self.queue==0 then +self.checking=false +return +end +local time=timer.getAbsTime() +local playing=false +local next=nil +local remove=nil +for i,_transmission in ipairs(self.queue)do +local transmission=_transmission +if time>=transmission.Tplay then +if transmission.isplaying then +if time>=transmission.Tstarted+transmission.duration then +transmission.isplaying=false +remove=i +self.Tlast=time +else +playing=true +end +else +local Tlast=self.Tlast +if transmission.interval==nil then +if next==nil then +next=transmission +end +else +if Tlast==nil or time-Tlast>=transmission.interval then +next=transmission +else +end +end +if next or Tlast then +break +end +end +else +end +end +if next~=nil and not playing then +self:Broadcast(next) +next.isplaying=true +next.Tstarted=time +end +if remove then +table.remove(self.queue,remove) +end +if self.schedonce then +self:_CheckRadioQueueDelayed() +end +end +function RADIOQUEUE:_GetRadioSender() +local sender=nil +if self.sendername then +sender=UNIT:FindByName(self.sendername) +if sender and sender:IsAlive()and(sender:IsAir()or sender:IsGround())then +return sender +end +end +return nil +end +function RADIOQUEUE:_GetRadioSenderCoord() +local vec3=nil +if self.sendername then +local sender=UNIT:FindByName(self.sendername) +if sender and sender:IsAlive()then +return sender:GetVec3() +end +local sender=STATIC:FindByName(self.sendername,false) +if sender then +return sender:GetVec3() +end +end +return nil +end +RADIOSPEECH={ +ClassName="RADIOSPEECH", +Vocabulary={ +EN={}, +DE={}, +RU={}, +} +} +RADIOSPEECH.Vocabulary.EN={ +["1"]={"1",0.25}, +["2"]={"2",0.25}, +["3"]={"3",0.30}, +["4"]={"4",0.35}, +["5"]={"5",0.35}, +["6"]={"6",0.42}, +["7"]={"7",0.38}, +["8"]={"8",0.20}, +["9"]={"9",0.32}, +["10"]={"10",0.35}, +["11"]={"11",0.40}, +["12"]={"12",0.42}, +["13"]={"13",0.38}, +["14"]={"14",0.42}, +["15"]={"15",0.42}, +["16"]={"16",0.52}, +["17"]={"17",0.59}, +["18"]={"18",0.40}, +["19"]={"19",0.47}, +["20"]={"20",0.38}, +["30"]={"30",0.29}, +["40"]={"40",0.35}, +["50"]={"50",0.32}, +["60"]={"60",0.44}, +["70"]={"70",0.48}, +["80"]={"80",0.26}, +["90"]={"90",0.36}, +["100"]={"100",0.55}, +["200"]={"200",0.55}, +["300"]={"300",0.61}, +["400"]={"400",0.60}, +["500"]={"500",0.61}, +["600"]={"600",0.65}, +["700"]={"700",0.70}, +["800"]={"800",0.54}, +["900"]={"900",0.60}, +["1000"]={"1000",0.60}, +["2000"]={"2000",0.61}, +["3000"]={"3000",0.64}, +["4000"]={"4000",0.62}, +["5000"]={"5000",0.69}, +["6000"]={"6000",0.69}, +["7000"]={"7000",0.75}, +["8000"]={"8000",0.59}, +["9000"]={"9000",0.65}, +["chevy"]={"chevy",0.35}, +["colt"]={"colt",0.35}, +["springfield"]={"springfield",0.65}, +["dodge"]={"dodge",0.35}, +["enfield"]={"enfield",0.5}, +["ford"]={"ford",0.32}, +["pontiac"]={"pontiac",0.55}, +["uzi"]={"uzi",0.28}, +["degrees"]={"degrees",0.5}, +["kilometers"]={"kilometers",0.65}, +["km"]={"kilometers",0.65}, +["miles"]={"miles",0.45}, +["meters"]={"meters",0.41}, +["mi"]={"miles",0.45}, +["feet"]={"feet",0.29}, +["br"]={"br",1.1}, +["bra"]={"bra",0.3}, +["returning to base"]={"returning_to_base",0.85}, +["on route to ground target"]={"on_route_to_ground_target",1.05}, +["intercepting bogeys"]={"intercepting_bogeys",1.00}, +["engaging ground target"]={"engaging_ground_target",1.20}, +["engaging bogeys"]={"engaging_bogeys",0.81}, +["wheels up"]={"wheels_up",0.42}, +["landing at base"]={"landing at base",0.8}, +["patrolling"]={"patrolling",0.55}, +["for"]={"for",0.31}, +["and"]={"and",0.31}, +["at"]={"at",0.3}, +["dot"]={"dot",0.26}, +["defender"]={"defender",0.45}, +} +RADIOSPEECH.Vocabulary.RU={ +["1"]={"1",0.34}, +["2"]={"2",0.30}, +["3"]={"3",0.23}, +["4"]={"4",0.51}, +["5"]={"5",0.31}, +["6"]={"6",0.44}, +["7"]={"7",0.25}, +["8"]={"8",0.43}, +["9"]={"9",0.45}, +["10"]={"10",0.53}, +["11"]={"11",0.66}, +["12"]={"12",0.70}, +["13"]={"13",0.66}, +["14"]={"14",0.80}, +["15"]={"15",0.65}, +["16"]={"16",0.75}, +["17"]={"17",0.74}, +["18"]={"18",0.85}, +["19"]={"19",0.80}, +["20"]={"20",0.58}, +["30"]={"30",0.51}, +["40"]={"40",0.51}, +["50"]={"50",0.67}, +["60"]={"60",0.76}, +["70"]={"70",0.68}, +["80"]={"80",0.84}, +["90"]={"90",0.71}, +["100"]={"100",0.35}, +["200"]={"200",0.59}, +["300"]={"300",0.53}, +["400"]={"400",0.70}, +["500"]={"500",0.50}, +["600"]={"600",0.58}, +["700"]={"700",0.64}, +["800"]={"800",0.77}, +["900"]={"900",0.75}, +["1000"]={"1000",0.87}, +["2000"]={"2000",0.83}, +["3000"]={"3000",0.84}, +["4000"]={"4000",1.00}, +["5000"]={"5000",0.77}, +["6000"]={"6000",0.90}, +["7000"]={"7000",0.77}, +["8000"]={"8000",0.92}, +["9000"]={"9000",0.87}, +["градусы"]={"degrees",0.5}, +["километры"]={"kilometers",0.65}, +["km"]={"kilometers",0.65}, +["мили"]={"miles",0.45}, +["mi"]={"miles",0.45}, +["метров"]={"meters",0.41}, +["m"]={"meters",0.41}, +["ноги"]={"feet",0.37}, +["br"]={"br",1.1}, +["bra"]={"bra",0.3}, +["возвращение на базу"]={"returning_to_base",1.40}, +["на пути к наземной цели"]={"on_route_to_ground_target",1.45}, +["перехват боги"]={"intercepting_bogeys",1.22}, +["поражение наземной цели"]={"engaging_ground_target",1.53}, +["привлечение болотных птиц"]={"engaging_bogeys",1.68}, +["колёса вверх..."]={"wheels_up",0.92}, +["посадка на базу"]={"landing at base",1.04}, +["патрулирование"]={"patrolling",0.96}, +["для"]={"for",0.27}, +["и"]={"and",0.17}, +["на сайте"]={"at",0.19}, +["точка"]={"dot",0.51}, +["защитник"]={"defender",0.45}, +} +function RADIOSPEECH:New(frequency,modulation) +local self=BASE:Inherit(self,RADIOQUEUE:New(frequency,modulation)) +self.Language="EN" +self:BuildTree() +return self +end +function RADIOSPEECH:SetLanguage(Langauge) +self.Language=Langauge +end +function RADIOSPEECH:AddSentenceToSpeech(RemainingSentence,Speech,Sentence,Data) +self:I({RemainingSentence,Speech,Sentence,Data}) +local Token,RemainingSentence=RemainingSentence:match("^ *([^ ]+)(.*)") +self:I({Token=Token,RemainingSentence=RemainingSentence}) +if Token then +if not Speech[Token]then +Speech[Token]={} +if RemainingSentence and RemainingSentence~=""then +Speech[Token].Next={} +self:AddSentenceToSpeech(RemainingSentence,Speech[Token].Next,Sentence,Data) +else +Speech[Token].Sentence=Sentence +Speech[Token].Data=Data +end +end +end +end +function RADIOSPEECH:BuildTree() +self.Speech={} +for Language,Sentences in pairs(self.Vocabulary)do +self:I({Language=Language,Sentences=Sentences}) +self.Speech[Language]={} +for Sentence,Data in pairs(Sentences)do +self:I({Sentence=Sentence,Data=Data}) +self:AddSentenceToSpeech(Sentence,self.Speech[Language],Sentence,Data) +end +end +self:I({Speech=self.Speech}) +return self +end +function RADIOSPEECH:SpeakWords(Sentence,Speech,Language) +local OriginalSentence=Sentence +local Word,RemainderSentence=Sentence:match("^[., ]*([^ .,]+)(.*)") +self:I({Word=Word,Speech=Speech[Word],RemainderSentence=RemainderSentence}) +if Word then +if Word~=""and tonumber(Word)==nil then +Word=Word:lower() +if Speech[Word]then +if Speech[Word].Next==nil then +self:I({Sentence=Speech[Word].Sentence,Data=Speech[Word].Data}) +self:NewTransmission(Speech[Word].Data[1]..".wav",Speech[Word].Data[2],Language.."/") +else +if RemainderSentence and RemainderSentence~=""then +return self:SpeakWords(RemainderSentence,Speech[Word].Next,Language) +end +end +end +return RemainderSentence +end +return OriginalSentence +else +return"" +end +end +function RADIOSPEECH:SpeakDigits(Sentence,Speech,Langauge) +local OriginalSentence=Sentence +local Digits,RemainderSentence=Sentence:match("^[., ]*([^ .,]+)(.*)") +self:I({Digits=Digits,Speech=Speech[Digits],RemainderSentence=RemainderSentence}) +if Digits then +if Digits~=""and tonumber(Digits)~=nil then +local Number=tonumber(Digits) +local Multiple=nil +while Number>=0 do +if Number>1000 then +Multiple=math.floor(Number/1000)*1000 +elseif Number>100 then +Multiple=math.floor(Number/100)*100 +elseif Number>20 then +Multiple=math.floor(Number/10)*10 +elseif Number>=0 then +Multiple=Number +end +Sentence=tostring(Multiple) +if Speech[Sentence]then +self:I({Speech=Speech[Sentence].Sentence,Data=Speech[Sentence].Data}) +self:NewTransmission(Speech[Sentence].Data[1]..".wav",Speech[Sentence].Data[2],Langauge.."/") +end +Number=Number-Multiple +Number=(Number==0)and-1 or Number +end +return RemainderSentence +end +return OriginalSentence +else +return"" +end +end +function RADIOSPEECH:Speak(Sentence,Language) +self:I({Sentence,Language}) +local Language=Language or"EN" +self:I({Language=Language}) +local Speech=self.Speech[Language] +self:I({Speech=Speech,Language=Language}) +self:NewTransmission("_In.wav",0.52,Language.."/") +repeat +Sentence=self:SpeakWords(Sentence,Speech,Language) +self:I({Sentence=Sentence}) +Sentence=self:SpeakDigits(Sentence,Speech,Language) +self:I({Sentence=Sentence}) +until not Sentence or Sentence=="" +self:NewTransmission("_Out.wav",0.28,Language.."/") +end +MSRS={ +ClassName="MSRS", +lid=nil, +port=5002, +name="MSRS", +backend="srsexe", +frequencies={}, +modulations={}, +coalition=0, +gender="female", +culture=nil, +voice=nil, +volume=1, +speed=1, +coordinate=nil, +provider="win", +Label="ROBOT", +ConfigFileName="Moose_MSRS.lua", +ConfigFilePath="Config\\", +ConfigLoaded=false, +poptions={}, +UsePowerShell=false, +} +MSRS.version="0.3.3" +MSRS.Voices={ +Amazon={ +Generative={ +en_AU={ +Olivia="Olivia", +}, +en_GB={ +Amy="Amy", +}, +en_US={ +Danielle="Danielle", +Joanna="Joanna", +Ruth="Ruth", +Stephen="Stephen", +}, +fr_FR={ +["Léa"]="Léa", +["Rémi"]="Rémi", +}, +de_DE={ +Vicki="Vicki", +Daniel="Daniel", +}, +it_IT={ +Bianca="Bianca", +Adriano="Adriano", +}, +es_ES={ +Lucia="Lucia", +Sergio="Sergio", +}, +}, +LongForm={ +en_US={ +Danielle="Danielle", +Gregory="Gregory", +Ivy="Ivy", +Ruth="Ruth", +Patrick="Patrick", +}, +es_ES={ +Alba="Alba", +["Raúl"]="Raúl", +}, +}, +Neural={ +en_AU={ +Olivia="Olivia", +}, +en_GB={ +Amy="Amy", +Emma="Emma", +Brian="Brian", +Arthur="Arthur", +}, +en_US={ +Danielle="Danielle", +Gregory="Gregory", +Ivy="Ivy", +Joanna="Joanna", +Kendra="Kendra", +Kimberly="Kimberly", +Salli="Salli", +Joey="Joey", +Kevin="Kevin", +Ruth="Ruth", +Stephen="Stephen", +}, +fr_FR={ +["Léa"]="Léa", +["Rémi"]="Rémi", +}, +de_DE={ +Vicki="Vicki", +Daniel="Daniel", +}, +it_IT={ +Bianca="Bianca", +Adriano="Adriano", +}, +es_ES={ +Lucia="Lucia", +Sergio="Sergio", +}, +}, +Standard={ +en_AU={ +Nicole="Nicole", +Russel="Russel", +}, +en_GB={ +Amy="Amy", +Emma="Emma", +Brian="Brian", +}, +en_IN={ +Aditi="Aditi", +Raveena="Raveena", +}, +en_US={ +Ivy="Ivy", +Joanna="Joanna", +Kendra="Kendra", +Kimberly="Kimberly", +Salli="Salli", +Joey="Joey", +Kevin="Kevin", +}, +fr_FR={ +Celine="Celine", +["Léa"]="Léa", +Mathieu="Mathieu", +}, +de_DE={ +Marlene="Marlene", +Vicki="Vicki", +Hans="Hans", +}, +it_IT={ +Carla="Carla", +Bianca="Bianca", +Giorgio="Giorgio", +}, +es_ES={ +Conchita="Conchita", +Lucia="Lucia", +Enrique="Enrique", +}, +}, +}, +Microsoft={ +["Hedda"]="Microsoft Hedda Desktop", +["Hazel"]="Microsoft Hazel Desktop", +["David"]="Microsoft David Desktop", +["Zira"]="Microsoft Zira Desktop", +["Hortense"]="Microsoft Hortense Desktop", +["de_DE_Hedda"]="Microsoft Hedda Desktop", +["en_GB_Hazel"]="Microsoft Hazel Desktop", +["en_US_David"]="Microsoft David Desktop", +["en_US_Zira"]="Microsoft Zira Desktop", +["fr_FR_Hortense"]="Microsoft Hortense Desktop", +}, +MicrosoftGRPC={ +["Hazel"]="Hazel", +["George"]="George", +["Susan"]="Susan", +["David"]="David", +["Zira"]="Zira", +["Mark"]="Mark", +["James"]="James", +["Catherine"]="Catherine", +["Richard"]="Richard", +["Linda"]="Linda", +["Ravi"]="Ravi", +["Heera"]="Heera", +["Sean"]="Sean", +["en_GB_Hazel"]="Hazel", +["en_GB_George"]="George", +["en_GB_Susan"]="Susan", +["en_US_David"]="David", +["en_US_Zira"]="Zira", +["en_US_Mark"]="Mark", +["en_AU_James"]="James", +["en_AU_Catherine"]="Catherine", +["en_CA_Richard"]="Richard", +["en_CA_Linda"]="Linda", +["en_IN_Ravi"]="Ravi", +["en_IN_Heera"]="Heera", +["en_IR_Sean"]="Sean", +}, +Google={ +Standard={ +["en_AU_Standard_A"]='en-AU-Standard-A', +["en_AU_Standard_B"]='en-AU-Standard-B', +["en_AU_Standard_C"]='en-AU-Standard-C', +["en_AU_Standard_D"]='en-AU-Standard-D', +["en_IN_Standard_A"]='en-IN-Standard-A', +["en_IN_Standard_B"]='en-IN-Standard-B', +["en_IN_Standard_C"]='en-IN-Standard-C', +["en_IN_Standard_D"]='en-IN-Standard-D', +["en_IN_Standard_E"]='en-IN-Standard-E', +["en_IN_Standard_F"]='en-IN-Standard-F', +["en_GB_Standard_A"]='en-GB-Standard-A', +["en_GB_Standard_B"]='en-GB-Standard-B', +["en_GB_Standard_C"]='en-GB-Standard-C', +["en_GB_Standard_D"]='en-GB-Standard-D', +["en_GB_Standard_F"]='en-GB-Standard-F', +["en_GB_Standard_N"]='en-GB-Standard-N', +["en_GB_Standard_O"]='en-GB-Standard-O', +["en_US_Standard_A"]='en-US-Standard-A', +["en_US_Standard_B"]='en-US-Standard-B', +["en_US_Standard_C"]='en-US-Standard-C', +["en_US_Standard_D"]='en-US-Standard-D', +["en_US_Standard_E"]='en-US-Standard-E', +["en_US_Standard_F"]='en-US-Standard-F', +["en_US_Standard_G"]='en-US-Standard-G', +["en_US_Standard_H"]='en-US-Standard-H', +["en_US_Standard_I"]='en-US-Standard-I', +["en_US_Standard_J"]='en-US-Standard-J', +["fr_FR_Standard_A"]="fr-FR-Standard-F", +["fr_FR_Standard_B"]="fr-FR-Standard-G", +["fr_FR_Standard_C"]="fr-FR-Standard-F", +["fr_FR_Standard_D"]="fr-FR-Standard-G", +["fr_FR_Standard_E"]="fr-FR-Standard-F", +["fr_FR_Standard_G"]="fr-FR-Standard-G", +["fr_FR_Standard_F"]="fr-FR-Standard-F", +["de_DE_Standard_A"]='de-DE-Standard-A', +["de_DE_Standard_B"]='de-DE-Standard-B', +["de_DE_Standard_C"]='de-DE-Standard-C', +["de_DE_Standard_D"]='de-DE-Standard-D', +["de_DE_Standard_E"]='de-DE-Standard-E', +["de_DE_Standard_F"]='de-DE-Standard-F', +["de_DE_Standard_G"]='de-DE-Standard-G', +["de_DE_Standard_H"]='de-DE-Standard-H', +["es_ES_Standard_A"]="es-ES-Standard-E", +["es_ES_Standard_B"]="es-ES-Standard-F", +["es_ES_Standard_C"]="es-ES-Standard-E", +["es_ES_Standard_D"]="es-ES-Standard-F", +["es_ES_Standard_E"]="es-ES-Standard-E", +["es_ES_Standard_F"]="es-ES-Standard-F", +["it_IT_Standard_A"]="it-IT-Standard-E", +["it_IT_Standard_B"]="it-IT-Standard-E", +["it_IT_Standard_C"]="it-IT-Standard-F", +["it_IT_Standard_D"]="it-IT-Standard-F", +["it_IT_Standard_E"]="it-IT-Standard-E", +["it_IT_Standard_F"]="it-IT-Standard-F", +}, +Wavenet={ +["en_AU_Wavenet_A"]='en-AU-Wavenet-A', +["en_AU_Wavenet_B"]='en-AU-Wavenet-B', +["en_AU_Wavenet_C"]='en-AU-Wavenet-C', +["en_AU_Wavenet_D"]='en-AU-Wavenet-D', +["en_IN_Wavenet_A"]='en-IN-Wavenet-A', +["en_IN_Wavenet_B"]='en-IN-Wavenet-B', +["en_IN_Wavenet_C"]='en-IN-Wavenet-C', +["en_IN_Wavenet_D"]='en-IN-Wavenet-D', +["en_IN_Wavenet_E"]='en-IN-Wavenet-E', +["en_IN_Wavenet_F"]='en-IN-Wavenet-F', +["en_GB_Wavenet_A"]='en-GB-Wavenet-A', +["en_GB_Wavenet_B"]='en-GB-Wavenet-B', +["en_GB_Wavenet_C"]='en-GB-Wavenet-C', +["en_GB_Wavenet_D"]='en-GB-Wavenet-D', +["en_GB_Wavenet_F"]='en-GB-Wavenet-F', +["en_GB_Wavenet_O"]='en-GB-Wavenet-O', +["en_GB_Wavenet_N"]='en-GB-Wavenet-N', +["en_US_Wavenet_A"]='en-US-Wavenet-A', +["en_US_Wavenet_B"]='en-US-Wavenet-B', +["en_US_Wavenet_C"]='en-US-Wavenet-C', +["en_US_Wavenet_D"]='en-US-Wavenet-D', +["en_US_Wavenet_E"]='en-US-Wavenet-E', +["en_US_Wavenet_F"]='en-US-Wavenet-F', +["en_US_Wavenet_G"]='en-US-Wavenet-G', +["en_US_Wavenet_H"]='en-US-Wavenet-H', +["en_US_Wavenet_I"]='en-US-Wavenet-I', +["en_US_Wavenet_J"]='en-US-Wavenet-J', +["fr_FR_Wavenet_A"]="fr-FR-Wavenet-F", +["fr_FR_Wavenet_B"]="fr-FR-Wavenet-G", +["fr_FR_Wavenet_C"]="fr-FR-Wavenet-F", +["fr_FR_Wavenet_D"]="fr-FR-Wavenet-G", +["fr_FR_Wavenet_E"]="fr-FR-Wavenet-F", +["fr_FR_Wavenet_G"]="fr-FR-Wavenet-G", +["fr_FR_Wavenet_F"]="fr-FR-Wavenet-F", +["de_DE_Wavenet_A"]='de-DE-Wavenet-A', +["de_DE_Wavenet_B"]='de-DE-Wavenet-B', +["de_DE_Wavenet_C"]='de-DE-Wavenet-C', +["de_DE_Wavenet_D"]='de-DE-Wavenet-D', +["de_DE_Wavenet_E"]='de-DE-Wavenet-E', +["de_DE_Wavenet_F"]='de-DE-Wavenet-F', +["de_DE_Wavenet_G"]='de-DE-Wavenet-G', +["de_DE_Wavenet_H"]='de-DE-Wavenet-H', +["es_ES_Wavenet_B"]="es-ES-Wavenet-E", +["es_ES_Wavenet_C"]="es-ES-Wavenet-F", +["es_ES_Wavenet_D"]="es-ES-Wavenet-E", +["es_ES_Wavenet_E"]="es-ES-Wavenet-E", +["es_ES_Wavenet_F"]="es-ES-Wavenet-F", +["it_IT_Wavenet_A"]="it-IT-Wavenet-E", +["it_IT_Wavenet_B"]="it-IT-Wavenet-E", +["it_IT_Wavenet_C"]="it-IT-Wavenet-F", +["it_IT_Wavenet_D"]="it-IT-Wavenet-F", +["it_IT_Wavenet_E"]="it-IT-Wavenet-E", +["it_IT_Wavenet_F"]="it-IT-Wavenet-F", +}, +Chirp3HD={ +["en_GB_Chirp3_HD_Aoede"]='en-GB-Chirp3-HD-Aoede', +["en_GB_Chirp3_HD_Charon"]='en-GB-Chirp3-HD-Charon', +["en_GB_Chirp3_HD_Fenrir"]='en-GB-Chirp3-HD-Fenrir', +["en_GB_Chirp3_HD_Kore"]='en-GB-Chirp3-HD-Kore', +["en_GB_Chirp3_HD_Leda"]='en-GB-Chirp3-HD-Leda', +["en_GB_Chirp3_HD_Orus"]='en-GB-Chirp3-HD-Orus', +["en_GB_Chirp3_HD_Puck"]='en-GB-Chirp3-HD-Puck', +["en_GB_Chirp3_HD_Zephyr"]='en-GB-Chirp3-HD-Zephyr', +["en_US_Chirp3_HD_Charon"]='en-US-Chirp3-HD-Charon', +["en_US_Chirp3_HD_Fenrir"]='en-US-Chirp3-HD-Fenrir', +["en_US_Chirp3_HD_Kore"]='en-US-Chirp3-HD-Kore', +["en_US_Chirp3_HD_Leda"]='en-US-Chirp3-HD-Leda', +["en_US_Chirp3_HD_Orus"]='en-US-Chirp3-HD-Orus', +["en_US_Chirp3_HD_Puck"]='en-US-Chirp3-HD-Puck', +["de_DE_Chirp3_HD_Aoede"]='de-DE-Chirp3-HD-Aoede', +["de_DE_Chirp3_HD_Charon"]='de-DE-Chirp3-HD-Charon', +["de_DE_Chirp3_HD_Fenrir"]='de-DE-Chirp3-HD-Fenrir', +["de_DE_Chirp3_HD_Kore"]='de-DE-Chirp3-HD-Kore', +["de_DE_Chirp3_HD_Leda"]='de-DE-Chirp3-HD-Leda', +["de_DE_Chirp3_HD_Orus"]='de-DE-Chirp3-HD-Orus', +["de_DE_Chirp3_HD_Puck"]='de-DE-Chirp3-HD-Puck', +["de_DE_Chirp3_HD_Zephyr"]='de-DE-Chirp3-HD-Zephyr', +["en_AU_Chirp3_HD_Aoede"]='en-AU-Chirp3-HD-Aoede', +["en_AU_Chirp3_HD_Charon"]='en-AU-Chirp3-HD-Charon', +["en_AU_Chirp3_HD_Fenrir"]='en-AU-Chirp3-HD-Fenrir', +["en_AU_Chirp3_HD_Kore"]='en-AU-Chirp3-HD-Kore', +["en_AU_Chirp3_HD_Leda"]='en-AU-Chirp3-HD-Leda', +["en_AU_Chirp3_HD_Orus"]='en-AU-Chirp3-HD-Orus', +["en_AU_Chirp3_HD_Puck"]='en-AU-Chirp3-HD-Puck', +["en_AU_Chirp3_HD_Zephyr"]='en-AU-Chirp3-HD-Zephyr', +["en_IN_Chirp3_HD_Aoede"]='en-IN-Chirp3-HD-Aoede', +["en_IN_Chirp3_HD_Charon"]='en-IN-Chirp3-HD-Charon', +["en_IN_Chirp3_HD_Fenrir"]='en-IN-Chirp3-HD-Fenrir', +["en_IN_Chirp3_HD_Kore"]='en-IN-Chirp3-HD-Kore', +["en_IN_Chirp3_HD_Leda"]='en-IN-Chirp3-HD-Leda', +["en_IN_Chirp3_HD_Orus"]='en-IN-Chirp3-HD-Orus', +}, +ChirpHD={ +["en_US_Chirp_HD_D"]='en-US-Chirp-HD-D', +["en_US_Chirp_HD_F"]='en-US-Chirp-HD-F', +["en_US_Chirp_HD_O"]='en-US-Chirp-HD-O', +["de_DE_Chirp_HD_D"]='de-DE-Chirp-HD-D', +["de_DE_Chirp_HD_F"]='de-DE-Chirp-HD-F', +["de_DE_Chirp_HD_O"]='de-DE-Chirp-HD-O', +["en_AU_Chirp_HD_D"]='en-AU-Chirp-HD-D', +["en_AU_Chirp_HD_F"]='en-AU-Chirp-HD-F', +["en_AU_Chirp_HD_O"]='en-AU-Chirp-HD-O', +["en_IN_Chirp_HD_D"]='en-IN-Chirp-HD-D', +["en_IN_Chirp_HD_F"]='en-IN-Chirp-HD-F', +["en_IN_Chirp_HD_O"]='en-IN-Chirp-HD-O', +}, +}, +Neural2={ +["en_GB_Neural2_A"]='en-GB-Neural2-A', +["en_GB_Neural2_B"]='en-GB-Neural2-B', +["en_GB_Neural2_C"]='en-GB-Neural2-C', +["en_GB_Neural2_D"]='en-GB-Neural2-D', +["en_GB_Neural2_F"]='en-GB-Neural2-F', +["en_GB_Neural2_N"]='en-GB-Neural2-N', +["en_GB_Neural2_O"]='en-GB-Neural2-O', +["en_US_Neural2_A"]='en-US-Neural2-A', +["en_US_Neural2_C"]='en-US-Neural2-C', +["en_US_Neural2_D"]='en-US-Neural2-D', +["en_US_Neural2_E"]='en-US-Neural2-E', +["en_US_Neural2_F"]='en-US-Neural2-F', +["en_US_Neural2_G"]='en-US-Neural2-G', +["en_US_Neural2_H"]='en-US-Neural2-H', +["en_US_Neural2_I"]='en-US-Neural2-I', +["en_US_Neural2_J"]='en-US-Neural2-J', +["de_DE_Neural2_G"]='de-DE-Neural2-G', +["de_DE_Neural2_H"]='de-DE-Neural2-H', +["en_AU_Neural2_A"]='en-AU-Neural2-A', +["en_AU_Neural2_B"]='en-AU-Neural2-B', +["en_AU_Neural2_C"]='en-AU-Neural2-C', +["en_AU_Neural2_D"]='en-AU-Neural2-D', +["en_IN_Neural2_A"]='en-IN-Neural2-A', +["en_IN_Neural2_B"]='en-IN-Neural2-B', +["en_IN_Neural2_C"]='en-IN-Neural2-C', +["en_IN_Neural2_D"]='en-IN-Neural2-D', +}, +News={ +["en_GB_News_G"]='en-GB-News-G', +["en_GB_News_H"]='en-GB-News-H', +["en_GB_News_I"]='en-GB-News-I', +["en_GB_News_J"]='en-GB-News-J', +["en_GB_News_K"]='en-GB-News-K', +["en_GB_News_L"]='en-GB-News-L', +["en_GB_News_M"]='en-GB-News-M', +["en_US_News_K"]='en-US-News-K', +["en_US_News_L"]='en-US-News-L', +["en_US_News_N"]='en-US-News-N', +["en_AU_News_E"]='en-AU-News-E', +["en_AU_News_F"]='en-AU-News-F', +["en_AU_News_G"]='en-AU-News-G', +}, +Casual={ +["en_US_Casual_K"]='en-US-Casual-K', +}, +Polyglot={ +["en_US_Polyglot_1"]='en-US-Polyglot-1', +["de_DE_Polyglot_1"]='de-DE-Polyglot-1', +["en_AU_Polyglot_1"]='en-AU-Polyglot-1', +}, +Studio={ +["en_GB_Studio_B"]='en-GB-Studio-B', +["en_GB_Studio_C"]='en-GB-Studio-C', +["en_US_Studio_O"]='en-US-Studio-O', +["en_US_Studio_Q"]='en-US-Studio-Q', +["de_DE_Studio_B"]='de-DE-Studio-B', +["de_DE_Studio_C"]='de-DE-Studio-C', +}, +} +MSRS.Backend={ +SRSEXE="srsexe", +GRPC="grpc", +} +MSRS.Provider={ +WINDOWS="win", +GOOGLE="gcloud", +AZURE="azure", +AMAZON="aws", +} +function MSRS.uuid() +local random=math.random +local template='yxxx-xxxxxxxxxxxx' +return string.gsub(template,'[xy]',function(c) +local v=(c=='x')and random(0,0xf)or random(8,0xb) +return string.format('%x',v) +end) +end +function MSRS:New(Path,Frequency,Modulation,Backend) +local self=BASE:Inherit(self,BASE:New()) +self:F({Path,Frequency,Modulation,Backend}) +Frequency=Frequency or 143 +Modulation=Modulation or radio.modulation.AM +self.lid=string.format("%s-%s | ","unknown",self.version) +if not self.ConfigLoaded then +self:SetPath(Path) +self:SetPort() +self:SetFrequencies(Frequency) +self:SetModulations(Modulation) +self:SetGender() +self:SetCoalition() +self:SetLabel() +self:SetVolume() +self:SetBackend(Backend) +else +if Path then +self:SetPath(Path) +end +if Frequency then +self:SetFrequencies(Frequency) +end +if Modulation then +self:SetModulations(Modulation) +end +if Backend then +self:SetBackend(Backend) +end +end +self.lid=string.format("%s-%s | ",self.name,self.version) +if not io or not os then +self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!") +end +return self +end +function MSRS:SetBackend(Backend) +self:F({Backend=Backend}) +Backend=Backend or MSRS.Backend.SRSEXE +local function Checker(back) +local ok=false +for _,_backend in pairs(MSRS.Backend)do +if tostring(back)==_backend then ok=true end +end +return ok +end +if Checker(Backend)then +self.backend=Backend +else +MESSAGE:New("ERROR: Backend "..tostring(Backend).." is not supported!",30,"MSRS",true):ToLog():ToAll() +end +return self +end +function MSRS:SetBackendGRPC() +self:F() +self:SetBackend(MSRS.Backend.GRPC) +return self +end +function MSRS:SetBackendSRSEXE() +self:F() +self:SetBackend(MSRS.Backend.SRSEXE) +return self +end +function MSRS.SetDefaultBackend(Backend) +MSRS.backend=Backend or MSRS.Backend.SRSEXE +end +function MSRS.SetDefaultBackendGRPC() +MSRS.backend=MSRS.Backend.GRPC +end +function MSRS:GetBackend() +return self.backend +end +function MSRS:SetPath(Path) +self:F({Path=Path}) +self.path=Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +local n=1;local nmax=1000 +while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do +self.path=self.path:sub(1,#self.path-1) +n=n+1 +end +self:F(string.format("SRS path=%s",self:GetPath())) +return self +end +function MSRS:GetPath() +return self.path +end +function MSRS:SetVolume(Volume) +self:F({Volume=Volume}) +local volume=Volume or 1 +if volume>1 then volume=1 elseif volume<0 then volume=0 end +self.volume=volume +return self +end +function MSRS:GetVolume() +return self.volume +end +function MSRS:SetLabel(Label) +self:F({Label=Label}) +self.Label=Label or"ROBOT" +return self +end +function MSRS:GetLabel() +return self.Label +end +function MSRS:SetPort(Port) +self:F({Port=Port}) +self.port=Port or 5002 +self:T(string.format("SRS port=%s",self:GetPort())) +return self +end +function MSRS:GetPort() +return self.port +end +function MSRS:SetCoalition(Coalition) +self:F({Coalition=Coalition}) +self.coalition=Coalition or 0 +return self +end +function MSRS:GetCoalition() +return self.coalition +end +function MSRS:SetFrequencies(Frequencies) +self:F(Frequencies) +self.frequencies=UTILS.EnsureTable(Frequencies,false) +return self +end +function MSRS:AddFrequencies(Frequencies) +self:F(Frequencies) +for _,_freq in pairs(UTILS.EnsureTable(Frequencies,false))do +self:T(self.lid..string.format("Adding frequency %s",tostring(_freq))) +table.insert(self.frequencies,_freq) +end +return self +end +function MSRS:GetFrequencies() +return self.frequencies +end +function MSRS:SetModulations(Modulations) +self:F(Modulations) +self.modulations=UTILS.EnsureTable(Modulations,false) +self:T(self.lid.."Modulations:") +self:T(self.modulations) +return self +end +function MSRS:AddModulations(Modulations) +self:F(Modulations) +for _,_mod in pairs(UTILS.EnsureTable(Modulations,false))do +table.insert(self.modulations,_mod) +end +return self +end +function MSRS:GetModulations() +return self.modulations +end +function MSRS:SetGender(Gender) +self:F({Gender=Gender}) +Gender=Gender or"female" +self.gender=Gender:lower() +self:T("Setting gender to "..tostring(self.gender)) +return self +end +function MSRS:SetCulture(Culture) +self:F({Culture=Culture}) +self.culture=Culture +return self +end +function MSRS:SetVoice(Voice) +self:F({Voice=Voice}) +self.voice=Voice +return self +end +function MSRS:SetVoiceProvider(Voice,Provider) +self:F({Voice=Voice,Provider=Provider}) +self.poptions=self.poptions or{} +self.poptions[Provider or self:GetProvider()].voice=Voice +return self +end +function MSRS:SetVoiceWindows(Voice) +self:F({Voice=Voice}) +self:SetVoiceProvider(Voice or"Microsoft Hazel Desktop",MSRS.Provider.WINDOWS) +return self +end +function MSRS:SetVoiceGoogle(Voice) +self:F({Voice=Voice}) +self:SetVoiceProvider(Voice or MSRS.Voices.Google.Standard.en_GB_Standard_A,MSRS.Provider.GOOGLE) +return self +end +function MSRS:SetVoiceAzure(Voice) +self:F({Voice=Voice}) +self:SetVoiceProvider(Voice or"en-US-AriaNeural",MSRS.Provider.AZURE) +return self +end +function MSRS:SetVoiceAmazon(Voice) +self:F({Voice=Voice}) +self:SetVoiceProvider(Voice or"Brian",MSRS.Provider.AMAZON) +return self +end +function MSRS:GetVoice(Provider) +Provider=Provider or self.provider +if Provider and self.poptions[Provider]and self.poptions[Provider].voice then +return self.poptions[Provider].voice +else +return self.voice +end +end +function MSRS:SetCoordinate(Coordinate) +self:F(Coordinate) +self.coordinate=Coordinate +return self +end +function MSRS:SetGoogle(PathToCredentials) +self:F({PathToCredentials=PathToCredentials}) +if PathToCredentials then +self.provider=MSRS.Provider.GOOGLE +self:SetProviderOptionsGoogle(PathToCredentials,PathToCredentials) +end +return self +end +function MSRS:SetGoogleAPIKey(APIKey) +self:F({APIKey=APIKey}) +if APIKey then +self.provider=MSRS.Provider.GOOGLE +if self.poptions[MSRS.Provider.GOOGLE]then +self.poptions[MSRS.Provider.GOOGLE].key=APIKey +else +self:SetProviderOptionsGoogle(nil,APIKey) +end +end +return self +end +function MSRS:SetProvider(Provider) +BASE:F({Provider=Provider}) +if self then +self.provider=Provider or MSRS.Provider.WINDOWS +return self +else +MSRS.provider=Provider or MSRS.Provider.WINDOWS +end +return +end +function MSRS:GetProvider() +return self.provider or MSRS.Provider.WINDOWS +end +function MSRS:SetProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region) +BASE:F({Provider,CredentialsFile,AccessKey,SecretKey,Region}) +local option=MSRS._CreateProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region) +if self then +self.poptions=self.poptions or{} +self.poptions[Provider]=option +else +MSRS.poptions=MSRS.poptions or{} +MSRS.poptions[Provider]=option +end +return option +end +function MSRS._CreateProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region) +BASE:F({Provider,CredentialsFile,AccessKey,SecretKey,Region}) +local option={} +option.provider=Provider +option.credentials=CredentialsFile +option.key=AccessKey +option.secret=SecretKey +option.region=Region +return option +end +function MSRS:SetProviderOptionsGoogle(CredentialsFile,AccessKey) +self:F({CredentialsFile,AccessKey}) +self:SetProviderOptions(MSRS.Provider.GOOGLE,CredentialsFile,AccessKey) +return self +end +function MSRS:SetProviderOptionsAmazon(AccessKey,SecretKey,Region) +self:F({AccessKey,SecretKey,Region}) +self:SetProviderOptions(MSRS.Provider.AMAZON,nil,AccessKey,SecretKey,Region) +return self +end +function MSRS:SetProviderOptionsAzure(AccessKey,Region) +self:F({AccessKey,Region}) +self:SetProviderOptions(MSRS.Provider.AZURE,nil,AccessKey,nil,Region) +return self +end +function MSRS:GetProviderOptions(Provider) +return self.poptions[Provider or self.provider]or{} +end +function MSRS:SetTTSProviderGoogle() +self:F() +self:SetProvider(MSRS.Provider.GOOGLE) +return self +end +function MSRS:SetTTSProviderMicrosoft() +self:F() +self:SetProvider(MSRS.Provider.WINDOWS) +return self +end +function MSRS:SetTTSProviderAzure() +self:F() +self:SetProvider(MSRS.Provider.AZURE) +return self +end +function MSRS:SetTTSProviderAmazon() +self:F() +self:SetProvider(MSRS.Provider.AMAZON) +return self +end +function MSRS:Help() +self:F() +local path=self:GetPath() +local exe="DCS-SR-ExternalAudio.exe" +local filename=os.getenv('TMP').."\\MSRS-help-"..MSRS.uuid()..".txt" +local command=string.format("%s/%s --help > %s",path,exe,filename) +os.execute(command) +local f=assert(io.open(filename,"rb")) +local data=f:read("*all") +f:close() +env.info("SRS help output:") +env.info("======================================================================") +env.info(data) +env.info("======================================================================") +return self +end +function MSRS:PlaySoundFile(Soundfile,Delay) +self:F({Soundfile,Delay}) +local soundfile=Soundfile:GetName() +local exists=UTILS.FileExists(soundfile) +if not exists then +self:E("ERROR: MSRS sound file does not exist! File="..soundfile) +return self +end +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MSRS.PlaySoundFile,self,Soundfile,0) +else +local command=self:_GetCommand() +command=command..' --file="'..tostring(soundfile)..'"' +command=string.gsub(command,"--ssml","-h") +self:_ExecCommand(command) +end +return self +end +function MSRS:PlaySoundText(SoundText,Delay) +self:F({SoundText,Delay}) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MSRS.PlaySoundText,self,SoundText,0) +else +if self.backend==MSRS.Backend.GRPC then +self:_DCSgRPCtts(SoundText.text,nil,SoundText.gender,SoundText.culture,SoundText.voice,SoundText.volume,SoundText.label,SoundText.coordinate) +else +local command=self:_GetCommand(nil,nil,nil,SoundText.gender,SoundText.voice,SoundText.culture,SoundText.volume,SoundText.speed) +command=command..string.format(" --text=\"%s\"",tostring(SoundText.text)) +self:_ExecCommand(command) +end +end +return self +end +function MSRS:PlayText(Text,Delay,Coordinate) +self:F({Text,Delay,Coordinate}) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MSRS.PlayText,self,Text,nil,Coordinate) +else +if self.backend==MSRS.Backend.GRPC then +self:T(self.lid.."Transmitting") +self:_DCSgRPCtts(Text,nil,nil,nil,nil,nil,nil,Coordinate) +else +self:PlayTextExt(Text,Delay,nil,nil,nil,nil,nil,nil,nil,Coordinate) +end +end +return self +end +function MSRS:PlayTextExt(Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate) +self:T({Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate}) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,self.PlayTextExt,self,Text,0,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate) +else +Frequencies=Frequencies or self:GetFrequencies() +Modulations=Modulations or self:GetModulations() +if self.backend==MSRS.Backend.SRSEXE then +local command=self:_GetCommand(UTILS.EnsureTable(Frequencies,false),UTILS.EnsureTable(Modulations,false),nil,Gender,Voice,Culture,Volume,nil,nil,Label,Coordinate) +command=command..string.format(" --text=\"%s\"",tostring(Text)) +self:_ExecCommand(command) +elseif self.backend==MSRS.Backend.GRPC then +self:_DCSgRPCtts(Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate) +end +end +return self +end +function MSRS:PlayTextFile(TextFile,Delay) +self:F({TextFile,Delay}) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,MSRS.PlayTextFile,self,TextFile,0) +else +local exists=UTILS.FileExists(TextFile) +if not exists then +self:E("ERROR: MSRS Text file does not exist! File="..tostring(TextFile)) +return self +end +local command=self:_GetCommand() +command=command..string.format(" --textFile=\"%s\"",tostring(TextFile)) +self:T(string.format("MSRS TextFile command=%s",command)) +local l=string.len(command) +self:T(string.format("Command length=%d",l)) +self:_ExecCommand(command) +end +return self +end +function MSRS:_GetLatLongAlt(Coordinate) +self:F({Coordinate=Coordinate}) +local lat=0.0 +local lon=0.0 +local alt=0.0 +if Coordinate then +lat,lon,alt=coord.LOtoLL(Coordinate) +end +return lat,lon,math.floor(alt) +end +function MSRS:_GetCommand(freqs,modus,coal,gender,voice,culture,volume,speed,port,label,coordinate) +self:F({freqs,modus,coal,gender,voice,culture,volume,speed,port,label,coordinate}) +local path=self:GetPath() +local exe="DCS-SR-ExternalAudio.exe" +local fullPath=string.format("%s\\%s",path,exe) +freqs=table.concat(freqs or self.frequencies,",") +modus=table.concat(modus or self.modulations,",") +coal=coal or self.coalition +gender=gender or self.gender +voice=voice or self:GetVoice(self.provider)or self.voice +culture=culture or self.culture +volume=volume or self.volume +speed=speed or self.speed +port=port or self.port +label=label or self.Label +coordinate=coordinate or self.coordinate +modus=modus:gsub("0","AM") +modus=modus:gsub("1","FM") +local pwsh=string.format('Start-Process -WindowStyle Hidden -WorkingDirectory \"%s\" -FilePath \"%s\" -ArgumentList \'-f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"',path,exe,freqs,modus,coal,port,label,volume) +local command=string.format('"%s\\%s" -f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"',path,exe,freqs,modus,coal,port,label,volume) +if voice and self.UsePowerShell~=true then +command=command..string.format(" --voice=\"%s\"",tostring(voice)) +pwsh=pwsh..string.format(" --voice=\"%s\"",tostring(voice)) +else +if gender and gender~="female"then +command=command..string.format(" -g %s",tostring(gender)) +pwsh=pwsh..string.format(" -g %s",tostring(gender)) +end +if culture and culture~="en-GB"then +command=command..string.format(" -l %s",tostring(culture)) +pwsh=pwsh..string.format(" -l %s",tostring(culture)) +end +end +if coordinate then +local lat,lon,alt=self:_GetLatLongAlt(coordinate) +command=command..string.format(" -L %.4f -O %.4f -A %d",lat,lon,alt) +pwsh=pwsh..string.format(" -L %.4f -O %.4f -A %d",lat,lon,alt) +end +if self.provider==MSRS.Provider.GOOGLE then +local pops=self:GetProviderOptions() +command=command..string.format(' --ssml -G "%s"',pops.credentials) +pwsh=pwsh..string.format(' --ssml -G "%s"',pops.credentials) +elseif self.provider==MSRS.Provider.WINDOWS then +else +self:E("ERROR: SRS only supports WINDOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as AWS and Azure.") +end +if not UTILS.FileExists(fullPath)then +self:E("ERROR: MSRS SRS executable does not exist! FullPath="..fullPath) +command="CommandNotFound" +end +self:T("MSRS command from _GetCommand="..command) +if self.UsePowerShell==true then +return pwsh +else +return command +end +end +function MSRS:_ExecCommand(command) +self:T2({command=command}) +if string.find(command,"CommandNotFound")then return 0 end +local batContent=command.." && exit" +local filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".bat" +if self.UsePowerShell==true then +filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".ps1" +batContent=command.."\'" +self:T({batContent=batContent}) +end +local script=io.open(filename,"w+") +script:write(batContent) +script:close() +self:T("MSRS batch file created: "..filename) +self:T("MSRS batch content: "..batContent) +local res=nil +if self.UsePowerShell~=true then +local filenvbs=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".vbs" +local script=io.open(filenvbs,"w+") +script:write(string.format('Dim WinScriptHost\n')) +script:write(string.format('Set WinScriptHost = CreateObject("WScript.Shell")\n')) +script:write(string.format('WinScriptHost.Run Chr(34) & "%s" & Chr(34), 0\n',filename)) +script:write(string.format('Set WinScriptHost = Nothing')) +script:close() +self:T("MSRS vbs file created to start batch="..filenvbs) +local runvbs=string.format('cscript.exe //Nologo //B "%s"',filenvbs) +self:T("MSRS execute VBS command="..runvbs) +res=os.execute(runvbs) +timer.scheduleFunction(os.remove,filename,timer.getTime()+1) +timer.scheduleFunction(os.remove,filenvbs,timer.getTime()+1) +self:T("MSRS vbs and batch file removed") +elseif self.UsePowerShell==true then +local pwsh=string.format('start /min "" powershell.exe -ExecutionPolicy Unrestricted -WindowStyle Hidden -Command "%s"',filename) +if string.len(pwsh)>255 then +self:E("[MSRS] - pwsh string too long") +end +res=os.execute(pwsh) +timer.scheduleFunction(os.remove,filename,timer.getTime()+1) +else +command=string.format('start /b "" "%s"',filename) +self:T("MSRS execute command="..command) +res=os.execute(command) +timer.scheduleFunction(os.remove,filename,timer.getTime()+1) +end +return res +end +function MSRS:_DCSgRPCtts(Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate) +self:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()") +self:T({Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate}) +local options={} +local ssml=Text or'' +Frequencies=UTILS.EnsureTable(Frequencies,true)or self:GetFrequencies() +options.plaintext=Text +options.srsClientName=Label or self.Label +if self.coordinate then +options.position={} +options.position.lat,options.position.lon,options.position.alt=self:_GetLatLongAlt(self.coordinate) +end +options.coalition=UTILS.GetCoalitionName(self.coalition):lower() +local provider=self.provider or MSRS.Provider.WINDOWS +options.provider={} +options.provider[provider]=self:GetProviderOptions(provider) +Voice=Voice or self:GetVoice(self.provider)or self.voice +if Voice then +options.provider[provider].voice=Voice +else +local preTag,genderProp,langProp,postTag='','','','' +local gender="" +if self.gender then +gender=string.format(' gender=\"%s\"',self.gender) +end +local language="" +if self.culture then +language=string.format(' language=\"%s\"',self.culture) +end +if self.gender or self.culture then +ssml=string.format("%s",gender,language,Text) +end +end +for _,freq in pairs(Frequencies)do +self:T("Calling GRPC.tts with the following parameter:") +self:T({ssml=ssml,freq=freq,options=options}) +self:T(options.provider[provider]) +GRPC.tts(ssml,freq*1e6,options) +end +end +function MSRS:LoadConfigFile(Path,Filename) +if lfs==nil then +env.info("*****Note - lfs and os need to be desanitized for MSRS to work!") +return false +end +local path=Path or lfs.writedir()..MSRS.ConfigFilePath +local file=Filename or MSRS.ConfigFileName or"Moose_MSRS.lua" +local pathandfile=path..file +local filexsists=UTILS.FileExists(pathandfile) +if filexsists and not MSRS.ConfigLoaded then +env.info("FF reading config file") +assert(loadfile(path..file))() +if MSRS_Config then +local Self=self or MSRS +Self.path=MSRS_Config.Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +Self.port=MSRS_Config.Port or 5002 +Self.backend=MSRS_Config.Backend or MSRS.Backend.SRSEXE +Self.frequencies=MSRS_Config.Frequency or{127,243} +Self.modulations=MSRS_Config.Modulation or{0,0} +Self.coalition=MSRS_Config.Coalition or 0 +if MSRS_Config.Coordinate then +Self.coordinate=COORDINATE:New(MSRS_Config.Coordinate[1],MSRS_Config.Coordinate[2],MSRS_Config.Coordinate[3]) +end +Self.culture=MSRS_Config.Culture or"en-GB" +Self.gender=MSRS_Config.Gender or"male" +Self.Label=MSRS_Config.Label or"MSRS" +Self.voice=MSRS_Config.Voice +Self.provider=MSRS_Config.Provider or MSRS.Provider.WINDOWS +for _,provider in pairs(MSRS.Provider)do +if MSRS_Config[provider]then +Self.poptions[provider]=MSRS_Config[provider] +end +end +Self.ConfigLoaded=true +end +env.info("MSRS - Successfully loaded default configuration from disk!",false) +end +if not filexsists then +env.info("MSRS - Cannot find default configuration file!",false) +return false +end +return true +end +function MSRS.getSpeechTime(length,speed,isGoogle) +local maxRateRatio=3 +speed=speed or 1.0 +isGoogle=isGoogle or false +local speedFactor=1.0 +if isGoogle then +speedFactor=speed +else +if speed~=0 then +speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1 +end +if speed<0 then +speedFactor=1/speedFactor +end +end +local wpm=math.ceil(100*speedFactor) +local cps=math.floor((wpm*5)/60) +if type(length)=="string"then +length=string.len(length) +end +return length/cps +end +MSRSQUEUE={ +ClassName="MSRSQUEUE", +Debugmode=nil, +lid=nil, +queue={}, +alias=nil, +dt=nil, +Tlast=nil, +checking=nil, +} +function MSRSQUEUE:New(alias) +local self=BASE:Inherit(self,BASE:New()) +self.alias=alias or"My Radio" +self.dt=1.0 +self.lid=string.format("MSRSQUEUE %s | ",self.alias) +return self +end +function MSRSQUEUE:Clear() +self:T(self.lid.."Clearing MSRSQUEUE") +self.queue={} +return self +end +function MSRSQUEUE:AddTransmission(transmission) +transmission.isplaying=false +transmission.Tstarted=nil +table.insert(self.queue,transmission) +if not self.checking then +self:_CheckRadioQueue() +end +return self +end +function MSRSQUEUE:SetTransmitOnlyWithPlayers(Switch) +self.TransmitOnlyWithPlayers=Switch +if Switch==false or Switch==nil then +if self.PlayerSet then +self.PlayerSet:FilterStop() +end +self.PlayerSet=nil +else +self.PlayerSet=SET_CLIENT:New():FilterStart() +end +return self +end +function MSRSQUEUE:NewTransmission(text,duration,msrs,tstart,interval,subgroups,subtitle,subduration,frequency,modulation,gender,culture,voice,volume,label,coordinate) +self:T({Text=text,Dur=duration,start=tstart,int=interval,sub=subgroups,subt=subtitle,sudb=subduration,F=frequency,M=modulation,G=gender,C=culture,V=voice,Vol=volume,L=label}) +if self.TransmitOnlyWithPlayers then +if self.PlayerSet and self.PlayerSet:CountAlive()==0 then +return self +end +end +if not text then +self:E(self.lid.."ERROR: No text specified.") +return nil +end +if type(text)~="string"then +self:E(self.lid.."ERROR: Text specified is NOT a string.") +return nil +end +local transmission={} +transmission.text=text +transmission.duration=duration or MSRS.getSpeechTime(text) +transmission.msrs=msrs +transmission.Tplay=tstart or timer.getAbsTime() +transmission.subtitle=subtitle +transmission.interval=interval or 0 +transmission.frequency=frequency or msrs.frequencies +transmission.modulation=modulation or msrs.modulations +transmission.subgroups=subgroups +if transmission.subtitle then +transmission.subduration=subduration or transmission.duration +else +transmission.subduration=0 +end +transmission.gender=gender or msrs.gender +transmission.culture=culture or msrs.culture +transmission.voice=voice or msrs.voice +transmission.volume=volume or msrs.volume +transmission.label=label or msrs.Label +transmission.coordinate=coordinate or msrs.coordinate +self:AddTransmission(transmission) +return transmission +end +function MSRSQUEUE:Broadcast(transmission) +self:T(self.lid.."Broadcast") +if transmission.frequency then +transmission.msrs:PlayTextExt(transmission.text,nil,transmission.frequency,transmission.modulation,transmission.gender,transmission.culture,transmission.voice,transmission.volume,transmission.label,transmission.coordinate) +else +transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate) +end +local function texttogroup(gid) +trigger.action.outTextForGroup(gid,transmission.subtitle,transmission.subduration,true) +end +if transmission.subgroups and#transmission.subgroups>0 then +for _,_group in pairs(transmission.subgroups)do +local group=_group +if group and group:IsAlive()then +local gid=group:GetID() +self:ScheduleOnce(4,texttogroup,gid) +end +end +end +end +function MSRSQUEUE:CalcTransmisstionDuration() +local Tnow=timer.getAbsTime() +local T=0 +for _,_transmission in pairs(self.queue)do +local transmission=_transmission +if transmission.isplaying then +local dt=Tnow-transmission.Tstarted +T=T+transmission.duration-dt +else +T=T+transmission.duration +end +end +return T +end +function MSRSQUEUE:_CheckRadioQueue(delay) +local N=#self.queue +self:T2(self.lid..string.format("Check radio queue %s: delay=%.3f sec, N=%d, checking=%s",self.alias,delay or 0,N,tostring(self.checking))) +if delay and delay>0 then +self:ScheduleOnce(delay,MSRSQUEUE._CheckRadioQueue,self) +self.checking=true +else +if N==0 then +self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking",self.alias)) +self.checking=false +return +end +local time=timer.getAbsTime() +self.checking=true +local dt=self.dt +local playing=false +local next=nil +local remove=nil +for i,_transmission in ipairs(self.queue)do +local transmission=_transmission +if time>=transmission.Tplay then +if transmission.isplaying then +if time>=transmission.Tstarted+transmission.duration then +transmission.isplaying=false +remove=i +self.Tlast=time +else +playing=true +dt=transmission.duration-(time-transmission.Tstarted) +end +else +local Tlast=self.Tlast +if transmission.interval==nil then +if next==nil then +next=transmission +end +else +if Tlast==nil or time-Tlast>=transmission.interval then +next=transmission +else +end +end +if next or Tlast then +break +end +end +else +end +end +if next~=nil and not playing then +self:T(self.lid..string.format("Broadcasting text=\"%s\" at T=%.3f",next.text,time)) +self:Broadcast(next) +next.isplaying=true +next.Tstarted=time +dt=next.duration +end +if remove then +table.remove(self.queue,remove) +N=N-1 +if#self.queue==0 then +self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking",self.alias)) +self.checking=false +return +end +end +self:_CheckRadioQueue(dt) +end +end +MSRS.LoadConfigFile() +COMMANDCENTER={ +ClassName="COMMANDCENTER", +CommandCenterName="", +CommandCenterCoalition=nil, +CommandCenterPositionable=nil, +Name="", +ReferencePoints={}, +ReferenceNames={}, +CommunicationMode="80", +} +COMMANDCENTER.AutoAssignMethods={ +["Random"]=1, +["Distance"]=2, +["Priority"]=3, +} +function COMMANDCENTER:New(CommandCenterPositionable,CommandCenterName) +local self=BASE:Inherit(self,BASE:New()) +self.CommandCenterPositionable=CommandCenterPositionable +self.CommandCenterName=CommandCenterName or CommandCenterPositionable:GetName() +self.CommandCenterCoalition=CommandCenterPositionable:GetCoalition() +self.Missions={} +self:SetAutoAssignTasks(false) +self:SetAutoAcceptTasks(true) +self:SetAutoAssignMethod(COMMANDCENTER.AutoAssignMethods.Distance) +self:SetFlashStatus(false) +self:SetMessageDuration(10) +self:HandleEvent(EVENTS.Birth, +function(self,EventData) +if EventData.IniObjectCategory==1 then +local EventGroup=GROUP:Find(EventData.IniDCSGroup) +if EventGroup and EventGroup:IsAlive()and self:HasGroup(EventGroup)then +local CommandCenterMenu=MENU_GROUP:New(EventGroup,self:GetText()) +local MenuReporting=MENU_GROUP:New(EventGroup,"Missions Reports",CommandCenterMenu) +local MenuMissionsSummary=MENU_GROUP_COMMAND:New(EventGroup,"Missions Status Report",MenuReporting,self.ReportSummary,self,EventGroup) +local MenuMissionsDetails=MENU_GROUP_COMMAND:New(EventGroup,"Missions Players Report",MenuReporting,self.ReportMissionsPlayers,self,EventGroup) +local PlayerUnit=EventData.IniUnit +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +local PlayerGroup=EventData.IniGroup +Mission:JoinUnit(PlayerUnit,PlayerGroup) +end +self:SetMenu() +end +end +end +) +self:HandleEvent(EVENTS.MissionEnd, +function(self,EventData) +local PlayerUnit=EventData.IniUnit +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +Mission:Stop() +end +end +) +self:HandleEvent(EVENTS.PlayerLeaveUnit, +function(self,EventData) +local PlayerUnit=EventData.IniUnit +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +if Mission:IsENGAGED()then +Mission:AbortUnit(PlayerUnit) +end +end +end +) +self:HandleEvent(EVENTS.Crash, +function(self,EventData) +local PlayerUnit=EventData.IniUnit +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +if Mission:IsENGAGED()then +Mission:CrashUnit(PlayerUnit) +end +end +end +) +self:SetMenu() +_SETTINGS:SetSystemMenu(CommandCenterPositionable) +self:SetCommandMenu() +return self +end +function COMMANDCENTER:GetName() +return self.CommandCenterName +end +function COMMANDCENTER:GetText() +return"Command Center ["..self.CommandCenterName.."]" +end +function COMMANDCENTER:GetShortText() +return"CC ["..self.CommandCenterName.."]" +end +function COMMANDCENTER:GetCoalition() +return self.CommandCenterCoalition +end +function COMMANDCENTER:GetPositionable() +return self.CommandCenterPositionable +end +function COMMANDCENTER:GetMissions() +return self.Missions or{} +end +function COMMANDCENTER:AddMission(Mission) +self.Missions[Mission]=Mission +return Mission +end +function COMMANDCENTER:RemoveMission(Mission) +self.Missions[Mission]=nil +return Mission +end +function COMMANDCENTER:SetReferenceZones(ReferenceZonePrefix) +local MatchPattern="(.*)#(.*)" +self:F({MatchPattern=MatchPattern}) +for ReferenceZoneName in pairs(_DATABASE.ZONENAMES)do +local ZoneName,ReferenceName=string.match(ReferenceZoneName,MatchPattern) +self:F({ZoneName=ZoneName,ReferenceName=ReferenceName}) +if ZoneName and ReferenceName and ZoneName==ReferenceZonePrefix then +self.ReferencePoints[ReferenceZoneName]=ZONE:New(ReferenceZoneName) +self.ReferenceNames[ReferenceZoneName]=ReferenceName +end +end +return self +end +function COMMANDCENTER:SetModeWWII() +self.CommunicationMode="WWII" +return self +end +function COMMANDCENTER:IsModeWWII() +return self.CommunicationMode=="WWII" +end +function COMMANDCENTER:SetMenu() +self:F2() +local MenuTime=timer.getTime() +for MissionID,Mission in pairs(self:GetMissions()or{})do +local Mission=Mission +Mission:SetMenu(MenuTime) +end +for MissionID,Mission in pairs(self:GetMissions()or{})do +Mission=Mission +Mission:RemoveMenu(MenuTime) +end +end +function COMMANDCENTER:GetMenu(TaskGroup) +local MenuTime=timer.getTime() +self.CommandCenterMenus=self.CommandCenterMenus or{} +local CommandCenterMenu +local CommandCenterText=self:GetText() +CommandCenterMenu=MENU_GROUP:New(TaskGroup,CommandCenterText):SetTime(MenuTime) +self.CommandCenterMenus[TaskGroup]=CommandCenterMenu +if self.AutoAssignTasks==false then +local AssignTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Assign Task",CommandCenterMenu,self.AssignTask,self,TaskGroup):SetTime(MenuTime):SetTag("AutoTask") +end +CommandCenterMenu:Remove(MenuTime,"AutoTask") +return self.CommandCenterMenus[TaskGroup] +end +function COMMANDCENTER:AssignTask(TaskGroup) +local Tasks={} +local AssignPriority=99999999 +local AutoAssignMethod=self.AutoAssignMethod +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +local MissionTasks=Mission:GetGroupTasks(TaskGroup) +for MissionTaskName,MissionTask in pairs(MissionTasks or{})do +local MissionTask=MissionTask +if MissionTask:IsStatePlanned()or MissionTask:IsStateReplanned()or MissionTask:IsStateAssigned()then +local TaskPriority=MissionTask:GetAutoAssignPriority(self.AutoAssignMethod,self,TaskGroup) +if TaskPriority Adding TASK ",MissionName=self:GetName(),TaskName=TaskName}) +self.Tasks[TaskName]=Task +self:GetCommandCenter():SetMenu() +return Task +end +function MISSION:RemoveTask(Task) +local TaskName=Task:GetTaskName() +self:T({"<== Removing TASK ",MissionName=self:GetName(),TaskName=TaskName}) +self:F(TaskName) +self.Tasks[TaskName]=self.Tasks[TaskName]or{n=0} +self.Tasks[TaskName]=nil +Task=nil +collectgarbage() +self:GetCommandCenter():SetMenu() +return nil +end +function MISSION:IsCOMPLETED() +return self:Is("COMPLETED") +end +function MISSION:IsIDLE() +return self:Is("IDLE") +end +function MISSION:IsENGAGED() +return self:Is("ENGAGED") +end +function MISSION:IsFAILED() +return self:Is("FAILED") +end +function MISSION:IsHOLD() +return self:Is("HOLD") +end +function MISSION:HasGroup(TaskGroup) +local Has=false +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +if Task:HasGroup(TaskGroup)then +Has=true +break +end +end +return Has +end +function MISSION:GetTasksRemaining() +local TasksRemaining=0 +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +if Task:IsStateSuccess()or Task:IsStateFailed()then +else +TasksRemaining=TasksRemaining+1 +end +end +return TasksRemaining +end +function MISSION:GetTaskTypes() +local TaskTypeList={} +local TasksRemaining=0 +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +local TaskType=Task:GetType() +TaskTypeList[TaskType]=TaskType +end +return TaskTypeList +end +function MISSION:AddPlayerName(PlayerName) +self.PlayerNames=self.PlayerNames or{} +self.PlayerNames[PlayerName]=PlayerName +return self +end +function MISSION:GetPlayerNames() +return self.PlayerNames +end +function MISSION:ReportBriefing() +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - Mission Briefing Report',Name,Status)) +Report:Add(self.MissionBriefing) +return Report:Text() +end +function MISSION:ReportPlayersPerTask(ReportGroup) +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - Players per Task Report',Name,Status)) +local PlayerList={} +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +local PlayerNames=Task:GetPlayerNames() +for PlayerName,PlayerGroup in pairs(PlayerNames)do +PlayerList[PlayerName]=Task:GetName() +end +end +for PlayerName,TaskName in pairs(PlayerList)do +Report:Add(string.format(' - Player (%s): Task "%s"',PlayerName,TaskName)) +end +return Report:Text() +end +function MISSION:ReportPlayersProgress(ReportGroup) +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - Players per Task Progress Report',Name,Status)) +local PlayerList={} +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +local TaskName=Task:GetName() +local Goal=Task:GetGoal() +PlayerList[TaskName]=PlayerList[TaskName]or{} +if Goal then +local TotalContributions=Goal:GetTotalContributions() +local PlayerContributions=Goal:GetPlayerContributions() +self:F({TotalContributions=TotalContributions,PlayerContributions=PlayerContributions}) +for PlayerName,PlayerContribution in pairs(PlayerContributions)do +PlayerList[TaskName][PlayerName]=string.format('Player (%s): Task "%s": %d%%',PlayerName,TaskName,PlayerContributions[PlayerName]*100/TotalContributions) +end +else +PlayerList[TaskName]["_"]=string.format('Player (---): Task "%s": %d%%',TaskName,0) +end +end +for TaskName,TaskData in pairs(PlayerList)do +for PlayerName,TaskText in pairs(TaskData)do +Report:Add(string.format(' - %s',TaskText)) +end +end +return Report:Text() +end +function MISSION:MarkTargetLocations(ReportGroup) +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - All Tasks are marked on the map. Select a Task from the Mission Menu and Join the Task!!!',Name,Status)) +for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)" +Report:Add(string.format('%s - %s - Task Overview Report',Name,Status)) +for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)" +Report:Add(string.format('%s - %s - %s Tasks Report',Name,Status,TaskStatus)) +local Tasks=0 +for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)=8 then +break +end +end +return Report:Text() +end +function MISSION:ReportDetails(ReportGroup) +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - Task Detailed Report',Name,Status)) +local TasksRemaining=0 +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +Report:Add(string.rep("-",140)) +Report:Add(Task:ReportDetails(ReportGroup)) +end +return Report:Text() +end +function MISSION:GetTasks() +return self.Tasks or{} +end +function MISSION:GetGroupTasks(TaskGroup) +local Tasks={} +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +if Task:HasGroup(TaskGroup)then +Tasks[#Tasks+1]=Task +end +end +return Tasks +end +function MISSION:MenuReportBriefing(ReportGroup) +local Report=self:ReportBriefing() +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Briefing) +end +function MISSION:MenuMarkTargetLocations(ReportGroup) +local Report=self:MarkTargetLocations(ReportGroup) +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +function MISSION:MenuReportTasksSummary(ReportGroup) +local Report=self:ReportSummary(ReportGroup) +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +function MISSION:MenuReportTasksPerStatus(ReportGroup,TaskStatus) +local Report=self:ReportOverview(ReportGroup,TaskStatus) +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +function MISSION:MenuReportPlayersPerTask(ReportGroup) +local Report=self:ReportPlayersPerTask() +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +function MISSION:MenuReportPlayersProgress(ReportGroup) +local Report=self:ReportPlayersProgress() +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +TASK={ +ClassName="TASK", +TaskScheduler=nil, +ProcessClasses={}, +Processes={}, +Players=nil, +Scores={}, +Menu={}, +SetGroup=nil, +FsmTemplate=nil, +Mission=nil, +CommandCenter=nil, +TimeOut=0, +AssignedGroups={}, +} +function TASK:New(Mission,SetGroupAssign,TaskName,TaskType,TaskBriefing) +local self=BASE:Inherit(self,FSM_TASK:New(TaskName)) +self:SetStartState("Planned") +self:AddTransition("Planned","Assign","Assigned") +self:AddTransition("Assigned","AssignUnit","Assigned") +self:AddTransition("Assigned","Success","Success") +self:AddTransition("Assigned","Hold","Hold") +self:AddTransition("Assigned","Fail","Failed") +self:AddTransition({"Planned","Assigned"},"Abort","Aborted") +self:AddTransition("Assigned","Cancel","Cancelled") +self:AddTransition("Assigned","Goal","*") +self.Fsm={} +local Fsm=self:GetUnitProcess() +Fsm:SetStartState("Planned") +Fsm:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="Assigned",Rejected="Reject"}) +Fsm:AddTransition("Assigned","Assigned","*") +self:AddTransition("*","PlayerCrashed","*") +self:AddTransition("*","PlayerAborted","*") +self:AddTransition("*","PlayerRejected","*") +self:AddTransition("*","PlayerDead","*") +self:AddTransition({"Failed","Aborted","Cancelled"},"Replan","Planned") +self:AddTransition("*","TimeOut","Cancelled") +self:F("New TASK "..TaskName) +self.Processes={} +self.Mission=Mission +self.CommandCenter=Mission:GetCommandCenter() +self.SetGroup=SetGroupAssign +self:SetType(TaskType) +self:SetName(TaskName) +self:SetID(Mission:GetNextTaskID(self)) +self:SetBriefing(TaskBriefing) +self.TaskInfo=TASKINFO:New(self) +self.TaskProgress={} +return self +end +function TASK:GetUnitProcess(TaskUnit) +if TaskUnit then +return self:GetStateMachine(TaskUnit) +else +self.FsmTemplate=self.FsmTemplate or FSM_PROCESS:New() +return self.FsmTemplate +end +end +function TASK:SetUnitProcess(FsmTemplate) +self.FsmTemplate=FsmTemplate +end +function TASK:JoinUnit(PlayerUnit,PlayerGroup) +self:F({PlayerUnit=PlayerUnit,PlayerGroup=PlayerGroup}) +local PlayerUnitAdded=false +local PlayerGroups=self:GetGroups() +if PlayerGroups:IsIncludeObject(PlayerGroup)then +if self:IsStatePlanned()or self:IsStateReplanned()then +end +if self:IsStateAssigned()then +local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) +self:F({IsGroupAssigned=IsGroupAssigned}) +if IsGroupAssigned then +self:AssignToUnit(PlayerUnit) +self:MessageToGroups(PlayerUnit:GetPlayerName().." joined Task "..self:GetName()) +end +end +end +return PlayerUnitAdded +end +function TASK:RejectGroup(PlayerGroup) +local PlayerGroups=self:GetGroups() +if PlayerGroups:IsIncludeObject(PlayerGroup)then +if self:IsStatePlanned()then +local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) +if IsGroupAssigned then +local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName() +self:GetMission():GetCommandCenter():MessageToGroup("Task "..self:GetName().." has been rejected! We will select another task.",PlayerGroup) +self:UnAssignFromGroup(PlayerGroup) +self:PlayerRejected(PlayerGroup:GetUnit(1)) +end +end +end +return self +end +function TASK:AbortGroup(PlayerGroup) +local PlayerGroups=self:GetGroups() +if PlayerGroups:IsIncludeObject(PlayerGroup)then +if self:IsStateAssigned()then +local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) +if IsGroupAssigned then +local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName() +self:UnAssignFromGroup(PlayerGroup) +PlayerGroups:Flush(self) +local IsRemaining=false +for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do +if self:IsGroupAssigned(AssignedGroup)==true then +IsRemaining=true +self:F({Task=self:GetName(),IsRemaining=IsRemaining}) +break +end +end +self:F({Task=self:GetName(),IsRemaining=IsRemaining}) +if IsRemaining==false then +self:Abort() +end +self:PlayerAborted(PlayerGroup:GetUnit(1)) +end +end +end +return self +end +function TASK:CrashGroup(PlayerGroup) +self:F({PlayerGroup=PlayerGroup}) +local PlayerGroups=self:GetGroups() +if PlayerGroups:IsIncludeObject(PlayerGroup)then +if self:IsStateAssigned()then +local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) +self:F({IsGroupAssigned=IsGroupAssigned}) +if IsGroupAssigned then +local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName() +self:MessageToGroups(PlayerName.." crashed! ") +self:UnAssignFromGroup(PlayerGroup) +PlayerGroups:Flush(self) +local IsRemaining=false +for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do +if self:IsGroupAssigned(AssignedGroup)==true then +IsRemaining=true +self:F({Task=self:GetName(),IsRemaining=IsRemaining}) +break +end +end +self:F({Task=self:GetName(),IsRemaining=IsRemaining}) +if IsRemaining==false then +self:Abort() +end +self:PlayerCrashed(PlayerGroup:GetUnit(1)) +end +end +end +return self +end +function TASK:GetMission() +return self.Mission +end +function TASK:GetGroups() +return self.SetGroup +end +function TASK:AddGroups(GroupSet) +GroupSet=GroupSet or SET_GROUP:New() +self.SetGroup:ForEachGroup( +function(GroupItem) +GroupSet:Add(GroupItem:GetName(),GroupItem) +end +) +return GroupSet +end +do +function TASK:IsGroupAssigned(TaskGroup) +local TaskGroupName=TaskGroup:GetName() +if self.AssignedGroups[TaskGroupName]then +return true +end +return false +end +function TASK:SetGroupAssigned(TaskGroup) +local TaskName=self:GetName() +local TaskGroupName=TaskGroup:GetName() +self.AssignedGroups[TaskGroupName]=TaskGroup +self:F(string.format("Task %s is assigned to %s",TaskName,TaskGroupName)) +self:GetMission():SetGroupAssigned(TaskGroup) +local SetAssignedGroups=self:GetGroups() +return self +end +function TASK:ClearGroupAssignment(TaskGroup) +local TaskName=self:GetName() +local TaskGroupName=TaskGroup:GetName() +self.AssignedGroups[TaskGroupName]=nil +self:GetMission():ClearGroupAssignment(TaskGroup) +local SetAssignedGroups=self:GetGroups() +SetAssignedGroups:ForEachGroup( +function(AssignedGroup) +if self:IsGroupAssigned(AssignedGroup)then +else +end +end +) +return self +end +end +do +function TASK:SetAssignMethod(AcceptClass) +local ProcessTemplate=self:GetUnitProcess() +ProcessTemplate:SetProcess("Planned","Accept",AcceptClass) +end +function TASK:AssignToGroup(TaskGroup) +self:F(TaskGroup:GetName()) +local TaskGroupName=TaskGroup:GetName() +local Mission=self:GetMission() +local CommandCenter=Mission:GetCommandCenter() +self:SetGroupAssigned(TaskGroup) +local TaskUnits=TaskGroup:GetUnits() +for UnitID,UnitData in pairs(TaskUnits)do +local TaskUnit=UnitData +local PlayerName=TaskUnit:GetPlayerName() +self:F(PlayerName) +if PlayerName~=nil and PlayerName~=""then +self:AssignToUnit(TaskUnit) +CommandCenter:MessageToGroup( +string.format('Task "%s": Briefing for player (%s):\n%s', +self:GetName(), +PlayerName, +self:GetBriefing() +),TaskGroup +) +end +end +CommandCenter:SetMenu() +self:MenuFlashTaskStatus(TaskGroup,self:GetMission():GetCommandCenter().FlashStatus) +return self +end +function TASK:UnAssignFromGroup(TaskGroup) +self:F2({TaskGroup=TaskGroup:GetName()}) +self:ClearGroupAssignment(TaskGroup) +local TaskUnits=TaskGroup:GetUnits() +for UnitID,UnitData in pairs(TaskUnits)do +local TaskUnit=UnitData +local PlayerName=TaskUnit:GetPlayerName() +if PlayerName~=nil and PlayerName~=""then +self:UnAssignFromUnit(TaskUnit) +end +end +local Mission=self:GetMission() +local CommandCenter=Mission:GetCommandCenter() +CommandCenter:SetMenu() +self:MenuFlashTaskStatus(TaskGroup,false) +end +end +function TASK:HasGroup(FindGroup) +local SetAttackGroup=self:GetGroups() +return SetAttackGroup:FindGroup(FindGroup:GetName()) +end +function TASK:AssignToUnit(TaskUnit) +self:F(TaskUnit:GetName()) +local FsmTemplate=self:GetUnitProcess() +local FsmUnit=self:SetStateMachine(TaskUnit,FsmTemplate:Copy(TaskUnit,self)) +FsmUnit:SetStartState("Planned") +FsmUnit:Accept() +return self +end +function TASK:UnAssignFromUnit(TaskUnit) +self:F(TaskUnit:GetName()) +self:RemoveStateMachine(TaskUnit) +self:RemoveTaskControlMenu(TaskUnit) +return self +end +function TASK:SetTimeOut(Timer) +self:F(Timer) +self.TimeOut=Timer +self:__TimeOut(self.TimeOut) +return self +end +function TASK:MessageToGroups(Message) +self:F({Message=Message}) +local Mission=self:GetMission() +local CC=Mission:GetCommandCenter() +for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do +TaskGroup=TaskGroup +if TaskGroup:IsAlive()==true then +CC:MessageToGroup(Message,TaskGroup,TaskGroup:GetName()) +end +end +end +function TASK:SendBriefingToAssignedGroups() +self:F2() +for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do +if TaskGroup:IsAlive()then +if self:IsGroupAssigned(TaskGroup)then +TaskGroup:Message(self.TaskBriefing,60) +end +end +end +end +function TASK:UnAssignFromGroups() +self:F2() +for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do +if TaskGroup:IsAlive()==true then +if self:IsGroupAssigned(TaskGroup)then +self:UnAssignFromGroup(TaskGroup) +end +end +end +end +function TASK:HasAliveUnits() +self:F() +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if TaskGroup:IsAlive()==true then +if self:IsStateAssigned()then +if self:IsGroupAssigned(TaskGroup)then +for TaskUnitID,TaskUnit in pairs(TaskGroup:GetUnits())do +if TaskUnit:IsAlive()then +self:T({HasAliveUnits=true}) +return true +end +end +end +end +end +end +self:T({HasAliveUnits=false}) +return false +end +function TASK:SetMenu(MenuTime) +self:F({self:GetName(),MenuTime}) +for TaskGroupID,TaskGroupData in pairs(self.SetGroup:GetSet())do +local TaskGroup=TaskGroupData +if TaskGroup:IsAlive()==true and TaskGroup:GetPlayerNames()then +local Mission=self:GetMission() +local MissionMenu=Mission:GetMenu(TaskGroup) +if MissionMenu then +self:SetMenuForGroup(TaskGroup,MenuTime) +end +end +end +end +function TASK:SetMenuForGroup(TaskGroup,MenuTime) +if self:IsStatePlanned()or self:IsStateAssigned()then +self:SetPlannedMenuForGroup(TaskGroup,MenuTime) +if self:IsGroupAssigned(TaskGroup)then +self:SetAssignedMenuForGroup(TaskGroup,MenuTime) +end +end +end +function TASK:SetPlannedMenuForGroup(TaskGroup,MenuTime) +self:F(TaskGroup:GetName()) +local Mission=self:GetMission() +local MissionName=Mission:GetName() +local MissionMenu=Mission:GetMenu(TaskGroup) +local TaskType=self:GetType() +local TaskPlayerCount=self:GetPlayerCount() +local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount) +local TaskText=string.format("%s",self:GetName()) +local TaskName=string.format("%s",self:GetName()) +self.MenuPlanned=self.MenuPlanned or{} +self.MenuPlanned[TaskGroup]=MENU_GROUP_DELAYED:New(TaskGroup,"Join Planned Task",MissionMenu,Mission.MenuReportTasksPerStatus,Mission,TaskGroup,"Planned"):SetTime(MenuTime):SetTag("Tasking") +local TaskTypeMenu=MENU_GROUP_DELAYED:New(TaskGroup,TaskType,self.MenuPlanned[TaskGroup]):SetTime(MenuTime):SetTag("Tasking") +local TaskTypeMenu=MENU_GROUP_DELAYED:New(TaskGroup,TaskText,TaskTypeMenu):SetTime(MenuTime):SetTag("Tasking") +if not Mission:IsGroupAssigned(TaskGroup)then +local JoinTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Join Task"),TaskTypeMenu,self.MenuAssignToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +local MarkTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Mark Task Location on Map"),TaskTypeMenu,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +end +local ReportTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Report Task Details"),TaskTypeMenu,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +return self +end +function TASK:SetAssignedMenuForGroup(TaskGroup,MenuTime) +self:F({TaskGroup:GetName(),MenuTime}) +local TaskType=self:GetType() +local TaskPlayerCount=self:GetPlayerCount() +local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount) +local TaskText=string.format("%s%s",self:GetName(),TaskPlayerString) +local TaskName=string.format("%s",self:GetName()) +for UnitName,TaskUnit in pairs(TaskGroup:GetPlayerUnits())do +local TaskUnit=TaskUnit +if TaskUnit then +local MenuControl=self:GetTaskControlMenu(TaskUnit) +local TaskControl=MENU_GROUP:New(TaskGroup,"Control Task",MenuControl):SetTime(MenuTime):SetTag("Tasking") +if self:IsStateAssigned()then +local TaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Abort Task"),TaskControl,self.MenuTaskAbort,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +end +local MarkMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Mark Task Location on Map"),TaskControl,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +local TaskTypeMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Report Task Details"),TaskControl,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +if not self.FlashTaskStatus then +local TaskFlashStatusMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Flash Task Details"),TaskControl,self.MenuFlashTaskStatus,self,TaskGroup,true):SetTime(MenuTime):SetTag("Tasking") +else +local TaskFlashStatusMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Stop Flash Task Details"),TaskControl,self.MenuFlashTaskStatus,self,TaskGroup,nil):SetTime(MenuTime):SetTag("Tasking") +end +end +end +return self +end +function TASK:RemoveMenu(MenuTime) +self:F({self:GetName(),MenuTime}) +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if TaskGroup:IsAlive()==true then +local TaskGroup=TaskGroup +if TaskGroup:IsAlive()==true and TaskGroup:GetPlayerNames()then +self:RefreshMenus(TaskGroup,MenuTime) +end +end +end +end +function TASK:RefreshMenus(TaskGroup,MenuTime) +self:F({TaskGroup:GetName(),MenuTime}) +local Mission=self:GetMission() +local MissionName=Mission:GetName() +local MissionMenu=Mission:GetMenu(TaskGroup) +local TaskName=self:GetName() +self.MenuPlanned=self.MenuPlanned or{} +local PlannedMenu=self.MenuPlanned[TaskGroup] +self.MenuAssigned=self.MenuAssigned or{} +local AssignedMenu=self.MenuAssigned[TaskGroup] +if PlannedMenu then +self.MenuPlanned[TaskGroup]=PlannedMenu:Remove(MenuTime,"Tasking") +PlannedMenu:Set() +end +if AssignedMenu then +self.MenuAssigned[TaskGroup]=AssignedMenu:Remove(MenuTime,"Tasking") +AssignedMenu:Set() +end +end +function TASK:RemoveAssignedMenuForGroup(TaskGroup) +self:F() +local Mission=self:GetMission() +local MissionName=Mission:GetName() +local MissionMenu=Mission:GetMenu(TaskGroup) +if MissionMenu then +MissionMenu:RemoveSubMenus() +end +end +function TASK:MenuAssignToGroup(TaskGroup) +self:F("Join Task menu selected") +self:AssignToGroup(TaskGroup) +end +function TASK:MenuMarkToGroup(TaskGroup) +self:F() +self:UpdateTaskInfo(self.DetectedItem) +local TargetCoordinates=self.TaskInfo:GetData("Coordinates") +if TargetCoordinates then +for TargetCoordinateID,TargetCoordinate in pairs(TargetCoordinates)do +local Report=REPORT:New():SetIndent(0) +self.TaskInfo:Report(Report,"M",TaskGroup,self) +local MarkText=Report:Text(", ") +self:F({Coordinate=TargetCoordinate,MarkText=MarkText}) +TargetCoordinate:MarkToGroup(MarkText,TaskGroup) +end +else +local TargetCoordinate=self.TaskInfo:GetData("Coordinate") +if TargetCoordinate then +local Report=REPORT:New():SetIndent(0) +self.TaskInfo:Report(Report,"M",TaskGroup,self) +local MarkText=Report:Text(", ") +self:F({Coordinate=TargetCoordinate,MarkText=MarkText}) +TargetCoordinate:MarkToGroup(MarkText,TaskGroup) +end +end +end +function TASK:MenuTaskStatus(TaskGroup) +if TaskGroup:IsAlive()then +local ReportText=self:ReportDetails(TaskGroup) +self:T(ReportText) +self:GetMission():GetCommandCenter():MessageTypeToGroup(ReportText,TaskGroup,MESSAGE.Type.Detailed) +end +end +function TASK:MenuFlashTaskStatus(TaskGroup,Flash) +self.FlashTaskStatus=Flash +if self.FlashTaskStatus then +self.FlashTaskScheduler,self.FlashTaskScheduleID=SCHEDULER:New(self,self.MenuTaskStatus,{TaskGroup},0,60) +else +if self.FlashTaskScheduler then +self.FlashTaskScheduler:Stop(self.FlashTaskScheduleID) +self.FlashTaskScheduler=nil +self.FlashTaskScheduleID=nil +end +end +end +function TASK:MenuTaskAbort(TaskGroup) +self:AbortGroup(TaskGroup) +end +function TASK:GetTaskName() +return self.TaskName +end +function TASK:GetTaskBriefing() +return self.TaskBriefing +end +function TASK:GetProcessTemplate(ProcessName) +local ProcessTemplate=self.ProcessClasses[ProcessName] +return ProcessTemplate +end +function TASK:FailProcesses(TaskUnitName) +for ProcessID,ProcessData in pairs(self.Processes[TaskUnitName])do +local Process=ProcessData +Process.Fsm:Fail() +end +end +function TASK:SetStateMachine(TaskUnit,Fsm) +self:F2({TaskUnit,self.Fsm[TaskUnit]~=nil,Fsm:GetClassNameAndID()}) +self.Fsm[TaskUnit]=Fsm +return Fsm +end +function TASK:GetStateMachine(TaskUnit) +self:F2({TaskUnit,self.Fsm[TaskUnit]~=nil}) +return self.Fsm[TaskUnit] +end +function TASK:RemoveStateMachine(TaskUnit) +self:F({TaskUnit=TaskUnit:GetName(),HasFsm=(self.Fsm[TaskUnit]~=nil)}) +if self.Fsm[TaskUnit]then +self.Fsm[TaskUnit]:Remove() +self.Fsm[TaskUnit]=nil +end +collectgarbage() +self:F("Garbage Collected, Processes should be finalized now ...") +end +function TASK:HasStateMachine(TaskUnit) +self:F({TaskUnit,self.Fsm[TaskUnit]~=nil}) +return(self.Fsm[TaskUnit]~=nil) +end +function TASK:GetScoring() +return self.Mission:GetScoring() +end +function TASK:GetTaskIndex() +local TaskType=self:GetType() +local TaskName=self:GetName() +return TaskType.."."..TaskName +end +function TASK:SetName(TaskName) +self.TaskName=TaskName +end +function TASK:GetName() +return self.TaskName +end +function TASK:SetType(TaskType) +self.TaskType=TaskType +end +function TASK:GetType() +return self.TaskType +end +function TASK:SetID(TaskID) +self.TaskID=TaskID +end +function TASK:GetID() +return self.TaskID +end +function TASK:StateSuccess() +self:SetState(self,"State","Success") +return self +end +function TASK:IsStateSuccess() +return self:Is("Success") +end +function TASK:StateFailed() +self:SetState(self,"State","Failed") +return self +end +function TASK:IsStateFailed() +return self:Is("Failed") +end +function TASK:StatePlanned() +self:SetState(self,"State","Planned") +return self +end +function TASK:IsStatePlanned() +return self:Is("Planned") +end +function TASK:StateAborted() +self:SetState(self,"State","Aborted") +return self +end +function TASK:IsStateAborted() +return self:Is("Aborted") +end +function TASK:StateCancelled() +self:SetState(self,"State","Cancelled") +return self +end +function TASK:IsStateCancelled() +return self:Is("Cancelled") +end +function TASK:StateAssigned() +self:SetState(self,"State","Assigned") +return self +end +function TASK:IsStateAssigned() +return self:Is("Assigned") +end +function TASK:StateHold() +self:SetState(self,"State","Hold") +return self +end +function TASK:IsStateHold() +return self:Is("Hold") +end +function TASK:StateReplanned() +self:SetState(self,"State","Replanned") +return self +end +function TASK:IsStateReplanned() +return self:Is("Replanned") +end +function TASK:GetStateString() +return self:GetState(self,"State") +end +function TASK:SetBriefing(TaskBriefing) +self:F(TaskBriefing) +self.TaskBriefing=TaskBriefing +return self +end +function TASK:GetBriefing() +return self.TaskBriefing +end +function TASK:onenterAssigned(From,Event,To,PlayerUnit,PlayerName) +if From~="Assigned"then +local PlayerNames=self:GetPlayerNames() +local PlayerText=REPORT:New() +for PlayerName,TaskName in pairs(PlayerNames)do +PlayerText:Add(PlayerName) +end +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." is assigned to players "..PlayerText:Text(",")..". Good Luck!") +self:SetGoalTotal() +if self.Dispatcher then +self:F("Firing Assign event ") +self.Dispatcher:Assign(self,PlayerUnit,PlayerName) +end +self:GetMission():__Start(1) +self:__Goal(-10,PlayerUnit,PlayerName) +self:SetMenu() +self:F({"--> Task Assigned",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"--> Task Player Names",PlayerNames=PlayerNames}) +end +end +function TASK:onenterSuccess(From,Event,To) +self:F({"<-> Task Replanned",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"<-> Task Player Names",PlayerNames=self:GetPlayerNames()}) +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." is successful! Good job!") +self:UnAssignFromGroups() +self:GetMission():__MissionGoals(1) +end +function TASK:onenterAborted(From,Event,To) +self:F({"<-- Task Aborted",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"<-- Task Player Names",PlayerNames=self:GetPlayerNames()}) +if From~="Aborted"then +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has been aborted! Task may be replanned.") +self:__Replan(5) +self:SetMenu() +end +end +function TASK:onenterCancelled(From,Event,To) +self:F({"<-- Task Cancelled",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"<-- Player Names",PlayerNames=self:GetPlayerNames()}) +if From~="Cancelled"then +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has been cancelled! The tactical situation has changed.") +self:UnAssignFromGroups() +self:SetMenu() +end +end +function TASK:onafterReplan(From,Event,To) +self:F({"Task Replanned",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"Task Player Names",PlayerNames=self:GetPlayerNames()}) +self:GetMission():GetCommandCenter():MessageToCoalition("Replanning Task "..self:GetName()..".") +self:SetMenu() +end +function TASK:onenterFailed(From,Event,To) +self:F({"Task Failed",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"Task Player Names",PlayerNames=self:GetPlayerNames()}) +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has failed!") +self:UnAssignFromGroups() +end +function TASK:onstatechange(From,Event,To) +if self:IsTrace()then +end +if self.Scores[To]then +local Scoring=self:GetScoring() +if Scoring then +self:F({self.Scores[To].ScoreText,self.Scores[To].Score}) +Scoring:_AddMissionScore(self.Mission,self.Scores[To].ScoreText,self.Scores[To].Score) +end +end +end +function TASK:onenterPlanned(From,Event,To) +if not self.TimeOut==0 then +self.__TimeOut(self.TimeOut) +end +end +function TASK:onbeforeTimeOut(From,Event,To) +if From=="Planned"then +self:RemoveMenu() +return true +end +return false +end +do +function TASK:SetGoal(Goal) +self.Goal=Goal +end +function TASK:GetGoal() +return self.Goal +end +function TASK:SetDispatcher(Dispatcher) +self.Dispatcher=Dispatcher +end +function TASK:SetDetection(Detection,DetectedItem) +self:F({DetectedItem,Detection}) +self.Detection=Detection +self.DetectedItem=DetectedItem +end +end +do +function TASK:ReportSummary(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local Report=REPORT:New() +Report:Add("Task "..self:GetName()) +Report:Add("State: <"..self:GetState()..">") +self.TaskInfo:Report(Report,"S",ReportGroup,self) +return Report:Text(', ') +end +function TASK:ReportOverview(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local TaskName=self:GetName() +local Report=REPORT:New() +self.TaskInfo:Report(Report,"O",ReportGroup,self) +return Report:Text() +end +function TASK:GetPlayerCount() +local PlayerCount=0 +for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do +local PlayerGroup=PlayerGroup +if PlayerGroup:IsAlive()==true then +if self:IsGroupAssigned(PlayerGroup)then +local PlayerNames=PlayerGroup:GetPlayerNames() +PlayerCount=PlayerCount+((PlayerNames)and#PlayerNames or 0) +end +end +end +return PlayerCount +end +function TASK:GetPlayerNames() +local PlayerNameMap={} +for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do +local PlayerGroup=PlayerGroup +if PlayerGroup:IsAlive()==true then +if self:IsGroupAssigned(PlayerGroup)then +local PlayerNames=PlayerGroup:GetPlayerNames() +for PlayerNameID,PlayerName in pairs(PlayerNames or{})do +PlayerNameMap[PlayerName]=PlayerGroup +end +end +end +end +return PlayerNameMap +end +function TASK:ReportDetails(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local Report=REPORT:New():SetIndent(3) +local Name=self:GetName() +local Status="<"..self:GetState()..">" +Report:Add("Task "..Name.." - "..Status.." - Detailed Report") +local PlayerNames=self:GetPlayerNames() +local PlayerReport=REPORT:New() +for PlayerName,PlayerGroup in pairs(PlayerNames)do +PlayerReport:Add("Players group "..PlayerGroup:GetCallsign()..": "..PlayerName) +end +local Players=PlayerReport:Text() +if Players~=""then +Report:AddIndent("Players assigned:","-") +Report:AddIndent(Players) +end +self.TaskInfo:Report(Report,"D",ReportGroup,self) +return Report:Text() +end +end +do +function TASK:AddProgress(PlayerName,ProgressText,ProgressTime,ProgressPoints) +self.TaskProgress=self.TaskProgress or{} +self.TaskProgress[ProgressTime]=self.TaskProgress[ProgressTime]or{} +self.TaskProgress[ProgressTime].PlayerName=PlayerName +self.TaskProgress[ProgressTime].ProgressText=ProgressText +self.TaskProgress[ProgressTime].ProgressPoints=ProgressPoints +self:GetMission():AddPlayerName(PlayerName) +return self +end +function TASK:GetPlayerProgress(PlayerName) +local ProgressPlayer=0 +for ProgressTime,ProgressData in pairs(self.TaskProgress)do +if PlayerName==ProgressData.PlayerName then +ProgressPlayer=ProgressPlayer+ProgressData.ProgressPoints +end +end +return ProgressPlayer +end +function TASK:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountPlayer","Player "..PlayerName.." has achieved progress.",Score) +return self +end +function TASK:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","The task is a success!",Score) +return self +end +function TASK:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The task is a failure!",Penalty) +return self +end +end +do +function TASK:InitTaskControlMenu(TaskUnit) +self.TaskControlMenuTime=timer.getTime() +return self.TaskControlMenuTime +end +function TASK:GetTaskControlMenu(TaskUnit,TaskName) +TaskName=TaskName or"" +local TaskGroup=TaskUnit:GetGroup() +local TaskPlayerCount=TaskGroup:GetPlayerCount() +if TaskPlayerCount<=1 then +self.TaskControlMenu=MENU_GROUP:New(TaskUnit:GetGroup(),"Task "..self:GetName().." control"):SetTime(self.TaskControlMenuTime) +else +self.TaskControlMenu=MENU_GROUP:New(TaskUnit:GetGroup(),"Task "..self:GetName().." control for "..TaskUnit:GetPlayerName()):SetTime(self.TaskControlMenuTime) +end +return self.TaskControlMenu +end +function TASK:RemoveTaskControlMenu(TaskUnit) +if self.TaskControlMenu then +self.TaskControlMenu:Remove() +self.TaskControlMenu=nil +end +end +function TASK:RefreshTaskControlMenu(TaskUnit,MenuTime,MenuTag) +if self.TaskControlMenu then +self.TaskControlMenu:Remove(MenuTime,MenuTag) +end +end +end +TASKINFO={ +ClassName="TASKINFO", +} +TASKINFO.Detail="" +function TASKINFO:New(Task) +local self=BASE:Inherit(self,BASE:New()) +self.Task=Task +self.VolatileInfo=SET_BASE:New() +self.PersistentInfo=SET_BASE:New() +self.Info=self.VolatileInfo +return self +end +function TASKINFO:AddInfo(Key,Data,Order,Detail,Keep,ShowKey,Type) +self.VolatileInfo:Add(Key,{Data=Data,Order=Order,Detail=Detail,ShowKey=ShowKey,Type=Type}) +if Keep==true then +self.PersistentInfo:Add(Key,{Data=Data,Order=Order,Detail=Detail,ShowKey=ShowKey,Type=Type}) +end +return self +end +function TASKINFO:GetInfo(Key) +local Object=self:Get(Key) +return Object.Data,Object.Order,Object.Detail +end +function TASKINFO:GetData(Key) +local Object=self.Info:Get(Key) +return Object and Object.Data +end +function TASKINFO:AddText(Key,Text,Order,Detail,Keep) +self:AddInfo(Key,Text,Order,Detail,Keep) +return self +end +function TASKINFO:AddTaskName(Order,Detail,Keep) +self:AddInfo("TaskName",self.Task:GetName(),Order,Detail,Keep) +return self +end +function TASKINFO:AddCoordinate(Coordinate,Order,Detail,Keep,ShowKey,Name) +self:AddInfo(Name or"Coordinate",Coordinate,Order,Detail,Keep,ShowKey,"Coordinate") +return self +end +function TASKINFO:GetCoordinate(Name) +return self:GetData(Name or"Coordinate") +end +function TASKINFO:AddCoordinates(Coordinates,Order,Detail,Keep) +self:AddInfo("Coordinates",Coordinates,Order,Detail,Keep) +return self +end +function TASKINFO:AddThreat(ThreatText,ThreatLevel,Order,Detail,Keep) +self:AddInfo("Threat"," ["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]:"..ThreatText,Order,Detail,Keep) +return self +end +function TASKINFO:GetThreat() +self:GetInfo("Threat") +return self +end +function TASKINFO:AddTargetCount(TargetCount,Order,Detail,Keep) +self:AddInfo("Counting",string.format("%d",TargetCount),Order,Detail,Keep) +return self +end +function TASKINFO:AddTargets(TargetCount,TargetTypes,Order,Detail,Keep) +self:AddInfo("Targets",string.format("%d of %s",TargetCount,TargetTypes),Order,Detail,Keep) +return self +end +function TASKINFO:GetTargets() +self:GetInfo("Targets") +return self +end +function TASKINFO:AddQFEAtCoordinate(Coordinate,Order,Detail,Keep) +self:AddInfo("QFE",Coordinate,Order,Detail,Keep) +return self +end +function TASKINFO:AddTemperatureAtCoordinate(Coordinate,Order,Detail,Keep) +self:AddInfo("Temperature",Coordinate,Order,Detail,Keep) +return self +end +function TASKINFO:AddWindAtCoordinate(Coordinate,Order,Detail,Keep) +self:AddInfo("Wind",Coordinate,Order,Detail,Keep) +return self +end +function TASKINFO:AddCargo(Cargo,Order,Detail,Keep) +self:AddInfo("Cargo",Cargo,Order,Detail,Keep) +return self +end +function TASKINFO:AddCargoSet(SetCargo,Order,Detail,Keep) +local CargoReport=REPORT:New() +CargoReport:Add("") +SetCargo:ForEachCargo( +function(Cargo) +CargoReport:Add(string.format(' - %s (%s) %s - status %s ',Cargo:GetName(),Cargo:GetType(),Cargo:GetTransportationMethod(),Cargo:GetCurrentState())) +end +) +self:AddInfo("Cargo",CargoReport:Text(),Order,Detail,Keep) +return self +end +function TASKINFO:Report(Report,Detail,ReportGroup,Task) +local Line=0 +local LineReport=REPORT:New() +if not self.Task:IsStatePlanned()and not self.Task:IsStateAssigned()then +self.Info=self.PersistentInfo +end +for Key,Data in UTILS.spairs(self.Info.Set,function(t,a,b)return t[a].Order0 then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterHasSEAD() +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2G_DISPATCHER:EvaluateCAS(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +local GroundUnitCount=DetectedSet:HasGroundUnits() +local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +local RadarCount=DetectedSet:HasSEAD() +if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==true then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2G_DISPATCHER:EvaluateBAI(DetectedItem,FriendlyCoalition) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +local GroundUnitCount=DetectedSet:HasGroundUnits() +local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +local RadarCount=DetectedSet:HasSEAD() +if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==false then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2G_DISPATCHER:RemoveTask(TaskIndex) +self.Mission:RemoveTask(self.Tasks[TaskIndex]) +self.Tasks[TaskIndex]=nil +end +function TASK_A2G_DISPATCHER:EvaluateRemoveTask(Mission,Task,TaskIndex,DetectedItemChanged) +if Task then +if(Task:IsStatePlanned()and DetectedItemChanged==true)or Task:IsStateCancelled()then +self:RemoveTask(TaskIndex) +end +end +return Task +end +function TASK_A2G_DISPATCHER:ProcessDetected(Detection) +self:F() +local AreaMsg={} +local TaskMsg={} +local ChangeMsg={} +local Mission=self.Mission +if Mission:IsIDLE()or Mission:IsENGAGED()then +local TaskReport=REPORT:New() +for TaskIndex,TaskData in pairs(self.Tasks)do +local Task=TaskData +if Task:IsStatePlanned()then +local DetectedItem=Detection:GetDetectedItemByIndex(TaskIndex) +if not DetectedItem then +local TaskText=Task:GetName() +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if self.FlashNewTask then +Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2G task %s for %s removed.",TaskText,Mission:GetShortText()),TaskGroup) +end +end +Task=self:RemoveTask(TaskIndex) +end +end +end +for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do +local DetectedItem=DetectedItem +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +local DetectedItemID=DetectedItem.ID +local TaskIndex=DetectedItem.Index +local DetectedItemChanged=DetectedItem.Changed +self:F({DetectedItemChanged=DetectedItemChanged,DetectedItemID=DetectedItemID,TaskIndex=TaskIndex}) +local Task=self.Tasks[TaskIndex] +if Task then +if Task:IsStateAssigned()then +if DetectedItemChanged==true then +local TargetsReport=REPORT:New() +local TargetSetUnit=self:EvaluateSEAD(DetectedItem) +if TargetSetUnit then +if Task:IsInstanceOf(TASK_A2G_SEAD)then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +TargetsReport:Add(Detection:GetChangeText(DetectedItem)) +else +Task:Cancel() +end +else +local TargetSetUnit=self:EvaluateCAS(DetectedItem) +if TargetSetUnit then +if Task:IsInstanceOf(TASK_A2G_CAS)then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +TargetsReport:Add(Detection:GetChangeText(DetectedItem)) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +else +local TargetSetUnit=self:EvaluateBAI(DetectedItem) +if TargetSetUnit then +if Task:IsInstanceOf(TASK_A2G_BAI)then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +TargetsReport:Add(Detection:GetChangeText(DetectedItem)) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +end +end +end +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +local TargetsText=TargetsReport:Text(", ") +if(Mission:IsGroupAssigned(TaskGroup))and TargetsText~=""and self.FlashNewTask then +Mission:GetCommandCenter():MessageToGroup(string.format("Task %s has change of targets:\n %s",Task:GetName(),TargetsText),TaskGroup) +end +end +end +end +end +if Task then +if Task:IsStatePlanned()then +if DetectedItemChanged==true then +if Task:IsInstanceOf(TASK_A2G_SEAD)then +local TargetSetUnit=self:EvaluateSEAD(DetectedItem) +if TargetSetUnit then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +else +if Task:IsInstanceOf(TASK_A2G_CAS)then +local TargetSetUnit=self:EvaluateCAS(DetectedItem) +if TargetSetUnit then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +else +if Task:IsInstanceOf(TASK_A2G_BAI)then +local TargetSetUnit=self:EvaluateBAI(DetectedItem) +if TargetSetUnit then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +end +end +end +end +end +if not Task then +local TargetSetUnit=self:EvaluateSEAD(DetectedItem) +if TargetSetUnit then +Task=TASK_A2G_SEAD:New(Mission,self.SetGroup,string.format("SEAD.%03d",DetectedItemID),TargetSetUnit) +DetectedItem.DesignateMenuName=string.format("SEAD.%03d",DetectedItemID) +Task:SetDetection(Detection,DetectedItem) +end +if not Task then +local TargetSetUnit=self:EvaluateCAS(DetectedItem) +if TargetSetUnit then +Task=TASK_A2G_CAS:New(Mission,self.SetGroup,string.format("CAS.%03d",DetectedItemID),TargetSetUnit) +DetectedItem.DesignateMenuName=string.format("CAS.%03d",DetectedItemID) +Task:SetDetection(Detection,DetectedItem) +end +if not Task then +local TargetSetUnit=self:EvaluateBAI(DetectedItem,self.Mission:GetCommandCenter():GetPositionable():GetCoalition()) +if TargetSetUnit then +Task=TASK_A2G_BAI:New(Mission,self.SetGroup,string.format("BAI.%03d",DetectedItemID),TargetSetUnit) +DetectedItem.DesignateMenuName=string.format("BAI.%03d",DetectedItemID) +Task:SetDetection(Detection,DetectedItem) +end +end +end +if Task then +self.Tasks[TaskIndex]=Task +Task:SetTargetZone(DetectedZone) +Task:SetDispatcher(self) +Task:UpdateTaskInfo(DetectedItem) +Mission:AddTask(Task) +function Task.OnEnterSuccess(Task,From,Event,To) +self:Success(Task) +end +function Task.OnEnterCancelled(Task,From,Event,To) +self:Cancelled(Task) +end +function Task.OnEnterFailed(Task,From,Event,To) +self:Failed(Task) +end +function Task.OnEnterAborted(Task,From,Event,To) +self:Aborted(Task) +end +TaskReport:Add(Task:GetName()) +else +self:F("This should not happen") +end +end +Detection:AcceptChanges(DetectedItem) +end +Mission:GetCommandCenter():SetMenu() +local TaskText=TaskReport:Text(", ") +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and self.FlashNewTask then +Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup) +end +end +end +return true +end +end +do +TASK_A2G={ +ClassName="TASK_A2G" +} +function TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskType,TaskBriefing) +local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing)) +self:F() +self.TargetSetUnit=TargetSetUnit +self.TaskType=TaskType +local Fsm=self:GetUnitProcess() +Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous") +Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"}) +Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"}) +Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous") +Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging") +Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous") +Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{}) +Fsm:AddTransition("Engaging","RouteToTarget","Engaging") +Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{}) +Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{}) +Fsm:AddTransition("Engaging","RouteToTargets","Engaging") +Fsm:AddTransition("Rejected","Reject","Aborted") +Fsm:AddTransition("Failed","Fail","Failed") +function Fsm:onafterAssigned(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:RouteToRendezVous() +end +function Fsm:onafterRouteToRendezVous(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Task:GetRendezVousZone(TaskUnit)then +self:__RouteToRendezVousZone(0.1) +else +if Task:GetRendezVousCoordinate(TaskUnit)then +self:__RouteToRendezVousPoint(0.1) +else +self:__ArriveAtRendezVous(0.1) +end +end +end +function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:__Engage(0.1) +end +function Fsm:onafterEngage(TaskUnit,Task) +self:F({self}) +self:__Account(0.1) +self:__RouteToTarget(0.1) +self:__RouteToTargets(-10) +end +function Fsm:onafterRouteToTarget(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Task:GetTargetZone(TaskUnit)then +self:__RouteToTargetZone(0.1) +else +local TargetUnit=Task.TargetSetUnit:GetFirst() +if TargetUnit then +local Coordinate=TargetUnit:GetPointVec3() +self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetY(),Coordinate:GetZ()}) +Task:SetTargetCoordinate(Coordinate,TaskUnit) +end +self:__RouteToTargetPoint(0.1) +end +end +function Fsm:onafterRouteToTargets(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +local TargetUnit=Task.TargetSetUnit:GetFirst() +if TargetUnit then +Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit) +end +self:__RouteToTargets(-10) +end +return self +end +function TASK_A2G:SetTargetSetUnit(TargetSetUnit) +self.TargetSetUnit=TargetSetUnit +end +function TASK_A2G:GetPlannedMenuText() +return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )" +end +function TASK_A2G:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") +ActRouteRendezVous:SetCoordinate(RendezVousCoordinate) +ActRouteRendezVous:SetRange(RendezVousRange) +end +function TASK_A2G:GetRendezVousCoordinate(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") +return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange() +end +function TASK_A2G:SetRendezVousZone(RendezVousZone,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") +ActRouteRendezVous:SetZone(RendezVousZone) +end +function TASK_A2G:GetRendezVousZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") +return ActRouteRendezVous:GetZone() +end +function TASK_A2G:SetTargetCoordinate(TargetCoordinate,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") +ActRouteTarget:SetCoordinate(TargetCoordinate) +end +function TASK_A2G:GetTargetCoordinate(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") +return ActRouteTarget:GetCoordinate() +end +function TASK_A2G:SetTargetZone(TargetZone,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +ActRouteTarget:SetZone(TargetZone) +end +function TASK_A2G:GetTargetZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +return ActRouteTarget:GetZone() +end +function TASK_A2G:SetGoalTotal() +self.GoalTotal=self.TargetSetUnit:CountAlive() +end +function TASK_A2G:GetGoalTotal() +return self.GoalTotal +end +function TASK_A2G:ReportOrder(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local Coordinate=self.TaskInfo:GetData("Coordinate") +local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate) +return Distance +end +function TASK_A2G:onafterGoal(TaskUnit,From,Event,To) +local TargetSetUnit=self.TargetSetUnit +if TargetSetUnit:CountAlive()==0 then +self:Success() +end +self:__Goal(-10) +end +function TASK_A2G:UpdateTaskInfo(DetectedItem) +if self:IsStatePlanned()or self:IsStateAssigned()then +local TargetCoordinate=DetectedItem and self.Detection:GetDetectedItemCoordinate(DetectedItem)or self.TargetSetUnit:GetFirst():GetCoordinate() +self.TaskInfo:AddTaskName(0,"MSOD") +self.TaskInfo:AddCoordinate(TargetCoordinate,1,"SOD") +local ThreatLevel,ThreatText +if DetectedItem then +ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(DetectedItem) +else +ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G() +end +self.TaskInfo:AddThreat(ThreatText,ThreatLevel,10,"MOD",true) +if self.Detection then +local DetectedItemsCount=self.TargetSetUnit:CountAlive() +local ReportTypes=REPORT:New() +local TargetTypes={} +for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do +local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit) +if not TargetTypes[TargetType]then +TargetTypes[TargetType]=TargetType +ReportTypes:Add(TargetType) +end +end +self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) +self.TaskInfo:AddTargets(DetectedItemsCount,ReportTypes:Text(", "),20,"D",true) +else +local DetectedItemsCount=self.TargetSetUnit:CountAlive() +local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames() +self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) +self.TaskInfo:AddTargets(DetectedItemsCount,DetectedItemsTypes,20,"D",true) +end +self.TaskInfo:AddQFEAtCoordinate(TargetCoordinate,30,"MOD") +self.TaskInfo:AddTemperatureAtCoordinate(TargetCoordinate,31,"MD") +self.TaskInfo:AddWindAtCoordinate(TargetCoordinate,32,"MD") +end +end +function TASK_A2G:GetAutoAssignPriority(AutoAssignMethod,CommandCenter,TaskGroup) +if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then +return math.random(1,9) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then +local Coordinate=self.TaskInfo:GetData("Coordinate") +local Distance=Coordinate:Get2DDistance(CommandCenter:GetPositionable():GetCoordinate()) +self:F({Distance=Distance}) +return math.floor(Distance) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then +return 1 +end +return 0 +end +end +do +TASK_A2G_SEAD={ +ClassName="TASK_A2G_SEAD" +} +function TASK_A2G_SEAD:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"SEAD",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing(TaskBriefing or"Execute a Suppression of Enemy Air Defenses.") +return self +end +function TASK_A2G_SEAD:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has SEADed a target.",Score) +return self +end +function TASK_A2G_SEAD:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All radar emitting targets have been successfully SEADed!",Score) +return self +end +function TASK_A2G_SEAD:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The SEADing has failed!",Penalty) +return self +end +end +do +TASK_A2G_BAI={ClassName="TASK_A2G_BAI"} +function TASK_A2G_BAI:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"BAI",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing(TaskBriefing or"Execute a Battlefield Air Interdiction of a group of enemy targets.") +return self +end +function TASK_A2G_BAI:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Battlefield Air Interdiction (BAI).",Score) +return self +end +function TASK_A2G_BAI:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!",Score) +return self +end +function TASK_A2G_BAI:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The Battlefield Air Interdiction (BAI) has failed!",Penalty) +return self +end +end +do +TASK_A2G_CAS={ClassName="TASK_A2G_CAS"} +function TASK_A2G_CAS:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"CAS",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing(TaskBriefing or("Execute a Close Air Support for a group of enemy targets. ".."Beware of friendlies at the vicinity! ")) +return self +end +function TASK_A2G_CAS:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Close Air Support (CAS).",Score) +return self +end +function TASK_A2G_CAS:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Close Air Support (CAS) was a success!",Score) +return self +end +function TASK_A2G_CAS:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The Close Air Support (CAS) has failed!",Penalty) +return self +end +end +do +TASK_A2A_DISPATCHER={ +ClassName="TASK_A2A_DISPATCHER", +Mission=nil, +Detection=nil, +Tasks={}, +SweepZones={}, +} +function TASK_A2A_DISPATCHER:New(Mission,SetGroup,Detection) +local self=BASE:Inherit(self,DETECTION_MANAGER:New(SetGroup,Detection)) +self.Detection=Detection +self.Mission=Mission +self.FlashNewTask=false +self.Detection:FilterCategories(Unit.Category.AIRPLANE,Unit.Category.HELICOPTER) +self.Detection:InitDetectRadar(true) +self.Detection:SetRefreshTimeInterval(30) +self:AddTransition("Started","Assign","Started") +self:__Start(5) +return self +end +function TASK_A2A_DISPATCHER:SetEngageRadius(EngageRadius) +self.Detection:SetFriendliesRange(EngageRadius or 100000) +return self +end +function TASK_A2A_DISPATCHER:SetSendMessages(onoff) +self.FlashNewTask=onoff +end +function TASK_A2A_DISPATCHER:EvaluateINTERCEPT(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +if DetectedItem.IsDetected==true then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +if DetectedItem.IsDetected==false then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2A_DISPATCHER:EvaluateENGAGE(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem) +if PlayersCount>0 and DetectedItem.IsDetected==true then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2A_DISPATCHER:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,DetectedItemIndex,DetectedItemChanged) +if Task then +if Task:IsStatePlanned()then +local TaskName=Task:GetName() +local TaskType=TaskName:match("(%u+)%.%d+") +self:T2({TaskType=TaskType}) +local Remove=false +local IsPlayers=Detection:IsPlayersNearBy(DetectedItem) +if TaskType=="ENGAGE"then +if IsPlayers==false then +Remove=true +end +end +if TaskType=="INTERCEPT"then +if IsPlayers==true then +Remove=true +end +if DetectedItem.IsDetected==false then +Remove=true +end +end +if TaskType=="SWEEP"then +if DetectedItem.IsDetected==true then +Remove=true +end +end +local DetectedSet=DetectedItem.Set +if DetectedSet:Count()==0 then +Remove=true +end +if DetectedItemChanged==true or Remove then +Task=self:RemoveTask(DetectedItemIndex) +end +end +end +return Task +end +function TASK_A2A_DISPATCHER:GetFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem,Unit.Category.AIRPLANE) +local FriendlyTypes={} +local FriendliesCount=0 +if FriendlyUnitsNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do +local FriendlyUnit=FriendlyUnitData +if FriendlyUnit:IsAirPlane()then +local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel() +FriendliesCount=FriendliesCount+1 +local FriendlyType=FriendlyUnit:GetTypeName() +FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1 +if DetectedTreatLevel0 then +for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do +FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType)) +end +else +FriendlyTypesReport:Add("-") +end +return FriendliesCount,FriendlyTypesReport +end +function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem) +local PlayerTypes={} +local PlayersCount=0 +if PlayersNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do +local PlayerUnit=PlayerUnitData +local PlayerName=PlayerUnit:GetPlayerName() +if PlayerUnit:IsAirPlane()and PlayerName~=nil then +local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel() +PlayersCount=PlayersCount+1 +local PlayerType=PlayerUnit:GetTypeName() +PlayerTypes[PlayerName]=PlayerType +if DetectedTreatLevel0 then +for PlayerName,PlayerType in pairs(PlayerTypes)do +PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType)) +end +else +PlayerTypesReport:Add("-") +end +return PlayersCount,PlayerTypesReport +end +function TASK_A2A_DISPATCHER:RemoveTask(TaskIndex) +self.Mission:RemoveTask(self.Tasks[TaskIndex]) +self.Tasks[TaskIndex]=nil +end +function TASK_A2A_DISPATCHER:ProcessDetected(Detection) +self:F() +local AreaMsg={} +local TaskMsg={} +local ChangeMsg={} +local Mission=self.Mission +if Mission:IsIDLE()or Mission:IsENGAGED()then +local TaskReport=REPORT:New() +for TaskIndex,TaskData in pairs(self.Tasks)do +local Task=TaskData +if Task:IsStatePlanned()then +local DetectedItem=Detection:GetDetectedItemByIndex(TaskIndex) +if not DetectedItem then +local TaskText=Task:GetName() +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2A task %s for %s removed.",TaskText,Mission:GetShortText()),TaskGroup) +end +Task=self:RemoveTask(TaskIndex) +end +end +end +for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do +local DetectedItem=DetectedItem +local DetectedSet=DetectedItem.Set +local DetectedCount=DetectedSet:Count() +local DetectedZone=DetectedItem.Zone +local DetectedID=DetectedItem.ID +local TaskIndex=DetectedItem.Index +local DetectedItemChanged=DetectedItem.Changed +local Task=self.Tasks[TaskIndex] +Task=self:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,TaskIndex,DetectedItemChanged) +if not Task and DetectedCount>0 then +local TargetSetUnit=self:EvaluateENGAGE(DetectedItem) +if TargetSetUnit then +Task=TASK_A2A_ENGAGE:New(Mission,self.SetGroup,string.format("ENGAGE.%03d",DetectedID),TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +local TargetSetUnit=self:EvaluateINTERCEPT(DetectedItem) +if TargetSetUnit then +Task=TASK_A2A_INTERCEPT:New(Mission,self.SetGroup,string.format("INTERCEPT.%03d",DetectedID),TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +local TargetSetUnit=self:EvaluateSWEEP(DetectedItem) +if TargetSetUnit then +Task=TASK_A2A_SWEEP:New(Mission,self.SetGroup,string.format("SWEEP.%03d",DetectedID),TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +end +end +end +if Task then +self.Tasks[TaskIndex]=Task +Task:SetTargetZone(DetectedZone,DetectedItem.Coordinate.y,DetectedItem.Coordinate.Heading) +Task:SetDispatcher(self) +Mission:AddTask(Task) +function Task.OnEnterSuccess(Task,From,Event,To) +self:Success(Task) +end +function Task.OnEnterCancelled(Task,From,Event,To) +self:Cancelled(Task) +end +function Task.OnEnterFailed(Task,From,Event,To) +self:Failed(Task) +end +function Task.OnEnterAborted(Task,From,Event,To) +self:Aborted(Task) +end +TaskReport:Add(Task:GetName()) +else +self:F("This should not happen") +end +end +if Task then +local FriendliesCount,FriendliesReport=self:GetFriendliesNearBy(DetectedItem,Unit.Category.AIRPLANE) +Task.TaskInfo:AddText("Friendlies",string.format("%d ( %s )",FriendliesCount,FriendliesReport:Text(",")),40,"MOD") +local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem) +Task.TaskInfo:AddText("Players",string.format("%d ( %s )",PlayersCount,PlayersReport:Text(",")),40,"MOD") +end +Detection:AcceptChanges(DetectedItem) +end +Mission:GetCommandCenter():SetMenu() +local TaskText=TaskReport:Text(", ") +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and(self.FlashNewTask)then +Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup) +end +end +end +return true +end +end +do +TASK_A2A={ +ClassName="TASK_A2A" +} +function TASK_A2A:New(Mission,SetAttack,TaskName,TargetSetUnit,TaskType,TaskBriefing) +local self=BASE:Inherit(self,TASK:New(Mission,SetAttack,TaskName,TaskType,TaskBriefing)) +self:F() +self.TargetSetUnit=TargetSetUnit +self.TaskType=TaskType +local Fsm=self:GetUnitProcess() +Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous") +Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"}) +Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"}) +Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous") +Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging") +Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous") +Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{}) +Fsm:AddTransition("Engaging","RouteToTarget","Engaging") +Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{}) +Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{}) +Fsm:AddTransition("Engaging","RouteToTargets","Engaging") +Fsm:AddTransition("Rejected","Reject","Aborted") +Fsm:AddTransition("Failed","Fail","Failed") +function Fsm:OnLeaveAssigned(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:SelectAction() +end +function Fsm:onafterRouteToRendezVous(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Task:GetRendezVousZone(TaskUnit)then +self:__RouteToRendezVousZone(0.1) +else +if Task:GetRendezVousCoordinate(TaskUnit)then +self:__RouteToRendezVousPoint(0.1) +else +self:__ArriveAtRendezVous(0.1) +end +end +end +function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:__Engage(0.1) +end +function Fsm:onafterEngage(TaskUnit,Task) +self:F({self}) +self:__Account(0.1) +self:__RouteToTarget(0.1) +self:__RouteToTargets(-10) +end +function Fsm:onafterRouteToTarget(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Task:GetTargetZone(TaskUnit)then +self:__RouteToTargetZone(0.1) +else +local TargetUnit=Task.TargetSetUnit:GetFirst() +if TargetUnit then +local Coordinate=TargetUnit:GetPointVec3() +self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetAlt(),Coordinate:GetZ()}) +Task:SetTargetCoordinate(Coordinate,TaskUnit) +end +self:__RouteToTargetPoint(0.1) +end +end +function Fsm:onafterRouteToTargets(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +local TargetUnit=Task.TargetSetUnit:GetFirst() +if TargetUnit then +Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit) +end +self:__RouteToTargets(-10) +end +return self +end +function TASK_A2A:SetTargetSetUnit(TargetSetUnit) +self.TargetSetUnit=TargetSetUnit +end +function TASK_A2A:GetPlannedMenuText() +return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )" +end +function TASK_A2A:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") +ActRouteRendezVous:SetCoordinate(RendezVousCoordinate) +ActRouteRendezVous:SetRange(RendezVousRange) +end +function TASK_A2A:GetRendezVousCoordinate(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") +return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange() +end +function TASK_A2A:SetRendezVousZone(RendezVousZone,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") +ActRouteRendezVous:SetZone(RendezVousZone) +end +function TASK_A2A:GetRendezVousZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") +return ActRouteRendezVous:GetZone() +end +function TASK_A2A:SetTargetCoordinate(TargetCoordinate,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") +ActRouteTarget:SetCoordinate(TargetCoordinate) +end +function TASK_A2A:GetTargetCoordinate(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") +return ActRouteTarget:GetCoordinate() +end +function TASK_A2A:SetTargetZone(TargetZone,Altitude,Heading,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +ActRouteTarget:SetZone(TargetZone,Altitude,Heading) +end +function TASK_A2A:GetTargetZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +return ActRouteTarget:GetZone() +end +function TASK_A2A:SetGoalTotal() +self.GoalTotal=self.TargetSetUnit:Count() +end +function TASK_A2A:GetGoalTotal() +return self.GoalTotal +end +function TASK_A2A:ReportOrder(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local Coordinate=self.TaskInfo:GetData("Coordinate") +local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate) +return Distance +end +function TASK_A2A:onafterGoal(TaskUnit,From,Event,To) +local TargetSetUnit=self.TargetSetUnit +if TargetSetUnit:Count()==0 then +self:Success() +end +self:__Goal(-10) +end +function TASK_A2A:UpdateTaskInfo(DetectedItem) +if self:IsStatePlanned()or self:IsStateAssigned()then +local TargetCoordinate=DetectedItem and self.Detection:GetDetectedItemCoordinate(DetectedItem)or self.TargetSetUnit:GetFirst():GetCoordinate() +self.TaskInfo:AddTaskName(0,"MSOD") +self.TaskInfo:AddCoordinate(TargetCoordinate,1,"SOD") +local ThreatLevel,ThreatText +if DetectedItem then +ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(DetectedItem) +else +ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G() +end +self.TaskInfo:AddThreat(ThreatText,ThreatLevel,10,"MOD",true) +if self.Detection then +local DetectedItemsCount=self.TargetSetUnit:Count() +local ReportTypes=REPORT:New() +local TargetTypes={} +for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do +local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit) +if not TargetTypes[TargetType]then +TargetTypes[TargetType]=TargetType +ReportTypes:Add(TargetType) +end +end +self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) +self.TaskInfo:AddTargets(DetectedItemsCount,ReportTypes:Text(", "),20,"D",true) +else +local DetectedItemsCount=self.TargetSetUnit:Count() +local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames() +self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) +self.TaskInfo:AddTargets(DetectedItemsCount,DetectedItemsTypes,20,"D",true) +end +end +end +function TASK_A2A:GetAutoAssignPriority(AutoAssignMethod,CommandCenter,TaskGroup) +if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then +return math.random(1,9) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then +local Coordinate=self.TaskInfo:GetData("Coordinate") +local Distance=Coordinate:Get2DDistance(CommandCenter:GetPositionable():GetCoordinate()) +return math.floor(Distance) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then +return 1 +end +return 0 +end +end +do +TASK_A2A_INTERCEPT={ +ClassName="TASK_A2A_INTERCEPT" +} +function TASK_A2A_INTERCEPT:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"INTERCEPT",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing(TaskBriefing or"Intercept incoming intruders.\n") +return self +end +function TASK_A2A_INTERCEPT:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has intercepted a target.",Score) +return self +end +function TASK_A2A_INTERCEPT:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully intercepted!",Score) +return self +end +function TASK_A2A_INTERCEPT:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The intercept has failed!",Penalty) +return self +end +end +do +TASK_A2A_SWEEP={ +ClassName="TASK_A2A_SWEEP" +} +function TASK_A2A_SWEEP:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"SWEEP",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing(TaskBriefing or"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n") +return self +end +function TASK_A2A_SWEEP:onafterGoal(TaskUnit,From,Event,To) +local TargetSetUnit=self.TargetSetUnit +if TargetSetUnit:Count()==0 then +self:Success() +end +self:__Goal(-10) +end +function TASK_A2A_SWEEP:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has sweeped a target.",Score) +return self +end +function TASK_A2A_SWEEP:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully sweeped!",Score) +return self +end +function TASK_A2A_SWEEP:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The sweep has failed!",Penalty) +return self +end +end +do +TASK_A2A_ENGAGE={ +ClassName="TASK_A2A_ENGAGE" +} +function TASK_A2A_ENGAGE:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"ENGAGE",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing(TaskBriefing or"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n") +return self +end +function TASK_A2A_ENGAGE:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has engaged and destroyed a target.",Score) +return self +end +function TASK_A2A_ENGAGE:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully engaged!",Score) +return self +end +function TASK_A2A_ENGAGE:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The target engagement has failed!",Penalty) +return self +end +end +do +TASK_CARGO={ +ClassName="TASK_CARGO", +} +function TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,TaskType,TaskBriefing) +local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing)) +self:F({Mission,SetGroup,TaskName,SetCargo,TaskType}) +self.SetCargo=SetCargo +self.TaskType=TaskType +self.SmokeColor=SMOKECOLOR.Red +self.CargoItemCount={} +self.CargoLimit=10 +self.DeployZones={} +self:AddTransition("*","CargoDeployed","*") +self:AddTransition("*","CargoPickedUp","*") +local Fsm=self:GetUnitProcess() +Fsm:AddTransition({"Planned","Assigned","Cancelled","WaitingForCommand","ArrivedAtPickup","ArrivedAtDeploy","Boarded","UnBoarded","Loaded","UnLoaded","Landed","Boarding"},"SelectAction","*") +Fsm:AddTransition("*","RouteToPickup","RoutingToPickup") +Fsm:AddProcess("RoutingToPickup","RouteToPickupPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtPickup",Cancelled="CancelRouteToPickup"}) +Fsm:AddTransition("Arrived","ArriveAtPickup","ArrivedAtPickup") +Fsm:AddTransition("Cancelled","CancelRouteToPickup","Cancelled") +Fsm:AddTransition("*","RouteToDeploy","RoutingToDeploy") +Fsm:AddProcess("RoutingToDeploy","RouteToDeployZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtDeploy",Cancelled="CancelRouteToDeploy"}) +Fsm:AddTransition("Arrived","ArriveAtDeploy","ArrivedAtDeploy") +Fsm:AddTransition("Cancelled","CancelRouteToDeploy","Cancelled") +Fsm:AddTransition({"ArrivedAtPickup","ArrivedAtDeploy","Landing"},"Land","Landing") +Fsm:AddTransition("Landing","Landed","Landed") +Fsm:AddTransition("*","PrepareBoarding","AwaitBoarding") +Fsm:AddTransition("AwaitBoarding","Board","Boarding") +Fsm:AddTransition("Boarding","Boarded","Boarded") +Fsm:AddTransition("*","Load","Loaded") +Fsm:AddTransition("*","PrepareUnBoarding","AwaitUnBoarding") +Fsm:AddTransition("AwaitUnBoarding","UnBoard","UnBoarding") +Fsm:AddTransition("UnBoarding","UnBoarded","UnBoarded") +Fsm:AddTransition("*","Unload","Unloaded") +Fsm:AddTransition("*","Planned","Planned") +Fsm:AddTransition("Deployed","Success","Success") +Fsm:AddTransition("Rejected","Reject","Aborted") +Fsm:AddTransition("Failed","Fail","Failed") +function Fsm:OnAfterAssigned(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:SelectAction() +end +function Fsm:onafterSelectAction(TaskUnit,Task) +local TaskUnitName=TaskUnit:GetName() +local MenuTime=Task:InitTaskControlMenu(TaskUnit) +local MenuControl=Task:GetTaskControlMenu(TaskUnit) +Task.SetCargo:ForEachCargo( +function(Cargo) +if Cargo:IsAlive()then +local TaskGroup=TaskUnit:GetGroup() +if Cargo:IsUnLoaded()then +local CargoBayFreeWeight=TaskUnit:GetCargoBayFreeWeight() +local CargoWeight=Cargo:GetWeight() +self:F({CargoBayFreeWeight=CargoBayFreeWeight}) +if CargoBayFreeWeight>CargoWeight then +if Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then +local NotInDeployZones=true +for DeployZoneName,DeployZone in pairs(Task.DeployZones)do +if Cargo:IsInZone(DeployZone)then +NotInDeployZones=false +end +end +if NotInDeployZones then +if not TaskUnit:InAir()then +if Cargo:CanBoard()==true then +if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +Cargo:Report("Ready for boarding.","board",TaskUnit:GetGroup()) +local BoardMenu=MENU_GROUP:New(TaskGroup,"Board cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,BoardMenu,self.MenuBoardCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +else +Cargo:Report("Board at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup().."."),"reporting",TaskUnit:GetGroup()) +end +else +if Cargo:CanLoad()==true then +if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +Cargo:Report("Ready for loading.","load",TaskUnit:GetGroup()) +local LoadMenu=MENU_GROUP:New(TaskGroup,"Load cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,LoadMenu,self.MenuLoadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +else +Cargo:Report("Load at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup()).." within "..Cargo.NearRadius..".","reporting",TaskUnit:GetGroup()) +end +else +if Cargo:CanSlingload()==true then +if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +Cargo:Report("Ready for sling loading.","slingload",TaskUnit:GetGroup()) +local SlingloadMenu=MENU_GROUP:New(TaskGroup,"Slingload cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,SlingloadMenu,self.MenuLoadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +else +Cargo:Report("Slingload at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup())..".","reporting",TaskUnit:GetGroup()) +end +end +end +end +else +Cargo:ReportResetAll(TaskUnit:GetGroup()) +end +end +else +if not Cargo:IsDeployed()==true then +local RouteToPickupMenu=MENU_GROUP:New(TaskGroup,"Route to pickup cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +Cargo:ReportResetAll(TaskUnit:GetGroup()) +if Cargo:CanBoard()==true then +if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +local BoardMenu=MENU_GROUP:New(TaskGroup,"Board cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,BoardMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +else +if Cargo:CanLoad()==true then +if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +local LoadMenu=MENU_GROUP:New(TaskGroup,"Load cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,LoadMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +else +if Cargo:CanSlingload()==true then +if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +local SlingloadMenu=MENU_GROUP:New(TaskGroup,"Slingload cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,SlingloadMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +end +end +end +end +end +end +for DeployZoneName,DeployZone in pairs(Task.DeployZones)do +if Cargo:IsInZone(DeployZone)then +Task:I({CargoIsDeployed=Task.CargoDeployed and"true"or"false"}) +if Cargo:IsDeployed()==false then +Cargo:SetDeployed(true) +Task:I({CargoIsAlive=Cargo:IsAlive()and"true"or"false"}) +if Cargo:IsAlive()then +Task:CargoDeployed(TaskUnit,Cargo,DeployZone) +end +end +end +end +end +if Cargo:IsLoaded()==true and Cargo:IsLoadedInCarrier(TaskUnit)==true then +if not TaskUnit:InAir()then +if Cargo:CanUnboard()==true then +local UnboardMenu=MENU_GROUP:New(TaskGroup,"Unboard cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,UnboardMenu,self.MenuUnboardCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +else +if Cargo:CanUnload()==true then +local UnloadMenu=MENU_GROUP:New(TaskGroup,"Unload cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,UnloadMenu,self.MenuUnloadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +end +end +end +for DeployZoneName,DeployZone in pairs(Task.DeployZones)do +if not Cargo:IsInZone(DeployZone)then +local RouteToDeployMenu=MENU_GROUP:New(TaskGroup,"Route to deploy cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),"Zone "..DeployZoneName,RouteToDeployMenu,self.MenuRouteToDeploy,self,DeployZone):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +end +end +end +) +Task:RefreshTaskControlMenu(TaskUnit,MenuTime,"Cargo") +self:__SelectAction(-1) +end +function Fsm:OnLeaveWaitingForCommand(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +end +function Fsm:MenuBoardCargo(Cargo) +self:__PrepareBoarding(1.0,Cargo) +end +function Fsm:MenuLoadCargo(Cargo) +self:__Load(1.0,Cargo) +end +function Fsm:MenuUnboardCargo(Cargo,DeployZone) +self:__PrepareUnBoarding(1.0,Cargo,DeployZone) +end +function Fsm:MenuUnloadCargo(Cargo,DeployZone) +self:__Unload(1.0,Cargo,DeployZone) +end +function Fsm:MenuRouteToPickup(Cargo) +self:__RouteToPickup(1.0,Cargo) +end +function Fsm:MenuRouteToDeploy(DeployZone) +self:__RouteToDeploy(1.0,DeployZone) +end +function Fsm:onafterRouteToPickup(TaskUnit,Task,From,Event,To,Cargo) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Cargo:IsAlive()then +self.Cargo=Cargo +Task:SetCargoPickup(self.Cargo,TaskUnit) +self:__RouteToPickupPoint(-0.1) +end +end +function Fsm:onafterArriveAtPickup(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if self.Cargo:IsAlive()then +if TaskUnit:IsAir()then +Task:GetMission():GetCommandCenter():MessageToGroup("Land",TaskUnit:GetGroup()) +self:__Land(-0.1,"Pickup") +else +self:__SelectAction(-0.1) +end +end +end +function Fsm:onafterCancelRouteToPickup(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +Task:GetMission():GetCommandCenter():MessageToGroup("Cancelled routing to Cargo "..self.Cargo:GetName(),TaskUnit:GetGroup()) +self:__SelectAction(-0.1) +end +function Fsm:onafterRouteToDeploy(TaskUnit,Task,From,Event,To,DeployZone) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:F(DeployZone) +self.DeployZone=DeployZone +Task:SetDeployZone(self.DeployZone,TaskUnit) +self:__RouteToDeployZone(-0.1) +end +function Fsm:onafterArriveAtDeploy(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if TaskUnit:IsAir()then +Task:GetMission():GetCommandCenter():MessageToGroup("Land",TaskUnit:GetGroup()) +self:__Land(-0.1,"Deploy") +else +self:__SelectAction(-0.1) +end +end +function Fsm:onafterCancelRouteToDeploy(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +Task:GetMission():GetCommandCenter():MessageToGroup("Cancelled routing to deploy zone "..self.DeployZone:GetName(),TaskUnit:GetGroup()) +self:__SelectAction(-0.1) +end +function Fsm:onafterLand(TaskUnit,Task,From,Event,To,Action) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Action=="Pickup"then +if self.Cargo:IsAlive()then +if self.Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then +if TaskUnit:InAir()then +self:__Land(-10,Action) +else +Task:GetMission():GetCommandCenter():MessageToGroup("Landed at pickup location...",TaskUnit:GetGroup()) +self:__Landed(-0.1,Action) +end +else +self:__RouteToPickup(-0.1,self.Cargo) +end +end +else +if TaskUnit:IsAlive()then +if TaskUnit:IsInZone(self.DeployZone)then +if TaskUnit:InAir()then +self:__Land(-10,Action) +else +Task:GetMission():GetCommandCenter():MessageToGroup("Landed at deploy zone "..self.DeployZone:GetName(),TaskUnit:GetGroup()) +self:__Landed(-0.1,Action) +end +else +self:__RouteToDeploy(-0.1,self.Cargo) +end +end +end +end +function Fsm:onafterLanded(TaskUnit,Task,From,Event,To,Action) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Action=="Pickup"then +if self.Cargo:IsAlive()then +if self.Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then +if TaskUnit:InAir()then +self:__Land(-0.1,Action) +else +self:__SelectAction(-0.1) +end +else +self:__RouteToPickup(-0.1,self.Cargo) +end +end +else +if TaskUnit:IsAlive()then +if TaskUnit:IsInZone(self.DeployZone)then +if TaskUnit:InAir()then +self:__Land(-10,Action) +else +self:__SelectAction(-0.1) +end +else +self:__RouteToDeploy(-0.1,self.Cargo) +end +end +end +end +function Fsm:onafterPrepareBoarding(TaskUnit,Task,From,Event,To,Cargo) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Cargo and Cargo:IsAlive()then +self:__Board(-0.1,Cargo) +end +end +function Fsm:onafterBoard(TaskUnit,Task,From,Event,To,Cargo) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +function Cargo:OnEnterLoaded(From,Event,To,TaskUnit,TaskProcess) +self:F({From,Event,To,TaskUnit,TaskProcess}) +TaskProcess:__Boarded(0.1,self) +end +if Cargo:IsAlive()then +if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +if TaskUnit:InAir()then +else +Cargo:MessageToGroup("Boarding ...",TaskUnit:GetGroup()) +if not Cargo:IsBoarding()then +Cargo:Board(TaskUnit,nil,self) +end +end +else +end +end +end +function Fsm:onafterBoarded(TaskUnit,Task,From,Event,To,Cargo) +local TaskUnitName=TaskUnit:GetName() +self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) +Cargo:MessageToGroup("Boarded cargo "..Cargo:GetName(),TaskUnit:GetGroup()) +self:__Load(-0.1,Cargo) +end +function Fsm:onafterLoad(TaskUnit,Task,From,Event,To,Cargo) +local TaskUnitName=TaskUnit:GetName() +self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) +if not Cargo:IsLoaded()then +Cargo:Load(TaskUnit) +end +Cargo:MessageToGroup("Loaded cargo "..Cargo:GetName(),TaskUnit:GetGroup()) +TaskUnit:AddCargo(Cargo) +Task:CargoPickedUp(TaskUnit,Cargo) +self:SelectAction(-1) +end +function Fsm:onafterPrepareUnBoarding(TaskUnit,Task,From,Event,To,Cargo) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID(),From,Event,To,Cargo}) +self.Cargo=Cargo +self.DeployZone=nil +if Cargo:IsAlive()then +for DeployZoneName,DeployZone in pairs(Task.DeployZones)do +if Cargo:IsInZone(DeployZone)then +self.DeployZone=DeployZone +break +end +end +self:__UnBoard(-0.1,Cargo,self.DeployZone) +end +end +function Fsm:onafterUnBoard(TaskUnit,Task,From,Event,To,Cargo,DeployZone) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID(),From,Event,To,Cargo,DeployZone}) +function self.Cargo:OnEnterUnLoaded(From,Event,To,DeployZone,TaskProcess) +self:F({From,Event,To,DeployZone,TaskProcess}) +TaskProcess:__UnBoarded(-0.1) +end +if self.Cargo:IsAlive()then +self.Cargo:MessageToGroup("UnBoarding ...",TaskUnit:GetGroup()) +if DeployZone then +self.Cargo:UnBoard(DeployZone:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) +else +self.Cargo:UnBoard(TaskUnit:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) +end +end +end +function Fsm:onafterUnBoarded(TaskUnit,Task) +local TaskUnitName=TaskUnit:GetName() +self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) +self.Cargo:MessageToGroup("UnBoarded cargo "..self.Cargo:GetName(),TaskUnit:GetGroup()) +self:Unload(self.Cargo) +end +function Fsm:onafterUnload(TaskUnit,Task,From,Event,To,Cargo,DeployZone) +local TaskUnitName=TaskUnit:GetName() +self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) +if not Cargo:IsUnLoaded()then +if DeployZone then +Cargo:UnLoad(DeployZone:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) +else +Cargo:UnLoad(TaskUnit:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) +end +end +TaskUnit:RemoveCargo(Cargo) +Cargo:MessageToGroup("Unloaded cargo "..Cargo:GetName(),TaskUnit:GetGroup()) +self:Planned() +self:__SelectAction(1) +end +return self +end +function TASK_CARGO:SetCargoLimit(CargoLimit) +self.CargoLimit=CargoLimit +return self +end +function TASK_CARGO:SetSmokeColor(SmokeColor) +if SmokeColor==nil then +self.SmokeColor=SMOKECOLOR.Red +elseif type(SmokeColor)=="number"then +self:F2(SmokeColor) +if SmokeColor>0 and SmokeColor<=5 then +self.SmokeColor=SMOKECOLOR.SmokeColor +end +end +end +function TASK_CARGO:GetSmokeColor() +return self.SmokeColor +end +function TASK_CARGO:GetPlannedMenuText() +return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )" +end +function TASK_CARGO:GetCargoSet() +return self.SetCargo +end +function TASK_CARGO:GetDeployZones() +return self.DeployZones +end +function TASK_CARGO:SetCargoPickup(Cargo,TaskUnit) +self:F({Cargo,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local MenuTime=self:InitTaskControlMenu(TaskUnit) +local MenuControl=self:GetTaskControlMenu(TaskUnit) +local ActRouteCargo=ProcessUnit:GetProcess("RoutingToPickup","RouteToPickupPoint") +ActRouteCargo:Reset() +ActRouteCargo:SetCoordinate(Cargo:GetCoordinate()) +ActRouteCargo:SetRange(Cargo:GetLoadRadius()) +ActRouteCargo:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Cargo "..Cargo:GetName(),MenuControl,MenuTime,"Cargo") +ActRouteCargo:Start() +return self +end +function TASK_CARGO:SetDeployZone(DeployZone,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local MenuTime=self:InitTaskControlMenu(TaskUnit) +local MenuControl=self:GetTaskControlMenu(TaskUnit) +local ActRouteDeployZone=ProcessUnit:GetProcess("RoutingToDeploy","RouteToDeployZone") +ActRouteDeployZone:Reset() +ActRouteDeployZone:SetZone(DeployZone) +ActRouteDeployZone:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Deploy Zone"..DeployZone:GetName(),MenuControl,MenuTime,"Cargo") +ActRouteDeployZone:Start() +return self +end +function TASK_CARGO:AddDeployZone(DeployZone,TaskUnit) +self.DeployZones[DeployZone:GetName()]=DeployZone +return self +end +function TASK_CARGO:RemoveDeployZone(DeployZone,TaskUnit) +self.DeployZones[DeployZone:GetName()]=nil +return self +end +function TASK_CARGO:SetDeployZones(DeployZones,TaskUnit) +for DeployZoneID,DeployZone in pairs(DeployZones or{})do +self.DeployZones[DeployZone:GetName()]=DeployZone +end +return self +end +function TASK_CARGO:GetTargetZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +return ActRouteTarget:GetZone() +end +function TASK_CARGO:SetScoreOnProgress(Text,Score,TaskUnit) +self:F({Text,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","Account",Text,Score) +return self +end +function TASK_CARGO:SetScoreOnSuccess(Text,Score,TaskUnit) +self:F({Text,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success",Text,Score) +return self +end +function TASK_CARGO:SetScoreOnFail(Text,Penalty,TaskUnit) +self:F({Text,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed",Text,Penalty) +return self +end +function TASK_CARGO:SetGoalTotal() +self.GoalTotal=self.SetCargo:Count() +end +function TASK_CARGO:GetGoalTotal() +return self.GoalTotal +end +function TASK_CARGO:UpdateTaskInfo() +if self:IsStatePlanned()or self:IsStateAssigned()then +self.TaskInfo:AddTaskName(0,"MSOD") +self.TaskInfo:AddCargoSet(self.SetCargo,10,"SOD",true) +local Coordinates={} +for CargoName,Cargo in pairs(self.SetCargo:GetSet())do +local Cargo=Cargo +if not Cargo:IsLoaded()then +Coordinates[#Coordinates+1]=Cargo:GetCoordinate() +end +end +self.TaskInfo:AddCoordinates(Coordinates,1,"M") +end +end +function TASK_CARGO:ReportOrder(ReportGroup) +return 0 +end +function TASK_CARGO:GetAutoAssignPriority(AutoAssignMethod,TaskGroup) +if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then +return math.random(1,9) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then +return 0 +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then +return 1 +end +return 0 +end +end +do +TASK_CARGO_TRANSPORT={ +ClassName="TASK_CARGO_TRANSPORT", +} +function TASK_CARGO_TRANSPORT:New(Mission,SetGroup,TaskName,SetCargo,TaskBriefing) +local self=BASE:Inherit(self,TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,"Transport",TaskBriefing)) +self:F() +Mission:AddTask(self) +local Fsm=self:GetUnitProcess() +local CargoReport=REPORT:New("Transport Cargo. The following cargo needs to be transported including initial positions:") +SetCargo:ForEachCargo( +function(Cargo) +local CargoType=Cargo:GetType() +local CargoName=Cargo:GetName() +local CargoCoordinate=Cargo:GetCoordinate() +CargoReport:Add(string.format('- "%s" (%s) at %s',CargoName,CargoType,CargoCoordinate:ToStringMGRS())) +end +) +self:SetBriefing( +TaskBriefing or +CargoReport:Text() +) +return self +end +function TASK_CARGO_TRANSPORT:ReportOrder(ReportGroup) +return 0 +end +function TASK_CARGO_TRANSPORT:IsAllCargoTransported() +local CargoSet=self:GetCargoSet() +local Set=CargoSet:GetSet() +local DeployZones=self:GetDeployZones() +local CargoDeployed=true +for CargoID,CargoData in pairs(Set)do +local Cargo=CargoData +self:F({Cargo=Cargo:GetName(),CargoDeployed=Cargo:IsDeployed()}) +if Cargo:IsDeployed()then +else +CargoDeployed=false +end +end +self:F({CargoDeployed=CargoDeployed}) +return CargoDeployed +end +function TASK_CARGO_TRANSPORT:onafterGoal(TaskUnit,From,Event,To) +local CargoSet=self.CargoSet +if self:IsAllCargoTransported()then +self:Success() +end +self:__Goal(-10) +end +end +do +TASK_CARGO_CSAR={ +ClassName="TASK_CARGO_CSAR", +} +function TASK_CARGO_CSAR:New(Mission,SetGroup,TaskName,SetCargo,TaskBriefing) +local self=BASE:Inherit(self,TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,"CSAR",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:AddTransition("*","CargoPickedUp","*") +self:AddTransition("*","CargoDeployed","*") +self:F({CargoDeployed=self.CargoDeployed~=nil and"true"or"false"}) +local Fsm=self:GetUnitProcess() +local CargoReport=REPORT:New("Rescue a downed pilot from the following position:") +SetCargo:ForEachCargo( +function(Cargo) +local CargoType=Cargo:GetType() +local CargoName=Cargo:GetName() +local CargoCoordinate=Cargo:GetCoordinate() +CargoReport:Add(string.format('- "%s" (%s) at %s',CargoName,CargoType,CargoCoordinate:ToStringMGRS())) +end +) +self:SetBriefing( +TaskBriefing or +CargoReport:Text() +) +return self +end +function TASK_CARGO_CSAR:ReportOrder(ReportGroup) +return 0 +end +function TASK_CARGO_CSAR:IsAllCargoTransported() +local CargoSet=self:GetCargoSet() +local Set=CargoSet:GetSet() +local DeployZones=self:GetDeployZones() +local CargoDeployed=true +for CargoID,CargoData in pairs(Set)do +local Cargo=CargoData +self:F({Cargo=Cargo:GetName(),CargoDeployed=Cargo:IsDeployed()}) +if Cargo:IsDeployed()then +else +CargoDeployed=false +end +end +self:F({CargoDeployed=CargoDeployed}) +return CargoDeployed +end +function TASK_CARGO_CSAR:onafterGoal(TaskUnit,From,Event,To) +local CargoSet=self.CargoSet +if self:IsAllCargoTransported()then +self:Success() +end +self:__Goal(-10) +end +end +do +TASK_CARGO_DISPATCHER={ +ClassName="TASK_CARGO_DISPATCHER", +Mission=nil, +Tasks={}, +CSAR={}, +CSARSpawned=0, +Transport={}, +TransportCount=0, +} +function TASK_CARGO_DISPATCHER:New(Mission,SetGroup) +local self=BASE:Inherit(self,TASK_MANAGER:New(SetGroup)) +self.Mission=Mission +self:AddTransition("Started","Assign","Started") +self:AddTransition("Started","CargoPickedUp","Started") +self:AddTransition("Started","CargoDeployed","Started") +self:SetCSARRadius() +self:__StartTasks(5) +self.MaxCSAR=nil +self.CountCSAR=0 +self:HandleEvent(EVENTS.Ejection) +return self +end +function TASK_CARGO_DISPATCHER:SetCSARZones(SetZonesCSAR) +self.SetZonesCSAR=SetZonesCSAR +end +function TASK_CARGO_DISPATCHER:SetMaxCSAR(MaxCSAR) +self.MaxCSAR=MaxCSAR +end +function TASK_CARGO_DISPATCHER:OnEventEjection(EventData) +self:F({EventData=EventData}) +if self.CSARTasks==true then +local CSARCoordinate=EventData.IniUnit:GetCoordinate() +local CSARCoalition=EventData.IniUnit:GetCoalition() +local CSARCountry=EventData.IniUnit:GetCountry() +local CSARHeading=EventData.IniUnit:GetHeading() +if CSARCoalition==self.Mission:GetCommandCenter():GetCoalition()then +if not self.SetZonesCSAR or(self.SetZonesCSAR and self.SetZonesCSAR:IsCoordinateInZone(CSARCoordinate))then +if not self.MaxCSAR or(self.MaxCSAR and self.CountCSAR/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") +end +BASE.ServerName="Unknown" +if lfs and loadfile then +local serverfile=lfs.writedir()..'Config/serverSettings.lua' +if UTILS.FileExists(serverfile)then +loadfile(serverfile)() +if cfg and cfg.name then +BASE.ServerName=cfg.name +end +end +BASE.ServerName=BASE.ServerName or"Unknown" +BASE:I("Server Name: "..tostring(BASE.ServerName)) +end +BASE:TraceOnOff(false) +env.info('*** MOOSE INCLUDE END *** ') diff --git a/Moose_CTLD_Pure/Moose_CTLD.lua b/Moose_CTLD_Pure/Moose_CTLD.lua new file mode 100644 index 0000000..cf04e6f --- /dev/null +++ b/Moose_CTLD_Pure/Moose_CTLD.lua @@ -0,0 +1,552 @@ +-- Moose_CTLD.lua +-- Pure-MOOSE, template-free CTLD-style logistics & troop transport +-- Drop-in script: no MIST, no mission editor templates required +-- Dependencies: Moose.lua must be loaded before this script +-- Author: Copilot (generated) + +-- Contract +-- Inputs: Config table or defaults. No ME templates needed. Zones may be named ME trigger zones or provided via coordinates in config. +-- Outputs: F10 menus for helo/transport groups; crate spawning/building; troop load/unload; optional JTAC hookup (via FAC module); +-- Error modes: missing Moose -> abort; unknown crate key -> message; spawn blocked in enemy airbase; zone missing -> message. + +if not _G.Moose or not _G.BASE then + env.info('[Moose_CTLD] Moose not detected (BASE class missing). Ensure Moose.lua is loaded before Moose_CTLD.lua') +end + +local CTLD = {} +CTLD.__index = CTLD + +-- ========================= +-- Defaults and State +-- ========================= +CTLD.Version = '0.1.0-alpha' + +CTLD.Config = { + CoalitionSide = coalition.side.BLUE, -- default coalition this instance serves (menus created for this side) + AllowedAircraft = { -- transport-capable unit type names (case-sensitive as in DCS DB) + 'UH-1H','Mi-8MTV2','Mi-24P','SA342M','SA342L','SA342Minigun','Ka-50','Ka-50_3','AH-64D_BLK_II','UH-60L','CH-47Fbl1','CH-47F','Mi-17','GazelleAI' + }, + UseGroupMenus = true, -- if true, F10 menus per player group; otherwise coalition-wide + UseCategorySubmenus = true, -- if true, organize crate requests by category submenu (menuCategory) + BuildRadius = 60, -- meters around build point to collect crates + CrateLifetime = 3600, -- seconds before crates auto-clean + MessageDuration = 15, -- seconds for on-screen messages + Debug = false, + PickupZoneSmokeColor = trigger.smokeColor.Green, -- default smoke color when spawning crates at pickup zones + + Zones = { -- Optional: supply by name (ME trigger zones) or define coordinates inline + PickupZones = { + -- examples: + -- { name = 'PICKUP_BLUE_MAIN' }, + -- { name = 'Pickup-West', smoke = trigger.smokeColor.Green }, + -- { coord = { x = 12345, y = 0, z = 67890 }, radius = 150, name = 'ScriptPickup1' }, + }, + DropZones = { + -- { name = 'DROP_BLUE_1' }, + }, + FOBZones = { + -- optional: where FOB crates can unpack to spawn FARP/FOB assets + }, + }, + + -- Crate catalog: key -> crate properties and build recipe + -- No ME templates; unit compositions are defined directly here. + CrateCatalog = { + -- Example: MANPADS team requiring 2 crates + MANPADS = { + description = '2x Crates -> MANPADS team', + weight = 120, -- affects sling/limits only informationally (no physics in script) + dcsCargoType = 'uh1h_cargo', -- static cargo type (can adjust per DCS version); user-tunable + required = 2, -- number of crates to assemble + side = coalition.side.BLUE, + category = Group.Category.GROUND, + build = function(point, headingDeg) + local u1 = { type = 'Soldier stinger', name = string.format('CTLD-MANPADS-%d', math.random(100000,999999)), + x = point.x, y = point.z, heading = math.rad(headingDeg or 0) } + local group = { + visible = false, lateActivation = false, tasks = {}, task = 'Ground Nothing', + units = { u1 }, route = { }, name = string.format('CTLD_MANPADS_%d', math.random(100000,999999)) + } + return group + end, + }, + -- Example: AAA site needing 3 crates + AAA = { + description = '3x Crates -> ZU-23 site', + weight = 400, + dcsCargoType = 'container_cargo', + required = 3, + side = coalition.side.BLUE, + category = Group.Category.GROUND, + build = function(point, headingDeg) + local hdg = math.rad(headingDeg or 0) + local function offset(dx, dz) return { x = point.x + dx, z = point.z + dz } end + local units = { + { type='ZU-23 Emplacement', name=string.format('CTLD-ZU23-%d', math.random(100000,999999)), x=point.x, y=point.z, heading=hdg }, + { type='Ural-375', name=string.format('CTLD-TRK-%d', math.random(100000,999999)), x=offset(15, 12).x, y=offset(15, 12).z, heading=hdg }, + { type='Infantry AK', name=string.format('CTLD-INF-%d', math.random(100000,999999)), x=offset(-12,-15).x, y=offset(-12,-15).z, heading=hdg }, + } + return { visible=false, lateActivation=false, tasks={}, task='Ground Nothing', units=units, route={}, name=string.format('CTLD_AAA_%d', math.random(100000,999999)) } + end, + }, + -- Example: FARP/FOB build from 4 crates (spawns helipads + support statics/vehicles) + FOB = { + description = '4x Crates -> FARP/FOB', + weight = 500, + dcsCargoType = 'container_cargo', + required = 4, + side = coalition.side.BLUE, + category = Group.Category.GROUND, + build = function(point, headingDeg) + local heading = math.rad(headingDeg or 0) + -- Spawn statics that provide FARP services + local function addStatic(typeName, dx, dz, nameSuffix) + local p = { x = point.x + dx, z = point.z + dz } + local st = { + name = string.format('CTLD-FOB-%s-%d', nameSuffix, math.random(100000,999999)), + type = typeName, + x = p.x, y = p.z, + heading = heading, + } + coalition.addStaticObject(coalition.side.BLUE, st) + end + -- Common FARP layout + addStatic('FARP', 0, 0, 'PAD') + addStatic('FARP Ammo Dump Coating', 15, 10, 'AMMO') + addStatic('FARP Fuel Depot', -15, 10, 'FUEL') + addStatic('FARP Tent', 10, -12, 'TENT1') + addStatic('FARP Tent', -10, -12, 'TENT2') + -- Ground support vehicles to enable rearm/refuel/repair + local units = { + { type='HEMTT TFFT', name=string.format('CTLD-FOB-FUEL-%d', math.random(100000,999999)), x=point.x + 20, y=point.z + 15, heading=heading }, + { type='Ural-375 PBU', name=string.format('CTLD-FOB-REPAIR-%d', math.random(100000,999999)), x=point.x - 20, y=point.z + 15, heading=heading }, + { type='Ural-375', name=string.format('CTLD-FOB-AMMO-%d', math.random(100000,999999)), x=point.x, y=point.z - 18, heading=heading }, + } + return { visible=false, lateActivation=false, tasks={}, task='Ground Nothing', units=units, route={}, name=string.format('CTLD_FOB_%d', math.random(100000,999999)) } + end, + }, + }, +} + +-- Internal state tables +CTLD._instances = CTLD._instances or {} +CTLD._crates = {} -- [crateName] = { key, zone, side, spawnTime, point } +CTLD._troopsLoaded = {} -- [groupName] = { count, typeKey } + +-- ========================= +-- Utilities +-- ========================= +local function _isIn(list, value) + for _,v in ipairs(list or {}) do if v == value then return true end end + return false +end + +local function _msgGroup(group, text, t) + if not group then return end + MESSAGE:New(text, t or CTLD.Config.MessageDuration):ToGroup(group) +end + +local function _msgCoalition(side, text, t) + MESSAGE:New(text, t or CTLD.Config.MessageDuration):ToCoalition(side) +end + +local function _findZone(z) + if z.name then + local mz = ZONE:FindByName(z.name) + if mz then return mz end + end + if z.coord then + local r = z.radius or 150 + local v = VECTOR2:New(z.coord.x, z.coord.z) + return ZONE_RADIUS:New(z.name or ('CTLD_ZONE_'..math.random(10000,99999)), v, r) + end + return nil +end + +local function _getUnitType(unit) + local ud = unit and unit:GetDesc() or nil + return ud and ud.typeName or unit and unit:GetTypeName() +end + +local function _nearestZonePoint(unit, list) + if not unit or not unit:IsAlive() then return nil end + local p3 = unit:GetPointVec3() + local best, bestd = nil, 1e12 + for _,z in ipairs(list or {}) do + local mz = _findZone(z) + if mz then + local d = p3:DistanceFromPoint(mz:GetPointVec3()) + if d < bestd then best, bestd = mz, d end + end + end + return best, bestd +end + +local function _coalitionAddGroup(side, category, groupData) + -- Enforce side/category in groupData just to be safe + groupData.category = category + return coalition.addGroup(side, category, groupData) +end + +local function _spawnStaticCargo(side, point, cargoType, name) + local static = { + name = name, + type = cargoType, + x = point.x, + y = point.z, + heading = 0, + canCargo = true, + } + return coalition.addStaticObject(side, static) +end + +local function _vec3FromUnit(unit) + local p = unit:GetPointVec3() + return { x = p.x, y = p.y, z = p.z } +end + +-- ========================= +-- Construction +-- ========================= +function CTLD:New(cfg) + local o = setmetatable({}, self) + o.Config = BASE:DeepCopy(CTLD.Config) + if cfg then o.Config = BASE:Inherit(o.Config, cfg) end + o.Side = o.Config.CoalitionSide + o.MenuRoots = {} + o.MenusByGroup = {} + o:InitZones() + o:InitMenus() + + -- Periodic cleanup for crates + o.Sched = SCHEDULER:New(nil, function() + o:CleanupCrates() + end, {}, 60, 60) + + table.insert(CTLD._instances, o) + _msgCoalition(o.Side, string.format('CTLD %s initialized for coalition', CTLD.Version)) + return o +end + +function CTLD:InitZones() + self.PickupZones = {} + self.DropZones = {} + self.FOBZones = {} + self._ZoneDefs = { PickupZones = {}, DropZones = {}, FOBZones = {} } + for _,z in ipairs(self.Config.Zones.PickupZones or {}) do + local mz = _findZone(z) + if mz then table.insert(self.PickupZones, mz); self._ZoneDefs.PickupZones[mz:GetName()] = z end + end + for _,z in ipairs(self.Config.Zones.DropZones or {}) do + local mz = _findZone(z) + if mz then table.insert(self.DropZones, mz); self._ZoneDefs.DropZones[mz:GetName()] = z end + end + for _,z in ipairs(self.Config.Zones.FOBZones or {}) do + local mz = _findZone(z) + if mz then table.insert(self.FOBZones, mz); self._ZoneDefs.FOBZones[mz:GetName()] = z end + end +end + +-- ========================= +-- Menus +-- ========================= +function CTLD:InitMenus() + if self.Config.UseGroupMenus then + self:WireBirthHandler() + else + self.MenuRoot = MENU_COALITION:New(self.Side, 'CTLD') + self:BuildCoalitionMenus(self.MenuRoot) + end +end + +function CTLD:WireBirthHandler() + local handler = EVENTHANDLER:New() + handler:HandleEvent(EVENTS.Birth) + local selfref = self + function handler:OnEventBirth(eventData) + local unit = eventData.IniUnit + if not unit or not unit:IsAlive() then return end + if unit:GetCoalition() ~= selfref.Side then return end + local typ = _getUnitType(unit) + if not _isIn(selfref.Config.AllowedAircraft, typ) then return end + local grp = unit:GetGroup() + if not grp then return end + local gname = grp:GetName() + if selfref.MenusByGroup[gname] then return end + selfref.MenusByGroup[gname] = selfref:BuildGroupMenus(grp) + _msgGroup(grp, 'CTLD menu available (F10)') + end + self.BirthHandler = handler +end + +function CTLD:BuildGroupMenus(group) + local root = MENU_GROUP:New(group, 'CTLD') + -- Request crate submenu per catalog entry + local reqRoot = MENU_GROUP:New(group, 'Request Crate', root) + if self.Config.UseCategorySubmenus then + local submenus = {} + local function getSubmenu(catLabel) + if not submenus[catLabel] then + submenus[catLabel] = MENU_GROUP:New(group, catLabel, reqRoot) + end + return submenus[catLabel] + end + for key,def in pairs(self.Config.CrateCatalog) do + local label = (def and (def.menu or def.description)) or key + local sideOk = (not def.side) or def.side == self.Side + if sideOk then + local catLabel = (def and def.menuCategory) or 'Other' + local parent = getSubmenu(catLabel) + MENU_GROUP_COMMAND:New(group, label, parent, function() + self:RequestCrateForGroup(group, key) + end) + end + end + else + for key,def in pairs(self.Config.CrateCatalog) do + local label = (def and (def.menu or def.description)) or key + local sideOk = (not def.side) or def.side == self.Side + if sideOk then + MENU_GROUP_COMMAND:New(group, label, reqRoot, function() + self:RequestCrateForGroup(group, key) + end) + end + end + end + -- Troops + MENU_GROUP_COMMAND:New(group, 'Load Troops', root, function() self:LoadTroops(group) end) + MENU_GROUP_COMMAND:New(group, 'Unload Troops', root, function() self:UnloadTroops(group) end) + + -- Build + MENU_GROUP_COMMAND:New(group, 'Build Here', root, function() + self:BuildAtGroup(group) + end) + + return root +end + +function CTLD:BuildCoalitionMenus(root) + -- Optional: implement coalition-level crate spawns at pickup zones + for key,_ in pairs(self.Config.CrateCatalog) do + MENU_COALITION_COMMAND:New(self.Side, 'Spawn '..key..' at nearest Pickup Zone', root, function() + -- Not group-context; skip here + _msgCoalition(self.Side, 'Group menus recommended for crate requests') + end) + end +end + +-- ========================= +-- Crates +-- ========================= +function CTLD:RequestCrateForGroup(group, crateKey) + local cat = self.Config.CrateCatalog[crateKey] + if not cat then _msgGroup(group, 'Unknown crate type: '..tostring(crateKey)) return end + local unit = group:GetUnit(1) + if not unit or not unit:IsAlive() then return end + local zone, dist = _nearestZonePoint(unit, self.Config.Zones.PickupZones) + local spawnPoint + if zone and dist < 10000 then + spawnPoint = zone:GetPointVec3() + -- if pickup zone has smoke configured, mark it + local zdef = self._ZoneDefs.PickupZones[zone:GetName()] + local smokeColor = (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor + if smokeColor then + trigger.action.smoke({ x = spawnPoint.x, z = spawnPoint.z }, smokeColor) + end + else + -- fallback: spawn near aircraft current position (safe offset) + local p = unit:GetPointVec3() + spawnPoint = POINT_VEC3:New(p.x + 10, p.y, p.z + 10) + end + local cname = string.format('CTLD_CRATE_%s_%d', crateKey, math.random(100000,999999)) + _spawnStaticCargo(self.Side, { x = spawnPoint.x, z = spawnPoint.z }, cat.dcsCargoType or 'uh1h_cargo', cname) + CTLD._crates[cname] = { + key = crateKey, + side = self.Side, + spawnTime = timer.getTime(), + point = { x = spawnPoint.x, z = spawnPoint.z }, + } + _msgGroup(group, string.format('Spawned crate %s at %s', crateKey, zone and zone:GetName() or 'current pos')) +end + +function CTLD:GetNearbyCrates(point, radius) + local result = {} + for name,meta in pairs(CTLD._crates) do + local dx = (meta.point.x - point.x) + local dz = (meta.point.z - point.z) + local d = math.sqrt(dx*dx + dz*dz) + if d <= radius then + table.insert(result, { name = name, meta = meta }) + end + end + return result +end + +function CTLD:CleanupCrates() + local now = timer.getTime() + local life = self.Config.CrateLifetime + for name,meta in pairs(CTLD._crates) do + if now - (meta.spawnTime or now) > life then + local obj = StaticObject.getByName(name) + if obj then obj:destroy() end + CTLD._crates[name] = nil + if self.Config.Debug then env.info('[CTLD] Cleaned up crate '..name) end + end + end +end + +-- ========================= +-- Build logic +-- ========================= +function CTLD:BuildAtGroup(group) + local unit = group:GetUnit(1) + if not unit or not unit:IsAlive() then return end + local p = unit:GetPointVec3() + local here = { x = p.x, z = p.z } + local radius = self.Config.BuildRadius + local nearby = self:GetNearbyCrates(here, radius) + if #nearby == 0 then _msgGroup(group, 'No crates within '..radius..'m') return end + + -- Count by key + local counts = {} + for _,c in ipairs(nearby) do + counts[c.meta.key] = (counts[c.meta.key] or 0) + 1 + end + + -- Helper to consume crates of a given key/qty + local function consumeCrates(key, qty) + local removed = 0 + for _,c in ipairs(nearby) do + if removed >= qty then break end + if c.meta.key == key then + local obj = StaticObject.getByName(c.name) + if obj then obj:destroy() end + CTLD._crates[c.name] = nil + removed = removed + 1 + end + end + end + + -- Try composite recipes first (requires is a map of key->qty) + for recipeKey,cat in pairs(self.Config.CrateCatalog) do + if type(cat.requires) == 'table' and cat.build then + local ok = true + for reqKey,qty in pairs(cat.requires) do + if (counts[reqKey] or 0) < qty then ok = false; break end + end + if ok then + local hdg = unit:GetHeading() + local gdata = cat.build({ x = here.x, z = here.z }, math.deg(hdg)) + local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata) + if g then + for reqKey,qty in pairs(cat.requires) do consumeCrates(reqKey, qty) end + _msgGroup(group, string.format('Built %s at your location', cat.description or recipeKey)) + return + else + _msgGroup(group, 'Build failed: DCS group spawn error') + return + end + end + end + end + + -- Then single-key recipes + for key,count in pairs(counts) do + local cat = self.Config.CrateCatalog[key] + if cat and cat.build and (not cat.requires) and count >= (cat.required or 1) then + local hdg = unit:GetHeading() + local gdata = cat.build({ x = here.x, z = here.z }, math.deg(hdg)) + local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata) + if g then + consumeCrates(key, cat.required or 1) + _msgGroup(group, string.format('Built %s at your location', cat.description or key)) + return + else + _msgGroup(group, 'Build failed: DCS group spawn error') + return + end + end + end + + _msgGroup(group, 'Insufficient crates to build any asset here') +end + +-- ========================= +-- Troops +-- ========================= +function CTLD:LoadTroops(group, opts) + local gname = group:GetName() + local unit = group:GetUnit(1) + if not unit or not unit:IsAlive() then return end + + local capacity = 6 -- simple default; can be adjusted per type later + CTLD._troopsLoaded[gname] = { + count = capacity, + typeKey = 'RIFLE', + } + _msgGroup(group, string.format('Loaded %d troops (virtual). Use Unload Troops to deploy.', capacity)) +end + +function CTLD:UnloadTroops(group) + local gname = group:GetName() + local load = CTLD._troopsLoaded[gname] + if not load or (load.count or 0) == 0 then _msgGroup(group, 'No troops onboard') return end + + local unit = group:GetUnit(1) + if not unit or not unit:IsAlive() then return end + local p = unit:GetPointVec3() + local here = { x = p.x, z = p.z } + local hdg = unit:GetHeading() + + local count = load.count + -- Spawn a simple infantry fireteam + local units = {} + for i=1, math.min(count, 8) do + table.insert(units, { + type = 'Infantry AK', name = string.format('CTLD-TROOP-%d', math.random(100000,999999)), + x = here.x + i*1.5, y = here.z + (i%2==0 and 2 or -2), heading = hdg + }) + end + local groupData = { + visible=false, lateActivation=false, tasks={}, task='Ground Nothing', + units=units, route={}, name=string.format('CTLD_TROOPS_%d', math.random(100000,999999)) + } + local spawned = _coalitionAddGroup(self.Side, Group.Category.GROUND, groupData) + if spawned then + CTLD._troopsLoaded[gname] = nil + _msgGroup(group, string.format('Deployed %d troops', #units)) + else + _msgGroup(group, 'Deploy failed: DCS group spawn error') + end +end + +-- ========================= +-- Public helpers +-- ========================= +function CTLD:RegisterCrate(key, def) + self.Config.CrateCatalog[key] = def +end + +function CTLD:MergeCatalog(tbl) + for k,v in pairs(tbl or {}) do self.Config.CrateCatalog[k] = v end +end + +function CTLD:AddPickupZone(z) + local mz = _findZone(z) + if mz then table.insert(self.PickupZones, mz); table.insert(self.Config.Zones.PickupZones, z) end +end + +function CTLD:AddDropZone(z) + local mz = _findZone(z) + if mz then table.insert(self.DropZones, mz); table.insert(self.Config.Zones.DropZones, z) end +end + +function CTLD:SetAllowedAircraft(list) + self.Config.AllowedAircraft = BASE:DeepCopy(list) +end + +-- ========================= +-- Return factory +-- ========================= +_MOOSE_CTLD = CTLD +return CTLD diff --git a/Moose_CTLD_Pure/Moose_CTLD_FAC.lua b/Moose_CTLD_Pure/Moose_CTLD_FAC.lua new file mode 100644 index 0000000..66a11f6 --- /dev/null +++ b/Moose_CTLD_Pure/Moose_CTLD_FAC.lua @@ -0,0 +1,207 @@ +-- Moose_CTLD_FAC.lua +-- FAC/RECCE features integrated with pure-MOOSE CTLD +-- Provides: recce zones, auto target marking (smoke/illum), JTAC auto-lase bootstrap, optional artillery mark tasks + +if not _G.Moose or not _G.BASE then + env.info('[Moose_CTLD_FAC] Moose not detected. Ensure Moose.lua is loaded before this script.') +end + +local FAC = {} +FAC.__index = FAC +FAC.Version = '0.1.0-alpha' + +FAC.Config = { + CoalitionSide = coalition.side.BLUE, + ScanInterval = 20, -- seconds between scans + MarkSmokeColor = trigger.smokeColor.Red, + MarkIllum = false, -- drop illumination at night if true + MarkText = true, -- place map marks with target info + DetectionRadius = 5000, -- meters within zone + MinReportSeparation = 400, -- meters between subsequent marks to reduce spam + UseGroupMenus = true, + Debug = false, + Arty = { -- optional artillery support + Enabled = true, + Groups = { -- names of friendly artillery groups to use for marking + -- 'BLUE_ARTY_1', + }, + Rounds = 3, + Spread = 120, -- meters randomization around mark point + } +} + +FAC._lastMarks = {} -- [zoneName] = { lastPoint = {x,z} } + +function FAC:New(ctld, cfg) + local o = setmetatable({}, self) + o.CTLD = ctld + o.Config = BASE:DeepCopy(FAC.Config) + if cfg then o.Config = BASE:Inherit(o.Config, cfg) end + o.Side = o.Config.CoalitionSide + o.Zones = {} + o.MenusByGroup = {} + + if o.Config.UseGroupMenus then o:WireBirthHandler() end + return o +end + +function FAC:WireBirthHandler() + local handler = EVENTHANDLER:New() + handler:HandleEvent(EVENTS.Birth) + local selfref = self + function handler:OnEventBirth(eventData) + local unit = eventData.IniUnit + if not unit or not unit:IsAlive() then return end + if unit:GetCoalition() ~= selfref.Side then return end + local grp = unit:GetGroup() + if not grp then return end + local gname = grp:GetName() + if selfref.MenusByGroup[gname] then return end + -- Simple menu: FAC actions + local root = MENU_GROUP:New(grp, 'FAC/RECCE') + MENU_GROUP_COMMAND:New(grp, 'List Recce Zones', root, function() selfref:MenuListZones(grp) end) + MENU_GROUP_COMMAND:New(grp, 'Mark Contacts (all zones)', root, function() selfref:ForceScanAll(grp) end) + selfref.MenusByGroup[gname] = root + MESSAGE:New('FAC/RECCE menu available (F10)', 10):ToGroup(grp) + end + self.BirthHandler = handler +end + +function FAC:AddRecceZone(def) + -- def: { name='ZONE_NAME' } or { coord={x,y,z}, radius=NN, name='Recce1' } + local z + if def.name then + z = ZONE:FindByName(def.name) + end + if not z and def.coord then + local r = def.radius or 5000 + z = ZONE_RADIUS:New(def.name or ('FAC_ZONE_'..math.random(10000,99999)), VECTOR2:New(def.coord.x, def.coord.z), r) + end + if not z then return nil end + local Z = { + Zone = z, + Name = z:GetName(), + Detector = self:CreateDetector(z), + LastScan = 0, + } + table.insert(self.Zones, Z) + return Z +end + +function FAC:CreateDetector(zone) + -- Detection in areas using Moose detection classes + local enemySide = (self.Side == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE + local setEnemies = SET_GROUP:New():FilterCoalitions(enemySide):FilterCategoryGround():FilterStart() + local det = DETECTION_AREAS:New(setEnemies, zone:GetRadius()) + det:BoundZone(zone) + return det +end + +function FAC:MenuListZones(group) + local names = {} + for _,Z in ipairs(self.Zones) do table.insert(names, Z.Name) end + MESSAGE:New('Recce zones: '..(table.concat(names, ', '):gsub('^%s+$','none')), 15):ToGroup(group) +end + +function FAC:ForceScanAll(group) + for _,Z in ipairs(self.Zones) do self:ScanZone(Z, group) end +end + +function FAC:Run() + -- schedule periodic scanning + if self.Sched then self.Sched:Stop() end + self.Sched = SCHEDULER:New(nil, function() + for _,Z in ipairs(self.Zones) do self:ScanZone(Z) end + end, {}, 5, self.Config.ScanInterval) +end + +local function _p3(v2) + return { x = v2.x, y = land.getHeight({x=v2.x, y=v2.y}), z = v2.y } +end + +function FAC:ScanZone(Z, notifyGroup) + local now = timer.getTime() + local det = Z.Detector + det:DetectionUpdate() + local reports = det:GetDetectedItems() + if not reports or #reports == 0 then + if notifyGroup then MESSAGE:New('No contacts detected in '..Z.Name, 10):ToGroup(notifyGroup) end + return + end + + local enemySide = (self.Side == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE + for _,rep in ipairs(reports) do + local contact = rep.object -- wrapper around GROUP or UNIT + local pos2 = rep.point -- vec2 + if pos2 then + local markPoint = { x = pos2.x, z = pos2.y } + local allow = true + local last = FAC._lastMarks[Z.Name] + if last then + local dx = (markPoint.x - last.x); local dz = (markPoint.z - last.z) + if math.sqrt(dx*dx+dz*dz) < self.Config.MinReportSeparation then allow = false end + end + if allow then + FAC._lastMarks[Z.Name] = { x = markPoint.x, z = markPoint.z } + self:MarkTarget(Z, markPoint, rep, enemySide) + if notifyGroup then MESSAGE:New(string.format('Marked contact in %s', Z.Name), 10):ToGroup(notifyGroup) end + end + end + end +end + +function FAC:MarkTarget(Z, point, rep, enemySide) + -- Smoke + trigger.action.smoke(point, self.Config.MarkSmokeColor) + -- Map mark + if self.Config.MarkText then + local txt = string.format('FAC: %s at %s', rep.type or 'Contact', coord.LLtoString(coord.LOtoLL(point), 0)) + trigger.action.markToCoalition(math.random(100000,999999), txt, point, self.Side, true) + end + -- Optional arty marking + if self.Config.Arty.Enabled then + self:ArtyMark(point) + end +end + +function FAC:ArtyMark(point) + local rounds = self.Config.Arty.Rounds or 1 + for _,gname in ipairs(self.Config.Arty.Groups or {}) do + local g = Group.getByName(gname) + if g and g:isExist() then + local ctrl = g:getController() + if ctrl then + for i=1,rounds do + local spread = self.Config.Arty.Spread or 0 + local tgt = { x = point.x + math.random(-spread, spread), y = point.z + math.random(-spread, spread) } + local task = { + id = 'FireAtPoint', + params = { + point = { x = tgt.x, y = land.getHeight({x=tgt.x, y=tgt.y}), z = tgt.y }, + expendQty = 1, + dispersion = 50, + attackQty = 1, + weaponType = 0, + } + } + ctrl:setTask(task) + end + end + end + end +end + +-- Bootstrap a JTAC on a spawned unit/group via MOOSE FAC_AUTO +function FAC:StartJTACOnGroup(groupName, code, smoke) + local grp = GROUP:FindByName(groupName) + if not grp then return nil end + local fac = FAC_AUTO:New(grp) + fac:SetLaser(true, code or 1688) + fac:SetSmoke(smoke or self.Config.MarkSmokeColor) + fac:SetDetectVehicles() + fac:Start() + return fac +end + +_MOOSE_CTLD_FAC = FAC +return FAC diff --git a/Moose_CTLD_Pure/README.md b/Moose_CTLD_Pure/README.md new file mode 100644 index 0000000..ff354b6 --- /dev/null +++ b/Moose_CTLD_Pure/README.md @@ -0,0 +1,106 @@ +# Moose_CTLD_Pure + +Pure-MOOSE CTLD-style logistics and FAC/RECCE without MIST or mission editor templates. Drop-in, config-driven. + +## What this is + +- Logistics and troop transport similar to popular CTLD scripts, implemented directly on MOOSE. +- No MIST. No mission editor templates. Unit compositions are defined in config tables. +- Optional FAC/RECCE module that auto-marks targets in zones and can drive artillery marking and JTAC auto-lase. + +## Quick start + +1) Load `Moose.lua` first, then include these files (order matters): + - `Moose_CTLD_Pure/Moose_CTLD.lua` + - `Moose_CTLD_Pure/Moose_CTLD_FAC.lua` (optional, for FAC/RECCE) + +2) Initialize CTLD with minimal config: + +```lua +local CTLD = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD.lua]]) +local ctld = CTLD:New({ + CoalitionSide = coalition.side.BLUE, + Zones = { + PickupZones = { { name = 'PICKUP_BLUE_MAIN' } }, + DropZones = { { name = 'DROP_BLUE_1' } }, + }, +}) + +-- Optionally load a larger crate catalog extracted from your CTLD.lua +local extracted = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\catalogs\CrateCatalog_CTLD_Extract.lua]]) +ctld:MergeCatalog(extracted) +``` + +- If you don't have ME trigger zones, define by coordinates: + +```lua +Zones = { + PickupZones = { + { coord = { x=123456, y=0, z=654321 }, radius=150, name='ScriptPickup1' }, + } +} +``` + +3) (Optional) FAC/RECCE: + +```lua +local FAC = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD_FAC.lua]]) +local fac = FAC:New(ctld, { + CoalitionSide = coalition.side.BLUE, + Arty = { Enabled = true, Groups = { 'BLUE_ARTY_1' }, Rounds = 3, Spread = 100 }, +}) +fac:AddRecceZone({ name = 'RECCE_ZONE_1' }) +fac:Run() +``` + +4) In mission, pilots of allowed aircraft (configured in `AllowedAircraft`) will see F10 menus: +- CTLD > Request Crate > [Type] +- CTLD > Load Troops / Unload Troops +- CTLD > Build Here +- FAC/RECCE > List Recce Zones / Mark Contacts (all zones) + +## Configuring crates and builds (no templates) + +Edit `CrateCatalog` in `Moose_CTLD.lua`. Each entry defines: +- `required`: how many crates to assemble +- `weight`: informational +- `dcsCargoType`: DCS static cargo type string (e.g., `uh1h_cargo`, `container_cargo`); tweak per map/mods +- `build(point, headingDeg)`: function returning a DCS group table for `coalition.addGroup` + +Example snippet: + +```lua +CrateCatalog = { + MANPADS = { + description = '2x Crates -> MANPADS team', + weight = 120, + dcsCargoType = 'uh1h_cargo', + required = 2, + side = coalition.side.BLUE, + category = Group.Category.GROUND, + build = function(point, headingDeg) + return { visible=false, lateActivation=false, units={ + { type='Soldier stinger', name='CTLD-MANPADS-1', x=point.x, y=point.z, heading=math.rad(headingDeg or 0) } + } } + end, + }, +} +``` + +## Notes and limitations + +- This avoids sling-load event dependency by using a player command "Build Here" that consumes nearby crates within a radius. You can still sling crates physically. +- Cargo static types vary across DCS versions. If a spawned crate isn’t sling-loadable, change `dcsCargoType` strings in `CrateCatalog` (e.g., `uh1h_cargo`, `container_cargo`, `ammo_cargo`, `container_20ft` depending on map/version). +- Troops are virtually loaded and spawned on unload. Adjust capacity logic if you want type-based capacities. +- FAC/RECCE detection leverages Moose `DETECTION_AREAS`. Tweak `ScanInterval`, `DetectionRadius`, and `MinReportSeparation` to balance spam/performance. +- Artillery marking uses `Controller.setTask('FireAtPoint')` on configured groups. Ensure those groups exist and are artillery-capable. +- JTAC Auto-Lase helper provided: `fac:StartJTACOnGroup(groupName, laserCode, smokeColor)` uses `FAC_AUTO`. + +## Extending + +- Add radio beacons, FOB build recipes, fuel/ammo crates, and CSAR hooks by registering more `CrateCatalog` entries and/or adding helper methods. +- To support per-airframe capacities and sling-only rules, extend `AllowedAircraft` and add a type->capacity map. + +## Changelog + +- 0.1.0-alpha: Initial release: CTLD crate/troops/build, FAC recce zones + arty mark + JTAC bootstrap. diff --git a/Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua b/Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua new file mode 100644 index 0000000..d8b7ba9 --- /dev/null +++ b/Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua @@ -0,0 +1,166 @@ +-- CrateCatalog_CTLD_Extract.lua +-- Auto-generated from CTLD.lua (Operation_Polar_Shield) spawnableCrates config +-- Returns a table of crate definitions suitable for CTLD:MergeCatalog() +-- Notes: +-- - Each entry has keys: description/menu, dcsCargoType, required or requires (composite), side, category, build(point, headingDeg) +-- - Single-unit entries spawn one unit by DCS type. Composite "SITE" entries spawn a multi-unit group approximating system components. + +local function singleUnit(unitType) + return function(point, headingDeg) + local name = string.format('%s-%d', unitType, math.random(100000,999999)) + local hdg = math.rad(headingDeg or 0) + return { + visible=false, lateActivation=false, tasks={}, task='Ground Nothing', route={}, + units={ { type=unitType, name=name, x=point.x, y=point.z, heading=hdg } }, + name = 'CTLD_'..name + } + end +end + +local function multiUnits(units) + -- units: array of { type, dx, dz } + return function(point, headingDeg) + local hdg = math.rad(headingDeg or 0) + local function off(dx, dz) return { x = point.x + dx, z = point.z + dz } end + local list = {} + for i,u in ipairs(units) do + local p = off(u.dx or 0, u.dz or 3*i) + table.insert(list, { + type = u.type, name = string.format('CTLD-%s-%d', u.type, math.random(100000,999999)), + x = p.x, y = p.z, heading = hdg + }) + end + return { visible=false, lateActivation=false, tasks={}, task='Ground Nothing', route={}, units=list, name=string.format('CTLD_SITE_%d', math.random(100000,999999)) } + end +end + +local BLUE = coalition.side.BLUE +local RED = coalition.side.RED + +local cat = {} + +-- Combat Vehicles (BLUE) +cat['BLUE_M1128_STRYKER_MGS'] = { menuCategory='Combat Vehicles', menu='M1128 Stryker MGS', description='M1128 Stryker MGS', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1128 Stryker MGS') } +cat['BLUE_M60A3_PATTON'] = { menuCategory='Combat Vehicles', menu='M-60A3 Patton', description='M-60A3 Patton', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-60') } +cat['BLUE_HMMWV_TOW'] = { menuCategory='Combat Vehicles', menu='Humvee - TOW', description='Humvee - TOW', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1045 HMMWV TOW') } +cat['BLUE_M1134_STRYKER_ATGM']= { menuCategory='Combat Vehicles', menu='M1134 Stryker ATGM',description='M1134 Stryker ATGM',dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1134 Stryker ATGM') } +cat['BLUE_LAV25'] = { menuCategory='Combat Vehicles', menu='LAV-25', description='LAV-25', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('LAV-25') } +cat['BLUE_M2A2_BRADLEY'] = { menuCategory='Combat Vehicles', menu='M2A2 Bradley', description='M2A2 Bradley', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-2 Bradley') } +cat['BLUE_VAB_MEPHISTO'] = { menuCategory='Combat Vehicles', menu='ATGM VAB Mephisto', description='ATGM VAB Mephisto', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('VAB_Mephisto') } +cat['BLUE_M1A2C_ABRAMS'] = { menuCategory='Combat Vehicles', menu='M1A2C Abrams', description='M1A2C Abrams', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1A2C_SEP_V3') } + +-- Combat Vehicles (RED) +cat['RED_BTR82A'] = { menuCategory='Combat Vehicles', menu='BTR-82A', description='BTR-82A', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('BTR-82A') } +cat['RED_BRDM2'] = { menuCategory='Combat Vehicles', menu='BRDM-2', description='BRDM-2', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('BRDM-2') } +cat['RED_BMP3'] = { menuCategory='Combat Vehicles', menu='BMP-3', description='BMP-3', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('BMP-3') } +cat['RED_T55'] = { menuCategory='Combat Vehicles', menu='T-55', description='T-55', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('T-55') } +cat['RED_T72B3'] = { menuCategory='Combat Vehicles', menu='T-72B3', description='T-72B3', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('T-72B3') } + +-- Support (BLUE) +cat['BLUE_MRAP_JTAC'] = { menuCategory='Support', menu='MRAP - JTAC', description='JTAC MRAP', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('MaxxPro_MRAP') } +cat['BLUE_M818_AMMO'] = { menuCategory='Support', menu='M-818 Ammo Truck', description='M-818 Ammo Truck', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M 818') } +cat['BLUE_M978_TANKER'] = { menuCategory='Support', menu='M-978 Tanker', description='M-978 Tanker', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M978 HEMTT Tanker') } +cat['BLUE_EWR_FPS117'] = { menuCategory='Support', menu='EWR Radar FPS-117', description='EWR Radar FPS-117', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('FPS-117') } + +-- Support (RED) +cat['RED_TIGR_JTAC'] = { menuCategory='Support', menu='Tigr - JTAC', description='JTAC Tigr', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Tigr_233036') } +cat['RED_URAL4320_AMMO'] = { menuCategory='Support', menu='Ural-4320-31 Ammo Truck', description='Ural-4320-31 Ammo Truck', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Ural-4320-31') } +cat['RED_ATZ10_TANKER'] = { menuCategory='Support', menu='ATZ-10 Refueler', description='ATZ-10 Refueler', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('ATZ-10') } +cat['RED_EWR_1L13'] = { menuCategory='Support', menu='EWR Radar 1L13', description='EWR Radar 1L13', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('1L13 EWR') } + +-- Artillery (BLUE) +cat['BLUE_MLRS'] = { menuCategory='Artillery', menu='MLRS', description='MLRS', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('MLRS') } +cat['BLUE_SMERCH_CM'] = { menuCategory='Artillery', menu='Smerch_CM', description='Smerch (CM)', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Smerch') } +cat['BLUE_L118_105MM'] = { menuCategory='Artillery', menu='L118 Light Artillery 105mm', description='L118 105mm', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('L118_Unit') } +cat['BLUE_SMERCH_HE'] = { menuCategory='Artillery', menu='Smerch_HE', description='Smerch (HE)', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Smerch_HE') } +cat['BLUE_M109'] = { menuCategory='Artillery', menu='M-109', description='M-109', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-109') } + +-- Artillery (RED) +cat['RED_GVOZDika'] = { menuCategory='Artillery', menu='SAU Gvozdika', description='SAU Gvozdika', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('SAU Gvozdika') } +cat['RED_2S19_MSTA'] = { menuCategory='Artillery', menu='SPH 2S19 Msta', description='SPH 2S19 Msta', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('SAU Msta') } +cat['RED_URAGAN_BM27'] = { menuCategory='Artillery', menu='Uragan_BM-27', description='Uragan BM-27', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Uragan_BM-27') } +cat['RED_BM21_GRAD'] = { menuCategory='Artillery', menu='BM-21 Grad Ural', description='BM-21 Grad Ural', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Grad-URAL') } +cat['RED_PLZ05'] = { menuCategory='Artillery', menu='PLZ-05 Mobile Artillery', description='PLZ-05', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('PLZ05') } + +-- AAA (BLUE) +cat['BLUE_GEPARD'] = { menuCategory='AAA', menu='Gepard AAA', description='Gepard AAA', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Gepard') } +cat['BLUE_CRAM'] = { menuCategory='AAA', menu='LPWS C-RAM', description='LPWS C-RAM', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('HEMTT_C-RAM_Phalanx') } +cat['BLUE_VULCAN_M163'] = { menuCategory='AAA', menu='SPAAA Vulcan M163', description='Vulcan M163', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Vulcan') } +cat['BLUE_BOFORS40'] = { menuCategory='AAA', menu='Bofors 40mm', description='Bofors 40mm', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('bofors40') } + +-- AAA (RED) +cat['RED_URAL_ZU23'] = { menuCategory='AAA', menu='Ural-375 ZU-23', description='Ural-375 ZU-23', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Ural-375 ZU-23') } +cat['RED_SHILKA'] = { menuCategory='AAA', menu='ZSU-23-4 Shilka', description='ZSU-23-4 Shilka', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('ZSU-23-4 Shilka') } +cat['RED_ZSU57_2'] = { menuCategory='AAA', menu='ZSU_57_2', description='ZSU_57_2', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('ZSU_57_2') } + +-- SAM short range (BLUE) +cat['BLUE_M1097_AVENGER'] = { menuCategory='SAM short range', menu='M1097 Avenger', description='M1097 Avenger', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1097 Avenger') } +cat['BLUE_M48_CHAPARRAL'] = { menuCategory='SAM short range', menu='M48 Chaparral', description='M48 Chaparral', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M48 Chaparral') } +cat['BLUE_ROLAND_ADS'] = { menuCategory='SAM short range', menu='Roland ADS', description='Roland ADS', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Roland ADS') } +cat['BLUE_M6_LINEBACKER'] = { menuCategory='SAM short range', menu='M6 Linebacker', description='M6 Linebacker', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M6 Linebacker') } +-- Rapier components and site +cat['BLUE_RAPIER_LN'] = { menuCategory='SAM short range', menu='Rapier Launcher', description='Rapier Launcher', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('rapier_fsa_launcher') } +cat['BLUE_RAPIER_SR'] = { menuCategory='SAM short range', menu='Rapier SR', description='Rapier SR', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('rapier_fsa_blindfire_radar') } +cat['BLUE_RAPIER_TR'] = { menuCategory='SAM short range', menu='Rapier Tracker', description='Rapier Tracker', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('rapier_fsa_optical_tracker_unit') } +cat['BLUE_RAPIER_SITE'] = { menuCategory='SAM short range', menu='Rapier - All crates', description='Rapier Site', dcsCargoType='container_cargo', requires={ BLUE_RAPIER_LN=1, BLUE_RAPIER_SR=1, BLUE_RAPIER_TR=1 }, side=BLUE, category=Group.Category.GROUND, + build=multiUnits({ {type='rapier_fsa_launcher'}, {type='rapier_fsa_blindfire_radar', dx=12, dz=6}, {type='rapier_fsa_optical_tracker_unit', dx=-12, dz=6} }) } + +-- SAM short range (RED) +cat['RED_OSA_9K33'] = { menuCategory='SAM short range', menu='9K33 Osa', description='9K33 Osa', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Osa 9A33 ln') } +cat['RED_STRELA1_9P31'] = { menuCategory='SAM short range', menu='9P31 Strela-1', description='9P31 Strela-1', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Strela-1 9P31') } +cat['RED_TUNGUSKA_2S6'] = { menuCategory='SAM short range', menu='2K22 Tunguska', description='2K22 Tunguska', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('2S6 Tunguska') } +cat['RED_STRELA10M3'] = { menuCategory='SAM short range', menu='SA-13 Strela-10M3', description='SA-13 Strela-10M3', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Strela-10M3') } +-- HQ-7 components and site +cat['RED_HQ7_LN'] = { menuCategory='SAM short range', menu='HQ-7_Launcher', description='HQ-7 Launcher', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('HQ-7_LN_SP') } +cat['RED_HQ7_STR'] = { menuCategory='SAM short range', menu='HQ-7_STR_SP', description='HQ-7 STR', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('HQ-7_STR_SP') } +cat['RED_HQ7_SITE'] = { menuCategory='SAM short range', menu='HQ-7 - All crates', description='HQ-7 Site', dcsCargoType='container_cargo', requires={ RED_HQ7_LN=1, RED_HQ7_STR=1 }, side=RED, category=Group.Category.GROUND, + build=multiUnits({ {type='HQ-7_LN_SP'}, {type='HQ-7_STR_SP', dx=10, dz=8} }) } + +-- SAM mid range (BLUE) HAWK + NASAMS +cat['BLUE_HAWK_LN'] = { menuCategory='SAM mid range', menu='HAWK Launcher', description='HAWK Launcher', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk ln') } +cat['BLUE_HAWK_SR'] = { menuCategory='SAM mid range', menu='HAWK Search Radar', description='HAWK SR', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk sr') } +cat['BLUE_HAWK_TR'] = { menuCategory='SAM mid range', menu='HAWK Track Radar', description='HAWK TR', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk tr') } +cat['BLUE_HAWK_PCP'] = { menuCategory='SAM mid range', menu='HAWK PCP', description='HAWK PCP', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk pcp') } +cat['BLUE_HAWK_CWAR'] = { menuCategory='SAM mid range', menu='HAWK CWAR', description='HAWK CWAR', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk cwar') } +cat['BLUE_HAWK_SITE'] = { menuCategory='SAM mid range', menu='HAWK - All crates', description='HAWK Site', dcsCargoType='container_cargo', requires={ BLUE_HAWK_LN=1, BLUE_HAWK_SR=1, BLUE_HAWK_TR=1, BLUE_HAWK_PCP=1, BLUE_HAWK_CWAR=1 }, side=BLUE, category=Group.Category.GROUND, + build=multiUnits({ {type='Hawk ln'}, {type='Hawk sr', dx=12, dz=8}, {type='Hawk tr', dx=-12, dz=8}, {type='Hawk pcp', dx=18, dz=12}, {type='Hawk cwar', dx=-18, dz=12} }) } + +cat['BLUE_NASAMS_LN'] = { menuCategory='SAM mid range', menu='NASAMS Launcher 120C', description='NASAMS LN 120C', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('NASAMS_LN_C') } +cat['BLUE_NASAMS_RADAR'] = { menuCategory='SAM mid range', menu='NASAMS Search/Track Radar', description='NASAMS Radar', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('NASAMS_Radar_MPQ64F1') } +cat['BLUE_NASAMS_CP'] = { menuCategory='SAM mid range', menu='NASAMS Command Post', description='NASAMS CP', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('NASAMS_Command_Post') } +cat['BLUE_NASAMS_SITE'] = { menuCategory='SAM mid range', menu='NASAMS - All crates', description='NASAMS Site', dcsCargoType='container_cargo', requires={ BLUE_NASAMS_LN=1, BLUE_NASAMS_RADAR=1, BLUE_NASAMS_CP=1 }, side=BLUE, category=Group.Category.GROUND, + build=multiUnits({ {type='NASAMS_LN_C'}, {type='NASAMS_Radar_MPQ64F1', dx=12, dz=8}, {type='NASAMS_Command_Post', dx=-12, dz=8} }) } + +-- SAM mid range (RED) KUB +cat['RED_KUB_LN'] = { menuCategory='SAM mid range', menu='KUB Launcher', description='KUB Launcher', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Kub 2P25 ln') } +cat['RED_KUB_RADAR'] = { menuCategory='SAM mid range', menu='KUB Radar', description='KUB Radar', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Kub 1S91 str') } +cat['RED_KUB_SITE'] = { menuCategory='SAM mid range', menu='KUB - All crates', description='KUB Site', dcsCargoType='container_cargo', requires={ RED_KUB_LN=1, RED_KUB_RADAR=1 }, side=RED, category=Group.Category.GROUND, + build=multiUnits({ {type='Kub 2P25 ln'}, {type='Kub 1S91 str', dx=12, dz=8} }) } + +-- SAM long range (BLUE) Patriot +cat['BLUE_PATRIOT_LN'] = { menuCategory='SAM long range', menu='Patriot Launcher', description='Patriot Launcher', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Patriot ln') } +cat['BLUE_PATRIOT_RADAR'] = { menuCategory='SAM long range', menu='Patriot Radar', description='Patriot Radar', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Patriot str') } +cat['BLUE_PATRIOT_ECS'] = { menuCategory='SAM long range', menu='Patriot ECS', description='Patriot ECS', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Patriot ECS') } +cat['BLUE_PATRIOT_SITE'] = { menuCategory='SAM long range', menu='Patriot - All crates', description='Patriot Site', dcsCargoType='container_cargo', requires={ BLUE_PATRIOT_LN=1, BLUE_PATRIOT_RADAR=1, BLUE_PATRIOT_ECS=1 }, side=BLUE, category=Group.Category.GROUND, + build=multiUnits({ {type='Patriot ln'}, {type='Patriot str', dx=14, dz=10}, {type='Patriot ECS', dx=-14, dz=10} }) } + +-- SAM long range (RED) BUK +cat['RED_BUK_LN'] = { menuCategory='SAM long range', menu='BUK Launcher', description='BUK Launcher', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('SA-11 Buk LN 9A310M1') } +cat['RED_BUK_SR'] = { menuCategory='SAM long range', menu='BUK Search Radar', description='BUK Search Radar', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('SA-11 Buk SR 9S18M1') } +cat['RED_BUK_CC'] = { menuCategory='SAM long range', menu='BUK CC Radar', description='BUK CC Radar', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('SA-11 Buk CC 9S470M1') } +cat['RED_BUK_SITE'] = { menuCategory='SAM long range', menu='BUK - All crates', description='BUK Site', dcsCargoType='container_cargo', requires={ RED_BUK_LN=1, RED_BUK_SR=1, RED_BUK_CC=1 }, side=RED, category=Group.Category.GROUND, + build=multiUnits({ {type='SA-11 Buk LN 9A310M1'}, {type='SA-11 Buk SR 9S18M1', dx=12, dz=8}, {type='SA-11 Buk CC 9S470M1', dx=-12, dz=8} }) } + +-- Drones (JTAC) +cat['BLUE_MQ9'] = { menuCategory='Drones', menu='MQ-9 Reaper - JTAC', description='MQ-9 JTAC', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.AIRPLANE, build=singleUnit('MQ-9 Reaper') } +cat['RED_WINGLOONG'] = { menuCategory='Drones', menu='WingLoong-I - JTAC', description='WingLoong-I JTAC', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.AIRPLANE, build=singleUnit('WingLoong-I') } + +-- FOB crates (Support) — three small crates build a FOB site +cat['FOB_SMALL'] = { menuCategory='Support', menu='FOB Crate - Small', description='FOB small crate', dcsCargoType='container_cargo', required=1, side=nil, category=Group.Category.GROUND, build=function(point, headingDeg) + -- spawns a harmless placeholder truck for visibility; consumed by FOB_SITE build + return singleUnit('Ural-375')(point, headingDeg) +end } +cat['FOB_SITE'] = { menuCategory='Support', menu='FOB Crates - All', description='FOB Site', dcsCargoType='container_cargo', requires={ FOB_SMALL=3 }, side=nil, category=Group.Category.GROUND, + build=multiUnits({ {type='HEMTT TFFT'}, {type='Ural-375 PBU', dx=10, dz=8}, {type='Ural-375', dx=-10, dz=8} }) } + +return cat diff --git a/Moose_CTLD_Pure/init_example.lua b/Moose_CTLD_Pure/init_example.lua new file mode 100644 index 0000000..24c947c --- /dev/null +++ b/Moose_CTLD_Pure/init_example.lua @@ -0,0 +1,44 @@ +-- init_example.lua (optional dev harness snippet) +-- Load Moose before this. Then do: +-- dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\init_example.lua]]) + +local CTLD = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD.lua]]) +local ctld = CTLD:New({ + CoalitionSide = coalition.side.BLUE, + Zones = { + PickupZones = { { name = 'PICKUP_BLUE_MAIN' } }, + DropZones = { { name = 'DROP_BLUE_1' } }, + }, +}) + +-- Load the extracted CTLD-based crate catalog +local extracted = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\catalogs\CrateCatalog_CTLD_Extract.lua]]) +ctld:MergeCatalog(extracted) + +-- Register a SAM site built from 4 crates +ctld:RegisterCrate('IR-SAM', { + description = '4x crates -> IR SAM site', + weight = 300, + dcsCargoType = 'container_cargo', + required = 4, + side = coalition.side.BLUE, + category = Group.Category.GROUND, + build = function(point, headingDeg) + local hdg = math.rad(headingDeg or 0) + local function off(dx, dz) return { x=point.x+dx, z=point.z+dz } end + local units = { + { type='Strela-10M3', name='CTLD-SAM-1', x=point.x, y=point.z, heading=hdg }, + { type='Ural-375', name='CTLD-SAM-SUP', x=off(10,12).x, y=off(10,12).z, heading=hdg }, + } + return { visible=false, lateActivation=false, units=units, name='CTLD_SAM_'..math.random(100000,999999) } + end, +}) + +-- FAC/RECCE setup +local FAC = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD_FAC.lua]]) +local fac = FAC:New(ctld, { + CoalitionSide = coalition.side.BLUE, + Arty = { Enabled=true, Groups={'BLUE_ARTY_1'}, Rounds=2, Spread=80 }, +}) +fac:AddRecceZone({ name = 'RECCE_ZONE_1' }) +fac:Run() diff --git a/Patch-MooseMissions/Download-MooseInclude.ps1 b/Patch-MooseMissions/Download-MooseInclude.ps1 new file mode 100644 index 0000000..145bcc4 --- /dev/null +++ b/Patch-MooseMissions/Download-MooseInclude.ps1 @@ -0,0 +1,83 @@ +# Download latest Moose_.lua from GitHub + +[CmdletBinding()] +param( + # Destination path for Moose_.lua. Defaults to repo root alongside Patch-MooseMissions folder + [Parameter(Mandatory=$false)] + [string]$MooseLuaPath = (Join-Path (Split-Path $PSScriptRoot -Parent) 'Moose_.lua'), + + # Use -Force to actually download; otherwise runs in WhatIf/preview mode + [Parameter(Mandatory=$false)] + [switch]$Force +) + +# Determine WhatIf mode (preview by default) +$runInWhatIfMode = -not $Force + +# Ensure destination directory exists (avoid null/invalid path issues) +if ([string]::IsNullOrWhiteSpace($MooseLuaPath)) { + throw "Destination path for Moose_.lua is empty. Provide -MooseLuaPath." +} + +$destDir = Split-Path -Path $MooseLuaPath -Parent +if (-not [string]::IsNullOrWhiteSpace($destDir) -and -not (Test-Path $destDir)) { + New-Item -Path $destDir -ItemType Directory -Force -WhatIf:$false | Out-Null +} + +# Enable TLS 1.2 for GitHub downloads on older environments +try { + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 +} catch {} + +if (-not $runInWhatIfMode) { + Write-Host "Downloading latest Moose_.lua from GitHub..." -ForegroundColor Yellow + + $mooseGitHubUrl = "https://raw.githubusercontent.com/FlightControl-Master/MOOSE_INCLUDE/develop/Moose_Include_Static/Moose_.lua" + $backupPath = "$MooseLuaPath.backup" + + try { + # Create a single backup of existing Moose_.lua if it exists and backup doesn't exist yet + if ((Test-Path $MooseLuaPath) -and -not (Test-Path $backupPath)) { + Write-Host " Creating backup: $backupPath" -ForegroundColor Gray + Copy-Item $MooseLuaPath $backupPath -Force -WhatIf:$false + } + + # Download the file + $ProgressPreference = 'SilentlyContinue' # Speeds up Invoke-WebRequest + Invoke-WebRequest -Uri $mooseGitHubUrl -OutFile $MooseLuaPath -ErrorAction Stop + $ProgressPreference = 'Continue' + + # Verify the download + if (Test-Path $MooseLuaPath) { + $fileSize = (Get-Item $MooseLuaPath).Length + Write-Host " SUCCESS: Downloaded Moose_.lua ($([math]::Round($fileSize/1MB, 2)) MB)" -ForegroundColor Green + } else { + throw "Downloaded file not found after download" + } + } + catch { + Write-Error "Failed to download Moose_.lua from GitHub: $_" + Write-Host " URL: $mooseGitHubUrl" -ForegroundColor Red + + # Check if we have a backup or existing file to use + if (Test-Path $MooseLuaPath) { + Write-Warning "Using existing Moose_.lua file instead" + } else { + Write-Error "No Moose_.lua file available. Cannot proceed." + exit 1 + } + } + + Write-Host "" +} else { + Write-Host "WHATIF: Would download latest Moose_.lua from GitHub" -ForegroundColor Magenta + Write-Host " URL: https://raw.githubusercontent.com/FlightControl-Master/MOOSE_INCLUDE/develop/Moose_Include_Static/Moose_.lua" -ForegroundColor Gray + Write-Host " Destination: $MooseLuaPath" -ForegroundColor Gray + + # Still need to verify file exists for WhatIf to show what would be patched + if (-not (Test-Path $MooseLuaPath)) { + Write-Warning "Moose_.lua not found at $MooseLuaPath - run with -Force to download it" + Write-Warning "Continuing with WhatIf to show which missions would be processed..." + } + Write-Host "" +} \ No newline at end of file diff --git a/Patch-MooseMissions/README.md b/Patch-MooseMissions/README.md index de08f42..d6032b9 100644 --- a/Patch-MooseMissions/README.md +++ b/Patch-MooseMissions/README.md @@ -25,6 +25,27 @@ PowerShell script to automatically patch DCS mission files (.miz) with updated L ## Usage +### Get the latest Moose_.lua + +Use the helper script to fetch the latest Moose_.lua into the repo root (C:\DCS_MissionDev\Moose_.lua by default): + +```powershell +# Preview (no changes) +./Download-MooseInclude.ps1 + +# Actually download +./Download-MooseInclude.ps1 -Force + +# Optional: choose a different destination +./Download-MooseInclude.ps1 -Force -MooseLuaPath 'D:\Scripts\Moose_.lua' +``` + +If your system blocks script execution, temporarily allow scripts for the current session only: + +```powershell +Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass +``` + ### Basic Usage Update a mission with automatic version increment: