From 5bb2b05f71e89a72f54426a3b802980e51d196f7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 10 Jul 2021 17:10:46 +0200 Subject: [PATCH 01/68] Added CTLD and CSAR --- Moose Setup/Moose.files | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 0bdf83161..c903cbb90 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -74,6 +74,8 @@ Ops/Airboss.lua Ops/RecoveryTanker.lua Ops/RescueHelo.lua Ops/ATIS.lua +Ops/CTLD.lua +Ops/CSAR.lua AI/AI_Balancer.lua AI/AI_Air.lua From 9356d67d744f7db0bf512163d4afce9509192a46 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 10 Jul 2021 17:10:53 +0200 Subject: [PATCH 02/68] Added CTLD and CSAR --- Moose Development/Moose/Ops/CSAR.lua | 2117 +++++++++++++++++++++ Moose Development/Moose/Ops/CTLD.lua | 2591 ++++++++++++++++++++++++++ 2 files changed, 4708 insertions(+) create mode 100644 Moose Development/Moose/Ops/CSAR.lua create mode 100644 Moose Development/Moose/Ops/CTLD.lua diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua new file mode 100644 index 000000000..0a3d14c64 --- /dev/null +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -0,0 +1,2117 @@ +--- **Ops** -- Combat Search and Rescue. +-- +-- === +-- +-- **CSAR** - MOOSE based Helicopter CSAR Operations. +-- +-- === +-- +-- ## Missions: +-- +-- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CSAR) +-- +-- === +-- +-- **Main Features:** +-- +-- * MOOSE-based Helicopter CSAR Operations for Players. +-- +-- === +-- +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing) +-- @module Ops.CSAR +-- @image OPS_CSAR.jpg + +-- Date: July 2021 + +------------------------------------------------------------------------- +--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM +-- @type CSAR +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @extends Core.Fsm#FSM + +--- *Combat search and rescue (CSAR) are search and rescue operations that are carried out during war that are within or near combat zones.* (Wikipedia) +-- +-- === +-- +-- ![Banner Image](OPS_CSAR.jpg) +-- +-- # CSAR Concept +-- +-- * MOOSE-based Helicopter CSAR Operations for Players. +-- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. +-- * No need for extra MIST loading. +-- * Additional events to tailor your mission. +-- +-- ## 0. Prerequisites +-- +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". +-- +-- ## 1. Basic Setup +-- +-- A basic setup example is the following: +-- +-- -- Instantiate and start a CSAR for the blue side, with template "Downed Pilot" and alias "Luftrettung" +-- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") +-- -- options +-- my_csar.immortalcrew = true -- downed pilot spawn is immortal +-- my_csar.invisiblecrew = false -- downed pilot spawn is visible +-- -- start the FSM +-- my_csar:__Start(5) +-- +-- ## 2. Options +-- +-- The following options are available (with their defaults). Only set the ones you want changed: +-- +-- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. +-- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! +-- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. +-- self.autosmokedistance = 1000 -- distance for autosmoke +-- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. +-- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. +-- self.enableForAI = false -- set to false to disable AI pilots from being rescued. +-- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. +-- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. +-- self.immortalcrew = true -- Set to true to make wounded crew immortal. +-- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. +-- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. +-- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. +-- self.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. +-- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. +-- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. +-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- self.verbose = 0 -- set to > 1 for stats output for debugging. +-- -- (added 0.1.4) limit amount of downed pilots spawned by **ejection** events +-- self.limitmaxdownedpilots = true +-- self.maxdownedpilots = 10 +-- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors +-- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters +-- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters +-- self.pilotmustopendoors = false -- switch to true to enable check of open doors +-- +-- ## 2.1 Experimental Features +-- +-- WARNING - Here\'ll be dragons! +-- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua +-- Needs SRS => 1.9.6 to work (works on the **server** side of SRS) +-- self.useSRS = false -- Set true to use FF\'s SRS integration +-- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) +-- self.SRSchannel = 300 -- radio channel +-- self.SRSModulation = radio.modulation.AM -- modulation +-- +-- ## 3. Results +-- +-- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: +-- +-- self.rescues -- number of successful landings *with* saved pilots +-- self.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) +-- +-- ## 4. Events +-- +-- The class comes with a number of FSM-based events that missions designers can use to shape their mission. +-- These are: +-- +-- ### 4.1. PilotDown. +-- +-- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: +-- +-- function my_csar:OnAfterPilotDown(from, event, to, spawnedgroup, frequency, groupname, coordinates_text) +-- ... your code here ... +-- end +-- +-- ### 4.2. Approach. +-- +-- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: +-- +-- function my_csar:OnAfterApproach(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 4.3. Boarded. +-- +-- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: +-- +-- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 4.4. Returning. +-- +-- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: +-- +-- function my_csar:OnAfterReturning(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 4.5. Rescued. +-- +-- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: +-- +-- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname, pilotssaved) +-- ... your code here ... +-- end +-- +-- ## 5. Spawn downed pilots at location to be picked up. +-- +-- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: +-- +-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +-- +-- +-- @field #CSAR +CSAR = { + ClassName = "CSAR", + verbose = 0, + lid = "", + coalition = 1, + coalitiontxt = "blue", + FreeVHFFrequencies = {}, + UsedVHFFrequencies = {}, + takenOff = {}, + csarUnits = {}, -- table of unit names + downedPilots = {}, + woundedGroups = {}, + landedStatus = {}, + addedTo = {}, + woundedGroups = {}, -- contains the new group of units + inTransitGroups = {}, -- contain a table for each SAR with all units he has with the original names + smokeMarkers = {}, -- tracks smoke markers for groups + heliVisibleMessage = {}, -- tracks if the first message has been sent of the heli being visible + heliCloseMessage = {}, -- tracks heli close message ie heli < 500m distance + max_units = 6, --number of pilots that can be carried + hoverStatus = {}, -- tracks status of a helis hover above a downed pilot + pilotDisabled = {}, -- tracks what aircraft a pilot is disabled for + pilotLives = {}, -- tracks how many lives a pilot has + useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + csarPrefix = {}, + template = nil, + mash = {}, + smokecolor = 4, + rescues = 0, + rescuedpilots = 0, + limitmaxdownedpilots = true, + maxdownedpilots = 10, +} + +--- Downed pilots info. +-- @type CSAR.DownedPilot +-- @field #number index Pilot index. +-- @field #string name Name of the spawned group. +-- @field #number side Coalition. +-- @field #string originalUnit Name of the original unit. +-- @field #string desc Description. +-- @field #string typename Typename of Unit. +-- @field #number frequency Frequency of the NDB. +-- @field #string player Player name if applicable. +-- @field Wrapper.Group#GROUP group Spawned group object. +-- @field #number timestamp Timestamp for approach process + +--- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. +-- @field #CSAR.SkipFrequencies +CSAR.SkipFrequencies = { + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580,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 + } + +--- All slot / Limit settings +-- @type CSAR.AircraftType +-- @field #string typename Unit type name. +CSAR.AircraftType = {} -- Type and limit +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-24P"] = 8 +CSAR.AircraftType["Mi-24V"] = 8 + +--- CSAR class version. +-- @field #string version +CSAR.version="0.1.8r1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- DONE: SRS Integration (to be tested) +-- TODO: Maybe - add option to smoke/flare closest MASH + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new CSAR object and start the FSM. +-- @param #CSAR self +-- @param #number Coalition Coalition side. Can also be passed as a string "red", "blue" or "neutral". +-- @param #string Template Name of the late activated infantry unit standing in for the downed pilot. +-- @param #string Alias An *optional* alias how this object is called in the logs etc. +-- @return #CSAR self +function CSAR:New(Coalition, Template, Alias) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CSAR + + --set Coalition + 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 + + -- Set alias. + 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 + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- CSAR status update. + self:AddTransition("*", "PilotDown", "*") -- Downed Pilot added + self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. + self:AddTransition("*", "Boarded", "*") -- Pilot boarded. + self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. + self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + -- tables, mainly for tracking actions + self.addedTo = {} + self.allheligroupset = {} -- GROUP_SET of all helis + self.csarUnits = {} -- table of CSAR unit names + self.FreeVHFFrequencies = {} + self.heliVisibleMessage = {} -- tracks if the first message has been sent of the heli being visible + self.heliCloseMessage = {} -- tracks heli close message ie heli < 500m distance + self.hoverStatus = {} -- tracks status of a helis hover above a downed pilot + self.inTransitGroups = {} -- contain a table for each SAR with all units he has with the original names + self.landedStatus = {} + self.lastCrash = {} + self.takenOff = {} + self.smokeMarkers = {} -- tracks smoke markers for groups + self.UsedVHFFrequencies = {} + self.woundedGroups = {} -- contains the new group of units + self.downedPilots = {} -- Replacement woundedGroups + self.downedpilotcounter = 1 + + -- settings, counters etc + self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH + self.rescuedpilots = 0 -- counter for saved pilots + self.csarOncrash = false -- If set to true, will generate a csar when a plane crashes as well. + self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined arms. + self.enableForAI = false -- set to false to disable AI units from being rescued. + self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue + self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. + self.immortalcrew = true -- Set to true to make wounded crew immortal + self.invisiblecrew = false -- Set to true to make wounded crew insvisible + self.messageTime = 15 -- Time to show longer messages for in seconds + self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS + self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. + self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter + self.loadtimemax = 135 -- seconds + self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! + self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase + self.max_units = 6 --max number of pilots that can be carried + self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! + self.template = Template or "generic" -- template for downed pilot + self.mashprefix = {"MASH"} -- prefixes used to find MASHes + self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? + self.autosmoke = false -- automatically smoke location when heli is near + self.autosmokedistance = 1000 -- distance for autosmoke + -- added 0.1.4 + self.limitmaxdownedpilots = true + self.maxdownedpilots = 25 + -- generate Frequencies + self:_GenerateVHFrequencies() + -- added 0.1.8 + self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters + self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters + self.pilotmustopendoors = false -- switch to true to enable check on open doors + + -- WARNING - here\'ll be dragons + -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua + -- needs SRS => 1.9.6 to work (works on the *server* side) + self.useSRS = false -- Use FF\'s SRS integration + self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) + self.SRSchannel = 300 -- radio channel + self.SRSModulation = radio.modulation.AM -- modulation + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. + -- @function [parent=#CSAR] Start + -- @param #CSAR self + + --- Triggers the FSM event "Start" after a delay. Starts the CSAR. Initializes parameters and starts event handlers. + -- @function [parent=#CSAR] __Start + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CSAR and all its event handlers. + -- @param #CSAR self + + --- Triggers the FSM event "Stop" after a delay. Stops the CSAR and all its event handlers. + -- @function [parent=#CSAR] __Stop + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CSAR] Status + -- @param #CSAR self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CSAR] __Status + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- On After "PilotDown" event. Downed Pilot detected. + -- @function [parent=#CSAR] OnAfterPilotDown + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Group#GROUP Group Group object of the downed pilot. + -- @param #number Frequency Beacon frequency in kHz. + -- @param #string Leadername Name of the #UNIT of the downed pilot. + -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. + + --- On After "Aproach" event. Heli close to downed Pilot. + -- @function [parent=#CSAR] OnAfterApproach + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. + + --- On After "Boarded" event. Downed pilot boarded heli. + -- @function [parent=#CSAR] OnAfterBoarded + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. + + --- On After "Returning" event. Heli can return home with downed pilot(s). + -- @function [parent=#CSAR] OnAfterReturning + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. + + --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. + -- @function [parent=#CSAR] OnAfterRescued + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. + -- @param #string HeliName Name of the helicopter group. + -- @param #number PilotsSaved Number of the saved pilots on board when landing. + + return self +end + +------------------------ +--- Helper Functions --- +------------------------ + +--- (Internal) Function to insert downed pilot tracker object. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP Group The #GROUP object +-- @param #string Groupname Name of the spawned group. +-- @param #number Side Coalition. +-- @param #string OriginalUnit Name of original Unit. +-- @param #string Description Descriptive text. +-- @param #string Typename Typename of unit. +-- @param #number Frequency Frequency of the NDB in Hz +-- @param #string Playername Name of Player (if applicable) +-- @return #CSAR self. +function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername) + self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) + + -- create new entry + local DownedPilot = {} -- #CSAR.DownedPilot + DownedPilot.desc = Description or "" + DownedPilot.frequency = Frequency or 0 + DownedPilot.index = self.downedpilotcounter + DownedPilot.name = Groupname or "" + DownedPilot.originalUnit = OriginalUnit or "" + DownedPilot.player = Playername or "" + DownedPilot.side = Side or 0 + DownedPilot.typename = Typename or "" + DownedPilot.group = Group + DownedPilot.timestamp = 0 + + -- Add Pilot + local PilotTable = self.downedPilots + local counter = self.downedpilotcounter + PilotTable[counter] = {} + PilotTable[counter] = DownedPilot + self:T({Table=PilotTable}) + self.downedPilots = PilotTable + -- Increase counter + self.downedpilotcounter = self.downedpilotcounter+1 + return self +end + +--- (Internal) Count pilots on board. +-- @param #CSAR self +-- @param #string _heliName +-- @return #number count +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 + +--- (Internal) Function to check for dupe eject events. +-- @param #CSAR self +-- @param #string _unitname Name of unit. +-- @return #boolean Outcome +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 + +--- (Internal) Spawn a downed pilot +-- @param #CSAR self +-- @param #number country Country for template. +-- @param Core.Point#COORDINATE point Coordinate to spawn at. +-- @param #number frequency Frequency of the pilot's beacon +-- @return Wrapper.Group#GROUP group The #GROUP object. +-- @return #string alias The alias name. +function CSAR:_SpawnPilotInField(country,point,frequency) + self:T({country,point,frequency}) + local freq = frequency or 1000 + local freq = freq / 1000 -- kHz + for i=1,10 do + math.random(i,10000) + end + local template = self.template + local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,99)) + local coalition = self.coalition + local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? + local _spawnedGroup = SPAWN + :NewWithAlias(template,alias) + :InitCoalition(coalition) + :InitCountry(country) + :InitAIOnOff(pilotcacontrol) + :InitDelayOff() + :SpawnFromCoordinate(point) + + return _spawnedGroup, alias -- Wrapper.Group#GROUP object +end + +--- (Internal) Add options to a downed pilot +-- @param #CSAR self +-- @param Wrapper.Group#GROUP group Group to use. +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 + +--- (Internal) Function to spawn a CSAR object into the scene. +-- @param #CSAR self +-- @param #number _coalition Coalition +-- @param DCS#country.id _country Country ID +-- @param Core.Point#COORDINATE _point Coordinate +-- @param #string _typeName Typename +-- @param #string _unitName Unitname +-- @param #string _playerName Playername +-- @param #number _freq Frequency +-- @param #boolean noMessage +-- @param #string _description Description +function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) + self:T(self.lid .. " _AddCsar") + self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) + + local template = self.template + + if not _freq then + _freq = self:_GenerateADFFrequency() + if not _freq then _freq = 333000 end --noob catch + end + + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) + + local _typeName = _typeName or "PoW" + + if not noMessage then + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) + --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) + end + + if _freq then + self:_AddBeaconToGroup(_spawnedGroup, _freq) + end + + self:_AddSpecialOptions(_spawnedGroup) + + local _text = " " + if _playerName ~= nil then + _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName + elseif _typeName ~= nil then + _text = "AI Pilot of " .. _unitName .. " - " .. _typeName + else + _text = _description + end + + self:T({_spawnedGroup, _alias}) + + local _GroupName = _spawnedGroup:GetName() or _alias + + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) + + self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) + + return self +end + +--- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +-- @param #CSAR self +-- @param #string _zone Name of the zone. +-- @param #number _coalition Coalition. +-- @param #string _description (optional) Description. +-- @param #boolean _randomPoint (optional) Random yes or no. +-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) + self:T(self.lid .. " _SpawnCsarAtZone") + local freq = self:_GenerateADFFrequency() + local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position + if _triggerZone == nil then + self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) + return + end + + local _description = _description or "Unknown" + + 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 = country.id.USA + elseif _coalition == coalition.side.RED then + _country = country.id.RUSSIA + else + _country = country.id.UN_PEACEKEEPERS + end + + self:_AddCsar(_coalition, _country, pos, "PoW", _description, nil, freq, _nomessage, _description) + + return self +end + +--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +-- @param #CSAR self +-- @param #string Zone Name of the zone. +-- @param #number Coalition Coalition. +-- @param #string Description (optional) Description. +-- @param #boolean RandomPoint (optional) Random yes or no. +-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. +-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so: +-- +-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) + return self +end + +-- TODO: Split in functions per Event type +--- (Internal) Event handler. +-- @param #CSAR self +function CSAR:_EventHandler(EventData) + self:T(self.lid .. " _EventHandler") + self:T({Event = EventData.id}) + + local _event = EventData -- Core.Event#EVENTDATA + + -- no event + if _event == nil or _event.initiator == nil then + return false + + -- take off + elseif _event.id == EVENTS.Takeoff then -- taken off + self:T(self.lid .. " Event unit - Takeoff") + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + if _event.IniGroupName then + self.takenOff[_event.IniUnitName] = true + end + + return true + + -- player enter unit + elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit + self:T(self.lid .. " Event unit - Player Enter") + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + if _event.IniPlayerName then + self.takenOff[_event.IniPlayerName] = nil + end + + local _unit = _event.IniUnit + local _group = _event.IniGroup + if _unit:IsHelicopter() or _group:IsHelicopter() then + self:_AddMedevacMenuItem() + end + + return true + + elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then + -- Pilot dead + + 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 -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + -- Catch multiple events here? + if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then + if self:_DoubleEjection(_unitname) then + return + end + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!", self.coalition, 10) + --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) + else + self:T(self.lid .. " Pilot has not taken off, ignore") + end + + return + + elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then + if _event.id == EVENTS.PilotDead and self.csarOncrash == false then + return + end + self:T(self.lid .. " Event unit - Pilot Ejected") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _unit:GetCoalition() + if _coalition ~= self.coalition then + return --ignore! + end + + if self.enableForAI == false and _event.IniPlayerName == nil then + return + end + + if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then + self:T(self.lid .. " Pilot has not taken off, ignore") + return -- give up, pilot hasnt taken off + end + + if self:_DoubleEjection(_unitname) then + return + end + + -- limit no of pilots in the field. + if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then + return + end + + -- all checks passed, get going. + local _freq = self:_GenerateADFFrequency() + self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") + + return true + + 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 -- Wrapper.Unit#UNIT + + if _unit == nil then + self:T(self.lid .. " Unit nil on landing") + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + self.takenOff[_event.IniUnitName] = nil + + local _place = _event.Place -- Wrapper.Airbase#AIRBASE + + if _place == nil then + self:T(self.lid .. " Landing Place Nil") + return -- error! + end + + if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then + self:_RescuePilots(_unit) + else + self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) + end + end + + return true + end + return self +end + +--- (Internal) Initialize the action for a pilot. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _downedGroup The group to rescue. +-- @param #string _GroupName Name of the Group +-- @param #number _freq Beacon frequency. +-- @param #boolean _nomessage Send message true or false. +function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) + self:T(self.lid .. " _InitSARForPilot") + local _leader = _downedGroup:GetUnit(1) + --local _groupName = _downedGroup:GetName() + local _groupName = _GroupName + local _freqk = _freq / 1000 + local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) + local _leadername = _leader:GetName() + + if not _nomessage then + local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _leadername, _coordinatesText, _freqk) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + end + + for _,_heliName in pairs(self.csarUnits) do + self:_CheckWoundedGroupStatus(_heliName, _groupName) + end + + -- trigger FSM event + self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) + + return self +end + +--- (Internal) Check if a name is in downed pilot table +-- @param #CSAR self +-- @param #string name Name to search for. +-- @return #boolean Outcome. +-- @return #CSAR.DownedPilot Table if found else nil. +function CSAR:_CheckNameInDownedPilots(name) + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local found = false + local table = nil + for _,_pilot in pairs(PilotTable) do + if _pilot.name == name then + found = true + table = _pilot + break + end + end + return found, table +end + +--- (Internal) Check if a name is in downed pilot table and remove it. +-- @param #CSAR self +-- @param #string name Name to search for. +-- @param #boolean force Force removal. +-- @return #boolean Outcome. +function CSAR:_RemoveNameFromDownedPilots(name,force) + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local found = false + for _,_pilot in pairs(PilotTable) do + if _pilot.name == name then + local group = _pilot.group -- Wrapper.Group#GROUP + if group then + if (not group:IsAlive()) or ( force == true) then -- don\'t delete groups which still exist + found = true + _pilot.desc = nil + _pilot.frequency = nil + _pilot.index = nil + _pilot.name = nil + _pilot.originalUnit = nil + _pilot.player = nil + _pilot.side = nil + _pilot.typename = nil + _pilot.group = nil + _pilot.timestamp = nil + end + end + end + end + return found +end + +--- (Internal) Check state of wounded group. +-- @param #CSAR self +-- @param #string heliname heliname +-- @param #string woundedgroupname woundedgroupname +function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) + self:T(self.lid .. " _CheckWoundedGroupStatus") + local _heliName = heliname + local _woundedGroupName = woundedgroupname + self:T({Heli = _heliName, Downed = _woundedGroupName}) + -- if wounded group is not here then message already been sent to SARs + -- stop processing any further + 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 then + local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT + + local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking + + if _heliUnit == nil then + self.heliVisibleMessage[_lookupKeyHeli] = nil + self.heliCloseMessage[_lookupKeyHeli] = nil + self.landedStatus[_lookupKeyHeli] = nil + self:T("...helinunit nil!") + return + end + + --if self:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then + local _heliCoord = _heliUnit:GetCoordinate() + local _leaderCoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_heliCoord,_leaderCoord) + if _distance < self.approachdist_near and _distance > 0 then + if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then + -- we\'re close, reschedule + _downedpilot.timestamp = timer.getAbsTime() + self:__Approach(-5,heliname,woundedgroupname) + end + elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then + self.heliVisibleMessage[_lookupKeyHeli] = nil + --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away + _downedpilot.timestamp = timer.getAbsTime() + self:__Approach(-10,heliname,woundedgroupname) + end + else + self:T("...Downed Pilot KIA?!") + self:_RemoveNameFromDownedPilots(_downedpilot.name) + end + return self +end + +--- (Internal) Function to pop a smoke at a wounded pilot\'s positions. +-- @param #CSAR self +-- @param #string _woundedGroupName Name of the group. +-- @param Wrapper.Group#GROUP _woundedLeader Object of the group. +function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) + self:T(self.lid .. " _PopSmokeForGroup") + -- have we popped smoke already in the last 5 mins + local _lastSmoke = self.smokeMarkers[_woundedGroupName] + if _lastSmoke == nil or timer.getTime() > _lastSmoke then + + local _smokecolor = self.smokecolor + local _smokecoord = _woundedLeader:GetCoordinate() + _smokecoord:Smoke(_smokecolor) + self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time + end + return self +end + +--- (Internal) Function to pickup the wounded pilot from the ground. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heliUnit Object of the group. +-- @param #string _pilotName Name of the pilot. +-- @param Wrapper.Group#GROUP _woundedGroup Object of the group. +-- @param #string _woundedGroupName Name of the group. +function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _PickupUnit") + -- board + local _heliName = _heliUnit:GetName() + local _groups = self.inTransitGroups[_heliName] + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + + -- init table if there is none for this helicopter + if not _groups then + self.inTransitGroups[_heliName] = {} + _groups = self.inTransitGroups[_heliName] + end + + -- if the heli can\'t pick them up, show a message and return + 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, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) + return true + end + + local found,downedgrouptable = self:_CheckNameInDownedPilots(_woundedGroupName) + local grouptable = downedgrouptable --#CSAR.DownedPilot + self.inTransitGroups[_heliName][_woundedGroupName] = + { + -- DONE: Fix with #CSAR.DownedPilot + originalUnit = grouptable.originalUnit, + woundedGroup = _woundedGroupName, + side = self.coalition, + desc = grouptable.desc, + player = grouptable.player, + } + + _woundedGroup:Destroy() + self:_RemoveNameFromDownedPilots(_woundedGroupName,true) + + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) + + self:__Boarded(5,_heliName,_woundedGroupName) + + return true +end + +--- (Internal) Move group to destination. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _leader +-- @param Core.Point#COORDINATE _destination +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 + + +--- (internal) Function to check if the heli door(s) are open. Thanks to Shadowze. +-- @param #CSAR self +-- @param #string unit_name Name of unit. +-- @return #boolean outcome The outcome. +function CSAR:_IsLoadingDoorOpen( unit_name ) + self:T(self.lid .. " _IsLoadingDoorOpen") + local ret_val = false + local unit = Unit.getByName(unit_name) + if unit ~= nil then + local type_name = unit:getTypeName() + + if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then + self:I(unit_name .. " Cargo doors are open or cargo door not present") + ret_val = true + end + + if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then + self:I(unit_name .. " a side door is open") + ret_val = true + end + + if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then + self:I(unit_name .. " a side door is open ") + ret_val = true + end + + if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then + self:I(unit_name .. " front door(s) are open") + ret_val = true + end + + if ret_val == false then + self:I(unit_name .. " all doors are closed") + end + return ret_val + + end -- nil + + return false +end + +--- (Internal) Function to check if heli is close to group. +-- @param #CSAR self +-- @param #number _distance +-- @param Wrapper.Unit#UNIT _heliUnit +-- @param #string _heliName +-- @param Wrapper.Group#GROUP _woundedGroup +-- @param #string _woundedGroupName +-- @return #boolean Outcome +function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _CheckCloseWoundedGroup") + + local _woundedLeader = _woundedGroup + local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking + + local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot + local _pilotName = _pilotable.desc + + + local _reset = true + + if (self.autosmoke == true) and (_distance < self.autosmokedistance) then + self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) + end + + if self.heliVisibleMessage[_lookupKeyHeli] == nil then + if self.autosmoke == true then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime,true,true) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime,true,true) + end + --mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end + + if (_distance < 500) then + + 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.", _heliName, _pilotName), self.messageTime,true,true) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) + end + --mark as shown for THIS heli and THIS group + self.heliCloseMessage[_lookupKeyHeli] = true + end + + -- have we landed close enough? + if not _heliUnit:InAir() then + + -- if you land on them, doesnt matter if they were heading to someone else as you\'re closer, you win! :) + if self.pilotRuntoExtractPoint == true then + if (_distance < self.extractDistance) then + local _time = self.landedStatus[_lookupKeyHeli] + if _time == nil then + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) + _time = self.landedStatus[_lookupKeyHeli] + self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) + else + _time = self.landedStatus[_lookupKeyHeli] - 10 + self.landedStatus[_lookupKeyHeli] = _time + end + if _time <= 0 or _distance < self.loadDistance then + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, bugger!", self.messageTime, true) + return true + else + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + end + else + if (_distance < self.loadDistance) then + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, honk!", self.messageTime, true) + return true + else + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + end + else + + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + + if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then + + if _distance < 8.0 then + + --check height! + local leaderheight = _woundedLeader:GetHeight() + if leaderheight < 0 then leaderheight = 0 end + local _height = _heliUnit:GetHeight() - leaderheight + + if _height <= 20.0 then + + local _time = self.hoverStatus[_lookupKeyHeli] + + if _time == nil then + self.hoverStatus[_lookupKeyHeli] = 10 + _time = 10 + else + _time = self.hoverStatus[_lookupKeyHeli] - 10 + self.hoverStatus[_lookupKeyHeli] = _time + end + + if _time > 0 then + 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 + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, noob!", self.messageTime, true) + return true + else + self.hoverStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + _reset = false + else + self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) + end + end + + end + end + end + + if _reset then + self.hoverStatus[_lookupKeyHeli] = nil + end + + if _distance < 500 then + return true + else + return false + end +end + +--- (Internal) Check if group not KIA. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _woundedGroup +-- @param #string _woundedGroupName +-- @param Wrapper.Unit#UNIT _heliUnit +-- @param #string _heliName +-- @return #boolean Outcome +function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) + self:T(self.lid .. " _CheckGroupNotKIA") + -- check if unit has died or been picked up + local inTransit = false + if _woundedGroup and _heliUnit then + for _currentHeli, _groups in pairs(self.inTransitGroups) do + if _groups[_woundedGroupName] then + inTransit = true + self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, self.messageTime) + break + end -- end name check + end -- end loop + if not inTransit then + -- KIA + self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, self.messageTime) + end + --stops the message being displayed again + self:_RemoveNameFromDownedPilots(_woundedGroupName) + end + --continue + return inTransit +end + +--- (Internal) Monitor in-flight returning groups. +-- @param #CSAR self +-- @param #string heliname Heli name +-- @param #string groupname Group name +function CSAR:_ScheduledSARFlight(heliname,groupname) + self:T(self.lid .. " _ScheduledSARFlight") + self:T({heliname,groupname}) + local _heliUnit = self:_GetSARHeli(heliname) + local _woundedGroupName = groupname + + if (_heliUnit == nil) then + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return + end + + if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then + -- Groups already rescued + return + end + + local _dist = self:_GetClosestMASH(_heliUnit) + + if _dist == -1 then + return + end + + if _dist < 200 and _heliUnit:InAir() == false then + self:_RescuePilots(_heliUnit) + return + end + + --queue up + self:__Returning(-5,heliname,_woundedGroupName) + return self +end + +--- (Internal) Mark pilot as rescued and remove from tables. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heliUnit +function CSAR:_RescuePilots(_heliUnit) + self:T(self.lid .. " _RescuePilots") + local _heliName = _heliUnit:GetName() + local _rescuedGroups = self.inTransitGroups[_heliName] + + if _rescuedGroups == nil then + -- Groups already rescued + return + end + + -- DONE: count saved units? + local PilotsSaved = self:_PilotsOnboard(_heliName) + + self.inTransitGroups[_heliName] = nil + + local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved) + + self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) + -- trigger event + self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) + return self +end + +--- (Internal) Check and return Wrappe.Unit#UNIT based on the name if alive. +-- @param #CSAR self +-- @param #string _unitname Name of Unit +-- @return #UNIT or nil +function CSAR:_GetSARHeli(_unitName) + self:T(self.lid .. " _GetSARHeli") + local unit = UNIT:FindByName(_unitName) + if unit and unit:IsAlive() then + return unit + else + return nil + end +end + +--- (Internal) Display message to single Unit. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _unit Unit #UNIT to display to. +-- @param #string _text Text of message. +-- @param #number _time Message show duration. +-- @param #boolean _clear (optional) Clear screen. +-- @param #boolean _speak (optional) Speak message via SRS. +function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) + self:T(self.lid .. " _DisplayMessageToSAR") + local group = _unit:GetGroup() + local _clear = _clear or nil + local _time = _time or self.messageTime + local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) + -- integrate SRS + if _speak and self.useSRS then + local srstext = SOUNDTEXT:New(_text) + local path = self.SRSPath + local modulation = self.SRSModulation + local channel = self.SRSchannel + local msrs = MSRS:New(path,channel,modulation) + msrs:PlaySoundText(srstext, 2) + end + return self +end + +--- (Internal) Function to get string of a group\'s position. +-- @param #CSAR self +-- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. +-- @return #string Coordinates as Text +function CSAR:_GetPositionOfWounded(_woundedGroup) + self:T(self.lid .. " _GetPositionOfWounded") + local _coordinate = _woundedGroup:GetCoordinate() + local _coordinatesText = "None" + if _coordinate then + if self.coordtype == 0 then -- Lat/Long DMTM + _coordinatesText = _coordinate:ToStringLLDDM() + elseif self.coordtype == 1 then -- Lat/Long DMS + _coordinatesText = _coordinate:ToStringLLDMS() + elseif self.coordtype == 2 then -- MGRS + _coordinatesText = _coordinate:ToStringMGRS() + elseif self.coordtype == 3 then -- Bullseye Imperial + local Settings = _SETTINGS:SetImperial() + _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + else -- Bullseye Metric --(medevac.coordtype == 4) + local Settings = _SETTINGS:SetMetric() + _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + end + end + return _coordinatesText +end + +--- (Internal) Display active SAR tasks to player. +-- @param #CSAR self +-- @param #string _unitName Unit to display to +function CSAR:_DisplayActiveSAR(_unitName) + self:T(self.lid .. " _DisplayActiveSAR") + local _msg = "Active MEDEVAC/SAR:" + local _heli = self:_GetSARHeli(_unitName) -- Wrapper.Unit#UNIT + if _heli == nil then + return + end + + local _heliSide = self.coalition + local _csarList = {} + + local _DownedPilotTable = self.downedPilots + self:T({Table=_DownedPilotTable}) + for _, _value in pairs(_DownedPilotTable) do + local _groupName = _value.name + self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) + self:T({Table=_value}) + --local _woundedGroup = GROUP:FindByName(_groupName) + local _woundedGroup = _value.group + if _woundedGroup then + local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) + local _helicoord = _heli:GetCoordinate() + local _woundcoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_helicoord, _woundcoord) + self:T({_distance = _distance}) + -- change distance to miles if self.coordtype < 4 + local distancetext = "" + if self.coordtype < 4 then + distancetext = string.format("%.3fnm",UTILS.MetersToNM(_distance)) + else + distancetext = string.format("%.3fkm", _distance/1000.0) + end + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) + end + end + + local function sortDistance(a, b) + return a.dist < b.dist + end + + table.sort(_csarList, sortDistance) + + for _, _line in pairs(_csarList) do + _msg = _msg .. "\n" .. _line.msg + end + + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) + return self +end + +--- (Internal) Find the closest downed pilot to a heli. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT +-- @return #table Table of results +function CSAR:_GetClosestDownedPilot(_heli) + self:T(self.lid .. " _GetClosestDownedPilot") + local _side = self.coalition + local _closestGroup = nil + local _shortestDistance = -1 + local _distance = 0 + local _closestGroupInfo = nil + local _heliCoord = _heli:GetCoordinate() + + local DownedPilotsTable = self.downedPilots + for _, _groupInfo in pairs(DownedPilotsTable) do + local _woundedName = _groupInfo.name + local _tempWounded = _groupInfo.group + + -- check group exists and not moving to someone else + if _tempWounded then + local _tempCoord = _tempWounded:GetCoordinate() + _distance = self:_GetDistance(_heliCoord, _tempCoord) + + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance + _closestGroup = _tempWounded + _closestGroupInfo = _groupInfo + end + end + end + + return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } +end + +--- (Internal) Fire a flare at the point of a downed pilot. +-- @param #CSAR self +-- @param #string _unitName Name of the unit. +function CSAR:_SignalFlare(_unitName) + self:T(self.lid .. " _SignalFlare") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + + local _closest = self:_GetClosestDownedPilot(_heli) + + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = 0 + if self.coordtype < 4 then + _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.3fkm",_closest.distance) + end + local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) + + local _coord = _closest.pilot:GetCoordinate() + _coord:FlareRed(_clockDir) + else + local disttext = "4.3nm" + if self.coordtype == 4 then + disttext = "8km" + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) + end + return self +end + +--- (Internal) Display info to all SAR groups. +-- @param #CSAR self +-- @param #string _message Message to display. +-- @param #number _side Coalition of message. +-- @param #number _messagetime How long to show. +function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) + self:T(self.lid .. " _DisplayToAllSAR") + for _, _unitName in pairs(self.csarUnits) do + local _unit = self:_GetSARHeli(_unitName) + if _unit then + if not _messagetime then + self:_DisplayMessageToSAR(_unit, _message, _messagetime) + end + end + end + return self +end + +---(Internal) Request smoke at closest downed pilot. +--@param #CSAR self +--@param #string _unitName Name of the helicopter +function CSAR:_Reqsmoke( _unitName ) + self:T(self.lid .. " _Reqsmoke") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + local _closest = self:_GetClosestDownedPilot(_heli) + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = 0 + if self.coordtype < 4 then + _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.3fkm",_closest.distance) + end + local _msg = string.format("%s - Popping signal smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) + local _coord = _closest.pilot:GetCoordinate() + local color = self.smokecolor + _coord:Smoke(color) + else + local disttext = "4.3nm" + if self.coordtype == 4 then + disttext = "8km" + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) + end + return self +end + +--- (Internal) Determine distance to closest MASH. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT +-- @retunr +function CSAR:_GetClosestMASH(_heli) + self:T(self.lid .. " _GetClosestMASH") + local _mashset = self.mash -- Core.Set#SET_GROUP + local _mashes = _mashset:GetSetObjects() -- #table + local _shortestDistance = -1 + local _distance = 0 + local _helicoord = _heli:GetCoordinate() + + local function GetCloseAirbase(coordinate,Coalition,Category) + + local a=coordinate:GetVec3() + local distmin=math.huge + local airbase=nil + for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do + local b=DCSairbase:getPoint() + + local c=UTILS.VecSubstract(a,b) + local dist=UTILS.VecNorm(c) + + if dist 0 then + local PilotTable = self.downedPilots + for _,_pilot in pairs (PilotTable) do + self:T({_pilot}) + local pilot = _pilot -- #CSAR.DownedPilot + local group = pilot.group + local frequency = pilot.frequency or 0 -- thanks to @Thrud + if group and group:IsAlive() and frequency > 0 then + self:_AddBeaconToGroup(group,frequency) + end + end + end + return self +end + +--- (Internal) Helper function to count active downed pilots. +-- @param #CSAR self +-- @return #number Number of pilots in the field. +function CSAR:_CountActiveDownedPilots() + self:T(self.lid .. " _CountActiveDownedPilots") + local PilotsInFieldN = 0 + for _, _unitName in pairs(self.downedPilots) do + self:T({_unitName}) + if _unitName.name ~= nil then + PilotsInFieldN = PilotsInFieldN + 1 + end + end + return PilotsInFieldN +end + +--- (Internal) Helper to decide if we're over max limit. +-- @param #CSAR self +-- @return #boolean True or false. +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 + return true + else + return false + end +end + + ------------------------------ + --- FSM internal Functions --- + ------------------------------ + +--- (Internal) Function called after Start() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStart(From, Event, To) + self:T({From, Event, To}) + self:I(self.lid .. "Started.") + -- event handler + self:HandleEvent(EVENTS.Takeoff, self._EventHandler) + self:HandleEvent(EVENTS.Land, self._EventHandler) + self:HandleEvent(EVENTS.Ejection, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.PilotDead, self._EventHandler) + if 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:__Status(-10) + return self +end + +--- (Internal) Function called before Status() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + -- housekeeping + self:_AddMedevacMenuItem() + self:_RefreshRadioBeacons() + for _,_sar in pairs (self.csarUnits) do + local PilotTable = self.downedPilots + for _,_entry in pairs (PilotTable) do + local entry = _entry -- #CSAR.DownedPilot + local name = entry.name + local timestamp = entry.timestamp or 0 + local now = timer.getAbsTime() + if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. + self:_CheckWoundedGroupStatus(_sar,name) + end + end + end + return self +end + +--- (Internal) Function called after Status() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStatus(From, Event, To) + self:T({From, Event, To}) + -- collect some stats + 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 + +--- (Internal) Function called after Stop() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStop(From, Event, To) + self:T({From, Event, To}) + -- event handler + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.PlayerEnterUnit) + self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + self:UnHandleEvent(EVENTS.PilotDead) + self:T(self.lid .. "Stopped.") + return self +end + +--- (Internal) Function called before Approach() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. +function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) + return self +end + +--- (Internal) Function called before Boarded() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. +function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname) + return self +end + +--- (Internal) Function called before Returning() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. +function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname) + return self +end + +--- (Internal) Function called before Rescued() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. +-- @param #string HeliName Name of the helicopter group. +-- @param #number PilotsSaved Number of the saved pilots on board when landing. +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 + return self +end + +--- (Internal) Function called before PilotDown() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP Group Group object of the downed pilot. +-- @param #number Frequency Beacon frequency in kHz. +-- @param #string Leadername Name of the #UNIT of the downed pilot. +-- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. +function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, CoordinatesText) + self:T({From, Event, To, Group, Frequency, Leadername, CoordinatesText}) + return self +end +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End Ops.CSAR +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua new file mode 100644 index 000000000..4c9adf68d --- /dev/null +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -0,0 +1,2591 @@ +--- **Ops** -- Combat Troops & Logistics Deployment. +-- +-- === +-- +-- **CTLD** - MOOSE based Helicopter CTLD Operations. +-- +-- === +-- +-- ## Missions: +-- +-- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CTLD) +-- +-- === +-- +-- **Main Features:** +-- +-- * MOOSE-based Helicopter CTLD Operations for Players. +-- +-- === +-- +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing) +-- @module Ops.CTLD +-- @image OPS_CTLD.jpg + +-- Date: July 2021 + +do +------------------------------------------------------ +--- **CTLD_CARGO** class, extends #Core.Base#BASE +-- @type CTLD_CARGO +-- @field #number ID ID of this cargo. +-- @field #string Name Name for menu. +-- @field #table Templates Table of #POSITIONABLE objects. +-- @field #CTLD_CARGO.Enum Type Enumerator of Type. +-- @field #boolean HasBeenMoved Flag for moving. +-- @field #boolean LoadDirectly Flag for direct loading. +-- @field #number CratesNeeded Crates needed to build. +-- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. +-- @field #boolean HasBeenDropped True if dropped from heli. +-- @extends Core.Fsm#FSM +CTLD_CARGO = { + ClassName = "CTLD_CARGO", + ID = 0, + Name = "none", + Templates = {}, + CargoType = "none", + HasBeenMoved = false, + LoadDirectly = false, + CratesNeeded = 0, + Positionable = nil, + HasBeenDropped = false, + } + + --- Define cargo types. + -- @type CTLD_CARGO.Enum + -- @field #string Type Type of Cargo. + CTLD_CARGO.Enum = { + ["VEHICLE"] = "Vehicle", -- #string vehicles + ["TROOPS"] = "Troops", -- #string troops + ["FOB"] = "FOB", -- #string FOB + ["CRATE"] = "Crate", -- #string crate + } + + --- Function to create new CTLD_CARGO object. + -- @param #CTLD_CARGO self + -- @param #number ID ID of this #CTLD_CARGO + -- @param #string Name Name for menu. + -- @param #table Templates Table of #POSITIONABLE objects. + -- @param #CTLD_CARGO.Enum Sorte Enumerator of Type. + -- @param #boolean HasBeenMoved Flag for moving. + -- @param #boolean LoadDirectly Flag for direct loading. + -- @param #number CratesNeeded Crates needed to build. + -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. + -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. + -- @return #CTLD_CARGO self + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped) + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #CTLD + self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) + self.ID = ID or math.random(100000,1000000) + self.Name = Name or "none" -- #string + self.Templates = Templates or {} -- #table + self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum + self.HasBeenMoved = HasBeenMoved or false -- #booolean + self.LoadDirectly = LoadDirectly or false -- #booolean + self.CratesNeeded = CratesNeeded or 0 -- #number + self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE + self.HasBeenDropped = Dropped or false --#boolean + return self + end + + --- Query ID. + -- @param #CTLD_CARGO self + -- @return #number ID + function CTLD_CARGO:GetID() + return self.ID + end + + --- Query Name. + -- @param #CTLD_CARGO self + -- @return #string Name + function CTLD_CARGO:GetName() + return self.Name + end + + --- Query Templates. + -- @param #CTLD_CARGO self + -- @return #table Templates + function CTLD_CARGO:GetTemplates() + return self.Templates + end + + --- Query has moved. + -- @param #CTLD_CARGO self + -- @return #boolean Has moved + function CTLD_CARGO:HasMoved() + return self.HasBeenMoved + end + + --- Query was dropped. + -- @param #CTLD_CARGO self + -- @return #boolean Has been dropped. + function CTLD_CARGO:WasDropped() + return self.HasBeenDropped + end + + --- Query directly loadable. + -- @param #CTLD_CARGO self + -- @return #boolean loadable + function CTLD_CARGO:CanLoadDirectly() + return self.LoadDirectly + end + + --- Query number of crates or troopsize. + -- @param #CTLD_CARGO self + -- @return #number Crates or size of troops. + function CTLD_CARGO:GetCratesNeeded() + return self.CratesNeeded + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return #CTLD_CARGO.Enum Type + function CTLD_CARGO:GetType() + return self.CargoType + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return Wrapper.Positionable#POSITIONABLE Positionable + function CTLD_CARGO:GetPositionable() + return self.Positionable + end + + --- Set HasMoved. + -- @param #CTLD_CARGO self + -- @param #boolean moved + function CTLD_CARGO:SetHasMoved(moved) + self.HasBeenMoved = moved or false + end + + --- Query if cargo has been loaded. + -- @param #CTLD_CARGO self + -- @param #boolean loaded + function CTLD_CARGO:Isloaded() + if self.HasBeenMoved and not self.WasDropped() then + return true + else + return false + end + end + --- Set WasDropped. + -- @param #CTLD_CARGO self + -- @param #boolean dropped + function CTLD_CARGO:SetWasDropped(dropped) + self.HasBeenDropped = dropped or false + end + +end + +do +------------------------------------------------------------------------- +--- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM +-- @type CTLD +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @extends Core.Fsm#FSM + +--- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there\'s POG stuff to be done.* (Mil Saying) +-- +-- === +-- +-- ![Banner Image](OPS_CTLD.jpg) +-- +-- # CTLD Concept +-- +-- * MOOSE-based CTLD for Players. +-- * Object oriented refactoring of Ciribob\'s fantastic CTLD script. +-- * No need for extra MIST loading. +-- * Additional events to tailor your mission. +-- * ANY late activated group can serve as cargo, either as troops or crates, which have to be build on-location. +-- +-- ## 0. Prerequisites +-- +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces. +-- +-- ## 1. Basic Setup +-- +-- ## 1.1 Create and start a CTLD instance +-- +-- A basic setup example is the following: +-- +-- -- Instantiate and start a CTLD for the blue side, using helicopter groups named "Helicargo" and alias "Lufttransportbrigade I" +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo"},"Lufttransportbrigade I") +-- my_ctld:__Start(5) +-- +-- ## 1.2 Add cargo types available +-- +-- Add *generic* cargo types that you need for your missions, here infantry units, vehicles and a FOB. These need to be late-activated Wrapper.Group#GROUP objects: +-- +-- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3 +-- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed) +-- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3) +-- +-- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4 +-- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4) +-- +-- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build +-- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) +-- +-- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: +-- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) +-- +-- ## 1.3 Add logistics zones +-- +-- Add zones for loading troops and crates and dropping, building crates +-- +-- -- Add a zone of type LOAD to our setup. Players can load troops and crates. +-- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside of the zone. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) +-- +-- -- Add a zone of type DROP. Players can drop crates here. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- -- NOTE: Troops can be unloaded anywhere, also when hovering in parameters. +-- my_ctld:AddCTLDZone("Dropzone",CTLD.CargoZoneType.DROP,SMOKECOLOR.Red,true,true) +-- +-- -- Add two zones of type MOVE. Dropped troops and vehicles will move to the nearest one. See options. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Movezone",CTLD.CargoZoneType.MOVE,SMOKECOLOR.Orange,false,false) +-- +-- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true) +-- +-- +-- ## 2. Options +-- +-- The following options are available (with their defaults). Only set the ones you want changed: +-- +-- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. +-- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. +-- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. +-- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. +-- my_ctld.forcehoverload = true -- Crates (not: troops) can only be loaded while hovering. +-- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. +-- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). +-- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... +-- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) +-- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) +-- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. +-- +-- ## 2.1 User functions +-- +-- ### 2.1.1 Adjust or add chopper unit-type capabilities +-- +-- Use this function to adjust what a heli type can or cannot do: +-- +-- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. +-- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: +-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8) +-- +-- Default unit type capabilities are: +-- +-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, +-- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, +-- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, +-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, +-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, +-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, +-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- +-- +-- ### 2.1.2 Activate and deactivate zones +-- +-- Activate a zone: +-- +-- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:ActivateZone(Name,CTLD.CargoZoneType.MOVE) +-- +-- Deactivate a zone: +-- +-- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) +-- +-- ## 3. Events +-- +-- The class comes with a number of FSM-based events that missions designers can use to shape their mission. +-- These are: +-- +-- ## 3.1 OnAfterTroopsPickedUp +-- +-- This function is called when a player has loaded Troops: +-- +-- function my_ctld:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.2 OnAfterCratesPickedUp +-- +-- This function is called when a player has picked up crates: +-- +-- function my_ctld:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.3 OnAfterTroopsDeployed +-- +-- This function is called when a player has deployed troops into the field: +-- +-- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) +-- ... your code here ... +-- end +-- +-- ## 3.4 OnAfterCratesDropped +-- +-- This function is called when a player has deployed crates to a DROP zone: +-- +-- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- ... your code here ... +-- end +-- +-- ## 3.5 OnAfterCratesBuild +-- +-- This function is called when a player has build a vehicle or FOB: +-- +-- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- ... your code here ... +-- end + -- +-- ## 3.6 A simple SCORING example: +-- +-- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) +-- +-- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- local points = 10 +-- local PlayerName = Unit:GetPlayerName() +-- my_scoring:_AddPlayerFromUnit( Unit ) +-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) +-- end +-- +-- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- local points = 5 +-- local PlayerName = Unit:GetPlayerName() +-- my_scoring:_AddPlayerFromUnit( Unit ) +-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) +-- end +-- +-- ## 4. F10 Menu structure +-- +-- CTLD management menu is under the F10 top menu and called "CTLD" +-- +-- ## 4.1 Manage Crates +-- +-- Use this entry to get, load, list nearby, drop, and build crates. Also @see options. +-- +-- ## 4.2 Manage Troops +-- +-- Use this entry to load and drop troops. +-- +-- ## 4.3 List boarded cargo +-- +-- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. +-- +-- ## 4.4 Smoke & Flare zones nearby +-- +-- Does what it says. +-- +-- ## 4.5 List active zone beacons +-- +-- Lists active radio beacons for all zones, where zones are both active and have a beacon. @see `CTLD:AddCTLDZone()` +-- +-- ## 4.6 Show hover parameters +-- +-- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. +-- +-- ## 5. Support for Hercules mod by Anubis +-- +-- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can +-- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class. +-- +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") +-- +-- Enable these options for Hercules support: +-- +-- my_ctld.enableHercules = true +-- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft +-- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- +-- Also, the following options need to be set to `true`: +-- +-- my_ctld.useprefix = true -- this is true by default and MUST BE ON. +-- +-- Standard transport capabilities as per the real Hercules are: +-- +-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers +-- +-- @field #CTLD +CTLD = { + ClassName = "CTLD", + verbose = 0, + lid = "", + coalition = 1, + coalitiontxt = "blue", + PilotGroups = {}, -- #GROUP_SET of heli pilots + CtldUnits = {}, -- Table of helicopter #GROUPs + FreeVHFFrequencies = {}, -- Table of VHF + FreeUHFFrequencies = {}, -- Table of UHF + FreeFMFrequencies = {}, -- Table of FM + CargoCounter = 0, + dropOffZones = {}, + wpZones = {}, + Cargo_Troops = {}, -- generic troops objects + Cargo_Crates = {}, -- generic crate objects + Loaded_Cargo = {}, -- cargo aboard units + Spawned_Crates = {}, -- Holds objects for crates spawned generally + Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects + CrateDistance = 30, -- list crates in this radius + debug = false, + wpZones = {}, + pickupZones = {}, + dropOffZones = {}, +} + +------------------------------ +-- DONE: Zone Checks +-- DONE: TEST Hover load and unload +-- DONE: Crate unload +-- DONE: Hover (auto-)load +-- TODO: (More) Housekeeping +-- DONE: Troops running to WP Zone +-- DONE: Zone Radio Beacons +-- DONE: Stats Running +-- DONE: Added support for Hercules +-- TODO: Possibly - either/or loading crates and troops +-- TODO: Limit of troops, crates buildable? +------------------------------ + +--- Radio Beacons +-- @type CTLD.ZoneBeacon +-- @field #string name -- Name of zone for the coordinate +-- @field #number frequency -- in mHz +-- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM + +--- Zone Info. +-- @type CTLD.CargoZone +-- @field #string name Name of Zone. +-- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. +-- @field #boolean active Active or not. +-- @field #string type Type of zone, i.e. load,drop,move +-- @field #boolean hasbeacon Create and run radio beacons if active. +-- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon + +--- Zone Type Info. +-- @type CTLD. +CTLD.CargoZoneType = { + LOAD = "load", + DROP = "drop", + MOVE = "move", +} + +--- Buildable table info. +-- @type CTLD.Buildable +-- @field #string Name Name of the object. +-- @field #number Required Required crates. +-- @field #number Found Found crates. +-- @field #table Template Template names for this build. +-- @field #boolean CanBuild Is buildable or not. +-- @field #CTLD_CARGO.Enum Type Type enumerator (for moves). + +--- Unit capabilities. +-- @type CTLD.UnitCapabilities +-- @field #string type Unit type. +-- @field #boolean crates Can transport crate. +-- @field #boolean troops Can transport troops. +-- @field #number cratelimit Number of crates transportable. +-- @field #number trooplimit Number of troop units transportable. +CTLD.UnitTypes = { + ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, + ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, + ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, + ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, + ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, + ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, + ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, + ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, + ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, + ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers +} + +--- Updated and sorted known NDB beacons (in kHz!) from the available maps +-- @field #CTLD.SkipFrequencies +CTLD.SkipFrequencies = { + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580,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 + } + +--- CTLD class version. +-- @field #string version +CTLD.version="0.1.3r2" + +--- Instantiate a new CTLD. +-- @param #CTLD self +-- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL +-- @param #table Prefixes Table of pilot prefixes. +-- @param #string Alias Alias of this CTLD for logging. +-- @return #CTLD self +function CTLD:New(Coalition, Prefixes, Alias) + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CTLD + + BASE:T({Coalition, Prefixes, Alias}) + + --set Coalition + 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 + + -- Set alias. + 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 + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- CTLD status update. + self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + -- tables + self.PilotGroups ={} + self.CtldUnits = {} + + -- Beacons + self.FreeVHFFrequencies = {} + self.FreeUHFFrequencies = {} + self.FreeFMFrequencies = {} + self.UsedVHFFrequencies = {} + self.UsedUHFFrequencies = {} + self.UsedFMFrequencies = {} + --self.jtacGeneratedLaserCodes = {} + + -- radio beacons + self.RadioSound = "beacon.ogg" + + -- zones stuff + self.pickupZones = {} + self.dropOffZones = {} + self.wpZones = {} + + -- Cargo + self.Cargo_Crates = {} + self.Cargo_Troops = {} + self.Loaded_Cargo = {} + self.Spawned_Crates = {} + self.Spawned_Cargo = {} + self.MenusDone = {} + self.DroppedTroops = {} + self.DroppedCrates = {} + self.CargoCounter = 0 + self.CrateCounter = 0 + self.TroopCounter = 0 + + -- setup + self.CrateDistance = 30 -- list/load crates in this radius + self.prefixes = Prefixes or {"Cargoheli"} + --self.I({prefixes = self.prefixes}) + self.useprefix = true + + self.maximumHoverHeight = 15 + self.minimumHoverHeight = 4 + self.forcehoverload = true + self.hoverautoloading = true + + self.smokedistance = 2000 + self.movetroopstowpzone = true + self.movetroopsdistance = 5000 + + -- added support Hercules Mod + self.enableHercules = false + self.HercMinAngels = 165 -- for troop/cargo drop via chute + self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + + -- message suppression + self.suppressmessages = false + + for i=1,100 do + math.random() + end + + self:_GenerateVHFrequencies() + self:_GenerateUHFrequencies() + self:_GenerateFMFrequencies() + --self:_GenerateLaserCodes() -- curr unused + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] Start + -- @param #CTLD self + + --- Triggers the FSM event "Start" after a delay. Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] __Start + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CTLD and all its event handlers. + -- @param #CTLD self + + --- Triggers the FSM event "Stop" after a delay. Stops the CTLD and all its event handlers. + -- @function [parent=#CTLD] __Stop + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CTLD] Status + -- @param #CTLD self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CTLD] __Status + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- FSM Function OnAfterTroopsPickedUp. + -- @function [parent=#CTLD] OnAfterTroopsPickedUp + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self + + --- FSM Function OnAfterCratesPickedUp. + -- @function [parent=#CTLD] OnAfterCratesPickedUp + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsDeployed. + -- @function [parent=#CTLD] OnAfterTroopsDeployed + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + + --- FSM Function OnAfterCratesDropped. + -- @function [parent=#CTLD] OnAfterCratesDropped + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + + --- FSM Function OnAfterCratesBuild. + -- @function [parent=#CTLD] OnAfterCratesBuild + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsRTB. + -- @function [parent=#CTLD] OnAfterTroopsRTB + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + + return self +end + +------------------------------------------------------------------- +-- Helper and User Functions +------------------------------------------------------------------- + +--- (Internal) Function to get capabilities of a chopper +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The unit +-- @return #table Capabilities Table of caps +function CTLD:_GetUnitCapabilities(Unit) + self:T(self.lid .. " _GetUnitCapabilities") + local _unit = Unit -- Wrapper.Unit#UNIT + local unittype = _unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + if not capabilities or capabilities == {} then + -- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, + capabilities = {} + capabilities.troops = false + capabilities.crates = false + capabilities.cratelimit = 0 + capabilities.trooplimit = 0 + capabilities.type = "generic" + end + return capabilities +end + + +--- (Internal) Function to generate valid UHF Frequencies +-- @param #CTLD self +function CTLD:_GenerateUHFrequencies() + self:T(self.lid .. " _GenerateUHFrequencies") + self.FreeUHFFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + table.insert(self.FreeUHFFrequencies, _start) + _start = _start + 500000 + end + + return self +end + +--- (Internal) Function to generate valid FM Frequencies +-- @param #CTLD sel +function CTLD:_GenerateFMFrequencies() + self:T(self.lid .. " _GenerateFMrequencies") + self.FreeFMFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + + _start = _start + 500000 + end + + for _first = 3, 7 do + for _second = 0, 5 do + for _third = 0, 9 do + local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit + table.insert(self.FreeFMFrequencies, _frequency) + end + end + end + + return self +end + +--- (Internal) Populate table with available VHF beacon frequencies. +-- @param #CTLD self +function CTLD:_GenerateVHFrequencies() + self:T(self.lid .. " _GenerateVHFrequencies") + local _skipFrequencies = self.SkipFrequencies + + self.FreeVHFFrequencies = {} + self.UsedVHFFrequencies = {} + + -- first range + local _start = 200000 + while _start < 400000 do + + -- skip existing NDB frequencies# + 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(self.FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- second range + _start = 400000 + while _start < 850000 do + -- skip existing NDB frequencies + 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(self.FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- third range + _start = 850000 + while _start <= 999000 do -- adjusted for Gazelle + -- skip existing NDB frequencies + 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(self.FreeVHFFrequencies, _start) + end + _start = _start + 50000 + end + + return self +end + +--- (Internal) Function to generate valid laser codes. +-- @param #CTLD self +function CTLD:_GenerateLaserCodes() + self:T(self.lid .. " _GenerateLaserCodes") + self.jtacGeneratedLaserCodes = {} + -- generate list of laser codes + local _code = 1111 + local _count = 1 + while _code < 1777 and _count < 30 do + while true do + _code = _code + 1 + if not self:_ContainsDigit(_code, 8) + and not self:_ContainsDigit(_code, 9) + and not self:_ContainsDigit(_code, 0) then + table.insert(self.jtacGeneratedLaserCodes, _code) + break + end + end + _count = _count + 1 + end +end + +--- (Internal) Helper function to generate laser codes. +-- @param #CTLD self +-- @param #number _number +-- @param #number _numberToFind +function CTLD:_ContainsDigit(_number, _numberToFind) + self:T(self.lid .. " _ContainsDigit") + 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 + +--- (Internal) Event handler function +-- @param #CTLD self +-- @param Core.Event#EVENTDATA EventData +function CTLD:_EventHandler(EventData) + -- TODO: events dead and playerleaveunit - nil table entries + self:T(string.format("%s Event = %d",self.lid, EventData.id)) + local event = EventData -- Core.Event#EVENTDATA + if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then + local _coalition = event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + -- check is Helicopter + 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 + -- Herc support + --self:T_unit:GetTypeName()) + if _unit:GetTypeName() == "Hercules" and self.enableHercules then + self:_RefreshF10Menus() + end + return + elseif event.id == EVENTS.PlayerLeaveUnit then + -- remove from pilot table + local unitname = event.IniUnitName or "none" + self.CtldUnits[unitname] = nil + self.Loaded_Cargo[unitname] = nil + end + return self +end + +--- (Internal) Function to message a group. +-- @param #CTLD self +-- @param #string Text The text to display. +-- @param #number Time Number of seconds to display the message. +-- @param #boolean Clearscreen Clear screen or not. +-- @param Wrapper.Group#GROUP Group The group receiving the message. +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 + +--- (Internal) Function to load troops into a heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargotype +function CTLD:_LoadTroops(Group, Unit, Cargotype) + self:T(self.lid .. " _LoadTroops") + -- landed or hovering over load zone? + local grounded = not self:IsUnitInAir(Unit) + local hoverload = self:CanHoverLoad(Unit) + -- check if we are in LOAD zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + 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) + --local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + end + -- load troops into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + local cargotype = Cargotype -- #CTLD_CARGO + local cratename = cargotype:GetName() -- #string + --self:Tself.lid .. string.format("Troops %s requested", cratename)) + -- see if this heli can load troops + local unittype = unit:GetTypeName() + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) + local cantroops = capabilities.troops -- #boolean + local trooplimit = capabilities.trooplimit -- #number + local troopsize = cargotype:GetCratesNeeded() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + if troopsize + numberonboard > trooplimit then + self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + --local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) + return + else + self.CargoCounter = self.CargoCounter + 1 + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + self:T({cargotype=loadcargotype}) + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert(loaded.Cargo,loadcargotype) + self.Loaded_Cargo[unitname] = loaded + self:_SendMessage("Troops boarded!", 10, false, Group) + --local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) + self:__TroopsPickedUp(1,Group, Unit, Cargotype) + end + return self +end + +--- (Internal) Function to spawn crates in front of the heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargo +-- @param #number number Number of crates to generate (for dropping) +-- @param #boolean drop If true we\'re dropping from heli rather than loading. +function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) + self:T(self.lid .. " _GetCrates") + local cgoname = Cargo:GetName() + --self:T{cgoname, number, drop}) + -- check if we are in LOAD zone + local inzone = false + local drop = drop or false + if not drop then + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + else + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + end + + if not inzone then + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + end + + -- avoid crate spam + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities + local canloadcratesno = capabilities.cratelimit + local loaddist = self.CrateDistance or 30 + local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) + if numbernearby >= canloadcratesno and not drop then + self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) + --local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) + return self + end + -- spawn crates in front of helicopter + local IsHerc = self:IsHercules(Unit) -- Herc + local cargotype = Cargo -- #CTLD_CARGO + local number = number or cargotype:GetCratesNeeded() --#number + local cratesneeded = cargotype:GetCratesNeeded() --#number + local cratename = cargotype:GetName() + --self:Tself.lid .. string.format("Crate %s requested", cratename)) + local cratetemplate = "Container"-- #string + -- get position and heading of heli + local position = Unit:GetCoordinate() + local heading = Unit:GetHeading() + 1 + local height = Unit:GetHeight() + local droppedcargo = {} + -- loop crates needed + for i=1,number do + local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) + local cratedistance = i*4 + 8 + if IsHerc then + -- wider radius + cratedistance = i*4 + 12 + end + for i=1,50 do + math.random(90,270) + end + local rheading = math.floor(math.random(90,270) * heading + 1 / 360) + if not IsHerc then + rheading = rheading + 180 -- mirror for Helis + end + if rheading > 360 then rheading = rheading - 360 end -- catch > 360 + local cratecoord = position:Translate(cratedistance,rheading) + local cratevec2 = cratecoord:GetVec2() + self.CrateCounter = self.CrateCounter + 1 + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",country.id.GERMANY) + :InitCoordinate(cratecoord) + :Spawn(270,cratealias) + + local templ = cargotype:GetTemplates() + local sorte = cargotype:GetType() + 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) + table.insert(droppedcargo,realcargo) + else + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter]) + end + table.insert(self.Spawned_Cargo, realcargo) + 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) + end + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) + return self +end + +--- (Internal) Function to find and list nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCratesNearby( _group, _unit) + self:T(self.lid .. " _ListCratesNearby") + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(_group,_unit, finddist) -- #table + if number > 0 then + local text = REPORT:New("Crates Found Nearby:") + text:Add("------------------------------------------------------------") + for _,_entry in pairs (crates) do + local entry = _entry -- #CTLD_CARGO + local name = entry:GetName() --#string + -- TODO Meaningful sorting/aggregation + local dropped = entry:WasDropped() + if dropped then + text:Add(string.format("Dropped crate for %s",name)) + else + text:Add(string.format("Crate for %s",name)) + end + end + if text:GetCount() == 1 then + text:Add("--------- N O N E ------------") + end + text:Add("------------------------------------------------------------") + self:_SendMessage(text:Text(), 30, true, _group) + --local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) + else + self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) + --local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) + end + return self +end + +--- (Internal) Return distance in meters between two coordinates. +-- @param #CTLD self +-- @param Core.Point#COORDINATE _point1 Coordinate one +-- @param Core.Point#COORDINATE _point2 Coordinate two +-- @return #number Distance in meters +function CTLD:_GetDistance(_point1, _point2) + self:T(self.lid .. " _GetDistance") + if _point1 and _point2 then + local distance = _point1:DistanceFromPointVec2(_point2) + return distance + else + return -1 + end +end + +--- (Internal) Function to find and return nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP _group Group +-- @param Wrapper.Unit#UNIT _unit Unit +-- @param #number _dist Distance +-- @return #table Table of crates +-- @return #number Number Number of crates found +function CTLD:_FindCratesNearby( _group, _unit, _dist) + self:T(self.lid .. " _FindCratesNearby") + local finddist = _dist + local location = _group:GetCoordinate() + local existingcrates = self.Spawned_Cargo -- #table + -- cycle + local index = 0 + local found = {} + for _,_cargoobject in pairs (existingcrates) do + local cargo = _cargoobject -- #CTLD_CARGO + local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates + local staticid = cargo:GetID() + if static and static:IsAlive() then + local staticpos = static:GetCoordinate() + local distance = self:_GetDistance(location,staticpos) + if distance <= finddist and static then + index = index + 1 + table.insert(found, staticid, cargo) + end + end + end + --self:Tstring.format("Found crates = %d",index)) + -- table.sort(found) + --self:T({found}) + return found, index +end + +--- (Internal) Function to get and load nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_LoadCratesNearby(Group, Unit) + self:T(self.lid .. " _LoadCratesNearby") + -- load crates into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load crates + local unittype = unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(Unit) + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + local grounded = not self:IsUnitInAir(Unit) + local canhoverload = self:CanHoverLoad(Unit) + --- cases ------------------------------- + -- Chopper can\'t do crates - bark & return + -- Chopper can do crates - + -- --> hover if forcedhover or bark and return + -- --> hover or land if not forcedhover + ----------------------------------------- + if not cancrates then + self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + --local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) + elseif self.forcehoverload and not canhoverload then + self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) + --local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + elseif not grounded and not canhoverload then + self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) + --local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + else + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + -- get nearby crates + local finddist = self.CrateDistance or 30 + local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + if number == 0 or numberonboard == cratelimit then + self:_SendMessage("Sorry no loadable crates nearby or fully loaded!", 10, false, Group) + --local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) + return -- exit + else + -- go through crates and load + local capacity = cratelimit - numberonboard + local crateidsloaded = {} + local loops = 0 + while loaded.Cratesloaded < cratelimit and loops < number do + --for _ind,_crate in pairs (nearcrates) do + loops = loops + 1 + local crateind = 0 + -- get crate with largest index + for _ind,_crate in pairs (nearcrates) do + if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then + --crate = _crate + crateind = _crate:GetID() + end + end + -- load one if we found one + if crateind > 0 then + local crate = nearcrates[crateind] -- #CTLD_CARGO + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved(true) + table.insert(loaded.Cargo, crate) + table.insert(crateidsloaded,crate:GetID()) + -- destroy crate + crate:GetPositionable():Destroy() + crate.Positionable = nil + self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + --local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) + self:__CratesPickedUp(1, Group, Unit, crate) + end + --if loaded.Cratesloaded == cratelimit then break end + end + self.Loaded_Cargo[unitname] = loaded + -- clean up real world crates + local existingcrates = self.Spawned_Cargo -- #table + local newexcrates = {} + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + for _,_ID in pairs(crateidsloaded) do + if ID ~= _ID then + table.insert(newexcrates,_crate) + end + end + end + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + end + end + return self +end + +--- (Internal) Function to list loaded cargo. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCargo(Group, Unit) + self:T(self.lid .. " _ListCargo") + local unitname = Unit:GetName() + local unittype = Unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local trooplimit = capabilities.trooplimit -- #boolean + local cratelimit = capabilities.cratelimit -- #number + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + 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 {} -- #table + 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 -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() 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 + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS then + report:Add(string.format("Crate: %s size 1",cargo:GetName())) + cratecount = cratecount + 1 + end + end + if cratecount == 0 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + local text = report:Text() + self:_SendMessage(text, 30, true, Group) + --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + else + self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) + --local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) + end + return self +end + +--- (Internal) Function to check if a unit is a Hercules C-130. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #boolean Outcome +function CTLD:IsHercules(Unit) + if Unit:GetTypeName() == "Hercules" then + return true + else + return false + end +end + +--- (Internal) Function to unload troops from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_UnloadTroops(Group, Unit) + self:T(self.lid .. " _UnloadTroops") + -- check if we are in LOAD zone + local droppingatbase = false + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if inzone then + droppingatbase = true + end + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + if not droppingatbase or self.debug then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for troops + local cargotable = loadedcargo.Cargo + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + -- unload troops + local name = cargo:GetName() or "none" + local temptable = cargo:GetTemplates() or {} + local position = Group:GetCoordinate() + local zoneradius = 100 -- drop zone radius + local factor = 1 + if IsHerc then + factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping + 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):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + end -- template loop + cargo:SetWasDropped(true) + self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) + --local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) + self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) + end -- if type end + end -- cargotable loop + else -- droppingatbase + self:_SendMessage("Troops have returned to base!", 10, false, Group) + --local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) + self:__TroopsRTB(1, Group, Unit) + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local cargotable = loadedcargo.Cargo or {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local dropped = cargo:WasDropped() + --local moved = cargo:HasMoved() + if type ~= CTLD_CARGO.Enum.TROOP and not dropped then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Cratesloaded + 1 + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + if IsHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end + end + return self +end + +--- (Internal) Function to unload crates from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_UnloadCrates(Group, Unit) + self:T(self.lid .. " _UnloadCrates") + -- check if we are in DROP zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then + return self + end + end + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for troops + local cargotable = loadedcargo.Cargo + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + -- unload crates + self:_GetCrates(Group, Unit, cargo, 1, true) + cargo:SetWasDropped(true) + cargo:SetHasMoved(true) + --local name cargo:GetName() + --local m = MESSAGE:New(string.format("Dropped Crate for %s!",name),10,"CTLD"):ToGroup(Group) + end + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local size = cargo:GetCratesNeeded() + if type == CTLD_CARGO.Enum.TROOP then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Troopsloaded + size + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + if IsHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end + end + return self +end + +--- (Internal) Function to build nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_BuildCrates(Group, Unit) + self:T(self.lid .. " _BuildCrates") + -- get nearby crates + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _,_crate in pairs(crates) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + 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 dropped + end -- end crate loop + -- ok let\'s list what we have + local report = REPORT:New("Checklist Buildable Crates") + report:Add("------------------------------------------------------------") + for _,_build in pairs(buildables) do + local build = _build -- Object table from above + local name = build.Name + local needed = build.Required + local found = build.Found + local txtok = "NO" + if build.CanBuild then + txtok = "YES" + end + --self:T{name,needed,found,txtok}) + local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) + report:Add(text) + end -- end list buildables + if not foundbuilds then report:Add(" --- None Found ---") end + report:Add("------------------------------------------------------------") + local text = report:Text() + self:_SendMessage(text, 30, true, Group) + --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + -- let\'s get going + if canbuild then + -- loop again + for _,_build in pairs(buildables) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_CleanUpCrates(crates,build,number) + self:_BuildObjectFromCrates(Group,Unit,build) + end + end + end + else + self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + --local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) + end -- number > 0 + return self +end + +--- (Internal) Function to actually SPAWN buildables in the mission. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Group#UNIT Unit +-- @param #CTLD.Buildable Build +function CTLD:_BuildObjectFromCrates(Group,Unit,Build) + self:T(self.lid .. " _BuildObjectFromCrates") + -- Spawn-a-crate-content + local position = Unit:GetCoordinate() or Group:GetCoordinate() + local unitname = Unit:GetName() or Group:GetName() + local name = Build.Name + local type = Build.Type -- #CTLD_CARGO.Enum + local canmove = false + if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end + local temptable = Build.Template or {} + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) + local randomcoord = zone:GetRandomCoordinate(35):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone and canmove then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + end -- template loop + return self +end + +--- (Internal) Function to move group to WP zone. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The Group to move. +function CTLD:_MoveGroupToZone(Group) + self:T(self.lid .. " _MoveGroupToZone") + local groupname = Group:GetName() or "none" + local groupcoord = Group:GetCoordinate() + --self:Tself.lid .. " _MoveGroupToZone for " .. groupname) + -- Get closest zone of type + local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) + --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) + if (distance <= self.movetroopsdistance) and zone then + -- yes, we can ;) + local groupname = Group:GetName() + --self:Tstring.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) + local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE + local coordinate = zonecoord:GetVec2() + --self:T{coordinate=coordinate}) + Group:SetAIOn() + Group:OptionAlarmStateAuto() + Group:OptionDisperseOnAttack(30) + Group:OptionROEOpenFirePossible() + Group:RouteToVec2(coordinate,5) + end + return self +end + +--- (Internal) Housekeeping - Cleanup crates when build +-- @param #CTLD self +-- @param #table Crates Table of #CTLD_CARGO objects near the unit. +-- @param #CTLD.Buildable Build Table build object. +-- @param #number Number Number of objects in Crates (found) to limit search. +function CTLD:_CleanUpCrates(Crates,Build,Number) + self:T(self.lid .. " _CleanUpCrates") + -- clean up real world crates + local build = Build -- #CTLD.Buildable + --self:T{Build = Build}) + local existingcrates = self.Spawned_Cargo -- #table of exising crates + local newexcrates = {} + -- get right number of crates to destroy + local numberdest = Build.Required + local nametype = Build.Name + local found = 0 + local rounds = Number + local destIDs = {} + + -- loop and find matching IDs in the set + for _,_crate in pairs(Crates) do + local nowcrate = _crate -- #CTLD_CARGO + local name = nowcrate:GetName() + --self:Tstring.format("Looking for Crate for %s", name)) + local thisID = nowcrate:GetID() + if name == nametype then -- matching crate type + table.insert(destIDs,thisID) + found = found + 1 + nowcrate:GetPositionable():Destroy() + nowcrate.Positionable = nil + --self:Tstring.format("%s Found %d Need %d", name, found, numberdest)) + end + if found == numberdest then break end -- got enough + end + --self:T{destIDs}) + -- loop and remove from real world representation + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + for _,_ID in pairs(destIDs) do + if ID ~= _ID then + table.insert(newexcrates,_crate) + end + end + end + + -- reset Spawned_Cargo + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + return self +end + +--- (Internal) Housekeeping - Function to refresh F10 menus. +-- @param #CTLD self +-- @return #CTLD self +function CTLD:_RefreshF10Menus() + self:T(self.lid .. " _RefreshF10Menus") + local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP + local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects + --self:T({PlayerTable=PlayerTable}) + -- rebuild units table + local _UnitList = {} + for _key, _group in pairs (PlayerTable) do + local _unit = _group:GetUnit(1) -- Wrapper.Unit#UNIT Asume that there is only one unit in the flight for players + if _unit then + if _unit:IsAlive() and _unit:IsPlayer() then + if _unit:IsHelicopter() or (_unit:GetTypeName() == "Hercules" and self.enableHercules) then --ensure no stupid unit entries here + local unitName = _unit:GetName() + _UnitList[unitName] = unitName + end + end -- end isAlive + end -- end if _unit + end -- end for + self.CtldUnits = _UnitList + + -- build unit menus + + local menucount = 0 + local menus = {} + for _, _unitName in pairs(self.CtldUnits) do + if not self.MenusDone[_unitName] then + local _unit = UNIT:FindByName(_unitName) -- Wrapper.Unit#UNIT + if _unit then + local _group = _unit:GetGroup() -- Wrapper.Group#GROUP + if _group then + -- get chopper capabilities + local unittype = _unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cantroops = capabilities.troops + local cancrates = capabilities.crates + -- top menu + local topmenu = MENU_GROUP:New(_group,"CTLD",nil) + local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) + local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) + local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, true):Refresh() + -- sub menus + -- sub menu crates management + if cancrates then + local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) + local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) + for _,_entry in pairs(self.Cargo_Crates) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Get crate for %s",entry.Name) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end + listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) + local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit):Refresh() + end + -- sub menu troops management + if cantroops then + local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) + for _,_entry in pairs(self.Cargo_Troops) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) + end + local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() + end + local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) + if unittype == "Hercules" then + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + else + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + end + self.MenusDone[_unitName] = true + end -- end group + end -- end unit + else -- menu build check + self:T(self.lid .. " Menus already done for this group!") + end -- end menu build check + end -- end for + return self + end + +--- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. +-- @param #CTLD self +-- @param #string Name Unique name of this type of troop. E.g. "Anti-Air Small". +-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. +-- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. +-- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops) + self:T(self.lid .. " AddTroopsCargo") + self.CargoCounter = self.CargoCounter + 1 + -- Troops are directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops) + table.insert(self.Cargo_Troops,cargo) + return self +end + +--- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. +-- @param #CTLD self +-- @param #string Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. +-- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. +-- @param #number NoCrates Number of crates needed to build this cargo. +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates) + self:T(self.lid .. " AddCratesCargo") + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates) + table.insert(self.Cargo_Crates,cargo) + return self +end + +--- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance. +-- @param #CTLD self +-- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone. +function CTLD:AddZone(Zone) + self:T(self.lid .. " AddZone") + local zone = Zone -- #CTLD.CargoZone + if zone.type == CTLD.CargoZoneType.LOAD then + table.insert(self.pickupZones,zone) + --self:T"Registered LOAD zone " .. zone.name) + elseif zone.type == CTLD.CargoZoneType.DROP then + table.insert(self.dropOffZones,zone) + --self:T"Registered DROP zone " .. zone.name) + else + table.insert(self.wpZones,zone) + --self:T"Registered MOVE zone " .. zone.name) + end + return self +end + +--- User function - Activate Name #CTLD.CargoZone.Type ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneTyp ZoneType Type of zone this belongs to. +-- @param #boolean NewState (Optional) Set to true to activate, false to switch off. +function CTLD:ActivateZone(Name,ZoneType,NewState) + self:T(self.lid .. " AddZone") + local newstate = true + -- set optional in case we\'re deactivating + if not NewState or NewState == false then + newstate = false + end + -- get correct table + local zone = ZoneType -- #CTLD.CargoZone + local table = {} + if zone.type == CTLD.CargoZoneType.LOAD then + table = self.pickupZones + elseif zone.type == CTLD.CargoZoneType.DROP then + table = self.dropOffZones + else + table = self.wpZones + end + -- loop table + for _,_zone in pairs(table) do + local thiszone = _zone --#CTLD.CargoZone + if thiszone.name == Name then + thiszone.active = newstate + break + end + end + return self +end + +--- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneTyp ZoneType Type of zone this belongs to. +function CTLD:DeactivateZone(Name,ZoneType) + self:T(self.lid .. " AddZone") + self:ActivateZone(Name,ZoneType,false) + return self +end + +--- (Internal) Function to obtain a valid FM frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetFMBeacon(Name) + self:T(self.lid .. " _GetFMBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeFMFrequencies <= 1 then + self.FreeFMFrequencies = self.UsedFMFrequencies + self.UsedFMFrequencies = {} + end + --random + local FM = table.remove(self.FreeFMFrequencies, math.random(#self.FreeFMFrequencies)) + table.insert(self.UsedFMFrequencies, FM) + beacon.name = Name + beacon.frequency = FM / 1000000 + beacon.modulation = radio.modulation.FM + + return beacon +end + +--- (Internal) Function to obtain a valid UHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetUHFBeacon(Name) + self:T(self.lid .. " _GetUHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeUHFFrequencies <= 1 then + self.FreeUHFFrequencies = self.UsedUHFFrequencies + self.UsedUHFFrequencies = {} + end + --random + local UHF = table.remove(self.FreeUHFFrequencies, math.random(#self.FreeUHFFrequencies)) + table.insert(self.UsedUHFFrequencies, UHF) + beacon.name = Name + beacon.frequency = UHF / 1000000 + beacon.modulation = radio.modulation.AM + + return beacon +end + +--- (Internal) Function to obtain a valid VHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetVHFBeacon(Name) + self:T(self.lid .. " _GetVHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeVHFFrequencies <= 3 then + self.FreeVHFFrequencies = self.UsedVHFFrequencies + self.UsedVHFFrequencies = {} + end + --get random + local VHF = table.remove(self.FreeVHFFrequencies, math.random(#self.FreeVHFFrequencies)) + table.insert(self.UsedVHFFrequencies, VHF) + beacon.name = Name + beacon.frequency = VHF / 1000000 + beacon.modulation = radio.modulation.FM + return beacon +end + + +--- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. +-- Zones of type LOAD: Players load crates and troops here. +-- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. +-- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). +-- @param #CTLD self +-- @param #string Name Name of this zone, as in Mission Editor. +-- @param #string Type Type of this zone, #CTLD.CargoZoneType +-- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red +-- @param #string Active Is this zone currently active? +-- @param #string HasBeacon Does this zone have a beacon if it is active? +-- @return #CTLD self +function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon) + self:T(self.lid .. " AddCTLDZone") + + local ctldzone = {} -- #CTLD.CargoZone + ctldzone.active = Active or false + ctldzone.color = Color or SMOKECOLOR.Red + ctldzone.name = Name or "NONE" + ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType + ctldzone.hasbeacon = HasBeacon or false + + if HasBeacon then + ctldzone.fmbeacon = self:_GetFMBeacon(Name) + ctldzone.uhfbeacon = self:_GetUHFBeacon(Name) + ctldzone.vhfbeacon = self:_GetVHFBeacon(Name) + else + ctldzone.fmbeacon = nil + ctldzone.uhfbeacon = nil + ctldzone.vhfbeacon = nil + end + + self:AddZone(ctldzone) + return self +end + +--- (Internal) Function to show list of radio beacons +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +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} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency * 1000 -- KHz + local UHF = UHFbeacon.frequency -- MHz + 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) + --local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) + return self +end + +--- (Internal) Add radio beacon to zone. Runs 30 secs. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @param #string Sound Name of soundfile. +-- @param #number Mhz Frequency in Mhz. +-- @param #number Modulation Modulation AM or FM. +function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation) + self:T(self.lid .. " _AddRadioBeacon") + local Zone = ZONE:FindByName(Name) + local Sound = Sound or "beacon.ogg" + if Zone then + local ZoneCoord = Zone:GetCoordinate() + local ZoneVec3 = ZoneCoord:GetVec3() + local Frequency = Mhz * 1000000 -- Freq in Hertz + local Sound = "l10n/DEFAULT/"..Sound + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight + end + return self +end + +--- (Internal) Function to refresh radio beacons +-- @param #CTLD self +function CTLD:_RefreshRadioBeacons() + self:T(self.lid .. " _RefreshRadioBeacons") + + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + local Sound = self.RadioSound + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency -- KHz + local UHF = UHFbeacon.frequency -- MHz + self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM) + self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM) + self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM) + end + end + end + return self +end + +--- (Internal) Function to see if a unit is in a specific zone type. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit Unit +-- @param #CTLD.CargoZoneType Zonetype Zonetype +-- @return #boolean Outcome Is in zone or not +-- @return #string name Closest zone name +-- @return #string zone Closest Core.Zone#ZONE object +-- @return #number distance Distance to closest zone +function CTLD:IsUnitInZone(Unit,Zonetype) + self:T(self.lid .. " IsUnitInZone") + local unitname = Unit:GetName() + --self:Tstring.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) + local zonetable = {} + local outcome = false + if Zonetype == CTLD.CargoZoneType.LOAD then + zonetable = self.pickupZones -- #table + elseif Zonetype == CTLD.CargoZoneType.DROP then + zonetable = self.dropOffZones -- #table + else + zonetable = self.wpZones -- #table + end + --- now see if we\'re in + local zonecoord = nil + local colorret = nil + local maxdist = 1000000 -- 100km + local zoneret = nil + local zonenameret = nil + for _,_cargozone in pairs(zonetable) do + local czone = _cargozone -- #CTLD.CargoZone + local unitcoord = Unit:GetCoordinate() + local zonename = czone.name + local zone = ZONE:FindByName(zonename) + zonecoord = zone:GetCoordinate() + local active = czone.active + local color = czone.color + local zoneradius = zone:GetRadius() + local distance = self:_GetDistance(zonecoord,unitcoord) + --self:Tstring.format("Check distance: %d",distance)) + if distance <= zoneradius and active then + outcome = true + end + if maxdist > distance then + maxdist = distance + zoneret = zone + zonenameret = zonename + colorret = color + end + end + --self:T{outcome, zonenameret, zoneret, maxdist}) + return outcome, zonenameret, zoneret, maxdist +end + +--- User function - Start smoke in a zone close to the Unit. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The Unit. +-- @param #boolean Flare If true, flare instead. +function CTLD:SmokeZoneNearBy(Unit, Flare) + self:T(self.lid .. " SmokeZoneNearBy") + -- table of #CTLD.CargoZone table + local unitcoord = Unit:GetCoordinate() + local Group = Unit:GetGroup() + local smokedistance = self.smokedistance + local smoked = false + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + local CZone = cargozone --#CTLD.CargoZone + local zonename = CZone.name + local zone = ZONE:FindByName(zonename) + local zonecoord = zone:GetCoordinate() + local active = CZone.active + local color = CZone.color + local distance = self:_GetDistance(zonecoord,unitcoord) + if distance < smokedistance and active then + -- smoke zone since we\'re nearby + if not Flare then + zonecoord:Smoke(color or SMOKECOLOR.White) + else + if color == SMOKECOLOR.Blue then color = FLARECOLOR.White end + zonecoord:Flare(color or FLARECOLOR.White) + end + local txt = "smoking" + if Flare then txt = "flaring" end + self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) + --local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) + smoked = true + end + end + end + if not smoked then + local distance = UTILS.MetersToNM(self.smokedistance) + self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) + --local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) + end + return self +end + + --- User - Function to add/adjust unittype capabilities. + -- @param #CTLD self + -- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission. + -- @param #boolean Cancrates Unit can load crates. + -- @param #boolean Cantroops Unit can load troops. + -- @param #number Cratelimit Unit can carry number of crates. + -- @param #number Trooplimit Unit can carry number of troops. + function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit) + self:T(self.lid .. " UnitCapabilities") + local unittype = nil + local unit = nil + if type(Unittype) == "string" then + unittype = Unittype + elseif type(Unittype) == "table" then + unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT + unittype = unit:GetTypeName() + else + return self + end + -- set capabilities + local capabilities = {} -- #CTLD.UnitCapabilities + capabilities.type = unittype + capabilities.crates = Cancrates or false + capabilities.troops = Cantroops or false + capabilities.cratelimit = Cratelimit or 0 + capabilities.trooplimit = Trooplimit or 0 + self.UnitTypes[unittype] = capabilities + return self + end + + --- (Internal) Check if a unit is hovering *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsCorrectHover(Unit) + self:T(self.lid .. " IsCorrectHover") + local outcome = false + -- see if we are in air and within parameters. + if self:IsUnitInAir(Unit) then + -- get speed and height + local uspeed = Unit:GetVelocityMPS() + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + local maxh = self.maximumHoverHeight -- 15 + local minh = self.minimumHoverHeight -- 5 + local mspeed = 2 -- 2 m/s + --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then + -- yep within parameters + outcome = true + end + end + return outcome + end + + --- (Internal) Check if a Hercules is flying *in parameters* for air drops. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsCorrectFlightParameters(Unit) + self:T(self.lid .. " IsCorrectFlightParameters") + local outcome = false + -- see if we are in air and within parameters. + if self:IsUnitInAir(Unit) then + -- get speed and height + local uspeed = Unit:GetVelocityMPS() + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + local maxh = self.HercMinAngels-- 1500m + local minh = self.HercMaxAngels -- 5000m + local mspeed = 2 -- 2 m/s + -- TODO:Add speed test for Herc, should not be above 280kph/150kn + --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + if (aheight <= maxh) and (aheight >= minh) then + -- yep within parameters + outcome = true + end + end + return outcome + end + + --- (Internal) List if a unit is hovering *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_ShowHoverParams(Group,Unit) + local inhover = self:IsCorrectHover(Unit) + local htxt = "true" + if not inhover then htxt = "false" end + local 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) + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + return self + end + + --- (Internal) List if a Herc unit is flying *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_ShowFlightParams(Group,Unit) + local inhover = self:IsCorrectFlightParameters(Unit) + local htxt = "true" + if not inhover then htxt = "false" end + local minheight = UTILS.MetersToFeet(self.HercMinAngels) + local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + local text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) + return self + end + + + --- (Internal) Check if a unit is in a load zone and is hovering in parameters. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:CanHoverLoad(Unit) + self:T(self.lid .. " CanHoverLoad") + if self:IsHercules(Unit) then return false end + local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) + return outcome + end + + --- (Internal) Check if a unit is above ground. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsUnitInAir(Unit) + -- get speed and height + local minheight = self.minimumHoverHeight + if self.enableHercules and Unit:GetTypeName() == "Hercules" then + minheight = 5.1 -- herc is 5m AGL on the ground + end + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + if aheight >= minheight then + return true + else + return false + end + end + + --- (Internal) Autoload if we can do crates, have capacity free and are in a load zone. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #CTLD self + function CTLD:AutoHoverLoad(Unit) + self:T(self.lid .. " AutoHoverLoad") + -- get capabilities and current load + local unittype = Unit:GetTypeName() + local unitname = Unit:GetName() + local Group = Unit:GetGroup() + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + if cancrates then + -- get load + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + 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 + + --- (Internal) Run through all pilots and see if we autoload. + -- @param #CTLD self + -- @return #CTLD self + function CTLD:CheckAutoHoverload() + if self.hoverautoloading then + for _,_pilot in pairs (self.CtldUnits) do + local Unit = UNIT:FindByName(_pilot) + self:AutoHoverLoad(Unit) + end + end + return self + end + +------------------------------------------------------------------- +-- FSM functions +------------------------------------------------------------------- + + --- (Internal) FSM Function onafterStart. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStart(From, Event, To) + self:T({From, Event, To}) + if self.useprefix or self.enableHercules then + local prefix = self.prefixes + --self:T{prefix=prefix}) + if self.enableHercules then + --self:T("CTLD with prefixes and Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() + else + --self:T("CTLD with prefixes NO Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() + end + else + --self:T("CTLD NO prefixes NO Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() + end + -- Events + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:__Status(-5) + return self + end + + --- (Internal) FSM Function onbeforeStatus. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + self:_RefreshF10Menus() + self:_RefreshRadioBeacons() + self:CheckAutoHoverload() + return self + end + + --- (Internal) FSM Function onafterStatus. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStatus(From, Event, To) + self:T({From, Event, To}) + -- gather some stats + -- pilots + local pilots = 0 + for _,_pilot in pairs (self.CtldUnits) do + + pilots = pilots + 1 + end + + -- spawned cargo boxes curr in field + local boxes = 0 + for _,_pilot in pairs (self.Spawned_Cargo) do + boxes = boxes + 1 + end + + local cc = self.CargoCounter + local tc = self.TroopCounter + + if self.debug or self.verbose > 0 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() + end + self:__Status(-30) + return self + end + + --- (Internal) FSM Function onafterStop. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStop(From, Event, To) + self:T({From, Event, To}) + self:UnhandleEvent(EVENTS.PlayerEnterAircraft) + self:UnhandleEvent(EVENTS.PlayerEnterUnit) + self:UnhandleEvent(EVENTS.PlayerLeaveUnit) + return self + end + + --- (Internal) FSM Function onbeforeTroopsPickedUp. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + function CTLD:onbeforeTroopsPickedUp(From, Event, To, Group, Unit, Cargo) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeCratesPickedUp. + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsDeployed. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeCratesDropped. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeCratesBuild. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. + -- @return #CTLD self + function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsRTB. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit) + self:T({From, Event, To}) + return self + end + +end -- end do +------------------------------------------------------------------- +-- End Ops.CTLD.lua +------------------------------------------------------------------- From 18b45c9621b17243f18b1af779b5320e16c39ab6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 10 Jul 2021 17:11:00 +0200 Subject: [PATCH 03/68] Added CTLD and CSAR --- Moose Development/Moose/Modules.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 2ee24ee38..ad27b6b84 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -73,6 +73,8 @@ __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) From 29210f670c4a7da4c7d97d653829f75d3ac21f79 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 11 Jul 2021 18:37:46 +0200 Subject: [PATCH 04/68] Added Fisher-Yeats table shuffle and Helicopter door check --- Moose Development/Moose/Utilities/Utils.lua | 60 +++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index d01707797..943796075 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1504,3 +1504,63 @@ function UTILS.GetOSTime() return nil end + +--- Shuffle a table accoring to Fisher Yeates algorithm +--@param #table table to be shuffled +--@return #table +function UTILS.ShuffleTable(t) + if t == nil or type(t) ~= "table" then + BASE:I("Error in ShuffleTable: Missing or wrong tyÃ¥e 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 + +--- (Helicopter) Check if one loading door is open. +--@param #string unit_name Unit name to be checked +--@return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists. +function UTILS.IsLoadingDoorOpen( unit_name ) + + local ret_val = false + local unit = Unit.getByName(unit_name) + if unit ~= nil then + local type_name = unit:getTypeName() + + if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then + BASE:T(unit_name .. " Cargo doors are open or cargo door not present") + ret_val = 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") + ret_val = 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 ") + ret_val = true + end + + if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then + BASE:T(unit_name .. " front door(s) are open") + ret_val = true + end + + if ret_val == false then + BASE:T(unit_name .. " all doors are closed") + end + return ret_val + + end -- nil + + return nil +end \ No newline at end of file From 6fbf050b723bcd36661de6dc131f682cc5c377d9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Jul 2021 18:16:00 +0200 Subject: [PATCH 05/68] Slightly updated versions as the new emissions on/off doesn't work really. --- Moose Development/Moose/Functional/Mantis.lua | 12 +- Moose Development/Moose/Functional/Sead.lua | 244 +++++++----------- Moose Development/Moose/Functional/Shorad.lua | 101 +++++--- 3 files changed, 167 insertions(+), 190 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 23faabb3f..18d520750 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -20,7 +20,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg --- Date: Apr 2021 +-- Date: July 2021 ------------------------------------------------------------------------- --- **MANTIS** class, extends #Core.Base#BASE @@ -59,7 +59,7 @@ -- @extends Core.Base#BASE ---- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat +--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat -- -- Simple Class for a more intelligent Air Defense System -- @@ -191,7 +191,7 @@ MANTIS = { ShoradLink = false, ShoradTime = 600, ShoradActDistance = 15000, - UseEmOnOff = true, + UseEmOnOff = false, } ----------------------------------------------------------------------- @@ -208,7 +208,7 @@ do --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) - --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional, deault true) + --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN --@return #MANTIS self --@usage Start up your MANTIS with a basic setting -- @@ -267,6 +267,8 @@ do if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false + else + self.UseEmOnOff = true end end @@ -308,7 +310,7 @@ do end -- @field #string version - self.version="0.4.1" + self.version="0.4.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) return self diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 03b8ea47b..c99e1f241 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -17,8 +17,8 @@ -- -- ### Authors: **FlightControl**, **applevangelist** -- --- Last Update: April 2021 --- +-- Last Update: July 2021 +-- -- === -- -- @module Functional.Sead @@ -39,38 +39,21 @@ -- -- @field #SEAD 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 -- default 75% engagement range Feature Request #1355 + 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 -- default 75% engagement range Feature Request #1355 } - -- TODO Complete list? --- Missile enumerators -- @field Harms SEAD.Harms = { - --[[ - ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired - ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired - ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired - ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired - ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired - ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired - ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired - ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired - ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired - ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired - ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired - ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired - ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired - ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired - --]] ["AGM_88"] = "AGM_88", ["AGM_45"] = "AGM_45", ["AGM_122"] = "AGM_122", @@ -97,20 +80,20 @@ SEAD = { -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) function SEAD:New( SEADGroupPrefixes ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( SEADGroupPrefixes ) - - if type( SEADGroupPrefixes ) == 'table' then - for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do - self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix - end - else - self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes - end - - self:HandleEvent( EVENTS.Shot ) - self:I("*** SEAD - Started Version 0.2.7") - return self + local self = BASE:Inherit( self, BASE:New() ) + self:F( SEADGroupPrefixes ) + + if type( SEADGroupPrefixes ) == 'table' then + for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do + self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix + end + else + self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes + end + + self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) + self:I("*** SEAD - Started Version 0.2.8") + return self end --- Update the active SEAD Set @@ -164,112 +147,83 @@ end -- @see SEAD -- @param #SEAD -- @param Core.Event#EVENTDATA EventData -function SEAD:OnEventShot( EventData ) - self:T( { EventData } ) +function SEAD:HandleEventShot( EventData ) + self:T( { EventData } ) - local SEADUnit = EventData.IniDCSUnit - local SEADUnitName = EventData.IniDCSUnitName - local SEADWeapon = EventData.Weapon -- Identify the weapon fired - local SEADWeaponName = EventData.WeaponName -- return weapon type + local SEADUnit = EventData.IniDCSUnit + local SEADUnitName = EventData.IniDCSUnitName + local SEADWeapon = EventData.Weapon -- Identify the weapon fired + local SEADWeaponName = EventData.WeaponName -- return weapon type - self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) - self:T({ SEADWeapon }) - - --[[check for SEAD missiles - if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.LD-10" --LD-10 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_84E" --AGM84 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_84A" --AGM84 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired - --]] + self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) + self:T({ SEADWeapon }) + if self:_CheckHarms(SEADWeaponName) then local _targetskill = "Random" local _targetMimgroupName = "none" - local _evade = math.random (1,100) -- random number for chance of evading action - local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object - if _targetUnit and _targetUnit:IsAlive() then - local _targetMimgroup = _targetUnit:GetGroup() - local _targetMimgroupName = _targetMimgroup:GetName() -- group name - --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill - self:T( self.SEADGroupPrefixes ) - self:T( _targetMimgroupName ) - end - -- see if we are shot at - local SEADGroupFound = false - for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do - if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then - SEADGroupFound = true - self:T( '*** SEAD - Group Found' ) - break - end - end - if SEADGroupFound == true then -- yes we are being attacked - if _targetskill == "Random" then -- when skill is random, choose a skill - local Skills = { "Average", "Good", "High", "Excellent" } - _targetskill = Skills[ math.random(1,4) ] - end - self:T( _targetskill ) - if self.TargetSkill[_targetskill] then - if (_evade > self.TargetSkill[_targetskill].Evade) then - - self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) - - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimcont= _targetMimgroup:getController() - - routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly - - --tracker ID table to switch groups off and on again - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } + local _evade = math.random (1,100) -- random number for chance of evading action + local _targetMim = EventData.Weapon:getTarget() -- Identify target + local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object + if _targetUnit and _targetUnit:IsAlive() then + local _targetMimgroup = _targetUnit:GetGroup() + local _targetMimgroupName = _targetMimgroup:GetName() -- group name + --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill + self:T( self.SEADGroupPrefixes ) + self:T( _targetMimgroupName ) + end + -- see if we are shot at + local SEADGroupFound = false + for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do + if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then + SEADGroupFound = true + self:T( '*** SEAD - Group Found' ) + break + end + end + if SEADGroupFound == true then -- yes we are being attacked + if _targetskill == "Random" then -- when skill is random, choose a skill + local Skills = { "Average", "Good", "High", "Excellent" } + _targetskill = Skills[ math.random(1,4) ] + end + self:T( _targetskill ) + if self.TargetSkill[_targetskill] then + if (_evade > self.TargetSkill[_targetskill].Evade) then + + self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) + + local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) + local _targetMimcont= _targetMimgroup:getController() + + routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly + + --tracker ID table to switch groups off and on again + local id = { + groupName = _targetMimgroup, + ctrl = _targetMimcont + } - local function SuppressionEnd(id) --switch group back on - local range = self.EngagementRange -- Feature Request #1355 - self:T(string.format("*** SEAD - Engagement Range is %d", range)) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) - --id.groupName:enableEmission(true) - id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 - self.SuppressedGroups[id.groupName] = nil --delete group id from table when done - end - -- randomize switch-on time - local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - local SuppressionEndTime = timer.getTime() + delay - --create entry - if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet - self.SuppressedGroups[id.groupName] = { - SuppressionEndTime = delay - } - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - --_targetMimgroup:enableEmission(false) - timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function - end - end - end - end - end + local function SuppressionEnd(id) --switch group back on + local range = self.EngagementRange -- Feature Request #1355 + self:T(string.format("*** SEAD - Engagement Range is %d", range)) + id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) + --id.groupName:enableEmission(true) + id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 + self.SuppressedGroups[id.groupName] = nil --delete group id from table when done + end + -- randomize switch-on time + local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) + local SuppressionEndTime = timer.getTime() + delay + --create entry + if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet + self.SuppressedGroups[id.groupName] = { + SuppressionEndTime = delay + } + Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) + --_targetMimgroup:enableEmission(false) + timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function + end + end + end + end + end end diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 011a4989b..958877ec8 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -13,12 +13,12 @@ -- -- === -- --- ### Author : **applevangelist** +-- ### Author : **applevangelist ** -- -- @module Functional.Shorad -- @image Functional.Shorad.jpg -- --- Date: May 2021 +-- Date: July 2021 ------------------------------------------------------------------------- --- **SHORAD** class, extends Core.Base#BASE @@ -94,7 +94,7 @@ SHORAD = { lid = "", DefendHarms = true, DefendMavs = true, - DefenseLowProb = 75, + DefenseLowProb = 70, DefenseHighProb = 90, UseEmOnOff = false, } @@ -108,22 +108,6 @@ do --- Missile enumerators -- @field Harms SHORAD.Harms = { - --[[ - ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired - ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired - ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired - ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired - ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired - ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired - ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired - ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired - ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired - ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired - ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired - ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired - ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired - ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired - --]] ["AGM_88"] = "AGM_88", ["AGM_45"] = "AGM_45", ["AGM_122"] = "AGM_122", @@ -157,7 +141,9 @@ do -- @param #number Radius Defense radius in meters, used to switch on groups -- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call -- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" - function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition) + -- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch) + -- @retunr #SHORAD self + function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff) local self = BASE:Inherit( self, BASE:New() ) self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) @@ -171,22 +157,23 @@ do self.ActiveTimer = ActiveTimer or 600 self.ActiveGroups = {} self.Groupset = GroupSet - self:HandleEvent( EVENTS.Shot ) self.DefendHarms = true self.DefendMavs = true self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin - self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green - self:I("*** SHORAD - Started Version 0.2.5") + self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green + self:I("*** SHORAD - Started Version 0.2.8") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() + self:HandleEvent(EVENTS.Shot, self.HandleEventShot) return self end --- Initially set all groups to alarm state GREEN -- @param #SHORAD self function SHORAD:_InitState() + self:T(self.lid .. " _InitState") local table = {} local set = self.Groupset self:T({set = set}) @@ -195,31 +182,48 @@ do if self.UseEmOnOff then --_group:SetAIOff() _group:EnableEmission(false) + _group:OptionAlarmStateRed() --Wrapper.Group#GROUP else _group:OptionAlarmStateGreen() --Wrapper.Group#GROUP end + _group:OptionDisperseOnAttack(30) end -- gather entropy - for i=1,10 do + for i=1,100 do math.random() end + return self end - --- Switch debug state + --- Switch debug state on -- @param #SHORAD self -- @param #boolean debug Switch debug on (true) or off (false) - function SHORAD:SwitchDebug(debug) - self:T( { debug } ) - local onoff = debug or false - if debug then - self.debug = true - --tracing - BASE:TraceOn() - BASE:TraceClass("SHORAD") + function SHORAD:SwitchDebug(onoff) + self:T( { onoff } ) + if onoff then + self:SwitchDebugOn() else - self.debug = false - BASE:TraceOff() + self.SwitchDebugOff() end + return self + end + + --- Switch debug state on + -- @param #SHORAD self + function SHORAD:SwitchDebugOn() + self.debug = true + --tracing + BASE:TraceOn() + BASE:TraceClass("SHORAD") + return self + end + + --- Switch debug state off + -- @param #SHORAD self + function SHORAD:SwitchDebugOff() + self.debug = false + BASE:TraceOff() + return self end --- Switch defense for HARMs @@ -229,6 +233,7 @@ do self:T( { onoff } ) local onoff = onoff or true self.DefendHarms = onoff + return self end --- Switch defense for AGMs @@ -238,6 +243,7 @@ do self:T( { onoff } ) local onoff = onoff or true self.DefendMavs = onoff + return self end --- Set defense probability limits @@ -256,35 +262,42 @@ do end self.DefenseLowProb = low self.DefenseHighProb = high + return self end --- Set the number of seconds a SHORAD site will stay active -- @param #SHORAD self -- @param #number seconds Number of seconds systems stay active function SHORAD:SetActiveTimer(seconds) + self:T(self.lid .. " SetActiveTimer") local timer = seconds or 600 if timer < 0 then timer = 600 end self.ActiveTimer = timer + return self end --- Set the number of meters for the SHORAD defense zone -- @param #SHORAD self -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active function SHORAD:SetDefenseRadius(meters) + self:T(self.lid .. " SetDefenseRadius") local radius = meters or 20000 if radius < 0 then radius = 20000 end self.Radius = radius + return self end --- Set using Emission on/off instead of changing alarm state -- @param #SHORAD self -- @param #boolean switch Decide if we are changing alarm state or AI state function SHORAD:SetUsingEmOnOff(switch) + self:T(self.lid .. " SetUsingEmOnOff") self.UseEmOnOff = switch or false + return self end --- Check if a HARM was fired @@ -292,6 +305,7 @@ do -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckHarms(WeaponName) + self:T(self.lid .. " _CheckHarms") self:T( { WeaponName } ) local hit = false if self.DefendHarms then @@ -307,6 +321,7 @@ do -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckMavs(WeaponName) + self:T(self.lid .. " _CheckMavs") self:T( { WeaponName } ) local hit = false if self.DefendMavs then @@ -322,6 +337,7 @@ do -- @param #string Coalition name -- @return #boolean Returns false for a match function SHORAD:_CheckCoalition(Coalition) + self:T(self.lid .. " _CheckCoalition") local owncoalition = self.Coalition local othercoalition = "" if Coalition == 0 then @@ -344,6 +360,7 @@ do -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtShorad(TargetGroupName) + self:T(self.lid .. " _CheckShotAtShorad") local tgtgrp = TargetGroupName local shorad = self.Groupset local shoradset = shorad:GetAliveSet() --#table @@ -352,7 +369,7 @@ do local groupname = _groups:GetName() if string.find(groupname, tgtgrp, 1) then returnname = true - _groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive + --_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive end end return returnname @@ -363,6 +380,7 @@ do -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtSams(TargetGroupName) + self:T(self.lid .. " _CheckShotAtSams") local tgtgrp = TargetGroupName local shorad = self.Samset --local shoradset = shorad:GetAliveSet() --#table @@ -381,6 +399,7 @@ do -- @param #SHORAD self -- @return #boolean Returns true for a detection, else false function SHORAD:_ShotIsDetected() + self:T(self.lid .. " _ShotIsDetected") local IsDetected = false local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value local ActualDetection = math.random(1,100) -- value for this shot @@ -405,6 +424,7 @@ do -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat) + self:T(self.lid .. " WakeUpShorad") self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) local targetcat = TargetCat or Object.Category.UNIT local targetgroup = TargetGroup @@ -442,7 +462,7 @@ do self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) if self.UseEmOnOff then - _group:SetAIOn() + --_group:SetAIOn() _group:EnableEmission(true) end _group:OptionAlarmStateRed() @@ -454,14 +474,15 @@ do end end end + return self end --- Main function - work on the EventData -- @param #SHORAD self -- @param Core.Event#EVENTDATA EventData The event details table data set - function SHORAD:OnEventShot( EventData ) + function SHORAD:HandleEventShot( EventData ) self:T( { EventData } ) - + self:T(self.lid .. " HandleEventShot") --local ShootingUnit = EventData.IniDCSUnit --local ShootingUnitName = EventData.IniDCSUnitName local ShootingWeapon = EventData.Weapon -- Identify the weapon fired @@ -524,4 +545,4 @@ do end ----------------------------------------------------------------------- -- SHORAD end ------------------------------------------------------------------------ +----------------------------------------------------------------------- \ No newline at end of file From 2fb0ab1aed120746bb3ba4699b235392007a8e15 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 14 Jul 2021 08:41:14 +0200 Subject: [PATCH 06/68] Reflect DEVELOP changes in MASTER --- Moose Development/Moose/Functional/Mantis.lua | 657 ++++++++++++------ Moose Development/Moose/Ops/CSAR.lua | 84 +-- Moose Development/Moose/Ops/CTLD.lua | 48 +- 3 files changed, 524 insertions(+), 265 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 18d520750..d88d107de 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -191,7 +191,17 @@ MANTIS = { ShoradLink = false, ShoradTime = 600, ShoradActDistance = 15000, - UseEmOnOff = false, + UseEmOnOff = false, + TimeStamp = 0, + state2flag = false, +} + +--- Advanced state enumerator +-- @type MANTIS.AdvancedState +MANTIS.AdvancedState = { + GREEN = 0, + AMBER = 1, + RED = 2, } ----------------------------------------------------------------------- @@ -263,7 +273,10 @@ do self.ShoradLink = false self.ShoradTime = 600 self.ShoradActDistance = 15000 - -- TODO: add emissions on/off when available .... in 2 weeks + self.TimeStamp = timer.getAbsTime() + self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins + self.state2flag = false + if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false @@ -279,7 +292,7 @@ do end -- Inherit everything from BASE class. - local self = BASE:Inherit(self, BASE:New()) -- #MANTIS + local self = BASE:Inherit(self, FSM:New()) -- #MANTIS -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -310,27 +323,122 @@ do end -- @field #string version - self.version="0.4.2" + self.version="0.5.1" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) - return self - end + --- FSM Functions --- + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- MANTIS status update. + self:AddTransition("*", "Relocating", "*") -- MANTIS HQ and EWR are relocating. + self:AddTransition("*", "GreenState", "*") -- MANTIS A SAM switching to GREEN state. + self:AddTransition("*", "RedState", "*") -- MANTIS A SAM switching to RED state. + self:AddTransition("*", "AdvStateChange", "*") -- MANTIS advanced mode state change. + self:AddTransition("*", "ShoradActivated", "*") -- MANTIS woke up a connected SHORAD. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the MANTIS. Initializes parameters and starts event handlers. + -- @function [parent=#MANTIS] Start + -- @param #MANTIS self + + --- Triggers the FSM event "Start" after a delay. Starts the MANTIS. Initializes parameters and starts event handlers. + -- @function [parent=#MANTIS] __Start + -- @param #MANTIS self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the MANTIS and all its event handlers. + -- @param #MANTIS self + + --- Triggers the FSM event "Stop" after a delay. Stops the MANTIS and all its event handlers. + -- @function [parent=#MANTIS] __Stop + -- @param #MANTIS self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#MANTIS] Status + -- @param #MANTIS self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#MANTIS] __Status + -- @param #MANTIS self + -- @param #number delay Delay in seconds. + + --- On After "Relocating" event. HQ and/or EWR moved. + -- @function [parent=#MANTIS] OnAfterRelocating + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + + --- On After "GreenState" event. A SAM group was switched to GREEN alert. + -- @function [parent=#MANTIS] OnAfterGreenState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + + --- On After "RedState" event. A SAM group was switched to RED alert. + -- @function [parent=#MANTIS] OnAfterRedState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + + --- On After "AdvStateChange" event. Advanced state changed, influencing detection speed. + -- @function [parent=#MANTIS] OnAfterAdvStateChange + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red + -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red + -- @param #number Interval Calculated detection interval based on state and advanced feature setting + -- @return #MANTIS self + + --- On After "ShoradActivated" event. Mantis has activated a SHORAD. + -- @function [parent=#MANTIS] OnAfterShoradActivated + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #string Name Name of the GROUP which SHORAD shall protect + -- @param #number Radius Radius around the named group to find SHORAD groups + -- @param #number Ontime Seconds the SHORAD will stay active + + return self + end ----------------------------------------------------------------------- -- MANTIS helper functions ----------------------------------------------------------------------- - --- [internal] Function to get the self.SAM_Table + --- [Internal] Function to get the self.SAM_Table -- @param #MANTIS self -- @return #table table function MANTIS:_GetSAMTable() + self:T(self.lid .. "GetSAMTable") return self.SAM_Table end - --- [internal] Function to set the self.SAM_Table + --- [Internal] Function to set the self.SAM_Table -- @param #MANTIS self -- @return #MANTIS self function MANTIS:_SetSAMTable(table) + self:T(self.lid .. "SetSAMTable") self.SAM_Table = table return self end @@ -339,41 +447,50 @@ do -- @param #MANTIS self -- @param #number radius Radius upon which detected objects will be grouped function MANTIS:SetEWRGrouping(radius) + self:T(self.lid .. "SetEWRGrouping") local radius = radius or 5000 self.grouping = radius + return self end --- Function to set the detection radius of the EWR in meters -- @param #MANTIS self -- @param #number radius Radius of the EWR detection zone function MANTIS:SetEWRRange(radius) + self:T(self.lid .. "SetEWRRange") local radius = radius or 80000 self.acceptrange = radius + return self end --- Function to set switch-on/off zone for the SAM sites in meters -- @param #MANTIS self -- @param #number radius Radius of the firing zone function MANTIS:SetSAMRadius(radius) + self:T(self.lid .. "SetSAMRadius") local radius = radius or 25000 self.checkradius = radius + return self end --- Function to set SAM firing engage range, 0-100 percent, e.g. 75 -- @param #MANTIS self -- @param #number range Percent of the max fire range function MANTIS:SetSAMRange(range) + self:T(self.lid .. "SetSAMRange") local range = range or 75 if range < 0 or range > 100 then range = 75 end self.engagerange = range + return self end --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night -- @param #MANTIS self -- @param #number range Percent of the max fire range function MANTIS:SetNewSAMRangeWhileRunning(range) + self:T(self.lid .. "SetNewSAMRangeWhileRunning") local range = range or 75 if range < 0 or range > 100 then range = 75 @@ -381,20 +498,32 @@ do self.engagerange = range self:_RefreshSAMTable() self.mysead.EngagementRange = range + return self end --- Function to set switch-on/off the debug state -- @param #MANTIS self -- @param #boolean onoff Set true to switch on function MANTIS:Debug(onoff) + self:T(self.lid .. "SetDebug") local onoff = onoff or false self.debug = onoff + if onoff then + -- Debug trace. + BASE:TraceOn() + BASE:TraceClass("MANTIS") + BASE:TraceLevel(1) + else + BASE:TraceOff() + end + return self end --- Function to get the HQ object for further use -- @param #MANTIS self -- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist function MANTIS:GetCommandCenter() + self:T(self.lid .. "GetCommandCenter") if self.HQ_CC then return self.HQ_CC else @@ -406,26 +535,31 @@ do -- @param #MANTIS self -- @param #string prefix Name of the AWACS group in the mission editor 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 to set AWACS detection range. Defaults to 250.000m (250km) - use **before** starting your Mantis! -- @param #MANTIS self -- @param #number range Detection range of the AWACS group function MANTIS:SetAwacsRange(range) - local range = range or 250000 - self.awacsrange = range + self:T(self.lid .. "SetAwacsRange") + local range = range or 250000 + self.awacsrange = range + return self end --- Function to set the HQ object for further use -- @param #MANTIS self -- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ function MANTIS:SetCommandCenter(group) + self:T(self.lid .. "SetCommandCenter") local group = group or nil if group ~= nil then if type(group) == "string" then @@ -436,14 +570,17 @@ do self.HQ_Template_CC = group:GetName() end end + return self end --- Function to set the detection interval -- @param #MANTIS self -- @param #number interval The interval in seconds function MANTIS:SetDetectInterval(interval) + self:T(self.lid .. "SetDetectInterval") local interval = interval or 30 self.detectinterval = interval + return self end --- Function to set Advanded Mode @@ -453,7 +590,8 @@ do -- @usage Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. -- E.g. `mymantis:SetAdvancedMode(true, 90)` function MANTIS:SetAdvancedMode(onoff, ratio) - self:F({onoff, ratio}) + self:T(self.lid .. "SetAdvancedMode") + self:T({onoff, ratio}) local onoff = onoff or false local ratio = ratio or 100 if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then @@ -461,53 +599,58 @@ do self.advanced = true self.adv_state = 0 self.Adv_EWR_Group = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() - env.info(string.format("***** Starting Advanced Mode MANTIS Version %s *****", self.version)) + 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() - BASE:E(text) + self:E(text) end + return self end --- Set using Emissions on/off instead of changing alarm state -- @param #MANTIS self -- @param #boolean switch Decide if we are changing alarm state or Emission state function MANTIS:SetUsingEmOnOff(switch) + self:T(self.lid .. "SetUsingEmOnOff") self.UseEmOnOff = switch or false + return self end --- [Internal] Function to check if HQ is alive -- @param #MANTIS self -- @return #boolean True if HQ is alive, else false function MANTIS:_CheckHQState() + self:T(self.lid .. "CheckHQState") local text = self.lid.." Checking HQ State" - self:T(text) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end -- start check if self.advanced then local hq = self.HQ_Template_CC local hqgrp = GROUP:FindByName(hq) if hqgrp then if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive - env.info(self.lid.." HQ is alive!") + self:T(self.lid.." HQ is alive!") return true else - env.info(self.lid.." HQ is dead!") + self:T(self.lid.." HQ is dead!") return false end end - end + end + return self end --- [Internal] Function to check if EWR is (at least partially) alive -- @param #MANTIS self -- @return #boolean True if EWR is alive, else false function MANTIS:_CheckEWRState() + self:T(self.lid .. "CheckEWRState") local text = self.lid.." Checking EWR State" - self:F(text) + self:T(text) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end -- start check if self.advanced then local EWR_Group = self.Adv_EWR_Group @@ -521,24 +664,26 @@ do end end end - env.info(self.lid..string.format(" No of EWR alive is %d", nalive)) + self:T(self.lid..string.format(" No of EWR alive is %d", nalive)) if nalive > 0 then return true else return false end - end + end + return self end --- [Internal] Function to determine state of the advanced mode -- @param #MANTIS self -- @return #number Newly calculated interval -- @return #number Previous state for tracking 0, 1, or 2 - function MANTIS:_CheckAdvState() - local text = self.lid.." Checking Advanced State" - self:F(text) + function MANTIS:_CalcAdvState() + self:T(self.lid .. "CalcAdvState") + local text = self.lid.." Calculating Advanced State" + self:T(text) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end -- start check local currstate = self.adv_state -- save curr state for comparison later local EWR_State = self:_CheckEWRState() @@ -557,9 +702,9 @@ do ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6 local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78 local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) - self:F(text) + self:T(text) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end return newinterval, currstate end @@ -568,7 +713,8 @@ do -- @param #boolean hq If true, will relocate HQ object -- @param #boolean ewr If true, will relocate EWR objects function MANTIS:SetAutoRelocate(hq, ewr) - self:F({hq, ewr}) + self:T(self.lid .. "SetAutoRelocate") + self:T({hq, ewr}) local hqrel = hq or false local ewrel = ewr or false if hqrel or ewrel then @@ -576,23 +722,25 @@ do self.autorelocateunits = { HQ = hqrel, EWR = ewrel } self:T({self.autorelocate, self.autorelocateunits}) end + return self end --- [Internal] Function to execute the relocation -- @param #MANTIS self function MANTIS:_RelocateGroups() - self:T(self.lid.." Relocating Groups") + 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 env.info(text) end + if self.verbose then self:I(text) end if self.autorelocate then -- relocate HQ - if self.autorelocateunits.HQ and self.HQ_CC then --only relocate if HQ exists + local HQGroup = self.HQ_CC + if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists local _hqgrp = self.HQ_CC self:T(self.lid.." Relocating HQ") local text = self.lid.." Relocating HQ" - local m= MESSAGE:New(text,10,"MANTIS"):ToAll() - _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) + --local m= MESSAGE:New(text,10,"MANTIS"):ToAll() + _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) end --relocate EWR -- TODO: maybe dependent on AlarmState? Observed: SA11 SR only relocates if no objects in reach @@ -601,26 +749,27 @@ do local EWR_GRP = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP for _,_grp in pairs (EWR_Grps) do - if _grp:IsGround() then + if _grp:IsAlive() and _grp:IsGround() then self:T(self.lid.." Relocating EWR ".._grp:GetName()) local text = self.lid.." Relocating EWR ".._grp:GetName() local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end _grp:RelocateGroundRandomInRadius(20,500,true,true) end end end end + return self end - --- (Internal) Function to check if any object is in the given SAM zone + --- [Internal] Function to check if any object is in the given SAM zone -- @param #MANTIS self -- @param #table dectset Table of coordinates of detected items - -- @param samcoordinate Core.Point#COORDINATE Coordinate object. + -- @param Core.Point#COORDINATE samcoordinate Coordinate object. -- @return #boolean True if in any zone, else false -- @return #number Distance Target distance in meters or zero when no object is in zone function MANTIS:CheckObjectInZone(dectset, samcoordinate) - self:F(self.lid.."CheckObjectInZone Called") + self:T(self.lid.."CheckObjectInZone") -- check if non of the coordinate is in the given defense zone local radius = self.checkradius local set = dectset @@ -632,7 +781,7 @@ do local targetdistance = samcoordinate:DistanceFromPointVec2(coord) local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) - if self.verbose then env.info(self.lid..text) end + if self.verbose then self:I(self.lid..text) end -- end output to cross-check if targetdistance <= radius then return true, targetdistance @@ -641,11 +790,11 @@ do return false, 0 end - --- (Internal) Function to start the detection via EWR groups + --- [Internal] Function to start the detection via EWR groups -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartDetection() - self:F(self.lid.."Starting Detection") + self:T(self.lid.."Starting Detection") -- start detection local groupset = self.EWR_Group @@ -653,14 +802,14 @@ do local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object - _MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones - _MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) - _MANTISdetection:SetAcceptRange(acceptrange) - _MANTISdetection:SetRefreshTimeInterval(interval) - _MANTISdetection:Start() + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object + local MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones + MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) + MANTISdetection:SetAcceptRange(acceptrange) + MANTISdetection:SetRefreshTimeInterval(interval) + MANTISdetection:Start() - function _MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) + function MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false if DetectedItem.IsDetected and debug then @@ -669,14 +818,14 @@ do local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end end - return _MANTISdetection + return MANTISdetection end - --- (Internal) Function to start the detection via AWACS if defined as separate + --- [Internal] Function to start the detection via AWACS if defined as separate -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartAwacsDetection() - self:F(self.lid.."Starting Awacs Detection") + self:T(self.lid.."Starting Awacs Detection") -- start detection local group = self.AWACS_Prefix @@ -685,14 +834,14 @@ do --local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object - _MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones - _MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) - _MANTISAwacs:SetAcceptRange(self.awacsrange) --250km - _MANTISAwacs:SetRefreshTimeInterval(interval) - _MANTISAwacs:Start() + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object + local MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones + MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) + MANTISAwacs:SetAcceptRange(self.awacsrange) --250km + MANTISAwacs:SetRefreshTimeInterval(interval) + MANTISAwacs:Start() - function _MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) + function MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false if DetectedItem.IsDetected and debug then @@ -701,15 +850,15 @@ do local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end end - return _MANTISAwacs + return MANTISAwacs end - --- (Internal) Function to set the SAM start state + --- [Internal] Function to set the SAM start state -- @param #MANTIS self -- @return #MANTIS self function MANTIS:SetSAMStartState() -- DONE: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 - self:F(self.lid.."Setting SAM Start States") + self:T(self.lid.."Setting SAM Start States") -- get SAM Group local SAM_SET = self.SAM_Group local SAM_Grps = SAM_SET.Set --table of objects @@ -742,11 +891,11 @@ do return self end - --- (Internal) Function to update SAM table and SEAD state + --- [Internal] Function to update SAM table and SEAD state -- @param #MANTIS self -- @return #MANTIS self function MANTIS:_RefreshSAMTable() - self:F(self.lid.."Setting SAM Start States") + self:T(self.lid.."RefreshSAMTable") -- Requires SEAD 0.2.2 or better -- get SAM Group local SAM_SET = self.SAM_Group @@ -779,6 +928,7 @@ do -- @param Functional.Shorad#SHORAD Shorad The #SHORAD object -- @param #number Shoradtime Number of seconds #SHORAD stays active post wake-up function MANTIS:AddShorad(Shorad,Shoradtime) + self:T(self.lid.."AddShorad") local Shorad = Shorad or nil local ShoradTime = Shoradtime or 600 local ShoradLink = true @@ -787,184 +937,279 @@ do self.Shorad = Shorad --#SHORAD self.ShoradTime = Shoradtime -- #number end + return self end --- Function to unlink #MANTIS from a #SHORAD installation -- @param #MANTIS self function MANTIS:RemoveShorad() + self:T(self.lid.."RemoveShorad") self.ShoradLink = false + return self end ----------------------------------------------------------------------- -- MANTIS main functions ----------------------------------------------------------------------- - --- Function to set the SAM start state + --- [Internal] Check detection function + -- @param #MANTIS self + -- @param Functional.Detection#DETECTION_AREAS detection Detection object + -- @return #MANTIS self + function MANTIS:_Check(detection) + self:T(self.lid .. "Check") + --get detected set + local detset = detection:GetDetectedItemCoordinates() + self:T("Check:", {detset}) + -- randomly update SAM Table + local rand = math.random(1,100) + if rand > 65 then -- 1/3 of cases + self:_RefreshSAMTable() + end + -- switch SAMs on/off if (n)one of the detected groups is inside their reach + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates + for _,_data in pairs (samset) do + local samcoordinate = _data[2] + local name = _data[1] + local samgroup = GROUP:FindByName(name) + local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) + if IsInZone then --check any target in zone + if samgroup:IsAlive() then + -- switch on SAM + if self.UseEmOnOff then + -- TODO: add emissions on/off + --samgroup:SetAIOn() + samgroup:EnableEmission(true) + end + samgroup:OptionAlarmStateRed() + self:__RedState(1,samgroup) + -- link in to SHORAD if available + -- DONE: Test integration fully + if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early + local Shorad = self.Shorad + local radius = self.checkradius + local ontime = self.ShoradTime + Shorad:WakeUpShorad(name, radius, ontime) + self:__ShoradActivated(1,name, radius, ontime) + end + -- debug output + local text = string.format("SAM %s switched to alarm state RED!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end --end alive + else + if samgroup:IsAlive() then + -- switch off SAM + if self.UseEmOnOff then + -- TODO: add emissions on/off + samgroup:EnableEmission(false) + self:__GreenState(1,samgroup) + --samgroup:SetAIOff() + else + samgroup:OptionAlarmStateGreen() + self:__GreenState(1,samgroup) + end + --samgroup:OptionROEWeaponFree() + --samgroup:SetAIOn() + local text = string.format("SAM %s switched to alarm state GREEN!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end --end alive + end --end check + end --for for loop + return self + end + + --- [Internal] Relocation relay function -- @param #MANTIS self -- @return #MANTIS self - function MANTIS:Start() - self:F(self.lid.."Starting MANTIS") - self:SetSAMStartState() - self.Detection = self:StartDetection() - if self.advAwacs then - self.AWACS_Detection = self:StartAwacsDetection() - end - -- detection function - local function check(detection) - --get detected set - local detset = detection:GetDetectedItemCoordinates() - self:F("Check:", {detset}) - -- randomly update SAM Table - local rand = math.random(1,100) - if rand > 65 then -- 1/3 of cases - self:_RefreshSAMTable() - end - -- switch SAMs on/off if (n)one of the detected groups is inside their reach - local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates - for _,_data in pairs (samset) do - local samcoordinate = _data[2] - local name = _data[1] - local samgroup = GROUP:FindByName(name) - local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) - if IsInZone then --check any target in zone + function MANTIS:_Relocate() + self:T(self.lid .. "Relocate") + self:_RelocateGroups() + return self + end + + --- [Internal] Check advanced state + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:_CheckAdvState() + self:T(self.lid .. "CheckAdvSate") + local interval, oldstate = self:_CalcAdvState() + local newstate = self.adv_state + if newstate ~= oldstate then + -- deal with new state + self:__AdvStateChange(1,oldstate,newstate,interval) + if newstate == 2 then + -- switch alarm state RED + self.state2flag = true + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates + for _,_data in pairs (samset) do + local name = _data[1] + local samgroup = GROUP:FindByName(name) if samgroup:IsAlive() then - -- switch on SAM if self.UseEmOnOff then -- TODO: add emissions on/off --samgroup:SetAIOn() samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() - -- link in to SHORAD if available - -- DONE: Test integration fully - if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early - local Shorad = self.Shorad - local radius = self.checkradius - local ontime = self.ShoradTime - Shorad:WakeUpShorad(name, radius, ontime) - end - -- debug output - local text = string.format("SAM %s switched to alarm state RED!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(self.lid..text) end - end --end alive - else - if samgroup:IsAlive() then - -- switch off SAM - if self.UseEmOnOff then - -- TODO: add emissions on/off - samgroup:EnableEmission(false) - --samgroup:SetAIOff() - else - samgroup:OptionAlarmStateGreen() - end - --samgroup:OptionROEWeaponFree() - --samgroup:SetAIOn() - local text = string.format("SAM %s switched to alarm state GREEN!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(self.lid..text) end - end --end alive - end --end check - end --for for loop - end --end function - -- relocation relay function - local function relocate() - self:_RelocateGroups() - end - -- check advanced state - local function checkadvstate() - local interval, oldstate = self:_CheckAdvState() - local newstate = self.adv_state - if newstate ~= oldstate then - -- deal with new state - if newstate == 2 then - -- switch alarm state RED - if self.MantisTimer.isrunning then - self.MantisTimer:Stop() - self.MantisTimer.isrunning = false - end -- stop Awacs timer - if self.MantisATimer.isrunning then - self.MantisATimer:Stop() - self.MantisATimer.isrunning = false - end -- stop timer - local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates - for _,_data in pairs (samset) do - local name = _data[1] - local samgroup = GROUP:FindByName(name) - if samgroup:IsAlive() then - if self.UseEmOnOff then - -- TODO: add emissions on/off - --samgroup:SetAIOn() - samgroup:EnableEmission(true) - end - samgroup:OptionAlarmStateRed() - end -- end alive - end -- end for loop - elseif newstate <= 1 then - -- change MantisTimer to slow down or speed up - if self.MantisTimer.isrunning then - self.MantisTimer:Stop() - self.MantisTimer.isrunning = false - end - if self.MantisATimer.isrunning then - self.MantisATimer:Stop() - self.MantisATimer.isrunning = false - end - self.MantisTimer = TIMER:New(check,self.Detection) - self.MantisTimer:Start(5,interval,nil) - self.MantisTimer.isrunning = true - if self.advAwacs then - self.MantisATimer = TIMER:New(check,self.AWACS_Detection) - self.MantisATimer:Start(15,interval,nil) - self.MantisATimer.isrunning = true - end - end - end -- end newstate vs oldstate - end - -- timers to run the system - local interval = self.detectinterval - self.MantisTimer = TIMER:New(check,self.Detection) - self.MantisTimer:Start(5,interval,nil) - self.MantisTimer.isrunning = true - -- Awacs timer + end -- end alive + end -- end for loop + elseif newstate <= 1 then + -- change MantisTimer to slow down or speed up + self.detectinterval = interval + self.state2flag = false + end + end -- end newstate vs oldstate + return self + end + + --- [Internal] Function to set start state + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterStart(From, Event, To) + self:T({From, Event, To}) + self:T(self.lid.."Starting MANTIS") + self:SetSAMStartState() + self.Detection = self:StartDetection() if self.advAwacs then - self.MantisATimer = TIMER:New(check,self.AWACS_Detection) - self.MantisATimer:Start(15,interval,nil) - self.MantisATimer.isrunning = true - end - -- timer to relocate HQ and EWR - if self.autorelocate then - local relointerval = math.random(1800,3600) -- random between 30 and 60 mins - self.MantisReloTimer = TIMER:New(relocate) - self.MantisReloTimer:Start(relointerval,relointerval,nil) - end - -- timer for advanced state check - if self.advanced then - self.MantisAdvTimer = TIMER:New(checkadvstate) - self.MantisAdvTimer:Start(30,interval*5,nil) + self.AWACS_Detection = self:StartAwacsDetection() end + self:__Status(self.detectinterval) return self end - --- Function to stop MANTIS + --- [Internal] Before status function for MANTIS -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State -- @return #MANTIS self - function MANTIS:Stop() - if self.MantisTimer.isrunning then - self.MantisTimer:Stop() + function MANTIS:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + -- check detection + if not self.state2flag then + self:_Check(self.Detection) end - if self.MantisATimer.isrunning then - self.MantisATimer:Stop() + + -- check Awacs + if self.advAwacs and not self.state2flag then + self:_Check(self.AWACS_Detection) end + + -- relocate HQ and EWR if self.autorelocate then - self.MantisReloTimer:Stop() + local relointerval = self.relointerval + local thistime = timer.getAbsTime() + local timepassed = thistime - self.TimeStamp + + local halfintv = math.floor(timepassed / relointerval) + + --self:T({timepassed=timepassed, halfintv=halfintv}) + + if halfintv >= 1 then + self.TimeStamp = timer.getAbsTime() + self:_Relocate() + self:__Relocating(1) + end end + + -- timer for advanced state check if self.advanced then - self.MantisAdvTimer:Stop() + self:_CheckAdvState() end - return self + + return self + end + + --- [Internal] Status function for MANTIS + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterStatus(From,Event,To) + self:T({From, Event, To}) + local interval = self.detectinterval * -1 + self:__Status(interval) + return self end + --- [Internal] Function to stop MANTIS + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterStop(From, Event, To) + self:T({From, Event, To}) + return self + end + + --- [Internal] Function triggered by Event Relocating + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterRelocating(From, Event, To) + self:T({From, Event, To}) + return self + end + + --- [Internal] Function triggered by Event GreenState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + function MANTIS:onafterGreenState(From, Event, To, Group) + self:T({From, Event, To, Group}) + return self + end + + --- [Internal] Function triggered by Event RedState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + function MANTIS:onafterRedState(From, Event, To, Group) + self:T({From, Event, To, Group}) + return self + end + + --- [Internal] Function triggered by Event AdvStateChange + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red + -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red + -- @param #number Interval Calculated detection interval based on state and advanced feature setting + -- @return #MANTIS self + function MANTIS:onafterAdvStateChange(From, Event, To, Oldstate, Newstate, Interval) + self:T({From, Event, To, Oldstate, Newstate, Interval}) + return self + end + + --- [Internal] Function triggered by Event ShoradActivated + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #string Name Name of the GROUP which SHORAD shall protect + -- @param #number Radius Radius around the named group to find SHORAD groups + -- @param #number Ontime Seconds the SHORAD will stay active + function MANTIS:onafterShoradActivated(From, Event, To, Name, Radius, Ontime) + self:T({From, Event, To, Name, Radius, Ontime}) + return self + end end ----------------------------------------------------------------------- -- MANTIS end diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 0a3d14c64..49a357e34 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -609,7 +609,8 @@ end -- @param #number _freq Frequency -- @param #boolean noMessage -- @param #string _description Description -function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) +-- @param #boolean forcedesc Use the description only for the pilot track entry +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}) @@ -622,11 +623,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) - local _typeName = _typeName or "PoW" + local _typeName = _typeName or "Pilot" if not noMessage then self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) - --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) end if _freq then @@ -635,15 +635,14 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:_AddSpecialOptions(_spawnedGroup) - local _text = " " - if _playerName ~= nil then - _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName - elseif _typeName ~= nil then - _text = "AI Pilot of " .. _unitName .. " - " .. _typeName - else - _text = _description - end - + local _text = _description + if not forcedesc then + if _playerName ~= nil then + _text = "Pilot " .. _playerName + elseif _unitName ~= nil then + _text = "AI Pilot of " .. _unitName + end + end self:T({_spawnedGroup, _alias}) local _GroupName = _spawnedGroup:GetName() or _alias @@ -662,7 +661,10 @@ end -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. -- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. -function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) +-- @param #string unitname (optional) Name of the lost unit. +-- @param #string typename (optional) Type of plane. +-- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position @@ -671,7 +673,9 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ return end - local _description = _description or "Unknown" + local _description = _description or "PoW" + local unitname = unitname or "Old Rusty" + local typename = typename or "Phantom II" local pos = {} if _randomPoint then @@ -690,7 +694,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ _country = country.id.UN_PEACEKEEPERS end - self:_AddCsar(_coalition, _country, pos, "PoW", _description, nil, freq, _nomessage, _description) + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc) return self end @@ -702,12 +706,15 @@ end -- @param #string Description (optional) Description. -- @param #boolean RandomPoint (optional) Random yes or no. -- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. --- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so: +-- @param #string Unitname (optional) Name of the lost unit. +-- @param #string Typename (optional) Type of plane. +-- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. +-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition --- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) - self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" ) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) return self end @@ -1199,7 +1206,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _time <= 0 or _distance < self.loadDistance then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, bugger!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true else self.landedStatus[_lookupKeyHeli] = nil @@ -1211,7 +1218,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG else if (_distance < self.loadDistance) then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, honk!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true else self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1252,7 +1259,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG 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 if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, noob!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true else self.hoverStatus[_lookupKeyHeli] = nil @@ -1377,7 +1384,7 @@ end --- (Internal) Check and return Wrappe.Unit#UNIT based on the name if alive. -- @param #CSAR self -- @param #string _unitname Name of Unit --- @return #UNIT or nil +-- @return Wrapper.Unit#UNIT The unit or nil function CSAR:_GetSARHeli(_unitName) self:T(self.lid .. " _GetSARHeli") local unit = UNIT:FindByName(_unitName) @@ -1428,12 +1435,8 @@ function CSAR:_GetPositionOfWounded(_woundedGroup) _coordinatesText = _coordinate:ToStringLLDMS() elseif self.coordtype == 2 then -- MGRS _coordinatesText = _coordinate:ToStringMGRS() - elseif self.coordtype == 3 then -- Bullseye Imperial - local Settings = _SETTINGS:SetImperial() - _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) - else -- Bullseye Metric --(medevac.coordtype == 4) - local Settings = _SETTINGS:SetMetric() - _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + else -- Bullseye Metric --(medevac.coordtype == 4 or 3) + _coordinatesText = _coordinate:ToStringBULLS(self.coalition) end end return _coordinatesText @@ -1467,12 +1470,11 @@ function CSAR:_DisplayActiveSAR(_unitName) local _woundcoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_helicoord, _woundcoord) self:T({_distance = _distance}) - -- change distance to miles if self.coordtype < 4 local distancetext = "" - if self.coordtype < 4 then - distancetext = string.format("%.3fnm",UTILS.MetersToNM(_distance)) + if _SETTINGS:IsImperial() then + distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) else - distancetext = string.format("%.3fkm", _distance/1000.0) + distancetext = string.format("%.1fkm", _distance/1000.0) end table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) end @@ -1542,10 +1544,10 @@ function CSAR:_SignalFlare(_unitName) local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 - if self.coordtype < 4 then - _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) else - _distance = string.format("%.3fkm",_closest.distance) + _distance = string.format("%.1fkm",_closest.distance) end local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) @@ -1554,7 +1556,7 @@ function CSAR:_SignalFlare(_unitName) _coord:FlareRed(_clockDir) else local disttext = "4.3nm" - if self.coordtype == 4 then + if _SETTINGS:IsMetric() then disttext = "8km" end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) @@ -1593,10 +1595,10 @@ function CSAR:_Reqsmoke( _unitName ) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 - if self.coordtype < 4 then - _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) else - _distance = string.format("%.3fkm",_closest.distance) + _distance = string.format("%.1fkm",_closest.distance) end local _msg = string.format("%s - Popping signal smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) @@ -1605,7 +1607,7 @@ function CSAR:_Reqsmoke( _unitName ) _coord:Smoke(color) else local disttext = "4.3nm" - if self.coordtype == 4 then + if _SETTINGS:IsMetric() then disttext = "8km" end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4c9adf68d..8db791730 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -81,8 +81,8 @@ CTLD_CARGO = { self.Name = Name or "none" -- #string self.Templates = Templates or {} -- #table self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum - self.HasBeenMoved = HasBeenMoved or false -- #booolean - self.LoadDirectly = LoadDirectly or false -- #booolean + self.HasBeenMoved = HasBeenMoved or false -- #boolean + self.LoadDirectly = LoadDirectly or false -- #boolean self.CratesNeeded = CratesNeeded or 0 -- #number self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE self.HasBeenDropped = Dropped or false --#boolean @@ -410,6 +410,7 @@ do -- my_ctld.enableHercules = true -- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft -- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- my_ctld.HercMaxSpeed = 77 -- 77mps or 270 kph or 150 kn -- -- Also, the following options need to be set to `true`: -- @@ -478,7 +479,7 @@ CTLD = { -- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon --- Zone Type Info. --- @type CTLD. +-- @type CTLD.CargoZoneType CTLD.CargoZoneType = { LOAD = "load", DROP = "drop", @@ -651,6 +652,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.enableHercules = false self.HercMinAngels = 165 -- for troop/cargo drop via chute self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps -- message suppression self.suppressmessages = false @@ -803,16 +805,10 @@ function CTLD:_GenerateUHFrequencies() end --- (Internal) Function to generate valid FM Frequencies --- @param #CTLD sel +-- @param #CTLD self function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} - local _start = 220000000 - - while _start < 399000000 do - - _start = _start + 500000 - end for _first = 3, 7 do for _second = 0, 5 do @@ -2289,10 +2285,12 @@ end local aheight = uheight - gheight -- height above ground local maxh = self.HercMinAngels-- 1500m local minh = self.HercMaxAngels -- 5000m - local mspeed = 2 -- 2 m/s - -- TODO:Add speed test for Herc, should not be above 280kph/150kn - --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) - if (aheight <= maxh) and (aheight >= minh) then + local maxspeed = self.HercMaxSpeed -- 77 mps + -- TODO: TEST - Speed test for Herc, should not be above 280kph/150kn + 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 -- yep within parameters outcome = true end @@ -2308,7 +2306,14 @@ end local inhover = self:IsCorrectHover(Unit) local htxt = "true" if not inhover then htxt = "false" end - local 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) + 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 %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) + end self:_SendMessage(text, 10, false, Group) --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self @@ -2322,9 +2327,16 @@ end local inhover = self:IsCorrectFlightParameters(Unit) local htxt = "true" if not inhover then htxt = "false" end - local minheight = UTILS.MetersToFeet(self.HercMinAngels) - local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) - local text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + local text = "" + if _SETTINGS:IsImperial() then + local minheight = UTILS.MetersToFeet(self.HercMinAngels) + local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + 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.HercMinAngels + local maxheight = self.HercMaxAngels + 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) --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) return self From bbfc2e9ed705adcd48e8a0b6b881d10b2755b092 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 14 Jul 2021 08:44:55 +0200 Subject: [PATCH 07/68] Added beacon frequency and laser code generator functions --- Moose Development/Moose/Utilities/Utils.lua | 147 +++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 943796075..fae53bb17 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1563,4 +1563,149 @@ function UTILS.IsLoadingDoorOpen( unit_name ) end -- nil return nil -end \ No newline at end of file +end + +--- Function to generate valid FM frequencies in mHz for radio beacons (FM). +-- @return #table Table of frequencies. +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 --extra 0 because we didnt bother with 4th digit + table.insert(FreeFMFrequencies, _frequency) + end + end + end + return FreeFMFrequencies +end + +--- Function to generate valid VHF frequencies in kHz for radio beacons (FM). +-- @return #table VHFrequencies +function UTILS.GenerateVHFrequencies() + + -- known and sorted map-wise NDBs in kHz + local _skipFrequencies = { + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580, + 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 + } + + local FreeVHFFrequencies = {} + + -- first range + local _start = 200000 + while _start < 400000 do + + -- skip existing NDB frequencies# + 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 + + -- second range + _start = 400000 + while _start < 850000 do + -- skip existing NDB frequencies + 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 + + -- third range + _start = 850000 + while _start <= 999000 do -- adjusted for Gazelle + -- skip existing NDB frequencies + 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 to generate valid UHF Frequencies in mHz (AM). +-- @return #table UHF Frequencies +function UTILS.GenerateUHFrequencies() + + local FreeUHFFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + table.insert(FreeUHFFrequencies, _start) + _start = _start + 500000 + end + + return FreeUHFFrequencies +end + +--- Function to generate valid laser codes for JTAC. +-- @return #table Laser Codes. +function UTILS.GenerateLaserCodes() + local jtacGeneratedLaserCodes = {} + + -- helper function + 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 + + -- generate list of laser codes + local _code = 1111 + local _count = 1 + while _code < 1777 and _count < 30 do + while true do + _code = _code + 1 + if not self:_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 From b4707bb3ebdd28d8e96d98b39005b23904598839 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 14 Jul 2021 15:37:47 +0200 Subject: [PATCH 08/68] Update Mantis.lua (#1568) * Update Mantis.lua Added state tracker so that Red/Green Events only get triggered when a state actually changes. * Update Mantis.lua --- Moose Development/Moose/Functional/Mantis.lua | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index d88d107de..1186378e1 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -59,7 +59,7 @@ -- @extends Core.Base#BASE ---- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat +--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat -- -- Simple Class for a more intelligent Air Defense System -- @@ -194,6 +194,7 @@ MANTIS = { UseEmOnOff = false, TimeStamp = 0, state2flag = false, + SamStateTracker = {}, } --- Advanced state enumerator @@ -276,6 +277,7 @@ do self.TimeStamp = timer.getAbsTime() self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins self.state2flag = false + self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode if EmOnOff then if EmOnOff == false then @@ -323,7 +325,7 @@ do end -- @field #string version - self.version="0.5.1" + self.version="0.5.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -867,7 +869,7 @@ do local engagerange = self.engagerange -- firing range in % of max --cycle through groups and set alarm state etc for _i,_group in pairs (SAM_Grps) do - local group = _group + local group = _group -- Wrapper.Group#GROUP -- TODO: add emissions on/off if self.UseEmOnOff then group:EnableEmission(false) @@ -876,11 +878,12 @@ do group:OptionAlarmStateGreen() -- AI off end group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --default engagement will be 75% of firing range - if group:IsGround() then + if group:IsGround() and group:IsAlive() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() table.insert( SAM_Tbl, {grpname, grpcoord}) table.insert( SEAD_Grps, grpname ) + self.SamStateTracker[grpname] = "GREEN" end end self.SAM_Table = SAM_Tbl @@ -907,7 +910,7 @@ do for _i,_group in pairs (SAM_Grps) do local group = _group group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --engagement will be 75% of firing range - if group:IsGround() then + if group:IsGround() and group:IsAlive() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() table.insert( SAM_Tbl, {grpname, grpcoord}) -- make the table lighter, as I don't really use the zone here @@ -982,7 +985,10 @@ do samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() - self:__RedState(1,samgroup) + if self.SamStateTracker[name] ~= "RED" then + self:__RedState(1,samgroup) + self.SamStateTracker[name] = "RED" + end -- link in to SHORAD if available -- DONE: Test integration fully if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early @@ -1001,16 +1007,13 @@ do if samgroup:IsAlive() then -- switch off SAM if self.UseEmOnOff then - -- TODO: add emissions on/off samgroup:EnableEmission(false) - self:__GreenState(1,samgroup) - --samgroup:SetAIOff() - else - samgroup:OptionAlarmStateGreen() - self:__GreenState(1,samgroup) end - --samgroup:OptionROEWeaponFree() - --samgroup:SetAIOn() + samgroup:OptionAlarmStateGreen() + if self.SamStateTracker[name] ~= "GREEN" then + self:__GreenState(1,samgroup) + self.SamStateTracker[name] = "GREEN" + end local text = string.format("SAM %s switched to alarm state GREEN!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(self.lid..text) end From 0db35a0e9fa575f757dbbb59aa62540afad04a14 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 16 Jul 2021 14:00:05 +0200 Subject: [PATCH 09/68] Updated according to develop version --- Moose Development/Moose/Ops/CSAR.lua | 107 +++------------ Moose Development/Moose/Ops/CTLD.lua | 186 +++++---------------------- 2 files changed, 50 insertions(+), 243 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 49a357e34..c18f32d0e 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -212,24 +212,7 @@ CSAR = { -- @field #string player Player name if applicable. -- @field Wrapper.Group#GROUP group Spawned group object. -- @field #number timestamp Timestamp for approach process - ---- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. --- @field #CSAR.SkipFrequencies -CSAR.SkipFrequencies = { - 214,274,291.5,295,297.5, - 300.5,304,307,309.5,311,312,312.5,316, - 320,324,328,329,330,336,337, - 342,343,348,351,352,353,358, - 363,365,368,372.5,374, - 380,381,384,389,395,396, - 414,420,430,432,435,440,450,455,462,470,485, - 507,515,520,525,528,540,550,560,570,577,580,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 - } - + --- All slot / Limit settings -- @type CSAR.AircraftType -- @field #string typename Unit type name. @@ -245,7 +228,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.8r1" +CSAR.version="0.1.8r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -872,7 +855,11 @@ function CSAR:_EventHandler(EventData) end if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then - self:_RescuePilots(_unit) + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_event.IniUnitName) then + self:_DisplayMessageToSAR(_unit, "Open the door to let me out!", self.messageTime, true) + else + self:_RescuePilots(_unit) + end else self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) end @@ -1113,27 +1100,27 @@ function CSAR:_IsLoadingDoorOpen( unit_name ) local type_name = unit:getTypeName() if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then - self:I(unit_name .. " Cargo doors are open or cargo door not present") + self:T(unit_name .. " Cargo doors are open or cargo door not present") ret_val = true end if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then - self:I(unit_name .. " a side door is open") + self:T(unit_name .. " a side door is open") ret_val = true end if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then - self:I(unit_name .. " a side door is open ") + self:T(unit_name .. " a side door is open ") ret_val = true end if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then - self:I(unit_name .. " front door(s) are open") + self:T(unit_name .. " front door(s) are open") ret_val = true end if ret_val == false then - self:I(unit_name .. " all doors are closed") + self:T(unit_name .. " all doors are closed") end return ret_val @@ -1346,8 +1333,12 @@ function CSAR:_ScheduledSARFlight(heliname,groupname) end if _dist < 200 and _heliUnit:InAir() == false then + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(heliname) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true) + else self:_RescuePilots(_heliUnit) return + end end --queue up @@ -1754,71 +1745,9 @@ end --- (Internal) Populate table with available beacon frequencies. -- @param #CSAR self function CSAR:_GenerateVHFrequencies() - self:T(self.lid .. " _GenerateVHFrequencies") - local _skipFrequencies = self.SkipFrequencies - + self:T(self.lid .. " _GenerateVHFrequencies") local FreeVHFFrequencies = {} - local UsedVHFFrequencies = {} - - -- first range - local _start = 200000 - while _start < 400000 do - - -- skip existing NDB frequencies - 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 - - -- second range - _start = 400000 - while _start < 850000 do - - -- skip existing NDB frequencies - 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 - - -- third range - _start = 850000 - while _start <= 999000 do -- updated for Gazelle - - -- skip existing NDB frequencies - 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 + FreeVHFFrequencies = UTILS.GenerateVHFrequencies() self.FreeVHFFrequencies = FreeVHFFrequencies return self end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 8db791730..d5bd6f962 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -262,6 +262,7 @@ do -- -- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. -- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. +-- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. -- my_ctld.forcehoverload = true -- Crates (not: troops) can only be loaded while hovering. @@ -515,26 +516,9 @@ CTLD.UnitTypes = { ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers } ---- Updated and sorted known NDB beacons (in kHz!) from the available maps --- @field #CTLD.SkipFrequencies -CTLD.SkipFrequencies = { - 214,274,291.5,295,297.5, - 300.5,304,307,309.5,311,312,312.5,316, - 320,324,328,329,330,336,337, - 342,343,348,351,352,353,358, - 363,365,368,372.5,374, - 380,381,384,389,395,396, - 414,420,430,432,435,440,450,455,462,470,485, - 507,515,520,525,528,540,550,560,570,577,580,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 - } - --- CTLD class version. -- @field #string version -CTLD.version="0.1.3r2" +CTLD.version="0.1.4r1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -643,6 +627,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.minimumHoverHeight = 4 self.forcehoverload = true self.hoverautoloading = true + self.dropcratesanywhere = false -- #1570 self.smokedistance = 2000 self.movetroopstowpzone = true @@ -794,13 +779,7 @@ end function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") self.FreeUHFFrequencies = {} - local _start = 220000000 - - while _start < 399000000 do - table.insert(self.FreeUHFFrequencies, _start) - _start = _start + 500000 - end - + self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() return self end @@ -809,16 +788,7 @@ end function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} - - for _first = 3, 7 do - for _second = 0, 5 do - for _third = 0, 9 do - local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit - table.insert(self.FreeFMFrequencies, _frequency) - end - end - end - + self.FreeFMFrequencies = UTILS.GenerateFMFrequencies() return self end @@ -826,106 +796,13 @@ end -- @param #CTLD self function CTLD:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") - local _skipFrequencies = self.SkipFrequencies - - self.FreeVHFFrequencies = {} - self.UsedVHFFrequencies = {} - - -- first range - local _start = 200000 - while _start < 400000 do - -- skip existing NDB frequencies# - 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(self.FreeVHFFrequencies, _start) - end - _start = _start + 10000 - end - - -- second range - _start = 400000 - while _start < 850000 do - -- skip existing NDB frequencies - 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(self.FreeVHFFrequencies, _start) - end - _start = _start + 10000 - end - - -- third range - _start = 850000 - while _start <= 999000 do -- adjusted for Gazelle - -- skip existing NDB frequencies - 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(self.FreeVHFFrequencies, _start) - end - _start = _start + 50000 - end - + self.FreeVHFFrequencies = {} + self.UsedVHFFrequencies = {} + self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() return self end ---- (Internal) Function to generate valid laser codes. --- @param #CTLD self -function CTLD:_GenerateLaserCodes() - self:T(self.lid .. " _GenerateLaserCodes") - self.jtacGeneratedLaserCodes = {} - -- generate list of laser codes - local _code = 1111 - local _count = 1 - while _code < 1777 and _count < 30 do - while true do - _code = _code + 1 - if not self:_ContainsDigit(_code, 8) - and not self:_ContainsDigit(_code, 9) - and not self:_ContainsDigit(_code, 0) then - table.insert(self.jtacGeneratedLaserCodes, _code) - break - end - end - _count = _count + 1 - end -end - ---- (Internal) Helper function to generate laser codes. --- @param #CTLD self --- @param #number _number --- @param #number _numberToFind -function CTLD:_ContainsDigit(_number, _numberToFind) - self:T(self.lid .. " _ContainsDigit") - 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 - --- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData @@ -1056,24 +933,26 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) if not drop then inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) else - inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if self.dropcratesanywhere then -- #1570 + inzone = true + else + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + end end if not inzone then self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) - --local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end - + -- avoid crate spam local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 30 local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) - --local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) + return self end -- spawn crates in front of helicopter @@ -1082,7 +961,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number local cratename = cargotype:GetName() - --self:Tself.lid .. string.format("Crate %s requested", cratename)) local cratetemplate = "Container"-- #string -- get position and heading of heli local position = Unit:GetCoordinate() @@ -1130,7 +1008,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:__CratesDropped(1, Group, Unit, droppedcargo) end self:_SendMessage(text, 10, false, Group) - --local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) return self end @@ -1149,7 +1026,6 @@ function CTLD:_ListCratesNearby( _group, _unit) for _,_entry in pairs (crates) do local entry = _entry -- #CTLD_CARGO local name = entry:GetName() --#string - -- TODO Meaningful sorting/aggregation local dropped = entry:WasDropped() if dropped then text:Add(string.format("Dropped crate for %s",name)) @@ -1158,14 +1034,12 @@ function CTLD:_ListCratesNearby( _group, _unit) end end if text:GetCount() == 1 then - text:Add("--------- N O N E ------------") + text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(), 30, true, _group) - --local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) - --local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) end return self end @@ -1347,7 +1221,7 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) report:Add("------------------------------------------------------------") - report:Add("-- TROOPS --") + report:Add(" -- TROOPS --") for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum @@ -1356,10 +1230,10 @@ function CTLD:_ListCargo(Group, Unit) end end if report:GetCount() == 4 then - report:Add("--------- N O N E ------------") + report:Add(" N O N E") end report:Add("------------------------------------------------------------") - report:Add("-- CRATES --") + report:Add(" -- CRATES --") local cratecount = 0 for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO @@ -1370,7 +1244,7 @@ function CTLD:_ListCargo(Group, Unit) end end if cratecount == 0 then - report:Add("--------- N O N E ------------") + report:Add(" N O N E") end report:Add("------------------------------------------------------------") local text = report:Text() @@ -1498,13 +1372,16 @@ end -- @param Wrappe.Unit#UNIT Unit function CTLD:_UnloadCrates(Group, Unit) self:T(self.lid .. " _UnloadCrates") - -- check if we are in DROP zone - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) - if not inzone then - self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) - if not self.debug then - return self + + if not self.dropcratesanywhere then -- #1570 + -- check if we are in DROP zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then + return self + end end end -- check for hover unload @@ -2058,7 +1935,7 @@ function CTLD:_ListRadioBeacons(Group, Unit) end end if report:GetCount() == 1 then - report:Add("--------- N O N E ------------") + report:Add(" N O N E") end report:Add("------------------------------------------------------------") self:_SendMessage(report:Text(), 30, true, Group) @@ -2431,6 +2308,7 @@ end -- @return #CTLD self function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) + self:I(self.lid .. "Started.") if self.useprefix or self.enableHercules then local prefix = self.prefixes --self:T{prefix=prefix}) From 8cc1c24b64123991d4ce6fe38100c45520eef62c Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 17 Jul 2021 15:58:11 +0200 Subject: [PATCH 10/68] Update CSAR.lua Changes as per development version. Taking care of dead pilots correctly, added FSM event KIA --- Moose Development/Moose/Ops/CSAR.lua | 141 +++++++++++++++------------ 1 file changed, 78 insertions(+), 63 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index c18f32d0e..df419a5b9 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1,3 +1,4 @@ + --- **Ops** -- Combat Search and Rescue. -- -- === @@ -212,6 +213,27 @@ CSAR = { -- @field #string player Player name if applicable. -- @field Wrapper.Group#GROUP group Spawned group object. -- @field #number timestamp Timestamp for approach process +-- @field #boolean alive Group is alive or dead/rescued +-- +--- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. + +--[[ Moved to Utils +-- @field #CSAR.SkipFrequencies +CSAR.SkipFrequencies = { + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580,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 + } +--]] --- All slot / Limit settings -- @type CSAR.AircraftType @@ -222,13 +244,14 @@ 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-8MTV2"] = 12 +CSAR.AircraftType["Mi-8MT"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.8r2" +CSAR.version="0.1.8r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -300,6 +323,7 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "Boarded", "*") -- Pilot boarded. self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. + self:AddTransition("*", "KIA", "*") -- Pilot killed in action. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables, mainly for tracking actions @@ -441,6 +465,14 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string HeliName Name of the helicopter group. -- @param #number PilotsSaved Number of the saved pilots on board when landing. + --- On After "KIA" event. Pilot is dead. + -- @function [parent=#CSAR] OnAfterKIA + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Pilotname Name of the pilot KIA. + return self end @@ -474,6 +506,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.typename = Typename or "" DownedPilot.group = Group DownedPilot.timestamp = 0 + DownedPilot.alive = true -- Add Pilot local PilotTable = self.downedPilots @@ -910,7 +943,7 @@ function CSAR:_CheckNameInDownedPilots(name) local found = false local table = nil for _,_pilot in pairs(PilotTable) do - if _pilot.name == name then + if _pilot.name == name and _pilot.alive == true then found = true table = _pilot break @@ -927,25 +960,10 @@ end function CSAR:_RemoveNameFromDownedPilots(name,force) local PilotTable = self.downedPilots --#CSAR.DownedPilot local found = false - for _,_pilot in pairs(PilotTable) do + for _index,_pilot in pairs(PilotTable) do if _pilot.name == name then - local group = _pilot.group -- Wrapper.Group#GROUP - if group then - if (not group:IsAlive()) or ( force == true) then -- don\'t delete groups which still exist - found = true - _pilot.desc = nil - _pilot.frequency = nil - _pilot.index = nil - _pilot.name = nil - _pilot.originalUnit = nil - _pilot.player = nil - _pilot.side = nil - _pilot.typename = nil - _pilot.group = nil - _pilot.timestamp = nil - end + self.downedPilots[_index].alive = false end - end end return found end @@ -968,7 +986,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end local _woundedGroup = _downedpilot.group - if _woundedGroup ~= nil then + if _woundedGroup ~= nil and _woundedGroup:IsAlive() then local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking @@ -981,7 +999,6 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) return end - --if self:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) @@ -999,7 +1016,10 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end else self:T("...Downed Pilot KIA?!") - self:_RemoveNameFromDownedPilots(_downedpilot.name) + if not _downedpilot.alive then + --self:__KIA(1,_downedpilot.name) + self:_RemoveNameFromDownedPilots(_downedpilot.name, true) + end end return self end @@ -1275,36 +1295,6 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end end ---- (Internal) Check if group not KIA. --- @param #CSAR self --- @param Wrapper.Group#GROUP _woundedGroup --- @param #string _woundedGroupName --- @param Wrapper.Unit#UNIT _heliUnit --- @param #string _heliName --- @return #boolean Outcome -function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) - self:T(self.lid .. " _CheckGroupNotKIA") - -- check if unit has died or been picked up - local inTransit = false - if _woundedGroup and _heliUnit then - for _currentHeli, _groups in pairs(self.inTransitGroups) do - if _groups[_woundedGroupName] then - inTransit = true - self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, self.messageTime) - break - end -- end name check - end -- end loop - if not inTransit then - -- KIA - self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, self.messageTime) - end - --stops the message being displayed again - self:_RemoveNameFromDownedPilots(_woundedGroupName) - end - --continue - return inTransit -end - --- (Internal) Monitor in-flight returning groups. -- @param #CSAR self -- @param #string heliname Heli name @@ -1455,7 +1445,7 @@ function CSAR:_DisplayActiveSAR(_unitName) self:T({Table=_value}) --local _woundedGroup = GROUP:FindByName(_groupName) local _woundedGroup = _value.group - if _woundedGroup then + if _woundedGroup and _value.alive then local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) local _helicoord = _heli:GetCoordinate() local _woundcoord = _woundedGroup:GetCoordinate() @@ -1745,7 +1735,9 @@ end --- (Internal) Populate table with available beacon frequencies. -- @param #CSAR self function CSAR:_GenerateVHFrequencies() - self:T(self.lid .. " _GenerateVHFrequencies") + self:T(self.lid .. " _GenerateVHFrequencies") + --local _skipFrequencies = self.SkipFrequencies + local FreeVHFFrequencies = {} FreeVHFFrequencies = UTILS.GenerateVHFrequencies() self.FreeVHFFrequencies = FreeVHFFrequencies @@ -1844,8 +1836,8 @@ function CSAR:_CountActiveDownedPilots() self:T(self.lid .. " _CountActiveDownedPilots") local PilotsInFieldN = 0 for _, _unitName in pairs(self.downedPilots) do - self:T({_unitName}) - if _unitName.name ~= nil then + self:T({_unitName.desc}) + if _unitName.alive == true then PilotsInFieldN = PilotsInFieldN + 1 end end @@ -1896,6 +1888,26 @@ function CSAR:onafterStart(From, Event, To) return self end +--- (Internal) Function called before Status() event. +-- @param #CSAR self +function CSAR:_CheckDownedPilotTable() + local pilots = self.downedPilots + for _,_entry in pairs (pilots) do + self:T("Checking for " .. _entry.name) + self:T({entry=_entry}) + local group = _entry.group + if not group:IsAlive() then + self:T("Group is dead") + if _entry.alive == true then + self:T("Switching .alive to false") + self:__KIA(1,_entry.desc) + self:_RemoveNameFromDownedPilots(_entry.name,true) + end + end + end + return self +end + --- (Internal) Function called before Status() event. -- @param #CSAR self. -- @param #string From From state. @@ -1906,15 +1918,18 @@ function CSAR:onbeforeStatus(From, Event, To) -- housekeeping self:_AddMedevacMenuItem() self:_RefreshRadioBeacons() + self:_CheckDownedPilotTable() for _,_sar in pairs (self.csarUnits) do local PilotTable = self.downedPilots for _,_entry in pairs (PilotTable) do - local entry = _entry -- #CSAR.DownedPilot - local name = entry.name - local timestamp = entry.timestamp or 0 - local now = timer.getAbsTime() - if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. - self:_CheckWoundedGroupStatus(_sar,name) + if _entry.alive then + local entry = _entry -- #CSAR.DownedPilot + local name = entry.name + local timestamp = entry.timestamp or 0 + local now = timer.getAbsTime() + if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. + self:_CheckWoundedGroupStatus(_sar,name) + end end end end From 22826b4cd129ec3ec77b60ef31a32dd0116c7695 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:00:40 +0200 Subject: [PATCH 11/68] Update CTLD.lua Added correct Mi-8MT unit typename --- Moose Development/Moose/Ops/CTLD.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index d5bd6f962..14195a9b4 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -510,6 +510,7 @@ CTLD.UnitTypes = { ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, + ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, @@ -780,6 +781,14 @@ function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") self.FreeUHFFrequencies = {} self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() + --[[ + local _start = 220000000 + + while _start < 399000000 do + table.insert(self.FreeUHFFrequencies, _start) + _start = _start + 500000 + end + --]] return self end @@ -799,7 +808,9 @@ function CTLD:_GenerateVHFrequencies() self.FreeVHFFrequencies = {} self.UsedVHFFrequencies = {} + self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() + return self end From 277c26821e27a47358f1131c91e223951c677e93 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 18 Jul 2021 14:54:37 +0200 Subject: [PATCH 12/68] Updated according to develop version. Added troop extract --- Moose Development/Moose/Ops/CTLD.lua | 221 ++++++++++++++++----------- 1 file changed, 136 insertions(+), 85 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 14195a9b4..3488ee961 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -290,7 +290,7 @@ do -- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, -- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, -- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, --- ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, +-- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, -- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, -- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, -- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, @@ -336,8 +336,16 @@ do -- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) -- ... your code here ... -- end +-- +-- ## 3.4 OnAfterTroopsExtracted -- --- ## 3.4 OnAfterCratesDropped +-- This function is called when a player has re-boarded already deployed troops from the field: +-- +-- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops) +-- ... your code here ... +-- end +-- +-- ## 3.5 OnAfterCratesDropped -- -- This function is called when a player has deployed crates to a DROP zone: -- @@ -345,7 +353,7 @@ do -- ... your code here ... -- end -- --- ## 3.5 OnAfterCratesBuild +-- ## 3.6 OnAfterCratesBuild -- -- This function is called when a player has build a vehicle or FOB: -- @@ -353,7 +361,7 @@ do -- ... your code here ... -- end -- --- ## 3.6 A simple SCORING example: +-- ## 3.7 A simple SCORING example: -- -- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) -- @@ -381,7 +389,8 @@ do -- -- ## 4.2 Manage Troops -- --- Use this entry to load and drop troops. +-- Use this entry to load, drop and extract troops. NOTE - with extract you can only load troops from the field that were deployed prior. +-- Currently limited CTLD_CARGO troops, which are build from **one** template. Also, this will heal/complete your units as they are respawned. -- -- ## 4.3 List boarded cargo -- @@ -519,7 +528,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r1" +CTLD.version="0.1.4r2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -577,6 +586,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- CTLD status update. self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. @@ -595,7 +605,6 @@ function CTLD:New(Coalition, Prefixes, Alias) self.UsedVHFFrequencies = {} self.UsedUHFFrequencies = {} self.UsedFMFrequencies = {} - --self.jtacGeneratedLaserCodes = {} -- radio beacons self.RadioSound = "beacon.ogg" @@ -650,7 +659,6 @@ function CTLD:New(Coalition, Prefixes, Alias) self:_GenerateVHFrequencies() self:_GenerateUHFrequencies() self:_GenerateFMFrequencies() - --self:_GenerateLaserCodes() -- curr unused ------------------------ --- Pseudo Functions --- @@ -693,6 +701,17 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #CTLD_CARGO Cargo Cargo troops. -- @return #CTLD self + --- FSM Function OnAfterTroopsExtracted. + -- @function [parent=#CTLD] OnAfterTroopsExtracted + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self + --- FSM Function OnAfterCratesPickedUp. -- @function [parent=#CTLD] OnAfterCratesPickedUp -- @param #CTLD self @@ -781,14 +800,6 @@ function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") self.FreeUHFFrequencies = {} self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() - --[[ - local _start = 220000000 - - while _start < 399000000 do - table.insert(self.FreeUHFFrequencies, _start) - _start = _start + 500000 - end - --]] return self end @@ -805,12 +816,9 @@ end -- @param #CTLD self function CTLD:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") - self.FreeVHFFrequencies = {} self.UsedVHFFrequencies = {} - self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() - return self end @@ -818,7 +826,6 @@ end -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData function CTLD:_EventHandler(EventData) - -- TODO: events dead and playerleaveunit - nil table entries self:T(string.format("%s Event = %d",self.lid, EventData.id)) local event = EventData -- Core.Event#EVENTDATA if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then @@ -837,6 +844,7 @@ function CTLD:_EventHandler(EventData) -- Herc support --self:T_unit:GetTypeName()) if _unit:GetTypeName() == "Hercules" and self.enableHercules then + self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end return @@ -880,7 +888,6 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) 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) - --local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end -- load troops into heli @@ -889,10 +896,8 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) local unitname = unit:GetName() local cargotype = Cargotype -- #CTLD_CARGO local cratename = cargotype:GetName() -- #string - --self:Tself.lid .. string.format("Troops %s requested", cratename)) -- see if this heli can load troops local unittype = unit:GetTypeName() - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local capabilities = self:_GetUnitCapabilities(Unit) local cantroops = capabilities.troops -- #boolean local trooplimit = capabilities.trooplimit -- #number @@ -911,7 +916,6 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) end if troopsize + numberonboard > trooplimit then self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) - --local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) return else self.CargoCounter = self.CargoCounter + 1 @@ -921,12 +925,101 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded self:_SendMessage("Troops boarded!", 10, false, Group) - --local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) self:__TroopsPickedUp(1,Group, Unit, Cargotype) end return self end + --- (Internal) Function to extract (load from the field) troops into a heli. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @param #CTLD_CARGO Cargotype + function CTLD:_ExtractTroops(Group, Unit) -- #1574 thanks to @bbirchnz! + self:T(self.lid .. " _ExtractTroops") + -- landed or hovering over load zone? + local grounded = not self:IsUnitInAir(Unit) + local hoverload = self:CanHoverLoad(Unit) + + 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 + -- load troops into heli + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load troops + local unittype = unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(Unit) + local cantroops = capabilities.troops -- #boolean + local trooplimit = capabilities.trooplimit -- #number + local unitcoord = unit:GetCoordinate() + + -- find nearest group of deployed troops + local nearestGroup = nil + local nearestGroupIndex = -1 + local nearestDistance = 10000000 + for k,v in pairs(self.DroppedTroops) do + local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) + if distance < nearestDistance then + nearestGroup = v + nearestGroupIndex = k + nearestDistance = distance + end + end + + if nearestGroup == nil or nearestDistance > self.CrateDistance then + self:_SendMessage("No units close enough to extract!", 10, false, Group) + return self + end + -- find matching cargo type + local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$") + local Cargotype = nil + for k,v in pairs(self.Cargo_Troops) do + if v.Name == groupType then + Cargotype = v + break + end + end + + if Cargotype == nil then + self:_SendMessage("Can't find a matching cargo type for " .. groupType, 10, false, Group) + return self + end + + local troopsize = Cargotype:GetCratesNeeded() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + 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 + else + self.CargoCounter = self.CargoCounter + 1 + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + self:T({cargotype=loadcargotype}) + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert(loaded.Cargo,loadcargotype) + self.Loaded_Cargo[unitname] = loaded + self:_SendMessage("Troops boarded!", 10, false, Group) + self:__TroopsExtracted(1,Group, Unit, nearestGroup) + + -- clean up: + table.remove(self.DroppedTroops, nearestGroupIndex) + nearestGroup:Destroy() + end + return self + end + --- (Internal) Function to spawn crates in front of the heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -937,7 +1030,6 @@ end function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:T(self.lid .. " _GetCrates") local cgoname = Cargo:GetName() - --self:T{cgoname, number, drop}) -- check if we are in LOAD zone local inzone = false local drop = drop or false @@ -963,7 +1055,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) 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 -- spawn crates in front of helicopter @@ -1098,9 +1189,6 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) end end end - --self:Tstring.format("Found crates = %d",index)) - -- table.sort(found) - --self:T({found}) return found, index end @@ -1131,13 +1219,10 @@ function CTLD:_LoadCratesNearby(Group, Unit) ----------------------------------------- if not cancrates then self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) - --local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) elseif self.forcehoverload and not canhoverload then self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) - --local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) elseif not grounded and not canhoverload then self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) - --local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) else -- have we loaded stuff already? local numberonboard = 0 @@ -1156,7 +1241,6 @@ function CTLD:_LoadCratesNearby(Group, Unit) local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table if number == 0 or numberonboard == cratelimit then self:_SendMessage("Sorry no loadable crates nearby or fully loaded!", 10, false, Group) - --local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) return -- exit else -- go through crates and load @@ -1164,13 +1248,11 @@ function CTLD:_LoadCratesNearby(Group, Unit) local crateidsloaded = {} local loops = 0 while loaded.Cratesloaded < cratelimit and loops < number do - --for _ind,_crate in pairs (nearcrates) do loops = loops + 1 local crateind = 0 -- get crate with largest index for _ind,_crate in pairs (nearcrates) do if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then - --crate = _crate crateind = _crate:GetID() end end @@ -1185,10 +1267,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) crate:GetPositionable():Destroy() crate.Positionable = nil self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) - --local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) self:__CratesPickedUp(1, Group, Unit, crate) end - --if loaded.Cratesloaded == cratelimit then break end end self.Loaded_Cargo[unitname] = loaded -- clean up real world crates @@ -1220,7 +1300,6 @@ function CTLD:_ListCargo(Group, Unit) local unitname = Unit:GetName() local unittype = Unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local trooplimit = capabilities.trooplimit -- #boolean local cratelimit = capabilities.cratelimit -- #number local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo @@ -1260,10 +1339,8 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") local text = report:Text() self:_SendMessage(text, 30, true, Group) - --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) else self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) - --local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) end return self end @@ -1337,13 +1414,11 @@ function CTLD:_UnloadTroops(Group, Unit) end -- template loop cargo:SetWasDropped(true) self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) - --local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) end -- if type end end -- cargotable loop else -- droppingatbase self:_SendMessage("Troops have returned to base!", 10, false, Group) - --local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) self:__TroopsRTB(1, Group, Unit) end -- cleanup load list @@ -1357,7 +1432,6 @@ function CTLD:_UnloadTroops(Group, Unit) local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum local dropped = cargo:WasDropped() - --local moved = cargo:HasMoved() if type ~= CTLD_CARGO.Enum.TROOP and not dropped then table.insert(loaded.Cargo,_cargo) loaded.Cratesloaded = loaded.Cratesloaded + 1 @@ -1368,10 +1442,8 @@ function CTLD:_UnloadTroops(Group, Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1389,7 +1461,6 @@ function CTLD:_UnloadCrates(Group, Unit) 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) - --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end @@ -1418,8 +1489,6 @@ function CTLD:_UnloadCrates(Group, Unit) self:_GetCrates(Group, Unit, cargo, 1, true) cargo:SetWasDropped(true) cargo:SetHasMoved(true) - --local name cargo:GetName() - --local m = MESSAGE:New(string.format("Dropped Crate for %s!",name),10,"CTLD"):ToGroup(Group) end end -- cleanup load list @@ -1441,10 +1510,8 @@ function CTLD:_UnloadCrates(Group, Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1505,7 +1572,6 @@ function CTLD:_BuildCrates(Group, Unit) if build.CanBuild then txtok = "YES" end - --self:T{name,needed,found,txtok}) local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) report:Add(text) end -- end list buildables @@ -1513,7 +1579,6 @@ function CTLD:_BuildCrates(Group, Unit) report:Add("------------------------------------------------------------") local text = report:Text() self:_SendMessage(text, 30, true, Group) - --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) -- let\'s get going if canbuild then -- loop again @@ -1527,7 +1592,6 @@ function CTLD:_BuildCrates(Group, Unit) end else self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) - --local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) end -- number > 0 return self end @@ -1571,17 +1635,14 @@ function CTLD:_MoveGroupToZone(Group) self:T(self.lid .. " _MoveGroupToZone") local groupname = Group:GetName() or "none" local groupcoord = Group:GetCoordinate() - --self:Tself.lid .. " _MoveGroupToZone for " .. groupname) -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) if (distance <= self.movetroopsdistance) and zone then -- yes, we can ;) local groupname = Group:GetName() - --self:Tstring.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE local coordinate = zonecoord:GetVec2() - --self:T{coordinate=coordinate}) Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) @@ -1600,7 +1661,6 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) self:T(self.lid .. " _CleanUpCrates") -- clean up real world crates local build = Build -- #CTLD.Buildable - --self:T{Build = Build}) local existingcrates = self.Spawned_Cargo -- #table of exising crates local newexcrates = {} -- get right number of crates to destroy @@ -1614,18 +1674,15 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) for _,_crate in pairs(Crates) do local nowcrate = _crate -- #CTLD_CARGO local name = nowcrate:GetName() - --self:Tstring.format("Looking for Crate for %s", name)) local thisID = nowcrate:GetID() if name == nametype then -- matching crate type table.insert(destIDs,thisID) found = found + 1 nowcrate:GetPositionable():Destroy() nowcrate.Positionable = nil - --self:Tstring.format("%s Found %d Need %d", name, found, numberdest)) end if found == numberdest then break end -- got enough end - --self:T{destIDs}) -- loop and remove from real world representation for _,_crate in pairs(existingcrates) do local excrate = _crate -- #CTLD_CARGO @@ -1650,7 +1707,6 @@ function CTLD:_RefreshF10Menus() self:T(self.lid .. " _RefreshF10Menus") local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - --self:T({PlayerTable=PlayerTable}) -- rebuild units table local _UnitList = {} for _key, _group in pairs (PlayerTable) do @@ -1667,7 +1723,6 @@ function CTLD:_RefreshF10Menus() self.CtldUnits = _UnitList -- build unit menus - local menucount = 0 local menus = {} for _, _unitName in pairs(self.CtldUnits) do @@ -1679,7 +1734,6 @@ function CTLD:_RefreshF10Menus() -- get chopper capabilities local unittype = _unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cantroops = capabilities.troops local cancrates = capabilities.crates -- top menu @@ -1713,6 +1767,7 @@ function CTLD:_RefreshF10Menus() menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) end local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() + local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() end local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) if unittype == "Hercules" then @@ -1768,13 +1823,10 @@ function CTLD:AddZone(Zone) local zone = Zone -- #CTLD.CargoZone if zone.type == CTLD.CargoZoneType.LOAD then table.insert(self.pickupZones,zone) - --self:T"Registered LOAD zone " .. zone.name) elseif zone.type == CTLD.CargoZoneType.DROP then table.insert(self.dropOffZones,zone) - --self:T"Registered DROP zone " .. zone.name) else table.insert(self.wpZones,zone) - --self:T"Registered MOVE zone " .. zone.name) end return self end @@ -1839,7 +1891,6 @@ function CTLD:_GetFMBeacon(Name) beacon.name = Name beacon.frequency = FM / 1000000 beacon.modulation = radio.modulation.FM - return beacon end @@ -1950,7 +2001,6 @@ function CTLD:_ListRadioBeacons(Group, Unit) end report:Add("------------------------------------------------------------") self:_SendMessage(report:Text(), 30, true, Group) - --local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) return self end @@ -2013,7 +2063,6 @@ end function CTLD:IsUnitInZone(Unit,Zonetype) self:T(self.lid .. " IsUnitInZone") local unitname = Unit:GetName() - --self:Tstring.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) local zonetable = {} local outcome = false if Zonetype == CTLD.CargoZoneType.LOAD then @@ -2039,7 +2088,6 @@ function CTLD:IsUnitInZone(Unit,Zonetype) local color = czone.color local zoneradius = zone:GetRadius() local distance = self:_GetDistance(zonecoord,unitcoord) - --self:Tstring.format("Check distance: %d",distance)) if distance <= zoneradius and active then outcome = true end @@ -2050,7 +2098,6 @@ function CTLD:IsUnitInZone(Unit,Zonetype) colorret = color end end - --self:T{outcome, zonenameret, zoneret, maxdist}) return outcome, zonenameret, zoneret, maxdist end @@ -2086,7 +2133,6 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) local txt = "smoking" if Flare then txt = "flaring" end self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) - --local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) smoked = true end end @@ -2094,7 +2140,6 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) if not smoked then local distance = UTILS.MetersToNM(self.smokedistance) self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) - --local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) end return self end @@ -2147,7 +2192,6 @@ end local maxh = self.maximumHoverHeight -- 15 local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s - --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true @@ -2174,7 +2218,7 @@ end local maxh = self.HercMinAngels-- 1500m local minh = self.HercMaxAngels -- 5000m local maxspeed = self.HercMaxSpeed -- 77 mps - -- TODO: TEST - Speed test for Herc, should not be above 280kph/150kn + -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn 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)) @@ -2203,7 +2247,6 @@ end text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) - --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self end @@ -2226,7 +2269,6 @@ end 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) - --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) return self end @@ -2274,7 +2316,6 @@ end local unitname = Unit:GetName() local Group = Unit:GetGroup() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if cancrates then @@ -2322,16 +2363,12 @@ end self:I(self.lid .. "Started.") if self.useprefix or self.enableHercules then local prefix = self.prefixes - --self:T{prefix=prefix}) if self.enableHercules then - --self:T("CTLD with prefixes and Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else - --self:T("CTLD with prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() end else - --self:T("CTLD NO prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() end -- Events @@ -2367,8 +2404,7 @@ end -- gather some stats -- pilots local pilots = 0 - for _,_pilot in pairs (self.CtldUnits) do - + for _,_pilot in pairs (self.CtldUnits) do pilots = pilots + 1 end @@ -2431,6 +2467,21 @@ end return self end + --- (Internal) FSM Function onbeforeTroopsExtracted. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsDeployed. -- @param #CTLD self -- @param #string From State. From 7ca7caea7501002ad297db0caa023c50f33fba32 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 24 Jul 2021 15:50:10 +0200 Subject: [PATCH 13/68] Updates with changes from develop --- Moose Development/Moose/Ops/ATIS.lua | 84 ++-- Moose Development/Moose/Ops/Airboss.lua | 597 +++++++++++++++-------- Moose Development/Moose/Ops/CSAR.lua | 91 ++-- Moose Development/Moose/Ops/CTLD.lua | 389 +++++++++++++-- Moose Development/Moose/Wrapper/Unit.lua | 12 + 5 files changed, 873 insertions(+), 300 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index e8b475195..8d5223cd0 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -383,13 +383,13 @@ ATIS.Alphabet = { --- Runway correction for converting true to magnetic heading. -- @type ATIS.RunwayM2T --- @field #number Caucasus 0° (East). --- @field #number Nevada +12° (East). --- @field #number Normandy -10° (West). --- @field #number PersianGulf +2° (East). --- @field #number TheChannel -10° (West). --- @field #number Syria +5° (East). --- @field #number MarianaIslands +2° (East). +-- @field #number Caucasus 0° (East). +-- @field #number Nevada +12° (East). +-- @field #number Normandy -10° (West). +-- @field #number PersianGulf +2° (East). +-- @field #number TheChannel -10° (West). +-- @field #number Syria +5° (East). +-- @field #number MarianaIslands +2° (East). ATIS.RunwayM2T={ Caucasus=0, Nevada=12, @@ -839,9 +839,9 @@ function ATIS:SetMapMarks(switch) return self end ---- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°. +--- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°. -- @param #ATIS self --- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended. +-- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended. -- @return #ATIS self function ATIS:SetRunwayHeadingsMagnetic(headings) @@ -974,12 +974,12 @@ end -- -- To get *true* from *magnetic* heading one has to add easterly or substract westerly variation, e.g -- --- A magnetic heading of 180° corresponds to a true heading of +-- A magnetic heading of 180° corresponds to a true heading of -- --- * 186° on the Caucaus map --- * 192° on the Nevada map --- * 170° on the Normany map --- * 182° on the Persian Gulf map +-- * 186° on the Caucaus map +-- * 192° on the Nevada map +-- * 170° on the Normany map +-- * 182° on the Persian Gulf map -- -- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation. -- @@ -1314,7 +1314,7 @@ function ATIS:onafterBroadcast(From, Event, To) local g= 9.80665 --[m/s^2] local M= 0.0289644 --[kg/mol] local T0=coord:GetTemperature(0)+273.15 --[K] Temp at sea level. - local TS=288.15 -- Standard Temperature assumed by Altimeter is 15°C + local TS=288.15 -- Standard Temperature assumed by Altimeter is 15°C local q=qnh*100 -- Calculate Pressure. @@ -1451,13 +1451,13 @@ function ATIS:onafterBroadcast(From, Event, To) --- Temperature and Dew Point --- --------------------------------- - -- Temperature in °C. + -- Temperature in °C. local temperature=coord:GetTemperature(height+5) - -- Dew point in °C. + -- Dew point in °C. local dewpoint=temperature-(100-self.relHumidity)/5 - -- Convert to °F. + -- Convert to °F. if self.TDegF then temperature=UTILS.CelciusToFarenheit(temperature) dewpoint=UTILS.CelciusToFarenheit(dewpoint) @@ -1617,6 +1617,30 @@ function ATIS:onafterBroadcast(From, Event, To) -- Scattered 4 clouddens=4 elseif cloudspreset:find("RainyPreset") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + elseif cloudspreset:find("RainyPreset1") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + elseif cloudspreset:find("RainyPreset2") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + elseif cloudspreset:find("RainyPreset3") then -- Overcast + Rain clouddens=9 if temperature>5 then @@ -1877,15 +1901,15 @@ function ATIS:onafterBroadcast(From, Event, To) -- Temperature if self.TDegF then if temperature<0 then - subtitle=string.format("Temperature -%s °F", TEMPERATURE) + subtitle=string.format("Temperature -%s °F", TEMPERATURE) else - subtitle=string.format("Temperature %s °F", TEMPERATURE) + subtitle=string.format("Temperature %s °F", TEMPERATURE) end else if temperature<0 then - subtitle=string.format("Temperature -%s °C", TEMPERATURE) + subtitle=string.format("Temperature -%s °C", TEMPERATURE) else - subtitle=string.format("Temperature %s °C", TEMPERATURE) + subtitle=string.format("Temperature %s °C", TEMPERATURE) end end local _TEMPERATURE=subtitle @@ -1906,15 +1930,15 @@ function ATIS:onafterBroadcast(From, Event, To) -- Dew point if self.TDegF then if dewpoint<0 then - subtitle=string.format("Dew point -%s °F", DEWPOINT) + subtitle=string.format("Dew point -%s °F", DEWPOINT) else - subtitle=string.format("Dew point %s °F", DEWPOINT) + subtitle=string.format("Dew point %s °F", DEWPOINT) end else if dewpoint<0 then - subtitle=string.format("Dew point -%s °C", DEWPOINT) + subtitle=string.format("Dew point -%s °C", DEWPOINT) else - subtitle=string.format("Dew point %s °C", DEWPOINT) + subtitle=string.format("Dew point %s °C", DEWPOINT) end end local _DEWPOINT=subtitle @@ -2252,8 +2276,8 @@ function ATIS:onafterReport(From, Event, To, Text) -- Replace other stuff. local text=string.gsub(text, "SM", "statute miles") - local text=string.gsub(text, "°C", "degrees Celsius") - local text=string.gsub(text, "°F", "degrees Fahrenheit") + local text=string.gsub(text, "°C", "degrees Celsius") + local text=string.gsub(text, "°F", "degrees Fahrenheit") local text=string.gsub(text, "inHg", "inches of Mercury") local text=string.gsub(text, "mmHg", "millimeters of Mercury") local text=string.gsub(text, "hPa", "hecto Pascals") @@ -2373,7 +2397,7 @@ end --- Get runway from user supplied magnetic heading. -- @param #ATIS self -- @param #number windfrom Wind direction (from) in degrees. --- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130°. +-- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130°. function ATIS:GetMagneticRunway(windfrom) local diffmin=nil @@ -2416,7 +2440,7 @@ function ATIS:GetNavPoint(navpoints, runway, left) local navL=self:GetRunwayLR(nav.runway) local hdgD=UTILS.HdgDiff(navy,rwyy) - if hdgD<=15 then --We allow an error of +-15° here. + if hdgD<=15 then --We allow an error of +-15° here. if navL==nil or (navL==true and left==true) or (navL==false and left==false) then return nav end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index f2ad2df53..6468751be 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -39,6 +39,7 @@ -- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] +-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**] -- * F/A-18C Hornet (AI) -- * F-14A Tomcat (AI) -- * E-2D Hawkeye (AI) @@ -101,7 +102,7 @@ -- ### Wags DCS Hornet Videos: -- -- * [DCS: F/A-18C Hornet - Episode 9: CASE I Carrier Landing](https://www.youtube.com/watch?v=TuigBLhtAH8) --- * [DCS: F/A-18C Hornet – Episode 16: CASE III Introduction](https://www.youtube.com/watch?v=DvlMHnLjbDQ) +-- * [DCS: F/A-18C Hornet – Episode 16: CASE III Introduction](https://www.youtube.com/watch?v=DvlMHnLjbDQ) -- * [DCS: F/A-18C Hornet Case I Carrier Landing Training Lesson Recording](https://www.youtube.com/watch?v=D33uM9q4xgA) -- -- ### AV-8B Harrier at USS Tarawa @@ -266,7 +267,7 @@ -- -- That being said, this script allows you to use any of the three cases to be used at any time. Or, in other words, *you* need to specify when which case is safe and appropriate. -- --- This is a lot of responsability. *You* are the boss, but *you* need to make the right decisions or things will go terribly wrong! +-- This is a lot of responsibility. *You* are the boss, but *you* need to make the right decisions or things will go terribly wrong! -- -- Recovery windows can be set up via the @{#AIRBOSS.AddRecoveryWindow} function as explained below. With this it is possible to seamlessly (within reason!) switch recovery cases in the same mission. -- @@ -293,14 +294,14 @@ -- -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Landing.png) -- --- Once the aircraft reaches the Inital, the landing pattern begins. The important steps of the pattern are shown in the image above. +-- Once the aircraft reaches the Initial, the landing pattern begins. The important steps of the pattern are shown in the image above. -- -- -- ## CASE III -- -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case3.png) -- --- A Case III recovery is conducted during nighttime. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above. +-- A Case III recovery is conducted during nighttime or when the visibility is below CASE II minima during the day. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above. -- -- The first holding zone starts 21 NM astern the carrier at angels 6. The separation between the stacks is 1000 ft just like in Case I. However, the distance to the boat -- increases by 1 NM with each stack. The general form can be written as D=15+6+(N-1), where D is the distance to the boat in NM and N the number of the stack starting at N=1. @@ -572,7 +573,10 @@ -- * **L**ined **U**p **L**eft or **R**ight: LUL, LUR -- * Too **H**igh or too **LO**w: H, LO -- * Too **F**ast or too **SLO**w: F, SLO --- * **Fly through** glideslope **down** or **up**: \\ , / +-- * **O**ver**S**hoot: OS, only referenced during **X** +-- * **Fly through** glideslope **down** or **up**: \\ , /, advisory only +-- * **D**rift **L**eft or **R**ight:DL, DR, advisory only +-- * **A**ngled **A**pproach: Angled approach (wings level and LUL): AA, advisory only -- -- Each grading, x, is subdivided by -- @@ -633,7 +637,7 @@ -- -- ## Foul Deck Waveoff -- --- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is crossing the ship's wake during Case I/II operations, +-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is at position IM-IC during Case I/II operations, -- or with an aircraft approaching the 3/4 NM during Case III operations. -- -- The approaching aircraft will be notified via LSO radio comms and is supposed to overfly the landing area to enter the Bolter pattern. **This pass is not graded**. @@ -721,7 +725,7 @@ -- -- The same holds true after the recovery window closes. The carrier will head back to the place where he left its assigned route and resume the path to the next waypoint defined in the mission editor. -- --- Note that the carrier will only head into the wind, if the wind direction is different by more than 5° from the current heading of the carrier (the angled runway, if any, fis taken into account here). +-- Note that the carrier will only head into the wind, if the wind direction is different by more than 5° from the current heading of the carrier (the angled runway, if any, fis taken into account here). -- -- === -- @@ -860,10 +864,10 @@ -- -- The graph displays the lineup error (LUE) as a function of the distance to the carrier. -- --- The pilot approaches the carrier from the port side, LUE>0°, at a distance of ~1 NM. --- At the beginning of the groove (X), he significantly overshoots to the starboard side (LUE<5°). +-- The pilot approaches the carrier from the port side, LUE>0°, at a distance of ~1 NM. +-- At the beginning of the groove (X), he significantly overshoots to the starboard side (LUE<5°). -- In the middle (IM), he performs good corrections and smoothly reduces the lineup error. --- Finally, at a distance of ~0.3 NM (IC) he has corrected his lineup with the runway to a reasonable level, |LUE|<0.5°. +-- Finally, at a distance of ~0.3 NM (IC) he has corrected his lineup with the runway to a reasonable level, |LUE|<0.5°. -- -- ## Glideslope Error -- @@ -872,7 +876,7 @@ -- The graph displays the glideslope error (GSE) as a function of the distance to the carrier. -- -- In this case the pilot already enters the groove (X) below the optimal glideslope. He is not able to correct his height in the IM part and --- stays significantly too low. In close, he performs a harsh correction to gain altitude and ends up even slightly too high (GSE>0.5°). +-- stays significantly too low. In close, he performs a harsh correction to gain altitude and ends up even slightly too high (GSE>0.5°). -- At his point further corrections are necessary. -- -- ## Angle of Attack @@ -1263,6 +1267,7 @@ AIRBOSS = { -- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string E2D Grumman E-2D Hawkeye AWACS. -- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod. +-- @field #string T45C T-45C by VNAO AIRBOSS.AircraftCarrier={ AV8B="AV8BNA", HORNET="FA-18C_hornet", @@ -1271,6 +1276,7 @@ AIRBOSS.AircraftCarrier={ F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", + T45C="T-45", S3B="S-3B", S3BTANKER="S-3B Tanker", E2D="E-2C", @@ -1338,6 +1344,8 @@ AIRBOSS.CarrierType={ -- @field #number _min Min _OK_ value. Default -0.5 deg. -- @field #number Left (LUR) threshold. Default -1.0 deg. -- @field #number Right (LUL) threshold. Default 1.0 deg. +-- @field #number LeftMed threshold for AA/OS measuring. Default -2.0 deg. +-- @field #number RightMed threshold for AA/OS measuring. Default 2.0 deg. -- @field #number LEFT LUR threshold. Default -3.0 deg. -- @field #number RIGHT LUL threshold. Default 3.0 deg. @@ -1940,17 +1948,22 @@ function AIRBOSS:New(carriername, alias) -- Welcome players. self:SetWelcomePlayers(true) + -- Coordinates + self.landingcoord=COORDINATE:New(0,0,0) --Core.Point#COORDINATE + self.sterncoord=COORDINATE:New(0, 0, 0) --Core.Point#COORDINATE + self.landingspotcoord=COORDINATE:New(0,0,0) --Core.Point#COORDINATE + -- Init carrier parameters. if self.carriertype==AIRBOSS.CarrierType.STENNIS then self:_InitStennis() elseif self.carriertype==AIRBOSS.CarrierType.ROOSEVELT then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.LINCOLN then - self:_InitNimitz() - elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then self:_InitNimitz() - elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then - self:_InitNimitz() + elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then + self:_InitNimitz() + elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then + self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.VINSON then -- TODO: Carl Vinson parameters. self:_InitStennis() @@ -1988,7 +2001,7 @@ function AIRBOSS:New(carriername, alias) self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5) self:_GetZoneLineup():SmokeZone(SMOKECOLOR.Green, 5) self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White, 45) - self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45) + self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45) self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue, 45) self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue, 45) self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Blue, 45) @@ -2028,7 +2041,7 @@ function AIRBOSS:New(carriername, alias) -- Bow bow:FlareYellow() - + -- Runway half width = 10 m. local r1=stern:Translate(self.carrierparam.rwywidth*0.5, FB+90) local r2=stern:Translate(self.carrierparam.rwywidth*0.5, FB-90) @@ -2425,7 +2438,7 @@ end -- @param #number duration Default duration of the recovery in minutes. Default 30 min. -- @param #number windondeck Default wind on deck in knots. Default 25 knots. -- @param #boolean uturn U-turn after recovery window closes on=true or off=false/nil. Default off. --- @param #number offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. +-- @param #number offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. -- @return #AIRBOSS self function AIRBOSS:SetMenuRecovery(duration, windondeck, uturn, offset) @@ -2643,10 +2656,10 @@ end --- Set multiplayer environment wire correction. -- @param #AIRBOSS self --- @param #number Dcorr Correction distance in meters. Default 8.7 m. +-- @param #number Dcorr Correction distance in meters. Default 12 m. -- @return #AIRBOSS self function AIRBOSS:SetMPWireCorrection(Dcorr) - self.mpWireCorrection=Dcorr or 8.7 + self.mpWireCorrection=Dcorr or 12 return self end @@ -2808,16 +2821,20 @@ end -- @param #number _max -- @param #number _min -- @param #number Left +-- @param #number LeftMed -- @param #number LEFT -- @param #number Right +-- @param #number RightMed -- @param #number RIGHT -- @return #AIRBOSS self -function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT) +function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT) 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 return self end @@ -3288,7 +3305,7 @@ function AIRBOSS:GetNextRecoveryTime(InSeconds) if InSeconds then return self.recoverywindow.START, self.recoverywindow.STOP else - return UTILS.SecondsToClock(self.recoverywindow.START), UTILS.SecondsToClock(self.recoverywindow.STOP) + return UTILS.SecondsToClock(self.recoverywindow.START), UTILS.SecondsToClock(self.recoverywindow.STOP) end else if InSeconds then @@ -3398,6 +3415,12 @@ function AIRBOSS:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Ejection) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft) self:HandleEvent(EVENTS.MissionEnd) + self:HandleEvent(EVENTS.RemoveUnit) + + --self.StatusScheduler=SCHEDULER:New(self) + --self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5) + + self.StatusTimer=TIMER:New(self._Status, self):Start(2, 0.5) -- Start status check in 1 second. self:__Status(1) @@ -3410,19 +3433,12 @@ end -- @param #string To To state. function AIRBOSS:onafterStatus(From, Event, To) - if true then - --env.info("FF Status ==> return") - --return - end - -- Get current time. local time=timer.getTime() -- Update marshal and pattern queue every 30 seconds. if time-self.Tqueue>self.dTqueue then - --collectgarbage() - -- Get time. local clock=UTILS.SecondsToClock(timer.getAbsTime()) local eta=UTILS.SecondsToClock(self:_GetETAatNextWP()) @@ -3433,7 +3449,7 @@ function AIRBOSS:onafterStatus(From, Event, To) local speed=self.carrier:GetVelocityKNOTS() -- Check water is ahead. - local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) + local collision=false --self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) local holdtime=0 if self.holdtimestamp then @@ -3490,14 +3506,8 @@ function AIRBOSS:onafterStatus(From, Event, To) self.recoverywindow.WIND=false end - else - - -- Find path around the obstacle. - if not self.detour then - --self:_Pathfinder() - end - end + end @@ -3528,14 +3538,21 @@ function AIRBOSS:onafterStatus(From, Event, To) self:_ActivateBeacons() end + -- Call status every ~0.5 seconds. + self:__Status(-30) + +end + +--- Check AI status. Pattern queue AI in the groove? Marshal queue AI arrived in holding zone? +-- @param #AIRBOSS self +function AIRBOSS:_Status() + -- Check player status. self:_CheckPlayerStatus() -- Check AI landing pattern status self:_CheckAIStatus() - -- Call status every ~0.5 seconds. - self:__Status(-self.dTstatus) end --- Check AI status. Pattern queue AI in the groove? Marshal queue AI arrived in holding zone? @@ -3587,11 +3604,15 @@ function AIRBOSS:_CheckAIStatus() -- Get lineup and distance to carrier. local lineup=self:_Lineup(unit, true) + local unitcoord=unit:GetCoord() + + local dist=unitcoord:Get2DDistance(self:GetCoord()) + -- Distance in NM. - local distance=UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())) + local distance=UTILS.MetersToNM(dist) -- Altitude in ft. - local alt=UTILS.MetersToFeet(unit:GetAltitude()) + local alt=UTILS.MetersToFeet(unitcoord.y) -- Check if parameters are right and flight is in the groove. if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then @@ -3857,7 +3878,7 @@ function AIRBOSS:_CheckRecoveryTimes() -- Check if time is less than 5 minutes. if nextwindow.WIND and nextwindow.START-time 5° different from the current heading. + -- Check that wind is blowing from a direction > 5° different from the current heading. local hdg=self:GetHeading() local wind=self:GetHeadingIntoWind() local delta=self:_GetDeltaHeading(hdg, wind) @@ -3875,7 +3896,7 @@ function AIRBOSS:_CheckRecoveryTimes() end --Debug info - 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))) + 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))) -- Time into the wind 1 day or if longer recovery time + the 5 min early. local t=math.max(nextwindow.STOP-nextwindow.START+self.dTturn, 60*60*24) @@ -4145,7 +4166,7 @@ end -- @param #string To To state. function AIRBOSS:onafterStop(From, Event, To) self:I(self.lid..string.format("Stopping airboss script.")) - + -- Unhandle events. self:UnHandleEvent(EVENTS.Birth) self:UnHandleEvent(EVENTS.Land) @@ -5471,6 +5492,7 @@ function AIRBOSS:_GetAircraftAoA(playerData) -- Get AC type. local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET + 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 @@ -5497,6 +5519,15 @@ function AIRBOSS:_GetAircraftAoA(playerData) aoa.OnSpeedMin = self:_AoAUnit2Deg(playerData, 14.5) --14.17 --14.5 units aoa.Fast = self:_AoAUnit2Deg(playerData, 14.0) --13.33 --14.0 units aoa.FAST = self:_AoAUnit2Deg(playerData, 13.0) --11.67 --13.0 units + elseif goshawk then + -- T-45C Goshawk parameters. + aoa.SLOW = 8.00 --19 + aoa.Slow = 7.75 --18 + aoa.OnSpeedMax = 7.25 --17.5 + aoa.OnSpeed = 7.00 --17 + aoa.OnSpeedMin = 6.75 --16.5 + aoa.Fast = 6.25 --16 + aoa.FAST = 6.00 --15 elseif skyhawk then -- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390 -- Note that these are arbitrary UNITS and not degrees. We need a conversion formula! @@ -5681,6 +5712,9 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif skyhawk then alt=UTILS.FeetToMeters(600) speed=UTILS.KnotsToMps(250) + elseif goshawk then + alt=UTILS.FeetToMeters(800) + speed=UTILS.KnotsToMps(300) end elseif step==AIRBOSS.PatternStep.BREAKENTRY then @@ -5691,11 +5725,14 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif skyhawk then alt=UTILS.FeetToMeters(600) speed=UTILS.KnotsToMps(250) + elseif goshawk then + alt=UTILS.FeetToMeters(800) + speed=UTILS.KnotsToMps(300) end elseif step==AIRBOSS.PatternStep.EARLYBREAK then - if hornet or tomcat or harrier then + if hornet or tomcat or harrier or goshawk then alt=UTILS.FeetToMeters(800) elseif skyhawk then alt=UTILS.FeetToMeters(600) @@ -5703,7 +5740,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.LATEBREAK then - if hornet or tomcat or harrier then + if hornet or tomcat or harrier or goshawk then alt=UTILS.FeetToMeters(800) elseif skyhawk then alt=UTILS.FeetToMeters(600) @@ -5711,7 +5748,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.ABEAM then - if hornet or tomcat or harrier then + if hornet or tomcat or harrier or goshawk then alt=UTILS.FeetToMeters(600) elseif skyhawk then alt=UTILS.FeetToMeters(500) @@ -5726,10 +5763,19 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) dist=UTILS.NMToMeters(1.2) end + if goshawk then + -- 0.9 to 1.1 NM per natops ch.4 page 48 + 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 @@ -5740,7 +5786,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.WAKE then - if hornet then + if hornet or goshawk then alt=UTILS.FeetToMeters(370) elseif tomcat then alt=UTILS.FeetToMeters(430) -- Tomcat should be a bit higher as it intercepts the GS a bit higher. @@ -5753,7 +5799,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.FINAL then - if hornet then + if hornet or goshawk then alt=UTILS.FeetToMeters(300) elseif tomcat then alt=UTILS.FeetToMeters(360) @@ -6079,66 +6125,32 @@ function AIRBOSS:_ScanCarrierZone() -- Create a new flight group if knownflight then - -- Debug output. - self:T2(self.lid..string.format("Known flight group %s of type %s in CCA.", groupname, actype)) - -- Check if flight is AI and if we want to handle it at all. - if knownflight.ai and self.handleai then + if knownflight.ai and knownflight.flag==-100 and self.handleai then - -- Defines if AI group should be handled by the airboss. - local iscarriersquad=true + local putintomarshal=false - -- Check if AI group is part of the group set if a set was defined. - if self.squadsetAI then - local group=self.squadsetAI:FindGroup(groupname) - if group then - iscarriersquad=true + -- Get flight group. + local flight=_DATABASE:GetFlightGroup(groupname) + + if flight and flight:IsInbound() and flight.destbase:GetName()==self.carrier:GetName() then + if flight.ishelo then else - iscarriersquad=false - end - end - - -- Check if group was explicitly excluded. - if self.excludesetAI then - local group=self.excludesetAI:FindGroup(groupname) - if group then - iscarriersquad=false + putintomarshal=true end + flight.airboss=self end - -- Get distance to carrier. - local dist=knownflight.group:GetCoordinate():Get2DDistance(self:GetCoordinate()) - -- Close in distance. Is >0 if AC comes closer wrt to first detected distance d0. - local closein=knownflight.dist0-dist - - -- Debug info. - self:T3(self.lid..string.format("Known AI flight group %s closed in by %.1f NM", knownflight.groupname, UTILS.MetersToNM(closein))) - - -- Is this group the tanker? - local istanker=self.tanker and self.tanker.tanker:GetName()==groupname - - -- Is this group the AWACS? - local isawacs=self.awacs and self.awacs.tanker:GetName()==groupname - - -- Send tanker to marshal stack? - local tanker2marshal = istanker and self.tanker:IsReturning() and self.tanker.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.tanker.recovery==true - - -- Send AWACS to marhsal stack? - local awacs2marshal = isawacs and self.awacs:IsReturning() and self.awacs.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.awacs.recovery==true - - -- Put flight into Marshal. - local putintomarshal=closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad and (not istanker) and (not isawacs) - - -- Send AI flight to marshal stack if group closes in more than 5 and has initial flag value. - if putintomarshal or tanker2marshal or awacs2marshal then + -- Send AI flight to marshal stack. + if putintomarshal then -- Get the next free stack for current recovery case. local stack=self:_GetFreeStack(knownflight.ai) -- Repawn. - local respawn=self.respawnAI --or tanker2marshal + local respawn=self.respawnAI if stack then @@ -6158,7 +6170,8 @@ function AIRBOSS:_ScanCarrierZone() break end -- Closed in or tanker/AWACS - end -- AI + + end else @@ -6262,7 +6275,7 @@ function AIRBOSS:_MarshalPlayer(playerData, stack) -- Set stack flag. flight.flag=stack - + -- Trigger Marshal event. self:Marshal(flight) end @@ -6521,7 +6534,7 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn) -- Route group. flight.group:Route(wp, 1) - + -- Trigger Marshal event. self:Marshal(flight) @@ -6920,7 +6933,7 @@ function AIRBOSS:_AddMarshalGroup(flight, stack) -- For case 1 we want the BRC but above routine return FB. radial=self:GetBRC() end - local text=string.format("Select TACAN %03d°, channel %d%s (%s)", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) + local text=string.format("Select TACAN %03d°, channel %d%s (%s)", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) self:MessageToPlayer(flight, text, nil, "") end @@ -7057,7 +7070,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) -- Recovery case. case=case or self.case - + if case==1 then return self:_GetFreeStack_Old(ai, case, empty) end @@ -7073,7 +7086,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) for i=1,nmaxstacks do stack[i]=self.NmaxStack -- Number of human flights per stack. end - + local nmax=1 -- Loop over all flights in marshal stack. @@ -7085,7 +7098,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) -- Get stack of flight. local n=flight.flag - + if n>nmax then nmax=n end @@ -7102,7 +7115,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) end end - + local nfree=nil if stack[nmax]==0 then -- Max occupied stack is completely full! @@ -7118,7 +7131,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) -- Case II/III return next stack nfree=nmax+1 end - + elseif stack[nmax]==self.NmaxStack then -- Max occupied stack is completely empty! This should happen only when there is no other flight in the marshal queue. self:E(self.lid..string.format("ERROR: Max occupied stack is empty. Should not happen! Nmax=%d, stack[nmax]=%d", nmax, stack[nmax])) @@ -7130,7 +7143,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) else nfree=nmax end - + end self:I(self.lid..string.format("Returning free stack %s", tostring(nfree))) @@ -8877,6 +8890,58 @@ function AIRBOSS:OnEventEjection(EventData) end +--- Airboss event handler for event REMOVEUNIT. +-- @param #AIRBOSS self +-- @param Core.Event#EVENTDATA EventData +function AIRBOSS:OnEventRemoveUnit(EventData) + self:F3({eventland = EventData}) + + -- Nil checks. + if EventData==nil then + self:E(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!") + self:E(EventData) + return + end + if EventData.IniUnit==nil then + self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!") + 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 removed!",_playername)) + + -- Get player flight. + local flight=self.players[_playername] + + -- Remove flight completely from all queues and collapse marshal if necessary. + if flight then + self:_RemoveFlight(flight, true) + end + + else + -- Debug message. + self:T(self.lid..string.format("AI unit %s removed!", EventData.IniUnitName)) + + -- Remove element/unit from flight group and from all queues if no elements alive. + self:_RemoveUnitFromFlight(EventData.IniUnit) + + -- What could happen is, that another element has landed (recovered) already and this one crashes. + -- This would mean that the flight would not be deleted from the queue ==> Check if section recovered. + local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights) + self:_CheckSectionRecovered(flight) + end + +end + --- Airboss event handler for event player leave unit. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData @@ -9281,7 +9346,7 @@ function AIRBOSS:_Initial(playerData) -- Hook down for students. 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°!" + hint=hint.." - Hook down, SAS on, Wing Sweep 68°!" else hint=hint.." - Hook down!" end @@ -10041,6 +10106,27 @@ function AIRBOSS:_Groove(playerData) -- Distance in NM. local d=UTILS.MetersToNM(rho) + -- Drift on lineup. + if rho>=RAR and rho<=RIM then + if gd.LUE>0.22 and lineupError<-0.22 then + env.info" Drift Right across centre ==> DR-" + gd.Drift=" DR" + 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 + -- Update max deviation of line up error. 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)) @@ -10339,24 +10425,25 @@ function AIRBOSS:_GetSternCoord() local FB=self:GetFinalBearing() -- Stern coordinate (sterndist<0). Also translate 10 meters starboard wrt Final bearing. - local stern=self:GetCoordinate() + self.sterncoord:UpdateFromCoordinate(self:GetCoordinate()) + --local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). if self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Tarawa: Translate 8 meters port. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(8, FB-90) + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then -- Stennis: translate 7 meters starboard wrt Final bearing. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(7, FB+90) + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(7, FB+90, true, true) else -- Nimitz SC: translate 8 meters starboard wrt Final bearing. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(9.5, FB+90) + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(9.5, FB+90, true, true) end -- Set altitude. - stern:SetAltitude(self.carrierparam.deckheight) + self.sterncoord:SetAltitude(self.carrierparam.deckheight) - return stern + return self.sterncoord end --- Get wire from landing position. @@ -10476,6 +10563,9 @@ function AIRBOSS:_Trapped(playerData) elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then -- A-4E gets slowed down much faster the the F/A-18C! dcorr=56 + elseif playerData.actype==AIRBOSS.AircraftCarrier.T45C then + -- T-45 also gets slowed down much faster the the F/A-18C. + dcorr=56 end -- Get wire. @@ -10561,6 +10651,8 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Initial zone. function AIRBOSS:_GetZoneInitial(case) + self.zoneInitial=self.zoneInitial or ZONE_POLYGON_BASE:New("Zone CASE I/II Initial") + -- Get radial, i.e. inverse of BRC. local radial=self:GetRadial(2, false, false) @@ -10568,7 +10660,7 @@ function AIRBOSS:_GetZoneInitial(case) local cv=self:GetCoordinate() -- Vec2 array. - local vec2 + local vec2={} if case==1 then -- Case I @@ -10599,9 +10691,12 @@ function AIRBOSS:_GetZoneInitial(case) end -- Polygon zone. - local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2) + --local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2) - return zone + self.zoneInitial:UpdateFromVec2(vec2) + + --return zone + return self.zoneInitial end --- Get lineup groove zone. @@ -10609,6 +10704,8 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Lineup zone. function AIRBOSS:_GetZoneLineup() + self.zoneLineup=self.zoneLineup or ZONE_POLYGON_BASE:New("Zone Lineup") + -- Get radial, i.e. inverse of BRC. local fbi=self:GetRadial(1, false, false) @@ -10625,10 +10722,13 @@ function AIRBOSS:_GetZoneLineup() -- Vec2 array. local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()} - -- Polygon zone. - local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) + self.zoneLineup:UpdateFromVec2(vec2) - return zone + -- Polygon zone. + --local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) + --return zone + + return self.zoneLineup end @@ -10640,6 +10740,8 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Groove zone. 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 @@ -10661,10 +10763,13 @@ function AIRBOSS:_GetZoneGroove(l, w, b) -- Vec2 array. local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()} - -- Polygon zone. - local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) + self.zoneGroove:UpdateFromVec2(vec2) - return zone + -- Polygon zone. + --local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) + --return zone + + return self.zoneGroove end --- Get Bullseye zone with radius 1 NM and DME 3 NM from the carrier. Radial depends on recovery case. @@ -10688,8 +10793,9 @@ function AIRBOSS:_GetZoneBullseye(case) -- Create zone. local zone=ZONE_RADIUS:New("Zone Bullseye", vec2, radius) - return zone + + --self.zoneBullseye=self.zoneBullseye or ZONE_RADIUS:New("Zone Bullseye", vec2, radius) end --- Get dirty up zone with radius 1 NM and DME 9 NM from the carrier. Radial depends on recovery case. @@ -10885,6 +10991,8 @@ end -- @return Core.Zone#ZONE Zone surrounding the carrier. function AIRBOSS:_GetZoneCarrierBox() + self.zoneCarrierbox=self.zoneCarrierbox or ZONE_POLYGON_BASE:New("Carrier Box Zone") + -- Stern coordinate. local S=self:_GetSternCoord() @@ -10913,9 +11021,12 @@ function AIRBOSS:_GetZoneCarrierBox() end -- Create polygon zone. - local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) + --local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) + --return zone - return zone + self.zoneCarrierbox:UpdateFromVec2(vec2) + + return self.zoneCarrierbox end --- Get zone of landing runway. @@ -10923,6 +11034,8 @@ end -- @return Core.Zone#ZONE_POLYGON Zone surrounding landing runway. function AIRBOSS:_GetZoneRunwayBox() + self.zoneRunwaybox=self.zoneRunwaybox or ZONE_POLYGON_BASE:New("Landing Runway Zone") + -- Stern coordinate. local S=self:_GetSternCoord() @@ -10945,9 +11058,12 @@ function AIRBOSS:_GetZoneRunwayBox() end -- Create polygon zone. - local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) + --local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) + --return zone - return zone + self.zoneRunwaybox:UpdateFromVec2(vec2) + + return self.zoneRunwaybox end @@ -11050,12 +11166,14 @@ function AIRBOSS:_GetZoneHolding(case, stack) -- Post 2.5 NM port of carrier. local Post=self:GetCoordinate():Translate(D, hdg+270) + --TODO: update zone not creating a new one. + -- Create holding zone. - zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", Post:GetVec2(), self.marshalradius) + self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", Post:GetVec2(), self.marshalradius) -- Delta pattern. if self.carriertype==AIRBOSS.CarrierType.TARAWA then - zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) + self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) end @@ -11074,10 +11192,12 @@ function AIRBOSS:_GetZoneHolding(case, stack) -- Square zone length=7NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier. -- So stay 0-5 NM (+1 NM error margin) port of carrier. - zoneHolding=ZONE_POLYGON_BASE:New("CASE II/III Holding Zone", p) + self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone") + + self.zoneHolding:UpdateFromVec2(p) end - return zoneHolding + return self.zoneHolding end --- Get zone where player are automatically commence when enter. @@ -11118,11 +11238,13 @@ function AIRBOSS:_GetZoneCommence(case, stack) end -- Create holding zone. - zone=ZONE_RADIUS:New("CASE I Commence Zone", Three:GetVec2(), R) + self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone") + + self.zoneCommence:UpdateFromVec2(Three:GetVec2(), R) else -- Case II/III - + stack=stack or 1 -- Start point at 21 NM for stack=1. @@ -11149,11 +11271,13 @@ function AIRBOSS:_GetZoneCommence(case, stack) end -- Zone polygon. - zone=ZONE_POLYGON_BASE:New("CASE II/III Commence Zone", p) + self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone") + + self.zoneCommence:UpdateFromVec2(p) end - return zone + return self.zoneCommence end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -11207,15 +11331,15 @@ function AIRBOSS:_AttitudeMonitor(playerData) -- Output 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)) + text=text..string.format("\nAoA=%.1f° = %.1f Units | |V|=%.1f knots", aoa, self:_AoADeg2Units(playerData, aoa), UTILS.MpsToKnots(vabs)) if self.Debug then -- Velocity vector. text=text..string.format("\nVx=%.1f Vy=%.1f Vz=%.1f m/s", velo.x, velo.y, velo.z) --Wind vector. 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) + 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) -- Get player velocity in km/h. local vplayer=playerData.unit:GetVelocityKMH() @@ -11236,14 +11360,14 @@ function AIRBOSS:_AttitudeMonitor(playerData) 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)) + 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) + text=text..string.format("\nGamma=%.1f° | Rho=%.1f°", relhead, phi) end MESSAGE:New(text, 1, nil , true):ToClient(playerData.client) @@ -11397,8 +11521,11 @@ end -- @return Core.Point#COORDINATE Optimal landing coordinate. function AIRBOSS:_GetOptLandingCoordinate() + -- Start with stern coordiante. + self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord()) + -- Stern coordinate. - local stern=self:_GetSternCoord() + --local stern=self:_GetSternCoord() -- Final bearing. local FB=self:GetFinalBearing(false) @@ -11406,10 +11533,11 @@ function AIRBOSS:_GetOptLandingCoordinate() if self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Landing 100 ft abeam, 120 ft alt. - stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) + --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) -- Alitude 120 ft. - stern:SetAltitude(UTILS.FeetToMeters(120)) + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) else @@ -11417,15 +11545,15 @@ function AIRBOSS:_GetOptLandingCoordinate() if self.carrierparam.wire3 then -- We take the position of the 3rd wire to approximately account for the length of the aircraft. local w3=self.carrierparam.wire3 - stern=stern:Translate(w3, FB, true) + self.landingcoord:Translate(w3, FB, true, true) end -- Add 2 meters to account for aircraft height. - stern.y=stern.y+2 + self.landingcoord.y=self.landingcoord.y+2 end - return stern + return self.landingcoord end --- Get landing spot on Tarawa. @@ -11433,8 +11561,10 @@ end -- @return Core.Point#COORDINATE Primary landing spot coordinate. function AIRBOSS:_GetLandingSpotCoordinate() + self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord()) + -- Stern coordinate. - local stern=self:_GetSternCoord() + --local stern=self:_GetSternCoord() if self.carriertype==AIRBOSS.CarrierType.TARAWA then @@ -11442,11 +11572,11 @@ function AIRBOSS:_GetLandingSpotCoordinate() local hdg=self:GetHeading() -- Primary landing spot 7.5 - stern=stern:Translate(57, hdg):SetAltitude(self.carrierparam.deckheight) + self.landingspotcoord:Translate(57, hdg, true, true):SetAltitude(self.carrierparam.deckheight) end - return stern + return self.landingspotcoord end --- Get true (or magnetic) heading of carrier. @@ -11509,7 +11639,7 @@ end --- Get wind speed on carrier deck parallel and perpendicular to runway. -- @param #AIRBOSS self --- @param #number alt Altitude in meters. Default 50 m. +-- @param #number alt Altitude in meters. Default 15 m. (change made from 50m from Discord discussion from Sickdog) -- @return #number Wind component parallel to runway im m/s. -- @return #number Wind component perpendicular to runway in m/s. -- @return #number Total wind strength in m/s. @@ -11532,7 +11662,7 @@ function AIRBOSS:GetWindOnDeck(alt) zc=UTILS.Rotate2D(zc, -self.carrierparam.rwyangle) -- Wind (from) vector - local vw=cv:GetWindWithTurbulenceVec3(alt or 50) + local vw=cv:GetWindWithTurbulenceVec3(alt or 15) -- Total wind velocity vector. -- Carrier velocity has to be negative. If carrier drives in the direction the wind is blowing from, we have less wind in total. @@ -11946,15 +12076,15 @@ function AIRBOSS:_EvalGrooveTime(playerData) local grade="" if t<9 then - grade="--" - elseif t<12 then - grade="(OK)" - elseif t<22 then - grade="OK" + grade="_NESA_" + elseif t<15 then + grade="NESA" + elseif t<19 then + grade="OK Groove" elseif t<=24 then - grade="(OK)" + grade="(LIG)" else - grade="--" + grade="LIG" end -- The unicorn! @@ -11993,9 +12123,9 @@ function AIRBOSS:_LSOgrade(playerData) local nS=count(G, '%(') local nN=N-nS-nL - -- Groove time 16-18 sec for a unicorn. + -- Groove time 15-18.99 sec for a unicorn. local Tgroove=playerData.Tgroove - local TgrooveUnicorn=Tgroove and (Tgroove>=16.0 and Tgroove<=18.0) or false + local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false local grade local points @@ -12128,7 +12258,35 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) -- Aircraft specific AoA values. local acaoa=self:_GetAircraftAoA(playerData) - + + --Angled Approach. + local P=nil + if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 and playerData.case<3 then + if LUE>self.lue.RIGHT then + P=underline("AA") + elseif + LUE>self.lue.RightMed then + P="AA " + elseif + LUE>self.lue.Right then + P=little("AA") + end + end + + --Overshoot Start. + local O=nil + if step==AIRBOSS.PatternStep.GROOVE_XX then + if LUEacaoa.SLOW then @@ -12161,7 +12319,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) A=little("LO") end - -- Line up. Good [-0.5, 0.5] + -- Line up. XX Step replaced by Overshoot start (OS). Good [-0.5, 0.5] local D=nil if LUE>self.lue.RIGHT then D=underline("LUL") @@ -12169,12 +12327,22 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) D="LUL" elseif LUE>self.lue._max then D=little("LUL") - elseif LUE=Hupdate then - self:T(self.lid..string.format("Carrier heading changed by %d°.", deltaHeading)) + self:T(self.lid..string.format("Carrier heading changed by %d°.", deltaHeading)) Hchange=true end @@ -14025,6 +14208,8 @@ 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 @@ -14390,9 +14575,15 @@ end -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Carrier coordinate. function AIRBOSS:GetCoordinate() - return self.carrier:GetCoordinate() + return self.carrier:GetCoord() end +--- Get carrier coordinate. +-- @param #AIRBOSS self +-- @return Core.Point#COORDINATE Carrier coordinate. +function AIRBOSS:GetCoord() + return self.carrier:GetCoord() +end --- Get static weather of this mission from env.mission.weather. -- @param #AIRBOSS self @@ -15451,7 +15642,7 @@ end function AIRBOSS:_MarshalCallNewFinalBearing(FB) -- Subtitle. - local text=string.format("new final bearing %03d°.", FB) + local text=string.format("new final bearing %03d°.", FB) -- Debug message. self:I(self.lid..text) @@ -15474,7 +15665,7 @@ end function AIRBOSS:_MarshalCallCarrierTurnTo(hdg) -- Subtitle. - local text=string.format("carrier is now starting turn to heading %03d°.", hdg) + local text=string.format("carrier is now starting turn to heading %03d°.", hdg) -- Debug message. self:I(self.lid..text) @@ -15527,11 +15718,11 @@ function AIRBOSS:_MarshalCallRecoveryStart(case) -- Debug output. local text=string.format("Starting aircraft recovery Case %d ops.", case) if case==1 then - text=text..string.format(" BRC %03d°.", self:GetBRC()) + text=text..string.format(" BRC %03d°.", self:GetBRC()) elseif case==2 then - text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC()) + 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)) + text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false)) end self:T(self.lid..text) @@ -15576,7 +15767,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) local CT=UTILS.Split(clock[1], ":") -- Subtitle text. - 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) + 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) -- Debug message. self:I(self.lid..text) @@ -15753,11 +15944,11 @@ function AIRBOSS:_AddF10Commands(_unitName) 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, "+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) @@ -15814,7 +16005,7 @@ function AIRBOSS:_SkipperStartRecovery(_unitName, case) -- Inform player. 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) + text=text..string.format(" Marshal radial %d°.", self.skipperOffset) end if self:IsRecovering() then text="negative, carrier is already recovering." @@ -15880,7 +16071,7 @@ function AIRBOSS:_SkipperRecoveryOffset(_unitName, offset) if playerData then -- Inform player. - local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) + local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) self:MessageToPlayer(playerData, text, "AIRBOSS") self.skipperOffset=offset @@ -16343,7 +16534,7 @@ function AIRBOSS:_RequestCommence(_unitName) -- For case 1 we want the BRC but above routine return FB. radial=self:GetBRC() end - text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) + text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) end -- TODO: Inform section members. @@ -16955,7 +17146,7 @@ function AIRBOSS:_DisplayCarrierInfo(_unitname) -- Only include current and future recovery windows. if Tabs1 then -- Get inverse magnetic radial potential offset. local radial=self:GetRadial(playerData.case, true, true, true) - stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n", radial, angels+15) + stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n", radial, angels+15) end end @@ -17354,7 +17545,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName) local brc=self:GetBRC() -- Help player to find its way to the initial zone. - text=text..string.format("\nTo Initial: Fly heading %03d° for %.1f NM and turn to BRC %03d°", flyhdg, flydist, brc) + 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 @@ -17369,7 +17560,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName) local hdg=self:GetRadial(playerData.case, true, true, true) -- Help player to find its way to the initial zone. - text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°", flyhdg, flydist, hdg) + text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°", flyhdg, flydist, hdg) end diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index df419a5b9..943b17cdd 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -368,7 +368,7 @@ function CSAR:New(Coalition, Template, Alias) self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near - self.autosmokedistance = 1000 -- distance for autosmoke + self.autosmokedistance = 2000 -- distance for autosmoke -- added 0.1.4 self.limitmaxdownedpilots = true self.maxdownedpilots = 25 @@ -886,6 +886,12 @@ function CSAR:_EventHandler(EventData) self:T(self.lid .. " Landing Place Nil") return -- error! end + + -- anyone on board? + if self.inTransitGroups[_event.IniUnitName] == nil then + -- ignore + return + end if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_event.IniUnitName) then @@ -998,10 +1004,15 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...helinunit nil!") return end - + local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) + -- autosmoke + if (self.autosmoke == true) and (_distance < self.autosmokedistance) and (_distance ~= -1) then + self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup) + end + if _distance < self.approachdist_near and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then -- we\'re close, reschedule @@ -1009,7 +1020,25 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:__Approach(-5,heliname,woundedgroupname) end elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then - self.heliVisibleMessage[_lookupKeyHeli] = nil + -- message once + if self.heliVisibleMessage[_lookupKeyHeli] == nil then + local _pilotName = _downedpilot.desc + if self.autosmoke == true then + local dist = self.autosmokedistance / 1000 + local disttext = string.format("%.0fkm",dist) + if _SETTINGS:IsImperial() then + local dist = UTILS.MetersToNM(self.autosmokedistance) + disttext = string.format("%.0fnm",dist) + end + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true) + end + --mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end + self.heliCloseMessage[_lookupKeyHeli] = nil + self.landedStatus[_lookupKeyHeli] = nil --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-10,heliname,woundedgroupname) @@ -1169,27 +1198,13 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG local _reset = true - if (self.autosmoke == true) and (_distance < self.autosmokedistance) then - self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) - end - - if self.heliVisibleMessage[_lookupKeyHeli] == nil then - if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime,true,true) - else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime,true,true) - end - --mark as shown for THIS heli and THIS group - self.heliVisibleMessage[_lookupKeyHeli] = true - end - if (_distance < 500) then 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.", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _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 ", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true) end --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true @@ -1206,7 +1221,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) - self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false) else _time = self.landedStatus[_lookupKeyHeli] - 10 self.landedStatus[_lookupKeyHeli] = _time @@ -1520,8 +1535,9 @@ function CSAR:_SignalFlare(_unitName) end local _closest = self:_GetClosestDownedPilot(_heli) - - if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + local smokedist = 8000 + if self.approachdist_far > smokedist then smokedist = self.approachdist_far end + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 @@ -1536,11 +1552,13 @@ function CSAR:_SignalFlare(_unitName) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) else - local disttext = "4.3nm" - if _SETTINGS:IsMetric() then - disttext = "8km" - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) + local _distance = smokedist + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime) end return self end @@ -1572,8 +1590,10 @@ function CSAR:_Reqsmoke( _unitName ) if _heli == nil then return end + local smokedist = 8000 + if smokedist < self.approachdist_far then smokedist = self.approachdist_far end local _closest = self:_GetClosestDownedPilot(_heli) - if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 if _SETTINGS:IsImperial() then @@ -1587,11 +1607,13 @@ function CSAR:_Reqsmoke( _unitName ) local color = self.smokecolor _coord:Smoke(color) else - local disttext = "4.3nm" - if _SETTINGS:IsMetric() then - disttext = "8km" - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) + local _distance = 0 + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime) end return self end @@ -1776,7 +1798,8 @@ function CSAR:_GetClockDirection(_heli, _group) if _heading then local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end - clock = math.floor(Aspect / 30) + --clock = math.floor(Aspect / 30) + clock = math.abs(UTILS.Round((Aspect / 30),0)) if clock == 0 then clock = 12 end end return clock diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3488ee961..235f2946e 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1,4 +1,4 @@ ---- **Ops** -- Combat Troops & Logistics Deployment. +--- **Ops** -- Combat Troops & Logistics Department. -- -- === -- @@ -18,7 +18,7 @@ -- -- === -- --- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing) +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), bbirchnz (additional code!!) -- @module Ops.CTLD -- @image OPS_CTLD.jpg @@ -37,6 +37,7 @@ do -- @field #number CratesNeeded Crates needed to build. -- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. -- @field #boolean HasBeenDropped True if dropped from heli. +-- @field #number PerCrateMass Mass in kg -- @extends Core.Fsm#FSM CTLD_CARGO = { ClassName = "CTLD_CARGO", @@ -49,6 +50,7 @@ CTLD_CARGO = { CratesNeeded = 0, Positionable = nil, HasBeenDropped = false, + PerCrateMass = 0 } --- Define cargo types. @@ -59,6 +61,7 @@ CTLD_CARGO = { ["TROOPS"] = "Troops", -- #string troops ["FOB"] = "FOB", -- #string FOB ["CRATE"] = "Crate", -- #string crate + ["REPAIR"] = "Repair", -- #string repair } --- Function to create new CTLD_CARGO object. @@ -72,8 +75,9 @@ CTLD_CARGO = { -- @param #number CratesNeeded Crates needed to build. -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. + -- @param #number PerCrateMass Mass in kg -- @return #CTLD_CARGO self - function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped) + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #CTLD self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) @@ -86,6 +90,7 @@ CTLD_CARGO = { self.CratesNeeded = CratesNeeded or 0 -- #number self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE self.HasBeenDropped = Dropped or false --#boolean + self.PerCrateMass = PerCrateMass or 0 -- #number return self end @@ -169,13 +174,25 @@ CTLD_CARGO = { return false end end + --- Set WasDropped. -- @param #CTLD_CARGO self -- @param #boolean dropped function CTLD_CARGO:SetWasDropped(dropped) self.HasBeenDropped = dropped or false end - + + --- Query crate type for REPAIR + -- @param #CTLD_CARGO self + -- @param #boolean + function CTLD_CARGO:IsRepair() + if self.CargoType == "Repair" then + return true + else + return false + end + end + end do @@ -224,6 +241,8 @@ do -- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3 -- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed) -- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3) +-- -- if you want to add weight to your Heli, troops can have a weight in kg **per person**. Currently no max weight checked. Fly carefully. +-- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3,80) -- -- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4 -- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4) @@ -231,10 +250,15 @@ do -- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build -- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) +-- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. Currently no max weight checked. Fly carefully. +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775) -- -- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: -- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) -- +-- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair +-- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) +-- -- ## 1.3 Add logistics zones -- -- Add zones for loading troops and crates and dropping, building crates @@ -272,6 +296,7 @@ do -- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) -- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) -- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. +-- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. -- -- ## 2.1 User functions -- @@ -353,12 +378,16 @@ do -- ... your code here ... -- end -- --- ## 3.6 OnAfterCratesBuild +-- ## 3.6 OnAfterCratesBuild, OnAfterCratesRepaired -- -- This function is called when a player has build a vehicle or FOB: -- -- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) -- ... your code here ... +-- end +-- +-- function my_ctld:OnAfterCratesRepaired(From, Event, To, Group, Unit, Vehicle) +-- ... your code here ... -- end -- -- ## 3.7 A simple SCORING example: @@ -385,7 +414,7 @@ do -- -- ## 4.1 Manage Crates -- --- Use this entry to get, load, list nearby, drop, and build crates. Also @see options. +-- Use this entry to get, load, list nearby, drop, build and repair crates. Also @see options. -- -- ## 4.2 Manage Troops -- @@ -420,7 +449,7 @@ do -- my_ctld.enableHercules = true -- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft -- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft --- my_ctld.HercMaxSpeed = 77 -- 77mps or 270 kph or 150 kn +-- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn -- -- Also, the following options need to be set to `true`: -- @@ -528,7 +557,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r2" +CTLD.version="0.1.4r3" --- Instantiate a new CTLD. -- @param #CTLD self @@ -591,7 +620,8 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. - self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables @@ -652,6 +682,9 @@ function CTLD:New(Coalition, Prefixes, Alias) -- message suppression self.suppressmessages = false + -- time to repair a unit/group + self.repairtime = 300 + for i=1,100 do math.random() end @@ -745,7 +778,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. -- @return #CTLD self - --- FSM Function OnAfterCratesBuild. + --- FSM Function OnAfterCratesBuild. -- @function [parent=#CTLD] OnAfterCratesBuild -- @param #CTLD self -- @param #string From State. @@ -755,7 +788,18 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self - + + --- FSM Function OnAfterCratesRepaired. + -- @function [parent=#CTLD] OnAfterCratesRepaired + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. + -- @return #CTLD self + --- FSM Function OnAfterTroopsRTB. -- @function [parent=#CTLD] OnAfterTroopsRTB -- @param #CTLD self @@ -919,13 +963,119 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) return else self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) self:T({cargotype=loadcargotype}) loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded self:_SendMessage("Troops boarded!", 10, false, Group) self:__TroopsPickedUp(1,Group, Unit, Cargotype) + self:_UpdateUnitCargoMass(Unit) + end + return self +end + +function CTLD:_FindRepairNearby(Group, Unit, Repairtype) + self:T(self.lid .. " _FindRepairNearby") + local unitcoord = Unit:GetCoordinate() + + -- find nearest group of deployed groups + local nearestGroup = nil + local nearestGroupIndex = -1 + local nearestDistance = 10000000 + for k,v in pairs(self.DroppedTroops) do + local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) + if distance < nearestDistance and distance ~= -1 then + nearestGroup = v + nearestGroupIndex = k + nearestDistance = distance + end + end + + -- found one and matching distance? + if nearestGroup == nil or nearestDistance > 1000 then + self:_SendMessage("No unit close enough to repair!", 10, false, Group) + return nil, nil + end + + local groupname = nearestGroup:GetName() + --self:I(string.format("***** Found Group %s",groupname)) + + -- helper to find matching template + local function matchstring(String,Table) + local match = false + if type(Table) == "table" then + for _,_name in pairs (Table) do + if string.find(String,_name) then + match = true + break + end + end + else + if type(String) == "string" then + if string.find(String,Table) then match = true end + end + end + return match + end + + -- walk through generics and find matching type + local Cargotype = nil + for k,v in pairs(self.Cargo_Crates) do + --self:I({groupname,v.Templates}) + if matchstring(groupname,v.Templates) and matchstring(groupname,Repairtype) then + Cargotype = v -- #CTLD_CARGO + break + end + end + + if Cargotype == nil then + --self:_SendMessage("Can't find a matching group for " .. Repairtype, 10, false, Group) + return nil, nil + else + return nearestGroup, Cargotype + end + +end + +--- (Internal) Function to repair an object. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #table Crates Table of #CTLD_CARGO objects near the unit. +-- @param #CTLD.Buildable Build Table build object. +-- @param #number Number Number of objects in Crates (found) to limit search. +function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number) + self:T(self.lid .. " _RepairObjectFromCrates") + local build = Build -- -- #CTLD.Buildable + --self:I({Build=Build}) + local Repairtype = build.Template -- #string + local NearestGroup, CargoType = self:_FindRepairNearby(Group,Unit,Repairtype) -- Wrapper.Group#GROUP, #CTLD_CARGO + --self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup}) + if NearestGroup ~= nil then + if self.repairtime < 2 then self.repairtime = 30 end -- noob catch + self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) + -- now we can build .... + --NearestGroup:Destroy(false) + local name = CargoType:GetName() + local required = CargoType:GetCratesNeeded() + local template = CargoType:GetTemplates() + local ctype = CargoType:GetType() + local object = {} -- #CTLD.Buildable + object.Name = CargoType:GetName() + object.Required = required + object.Found = required + object.Template = template + object.CanBuild = true + object.Type = ctype -- #CTLD_CARGO.Enum + 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:_BuildObjectFromCrates(Group,Unit,object) + else + self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) end return self end @@ -961,7 +1111,7 @@ end local nearestDistance = 10000000 for k,v in pairs(self.DroppedTroops) do local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) - if distance < nearestDistance then + if distance < nearestDistance and distance ~= -1 then nearestGroup = v nearestGroupIndex = k nearestDistance = distance @@ -1005,12 +1155,13 @@ end return else self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) self:T({cargotype=loadcargotype}) loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded self:_SendMessage("Troops boarded!", 10, false, Group) + self:_UpdateUnitCargoMass(Unit) self:__TroopsExtracted(1,Group, Unit, nearestGroup) -- clean up: @@ -1097,10 +1248,10 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) 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) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) table.insert(droppedcargo,realcargo) else - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter]) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],nil,cargotype.PerCrateMass) end table.insert(self.Spawned_Cargo, realcargo) end @@ -1132,7 +1283,7 @@ function CTLD:_ListCratesNearby( _group, _unit) if dropped then text:Add(string.format("Dropped crate for %s",name)) else - text:Add(string.format("Crate for %s",name)) + text:Add(string.format("Crate for %s, %d Kg",name, entry.PerCrateMass)) end end if text:GetCount() == 1 then @@ -1226,6 +1377,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) else -- have we loaded stuff already? local numberonboard = 0 + local massonboard = 0 local loaded = {} if self.Loaded_Cargo[unitname] then loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo @@ -1266,7 +1418,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- destroy crate crate:GetPositionable():Destroy() crate.Positionable = nil - self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + self:_UpdateUnitCargoMass(Unit) self:__CratesPickedUp(1, Group, Unit, crate) end end @@ -1290,6 +1443,43 @@ function CTLD:_LoadCratesNearby(Group, Unit) return self end +--- (Internal) Function to get current loaded mass +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #number mass in kgs +function CTLD:_GetUnitCargoMass(Unit) + self:T(self.lid .. " _GetUnitCargoMass") + local unitname = Unit:GetName() + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local loadedmass = 0 -- #number + if self.Loaded_Cargo[unitname] then + local cargotable = loadedcargo.Cargo or {} -- #table + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded()) + end + if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + loadedmass = loadedmass + cargo.PerCrateMass + end + end + end + return loadedmass +end + +--- (Internal) Function to calculate and set Unit internal cargo mass +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_UpdateUnitCargoMass(Unit) + self:T(self.lid .. " _UpdateUnitCargoMass") + local calculatedMass = self:_GetUnitCargoMass(Unit) + Unit:SetUnitInternalCargo(calculatedMass) + local report = REPORT:New("Loadmaster report") + report:Add("Carrying " .. calculatedMass .. "Kg") + self:_SendMessage(report:Text(),10,false,Unit:GetGroup()) +end + --- (Internal) Function to list loaded cargo. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -1303,6 +1493,7 @@ function CTLD:_ListCargo(Group, Unit) local trooplimit = capabilities.trooplimit -- #boolean local cratelimit = capabilities.cratelimit -- #number local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local loadedmass = self:_GetUnitCargoMass(Unit) -- #number if self.Loaded_Cargo[unitname] then local no_troops = loadedcargo.Troopsloaded or 0 local no_crates = loadedcargo.Cratesloaded or 0 @@ -1328,7 +1519,7 @@ function CTLD:_ListCargo(Group, Unit) for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS then + if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then report:Add(string.format("Crate: %s size 1",cargo:GetName())) cratecount = cratecount + 1 end @@ -1337,6 +1528,7 @@ function CTLD:_ListCargo(Group, Unit) report:Add(" N O N E") end report:Add("------------------------------------------------------------") + report:Add("Total Mass: ".. loadedmass .. " kg") local text = report:Text() self:_SendMessage(text, 30, true, Group) else @@ -1439,6 +1631,7 @@ function CTLD:_UnloadTroops(Group, Unit) end self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass(Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) @@ -1507,6 +1700,7 @@ function CTLD:_UnloadCrates(Group, Unit) end self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass(Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) @@ -1533,7 +1727,7 @@ function CTLD:_BuildCrates(Group, Unit) -- get dropped crates for _,_crate in pairs(crates) do local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() then + if Crate:WasDropped() and not Crate:IsRepair() then -- we can build these - maybe local name = Crate:GetName() local required = Crate:GetCratesNeeded() @@ -1596,12 +1790,92 @@ function CTLD:_BuildCrates(Group, Unit) return self end +--- (Internal) Function to repair nearby vehicles / FOBs +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_RepairCrates(Group, Unit) + self:T(self.lid .. " _RepairCrates") + -- get nearby crates + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _,_crate in pairs(crates) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() and Crate:IsRepair() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + 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 dropped + end -- end crate loop + -- ok let\'s list what we have + local report = REPORT:New("Checklist Repairs") + report:Add("------------------------------------------------------------") + for _,_build in pairs(buildables) do + local build = _build -- Object table from above + 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 -- end list buildables + if not foundbuilds then report:Add(" --- None Found ---") end + report:Add("------------------------------------------------------------") + local text = report:Text() + self:_SendMessage(text, 30, true, Group) + -- let\'s get going + if canbuild then + -- loop again + for _,_build in pairs(buildables) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_RepairObjectFromCrates(Group,Unit,crates,build,number) + end + end + end + else + self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + end -- number > 0 + return self +end + --- (Internal) Function to actually SPAWN buildables in the mission. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Group#UNIT Unit -- @param #CTLD.Buildable Build -function CTLD:_BuildObjectFromCrates(Group,Unit,Build) +-- @param #boolean Repair If true this is a repair and not a new build +-- @param Core.Point#COORDINATE Coordinate Location for repair (e.g. where the destroyed unit was) +function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) self:T(self.lid .. " _BuildObjectFromCrates") -- Spawn-a-crate-content local position = Unit:GetCoordinate() or Group:GetCoordinate() @@ -1613,17 +1887,31 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build) local temptable = Build.Template or {} local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) local randomcoord = zone:GetRandomCoordinate(35):GetVec2() + if Repair then + randomcoord = RepairLocation:GetVec2() + end for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 + if canmove then local alias = string.format("%s-%d", _template, math.random(1,100000)) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) :InitDelayOff() :SpawnFromVec2(randomcoord) + else -- don't random position of e.g. SAM units build as FOB + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + --:InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + end if self.movetroopstowpzone and canmove then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) end - self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + if Repair then + self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + else + self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + end end -- template loop return self end @@ -1756,7 +2044,8 @@ function CTLD:_RefreshF10Menus() end listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) - local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit):Refresh() + local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) + local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() end -- sub menu troops management if cantroops then @@ -1791,11 +2080,12 @@ function CTLD:_RefreshF10Menus() -- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. -- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. -- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). -function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops) +-- @param #number PerTroopMass Mass in kg of each soldier +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass) self:T(self.lid .. " AddTroopsCargo") self.CargoCounter = self.CargoCounter + 1 -- Troops are directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass) table.insert(self.Cargo_Troops,cargo) return self end @@ -1806,11 +2096,28 @@ end -- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. -- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. -- @param #number NoCrates Number of crates needed to build this cargo. -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates) +-- @param #number PerCrateMass Mass in kg of each crate +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass) self:T(self.lid .. " AddCratesCargo") self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass) + table.insert(self.Cargo_Crates,cargo) + return self +end + +--- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. +-- @param #CTLD self +-- @param #string Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #string Template Template of VEHICLE or FOB cargo that this can repair. +-- @param #CTLD_CARGO.Enum Type Type of cargo, here REPAIR. +-- @param #number NoCrates Number of crates needed to build this cargo. +-- @param #number PerCrateMass Mass in kg of each crate +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass) + self:T(self.lid .. " AddCratesRepair") + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass) table.insert(self.Cargo_Crates,cargo) return self end @@ -1840,15 +2147,15 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) self:T(self.lid .. " AddZone") local newstate = true -- set optional in case we\'re deactivating - if not NewState or NewState == false then - newstate = false - end + if NewState ~= nil then + newstate = NewState + end + -- get correct table - local zone = ZoneType -- #CTLD.CargoZone local table = {} - if zone.type == CTLD.CargoZoneType.LOAD then + if ZoneType == CTLD.CargoZoneType.LOAD then table = self.pickupZones - elseif zone.type == CTLD.CargoZoneType.DROP then + elseif ZoneType == CTLD.CargoZoneType.DROP then table = self.dropOffZones else table = self.wpZones @@ -1864,6 +2171,7 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) return self end + --- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. -- @param #CTLD self -- @param #string Name Name of the zone to change in the ME. @@ -2348,6 +2656,20 @@ end return self end + --- (Internal) Run through DroppedTroops and capture alive units + -- @param #CTLD self + -- @return #CTLD self + function CTLD:CleanDroppedTroops() + local troops = self.DroppedTroops + local newtable = {} + for _index, _group in pairs (troops) do + if _group and _group:IsAlive() then + newtable[_index] = _group + end + end + self.DroppedTroops = newtable + return self + end ------------------------------------------------------------------- -- FSM functions ------------------------------------------------------------------- @@ -2387,6 +2709,7 @@ end -- @return #CTLD self function CTLD:onbeforeStatus(From, Event, To) self:T({From, Event, To}) + self:CleanDroppedTroops() self:_RefreshF10Menus() self:_RefreshRadioBeacons() self:CheckAutoHoverload() diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index eb6d472d8..efd02a03d 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -580,6 +580,18 @@ function UNIT:GetAmmo() return nil end +--- Sets the Unit's Internal Cargo Mass, in kg +-- @param #UNIT self +-- @param #number mass to set cargo to +-- @return #UNIT self +function UNIT:SetUnitInternalCargo(mass) + local DCSUnit = self:GetDCSObject() + if DCSUnit then + trigger.action.setUnitInternalCargo(DCSUnit:getName(), mass) + end + return self +end + --- Get the number of ammunition and in particular the number of shells, rockets, bombs and missiles a unit currently has. -- @param #UNIT self -- @return #number Total amount of ammo the unit has left. This is the sum of shells, rockets, bombs and missiles. From 70fa1cf19fd6fc241ffedea1a0c370731271a280 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 24 Jul 2021 15:54:54 +0200 Subject: [PATCH 14/68] Update ATIS.lua Correct degree output extra chars --- Moose Development/Moose/Ops/ATIS.lua | 60 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 8d5223cd0..f865f98be 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -383,13 +383,13 @@ ATIS.Alphabet = { --- Runway correction for converting true to magnetic heading. -- @type ATIS.RunwayM2T --- @field #number Caucasus 0° (East). --- @field #number Nevada +12° (East). --- @field #number Normandy -10° (West). --- @field #number PersianGulf +2° (East). --- @field #number TheChannel -10° (West). --- @field #number Syria +5° (East). --- @field #number MarianaIslands +2° (East). +-- @field #number Caucasus 0° (East). +-- @field #number Nevada +12° (East). +-- @field #number Normandy -10° (West). +-- @field #number PersianGulf +2° (East). +-- @field #number TheChannel -10° (West). +-- @field #number Syria +5° (East). +-- @field #number MarianaIslands +2° (East). ATIS.RunwayM2T={ Caucasus=0, Nevada=12, @@ -839,9 +839,9 @@ function ATIS:SetMapMarks(switch) return self end ---- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°. +--- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°. -- @param #ATIS self --- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended. +-- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended. -- @return #ATIS self function ATIS:SetRunwayHeadingsMagnetic(headings) @@ -974,12 +974,12 @@ end -- -- To get *true* from *magnetic* heading one has to add easterly or substract westerly variation, e.g -- --- A magnetic heading of 180° corresponds to a true heading of +-- A magnetic heading of 180° corresponds to a true heading of -- --- * 186° on the Caucaus map --- * 192° on the Nevada map --- * 170° on the Normany map --- * 182° on the Persian Gulf map +-- * 186° on the Caucaus map +-- * 192° on the Nevada map +-- * 170° on the Normany map +-- * 182° on the Persian Gulf map -- -- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation. -- @@ -1314,7 +1314,7 @@ function ATIS:onafterBroadcast(From, Event, To) local g= 9.80665 --[m/s^2] local M= 0.0289644 --[kg/mol] local T0=coord:GetTemperature(0)+273.15 --[K] Temp at sea level. - local TS=288.15 -- Standard Temperature assumed by Altimeter is 15°C + local TS=288.15 -- Standard Temperature assumed by Altimeter is 15°C local q=qnh*100 -- Calculate Pressure. @@ -1451,13 +1451,13 @@ function ATIS:onafterBroadcast(From, Event, To) --- Temperature and Dew Point --- --------------------------------- - -- Temperature in °C. + -- Temperature in °C. local temperature=coord:GetTemperature(height+5) - -- Dew point in °C. + -- Dew point in °C. local dewpoint=temperature-(100-self.relHumidity)/5 - -- Convert to °F. + -- Convert to °F. if self.TDegF then temperature=UTILS.CelciusToFarenheit(temperature) dewpoint=UTILS.CelciusToFarenheit(dewpoint) @@ -1901,15 +1901,15 @@ function ATIS:onafterBroadcast(From, Event, To) -- Temperature if self.TDegF then if temperature<0 then - subtitle=string.format("Temperature -%s °F", TEMPERATURE) + subtitle=string.format("Temperature -%s °F", TEMPERATURE) else - subtitle=string.format("Temperature %s °F", TEMPERATURE) + subtitle=string.format("Temperature %s °F", TEMPERATURE) end else if temperature<0 then - subtitle=string.format("Temperature -%s °C", TEMPERATURE) + subtitle=string.format("Temperature -%s °C", TEMPERATURE) else - subtitle=string.format("Temperature %s °C", TEMPERATURE) + subtitle=string.format("Temperature %s °C", TEMPERATURE) end end local _TEMPERATURE=subtitle @@ -1930,15 +1930,15 @@ function ATIS:onafterBroadcast(From, Event, To) -- Dew point if self.TDegF then if dewpoint<0 then - subtitle=string.format("Dew point -%s °F", DEWPOINT) + subtitle=string.format("Dew point -%s °F", DEWPOINT) else - subtitle=string.format("Dew point %s °F", DEWPOINT) + subtitle=string.format("Dew point %s °F", DEWPOINT) end else if dewpoint<0 then - subtitle=string.format("Dew point -%s °C", DEWPOINT) + subtitle=string.format("Dew point -%s °C", DEWPOINT) else - subtitle=string.format("Dew point %s °C", DEWPOINT) + subtitle=string.format("Dew point %s °C", DEWPOINT) end end local _DEWPOINT=subtitle @@ -2276,8 +2276,8 @@ function ATIS:onafterReport(From, Event, To, Text) -- Replace other stuff. local text=string.gsub(text, "SM", "statute miles") - local text=string.gsub(text, "°C", "degrees Celsius") - local text=string.gsub(text, "°F", "degrees Fahrenheit") + local text=string.gsub(text, "°C", "degrees Celsius") + local text=string.gsub(text, "°F", "degrees Fahrenheit") local text=string.gsub(text, "inHg", "inches of Mercury") local text=string.gsub(text, "mmHg", "millimeters of Mercury") local text=string.gsub(text, "hPa", "hecto Pascals") @@ -2397,7 +2397,7 @@ end --- Get runway from user supplied magnetic heading. -- @param #ATIS self -- @param #number windfrom Wind direction (from) in degrees. --- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130°. +-- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130°. function ATIS:GetMagneticRunway(windfrom) local diffmin=nil @@ -2440,7 +2440,7 @@ function ATIS:GetNavPoint(navpoints, runway, left) local navL=self:GetRunwayLR(nav.runway) local hdgD=UTILS.HdgDiff(navy,rwyy) - if hdgD<=15 then --We allow an error of +-15° here. + if hdgD<=15 then --We allow an error of +-15° here. if navL==nil or (navL==true and left==true) or (navL==false and left==false) then return nav end From 33d761503d34f92beb6dbb7471bedbece3d89582 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 25 Jul 2021 13:26:00 +0200 Subject: [PATCH 15/68] Fix for crates dropping is loosing track of troops --- Moose Development/Moose/Ops/CTLD.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 235f2946e..63d7985b5 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1084,7 +1084,6 @@ end -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit - -- @param #CTLD_CARGO Cargotype function CTLD:_ExtractTroops(Group, Unit) -- #1574 thanks to @bbirchnz! self:T(self.lid .. " _ExtractTroops") -- landed or hovering over load zone? @@ -1624,7 +1623,7 @@ function CTLD:_UnloadTroops(Group, Unit) local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum local dropped = cargo:WasDropped() - if type ~= CTLD_CARGO.Enum.TROOP and not dropped then + if type ~= CTLD_CARGO.Enum.TROOPS and not dropped then table.insert(loaded.Cargo,_cargo) loaded.Cratesloaded = loaded.Cratesloaded + 1 end @@ -1671,8 +1670,8 @@ function CTLD:_UnloadCrates(Group, Unit) -- Get what we have loaded local unitname = Unit:GetName() if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - -- looking for troops + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for crate local cargotable = loadedcargo.Cargo for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO @@ -1685,21 +1684,23 @@ function CTLD:_UnloadCrates(Group, Unit) end end -- cleanup load list - local loaded = {} -- #CTLD.LoadedCargo + local loaded = {} -- #CTLD.LoadedCargo loaded.Troopsloaded = 0 loaded.Cratesloaded = 0 loaded.Cargo = {} + for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum local size = cargo:GetCratesNeeded() - if type == CTLD_CARGO.Enum.TROOP then + if type == CTLD_CARGO.Enum.TROOPS then table.insert(loaded.Cargo,_cargo) - loaded.Cratesloaded = loaded.Troopsloaded + size + loaded.Troopsloaded = loaded.Troopsloaded + size end end self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass(Unit) else if IsHerc then From 2753df821664a9e6ca3f6bd9543b0eaa3f4d0132 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 28 Jul 2021 19:45:47 +0200 Subject: [PATCH 16/68] Update CSAR.lua Fix for CSAR message to all not working, added option to suppress all messaging, make destroys silent to not affect scoring --- Moose Development/Moose/Ops/CSAR.lua | 43 +++++++++------------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 943b17cdd..e87f6ece8 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -95,6 +95,8 @@ -- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters -- self.pilotmustopendoors = false -- switch to true to enable check of open doors +-- -- (added 0.1.9) +-- self.suppressmessages = false -- switch off all messaging if you want to do your own -- -- ## 2.1 Experimental Features -- @@ -214,26 +216,6 @@ CSAR = { -- @field Wrapper.Group#GROUP group Spawned group object. -- @field #number timestamp Timestamp for approach process -- @field #boolean alive Group is alive or dead/rescued --- ---- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. - ---[[ Moved to Utils --- @field #CSAR.SkipFrequencies -CSAR.SkipFrequencies = { - 214,274,291.5,295,297.5, - 300.5,304,307,309.5,311,312,312.5,316, - 320,324,328,329,330,336,337, - 342,343,348,351,352,353,358, - 363,365,368,372.5,374, - 380,381,384,389,395,396, - 414,420,430,432,435,440,450,455,462,470,485, - 507,515,520,525,528,540,550,560,570,577,580,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 - } ---]] --- All slot / Limit settings -- @type CSAR.AircraftType @@ -251,7 +233,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.8r3" +CSAR.version="0.1.9r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -378,6 +360,7 @@ function CSAR:New(Coalition, Template, Alias) self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters self.pilotmustopendoors = false -- switch to true to enable check on open doors + self.suppressmessages = false -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua @@ -642,7 +625,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _typeName = _typeName or "Pilot" if not noMessage then - self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) end if _freq then @@ -806,8 +789,7 @@ function CSAR:_EventHandler(EventData) if self:_DoubleEjection(_unitname) then return end - self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!", self.coalition, 10) - --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!", self.coalition, self.messageTime) else self:T(self.lid .. " Pilot has not taken off, ignore") end @@ -1112,7 +1094,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam player = grouptable.player, } - _woundedGroup:Destroy() + _woundedGroup:Destroy(false) self:_RemoveNameFromDownedPilots(_woundedGroupName,true) self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) @@ -1403,7 +1385,9 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) local group = _unit:GetGroup() local _clear = _clear or nil local _time = _time or self.messageTime - local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) + if not self.suppressmessages then + local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) + end -- integrate SRS if _speak and self.useSRS then local srstext = SOUNDTEXT:New(_text) @@ -1570,12 +1554,11 @@ end -- @param #number _messagetime How long to show. function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) self:T(self.lid .. " _DisplayToAllSAR") + local messagetime = _messagetime or self.messageTime for _, _unitName in pairs(self.csarUnits) do local _unit = self:_GetSARHeli(_unitName) - if _unit then - if not _messagetime then - self:_DisplayMessageToSAR(_unit, _message, _messagetime) - end + if _unit and not self.suppressmessages then + self:_DisplayMessageToSAR(_unit, _message, _messagetime) end end return self From de0436970322d90f93d8b75f991c154c2a8dacd5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 29 Jul 2021 12:46:11 +0200 Subject: [PATCH 17/68] Various for AI_CARGO, CTLD, CSAR, align with dev --- Moose Development/Moose/AI/AI_Cargo.lua | 31 +++++----- Moose Development/Moose/AI/AI_Cargo_APC.lua | 3 +- .../Moose/AI/AI_Cargo_Helicopter.lua | 58 ++++++++++++++----- Moose Development/Moose/Ops/CSAR.lua | 6 +- Moose Development/Moose/Ops/CTLD.lua | 24 +++++--- 5 files changed, 80 insertions(+), 42 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua index 5bcd94437..43d5eb358 100644 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -47,19 +47,21 @@ function AI_CARGO:New( Carrier, CargoSet ) self:SetStartState( "Unloaded" ) - self:AddTransition( "Unloaded", "Pickup", "*" ) - self:AddTransition( "Loaded", "Deploy", "*" ) + -- Board + 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( "*", "Load", "Boarding" ) - self:AddTransition( "Boarding", "Board", "Boarding" ) - self:AddTransition( "Loaded", "Board", "Loaded" ) - self:AddTransition( "Boarding", "Loaded", "Boarding" ) - self:AddTransition( "Boarding", "PickedUp", "Loaded" ) - - self:AddTransition( "Loaded", "Unload", "Unboarding" ) - self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) - self:AddTransition( "Unboarding", "Unloaded", "Unboarding" ) - self:AddTransition( "Unboarding", "Deployed", "Unloaded" ) + -- Unload + self:AddTransition( "Loaded", "Deploy", "*" ) + self:AddTransition( "*", "Unload", "*" ) + self:AddTransition( "*", "Unboard", "*" ) + self:AddTransition( "*", "Unloaded", "Unloaded" ) + self:AddTransition( "Unloaded", "Deployed", "Unloaded" ) + --- Pickup Handler OnBefore for AI_CARGO -- @function [parent=#AI_CARGO] OnBeforePickup @@ -393,7 +395,7 @@ 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() and From == "Boarding" then + 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 ) @@ -509,7 +511,7 @@ 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() and From == "Unboarding" then + if Carrier and Carrier:IsAlive() then if not Cargo:IsUnLoaded() then self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend ) return @@ -580,4 +582,3 @@ function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend end end - diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index bee08e1b1..6bc78debb 100644 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -98,7 +98,8 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) self:AddTransition( "*", "Guard", "Unloaded" ) self:AddTransition( "*", "Home", "*" ) self:AddTransition( "*", "Reload", "Boarding" ) - + self:AddTransition( "*", "Deployed", "*" ) + self:AddTransition( "*", "PickedUp", "*" ) self:AddTransition( "*", "Destroyed", "Destroyed" ) self:SetCombatRadius( CombatRadius ) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 3a83a48ae..2c1d5c028 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -64,20 +64,24 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 ) self:SetStartState( "Unloaded" ) + -- Boarding + self:AddTransition( "Unloaded", "Pickup", "Unloaded" ) + self:AddTransition( "*", "Landed", "*" ) + self:AddTransition( "*", "Load", "*" ) + self:AddTransition( "*", "Loaded", "Loaded" ) + self:AddTransition( "Loaded", "PickedUp", "Loaded" ) - self:AddTransition( "Unloaded", "Pickup", "*" ) - self:AddTransition( "Loaded", "Deploy", "*" ) - self:AddTransition( "*", "Loaded", "Loaded" ) - self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) - self:AddTransition( "Unloaded", "Unboard", "Unloaded" ) - self:AddTransition( "Unloaded", "Unloaded", "Unloaded" ) - self:AddTransition( "*", "PickedUp", "*" ) - self:AddTransition( "*", "Landed", "*" ) - self:AddTransition( "*", "Queue", "*" ) - self:AddTransition( "*", "Orbit" , "*" ) + -- Unboarding + 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" ) + + -- RTB self:AddTransition( "*", "Home" , "*" ) - - self:AddTransition( "*", "Destroyed", "Destroyed" ) --- Pickup Handler OnBefore for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup @@ -207,6 +211,9 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:SetCarrier( Helicopter ) + self.landingspeed = 15 -- kph + self.landingheight = 5.5 -- meter + return self end @@ -255,6 +262,25 @@ function AI_CARGO_HELICOPTER:SetCarrier( Helicopter ) return self end +--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing. +-- @param #AI_CARGO_HELICOPTER self +-- @param #number speed Landing speed in kph(!), e.g. 15 +-- @param #number height Landing height in meters(!), e.g. 5.5 +-- @return #AI_CARGO_HELICOPTER self +-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so: +-- +-- BASE:TraceOn() +-- BASE:TraceClass("AI_CARGO_HELICOPTER") +-- +-- Watch the DCS.log for entries stating `Helicopter:, Height = Helicopter:, Velocity = Helicopter:` +-- Adjust if necessary. +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 --- @param #AI_CARGO_HELICOPTER self -- @param Wrapper.Group#GROUP Helicopter @@ -271,13 +297,13 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) -- 1 - When the helo lands normally on the ground. -- 2 - when the helo is hit and goes RTB or even when it is destroyed. -- For point 2, this is an issue, the infantry may not unload in this case! - -- So we check if the helo is on the ground, and velocity< 5. + -- So we check if the helo is on the ground, and velocity< 15. -- Only then the infantry can unload (and load too, for consistency)! - self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } ) + self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } ) if self.RoutePickup == true then - if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 15 then + if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then --self:Load( Helicopter:GetPointVec2() ) self:Load( self.PickupZone ) self.RoutePickup = false @@ -285,7 +311,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) end if self.RouteDeploy == true then - if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 15 then + if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then self:Unload( self.DeployZone ) self.RouteDeploy = false end diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index e87f6ece8..b18fbb93b 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1,4 +1,3 @@ - --- **Ops** -- Combat Search and Rescue. -- -- === @@ -1125,6 +1124,8 @@ end -- @return #boolean outcome The outcome. function CSAR:_IsLoadingDoorOpen( unit_name ) self:T(self.lid .. " _IsLoadingDoorOpen") + + --[[ local ret_val = false local unit = Unit.getByName(unit_name) if unit ~= nil then @@ -1156,8 +1157,9 @@ function CSAR:_IsLoadingDoorOpen( unit_name ) return ret_val end -- nil + --]] + return UTILS.IsLoadingDoorOpen(unit_name) - return false end --- (Internal) Function to check if heli is close to group. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 63d7985b5..b91663397 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -659,6 +659,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- setup self.CrateDistance = 30 -- list/load crates in this radius + self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor self.prefixes = Prefixes or {"Cargoheli"} --self.I({prefixes = self.prefixes}) self.useprefix = true @@ -1116,8 +1117,10 @@ end nearestDistance = distance end end - - if nearestGroup == nil or nearestDistance > self.CrateDistance then + + local extractdistance = self.CrateDistance * self.ExtractFactor + + if nearestGroup == nil or nearestDistance > extractdistance then self:_SendMessage("No units close enough to extract!", 10, false, Group) return self end @@ -1165,7 +1168,7 @@ end -- clean up: table.remove(self.DroppedTroops, nearestGroupIndex) - nearestGroup:Destroy() + nearestGroup:Destroy(false) end return self end @@ -1390,8 +1393,13 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- get nearby crates local finddist = self.CrateDistance or 30 local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table - if number == 0 or numberonboard == cratelimit then - self:_SendMessage("Sorry no loadable crates nearby or fully loaded!", 10, false, Group) + if number == 0 and self.hoverautoloading then + return -- exit + elseif number == 0 then + self:_SendMessage("Sorry no loadable crates nearby!", 10, false, Group) + return -- exit + elseif numberonboard == cratelimit then + self:_SendMessage("Sorry no fully loaded!", 10, false, Group) return -- exit else -- go through crates and load @@ -1415,7 +1423,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) table.insert(loaded.Cargo, crate) table.insert(crateidsloaded,crate:GetID()) -- destroy crate - crate:GetPositionable():Destroy() + crate:GetPositionable():Destroy(false) crate.Positionable = nil self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) self:_UpdateUnitCargoMass(Unit) @@ -1967,7 +1975,7 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) if name == nametype then -- matching crate type table.insert(destIDs,thisID) found = found + 1 - nowcrate:GetPositionable():Destroy() + nowcrate:GetPositionable():Destroy(false) nowcrate.Positionable = nil end if found == numberdest then break end -- got enough @@ -2651,7 +2659,7 @@ end if self.hoverautoloading then for _,_pilot in pairs (self.CtldUnits) do local Unit = UNIT:FindByName(_pilot) - self:AutoHoverLoad(Unit) + if self:CanHoverLoad(Unit) then self:AutoHoverLoad(Unit) end end end return self From c6268488de5ab5d22797f882afc0e1edd875095c Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 2 Aug 2021 19:03:07 +0200 Subject: [PATCH 18/68] Update Utils.lua --- Moose Development/Moose/Utilities/Utils.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index fae53bb17..905dc6c19 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1535,8 +1535,8 @@ function UTILS.IsLoadingDoorOpen( unit_name ) if unit ~= nil then local type_name = unit:getTypeName() - if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then - BASE:T(unit_name .. " Cargo doors are open or cargo door not present") + if type_name == "Mi-8MT" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then + BASE:T(unit_name .. " Cargo doors are open or cargo door not present") ret_val = true end From 4797665939f06aed93111f9808f9deba106079f4 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 2 Aug 2021 22:02:21 +0200 Subject: [PATCH 19/68] Update Range.lua - Added demo by shagrat --- Moose Development/Moose/Functional/Range.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 7ba9dcfc7..9e47bb30d 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -26,13 +26,15 @@ -- === -- -- ## Youtube Videos: - -- +-- -- * [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) -- * [MOOSE - On the Range - Demonstration Video](https://www.youtube.com/watch?v=kIXcxNB9_3M) -- -- === -- --- ## Missions: Example missions will be added later. +-- ## Missions: +-- +-- * [MAR - On the Range - MOOSE - SC](https://www.digitalcombatsimulator.com/en/files/3317765/) by shagrat -- -- === -- From 59857ed79d596afdfa1e1b68b66fcc0f8abcab81 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 18 Aug 2021 09:29:53 +0200 Subject: [PATCH 20/68] CSAR - added changes from Development for AFB landings and safer calculation of distances of lost pilots --- Moose Development/Moose/Ops/CSAR.lua | 68 +++++++++++++++++++--------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index b18fbb93b..2ec378641 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -22,7 +22,7 @@ -- @module Ops.CSAR -- @image OPS_CSAR.jpg --- Date: July 2021 +-- Date: Aug 2021 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -69,6 +69,7 @@ -- -- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. -- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! +-- self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. -- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. -- self.autosmokedistance = 1000 -- distance for autosmoke -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. @@ -232,7 +233,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.9r1" +CSAR.version="0.1.10r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -342,6 +343,7 @@ function CSAR:New(Coalition, Template, Alias) self.loadtimemax = 135 -- seconds self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase + self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. self.max_units = 6 --max number of pilots that can be carried self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! @@ -875,11 +877,14 @@ function CSAR:_EventHandler(EventData) end if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then + self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true) + --[[ if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_event.IniUnitName) then self:_DisplayMessageToSAR(_unit, "Open the door to let me out!", self.messageTime, true) else self:_RescuePilots(_unit) end + --]] else self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) end @@ -1298,7 +1303,8 @@ end -- @param #CSAR self -- @param #string heliname Heli name -- @param #string groupname Group name -function CSAR:_ScheduledSARFlight(heliname,groupname) +-- @param #boolean isairport If true, EVENT.Landing took place at an airport or FARP +function CSAR:_ScheduledSARFlight(heliname,groupname, isairport) self:T(self.lid .. " _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit = self:_GetSARHeli(heliname) @@ -1321,8 +1327,8 @@ function CSAR:_ScheduledSARFlight(heliname,groupname) return end - if _dist < 200 and _heliUnit:InAir() == false then - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(heliname) then + if ( _dist < self.FARPRescueDistance or isairport ) and _heliUnit:InAir() == false then + if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true) else self:_RescuePilots(_heliUnit) @@ -1331,7 +1337,7 @@ function CSAR:_ScheduledSARFlight(heliname,groupname) end --queue up - self:__Returning(-5,heliname,_woundedGroupName) + self:__Returning(-5,heliname,_woundedGroupName, isairport) return self end @@ -1487,10 +1493,17 @@ function CSAR:_GetClosestDownedPilot(_heli) local _shortestDistance = -1 local _distance = 0 local _closestGroupInfo = nil - local _heliCoord = _heli:GetCoordinate() + local _heliCoord = _heli:GetCoordinate() or _heli:GetCoordinate() + + if _heliCoord == nil then + self:E("****Error obtaining coordinate!") + return nil + end local DownedPilotsTable = self.downedPilots - for _, _groupInfo in pairs(DownedPilotsTable) do + + for _, _groupInfo in UTILS.spairs(DownedPilotsTable) do + --for _, _groupInfo in pairs(DownedPilotsTable) do local _woundedName = _groupInfo.name local _tempWounded = _groupInfo.group @@ -1732,9 +1745,21 @@ end function CSAR:_GetDistance(_point1, _point2) self:T(self.lid .. " _GetDistance") if _point1 and _point2 then - local distance = _point1:DistanceFromPointVec2(_point2) - return distance + local distance1 = _point1:Get2DDistance(_point2) + local distance2 = _point1:DistanceFromPointVec2(_point2) + self:I({dist1=distance1, dist2=distance2}) + 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 @@ -1900,19 +1925,19 @@ end -- @param #CSAR self function CSAR:_CheckDownedPilotTable() local pilots = self.downedPilots - for _,_entry in pairs (pilots) do - self:T("Checking for " .. _entry.name) - self:T({entry=_entry}) - local group = _entry.group - if not group:IsAlive() then - self:T("Group is dead") - if _entry.alive == true then - self:T("Switching .alive to false") + 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) - self:_RemoveNameFromDownedPilots(_entry.name,true) end end end + self.downedPilots = npilots return self end @@ -2032,9 +2057,10 @@ end -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. -- @param #string Woundedgroupname Name of the downed pilot\'s group. -function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) +-- @param #boolean IsAirport True if heli has landed on an AFB (from event land). +function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname, IsAirPort) self:T({From, Event, To, Heliname, Woundedgroupname}) - self:_ScheduledSARFlight(Heliname,Woundedgroupname) + self:_ScheduledSARFlight(Heliname,Woundedgroupname, IsAirPort) return self end From 1a7fb3c13e62ab46a706fbad85286d3ec53649cf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 18 Aug 2021 11:36:33 +0200 Subject: [PATCH 21/68] Fix for degree sign extra char --- Moose Development/Moose/Core/Point.lua | 28 +++--- .../Moose/Functional/Detection.lua | 10 +- Moose Development/Moose/Functional/Fox.lua | 6 +- .../Moose/Functional/PseudoATC.lua | 8 +- Moose Development/Moose/Functional/Range.lua | 18 ++-- Moose Development/Moose/Ops/ATIS.lua | 60 ++++++------ Moose Development/Moose/Ops/Airboss.lua | 98 +++++++++---------- .../Moose/Tasking/DetectionManager.lua | 2 +- .../Moose/Tasking/Task_Cargo_Dispatcher.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 8 +- Moose Development/Moose/Wrapper/Airbase.lua | 6 +- 11 files changed, 123 insertions(+), 123 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index f79d54593..ba77d7dda 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -806,13 +806,13 @@ do -- COORDINATE --- Returns a text of the temperature according the measurement system @{Settings}. -- The text will reflect the temperature like this: -- - -- - For Russian and European aircraft using the metric system - Degrees Celcius (°C) - -- - For Americain aircraft we link to the imperial system - Degrees Farenheit (°F) + -- - For Russian and European aircraft using the metric system - Degrees Celcius (°C) + -- - For Americain aircraft we link to the imperial system - Degrees Farenheit (°F) -- -- A text containing a pressure will look like this: -- - -- - `Temperature: %n.d °C` - -- - `Temperature: %n.d °F` + -- - `Temperature: %n.d °C` + -- - `Temperature: %n.d °F` -- -- @param #COORDINATE self -- @param height (Optional) parameter specifying the height ASL. @@ -825,9 +825,9 @@ do -- COORDINATE if DegreesCelcius then if Settings:IsMetric() then - return string.format( " %-2.2f °C", DegreesCelcius ) + return string.format( " %-2.2f °C", DegreesCelcius ) else - return string.format( " %-2.2f °F", UTILS.CelciusToFarenheit( DegreesCelcius ) ) + return string.format( " %-2.2f °F", UTILS.CelciusToFarenheit( DegreesCelcius ) ) end else return " no temperature" @@ -945,13 +945,13 @@ do -- COORDINATE --- Returns a text documenting the wind direction (from) and strength according the measurement system @{Settings}. -- The text will reflect the wind like this: -- - -- - For Russian and European aircraft using the metric system - Wind direction in degrees (°) and wind speed in meters per second (mps). - -- - For Americain aircraft we link to the imperial system - Wind direction in degrees (°) and wind speed in knots per second (kps). + -- - For Russian and European aircraft using the metric system - Wind direction in degrees (°) and wind speed in meters per second (mps). + -- - For Americain aircraft we link to the imperial system - Wind direction in degrees (°) and wind speed in knots per second (kps). -- -- A text containing a pressure will look like this: -- - -- - `Wind: %n ° at n.d mps` - -- - `Wind: %n ° at n.d kps` + -- - `Wind: %n ° at n.d mps` + -- - `Wind: %n ° at n.d kps` -- -- @param #COORDINATE self -- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground. @@ -964,9 +964,9 @@ do -- COORDINATE if Direction and Strength then if Settings:IsMetric() then - return string.format( " %d ° at %3.2f mps", Direction, UTILS.MpsToKmph( Strength ) ) + 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 ) ) + return string.format( " %d ° at %3.2f kps", Direction, UTILS.MpsToKnots( Strength ) ) end else return " no wind" @@ -998,7 +998,7 @@ do -- COORDINATE local AngleDegrees = UTILS.Round( UTILS.ToDegree( AngleRadians ), Precision ) - local s = string.format( '%03d°', AngleDegrees ) + local s = string.format( '%03d°', AngleDegrees ) return s end @@ -1085,7 +1085,7 @@ do -- COORDINATE function COORDINATE:GetHeadingText( Settings ) local Heading = self:GetHeading() if Heading then - return string.format( " bearing %3d°", Heading ) + return string.format( " bearing %3d°", Heading ) else return " bearing unknown" end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index eb9b2e982..4942b57bf 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -153,12 +153,12 @@ do -- DETECTION_BASE -- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. -- - -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. + -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. -- -- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: - -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% + -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% -- - -- Use the method @{Functional.Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. + -- Use the method @{Functional.Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. -- -- ### Cloudy Zones detection probability -- @@ -1155,10 +1155,10 @@ do -- DETECTION_BASE --- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. -- - -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. + -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. -- -- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: - -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% + -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% -- @param #DETECTION_BASE self -- @param AlphaAngleProbability The probability factor. -- @return #DETECTION_BASE self diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 5c5ec9f20..403ddc675 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -791,7 +791,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) if (missile.targetPlayer and player.unitname==missile.targetPlayer.unitname) or (distance0°, at a distance of ~1 NM. --- At the beginning of the groove (X), he significantly overshoots to the starboard side (LUE<5°). +-- The pilot approaches the carrier from the port side, LUE>0°, at a distance of ~1 NM. +-- At the beginning of the groove (X), he significantly overshoots to the starboard side (LUE<5°). -- In the middle (IM), he performs good corrections and smoothly reduces the lineup error. --- Finally, at a distance of ~0.3 NM (IC) he has corrected his lineup with the runway to a reasonable level, |LUE|<0.5°. +-- Finally, at a distance of ~0.3 NM (IC) he has corrected his lineup with the runway to a reasonable level, |LUE|<0.5°. -- -- ## Glideslope Error -- @@ -876,7 +876,7 @@ -- The graph displays the glideslope error (GSE) as a function of the distance to the carrier. -- -- In this case the pilot already enters the groove (X) below the optimal glideslope. He is not able to correct his height in the IM part and --- stays significantly too low. In close, he performs a harsh correction to gain altitude and ends up even slightly too high (GSE>0.5°). +-- stays significantly too low. In close, he performs a harsh correction to gain altitude and ends up even slightly too high (GSE>0.5°). -- At his point further corrections are necessary. -- -- ## Angle of Attack @@ -2438,7 +2438,7 @@ end -- @param #number duration Default duration of the recovery in minutes. Default 30 min. -- @param #number windondeck Default wind on deck in knots. Default 25 knots. -- @param #boolean uturn U-turn after recovery window closes on=true or off=false/nil. Default off. --- @param #number offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. +-- @param #number offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. -- @return #AIRBOSS self function AIRBOSS:SetMenuRecovery(duration, windondeck, uturn, offset) @@ -3878,7 +3878,7 @@ function AIRBOSS:_CheckRecoveryTimes() -- Check if time is less than 5 minutes. if nextwindow.WIND and nextwindow.START-time 5° different from the current heading. + -- Check that wind is blowing from a direction > 5° different from the current heading. local hdg=self:GetHeading() local wind=self:GetHeadingIntoWind() local delta=self:_GetDeltaHeading(hdg, wind) @@ -3896,7 +3896,7 @@ function AIRBOSS:_CheckRecoveryTimes() end --Debug info - 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))) + 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))) -- Time into the wind 1 day or if longer recovery time + the 5 min early. local t=math.max(nextwindow.STOP-nextwindow.START+self.dTturn, 60*60*24) @@ -6933,7 +6933,7 @@ function AIRBOSS:_AddMarshalGroup(flight, stack) -- For case 1 we want the BRC but above routine return FB. radial=self:GetBRC() end - local text=string.format("Select TACAN %03d°, channel %d%s (%s)", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) + local text=string.format("Select TACAN %03d°, channel %d%s (%s)", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) self:MessageToPlayer(flight, text, nil, "") end @@ -9346,7 +9346,7 @@ function AIRBOSS:_Initial(playerData) -- Hook down for students. 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°!" + hint=hint.." - Hook down, SAS on, Wing Sweep 68°!" else hint=hint.." - Hook down!" end @@ -11331,15 +11331,15 @@ function AIRBOSS:_AttitudeMonitor(playerData) -- Output 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)) + text=text..string.format("\nAoA=%.1f° = %.1f Units | |V|=%.1f knots", aoa, self:_AoADeg2Units(playerData, aoa), UTILS.MpsToKnots(vabs)) if self.Debug then -- Velocity vector. text=text..string.format("\nVx=%.1f Vy=%.1f Vz=%.1f m/s", velo.x, velo.y, velo.z) --Wind vector. 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) + 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) -- Get player velocity in km/h. local vplayer=playerData.unit:GetVelocityKMH() @@ -11360,14 +11360,14 @@ function AIRBOSS:_AttitudeMonitor(playerData) 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)) + 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) + text=text..string.format("\nGamma=%.1f° | Rho=%.1f°", relhead, phi) end MESSAGE:New(text, 1, nil , true):ToClient(playerData.client) @@ -12695,7 +12695,7 @@ function AIRBOSS:_PlayerHint(playerData, delay, soundoff) if self.holdingoffset<0 then turn="left" end - hint=hint..string.format("\nTurn %s and select TACAN %03d°.", turn, radial) + hint=hint..string.format("\nTurn %s and select TACAN %03d°.", turn, radial) end end @@ -12704,7 +12704,7 @@ function AIRBOSS:_PlayerHint(playerData, delay, soundoff) 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°." + hint=hint.."\nFAF! Checks completed. Nozzles 50°." else --TODO: Tomcat? hint=hint.."\nDirty up! Hook, gear and flaps down." @@ -12773,14 +12773,14 @@ function AIRBOSS:_StepHint(playerData, step) -- Late break. 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." + hint=hint.."\nWing Sweep 20°, Gear DOWN < 280 KIAS." end end -- Abeam. if step==AIRBOSS.PatternStep.ABEAM then if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - hint=hint.."\nNozzles 50°-60°. Antiskid OFF. Lights OFF." + 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 @@ -13215,7 +13215,7 @@ function AIRBOSS:_Debrief(playerData) end -- Re-enter message. - local text=string.format("fly heading %03d° for %d NM to re-enter the pattern.", heading, UTILS.MetersToNM(distance)) + 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 @@ -13411,9 +13411,9 @@ function AIRBOSS:_CheckCollisionCoord(coordto, coordfrom) local text="" if clear then - text=string.format("Path into direction %03d° is clear for the next %.1f NM.", direction, UTILS.MetersToNM(d)) + 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) + text=string.format("Detected obstacle at distance %.1f NM into direction %03d°.", UTILS.MetersToNM(d), direction) end self:T2(self.lid..text) @@ -13473,7 +13473,7 @@ function AIRBOSS:_Pathfinder() local collision=self:_CheckFreePathToNextWP(fromcoord) -- Debug info. - self:T2(self.lid..string.format("Pathfinder d=%.1f m, direction=%03d°, collision=%s", distance, direction, tostring(collision))) + self:T2(self.lid..string.format("Pathfinder d=%.1f m, direction=%03d°, collision=%s", distance, direction, tostring(collision))) -- If path is clear, we start a little detour. if not collision then @@ -13907,7 +13907,7 @@ function AIRBOSS:_CheckPatternUpdate() -- Update if carrier moves by more than 2.5 NM. local Dupdate=UTILS.NMToMeters(2.5) - -- Update if carrier turned by more than 5°. + -- Update if carrier turned by more than 5°. local Hupdate=5 ----------------------- @@ -13941,7 +13941,7 @@ function AIRBOSS:_CheckPatternUpdate() -- Check if orientation changed. local Hchange=false if math.abs(deltaHeading)>=Hupdate then - self:T(self.lid..string.format("Carrier heading changed by %d°.", deltaHeading)) + self:T(self.lid..string.format("Carrier heading changed by %d°.", deltaHeading)) Hchange=true end @@ -15642,7 +15642,7 @@ end function AIRBOSS:_MarshalCallNewFinalBearing(FB) -- Subtitle. - local text=string.format("new final bearing %03d°.", FB) + local text=string.format("new final bearing %03d°.", FB) -- Debug message. self:I(self.lid..text) @@ -15665,7 +15665,7 @@ end function AIRBOSS:_MarshalCallCarrierTurnTo(hdg) -- Subtitle. - local text=string.format("carrier is now starting turn to heading %03d°.", hdg) + local text=string.format("carrier is now starting turn to heading %03d°.", hdg) -- Debug message. self:I(self.lid..text) @@ -15718,11 +15718,11 @@ function AIRBOSS:_MarshalCallRecoveryStart(case) -- Debug output. local text=string.format("Starting aircraft recovery Case %d ops.", case) if case==1 then - text=text..string.format(" BRC %03d°.", self:GetBRC()) + text=text..string.format(" BRC %03d°.", self:GetBRC()) elseif case==2 then - text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC()) + 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)) + text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false)) end self:T(self.lid..text) @@ -15767,7 +15767,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) local CT=UTILS.Split(clock[1], ":") -- Subtitle text. - 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) + 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) -- Debug message. self:I(self.lid..text) @@ -15944,11 +15944,11 @@ function AIRBOSS:_AddF10Commands(_unitName) 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, "+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) @@ -16005,7 +16005,7 @@ function AIRBOSS:_SkipperStartRecovery(_unitName, case) -- Inform player. 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) + text=text..string.format(" Marshal radial %d°.", self.skipperOffset) end if self:IsRecovering() then text="negative, carrier is already recovering." @@ -16071,7 +16071,7 @@ function AIRBOSS:_SkipperRecoveryOffset(_unitName, offset) if playerData then -- Inform player. - local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) + local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) self:MessageToPlayer(playerData, text, "AIRBOSS") self.skipperOffset=offset @@ -16534,7 +16534,7 @@ function AIRBOSS:_RequestCommence(_unitName) -- For case 1 we want the BRC but above routine return FB. radial=self:GetBRC() end - text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) + text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) end -- TODO: Inform section members. @@ -17146,7 +17146,7 @@ function AIRBOSS:_DisplayCarrierInfo(_unitname) -- Only include current and future recovery windows. if Tabs1 then -- Get inverse magnetic radial potential offset. local radial=self:GetRadial(playerData.case, true, true, true) - stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n", radial, angels+15) + stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n", radial, angels+15) end end @@ -17545,7 +17545,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName) local brc=self:GetBRC() -- Help player to find its way to the initial zone. - text=text..string.format("\nTo Initial: Fly heading %03d° for %.1f NM and turn to BRC %03d°", flyhdg, flydist, brc) + 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 @@ -17560,7 +17560,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName) local hdg=self:GetRadial(playerData.case, true, true, true) -- Help player to find its way to the initial zone. - text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°", flyhdg, flydist, hdg) + text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°", flyhdg, flydist, hdg) end diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 3e16fad29..2e0a3e148 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -286,7 +286,7 @@ do -- DETECTION MANAGER self.CC:MessageToCoalition( Message ) end - Message = Message:gsub( "°", " degrees " ) + Message = Message:gsub( "°", " degrees " ) Message = Message:gsub( "(%d)%.(%d)", "%1 dot %2" ) -- Here we handle the transmission of the voice over. diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua index 6833f197c..50fd4b86f 100644 --- a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua @@ -574,7 +574,7 @@ do -- TASK_CARGO_DISPATCHER -- local Coordinate = PlaneUnit:GetPointVec2() -- TaskA2ADispatcher:AddCSARTask( "CSAR Task", Coordinate ) -- - -- -- Add a CSAR task to rescue a downed pilot from within a coordinate of country RUSSIA, which is pointing to the west (270°). + -- -- Add a CSAR task to rescue a downed pilot from within a coordinate of country RUSSIA, which is pointing to the west (270°). -- local Coordinate = PlaneUnit:GetPointVec2() -- TaskA2ADispatcher:AddCSARTask( "CSAR Task", Coordinate, 270, Country.RUSSIA ) -- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 905dc6c19..11a4ed681 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -492,8 +492,8 @@ UTILS.tostringLL = function( lat, lon, acc, DMS) end -- 024� 23' 12"N or 024� 23' 12.03"N - 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 + 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 -- degrees, decimal minutes. latMin = UTILS.Round(latMin, acc) @@ -518,8 +518,8 @@ UTILS.tostringLL = function( lat, lon, acc, DMS) end -- 024 23'N or 024 23.123'N - return string.format('%03d°', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' - .. string.format('%03d°', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi + return string.format('%03d°', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' + .. string.format('%03d°', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi end end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 223d459b6..99afa080c 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -509,7 +509,7 @@ AIRBASE.TerminalType = { --- Runway data. -- @type AIRBASE.Runway -- @field #number heading Heading of the runway in degrees. --- @field #string idx Runway ID: heading 070° ==> idx="07". +-- @field #string idx Runway ID: heading 070° ==> idx="07". -- @field #number length Length of runway in meters. -- @field Core.Point#COORDINATE position Position of runway start. -- @field Core.Point#COORDINATE endpoint End point of runway. @@ -1516,7 +1516,7 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Heading of runway. local hdg=c1:HeadingTo(c2) - -- Runway ID: heading=070° ==> idx="07" + -- Runway ID: heading=070° ==> idx="07" local idx=string.format("%02d", UTILS.Round((hdg-magvar)/10, 0)) -- Runway table. @@ -1594,7 +1594,7 @@ function AIRBASE:GetActiveRunway(magvar) local dot=UTILS.VecDot(Vwind, Vrunway) -- Debug. - --env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot)) + --env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot)) -- New min? if dotmin==nil or dot Date: Wed, 18 Aug 2021 11:47:25 +0200 Subject: [PATCH 22/68] more corrections --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 11a4ed681..22a5d35d4 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1510,7 +1510,7 @@ end --@return #table function UTILS.ShuffleTable(t) if t == nil or type(t) ~= "table" then - BASE:I("Error in ShuffleTable: Missing or wrong tyÃ¥e of Argument") + BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") return end math.random() From f3f63ab8aa2184453c0415d6e1bb0536345fca65 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 18 Aug 2021 11:52:13 +0200 Subject: [PATCH 23/68] Fix for degree sign extra char --- Moose Development/Moose/Functional/Fox.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 403ddc675..56dd5f51f 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -1707,8 +1707,8 @@ end --- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. -- @param #FOX self -- @param DCS#Weapon weapon The weapon. --- @return #number Notching heading right, i.e. missile heading +90� --- @return #number Notching heading left, i.e. missile heading -90�. +-- @return #number Notching heading right, i.e. missile heading +90°. +-- @return #number Notching heading left, i.e. missile heading -90°. function FOX:_GetNotchingHeadings(weapon) if weapon then From 20f28b3d2c972d0aa40bf44a1fc4586b818b0e3f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 18 Aug 2021 15:01:14 +0200 Subject: [PATCH 24/68] Fix for SAM pattern matching not working --- Moose Development/Moose/Functional/Sead.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index c99e1f241..008b9ae01 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -92,7 +92,7 @@ function SEAD:New( SEADGroupPrefixes ) end self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) - self:I("*** SEAD - Started Version 0.2.8") + self:I("*** SEAD - Started Version 0.2.9") return self end @@ -102,7 +102,7 @@ end -- @return #SEAD self function SEAD:UpdateSet( SEADGroupPrefixes ) - self:F( SEADGroupPrefixes ) + self:T( SEADGroupPrefixes ) if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do @@ -120,7 +120,7 @@ end -- @param #number range Set the engagement range in percent, e.g. 50 -- @return self function SEAD:SetEngagementRange(range) - self:F( { range } ) + self:T( { range } ) range = range or 75 if range < 0 or range > 100 then range = 75 @@ -135,7 +135,7 @@ end -- @param #string WeaponName -- @return #boolean Returns true for a match function SEAD:_CheckHarms(WeaponName) - self:F( { WeaponName } ) + self:T( { WeaponName } ) local hit = false for _,_name in pairs (SEAD.Harms) do if string.find(WeaponName,_name,1) then hit = true end @@ -166,7 +166,7 @@ function SEAD:HandleEventShot( EventData ) local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object if _targetUnit and _targetUnit:IsAlive() then local _targetMimgroup = _targetUnit:GetGroup() - local _targetMimgroupName = _targetMimgroup:GetName() -- group name + _targetMimgroupName = _targetMimgroup:GetName() -- group name --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill self:T( self.SEADGroupPrefixes ) self:T( _targetMimgroupName ) @@ -174,6 +174,7 @@ function SEAD:HandleEventShot( EventData ) -- see if we are shot at local SEADGroupFound = false for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do + self:T( SEADGroupPrefix ) if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then SEADGroupFound = true self:T( '*** SEAD - Group Found' ) From c7ea45e5fd7d053074cd135d1e7657618b10f659 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 18 Aug 2021 18:01:04 +0200 Subject: [PATCH 25/68] Clean up UTF-8 mess --- Moose Development/Moose/Core/Point.lua | 28 +++--- .../Moose/Functional/Detection.lua | 10 +- Moose Development/Moose/Functional/Fox.lua | 10 +- .../Moose/Functional/MissileTrainer.lua | 2 +- .../Moose/Functional/PseudoATC.lua | 8 +- Moose Development/Moose/Functional/RAT.lua | 2 +- Moose Development/Moose/Functional/Range.lua | 18 ++-- Moose Development/Moose/Ops/ATIS.lua | 60 ++++++------ Moose Development/Moose/Ops/Airboss.lua | 98 +++++++++---------- Moose Development/Moose/Sound/RadioSpeech.lua | 37 ++++--- .../Moose/Tasking/DetectionManager.lua | 2 +- .../Moose/Tasking/Task_Cargo_Dispatcher.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 10 +- Moose Development/Moose/Wrapper/Airbase.lua | 6 +- 14 files changed, 146 insertions(+), 147 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index ba77d7dda..f79d54593 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -806,13 +806,13 @@ do -- COORDINATE --- Returns a text of the temperature according the measurement system @{Settings}. -- The text will reflect the temperature like this: -- - -- - For Russian and European aircraft using the metric system - Degrees Celcius (°C) - -- - For Americain aircraft we link to the imperial system - Degrees Farenheit (°F) + -- - For Russian and European aircraft using the metric system - Degrees Celcius (°C) + -- - For Americain aircraft we link to the imperial system - Degrees Farenheit (°F) -- -- A text containing a pressure will look like this: -- - -- - `Temperature: %n.d °C` - -- - `Temperature: %n.d °F` + -- - `Temperature: %n.d °C` + -- - `Temperature: %n.d °F` -- -- @param #COORDINATE self -- @param height (Optional) parameter specifying the height ASL. @@ -825,9 +825,9 @@ do -- COORDINATE if DegreesCelcius then if Settings:IsMetric() then - return string.format( " %-2.2f °C", DegreesCelcius ) + return string.format( " %-2.2f °C", DegreesCelcius ) else - return string.format( " %-2.2f °F", UTILS.CelciusToFarenheit( DegreesCelcius ) ) + return string.format( " %-2.2f °F", UTILS.CelciusToFarenheit( DegreesCelcius ) ) end else return " no temperature" @@ -945,13 +945,13 @@ do -- COORDINATE --- Returns a text documenting the wind direction (from) and strength according the measurement system @{Settings}. -- The text will reflect the wind like this: -- - -- - For Russian and European aircraft using the metric system - Wind direction in degrees (°) and wind speed in meters per second (mps). - -- - For Americain aircraft we link to the imperial system - Wind direction in degrees (°) and wind speed in knots per second (kps). + -- - For Russian and European aircraft using the metric system - Wind direction in degrees (°) and wind speed in meters per second (mps). + -- - For Americain aircraft we link to the imperial system - Wind direction in degrees (°) and wind speed in knots per second (kps). -- -- A text containing a pressure will look like this: -- - -- - `Wind: %n ° at n.d mps` - -- - `Wind: %n ° at n.d kps` + -- - `Wind: %n ° at n.d mps` + -- - `Wind: %n ° at n.d kps` -- -- @param #COORDINATE self -- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground. @@ -964,9 +964,9 @@ do -- COORDINATE if Direction and Strength then if Settings:IsMetric() then - return string.format( " %d ° at %3.2f mps", Direction, UTILS.MpsToKmph( Strength ) ) + 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 ) ) + return string.format( " %d ° at %3.2f kps", Direction, UTILS.MpsToKnots( Strength ) ) end else return " no wind" @@ -998,7 +998,7 @@ do -- COORDINATE local AngleDegrees = UTILS.Round( UTILS.ToDegree( AngleRadians ), Precision ) - local s = string.format( '%03d°', AngleDegrees ) + local s = string.format( '%03d°', AngleDegrees ) return s end @@ -1085,7 +1085,7 @@ do -- COORDINATE function COORDINATE:GetHeadingText( Settings ) local Heading = self:GetHeading() if Heading then - return string.format( " bearing %3d°", Heading ) + return string.format( " bearing %3d°", Heading ) else return " bearing unknown" end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 4942b57bf..eb9b2e982 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -153,12 +153,12 @@ do -- DETECTION_BASE -- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. -- - -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. + -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. -- -- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: - -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% + -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% -- - -- Use the method @{Functional.Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. + -- Use the method @{Functional.Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. -- -- ### Cloudy Zones detection probability -- @@ -1155,10 +1155,10 @@ do -- DETECTION_BASE --- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. -- - -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. + -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. -- -- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: - -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% + -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% -- @param #DETECTION_BASE self -- @param AlphaAngleProbability The probability factor. -- @return #DETECTION_BASE self diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 56dd5f51f..efcdc99a5 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -791,7 +791,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) if (missile.targetPlayer and player.unitname==missile.targetPlayer.unitname) or (distance0°, at a distance of ~1 NM. --- At the beginning of the groove (X), he significantly overshoots to the starboard side (LUE<5°). +-- The pilot approaches the carrier from the port side, LUE>0°, at a distance of ~1 NM. +-- At the beginning of the groove (X), he significantly overshoots to the starboard side (LUE<5°). -- In the middle (IM), he performs good corrections and smoothly reduces the lineup error. --- Finally, at a distance of ~0.3 NM (IC) he has corrected his lineup with the runway to a reasonable level, |LUE|<0.5°. +-- Finally, at a distance of ~0.3 NM (IC) he has corrected his lineup with the runway to a reasonable level, |LUE|<0.5°. -- -- ## Glideslope Error -- @@ -876,7 +876,7 @@ -- The graph displays the glideslope error (GSE) as a function of the distance to the carrier. -- -- In this case the pilot already enters the groove (X) below the optimal glideslope. He is not able to correct his height in the IM part and --- stays significantly too low. In close, he performs a harsh correction to gain altitude and ends up even slightly too high (GSE>0.5°). +-- stays significantly too low. In close, he performs a harsh correction to gain altitude and ends up even slightly too high (GSE>0.5°). -- At his point further corrections are necessary. -- -- ## Angle of Attack @@ -2438,7 +2438,7 @@ end -- @param #number duration Default duration of the recovery in minutes. Default 30 min. -- @param #number windondeck Default wind on deck in knots. Default 25 knots. -- @param #boolean uturn U-turn after recovery window closes on=true or off=false/nil. Default off. --- @param #number offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. +-- @param #number offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. -- @return #AIRBOSS self function AIRBOSS:SetMenuRecovery(duration, windondeck, uturn, offset) @@ -3878,7 +3878,7 @@ function AIRBOSS:_CheckRecoveryTimes() -- Check if time is less than 5 minutes. if nextwindow.WIND and nextwindow.START-time 5° different from the current heading. + -- Check that wind is blowing from a direction > 5° different from the current heading. local hdg=self:GetHeading() local wind=self:GetHeadingIntoWind() local delta=self:_GetDeltaHeading(hdg, wind) @@ -3896,7 +3896,7 @@ function AIRBOSS:_CheckRecoveryTimes() end --Debug info - 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))) + 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))) -- Time into the wind 1 day or if longer recovery time + the 5 min early. local t=math.max(nextwindow.STOP-nextwindow.START+self.dTturn, 60*60*24) @@ -6933,7 +6933,7 @@ function AIRBOSS:_AddMarshalGroup(flight, stack) -- For case 1 we want the BRC but above routine return FB. radial=self:GetBRC() end - local text=string.format("Select TACAN %03d°, channel %d%s (%s)", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) + local text=string.format("Select TACAN %03d°, channel %d%s (%s)", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) self:MessageToPlayer(flight, text, nil, "") end @@ -9346,7 +9346,7 @@ function AIRBOSS:_Initial(playerData) -- Hook down for students. 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°!" + hint=hint.." - Hook down, SAS on, Wing Sweep 68°!" else hint=hint.." - Hook down!" end @@ -11331,15 +11331,15 @@ function AIRBOSS:_AttitudeMonitor(playerData) -- Output 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)) + text=text..string.format("\nAoA=%.1f° = %.1f Units | |V|=%.1f knots", aoa, self:_AoADeg2Units(playerData, aoa), UTILS.MpsToKnots(vabs)) if self.Debug then -- Velocity vector. text=text..string.format("\nVx=%.1f Vy=%.1f Vz=%.1f m/s", velo.x, velo.y, velo.z) --Wind vector. 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) + 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) -- Get player velocity in km/h. local vplayer=playerData.unit:GetVelocityKMH() @@ -11360,14 +11360,14 @@ function AIRBOSS:_AttitudeMonitor(playerData) 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)) + 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) + text=text..string.format("\nGamma=%.1f° | Rho=%.1f°", relhead, phi) end MESSAGE:New(text, 1, nil , true):ToClient(playerData.client) @@ -12695,7 +12695,7 @@ function AIRBOSS:_PlayerHint(playerData, delay, soundoff) if self.holdingoffset<0 then turn="left" end - hint=hint..string.format("\nTurn %s and select TACAN %03d°.", turn, radial) + hint=hint..string.format("\nTurn %s and select TACAN %03d°.", turn, radial) end end @@ -12704,7 +12704,7 @@ function AIRBOSS:_PlayerHint(playerData, delay, soundoff) 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°." + hint=hint.."\nFAF! Checks completed. Nozzles 50°." else --TODO: Tomcat? hint=hint.."\nDirty up! Hook, gear and flaps down." @@ -12773,14 +12773,14 @@ function AIRBOSS:_StepHint(playerData, step) -- Late break. 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." + hint=hint.."\nWing Sweep 20°, Gear DOWN < 280 KIAS." end end -- Abeam. if step==AIRBOSS.PatternStep.ABEAM then if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - hint=hint.."\nNozzles 50°-60°. Antiskid OFF. Lights OFF." + 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 @@ -13215,7 +13215,7 @@ function AIRBOSS:_Debrief(playerData) end -- Re-enter message. - local text=string.format("fly heading %03d° for %d NM to re-enter the pattern.", heading, UTILS.MetersToNM(distance)) + 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 @@ -13411,9 +13411,9 @@ function AIRBOSS:_CheckCollisionCoord(coordto, coordfrom) local text="" if clear then - text=string.format("Path into direction %03d° is clear for the next %.1f NM.", direction, UTILS.MetersToNM(d)) + 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) + text=string.format("Detected obstacle at distance %.1f NM into direction %03d°.", UTILS.MetersToNM(d), direction) end self:T2(self.lid..text) @@ -13473,7 +13473,7 @@ function AIRBOSS:_Pathfinder() local collision=self:_CheckFreePathToNextWP(fromcoord) -- Debug info. - self:T2(self.lid..string.format("Pathfinder d=%.1f m, direction=%03d°, collision=%s", distance, direction, tostring(collision))) + self:T2(self.lid..string.format("Pathfinder d=%.1f m, direction=%03d°, collision=%s", distance, direction, tostring(collision))) -- If path is clear, we start a little detour. if not collision then @@ -13907,7 +13907,7 @@ function AIRBOSS:_CheckPatternUpdate() -- Update if carrier moves by more than 2.5 NM. local Dupdate=UTILS.NMToMeters(2.5) - -- Update if carrier turned by more than 5°. + -- Update if carrier turned by more than 5°. local Hupdate=5 ----------------------- @@ -13941,7 +13941,7 @@ function AIRBOSS:_CheckPatternUpdate() -- Check if orientation changed. local Hchange=false if math.abs(deltaHeading)>=Hupdate then - self:T(self.lid..string.format("Carrier heading changed by %d°.", deltaHeading)) + self:T(self.lid..string.format("Carrier heading changed by %d°.", deltaHeading)) Hchange=true end @@ -15642,7 +15642,7 @@ end function AIRBOSS:_MarshalCallNewFinalBearing(FB) -- Subtitle. - local text=string.format("new final bearing %03d°.", FB) + local text=string.format("new final bearing %03d°.", FB) -- Debug message. self:I(self.lid..text) @@ -15665,7 +15665,7 @@ end function AIRBOSS:_MarshalCallCarrierTurnTo(hdg) -- Subtitle. - local text=string.format("carrier is now starting turn to heading %03d°.", hdg) + local text=string.format("carrier is now starting turn to heading %03d°.", hdg) -- Debug message. self:I(self.lid..text) @@ -15718,11 +15718,11 @@ function AIRBOSS:_MarshalCallRecoveryStart(case) -- Debug output. local text=string.format("Starting aircraft recovery Case %d ops.", case) if case==1 then - text=text..string.format(" BRC %03d°.", self:GetBRC()) + text=text..string.format(" BRC %03d°.", self:GetBRC()) elseif case==2 then - text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC()) + 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)) + text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false)) end self:T(self.lid..text) @@ -15767,7 +15767,7 @@ function AIRBOSS:_MarshalCallArrived(modex, case, brc, altitude, charlie, qfe) local CT=UTILS.Split(clock[1], ":") -- Subtitle text. - 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) + 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) -- Debug message. self:I(self.lid..text) @@ -15944,11 +15944,11 @@ function AIRBOSS:_AddF10Commands(_unitName) 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, "+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) @@ -16005,7 +16005,7 @@ function AIRBOSS:_SkipperStartRecovery(_unitName, case) -- Inform player. 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) + text=text..string.format(" Marshal radial %d°.", self.skipperOffset) end if self:IsRecovering() then text="negative, carrier is already recovering." @@ -16071,7 +16071,7 @@ function AIRBOSS:_SkipperRecoveryOffset(_unitName, offset) if playerData then -- Inform player. - local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) + local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) self:MessageToPlayer(playerData, text, "AIRBOSS") self.skipperOffset=offset @@ -16534,7 +16534,7 @@ function AIRBOSS:_RequestCommence(_unitName) -- For case 1 we want the BRC but above routine return FB. radial=self:GetBRC() end - text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) + text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n", radial, self.TACANchannel,self.TACANmode, self.TACANmorse) end -- TODO: Inform section members. @@ -17146,7 +17146,7 @@ function AIRBOSS:_DisplayCarrierInfo(_unitname) -- Only include current and future recovery windows. if Tabs1 then -- Get inverse magnetic radial potential offset. local radial=self:GetRadial(playerData.case, true, true, true) - stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n", radial, angels+15) + stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n", radial, angels+15) end end @@ -17545,7 +17545,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName) local brc=self:GetBRC() -- Help player to find its way to the initial zone. - text=text..string.format("\nTo Initial: Fly heading %03d° for %.1f NM and turn to BRC %03d°", flyhdg, flydist, brc) + 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 @@ -17560,7 +17560,7 @@ function AIRBOSS:_DisplayPlayerStatus(_unitName) local hdg=self:GetRadial(playerData.case, true, true, true) -- Help player to find its way to the initial zone. - text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°", flyhdg, flydist, hdg) + text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°", flyhdg, flydist, hdg) end diff --git a/Moose Development/Moose/Sound/RadioSpeech.lua b/Moose Development/Moose/Sound/RadioSpeech.lua index f77c446a2..5d748cae8 100644 --- a/Moose Development/Moose/Sound/RadioSpeech.lua +++ b/Moose Development/Moose/Sound/RadioSpeech.lua @@ -162,33 +162,32 @@ RADIOSPEECH.Vocabulary.RU = { ["8000"] = { "8000", 0.92 }, ["9000"] = { "9000", 0.87 }, - ["�тõÿõýø"] = { "degrees", 0.5 }, - ["úøûþüõтрþò"] = { "kilometers", 0.65 }, + ["градуÑÑ‹"] = { "degrees", 0.5 }, + ["километры"] = { "kilometers", 0.65 }, ["km"] = { "kilometers", 0.65 }, - ["üøûь"] = { "miles", 0.45 }, + ["мили"] = { "miles", 0.45 }, ["mi"] = { "miles", 0.45 }, - ["üõтры"] = { "meters", 0.41 }, + ["метров"] = { "meters", 0.41 }, ["m"] = { "meters", 0.41 }, - ["ýþóø"] = { "feet", 0.37 }, + ["ноги"] = { "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 }, - ["òþ÷òрðщðÑ�Ñ�ÑŒ ýð ñð÷у"] = { "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"] = { "dot", 0.51 }, - ["defender"] = { "defender", 0.45 }, + ["длÑ"] = { "for", 0.27 }, + ["и"] = { "and", 0.17 }, + ["на Ñайте"] = { "at", 0.19 }, + ["точка"] = { "dot", 0.51 }, + ["защитник"] = { "defender", 0.45 }, } --- Create a new RADIOSPEECH object for a given radio frequency/modulation. diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 2e0a3e148..3e16fad29 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -286,7 +286,7 @@ do -- DETECTION MANAGER self.CC:MessageToCoalition( Message ) end - Message = Message:gsub( "°", " degrees " ) + Message = Message:gsub( "°", " degrees " ) Message = Message:gsub( "(%d)%.(%d)", "%1 dot %2" ) -- Here we handle the transmission of the voice over. diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua index 50fd4b86f..6833f197c 100644 --- a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua @@ -574,7 +574,7 @@ do -- TASK_CARGO_DISPATCHER -- local Coordinate = PlaneUnit:GetPointVec2() -- TaskA2ADispatcher:AddCSARTask( "CSAR Task", Coordinate ) -- - -- -- Add a CSAR task to rescue a downed pilot from within a coordinate of country RUSSIA, which is pointing to the west (270°). + -- -- Add a CSAR task to rescue a downed pilot from within a coordinate of country RUSSIA, which is pointing to the west (270°). -- local Coordinate = PlaneUnit:GetPointVec2() -- TaskA2ADispatcher:AddCSARTask( "CSAR Task", Coordinate, 270, Country.RUSSIA ) -- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 22a5d35d4..84abdc68f 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -491,9 +491,9 @@ UTILS.tostringLL = function( lat, lon, acc, DMS) secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' end - -- 024� 23' 12"N or 024� 23' 12.03"N - 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 + -- 024° 23' 12"N or 024° 23' 12.03"N + 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 -- degrees, decimal minutes. latMin = UTILS.Round(latMin, acc) @@ -518,8 +518,8 @@ UTILS.tostringLL = function( lat, lon, acc, DMS) end -- 024 23'N or 024 23.123'N - return string.format('%03d°', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' - .. string.format('%03d°', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi + return string.format('%03d°', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' + .. string.format('%03d°', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi end end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 99afa080c..223d459b6 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -509,7 +509,7 @@ AIRBASE.TerminalType = { --- Runway data. -- @type AIRBASE.Runway -- @field #number heading Heading of the runway in degrees. --- @field #string idx Runway ID: heading 070° ==> idx="07". +-- @field #string idx Runway ID: heading 070° ==> idx="07". -- @field #number length Length of runway in meters. -- @field Core.Point#COORDINATE position Position of runway start. -- @field Core.Point#COORDINATE endpoint End point of runway. @@ -1516,7 +1516,7 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Heading of runway. local hdg=c1:HeadingTo(c2) - -- Runway ID: heading=070° ==> idx="07" + -- Runway ID: heading=070° ==> idx="07" local idx=string.format("%02d", UTILS.Round((hdg-magvar)/10, 0)) -- Runway table. @@ -1594,7 +1594,7 @@ function AIRBASE:GetActiveRunway(magvar) local dot=UTILS.VecDot(Vwind, Vrunway) -- Debug. - --env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot)) + --env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot)) -- New min? if dotmin==nil or dot Date: Sun, 22 Aug 2021 12:02:26 +0200 Subject: [PATCH 26/68] Bug fixes. Added support for Ships as load zones --- Moose Development/Moose/Ops/CTLD.lua | 125 +++++++++++++++++++++------ 1 file changed, 99 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index b91663397..60b111f26 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: July 2021 +-- Date: Aug 2021 do ------------------------------------------------------ @@ -264,7 +264,7 @@ do -- Add zones for loading troops and crates and dropping, building crates -- -- -- Add a zone of type LOAD to our setup. Players can load troops and crates. --- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside of the zone. +-- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside the zone. -- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. -- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) -- @@ -279,6 +279,13 @@ do -- -- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true) -- +-- -- Add a zone of type SHIP to our setup. Players can load troops and crates from this ship +-- -- "Tarawa" is the unitname (callsign) of the ship from the ME. Players can load, if they are inside the zone. +-- -- The ship is 240 meters long and 20 meters wide. +-- -- Note that smoke, flares, beacons don't work for this type of loadzone (yet). Also, you need to adjust +-- -- the max hover height to deck height plus 5 meters or so for loading to work. +-- -- When the ship is moving, forcing hoverload might not be a good idea. +-- my_ctld:AddCTLDZone("Tarawa",CTLD.CargoZoneType.SHIP,SMOKECOLOR.Blue,true,true,240,20) -- -- ## 2. Options -- @@ -511,11 +518,13 @@ CTLD = { -- @field #string name Name of Zone. -- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. -- @field #boolean active Active or not. --- @field #string type Type of zone, i.e. load,drop,move +-- @field #string type Type of zone, i.e. load,drop,move,ship -- @field #boolean hasbeacon Create and run radio beacons if active. -- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon -- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon -- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #number shiplength For ships - length of ship +-- @field #number shipwidth For ships - width of ship --- Zone Type Info. -- @type CTLD.CargoZoneType @@ -523,6 +532,7 @@ CTLD.CargoZoneType = { LOAD = "load", DROP = "drop", MOVE = "move", + SHIP = "ship", } --- Buildable table info. @@ -557,7 +567,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r3" +CTLD.version="0.1.5a1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -643,6 +653,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.pickupZones = {} self.dropOffZones = {} self.wpZones = {} + self.shipZones = {} -- Cargo self.Cargo_Crates = {} @@ -928,6 +939,9 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) local hoverload = self:CanHoverLoad(Unit) -- check if we are in LOAD zone 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 inzone then self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) if not self.debug then return self end @@ -1186,8 +1200,13 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) -- check if we are in LOAD zone local inzone = false local drop = drop or false + local ship = nil + local width = 20 if not drop then inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if not inzone then + inzone, ship, zone, distance, width = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) + end else if self.dropcratesanywhere then -- #1570 inzone = true @@ -1233,18 +1252,33 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) for i=1,50 do math.random(90,270) end - local rheading = math.floor(math.random(90,270) * heading + 1 / 360) + local rheading = math.floor(((math.random(90,270) * heading) + 1) / 360) if not IsHerc then rheading = rheading + 180 -- mirror for Helis end if rheading > 360 then rheading = rheading - 360 end -- catch > 360 local cratecoord = position:Translate(cratedistance,rheading) local cratevec2 = cratecoord:GetVec2() - self.CrateCounter = self.CrateCounter + 1 + self.CrateCounter = self.CrateCounter + 1 + 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) + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",country.id.GERMANY) + --:InitCoordinate(cratecoord) + :InitLinkToUnit(Ship,dist,Offy,0) + :Spawn(270,cratealias) + else self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",country.id.GERMANY) :InitCoordinate(cratecoord) + --:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) :Spawn(270,cratealias) - + end local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() self.CargoCounter = self.CargoCounter +1 @@ -1426,11 +1460,11 @@ function CTLD:_LoadCratesNearby(Group, Unit) crate:GetPositionable():Destroy(false) crate.Positionable = nil self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) - self:_UpdateUnitCargoMass(Unit) self:__CratesPickedUp(1, Group, Unit, crate) end end self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass(Unit) -- clean up real world crates local existingcrates = self.Spawned_Cargo -- #table local newexcrates = {} @@ -1482,9 +1516,10 @@ function CTLD:_UpdateUnitCargoMass(Unit) self:T(self.lid .. " _UpdateUnitCargoMass") local calculatedMass = self:_GetUnitCargoMass(Unit) Unit:SetUnitInternalCargo(calculatedMass) - local report = REPORT:New("Loadmaster report") - report:Add("Carrying " .. calculatedMass .. "Kg") - self:_SendMessage(report:Text(),10,false,Unit:GetGroup()) + --local report = REPORT:New("Loadmaster report") + --report:Add("Carrying " .. calculatedMass .. "Kg") + --self:_SendMessage(report:Text(),10,false,Unit:GetGroup()) + return self end --- (Internal) Function to list loaded cargo. @@ -1565,6 +1600,9 @@ function CTLD:_UnloadTroops(Group, Unit) -- check if we are in LOAD zone 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 = true end @@ -1901,17 +1939,16 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) end for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 - if canmove then local alias = string.format("%s-%d", _template, math.random(1,100000)) - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitRandomizeUnits(true,20,2) - :InitDelayOff() - :SpawnFromVec2(randomcoord) + if canmove then + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - --:InitRandomizeUnits(true,20,2) - :InitDelayOff() - :SpawnFromVec2(randomcoord) + :InitDelayOff() + :SpawnFromVec2(randomcoord) end if self.movetroopstowpzone and canmove then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) @@ -2141,6 +2178,8 @@ function CTLD:AddZone(Zone) table.insert(self.pickupZones,zone) elseif zone.type == CTLD.CargoZoneType.DROP then table.insert(self.dropOffZones,zone) + elseif zone.type == CTLD.CargoZoneType.SHIP then + table.insert(self.shipZones,zone) else table.insert(self.wpZones,zone) end @@ -2166,6 +2205,8 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) table = self.pickupZones elseif ZoneType == CTLD.CargoZoneType.DROP then table = self.dropOffZones + elseif ZoneType == CTLD.CargoZoneType.SHIP then + table = self.shipZones else table = self.wpZones end @@ -2263,8 +2304,10 @@ end -- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red -- @param #string Active Is this zone currently active? -- @param #string HasBeacon Does this zone have a beacon if it is active? +-- @param #number Shiplength Length of Ship for shipzones +-- @param #number Shipwidth Width of Ship for shipzones -- @return #CTLD self -function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon) +function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth) self:T(self.lid .. " AddCTLDZone") local ctldzone = {} -- #CTLD.CargoZone @@ -2284,6 +2327,11 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon) ctldzone.vhfbeacon = nil end + if Type == CTLD.CargoZoneType.SHIP then + ctldzone.shiplength = Shiplength or 100 + ctldzone.shipwidth = Shipwidth or 10 + end + self:AddZone(ctldzone) return self end @@ -2375,10 +2423,12 @@ end -- @param #CTLD.CargoZoneType Zonetype Zonetype -- @return #boolean Outcome Is in zone or not -- @return #string name Closest zone name --- @return #string zone Closest Core.Zone#ZONE object +-- @return Core.Zone#ZONE zone Closest Core.Zone#ZONE object -- @return #number distance Distance to closest zone +-- @return #number width Radius of zone or width of ship function CTLD:IsUnitInZone(Unit,Zonetype) self:T(self.lid .. " IsUnitInZone") + self:T(Zonetype) local unitname = Unit:GetName() local zonetable = {} local outcome = false @@ -2386,6 +2436,8 @@ function CTLD:IsUnitInZone(Unit,Zonetype) zonetable = self.pickupZones -- #table elseif Zonetype == CTLD.CargoZoneType.DROP then zonetable = self.dropOffZones -- #table + elseif Zonetype == CTLD.CargoZoneType.SHIP then + zonetable = self.shipZones -- #table else zonetable = self.wpZones -- #table end @@ -2394,16 +2446,29 @@ function CTLD:IsUnitInZone(Unit,Zonetype) local colorret = nil local maxdist = 1000000 -- 100km local zoneret = nil + local zonewret = nil local zonenameret = nil for _,_cargozone in pairs(zonetable) do local czone = _cargozone -- #CTLD.CargoZone local unitcoord = Unit:GetCoordinate() local zonename = czone.name - local zone = ZONE:FindByName(zonename) - zonecoord = zone:GetCoordinate() local active = czone.active local color = czone.color - local zoneradius = zone:GetRadius() + local zone = nil + local zoneradius = 100 + local zonewidth = 20 + if Zonetype == CTLD.CargoZoneType.SHIP then + self:T("Checking Type Ship: "..zonename) + zone = UNIT:FindByName(zonename) + zonecoord = zone:GetCoordinate() + zoneradius = czone.shiplength + zonewidth = czone.shipwidth + else + zone = ZONE:FindByName(zonename) + zonecoord = zone:GetCoordinate() + zoneradius = zone:GetRadius() + zonewidth = zoneradius + end local distance = self:_GetDistance(zonecoord,unitcoord) if distance <= zoneradius and active then outcome = true @@ -2412,10 +2477,15 @@ function CTLD:IsUnitInZone(Unit,Zonetype) maxdist = distance zoneret = zone zonenameret = zonename + zonewret = zonewidth colorret = color end end - return outcome, zonenameret, zoneret, maxdist + if Zonetype == CTLD.CargoZoneType.SHIP then + return outcome, zonenameret, zoneret, maxdist, zonewret + else + return outcome, zonenameret, zoneret, maxdist + end end --- User function - Start smoke in a zone close to the Unit. @@ -2598,6 +2668,9 @@ end self:T(self.lid .. " CanHoverLoad") if self:IsHercules(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) --and self:IsCorrectHover(Unit) + end return outcome end @@ -2691,7 +2764,7 @@ end -- @return #CTLD self function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) - self:I(self.lid .. "Started.") + self:I(self.lid .. "Started ("..self.version..")") if self.useprefix or self.enableHercules then local prefix = self.prefixes if self.enableHercules then From 6cc3d73c04a3393cc3566eb93aef03080f2375b7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 22 Aug 2021 12:02:52 +0200 Subject: [PATCH 27/68] Changed priority to show bomb target height in ft if no Player Settings --- Moose Development/Moose/Functional/Range.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 9e47bb30d..2b02c0de5 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -26,7 +26,7 @@ -- === -- -- ## Youtube Videos: --- + -- -- * [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) -- * [MOOSE - On the Range - Demonstration Video](https://www.youtube.com/watch?v=kIXcxNB9_3M) -- @@ -2560,7 +2560,7 @@ function RANGE:_DisplayBombTargets(_unitname) -- Get elevation local elevation=coord:GetLandHeight() local eltxt=string.format("%d m", elevation) - if _settings:IsImperial() then + if not _settings:IsMetric() then elevation=UTILS.MetersToFeet(elevation) eltxt=string.format("%d ft", elevation) end From 6481d5d41eb3e64a64f5c83d6a352b825cdf72f6 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Wed, 25 Aug 2021 17:59:33 +1000 Subject: [PATCH 28/68] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 163 +++++++++++++++++++++--- 1 file changed, 143 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index f23945011..e4147c65c 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -32,6 +32,8 @@ -- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_(CVN-73)) (CVN-73) [Super Carrier Module] -- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module] -- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1)) (LHA-1) [**WIP**] +-- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6)) (LHA-6) [**WIP**] +-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**] -- -- **Supported Aircraft:** -- @@ -48,8 +50,8 @@ -- -- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier. -- --- The AV-8B Harrier and the USS Tarawa are WIP. Those two can only be used together, i.e. the Tarawa is the only carrier the harrier is supposed to land on and --- the no other fixed wing aircraft (human or AI controlled) are supposed to land on the Tarawa. Currently only Case I is supported. Case II/III take slightly steps from the CVN carrier. +-- The AV-8B Harrier and the USS Tarawa, USS America and Juan Carlos I are WIP. Those two can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and +-- the no other fixed wing aircraft (human or AI controlled) are supposed to land on these Ships. Currently only Case I is supported. Case II/III take slightly steps from the CVN carrier. -- However, the two Case II/III pattern are very similar so this is not a big drawback. -- -- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version. @@ -1292,6 +1294,8 @@ AIRBOSS.AircraftCarrier={ -- @field #string TRUMAN USS Harry S. Truman (CVN-75) [Super Carrier Module] -- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete] -- @field #string TARAWA USS Tarawa (LHA-1) +-- @field #string AMERICA USS America (LHA-6) +-- @field #string JCARLOS Juan Carlos I (L61) -- @field #string KUZNETSOV Admiral Kuznetsov (CV 1143.5) AIRBOSS.CarrierType={ ROOSEVELT="CVN_71", @@ -1301,6 +1305,8 @@ AIRBOSS.CarrierType={ STENNIS="Stennis", VINSON="VINSON", TARAWA="LHA_Tarawa", + AMERICA="USS America LHA-6", + JCARLOS="L61", KUZNETSOV="KUZNECOW", } @@ -1970,6 +1976,12 @@ function AIRBOSS:New(carriername, alias) elseif self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Tarawa parameters. self:_InitTarawa() + elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then + -- Use America parameters. + self:_InitAmerica() + elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then + -- Use Juan Carlos parameters. + self:_InitJcarlos() elseif self.carriertype==AIRBOSS.CarrierType.KUZNETSOV then -- Kusnetsov parameters - maybe... self:_InitStennis() @@ -2061,7 +2073,7 @@ function AIRBOSS:New(carriername, alias) -- Carrier specific. - if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA then + if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.JCARLOS then -- Flare wires. local w1=stern:Translate(self.carrierparam.wire1, FB) @@ -4401,6 +4413,85 @@ function AIRBOSS:_InitTarawa() end +--- Init parameters for LHA-6 America carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitAmerica() + + -- Init Stennis as default. + self:_InitStennis() + + -- Carrier Parameters. + self.carrierparam.sterndist =-125 + self.carrierparam.deckheight = 20 --67 ft + + -- Total size of the carrier (approx as rectangle). + self.carrierparam.totlength=257 + self.carrierparam.totwidthport=11 + self.carrierparam.totwidthstarboard=25 + + -- Landing runway. + self.carrierparam.rwyangle = 0 + self.carrierparam.rwylength = 240 + self.carrierparam.rwywidth = 15 + + -- Wires. + self.carrierparam.wire1=nil + self.carrierparam.wire2=nil + self.carrierparam.wire3=nil + self.carrierparam.wire4=nil + + -- Late break. + self.BreakLate.name="Late Break" + self.BreakLate.Xmin=-UTILS.NMToMeters(1) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -- Not more than 1.6 NM port. + self.BreakLate.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. + self.BreakLate.LimitXmin= 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax= nil + self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax= nil + +end + +--- Init parameters for L61 Juan Carlos carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitJcarlos() + + -- Init Stennis as default. + self:_InitStennis() + + -- Carrier Parameters. + self.carrierparam.sterndist =-125 + self.carrierparam.deckheight = 20 --67 ft + + -- Total size of the carrier (approx as rectangle). + self.carrierparam.totlength=231 + self.carrierparam.totwidthport=10 + self.carrierparam.totwidthstarboard=22 + + -- Landing runway. + self.carrierparam.rwyangle = 0 + self.carrierparam.rwylength = 202 + self.carrierparam.rwywidth = 14 + + -- Wires. + self.carrierparam.wire1=nil + self.carrierparam.wire2=nil + self.carrierparam.wire3=nil + self.carrierparam.wire4=nil + + -- Late break. + self.BreakLate.name="Late Break" + self.BreakLate.Xmin=-UTILS.NMToMeters(1) -- Not more than 1 NM behind the boat. Last check was at 0. + self.BreakLate.Xmax= UTILS.NMToMeters(5) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Zmin=-UTILS.NMToMeters(1.6) -- Not more than 1.6 NM port. + self.BreakLate.Zmax= UTILS.NMToMeters(1) -- Not more than 1 NM starboard. + self.BreakLate.LimitXmin= 0 -- Check and next step 0.8 NM port and in front of boat. + self.BreakLate.LimitXmax= nil + self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2 + self.BreakLate.LimitZmax= nil + +end --- Init parameters for Marshal Voice overs *Gabriella* by HighwaymanEd. -- @param #AIRBOSS self -- @param #string mizfolder (Optional) Folder within miz file where the sound files are located. @@ -6746,8 +6837,8 @@ function AIRBOSS:_GetMarshalAltitude(stack, case) -- Second point 1.5 NM ahead. p2=Carrier:Translate(UTILS.NMToMeters(1.5), hdg) - -- Tarawa Delta pattern. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + -- Tarawa,LHA,LHD Delta patterns. + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then -- Pattern is directly overhead the carrier. p1=Carrier:Translate(UTILS.NMToMeters(1.0), hdg+90) @@ -8592,7 +8683,7 @@ function AIRBOSS:OnEventLand(EventData) self:T(self.lid..text) -- Check carrier type. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then -- Power "Idle". self:RadioTransmission(self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true) @@ -8627,7 +8718,7 @@ function AIRBOSS:OnEventLand(EventData) -- AI unit landed -- -------------------- - if self.carriertype~=AIRBOSS.CarrierType.TARAWA then + if self.carriertype~=AIRBOSS.CarrierType.TARAWA or self.carriertype~=AIRBOSS.CarrierType.AMERICA or self.carriertype~=AIRBOSS.CarrierType.JCARLOS then -- Coordinate at landing event local coord=EventData.IniUnit:GetCoordinate() @@ -9671,8 +9762,8 @@ function AIRBOSS:_CheckForLongDownwind(playerData) -- 1.6 NM from carrier is too far. local limit=UTILS.NMToMeters(-1.6) - -- For the tarawa we give a bit more space. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + -- For the tarawa, other LHA and LHD we give a bit more space. + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then limit=UTILS.NMToMeters(-2.0) end @@ -9755,7 +9846,7 @@ function AIRBOSS:_Ninety(playerData) self:_PlayerHint(playerData) -- Next step: wake. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then -- Harrier has no wake stop. It stays port of the boat. self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.FINAL) else @@ -10429,7 +10520,7 @@ function AIRBOSS:_GetSternCoord() --local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then -- Tarawa: Translate 8 meters port. self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then @@ -11172,7 +11263,7 @@ function AIRBOSS:_GetZoneHolding(case, stack) self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", Post:GetVec2(), self.marshalradius) -- Delta pattern. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) end @@ -11225,7 +11316,7 @@ function AIRBOSS:_GetZoneCommence(case, stack) -- Three position local Three=self:GetCoordinate():Translate(D, hdg+275) - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then local Dx=UTILS.NMToMeters(2.25) @@ -11538,7 +11629,24 @@ function AIRBOSS:_GetOptLandingCoordinate() -- Alitude 120 ft. self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) + elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then + -- Landing 100 ft abeam, 120 ft alt. To allow adjustments to match different deck configurations. + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) + --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + + -- Alitude 120 ft. + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) + + elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then + + -- Landing 100 ft abeam, 120 ft alt. + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-100, true, true) + --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-100) + + -- Alitude 120 ft. + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) + else -- Ideally we want to land between 2nd and 3rd wire. @@ -11573,7 +11681,22 @@ function AIRBOSS:_GetLandingSpotCoordinate() -- Primary landing spot 7.5 self.landingspotcoord:Translate(57, hdg, true, true):SetAltitude(self.carrierparam.deckheight) + elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then + -- Landing 100 ft abeam, 120 alt. + local hdg=self:GetHeading() + + -- Primary landing spot 7.5 a little further forwad on the America + self.landingspotcoord:Translate(59, hdg, true, true):SetAltitude(self.carrierparam.deckheight) + + elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then + + -- Landing 100 ft abeam, 120 alt. + local hdg=self:GetHeading() + + -- Primary landing spot 5.0 -- TODO voice for different landing Spots. + self.landingspotcoord:Translate(89, hdg, true, true):SetAltitude(self.carrierparam.deckheight) + end return self.landingspotcoord @@ -12444,7 +12567,7 @@ function AIRBOSS:_GS(step, n) if n==-1 then gp=AIRBOSS.GroovePos.IC elseif n==1 then - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then gp=AIRBOSS.GroovePos.AL else gp=AIRBOSS.GroovePos.IW @@ -14334,17 +14457,17 @@ function AIRBOSS:_IsCarrierAircraft(unit) -- Get aircraft type name local aircrafttype=unit:GetTypeName() - -- Special case for Harrier which can only land on Tarawa. + -- Special case for Harrier which can only land on Tarawa, LHA and LHD. if aircrafttype==AIRBOSS.AircraftCarrier.AV8B then - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then return true else return false end end - -- Also only Harriers can land on the Tarawa. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + -- Also only Harriers can land on the Tarawa, LHA and LHD. + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then return false end @@ -17713,8 +17836,8 @@ function AIRBOSS:_MarkCaseZones(_unitName, flare) self:_GetZoneBullseye(case):FlareZone(FLARECOLOR.Green, 45) end - -- Tarawa landing spots. - if self.carriertype==AIRBOSS.CarrierType.TARAWA then + -- Tarawa, LHA and LHD landing spots. + if self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS then text=text.."\n* abeam landing stop with RED flares" -- Abeam landing spot zone. local ALSPT=self:_GetZoneAbeamLandingSpot() From 3962529698ade9215f8388d5229e1b3765c2d3ab Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Thu, 26 Aug 2021 09:33:40 +1000 Subject: [PATCH 29/68] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index e4147c65c..e1e150265 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -51,7 +51,7 @@ -- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier. -- -- The AV-8B Harrier and the USS Tarawa, USS America and Juan Carlos I are WIP. Those two can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and --- the no other fixed wing aircraft (human or AI controlled) are supposed to land on these Ships. Currently only Case I is supported. Case II/III take slightly steps from the CVN carrier. +-- the no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier. -- However, the two Case II/III pattern are very similar so this is not a big drawback. -- -- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version. From 7f18ea0e7a37d8303c621929bd4b0c2a393817db Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 27 Aug 2021 14:56:16 +0200 Subject: [PATCH 30/68] UNIT/GROUP - added function to get the skill of a unit. SEAD - added functionality to calculate time-2-impact of HARMS and adjust behaviour accordingly --- Moose Development/Moose/Functional/Sead.lua | 162 +++++++++++++----- Moose Development/Moose/Functional/Shorad.lua | 2 +- .../Moose/Wrapper/Controllable.lua | 9 +- Moose Development/Moose/Wrapper/Group.lua | 11 ++ Moose Development/Moose/Wrapper/Unit.lua | 10 ++ 5 files changed, 149 insertions(+), 45 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 008b9ae01..98b6fbdbd 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -17,7 +17,7 @@ -- -- ### Authors: **FlightControl**, **applevangelist** -- --- Last Update: July 2021 +-- Last Update: Aug 2021 -- -- === -- @@ -59,7 +59,7 @@ SEAD = { ["AGM_122"] = "AGM_122", ["AGM_84"] = "AGM_84", ["AGM_45"] = "AGM_45", - ["ALARN"] = "ALARM", + ["ALARM"] = "ALARM", ["LD-10"] = "LD-10", ["X_58"] = "X_58", ["X_28"] = "X_28", @@ -68,6 +68,23 @@ SEAD = { ["Kh25"] = "Kh25", } + --- Missile enumerators + -- @field HarmData + SEAD.HarmData = { + -- km and mach + ["AGM_88"] = { 150, 3}, + ["AGM_45"] = { 12, 2}, + ["AGM_122"] = { 16.5, 2.3}, + ["AGM_84"] = { 280, 0.85}, + ["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}, + } + --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. @@ -92,7 +109,7 @@ function SEAD:New( SEADGroupPrefixes ) end self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) - self:I("*** SEAD - Started Version 0.2.9") + self:I("*** SEAD - Started Version 0.2.10") return self end @@ -134,22 +151,56 @@ end -- @param #SEAD self -- @param #string WeaponName -- @return #boolean Returns true for a match + -- @return #string name Name of hit in table 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) then hit = true end + if string.find(WeaponName,_name,1) then + hit = true + name = _name + break + end end - return hit + return hit, name end + --- (Internal) Return distance in meters between two coordinates or -1 on error. + -- @param #SEAD self + -- @param Core.Point#COORDINATE _point1 Coordinate one + -- @param Core.Point#COORDINATE _point2 Coordinate two + -- @return #number Distance in meters + function SEAD:_GetDistance(_point1, _point2) + self:T("_GetDistance") + if _point1 and _point2 then + local distance1 = _point1:Get2DDistance(_point2) + local distance2 = _point1:DistanceFromPointVec2(_point2) + self:I({dist1=distance1, dist2=distance2}) + 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 + --- Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @see SEAD -- @param #SEAD -- @param Core.Event#EVENTDATA EventData function SEAD:HandleEventShot( EventData ) self:T( { EventData } ) - + local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT + local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE local SEADUnit = EventData.IniDCSUnit local SEADUnitName = EventData.IniDCSUnitName local SEADWeapon = EventData.Weapon -- Identify the weapon fired @@ -163,11 +214,14 @@ function SEAD:HandleEventShot( EventData ) local _targetMimgroupName = "none" local _evade = math.random (1,100) -- random number for chance of evading action local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object + local _targetUnit = UNIT:Find(_targetMim) -- Wrapper.Unit#UNIT + local _targetMimgroup = nil -- Wrapper.Group#GROUP if _targetUnit and _targetUnit:IsAlive() then - local _targetMimgroup = _targetUnit:GetGroup() + _targetMimgroup = _targetUnit:GetGroup() _targetMimgroupName = _targetMimgroup:GetName() -- group name - --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill + local _targetUnitName = _targetUnit:GetName() + _targetUnit:GetSkill() + _targetskill = _targetUnit:GetSkill() self:T( self.SEADGroupPrefixes ) self:T( _targetMimgroupName ) end @@ -189,39 +243,67 @@ function SEAD:HandleEventShot( EventData ) self:T( _targetskill ) if self.TargetSkill[_targetskill] then if (_evade > self.TargetSkill[_targetskill].Evade) then - - self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) - - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimcont= _targetMimgroup:getController() - routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly - - --tracker ID table to switch groups off and on again - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - - local function SuppressionEnd(id) --switch group back on - local range = self.EngagementRange -- Feature Request #1355 - self:T(string.format("*** SEAD - Engagement Range is %d", range)) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) - --id.groupName:enableEmission(true) - id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 - self.SuppressedGroups[id.groupName] = nil --delete group id from table when done + -- calculate distance of attacker + local _targetpos = _targetMimgroup:GetCoordinate() + local _distance = self:_GetDistance(SEADPlanePos, _targetpos) + -- weapon speed + local hit, data = self:_CheckHarms(SEADWeaponName) + local wpnpeed = 666 + local reach = 10 + if hit then + local wpndata = SEAD.HarmData[data] + reach = wpndata[1] * 1,1 + local mach = wpndata[2] + wpnpeed = math.floor(mach * 340.29) end - -- randomize switch-on time - local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - local SuppressionEndTime = timer.getTime() + delay - --create entry - if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet - self.SuppressedGroups[id.groupName] = { - SuppressionEndTime = delay - } - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - --_targetMimgroup:enableEmission(false) - timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function + -- time to impact + local _tti = math.floor(_distance / wpnpeed) -- estimated impact time + if _distance > 0 then + _distance = math.floor(_distance / 1000) -- km + else + _distance = 0 + end + + self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti )) + + local _targetMimgroup1 = Unit.getGroup(Weapon.getTarget(SEADWeapon)) + local _targetMimcont1 = _targetMimgroup1:getController() + + if reach >= _distance then + self:T("*** SEAD - Relocating") + _targetMimgroup:RelocateGroundRandomInRadius(20,300,false,false,"Diamond") + --routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly + + --tracker ID table to switch groups off and on again + local id = { + groupName = _targetMimgroup1, + ctrl = _targetMimcont1 + } + + local function SuppressionEnd(id) --switch group back on + local range = self.EngagementRange -- Feature Request #1355 + --self:T(string.format("*** SEAD - Engagement Range is %d", range)) + self:T("*** SEAD - Radar On") + id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) + --id.groupName:enableEmission(true) + id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 + self.SuppressedGroups[id.groupName] = nil --delete group id from table when done + end + -- randomize switch-on time + local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) + if delay < _tti then delay = _tti * 1,1 end + local SuppressionEndTime = timer.getTime() + delay + --create entry + if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet + self.SuppressedGroups[id.groupName] = { + SuppressionEndTime = delay + } + self:T(string.format("*** SEAD - Radar Off for %dsecs",delay)) + Controller.setOption(_targetMimcont1, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) + --_targetMimgroup:enableEmission(false) + timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function + end end end end diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 958877ec8..ce2d93d20 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -113,7 +113,7 @@ do ["AGM_122"] = "AGM_122", ["AGM_84"] = "AGM_84", ["AGM_45"] = "AGM_45", - ["ALARN"] = "ALARM", + ["ALARM"] = "ALARM", ["LD-10"] = "LD-10", ["X_58"] = "X_58", ["X_28"] = "X_28", diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 7f2f1f795..97a0e22d3 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3779,8 +3779,9 @@ end -- @param #number radius Radius of the relocation zone, default 500 -- @param #boolean onroad If true, route on road (less problems with AI way finding), default true -- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false +-- @param #string formation Formation string as in the mission editor, e.g. "Vee", "Diamond", "Line abreast", etc. Defaults to "Off Road" -- @return #CONTROLLABLE self -function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut) +function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut, formation) self:F2( { self.ControllableName } ) local _coord = self:GetCoordinate() @@ -3791,14 +3792,14 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc local _grptsk = {} local _candoroad = false local _shortcut = shortcut or false + local _formation = formation or "Off Road" -- create a DCS Task an push it on the group - -- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments) if onroad then - _grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,"Off Road",_shortcut) + _grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut) self:Route(_grptsk,5) else - self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road") + self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation) end return self diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index a9c678ab5..a1ccdfa85 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2594,6 +2594,17 @@ function GROUP:SetCommandImmortal(switch) return self end +--- Get skill from Group. Effectively gets the skill from Unit 1 as the group holds no skill value. +-- @param #GROUP self +-- @return #string Skill String of skill name. +function GROUP:GetSkill() + self:F2( self.GroupName ) + local unit = self:GetUnit(1) + local name = unit:GetName() + local skill = _DATABASE.Templates.Units[name].Template.skill or "Random" + return skill +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index efd02a03d..3fb557504 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1426,3 +1426,13 @@ function UNIT:EnableEmission(switch) return self end + +--- Get skill from Unit. +-- @param #UNIT self +-- @return #string Skill String of skill name. +function UNIT:GetSkill() + self:F2( self.UnitName ) + local name = self.UnitName + local skill = _DATABASE.Templates.Units[name].Template.skill or "Random" + return skill +end From 17378f509e5a3ecc924d6273291d4ba40a32553f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 27 Aug 2021 18:46:45 +0200 Subject: [PATCH 31/68] SEAD - Code cleanup and enabled delayed switch off --- Moose Development/Moose/Functional/Sead.lua | 101 ++++++++++---------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 98b6fbdbd..87e323785 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -68,7 +68,7 @@ SEAD = { ["Kh25"] = "Kh25", } - --- Missile enumerators + --- Missile enumerators - from DCS ME and Wikipedia -- @field HarmData SEAD.HarmData = { -- km and mach @@ -109,7 +109,7 @@ function SEAD:New( SEADGroupPrefixes ) end self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) - self:I("*** SEAD - Started Version 0.2.10") + self:I("*** SEAD - Started Version 0.3.1") return self end @@ -176,7 +176,7 @@ end if _point1 and _point2 then local distance1 = _point1:Get2DDistance(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2) - self:I({dist1=distance1, dist2=distance2}) + --self:T({dist1=distance1, dist2=distance2}) if distance1 and type(distance1) == "number" then return distance1 elseif distance2 and type(distance2) == "number" then @@ -198,7 +198,7 @@ end -- @param #SEAD -- @param Core.Event#EVENTDATA EventData function SEAD:HandleEventShot( EventData ) - self:T( { EventData } ) + self:T( { EventData.id } ) local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE local SEADUnit = EventData.IniDCSUnit @@ -207,31 +207,29 @@ function SEAD:HandleEventShot( EventData ) local SEADWeaponName = EventData.WeaponName -- return weapon type self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) - self:T({ SEADWeapon }) + --self:T({ SEADWeapon }) if self:_CheckHarms(SEADWeaponName) then + self:T( '*** SEAD - Weapon Match' ) local _targetskill = "Random" - local _targetMimgroupName = "none" - local _evade = math.random (1,100) -- random number for chance of evading action - local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetUnit = UNIT:Find(_targetMim) -- Wrapper.Unit#UNIT - local _targetMimgroup = nil -- Wrapper.Group#GROUP + local _targetgroupname = "none" + local _target = EventData.Weapon:getTarget() -- Identify target + local _targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT + local _targetgroup = nil -- Wrapper.Group#GROUP if _targetUnit and _targetUnit:IsAlive() then - _targetMimgroup = _targetUnit:GetGroup() - _targetMimgroupName = _targetMimgroup:GetName() -- group name + _targetgroup = _targetUnit:GetGroup() + _targetgroupname = _targetgroup:GetName() -- group name local _targetUnitName = _targetUnit:GetName() _targetUnit:GetSkill() _targetskill = _targetUnit:GetSkill() - self:T( self.SEADGroupPrefixes ) - self:T( _targetMimgroupName ) end -- see if we are shot at local SEADGroupFound = false for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do self:T( SEADGroupPrefix ) - if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then + if string.find( _targetgroupname, SEADGroupPrefix, 1, true ) then SEADGroupFound = true - self:T( '*** SEAD - Group Found' ) + self:T( '*** SEAD - Group Match Found' ) break end end @@ -240,25 +238,26 @@ function SEAD:HandleEventShot( EventData ) local Skills = { "Average", "Good", "High", "Excellent" } _targetskill = Skills[ math.random(1,4) ] end - self:T( _targetskill ) + --self:T( _targetskill ) if self.TargetSkill[_targetskill] then + local _evade = math.random (1,100) -- random number for chance of evading action if (_evade > self.TargetSkill[_targetskill].Evade) then - + self:T("*** SEAD - Evading") -- calculate distance of attacker - local _targetpos = _targetMimgroup:GetCoordinate() + local _targetpos = _targetgroup:GetCoordinate() local _distance = self:_GetDistance(SEADPlanePos, _targetpos) -- weapon speed local hit, data = self:_CheckHarms(SEADWeaponName) - local wpnpeed = 666 + local wpnspeed = 666 -- ;) local reach = 10 if hit then local wpndata = SEAD.HarmData[data] reach = wpndata[1] * 1,1 local mach = wpndata[2] - wpnpeed = math.floor(mach * 340.29) + wpnspeed = math.floor(mach * 340.29) end -- time to impact - local _tti = math.floor(_distance / wpnpeed) -- estimated impact time + local _tti = math.floor(_distance / wpnspeed) -- estimated impact time if _distance > 0 then _distance = math.floor(_distance / 1000) -- km else @@ -266,44 +265,40 @@ function SEAD:HandleEventShot( EventData ) end self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti )) - - local _targetMimgroup1 = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimcont1 = _targetMimgroup1:getController() if reach >= _distance then - self:T("*** SEAD - Relocating") - _targetMimgroup:RelocateGroundRandomInRadius(20,300,false,false,"Diamond") - --routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly + self:T("*** SEAD - Shot in Reach") - --tracker ID table to switch groups off and on again - local id = { - groupName = _targetMimgroup1, - ctrl = _targetMimcont1 - } - - local function SuppressionEnd(id) --switch group back on - local range = self.EngagementRange -- Feature Request #1355 - --self:T(string.format("*** SEAD - Engagement Range is %d", range)) - self:T("*** SEAD - Radar On") - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) - --id.groupName:enableEmission(true) - id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 - self.SuppressedGroups[id.groupName] = nil --delete group id from table when done + local function SuppressionStart(args) + self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2])) + local grp = args[1] -- Wrapper.Group#GROUP + grp:OptionAlarmStateGreen() + grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond") end + + local function SuppressionStop(args) + self:T(string.format("*** SEAD - %s Radar On",args[2])) + local grp = args[1] -- Wrapper.Group#GROUP + grp:OptionAlarmStateRed() + grp:OptionEngageRange(self.EngagementRange) + self.SuppressedGroups[args[2]] = false + end + -- randomize switch-on time local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - if delay < _tti then delay = _tti * 1,1 end - local SuppressionEndTime = timer.getTime() + delay - --create entry - if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet - self.SuppressedGroups[id.groupName] = { - SuppressionEndTime = delay - } - self:T(string.format("*** SEAD - Radar Off for %dsecs",delay)) - Controller.setOption(_targetMimcont1, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - --_targetMimgroup:enableEmission(false) - timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function + if delay > _tti then delay = delay / 2 end -- speed up + if _tti > (3*delay) then delay = (_tti / 2) * 0.9 end -- shot from afar + + local SuppressionStartTime = timer.getTime() + delay + local SuppressionEndTime = timer.getTime() + _tti + 10 + + 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},SuppressionStartTime) + timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) + self.SuppressedGroups[_targetgroupname] = true end + end end end From c98757d13c0324473af7276ba65908c061faabf4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 27 Aug 2021 18:46:59 +0200 Subject: [PATCH 32/68] CTLD - added ENGINEERING --- Moose Development/Moose/Ops/CTLD.lua | 875 +++++++++++++++++++++++---- 1 file changed, 743 insertions(+), 132 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 60b111f26..63a70c099 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -26,7 +26,280 @@ do ------------------------------------------------------ ---- **CTLD_CARGO** class, extends #Core.Base#BASE +--- **CTLD_ENGINEERING** class, extends Core.Base#BASE +-- @type CTLD_ENGINEERING +-- @field #string ClassName +-- @field #string lid +-- @field #string Name +-- @field Wrapper.Group#GROUP Group +-- @field Wrapper.Unit#UNIT Unit +-- @field Wrapper.Group#GROUP HeliGroup +-- @field Wrapper.Unit#UNIT HeliUnit +-- @field #string State +-- @extends Core.Base#BASE +CTLD_ENGINEERING = { + ClassName = "CTLD_ENGINEERING", + lid = "", + Name = "none", + Group = nil, + Unit = nil, + --C_Ops = nil, + HeliGroup = nil, + HeliUnit = nil, + State = "", + } + + --- CTLD_ENGINEERING class version. + -- @field #string version + CTLD_ENGINEERING.Version = "0.0.2" + + --- Create a new instance. + -- @param #CTLD_ENGINEERING self + -- @param #string Name + -- @param #string GroupName + -- @param Wrapper.Group#GROUP HeliGroup HeliGroup + -- @param Wrapper.Unit#UNIT HeliGroup HeliUnit + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:New(Name, GroupName, HeliGroup, HeliUnit) + + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #CTLD_ENGINEERING + + BASE:I({Name, GroupName, HeliGroup:GetName(), HeliUnit:GetName()}) + + self.Name = Name or "Engineer Squad" -- #string + self.Group = GROUP:FindByName(GroupName) -- Wrapper.Group#GROUP + self.Unit = self.Group:GetUnit(1) -- Wrapper.Unit#UNIT + --self.C_Ops = C_Ops -- Ops.CTLD#CTLD + self.HeliGroup = HeliGroup -- Wrapper.Group#GROUP + self.HeliUnit = HeliUnit -- Wrapper.Unit#UNIT + --self.distance = Distance or UTILS.NMToMeters(1) + self.currwpt = nil -- Core.Point#COORDINATE + self.lid = string.format("%s (%s) | ",self.Name, self.Version) + -- Start State. + self.State = "Stopped" + self.marktimer = 300 -- wait this many secs before trying a crate again + + --[[ Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Search", "Searching") + self:AddTransition("*", "Move", "Moving") + self:AddTransition("*", "Arrive", "Arrived") + self:AddTransition("*", "Build", "Building") + self:AddTransition("*", "Done", "Running") + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + self:__Start(5) + --]] + self:Start() + local parent = self:GetParent(self) + return self + end + + --- (Internal) Set the status + -- @param #CTLD_ENGINEERING self + -- @param #string State + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:SetStatus(State) + self.State = State + return self + end + + --- (Internal) Get the status + -- @param #CTLD_ENGINEERING self + -- @return #string State + function CTLD_ENGINEERING:GetStatus() + return self.State + end + + --- (Internal) Check the status + -- @param #CTLD_ENGINEERING self + -- @param #string State + -- @return #boolean Outcome + function CTLD_ENGINEERING:IsStatus(State) + return self.State == State + end + + --- (Internal) Check the negative status + -- @param #CTLD_ENGINEERING self + -- @param #string State + -- @return #boolean Outcome + function CTLD_ENGINEERING:IsNotStatus(State) + return self.State ~= State + end + + --- (Internal) Set start status. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Start() + self:T(self.lid.."Start") + self:SetStatus("Running") + return self + end + + --- (Internal) Set stop status. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Stop() + self:T(self.lid.."Stop") + self:SetStatus("Stopped") + return self + end + + --- (Internal) Set build status. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Build() + self:T(self.lid.."Build") + self:SetStatus("Building") + return self + end + + --- (Internal) Set done status. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Done() + self:T(self.lid.."Done") + local grp = self.Group -- Wrapper.Group#GROUP + grp:RelocateGroundRandomInRadius(7,100,false,false,"Diamond") + self:SetStatus("Running") + return self + end + + --- (Internal) Search for crates in reach. + -- @param #CTLD_ENGINEERING self + -- @param #table crates Table of found crate Ops.CTLD#CTLD_CARGO objects. + -- @param #number number Number of crates found. + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Search(crates,number) + self:T(self.lid.."Search") + self:SetStatus("Searching") + -- find crates close by + --local COps = self.C_Ops -- Ops.CTLD#CTLD + local dist = self.distance -- #number + local group = self.Group -- Wrapper.Group#GROUP + --local crates,number = COps:_FindCratesNearby(group,nil, dist) -- #table + local ctable = {} + local ind = 0 + if number > 0 then + -- get set of dropped only + for _,_cargo in pairs (crates) do + if _cargo:WasDropped() then + local ok = false + local chalk = _cargo:GetMark() + if chalk == nil then + ok = true + else + -- have we tried this cargo recently? + local tag = chalk.tag or "none" + local timestamp = chalk.timestamp or 0 + self:I({chalk}) + -- enough time gone? + local gone = timer.getAbsTime() - timestamp + self:I({time=gone}) + if gone >= self.marktimer then + ok = true + _cargo:WipeMark() + end -- end time check + end -- end chalk + 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 dropped + end -- end for + end -- end number + + if ind > 0 then + local crate = ctable[1] -- Ops.CTLD#CTLD_CARGO + local static = crate:GetPositionable() -- Wrapper.Static#STATIC + local crate_pos = static:GetCoordinate() -- Core.Point#COORDINATE + local gpos = group:GetCoordinate() -- Core.Point#COORDINATE + -- see how far we are from the crate + local distance = self:_GetDistance(gpos,crate_pos) + self:T(string.format("%s Distance to crate: %d", self.lid, distance)) + -- move there + if distance > 30 and distance ~= -1 and self:IsStatus("Searching") then + group:RouteGroundTo(crate_pos,15,"Line abreast",1) + self.currwpt = crate_pos -- Core.Point#COORDINATE + self:Move() + elseif distance <= 30 and distance ~= -1 then + -- arrived + self:Arrive() + end + else + self:T(self.lid.."No crates in reach!") + end + return self + end + + --- (Internal) Move towards crates in reach. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Move() + self:T(self.lid.."Move") + self:SetStatus("Moving") + -- check if we arrived on target + --local COps = self.C_Ops -- Ops.CTLD#CTLD + local group = self.Group -- Wrapper.Group#GROUP + local tgtpos = self.currwpt -- Core.Point#COORDINATE + local gpos = group:GetCoordinate() -- Core.Point#COORDINATE + -- see how far we are from the crate + local distance = self:_GetDistance(gpos,tgtpos) + self:T(string.format("%s Distance remaining: %d", self.lid, distance)) + if distance <= 30 and distance ~= -1 then + -- arrived + self:Arrive() + end + return self + end + + --- (Internal) Arrived at crates in reach. Stop group. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Arrive() + self:T(self.lid.."Arrive") + self:SetStatus("Arrived") + self.currwpt = nil + local Grp = self.Group -- Wrapper.Group#GROUP + Grp:RouteStop() + return self + end + + --- (Internal) Return distance in meters between two coordinates. + -- @param #CTLD_ENGINEERING self + -- @param Core.Point#COORDINATE _point1 Coordinate one + -- @param Core.Point#COORDINATE _point2 Coordinate two + -- @return #number Distance in meters or -1 + 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) + --self:I({dist1=distance1, dist2=distance2}) + 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 +------------------------------------------------------ +--- **CTLD_CARGO** class, extends Core.Base#BASE -- @type CTLD_CARGO -- @field #number ID ID of this cargo. -- @field #string Name Name for menu. @@ -38,7 +311,8 @@ do -- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. -- @field #boolean HasBeenDropped True if dropped from heli. -- @field #number PerCrateMass Mass in kg --- @extends Core.Fsm#FSM +-- @field #number Stock Number of builds available, -1 for unlimited +-- @extends Core.Base#BASE CTLD_CARGO = { ClassName = "CTLD_CARGO", ID = 0, @@ -50,7 +324,9 @@ CTLD_CARGO = { CratesNeeded = 0, Positionable = nil, HasBeenDropped = false, - PerCrateMass = 0 + PerCrateMass = 0, + Stock = nil, + Mark = nil, } --- Define cargo types. @@ -62,6 +338,7 @@ CTLD_CARGO = { ["FOB"] = "FOB", -- #string FOB ["CRATE"] = "Crate", -- #string crate ["REPAIR"] = "Repair", -- #string repair + ["ENGINEERS"] = "Engineers", -- #string engineers } --- Function to create new CTLD_CARGO object. @@ -76,8 +353,9 @@ CTLD_CARGO = { -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. -- @param #number PerCrateMass Mass in kg + -- @param #number Stock Number of builds available, nil for unlimited -- @return #CTLD_CARGO self - function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass) + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #CTLD self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) @@ -91,6 +369,8 @@ CTLD_CARGO = { self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE self.HasBeenDropped = Dropped or false --#boolean self.PerCrateMass = PerCrateMass or 0 -- #number + self.Stock = Stock or nil --#number + self.Mark = nil return self end @@ -182,6 +462,42 @@ CTLD_CARGO = { self.HasBeenDropped = dropped or false end + --- Get Stock. + -- @param #CTLD_CARGO self + -- @return #number Stock + function CTLD_CARGO:GetStock() + if self.Stock then + return self.Stock + else + return -1 + end + end + + --- Add Stock. + -- @param #CTLD_CARGO self + -- @param #number Number to add, one if nil. + -- @return #CTLD_CARGO self + function CTLD_CARGO:AddStock(Number) + if self.Stock then -- Stock nil? + local number = Number or 1 + self.Stock = self.Stock + number + end + return self + end + + --- Remove Stock. + -- @param #CTLD_CARGO self + -- @param #number Number to reduce, one if nil. + -- @return #CTLD_CARGO self + function CTLD_CARGO:RemoveStock(Number) + if self.Stock then -- Stock nil? + local number = Number or 1 + self.Stock = self.Stock - number + if self.Stock < 0 then self.Stock = 0 end + end + return self + end + --- Query crate type for REPAIR -- @param #CTLD_CARGO self -- @param #boolean @@ -192,6 +508,20 @@ CTLD_CARGO = { return false end end + + function CTLD_CARGO:AddMark(Mark) + self.Mark = Mark + return self + end + + function CTLD_CARGO:GetMark(Mark) + return self.Mark + end + + function CTLD_CARGO:WipeMark() + self.Mark = nil + return self + end end @@ -244,14 +574,16 @@ do -- -- if you want to add weight to your Heli, troops can have a weight in kg **per person**. Currently no max weight checked. Fly carefully. -- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3,80) -- --- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4 --- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4) +-- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4. No weight. We only have 2 in stock: +-- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4,nil,2) -- -- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build -- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) -- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. Currently no max weight checked. Fly carefully. -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775) +-- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) -- -- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: -- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) @@ -340,6 +672,26 @@ do -- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: -- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) -- +-- ## 2.1.3 Limit and manage available resources +-- +-- When adding generic cargo types, you can effectively limit how many units can be dropped/build by the players, e.g. +-- +-- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) +-- +-- You can manually add or remove the available stock like so: +-- +-- -- Crates +-- my_ctld:AddStockCrates("Humvee", 2) +-- my_ctld:RemoveStockCrates("Humvee", 2) +-- +-- -- Troops +-- my_ctld:AddStockTroops("Anti-Air", 2) +-- my_ctld:RemoveStockTroops("Anti-Air", 2) +-- +-- Notes: +-- Troops dropped back into a LOAD zone will effectively be added to the stock. Crates lost in e.g. a heli crash are just that - lost. +-- -- ## 3. Events -- -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. @@ -498,13 +850,13 @@ CTLD = { -- DONE: TEST Hover load and unload -- DONE: Crate unload -- DONE: Hover (auto-)load --- TODO: (More) Housekeeping +-- DONE: (More) Housekeeping -- DONE: Troops running to WP Zone -- DONE: Zone Radio Beacons -- DONE: Stats Running -- DONE: Added support for Hercules -- TODO: Possibly - either/or loading crates and troops --- TODO: Limit of troops, crates buildable? +-- DONE: (WIP) Limit of troops, crates buildable? ------------------------------ --- Radio Beacons @@ -567,7 +919,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.5a1" +CTLD.version="0.1.7a2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -621,7 +973,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self:SetStartState("Stopped") -- Add FSM transitions. - -- From State --> Event --> To State + -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- CTLD status update. self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. @@ -668,6 +1020,11 @@ function CTLD:New(Coalition, Prefixes, Alias) self.CrateCounter = 0 self.TroopCounter = 0 + -- added engineering + self.Engineers = 0 -- #number use as counter + self.EngineersInField = {} -- #table holds #CTLD_ENGINEERING objects + self.EngineerSearch = 2000 -- #number search distance for crates to build or repair + -- setup self.CrateDistance = 30 -- list/load crates in this radius self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor @@ -705,7 +1062,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self:_GenerateUHFrequencies() self:_GenerateFMFrequencies() - ------------------------ + ------------------------ --- Pseudo Functions --- ------------------------ @@ -934,6 +1291,18 @@ end -- @param #CTLD_CARGO Cargotype function CTLD:_LoadTroops(Group, Unit, Cargotype) self:T(self.lid .. " _LoadTroops") + -- check if we have stock + local instock = Cargotype:GetStock() + local cgoname = Cargotype:GetName() + local cgotype = Cargotype:GetType() + if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then + -- nothing left over + self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group) + return self + else + -- remove one + Cargotype:RemoveStock() + end -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) local hoverload = self:CanHoverLoad(Unit) @@ -978,7 +1347,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) return else self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, cgotype, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) self:T({cargotype=loadcargotype}) loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) @@ -997,24 +1366,26 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) -- find nearest group of deployed groups local nearestGroup = nil local nearestGroupIndex = -1 - local nearestDistance = 10000000 + local nearestDistance = 10000 for k,v in pairs(self.DroppedTroops) do local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) - if distance < nearestDistance and distance ~= -1 then + local unit = v:GetUnit(1) -- Wrapper.Unit#UNIT + local desc = unit:GetDesc() or nil + --self:I({desc = desc.attributes}) + if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then nearestGroup = v nearestGroupIndex = k nearestDistance = distance end end - + -- found one and matching distance? - if nearestGroup == nil or nearestDistance > 1000 then + if nearestGroup == nil or nearestDistance > self.EngineerSearch then self:_SendMessage("No unit close enough to repair!", 10, false, Group) return nil, nil end local groupname = nearestGroup:GetName() - --self:I(string.format("***** Found Group %s",groupname)) -- helper to find matching template local function matchstring(String,Table) @@ -1060,7 +1431,8 @@ end -- @param #table Crates Table of #CTLD_CARGO objects near the unit. -- @param #CTLD.Buildable Build Table build object. -- @param #number Number Number of objects in Crates (found) to limit search. -function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number) +-- @param #boolean Engineering If true it is an Engineering repair. +function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering) self:T(self.lid .. " _RepairObjectFromCrates") local build = Build -- -- #CTLD.Buildable --self:I({Build=Build}) @@ -1069,7 +1441,9 @@ function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number) --self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup}) if NearestGroup ~= nil then if self.repairtime < 2 then self.repairtime = 30 end -- noob catch - self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) + if not Engineering then + self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) + end -- now we can build .... --NearestGroup:Destroy(false) local name = CargoType:GetName() @@ -1090,7 +1464,11 @@ function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number) buildtimer:Start(self.repairtime) --self:_BuildObjectFromCrates(Group,Unit,object) else - self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) + 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 @@ -1123,67 +1501,98 @@ end local nearestGroup = nil local nearestGroupIndex = -1 local nearestDistance = 10000000 + 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) - if distance < nearestDistance and distance ~= -1 then + if distance <= extractdistance and distance ~= -1 then nearestGroup = v nearestGroupIndex = k nearestDistance = distance + table.insert(nearestList, math.floor(distance), v) + distancekeys[#distancekeys+1] = math.floor(distance) end end - local extractdistance = self.CrateDistance * self.ExtractFactor - if nearestGroup == nil or nearestDistance > extractdistance then self:_SendMessage("No units close enough to extract!", 10, false, Group) return self end - -- find matching cargo type - local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$") - local Cargotype = nil - for k,v in pairs(self.Cargo_Troops) do - if v.Name == groupType then - Cargotype = v - break + + -- sort reference keys + table.sort(distancekeys) + + local secondarygroups = {} + + for i=1,#distancekeys do + local nearestGroup = nearestList[distancekeys[i]] + -- find matching cargo type + 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() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + 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 self + else + self.CargoCounter = self.CargoCounter + 1 + 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}) + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert(loaded.Cargo,loadcargotype) + self.Loaded_Cargo[unitname] = loaded + self:_SendMessage("Troops boarded!", 10, false, Group) + self:_UpdateUnitCargoMass(Unit) + self:__TroopsExtracted(1,Group, Unit, nearestGroup) + + -- clean up: + --table.remove(self.DroppedTroops, nearestGroupIndex) + if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then + --self:I("*****This CargoType has multiple templates: "..Cargotype.Name) + for _,_key in pairs (Cargotype.Templates) do + table.insert(secondarygroups,_key) + end + end + nearestGroup:Destroy(false) + end end end - - if Cargotype == nil then - self:_SendMessage("Can't find a matching cargo type for " .. groupType, 10, false, Group) - return self - end - - local troopsize = Cargotype:GetCratesNeeded() -- #number - -- have we loaded stuff already? - local numberonboard = 0 - local loaded = {} - if self.Loaded_Cargo[unitname] then - loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo - numberonboard = loaded.Troopsloaded or 0 - else - loaded = {} -- #CTLD.LoadedCargo - 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 - else - self.CargoCounter = self.CargoCounter + 1 - local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) - self:T({cargotype=loadcargotype}) - loaded.Troopsloaded = loaded.Troopsloaded + troopsize - table.insert(loaded.Cargo,loadcargotype) - self.Loaded_Cargo[unitname] = loaded - self:_SendMessage("Troops boarded!", 10, false, Group) - self:_UpdateUnitCargoMass(Unit) - self:__TroopsExtracted(1,Group, Unit, nearestGroup) - - -- clean up: - table.remove(self.DroppedTroops, nearestGroupIndex) - nearestGroup:Destroy(false) + -- clean up secondary groups + 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) + end + end + end end + self:CleanDroppedTroops() return self end @@ -1196,8 +1605,20 @@ end -- @param #boolean drop If true we\'re dropping from heli rather than loading. function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:T(self.lid .. " _GetCrates") - local cgoname = Cargo:GetName() - -- check if we are in LOAD zone + if not drop then + local cgoname = Cargo:GetName() + -- check if we have stock + local instock = Cargo:GetStock() + if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then + -- nothing left over + self:_SendMessage(string.format("Sorry, we ran out of %s", cgoname), 10, false, Group) + return self + else + -- remove one + Cargo:RemoveStock() + end + end + -- check if we are in LOAD zone local inzone = false local drop = drop or false local ship = nil @@ -1341,9 +1762,21 @@ end function CTLD:_GetDistance(_point1, _point2) self:T(self.lid .. " _GetDistance") if _point1 and _point2 then - local distance = _point1:DistanceFromPointVec2(_point2) - return distance + local distance1 = _point1:Get2DDistance(_point2) + local distance2 = _point1:DistanceFromPointVec2(_point2) + --self:I({dist1=distance1, dist2=distance2}) + 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 @@ -1498,10 +1931,10 @@ function CTLD:_GetUnitCargoMass(Unit) for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + 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 not cargo:WasDropped() then + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then loadedmass = loadedmass + cargo.PerCrateMass end end @@ -1548,7 +1981,7 @@ function CTLD:_ListCargo(Group, Unit) for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) end end @@ -1561,7 +1994,7 @@ function CTLD:_ListCargo(Group, Unit) for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then report:Add(string.format("Crate: %s size 1",cargo:GetName())) cratecount = cratecount + 1 end @@ -1625,7 +2058,7 @@ function CTLD:_UnloadTroops(Group, Unit) for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then -- unload troops local name = cargo:GetName() or "none" local temptable = cargo:GetTemplates() or {} @@ -1645,12 +2078,21 @@ function CTLD:_UnloadTroops(Group, Unit) :InitRandomizeUnits(true,20,2) :InitDelayOff() :SpawnFromVec2(randomcoord) - if self.movetroopstowpzone then + if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) end end -- template loop cargo:SetWasDropped(true) - self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) + -- engineering group? + --self:I("Dropped Troop Type: "..type) + 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, Group, Unit) + 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 self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) end -- if type end end -- cargotable loop @@ -1669,9 +2111,23 @@ function CTLD:_UnloadTroops(Group, Unit) local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum local dropped = cargo:WasDropped() - if type ~= CTLD_CARGO.Enum.TROOPS and not dropped then + 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 + -- add troops back to stock + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and droppingatbase then + -- find right generic type + local name = cargo:GetName() + local gentroops = self.Cargo_Troops + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + if _troop.Name == name then + local stock = _troop:GetStock() + -- avoid making unlimited stock limited + if stock and tonumber(stock) >= 0 then _troop:AddStock() end + end + end + end end end self.Loaded_Cargo[unitname] = nil @@ -1690,7 +2146,7 @@ end --- (Internal) Function to unload crates from heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group --- @param Wrappe.Unit#UNIT Unit +-- @param Wrapper.Unit#UNIT Unit function CTLD:_UnloadCrates(Group, Unit) self:T(self.lid .. " _UnloadCrates") @@ -1722,7 +2178,7 @@ function CTLD:_UnloadCrates(Group, Unit) for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then -- unload crates self:_GetCrates(Group, Unit, cargo, 1, true) cargo:SetWasDropped(true) @@ -1739,7 +2195,7 @@ function CTLD:_UnloadCrates(Group, Unit) local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum local size = cargo:GetCratesNeeded() - if type == CTLD_CARGO.Enum.TROOPS then + if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then table.insert(loaded.Cargo,_cargo) loaded.Troopsloaded = loaded.Troopsloaded + size end @@ -1761,9 +2217,19 @@ end --- (Internal) Function to build nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group --- @param Wrappe.Unit#UNIT Unit -function CTLD:_BuildCrates(Group, Unit) +-- @param Wrapper.Unit#UNIT Unit +-- @param #boolean Engineering If true build is by an engineering team. +function CTLD:_BuildCrates(Group, Unit,Engineering) self:T(self.lid .. " _BuildCrates") + -- avoid users trying to build from flying Hercs + local type = Unit:GetTypeName() + if type == "Hercules" and self.enableHercules 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 -- get nearby crates local finddist = self.CrateDistance or 30 local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table @@ -1819,7 +2285,11 @@ function CTLD:_BuildCrates(Group, Unit) if not foundbuilds then report:Add(" --- None Found ---") end report:Add("------------------------------------------------------------") local text = report:Text() - self:_SendMessage(text, 30, true, Group) + if not Engineering then + self:_SendMessage(text, 30, true, Group) + else + self:T(text) + end -- let\'s get going if canbuild then -- loop again @@ -1832,7 +2302,7 @@ function CTLD:_BuildCrates(Group, Unit) end end else - self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end end -- number > 0 return self end @@ -1840,12 +2310,13 @@ end --- (Internal) Function to repair nearby vehicles / FOBs -- @param #CTLD self -- @param Wrapper.Group#GROUP Group --- @param Wrappe.Unit#UNIT Unit -function CTLD:_RepairCrates(Group, Unit) +-- @param Wrapper.Unit#UNIT Unit +-- @param #boolean Engineering If true, this is an engineering role +function CTLD:_RepairCrates(Group, Unit, Engineering) self:T(self.lid .. " _RepairCrates") -- get nearby crates local finddist = self.CrateDistance or 30 - local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table + local crates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table local buildables = {} local foundbuilds = false local canbuild = false @@ -1898,19 +2369,23 @@ function CTLD:_RepairCrates(Group, Unit) if not foundbuilds then report:Add(" --- None Found ---") end report:Add("------------------------------------------------------------") local text = report:Text() - self:_SendMessage(text, 30, true, Group) + if not Engineering then + self:_SendMessage(text, 30, true, Group) + else + self:T(text) + end -- let\'s get going if canbuild then -- loop again for _,_build in pairs(buildables) do local build = _build -- #CTLD.Buildable if build.CanBuild then - self:_RepairObjectFromCrates(Group,Unit,crates,build,number) + self:_RepairObjectFromCrates(Group,Unit,crates,build,number,Engineering) end end end else - self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end end -- number > 0 return self end @@ -1925,40 +2400,44 @@ end function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) self:T(self.lid .. " _BuildObjectFromCrates") -- Spawn-a-crate-content - local position = Unit:GetCoordinate() or Group:GetCoordinate() - local unitname = Unit:GetName() or Group:GetName() - local name = Build.Name - local type = Build.Type -- #CTLD_CARGO.Enum - local canmove = false - if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end - local temptable = Build.Template or {} - local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) - local randomcoord = zone:GetRandomCoordinate(35):GetVec2() - 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) - :InitRandomizeUnits(true,20,2) - :InitDelayOff() - :SpawnFromVec2(randomcoord) - else -- don't random position of e.g. SAM units build as FOB - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitDelayOff() - :SpawnFromVec2(randomcoord) - end - if self.movetroopstowpzone and canmove then - self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) - end + if Group and Group:IsAlive() then + local position = Unit:GetCoordinate() or Group:GetCoordinate() + local unitname = Unit:GetName() or Group:GetName() + local name = Build.Name + local type = Build.Type -- #CTLD_CARGO.Enum + local canmove = false + if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end + local temptable = Build.Template or {} + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) + local randomcoord = zone:GetRandomCoordinate(35):GetVec2() if Repair then - self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) - else - self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + randomcoord = RepairLocation:GetVec2() end - end -- template loop + 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) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + else -- don't random position of e.g. SAM units build as FOB + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + end + if self.movetroopstowpzone and canmove then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + 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 -- template loop + else + self:T(self.lid.."Group KIA while building!") + end return self end @@ -2127,11 +2606,13 @@ function CTLD:_RefreshF10Menus() -- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. -- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). -- @param #number PerTroopMass Mass in kg of each soldier -function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass) +-- @param #number Stock Number of groups in stock. Nil for unlimited. +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock) self:T(self.lid .. " AddTroopsCargo") + self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock}) self.CargoCounter = self.CargoCounter + 1 -- Troops are directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock) table.insert(self.Cargo_Troops,cargo) return self end @@ -2143,11 +2624,12 @@ end -- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. -- @param #number NoCrates Number of crates needed to build this cargo. -- @param #number PerCrateMass Mass in kg of each crate -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass) +-- @param #number Stock Number of groups in stock. Nil for unlimited. +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock) self:T(self.lid .. " AddCratesCargo") self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) table.insert(self.Cargo_Crates,cargo) return self end @@ -2159,11 +2641,12 @@ end -- @param #CTLD_CARGO.Enum Type Type of cargo, here REPAIR. -- @param #number NoCrates Number of crates needed to build this cargo. -- @param #number PerCrateMass Mass in kg of each crate -function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass) +-- @param #number Stock Number of groups in stock. Nil for unlimited. +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock) self:T(self.lid .. " AddCratesRepair") self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock) table.insert(self.Cargo_Crates,cargo) return self end @@ -2742,16 +3225,130 @@ end -- @param #CTLD self -- @return #CTLD self function CTLD:CleanDroppedTroops() + -- Troops local troops = self.DroppedTroops local newtable = {} for _index, _group in pairs (troops) do - if _group and _group:IsAlive() then - newtable[_index] = _group + self:T({_group.ClassName}) + if _group and _group.ClassName == "GROUP" then + if _group:IsAlive() then + newtable[_index] = _group + end end end self.DroppedTroops = newtable + -- Engineers + 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 + + --- User - function to add stock of a certain troops type + -- @param #CTLD self + -- @param #string Name Name as defined in the generic cargo. + -- @param #number Number Number of units/groups to add. + -- @return #CTLD self + function CTLD:AddStockTroops(Name, Number) + local name = Name or "none" + local number = Number or 1 + -- find right generic type + local gentroops = self.Cargo_Troops + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + if _troop.Name == name then + _troop:AddStock(number) + end + end + end + + --- User - function to add stock of a certain crates type + -- @param #CTLD self + -- @param #string Name Name as defined in the generic cargo. + -- @param #number Number Number of units/groups to add. + -- @return #CTLD self + function CTLD:AddStockCrates(Name, Number) + local name = Name or "none" + local number = Number or 1 + -- find right generic type + local gentroops = self.Cargo_Crates + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + if _troop.Name == name then + _troop:AddStock(number) + end + end + end + + --- User - function to remove stock of a certain troops type + -- @param #CTLD self + -- @param #string Name Name as defined in the generic cargo. + -- @param #number Number Number of units/groups to add. + -- @return #CTLD self + function CTLD:RemoveStockTroops(Name, Number) + local name = Name or "none" + local number = Number or 1 + -- find right generic type + local gentroops = self.Cargo_Troops + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + if _troop.Name == name then + _troop:RemoveStock(number) + end + end + end + + --- User - function to remove stock of a certain crates type + -- @param #CTLD self + -- @param #string Name Name as defined in the generic cargo. + -- @param #number Number Number of units/groups to add. + -- @return #CTLD self + function CTLD:RemoveStockCrates(Name, Number) + local name = Name or "none" + local number = Number or 1 + -- find right generic type + local gentroops = self.Cargo_Crates + for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO + if _troop.Name == name then + _troop:RemoveStock(number) + end + end + return self + end + + --- (Internal) Check on engineering teams + -- @param #CTLD self + -- @return #CTLD self + function CTLD:_CheckEngineers() + self:T(self.lid.." CheckEngineers") + local engtable = self.EngineersInField + for _ind,_engineers in pairs (engtable) do + local engineers = _engineers -- #CTLD_ENGINEERING + local wrenches = engineers.Group -- Wrapper.Group#GROUP + self:T(_engineers.lid .. _engineers:GetStatus()) + if wrenches and wrenches:IsAlive() then + if engineers:IsStatus("Running") or engineers:IsStatus("Searching") then + local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch) -- #table + engineers:Search(crates,number) + elseif engineers:IsStatus("Moving") then + engineers:Move() + elseif engineers:IsStatus("Arrived") then + engineers:Build() + local unit = wrenches:GetUnit(1) + self:_BuildCrates(wrenches,unit,true) + self:_RepairCrates(wrenches,unit,true) + engineers:Done() + end + else + engineers:Stop() + end + end + return self + end + ------------------------------------------------------------------- -- FSM functions ------------------------------------------------------------------- @@ -2795,6 +3392,7 @@ end self:_RefreshF10Menus() self:_RefreshRadioBeacons() self:CheckAutoHoverload() + self:_CheckEngineers() return self end @@ -2824,7 +3422,20 @@ end if self.debug or self.verbose > 0 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() + 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_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 From f6ed592f926ea5c7e3e50793878a19159a42512a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 27 Aug 2021 18:47:13 +0200 Subject: [PATCH 33/68] CSAR - remove noise --- Moose Development/Moose/Ops/CSAR.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 2ec378641..bed92db98 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1747,7 +1747,7 @@ function CSAR:_GetDistance(_point1, _point2) if _point1 and _point2 then local distance1 = _point1:Get2DDistance(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2) - self:I({dist1=distance1, dist2=distance2}) + --self:I({dist1=distance1, dist2=distance2}) if distance1 and type(distance1) == "number" then return distance1 elseif distance2 and type(distance2) == "number" then From 9d3a7aae783d0df1f08760ba4e3c4958ef2f4f20 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Sat, 28 Aug 2021 10:25:54 +1000 Subject: [PATCH 34/68] Add Landing Spot 5 to JC --- Moose Development/Moose/Ops/Airboss.lua | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index e1e150265..95d4f5eda 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1492,6 +1492,7 @@ AIRBOSS.GroovePos={ -- @field #AIRBOSS.RadioCall DEPARTANDREENTER "Depart and re-enter" call. -- @field #AIRBOSS.RadioCall EXPECTHEAVYWAVEOFF "Expect heavy wavoff" call. -- @field #AIRBOSS.RadioCall EXPECTSPOT75 "Expect spot 7.5" call. +-- @field #AIRBOSS.RadioCall EXPECTSPOT5 "Expect spot 5" call. -- @field #AIRBOSS.RadioCall FAST "You're fast" call. -- @field #AIRBOSS.RadioCall FOULDECK "Foul Deck" call. -- @field #AIRBOSS.RadioCall HIGH "You're high" call. @@ -4971,6 +4972,14 @@ function AIRBOSS:_InitVoiceOvers() subtitle="Expect spot 7.5", duration=2.0, subduration=5, + }, + EXPECTSPOT5={ + file="LSO-ExpectSpot5", + suffix="ogg", + loud=false, + subtitle="Expect spot 5", + duration=2.0, + subduration=5, }, STABILIZED={ file="LSO-Stabilized", @@ -9625,8 +9634,10 @@ function AIRBOSS:_Bullseye(playerData) -- Hint for player about altitude, AoA etc. self:_PlayerHint(playerData) - -- LSO expect spot 7.5 call - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- LSO expect spot 5 or 7.5 call + 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 then self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, nil, nil, nil, true) end @@ -9808,8 +9819,10 @@ function AIRBOSS:_Abeam(playerData) -- Paddles contact. self:RadioTransmission(self.LSORadio, self.LSOCall.PADDLESCONTACT, nil, nil, nil, true) - -- LSO expect spot 7.5 call - if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- LSO expect spot 5 or 7.5 call + if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then + self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT5, false, 5, nil, true) + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then self:RadioTransmission(self.LSORadio, self.LSOCall.EXPECTSPOT75, false, 5, nil, true) end @@ -11607,7 +11620,7 @@ function AIRBOSS:_GetAltCarrier(unit) return h end ---- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa we take the abeam landing spot 120 ft abeam the 7.5 position. +--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa and America we take the abeam landing spot 120 ft abeam the 7.5 position, for the Juan Carlos I it is 120 ft and abeam the 5 position. -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Optimal landing coordinate. function AIRBOSS:_GetOptLandingCoordinate() From 2cf939560eaf760ec4b8674f4ce50a04563d5774 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Sat, 28 Aug 2021 11:02:05 +1000 Subject: [PATCH 35/68] Allow for JC Spot 5 Voice over --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 95d4f5eda..09031a183 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -4978,7 +4978,7 @@ function AIRBOSS:_InitVoiceOvers() suffix="ogg", loud=false, subtitle="Expect spot 5", - duration=2.0, + duration=1.3, subduration=5, }, STABILIZED={ From c0a18957f0c0fbac28b1ccb5b700804f57625c13 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Sat, 28 Aug 2021 13:31:16 +1000 Subject: [PATCH 36/68] AoA for harrier and JC Spot 5 timings --- Moose Development/Moose/Ops/Airboss.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 09031a183..7f6ff2d4e 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -4647,6 +4647,7 @@ function AIRBOSS:SetVoiceOversLSOByRaynor(mizfolder) 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 @@ -4705,6 +4706,7 @@ function AIRBOSS:SetVoiceOversLSOByFF(mizfolder) 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 @@ -5640,14 +5642,14 @@ function AIRBOSS:_GetAircraftAoA(playerData) aoa.Fast = 8.25 --=17.5/2 aoa.FAST = 8.00 --=16.5/2 elseif harrier then - -- AV-8B Harrier parameters. This might need further tuning. + -- AV-8B Harrier parameters. Tuning done on the Fast AoA to allow for abeam and ninety at Nozzles 60 - 73. aoa.SLOW = 14.0 aoa.Slow = 13.0 aoa.OnSpeedMax = 12.0 aoa.OnSpeed = 11.0 aoa.OnSpeedMin = 10.0 - aoa.Fast = 9.0 - aoa.FAST = 8.0 + aoa.Fast = 8.0 + aoa.FAST = 7.5 end return aoa @@ -5907,7 +5909,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) alt=UTILS.FeetToMeters(300) --? elseif harrier then -- 300-325 ft - alt=UTILS.FeetToMeters(300) + alt=UTILS.FeetToMeters(300)-- Need to verify end aoa=aoaac.OnSpeed From 555bb7e68b0028964af4db6bab248050bdac06d7 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Sat, 28 Aug 2021 14:27:32 +1000 Subject: [PATCH 37/68] Update instructions for AV-8B Harrier --- Moose Development/Moose/Ops/Airboss.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 7f6ff2d4e..8ef801b31 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -110,6 +110,7 @@ -- ### AV-8B Harrier at USS Tarawa -- -- * [Harrier Ship Landing Mission with Auto LSO!](https://www.youtube.com/watch?v=lqmVvpunk2c) +-- * [Harrier Practice pattern USS America](https://youtu.be/99NigITYmcI) -- -- === -- @@ -297,6 +298,8 @@ -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Landing.png) -- -- Once the aircraft reaches the Initial, the landing pattern begins. The important steps of the pattern are shown in the image above. +-- The AV-8B Harrier pattern is very similar, the only differences are as there is no angled deck there is no wake check. from the ninety you wil fly strait in to 26 ft to port of the tram line. +-- The aim is to arrive abeam the landing spot in a stable hover at 120 ft with forward speed matched to the boat. From there the LSO will call "cleared to land". You then cross to the tram line at the designated landing spot at land vertcally. -- -- -- ## CASE III @@ -921,9 +924,9 @@ -- -- ## Sound Packs -- --- The AIRBOSS currently has two different "sound packs" for both LSO and Marshal radios. These contain voice overs by different actors. +-- The AIRBOSS currently has two different "sound packs" for LSO and three different "sound Packs" for Marshal radios. These contain voice overs by different actors. -- These can be set by @{#AIRBOSS.SetVoiceOversLSOByRaynor}() and @{#AIRBOSS.SetVoiceOversMarshalByRaynor}(). These are the default settings. --- The other sound files can be set by @{#AIRBOSS.SetVoiceOversLSOByFF}() and @{#AIRBOSS.SetVoiceOversMarshalByFF}(). +-- The other sound files can be set by @{#AIRBOSS.SetVoiceOversLSOByFF}(),@{#AIRBOSS.SetVoiceOversMarshalByGabriella}() and @{#AIRBOSS.SetVoiceOversMarshalByFF}(). -- Also combinations can be used, e.g. -- -- airbossStennis:SetVoiceOversLSOByFF() From 909859056889888360db490f3280c291fbcf3c61 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Sat, 28 Aug 2021 14:50:40 +1000 Subject: [PATCH 38/68] Sound Pack Gabriella add --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 8ef801b31..085ec11b4 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -926,7 +926,7 @@ -- -- The AIRBOSS currently has two different "sound packs" for LSO and three different "sound Packs" for Marshal radios. These contain voice overs by different actors. -- These can be set by @{#AIRBOSS.SetVoiceOversLSOByRaynor}() and @{#AIRBOSS.SetVoiceOversMarshalByRaynor}(). These are the default settings. --- The other sound files can be set by @{#AIRBOSS.SetVoiceOversLSOByFF}(),@{#AIRBOSS.SetVoiceOversMarshalByGabriella}() and @{#AIRBOSS.SetVoiceOversMarshalByFF}(). +-- The other sound files can be set by @{#AIRBOSS.SetVoiceOversLSOByFF}(), @{#AIRBOSS.SetVoiceOversMarshalByGabriella}() and @{#AIRBOSS.SetVoiceOversMarshalByFF}(). -- Also combinations can be used, e.g. -- -- airbossStennis:SetVoiceOversLSOByFF() From 4f51884b9da01e146f6ade3214a21e127330a371 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 28 Aug 2021 13:53:46 +0200 Subject: [PATCH 39/68] SEAD - make padding a variable (radar switch-back-on time) --- Moose Development/Moose/Functional/Sead.lua | 29 +++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 87e323785..e149fb697 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -24,7 +24,8 @@ -- @module Functional.Sead -- @image SEAD.JPG ---- @type SEAD +--- +-- @type SEAD -- @extends Core.Base#BASE --- Make SAM sites execute evasive and defensive behaviour when being fired upon. @@ -48,7 +49,8 @@ SEAD = { }, SEADGroupPrefixes = {}, SuppressedGroups = {}, - EngagementRange = 75 -- default 75% engagement range Feature Request #1355 + EngagementRange = 75, -- default 75% engagement range Feature Request #1355 + Padding = 10, } --- Missile enumerators @@ -89,13 +91,14 @@ SEAD = { -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. -- @param #SEAD self --- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken. +-- @param #table SEADGroupPrefixes Table of #string entries or single #string, which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken. +-- @param #number Padding (Optional) Extra number of seconds to add to radar switch-back-on time -- @return SEAD -- @usage -- -- CCCP SEAD Defenses -- -- Defends the Russian SA installations from SEAD attacks. -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) -function SEAD:New( SEADGroupPrefixes ) +function SEAD:New( SEADGroupPrefixes, Padding ) local self = BASE:Inherit( self, BASE:New() ) self:F( SEADGroupPrefixes ) @@ -108,7 +111,12 @@ function SEAD:New( SEADGroupPrefixes ) self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes end + local padding = Padding or 10 + if padding < 10 then padding = 10 end + self.Padding = padding + self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) + self:I("*** SEAD - Started Version 0.3.1") return self end @@ -147,6 +155,17 @@ function SEAD:SetEngagementRange(range) return self end +--- Set the padding in seconds, which extends the radar off time calculated by SEAD +-- @param #SEAD self +-- @param #number Padding Extra number of seconds to add for the switch-on +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 + --- Check if a known HARM was fired -- @param #SEAD self -- @param #string WeaponName @@ -290,7 +309,7 @@ function SEAD:HandleEventShot( EventData ) if _tti > (3*delay) then delay = (_tti / 2) * 0.9 end -- shot from afar local SuppressionStartTime = timer.getTime() + delay - local SuppressionEndTime = timer.getTime() + _tti + 10 + local SuppressionEndTime = timer.getTime() + _tti + self.Padding if not self.SuppressedGroups[_targetgroupname] then self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) From 393fa0bfbbf53a3ee98db59fdad49b8bf7af1512 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 28 Aug 2021 14:01:33 +0200 Subject: [PATCH 40/68] MANTIS - Changes from the dev branch merged --- Moose Development/Moose/Functional/Mantis.lua | 127 ++++++++++++------ 1 file changed, 89 insertions(+), 38 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 1186378e1..3843df13d 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -23,9 +23,9 @@ -- Date: July 2021 ------------------------------------------------------------------------- ---- **MANTIS** class, extends #Core.Base#BASE +--- **MANTIS** class, extends Core.Base#BASE -- @type MANTIS --- @field #string Classname +-- @field #string ClassName -- @field #string name Name of this Mantis -- @field #string SAM_Templates_Prefix Prefix to build the #SET_GROUP for SAM sites -- @field Core.Set#SET_GROUP SAM_Group The SAM #SET_GROUP @@ -195,6 +195,9 @@ MANTIS = { TimeStamp = 0, state2flag = false, SamStateTracker = {}, + DLink = false, + DLTimeStamp = 0, + Padding = 10, } --- Advanced state enumerator @@ -219,7 +222,8 @@ do --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) - --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN + --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional) + --@param #number Padding For #SEAD - Extra number of seconds to add to radar switch-back-on time (optional) --@return #MANTIS self --@usage Start up your MANTIS with a basic setting -- @@ -241,7 +245,7 @@ do -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- `mybluemantis:Start()` -- - function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, EmOnOff) + function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, EmOnOff, Padding) -- DONE: Create some user functions for these -- DONE: Make HQ useful @@ -278,7 +282,9 @@ do self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins self.state2flag = false self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode - + self.DLink = false + self.Padding = Padding or 10 + if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false @@ -325,7 +331,7 @@ do end -- @field #string version - self.version="0.5.2" + self.version="0.6.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -593,7 +599,7 @@ do -- E.g. `mymantis:SetAdvancedMode(true, 90)` function MANTIS:SetAdvancedMode(onoff, ratio) self:T(self.lid .. "SetAdvancedMode") - self:T({onoff, ratio}) + --self:T({onoff, ratio}) local onoff = onoff or false local ratio = ratio or 100 if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then @@ -619,6 +625,17 @@ do return self end + --- Set using an #INTEL_DLINK object instead of #DETECTION. Requires Develop branch of Moose.lua. + -- @param #MANTIS self + -- @param Ops.Intelligence#INTEL_DLINK DLink The data link object to be used. + function MANTIS:SetUsingDLink(DLink) + self:T(self.lid .. "SetUsingDLink") + self.DLink = true + self.Detection = DLink + self.DLTimeStamp = timer.getAbsTime() + return self + end + --- [Internal] Function to check if HQ is alive -- @param #MANTIS self -- @return #boolean True if HQ is alive, else false @@ -633,10 +650,10 @@ do local hqgrp = GROUP:FindByName(hq) if hqgrp then if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive - self:T(self.lid.." HQ is alive!") + --self:T(self.lid.." HQ is alive!") return true else - self:T(self.lid.." HQ is dead!") + --self:T(self.lid.." HQ is dead!") return false end end @@ -650,7 +667,7 @@ do function MANTIS:_CheckEWRState() self:T(self.lid .. "CheckEWRState") local text = self.lid.." Checking EWR State" - self:T(text) + --self:T(text) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(text) end -- start check @@ -666,7 +683,7 @@ do end end end - self:T(self.lid..string.format(" No of EWR alive is %d", nalive)) + --self:T(self.lid..string.format(" No of EWR alive is %d", nalive)) if nalive > 0 then return true else @@ -682,10 +699,8 @@ do -- @return #number Previous state for tracking 0, 1, or 2 function MANTIS:_CalcAdvState() self:T(self.lid .. "CalcAdvState") - local text = self.lid.." Calculating Advanced State" - self:T(text) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(text) end + 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 -- start check local currstate = self.adv_state -- save curr state for comparison later local EWR_State = self:_CheckEWRState() @@ -703,10 +718,12 @@ do local ratio = self.adv_ratio / 100 -- e.g. 80/100 = 0.8 ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6 local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78 - local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) - self:T(text) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(text) end + 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) + --self:T(text) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(text) end + end return newinterval, currstate end @@ -716,13 +733,13 @@ do -- @param #boolean ewr If true, will relocate EWR objects function MANTIS:SetAutoRelocate(hq, ewr) self:T(self.lid .. "SetAutoRelocate") - self:T({hq, ewr}) + --self:T({hq, ewr}) local hqrel = hq or false local ewrel = ewr or false if hqrel or ewrel then self.autorelocate = true self.autorelocateunits = { HQ = hqrel, EWR = ewrel } - self:T({self.autorelocate, self.autorelocateunits}) + --self:T({self.autorelocate, self.autorelocateunits}) end return self end @@ -739,7 +756,7 @@ do local HQGroup = self.HQ_CC if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists local _hqgrp = self.HQ_CC - self:T(self.lid.." Relocating HQ") + --self:T(self.lid.." Relocating HQ") local text = self.lid.." Relocating HQ" --local m= MESSAGE:New(text,10,"MANTIS"):ToAll() _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) @@ -752,7 +769,7 @@ do local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP for _,_grp in pairs (EWR_Grps) do if _grp:IsAlive() and _grp:IsGround() then - self:T(self.lid.." Relocating EWR ".._grp:GetName()) + --self:T(self.lid.." Relocating EWR ".._grp:GetName()) 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 @@ -778,12 +795,14 @@ do for _,_coord in pairs (set) do local coord = _coord -- get current coord to check -- output for cross-check - local dectstring = coord:ToStringLLDMS() - local samstring = samcoordinate:ToStringLLDMS() local targetdistance = samcoordinate:DistanceFromPointVec2(coord) - local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) - local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) - if self.verbose then self:I(self.lid..text) end + if self.verbose or self.debug then + local dectstring = coord:ToStringLLDMS() + local samstring = samcoordinate:ToStringLLDMS() + local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) + local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) + self:I(self.lid..text) + end -- end output to cross-check if targetdistance <= radius then return true, targetdistance @@ -888,7 +907,7 @@ do end self.SAM_Table = SAM_Tbl -- make SAMs evasive - local mysead = SEAD:New( SEAD_Grps ) + local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD mysead:SetEngagementRange(engagerange) self.mysead = mysead return self @@ -999,9 +1018,11 @@ do self:__ShoradActivated(1,name, radius, ontime) end -- debug output - local text = string.format("SAM %s switched to alarm state RED!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(self.lid..text) end + if self.debug or self.verbose then + local text = string.format("SAM %s switched to alarm state RED!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end end --end alive else if samgroup:IsAlive() then @@ -1014,9 +1035,11 @@ do self:__GreenState(1,samgroup) self.SamStateTracker[name] = "GREEN" end - local text = string.format("SAM %s switched to alarm state GREEN!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(self.lid..text) end + if self.debug or self.verbose then + local text = string.format("SAM %s switched to alarm state GREEN!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end end --end alive end --end check end --for for loop @@ -1066,6 +1089,20 @@ do end -- end newstate vs oldstate return self end + + --- [Internal] Check DLink state + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:_CheckDLinkState() + self:T(self.lid .. "_CheckDLinkState") + local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK + local TS = timer.getAbsTime() + if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then + self.DLink = false + self.Detection = self:StartDetection() -- fall back + self:I(self.lid .. "Intel DLink not running - switching back to single detection!") + end + end --- [Internal] Function to set start state -- @param #MANTIS self @@ -1077,11 +1114,13 @@ do self:T({From, Event, To}) self:T(self.lid.."Starting MANTIS") self:SetSAMStartState() - self.Detection = self:StartDetection() + if not self.DLink then + self.Detection = self:StartDetection() + end if self.advAwacs then self.AWACS_Detection = self:StartAwacsDetection() end - self:__Status(self.detectinterval) + self:__Status(-math.random(1,10)) return self end @@ -1120,11 +1159,16 @@ do end end - -- timer for advanced state check + -- advanced state check if self.advanced then self:_CheckAdvState() end + -- check DLink state + if self.DLink then + self:_CheckDLinkState() + end + return self end @@ -1136,6 +1180,13 @@ do -- @return #MANTIS self function MANTIS:onafterStatus(From,Event,To) self:T({From, Event, To}) + -- Display some states + if self.debug then + self:I(self.lid .. "Status Report") + for _name,_state in pairs(self.SamStateTracker) do + self:I(string.format("Site %s\tStatus %s",_name,_state)) + end + end local interval = self.detectinterval * -1 self:__Status(interval) return self From 5e8fe977528d7451713ed7e9c5ba6a94dea56455 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 1 Sep 2021 13:34:13 +0200 Subject: [PATCH 41/68] CTLD Added method to inject troops into the field. --- Moose Development/Moose/Ops/CTLD.lua | 68 ++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 63a70c099..3ddcda226 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: Aug 2021 +-- Date: Sep 2021 do ------------------------------------------------------ @@ -51,21 +51,21 @@ CTLD_ENGINEERING = { --- CTLD_ENGINEERING class version. -- @field #string version - CTLD_ENGINEERING.Version = "0.0.2" + CTLD_ENGINEERING.Version = "0.0.3" --- Create a new instance. -- @param #CTLD_ENGINEERING self -- @param #string Name - -- @param #string GroupName + -- @param #string GroupName Name of Engineering #GROUP object -- @param Wrapper.Group#GROUP HeliGroup HeliGroup - -- @param Wrapper.Unit#UNIT HeliGroup HeliUnit + -- @param Wrapper.Unit#UNIT HeliUnit HeliUnit -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:New(Name, GroupName, HeliGroup, HeliUnit) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #CTLD_ENGINEERING - BASE:I({Name, GroupName, HeliGroup:GetName(), HeliUnit:GetName()}) + --BASE:I({Name, GroupName}) self.Name = Name or "Engineer Squad" -- #string self.Group = GROUP:FindByName(GroupName) -- Wrapper.Group#GROUP @@ -195,10 +195,10 @@ CTLD_ENGINEERING = { -- have we tried this cargo recently? local tag = chalk.tag or "none" local timestamp = chalk.timestamp or 0 - self:I({chalk}) + --self:I({chalk}) -- enough time gone? local gone = timer.getAbsTime() - timestamp - self:I({time=gone}) + --self:I({time=gone}) if gone >= self.marktimer then ok = true _cargo:WipeMark() @@ -919,7 +919,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.7a2" +CTLD.version="0.1.7a3" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2088,7 +2088,7 @@ function CTLD:_UnloadTroops(Group, Unit) 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, Group, Unit) + 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) @@ -3349,6 +3349,56 @@ end return self end + --- (User) Pre-populate troops in the field. + -- @param #CTLD self + -- @param Core.Zone#ZONE Zone The zone where to drop the troops. + -- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn. + -- @return #CTLD self + -- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone: + -- -- create a matching #CTLD_CARGO type + -- local InjectTroopsType = CTLD_CARGO:New(nil,"Injected Infantry",{"Inf12"},CTLD_CARGO.Enum.TROOPS,true,true,12,nil,false,80) + -- -- get a #ZONE object + -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE + -- -- and go: + -- my_ctld:InjectTroops(dropzone,InjectTroopsType) + function CTLD:InjectTroops(Zone,Cargo) + self:T(self.lid.." InjectTroops") + local cargo = Cargo -- #CTLD_CARGO + self.CargoCounter = self.CargoCounter + 1 + cargo.ID = self.CargoCounter + table.insert(self.Cargo_Troops,cargo) + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + -- unload troops + local name = cargo:GetName() or "none" + local temptable = cargo:GetTemplates() or {} + local factor = 1.5 + local zone = Zone + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + end -- template loop + cargo:SetWasDropped(true) + -- engineering group? + 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:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) + else + self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) + end + end -- if type end + return self + end ------------------------------------------------------------------- -- FSM functions ------------------------------------------------------------------- From db5797bb4ec586f462a1444005ca1bc1f511d49f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 2 Sep 2021 18:48:40 +0200 Subject: [PATCH 42/68] Bug fixing --- Moose Development/Moose/Functional/Range.lua | 1 + Moose Development/Moose/Utilities/Utils.lua | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 2b02c0de5..9e5db273e 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -2831,6 +2831,7 @@ function RANGE:_CheckInZone(_unitName) local accur=0 if shots>0 then accur=_result.hits/shots*100 + if accur > 100 then accur = 100 end end -- Message text. diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 84abdc68f..71c3d524a 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1698,7 +1698,7 @@ function UTILS.GenerateLaserCodes() while _code < 1777 and _count < 30 do while true do _code = _code + 1 - if not self:_ContainsDigit(_code, 8) + if not ContainsDigit(_code, 8) and not ContainsDigit(_code, 9) and not ContainsDigit(_code, 0) then table.insert(jtacGeneratedLaserCodes, _code) From 53367c786e16236ac95d4014f2e84a2d9ea43c4e Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Wed, 1 Sep 2021 20:05:57 +1000 Subject: [PATCH 43/68] AV-8B LIG and Unicorn fix --- Moose Development/Moose/Ops/Airboss.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 085ec11b4..9bf8113f6 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -12206,6 +12206,7 @@ end -- * > 24 seconds: No Grade "--" -- -- If you manage to be between 16.4 and and 16.6 seconds, you will even get and okay underline "\_OK\_". +-- No groove time for Harrier on LHA, LHD set to Tgroove Unicorn as starting point to allow possible _OK_ 5.0. -- -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. @@ -12224,6 +12225,8 @@ function AIRBOSS:_EvalGrooveTime(playerData) grade="OK Groove" elseif t<=24 then grade="(LIG)" + elseif t>=25 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then -- VSTOL Operations with AV-8B + grade="OK V/STOL Groove" else grade="LIG" end @@ -12258,7 +12261,7 @@ function AIRBOSS:_LSOgrade(playerData) -- Put everything together. local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR - -- Count number of minor, normal and major deviations. + -- Count number of minor, normal and major deviations. TODO - work on Harrier counts due slower approach speed. local N=nXX+nIM+nIC+nAR local nL=count(G, '_')/2 local nS=count(G, '%(') @@ -12270,8 +12273,8 @@ function AIRBOSS:_LSOgrade(playerData) local grade local points - if N==0 and TgrooveUnicorn then - -- No deviations, should be REALLY RARE! + if N==0 and (TgrooveUnicorn or aircrafttype~=AIRBOSS.AircraftCarrier.AV8B) then + -- No deviations, should be REALLY RARE! Grove time for AV-8B Harrier not required, possible to still get Unicorn. grade="_OK_" points=5.0 G="Unicorn" From 8ecfd913a34fe19e2fc2b291120481aa9acc349a Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Wed, 1 Sep 2021 22:31:36 +1000 Subject: [PATCH 44/68] Av-8B specific deviation counts adj. --- Moose Development/Moose/Ops/Airboss.lua | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 9bf8113f6..dab7312e1 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -12252,7 +12252,7 @@ function AIRBOSS:_LSOgrade(playerData) return select(2, string.gsub(base, pattern, "")) end - -- Analyse flight data and conver to LSO text. + -- Analyse flight data and convert to LSO text. 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) @@ -12291,6 +12291,22 @@ function AIRBOSS:_LSOgrade(playerData) -- Only minor corrections grade="OK" points=4.0 + end + -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback) + -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. + if nL>2 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then + -- Larger deviations ==> "No grade" 2.0 points. + grade="--" + points=2.0 + + elseif nN>2 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then + -- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections. + grade="(OK)" + points=3.0 + else + -- Only minor corrections + grade="OK" + points=4.0 end end From ea09dc5a6e64fbb86d9676947cc67dd0d3042871 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Sat, 4 Sep 2021 10:38:00 +1000 Subject: [PATCH 45/68] Vstol groove timing --- Moose Development/Moose/Ops/Airboss.lua | 27 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index dab7312e1..af788cab9 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -12204,9 +12204,13 @@ end -- * 12-21 seconds: OK (15-18 is ideal) -- * 22-24 seconds: Fair "(OK) -- * > 24 seconds: No Grade "--" --- +-- -- If you manage to be between 16.4 and and 16.6 seconds, you will even get and okay underline "\_OK\_". -- No groove time for Harrier on LHA, LHD set to Tgroove Unicorn as starting point to allow possible _OK_ 5.0. +-- If time in the AV-8B +-- +-- * < 90 seconds: OK V/STOL +-- * > 91 Seconds: SLOW V/STOL (Early hover stop selection) -- -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data table. @@ -12225,8 +12229,10 @@ function AIRBOSS:_EvalGrooveTime(playerData) grade="OK Groove" elseif t<=24 then grade="(LIG)" - elseif t>=25 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then -- VSTOL Operations with AV-8B + elseif t<90 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then -- VSTOL Operations with AV-8B grade="OK V/STOL Groove" + elseif t>=91 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then -- VSTOL Early Hover stop selection slow to Abeam LDG Spot AV-8B + grade="SLOW V/STOL Groove" else grade="LIG" end @@ -12235,6 +12241,10 @@ function AIRBOSS:_EvalGrooveTime(playerData) if t>=16.4 and t<=16.6 then grade="_OK_" end + -- V/STOL Unicorn! + if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B and (t>=65.0 and t<=75.0) then + grade="_OK_ V/STOL" + end return grade end @@ -12267,14 +12277,15 @@ function AIRBOSS:_LSOgrade(playerData) local nS=count(G, '%(') local nN=N-nS-nL - -- Groove time 15-18.99 sec for a unicorn. + -- Groove time 15-18.99 sec for a unicorn. Or 65-75 for V/STOL unicorn. local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false - + local TgrooveVstolUnicorn=Tgroove and (Tgroove>=65.0 and Tgroove<=75.0)and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B or false + local grade local points - if N==0 and (TgrooveUnicorn or aircrafttype~=AIRBOSS.AircraftCarrier.AV8B) then - -- No deviations, should be REALLY RARE! Grove time for AV-8B Harrier not required, possible to still get Unicorn. + if N==0 and (TgrooveUnicorn or TgrooveVstolUnicorn ) then + -- No deviations, should be REALLY RARE! grade="_OK_" points=5.0 G="Unicorn" @@ -12294,12 +12305,12 @@ function AIRBOSS:_LSOgrade(playerData) end -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback) -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. - if nL>2 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then + if nL>3 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then -- Larger deviations ==> "No grade" 2.0 points. grade="--" points=2.0 - elseif nN>2 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then + elseif nN>3 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then -- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections. grade="(OK)" points=3.0 From ad56e399427cd92d338ffb37a8067f80717fff5b Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Sat, 4 Sep 2021 13:40:10 +1000 Subject: [PATCH 46/68] Docs AV-8B clarifications --- Moose Development/Moose/Ops/Airboss.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index af788cab9..4756b9357 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -50,8 +50,8 @@ -- -- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier. -- --- The AV-8B Harrier and the USS Tarawa, USS America and Juan Carlos I are WIP. Those two can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and --- the no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier. +-- The AV-8B Harrier, the USS Tarawa, USS America and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and +-- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier. -- However, the two Case II/III pattern are very similar so this is not a big drawback. -- -- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version. @@ -298,8 +298,8 @@ -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case1_Landing.png) -- -- Once the aircraft reaches the Initial, the landing pattern begins. The important steps of the pattern are shown in the image above. --- The AV-8B Harrier pattern is very similar, the only differences are as there is no angled deck there is no wake check. from the ninety you wil fly strait in to 26 ft to port of the tram line. --- The aim is to arrive abeam the landing spot in a stable hover at 120 ft with forward speed matched to the boat. From there the LSO will call "cleared to land". You then cross to the tram line at the designated landing spot at land vertcally. +-- The AV-8B Harrier pattern is very similar, the only differences are as there is no angled deck there is no wake check. from the ninety you wil fly a straight approach offset 26 ft to port (left) of the tram line. +-- The aim is to arrive abeam the landing spot in a stable hover at 120 ft with forward speed matched to the boat. From there the LSO will call "cleared to land". You then level cross to the tram line at the designated landing spot at land vertcally. -- -- -- ## CASE III @@ -1261,7 +1261,7 @@ AIRBOSS = { --- Aircraft types capable of landing on carrier (human+AI). -- @type AIRBOSS.AircraftCarrier --- @field #string AV8B AV-8B Night Harrier. Works only with the USS Tarawa. +-- @field #string AV8B AV-8B Night Harrier. Works only with the USS Tarawa, USS America and Juan Carlos I. -- @field #string A4EC A-4E Community mod. -- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics. -- @field #string F14A F-14A by Heatblur. @@ -1429,8 +1429,8 @@ AIRBOSS.PatternStep={ -- @field #string IM "IM": In the middle. -- @field #string IC "IC": In close. -- @field #string AR "AR": At the ramp. --- @field #string AL "AL": Abeam landing position (Tarawa). --- @field #string LC "LC": Level crossing (Tarawa). +-- @field #string AL "AL": Abeam landing position (V/STOL). +-- @field #string LC "LC": Level crossing (V/STOL). -- @field #string IW "IW": In the wires. AIRBOSS.GroovePos={ X0="X0", From b93ba13644cba8ade2345258a5f46c3157038318 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:20:37 +1000 Subject: [PATCH 47/68] bug fix to V/Stol groove. --- Moose Development/Moose/Ops/Airboss.lua | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 4756b9357..b97f7a91b 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -12219,7 +12219,7 @@ function AIRBOSS:_EvalGrooveTime(playerData) -- Time in groove. local t=playerData.Tgroove - + local grade="" if t<9 then grade="_NESA_" @@ -12229,20 +12229,24 @@ function AIRBOSS:_EvalGrooveTime(playerData) grade="OK Groove" elseif t<=24 then grade="(LIG)" - elseif t<90 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then -- VSTOL Operations with AV-8B + -- Time in groove for AV-8B + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<55 then -- VSTOL Late Hover stop selection too fast to Abeam LDG Spot AV-8B. + grade="FAST V/STOL Groove" + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<90 then -- VSTOL Operations with AV-8B. grade="OK V/STOL Groove" - elseif t>=91 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then -- VSTOL Early Hover stop selection slow to Abeam LDG Spot AV-8B + elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t>=91 then -- VSTOL Early Hover stop selection slow to Abeam LDG Spot AV-8B. grade="SLOW V/STOL Groove" else grade="LIG" end - + -- The unicorn! if t>=16.4 and t<=16.6 then grade="_OK_" end + -- V/STOL Unicorn! - if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B and (t>=65.0 and t<=75.0) then + if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and (t>=65.0 and t<=75.0) then grade="_OK_ V/STOL" end @@ -12277,10 +12281,10 @@ function AIRBOSS:_LSOgrade(playerData) local nS=count(G, '%(') local nN=N-nS-nL - -- Groove time 15-18.99 sec for a unicorn. Or 65-75 for V/STOL unicorn. + -- Groove time 15-18.99 sec for a unicorn. Or 65-70 for V/STOL unicorn. local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false - local TgrooveVstolUnicorn=Tgroove and (Tgroove>=65.0 and Tgroove<=75.0)and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B or false + local TgrooveVstolUnicorn=Tgroove and (Tgroove>=65.0 and Tgroove<=70.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false local grade local points @@ -12298,28 +12302,24 @@ function AIRBOSS:_LSOgrade(playerData) -- No larger but average deviations ==> "Fair Pass" Pass with average deviations and corrections. grade="(OK)" points=3.0 - else - -- Only minor corrections - grade="OK" - points=4.0 - end - -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback) + -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback) -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. - if nL>3 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then + elseif nL>3 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -- Larger deviations ==> "No grade" 2.0 points. grade="--" points=2.0 - elseif nN>3 and aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then + elseif nN>2 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then -- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections. grade="(OK)" points=3.0 - else + else -- Only minor corrections grade="OK" points=4.0 end - end + +end -- Replace" )"( and "__" G=G:gsub("%)%(", "") From e6e2651f8c0b0d9e478df413f91dbb98993d2672 Mon Sep 17 00:00:00 2001 From: Penecruz <73371761+Penecruz@users.noreply.github.com> Date: Mon, 6 Sep 2021 11:08:29 +1000 Subject: [PATCH 48/68] Bug fix to AV-8B grading WIP --- Moose Development/Moose/Ops/Airboss.lua | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index b97f7a91b..de432cd0f 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -12294,7 +12294,18 @@ function AIRBOSS:_LSOgrade(playerData) points=5.0 G="Unicorn" else - if nL>0 then + + -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback) + -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. + if nL>3 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- Larger deviations ==> "No grade" 2.0 points. + grade="--" + points=2.0 + elseif nN>2 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then + -- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections. + grade="(OK)" + points=3.0 + elseif nL>0 then -- Larger deviations ==> "No grade" 2.0 points. grade="--" points=2.0 @@ -12302,18 +12313,7 @@ function AIRBOSS:_LSOgrade(playerData) -- No larger but average deviations ==> "Fair Pass" Pass with average deviations and corrections. grade="(OK)" points=3.0 - -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback) - -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. - elseif nL>3 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - -- Larger deviations ==> "No grade" 2.0 points. - grade="--" - points=2.0 - - elseif nN>2 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then - -- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections. - grade="(OK)" - points=3.0 - else + else -- Only minor corrections grade="OK" points=4.0 From a844a5d6971a81afbcfe36f260e17614e06818db Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 7 Sep 2021 19:51:51 +0200 Subject: [PATCH 49/68] CSAR: Align to Dev changes --- Moose Development/Moose/Ops/CSAR.lua | 64 +++------------------------- 1 file changed, 7 insertions(+), 57 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index bed92db98..6817db0e1 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -22,7 +22,7 @@ -- @module Ops.CSAR -- @image OPS_CSAR.jpg --- Date: Aug 2021 +-- Date: Sep 2021 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -233,7 +233,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.10r3" +CSAR.version="0.1.10r5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -349,7 +349,7 @@ function CSAR:New(Coalition, Template, Alias) self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.template = Template or "generic" -- template for downed pilot self.mashprefix = {"MASH"} -- prefixes used to find MASHes - self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? + self.autosmoke = false -- automatically smoke location when heli is near self.autosmokedistance = 2000 -- distance for autosmoke -- added 0.1.4 @@ -718,7 +718,6 @@ function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessa return self end --- TODO: Split in functions per Event type --- (Internal) Event handler. -- @param #CSAR self function CSAR:_EventHandler(EventData) @@ -790,7 +789,7 @@ function CSAR:_EventHandler(EventData) if self:_DoubleEjection(_unitname) then return end - self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!", self.coalition, self.messageTime) + else self:T(self.lid .. " Pilot has not taken off, ignore") end @@ -878,13 +877,6 @@ function CSAR:_EventHandler(EventData) if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true) - --[[ - if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_event.IniUnitName) then - self:_DisplayMessageToSAR(_unit, "Open the door to let me out!", self.messageTime, true) - else - self:_RescuePilots(_unit) - end - --]] else self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) end @@ -904,7 +896,6 @@ end function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) self:T(self.lid .. " _InitSARForPilot") local _leader = _downedGroup:GetUnit(1) - --local _groupName = _downedGroup:GetName() local _groupName = _GroupName local _freqk = _freq / 1000 local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) @@ -920,7 +911,7 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) end -- trigger FSM event - self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) + self:__PilotDown(2,_downedGroup, _freqk, _groupName, _coordinatesText) return self end @@ -1090,7 +1081,6 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam local grouptable = downedgrouptable --#CSAR.DownedPilot self.inTransitGroups[_heliName][_woundedGroupName] = { - -- DONE: Fix with #CSAR.DownedPilot originalUnit = grouptable.originalUnit, woundedGroup = _woundedGroupName, side = self.coalition, @@ -1129,40 +1119,6 @@ end -- @return #boolean outcome The outcome. function CSAR:_IsLoadingDoorOpen( unit_name ) self:T(self.lid .. " _IsLoadingDoorOpen") - - --[[ - local ret_val = false - local unit = Unit.getByName(unit_name) - if unit ~= nil then - local type_name = unit:getTypeName() - - if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then - self:T(unit_name .. " Cargo doors are open or cargo door not present") - ret_val = true - end - - if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then - self:T(unit_name .. " a side door is open") - ret_val = true - end - - if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then - self:T(unit_name .. " a side door is open ") - ret_val = true - end - - if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then - self:T(unit_name .. " front door(s) are open") - ret_val = true - end - - if ret_val == false then - self:T(unit_name .. " all doors are closed") - end - return ret_val - - end -- nil - --]] return UTILS.IsLoadingDoorOpen(unit_name) end @@ -1195,14 +1151,12 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG else self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true) end - --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true end -- have we landed close enough? if not _heliUnit:InAir() then - -- if you land on them, doesnt matter if they were heading to someone else as you\'re closer, you win! :) if self.pilotRuntoExtractPoint == true then if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] @@ -1353,8 +1307,7 @@ function CSAR:_RescuePilots(_heliUnit) -- Groups already rescued return end - - -- DONE: count saved units? + local PilotsSaved = self:_PilotsOnboard(_heliName) self.inTransitGroups[_heliName] = nil @@ -1450,7 +1403,6 @@ function CSAR:_DisplayActiveSAR(_unitName) local _groupName = _value.name self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) self:T({Table=_value}) - --local _woundedGroup = GROUP:FindByName(_groupName) local _woundedGroup = _value.group if _woundedGroup and _value.alive then local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) @@ -1747,7 +1699,6 @@ function CSAR:_GetDistance(_point1, _point2) if _point1 and _point2 then local distance1 = _point1:Get2DDistance(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2) - --self:I({dist1=distance1, dist2=distance2}) if distance1 and type(distance1) == "number" then return distance1 elseif distance2 and type(distance2) == "number" then @@ -1768,7 +1719,6 @@ end -- @param #CSAR self function CSAR:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") - --local _skipFrequencies = self.SkipFrequencies local FreeVHFFrequencies = {} FreeVHFFrequencies = UTILS.GenerateVHFrequencies() @@ -1808,7 +1758,6 @@ function CSAR:_GetClockDirection(_heli, _group) if _heading then local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end - --clock = math.floor(Aspect / 30) clock = math.abs(UTILS.Round((Aspect / 30),0)) if clock == 0 then clock = 12 end end @@ -1917,6 +1866,7 @@ function CSAR:onafterStart(From, Event, To) else self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end + self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also? self:__Status(-10) return self end From 8873504daf8f829fe9024c9a13d9d1e1950bbf6f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 7 Sep 2021 19:52:06 +0200 Subject: [PATCH 50/68] CTLD: Align to Dev changes --- Moose Development/Moose/Ops/CTLD.lua | 164 ++++++++++++++++----------- 1 file changed, 95 insertions(+), 69 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3ddcda226..59add202b 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -577,6 +577,12 @@ do -- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4. No weight. We only have 2 in stock: -- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4,nil,2) -- +-- -- add an engineers unit called "Wrenches" using template "Engineers", of type ENGINEERS with size 2. Engineers can be loaded, dropped, +-- -- and extracted like troops. However, they will seek to build and/or repair crates found in a given radius. Handy if you can\'t stay +-- -- to build or repair or under fire. +-- my_ctld:AddTroopsCargo("Wrenches",{"Engineers"},CTLD_CARGO.Enum.ENGINEERS,4) +-- myctld.EngineerSearch = 2000 -- teams will search for crates in this radius. +-- -- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build -- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) @@ -590,6 +596,7 @@ do -- -- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair -- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) +-- my_ctld.repairtime = 300 -- takes 300 seconds to repair something -- -- ## 1.3 Add logistics zones -- @@ -614,8 +621,7 @@ do -- -- Add a zone of type SHIP to our setup. Players can load troops and crates from this ship -- -- "Tarawa" is the unitname (callsign) of the ship from the ME. Players can load, if they are inside the zone. -- -- The ship is 240 meters long and 20 meters wide. --- -- Note that smoke, flares, beacons don't work for this type of loadzone (yet). Also, you need to adjust --- -- the max hover height to deck height plus 5 meters or so for loading to work. +-- -- Note that you need to adjust the max hover height to deck height plus 5 meters or so for loading to work. -- -- When the ship is moving, forcing hoverload might not be a good idea. -- my_ctld:AddCTLDZone("Tarawa",CTLD.CargoZoneType.SHIP,SMOKECOLOR.Blue,true,true,240,20) -- @@ -636,6 +642,7 @@ do -- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) -- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. -- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. +-- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Don\'t forget to change this matching your coalition! -- -- ## 2.1 User functions -- @@ -645,19 +652,20 @@ do -- -- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. -- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: --- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8) +-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12) -- --- Default unit type capabilities are: +-- -- Default unit type capabilities are: -- --- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, --- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, --- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, --- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, --- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, --- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, --- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, --- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, --- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, +-- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, +-- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, +-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, +-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, +-- ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, +-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, +-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, +-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, +-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, -- -- -- ### 2.1.2 Activate and deactivate zones @@ -904,22 +912,23 @@ CTLD.CargoZoneType = { -- @field #number cratelimit Number of crates transportable. -- @field #number trooplimit Number of troop units transportable. CTLD.UnitTypes = { - ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, - ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, - ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, - ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, - ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, - ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, - ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, - ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, - ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, - ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, - ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers + ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, + ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, + ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, + ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, + ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, + ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, + ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, + ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, + ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, + ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, + ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, -- 19t cargo, 64 paratroopers. + --Actually it's longer, but the center coord is off-center of the model. } --- CTLD class version. -- @field #string version -CTLD.version="0.1.7a3" +CTLD.version="0.1.7a5" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1026,7 +1035,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.EngineerSearch = 2000 -- #number search distance for crates to build or repair -- setup - self.CrateDistance = 30 -- list/load crates in this radius + self.CrateDistance = 35 -- list/load crates in this radius self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor self.prefixes = Prefixes or {"Cargoheli"} --self.I({prefixes = self.prefixes}) @@ -1054,6 +1063,9 @@ function CTLD:New(Coalition, Prefixes, Alias) -- time to repair a unit/group self.repairtime = 300 + -- country of crates spawned + self.cratecountry = country.id.GERMANY + for i=1,100 do math.random() end @@ -1202,6 +1214,7 @@ function CTLD:_GetUnitCapabilities(Unit) capabilities.cratelimit = 0 capabilities.trooplimit = 0 capabilities.type = "generic" + capabilities.length = 20 end return capabilities end @@ -1665,20 +1678,18 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) -- loop crates needed for i=1,number do local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) - local cratedistance = i*4 + 8 + local cratedistance = (i-1)*2.5 + capabilities.length + if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end + local addon = 0 if IsHerc then - -- wider radius - cratedistance = i*4 + 12 + -- spawn behind the Herc + addon = 180 end - for i=1,50 do - math.random(90,270) - end - local rheading = math.floor(((math.random(90,270) * heading) + 1) / 360) - if not IsHerc then - rheading = rheading + 180 -- mirror for Helis - end - if rheading > 360 then rheading = rheading - 360 end -- catch > 360 - local cratecoord = position:Translate(cratedistance,rheading) + -- altered heading logic + -- TODO: right standard deviation? + local randomheading = UTILS.RandomGaussian(0,30,-90,90,100) + randomheading = math.fmod((heading + randomheading + addon), 360) + local cratecoord = position:Translate(cratedistance,randomheading) local cratevec2 = cratecoord:GetVec2() self.CrateCounter = self.CrateCounter + 1 if type(ship) == "string" then @@ -1690,19 +1701,19 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) dist = dist - (20 + math.random(1,10)) local width = width / 2 local Offy = math.random(-width,width) - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",country.id.GERMANY) + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",self.cratecountry) --:InitCoordinate(cratecoord) :InitLinkToUnit(Ship,dist,Offy,0) :Spawn(270,cratealias) else - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",country.id.GERMANY) - :InitCoordinate(cratecoord) - --:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) - :Spawn(270,cratealias) + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",self.cratecountry) + :InitCoordinate(cratecoord) + --:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) + :Spawn(270,cratealias) end local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() - self.CargoCounter = self.CargoCounter +1 + 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) @@ -1712,13 +1723,13 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) end table.insert(self.Spawned_Cargo, realcargo) 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) - end - self:_SendMessage(text, 10, false, Group) - return self + 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) + end + self:_SendMessage(text, 10, false, Group) + return self end --- (Internal) Function to find and list nearby crates. @@ -1825,7 +1836,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) local unitname = unit:GetName() -- see if this heli can load crates local unittype = unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities(Unit) + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number @@ -2827,8 +2838,8 @@ 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} - for i=1,3 do + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} + for i=1,4 do for index,cargozone in pairs(zones[i]) do -- Get Beacon object from zone local czone = cargozone -- #CTLD.CargoZone @@ -2858,9 +2869,15 @@ end -- @param #string Sound Name of soundfile. -- @param #number Mhz Frequency in Mhz. -- @param #number Modulation Modulation AM or FM. -function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation) +-- @param #boolean IsShip If true zone is a ship. +function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip) self:T(self.lid .. " _AddRadioBeacon") - local Zone = ZONE:FindByName(Name) + local Zone = nil + if IsShip then + Zone = UNIT:FindByName(Name) + else + Zone = ZONE:FindByName(Name) + end local Sound = Sound or "beacon.ogg" if Zone then local ZoneCoord = Zone:GetCoordinate() @@ -2877,8 +2894,10 @@ end function CTLD:_RefreshRadioBeacons() self:T(self.lid .. " _RefreshRadioBeacons") - local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} - for i=1,3 do + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} + for i=1,4 do + local IsShip = false + if i == 4 then IsShip = true end for index,cargozone in pairs(zones[i]) do -- Get Beacon object from zone local czone = cargozone -- #CTLD.CargoZone @@ -2891,9 +2910,9 @@ function CTLD:_RefreshRadioBeacons() local FM = FMbeacon.frequency -- MHz local VHF = VHFbeacon.frequency -- KHz local UHF = UHFbeacon.frequency -- MHz - self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM) - self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM) - self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM) + self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM, IsShip) + self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip) + self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip) end end end @@ -2982,12 +3001,17 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) local Group = Unit:GetGroup() local smokedistance = self.smokedistance local smoked = false - local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} - for i=1,3 do + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} + for i=1,4 do for index,cargozone in pairs(zones[i]) do local CZone = cargozone --#CTLD.CargoZone local zonename = CZone.name - local zone = ZONE:FindByName(zonename) + local zone = nil + if i == 4 then + zone = UNIT:FindByName(zonename) + else + zone = ZONE:FindByName(zonename) + end local zonecoord = zone:GetCoordinate() local active = CZone.active local color = CZone.color @@ -3017,11 +3041,12 @@ end --- User - Function to add/adjust unittype capabilities. -- @param #CTLD self -- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission. - -- @param #boolean Cancrates Unit can load crates. - -- @param #boolean Cantroops Unit can load troops. - -- @param #number Cratelimit Unit can carry number of crates. - -- @param #number Trooplimit Unit can carry number of troops. - function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit) + -- @param #boolean Cancrates Unit can load crates. Default false. + -- @param #boolean Cantroops Unit can load troops. Default false. + -- @param #number Cratelimit Unit can carry number of crates. Default 0. + -- @param #number Trooplimit Unit can carry number of troops. Default 0. + -- @param #number Length Unit lenght (in mteres) for the load radius. Default 20. + function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length) self:T(self.lid .. " UnitCapabilities") local unittype = nil local unit = nil @@ -3040,6 +3065,7 @@ end capabilities.troops = Cantroops or false capabilities.cratelimit = Cratelimit or 0 capabilities.trooplimit = Trooplimit or 0 + capabilities.length = Length or 20 self.UnitTypes[unittype] = capabilities return self end From 136bd19f1911a2d9b48230995757ff587cdf99d0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 11 Sep 2021 10:03:49 +0200 Subject: [PATCH 51/68] Bug fixing --- Moose Development/Moose/Core/Set.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 6aa72d67e..4ddac7939 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -4746,7 +4746,7 @@ do -- SET_AIRBASE local airbaseName, airbase=self:FindInDatabase(EventData) - if airbase and airbase:IsShip() or airbase:IsHelipad() then + if airbase and (airbase:IsShip() or airbase:IsHelipad()) then self:RemoveAirbasesByName(airbaseName) end From 05ce7e4513abc503bda74886671218d0a59c5141 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 11 Sep 2021 15:20:20 +0200 Subject: [PATCH 52/68] CTLD - added alternative crate spawn by @mousepilot. Add menu item to list stock.Injected troops will not lead to cargo type duplication. --- Moose Development/Moose/Ops/CTLD.lua | 166 ++++++++++++++++++++++++--- 1 file changed, 149 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 59add202b..0309975df 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -864,7 +864,11 @@ CTLD = { -- DONE: Stats Running -- DONE: Added support for Hercules -- TODO: Possibly - either/or loading crates and troops --- DONE: (WIP) Limit of troops, crates buildable? +-- DONE: Make inject respect existing cargo types +-- TODO: Drop beacons or flares/smoke +-- TODO: Add statics as cargo +-- DONE: List cargo in stock +-- DONE: Limit of troops, crates buildable? ------------------------------ --- Radio Beacons @@ -928,7 +932,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.7a5" +CTLD.version="0.1.8a1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1063,9 +1067,16 @@ function CTLD:New(Coalition, Prefixes, Alias) -- time to repair a unit/group self.repairtime = 300 + -- place spawned crates in front of aircraft + self.placeCratesAhead = false + -- country of crates spawned self.cratecountry = country.id.GERMANY + if self.coalition == coalition.side.RED then + self.cratecountry = country.id.RUSSIA + end + for i=1,100 do math.random() end @@ -1675,21 +1686,45 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) 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 then + -- spawn behind the Herc + addon = 180 + end -- loop crates needed for i=1,number do local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) - local cratedistance = (i-1)*2.5 + capabilities.length - if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end - local addon = 0 - if IsHerc then - -- spawn behind the Herc - addon = 180 + if not self.placeCratesAhead then + cratedistance = (i-1)*2.5 + capabilities.length + if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end + -- altered heading logic + -- TODO: right standard deviation? + rheading = UTILS.RandomGaussian(0,25,-90,90,100) + rheading = math.fmod((heading + rheading + addon), 360) + else + local initialSpacing = IsHerc and 16 or 12 -- initial spacing of the first crates + local crateSpacing = 4 -- further spacing of remaining crates + local lateralSpacing = 4 -- lateral spacing of crates + local nrSideBySideCrates = 3 -- number of crates that are placed side-by-side + + if cratesneeded == 1 then + -- single crate needed spawns straight ahead + cratedistance = initialSpacing + rheading = heading + else + if (i - 1) % nrSideBySideCrates == 0 then + cratedistance = i == 1 and initialSpacing or cratedistance + crateSpacing + angleOffNose = math.ceil(math.deg(math.atan(lateralSpacing / cratedistance))) + rheading = heading - angleOffNose + else + rheading = rheading + angleOffNose + end + end end - -- altered heading logic - -- TODO: right standard deviation? - local randomheading = UTILS.RandomGaussian(0,30,-90,90,100) - randomheading = math.fmod((heading + randomheading + addon), 360) - local cratecoord = position:Translate(cratedistance,randomheading) + local cratecoord = position:Translate(cratedistance,rheading) local cratevec2 = cratecoord:GetVec2() self.CrateCounter = self.CrateCounter + 1 if type(ship) == "string" then @@ -2023,6 +2058,82 @@ function CTLD:_ListCargo(Group, Unit) return self end +--- (Internal) Function to list loaded cargo. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +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 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) + + if (crateno > 0 or troopno > 0) then + + local report = REPORT:New("Inventory Sheet") + report:Add("------------------------------------------------------------") + report:Add(string.format("Troops: %d, Cratetypes: %d",troopno,crateno)) + report:Add("------------------------------------------------------------") + report:Add(" -- TROOPS --") + for _,_cargo in pairs(trptypes) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + 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 size: %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 -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + 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: %d stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),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 + --- (Internal) Function to check if a unit is a Hercules C-130. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit @@ -2565,6 +2676,8 @@ function CTLD:_RefreshF10Menus() local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) + local invtry = MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu, self._ListInventory, self, _group, _unit) + local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, false) local smokemenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, true):Refresh() -- sub menus @@ -2594,7 +2707,6 @@ function CTLD:_RefreshF10Menus() local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() end - local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) if unittype == "Hercules" then local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() else @@ -3375,6 +3487,7 @@ end return self end + -- DONE: Make inject respect existing cargo types --- (User) Pre-populate troops in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. @@ -3390,9 +3503,27 @@ end function CTLD:InjectTroops(Zone,Cargo) self:T(self.lid.." InjectTroops") local cargo = Cargo -- #CTLD_CARGO - self.CargoCounter = self.CargoCounter + 1 - cargo.ID = self.CargoCounter - table.insert(self.Cargo_Troops,cargo) + + local function IsTroopsMatch(cargo) + local match = false + local cgotbl = self.Cargo_Troops + local name = cargo:GetName() + for _,_cgo in pairs (cgotbl) do + local cname = _cgo:GetName() + if name == cname then + match = true + break + end + end + return match + end + + if not IsTroopsMatch(cargo) then + self.CargoCounter = self.CargoCounter + 1 + cargo.ID = self.CargoCounter + table.insert(self.Cargo_Troops,cargo) + end + local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then -- unload troops @@ -3425,6 +3556,7 @@ end end -- if type end return self end + ------------------------------------------------------------------- -- FSM functions ------------------------------------------------------------------- From 6cae3e62cf7e128976fd99712509dad0d8557921 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 12 Sep 2021 17:37:32 +0200 Subject: [PATCH 53/68] CTLD - small bug fix on stock removal --- Moose Development/Moose/Ops/CTLD.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 0309975df..779e21e7b 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -932,7 +932,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.8a1" +CTLD.version="0.1.8a2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1323,9 +1323,6 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) -- nothing left over self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group) return self - else - -- remove one - Cargotype:RemoveStock() end -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) @@ -1379,6 +1376,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) self:_SendMessage("Troops boarded!", 10, false, Group) self:__TroopsPickedUp(1,Group, Unit, Cargotype) self:_UpdateUnitCargoMass(Unit) + Cargotype:RemoveStock() end return self end @@ -1637,9 +1635,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) -- nothing left over self:_SendMessage(string.format("Sorry, we ran out of %s", cgoname), 10, false, Group) return self - else - -- remove one - Cargo:RemoveStock() end end -- check if we are in LOAD zone @@ -1755,6 +1750,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) table.insert(droppedcargo,realcargo) else realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],nil,cargotype.PerCrateMass) + Cargo:RemoveStock() end table.insert(self.Spawned_Cargo, realcargo) end From fd1b2ecb866702d6ce0980c0fa1f99ab1ff46a34 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 20 Sep 2021 14:26:42 +0200 Subject: [PATCH 54/68] ZONE - Docu bug fix --- Moose Development/Moose/Core/Zone.lua | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 65ac3cb9d..332a73dda 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -183,12 +183,12 @@ function ZONE_BASE:IsCoordinateInZone( Coordinate ) return InZone end ---- Returns if a PointVec2 is within the zone. +--- Returns if a PointVec2 is within the zone. (Name is misleading, actually takes a #COORDINATE) -- @param #ZONE_BASE self --- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 to test. +-- @param Core.Point#COORDINATE PointVec2 The coordinate to test. -- @return #boolean true if the PointVec2 is within the zone. -function ZONE_BASE:IsPointVec2InZone( PointVec2 ) - local InZone = self:IsVec2InZone( PointVec2:GetVec2() ) +function ZONE_BASE:IsPointVec2InZone( Coordinate ) + local InZone = self:IsVec2InZone( Coordinate:GetVec2() ) return InZone end @@ -498,8 +498,8 @@ end -- -- @field #ZONE_RADIUS ZONE_RADIUS = { - ClassName="ZONE_RADIUS", - } + ClassName="ZONE_RADIUS", + } --- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self @@ -510,15 +510,15 @@ ZONE_RADIUS = { function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) -- Inherit ZONE_BASE. - local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS - self:F( { ZoneName, Vec2, Radius } ) + local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS + self:F( { ZoneName, Vec2, Radius } ) - self.Radius = Radius - self.Vec2 = Vec2 + self.Radius = Radius + self.Vec2 = Vec2 - --self.Coordinate=COORDINATE:NewFromVec2(Vec2) + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) - return self + return self end --- Update zone from a 2D vector. @@ -746,11 +746,11 @@ end -- @param #ZONE_RADIUS self -- @return DCS#Vec2 The location of the zone. function ZONE_RADIUS:GetVec2() - self:F2( self.ZoneName ) + self:F2( self.ZoneName ) - self:T2( { self.Vec2 } ) + self:T2( { self.Vec2 } ) - return self.Vec2 + return self.Vec2 end --- Sets the @{DCS#Vec2} of the zone. @@ -1165,20 +1165,20 @@ end -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @return DCS#Vec2 The random location within the zone. function ZONE_RADIUS:GetRandomVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) + self:F( self.ZoneName, inner, outer ) - local Point = {} - local Vec2 = self:GetVec2() - local _inner = inner or 0 - local _outer = outer or self:GetRadius() + local Point = {} + local Vec2 = self:GetVec2() + local _inner = inner or 0 + local _outer = outer or self:GetRadius() - 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); + 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); - self:T( { Point } ) + self:T( { Point } ) - return Point + return Point end --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. From a64424ecc8e98f94c34fac6780ada2c12f3498e8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 20 Sep 2021 14:27:22 +0200 Subject: [PATCH 55/68] Positionable - Add IsSubmarine, Passenger seats for VAB Mephisto --- .../Moose/Wrapper/Positionable.lua | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 51a0718c2..e4a7b4e4c 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -684,6 +684,27 @@ function POSITIONABLE:IsShip() end +--- Returns if the unit is a submarine. +-- @param #POSITIONABLE self +-- @return #boolean Submarines attributes result. +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 + + return nil +end + + --- Returns true if the POSITIONABLE is in the air. -- Polymorphic, is overridden in GROUP and UNIT. -- @param Wrapper.Positionable#POSITIONABLE self @@ -1513,6 +1534,7 @@ do -- Cargo ["Ural-4320 APA-5D"] = 10, ["Ural-4320T"] = 14, ["ZBD04A"] = 7, -- new by kappa + ["VAB_Mephisto"] = 8, -- new by Apple } local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95 From 2b22d5288c94be25e25607b593c1e65937b8e432 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 20 Sep 2021 14:27:45 +0200 Subject: [PATCH 56/68] CTLD - added persistence --- Moose Development/Moose/Ops/CTLD.lua | 476 ++++++++++++++++++++++++++- 1 file changed, 460 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 779e21e7b..35a22e8cf 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -304,7 +304,7 @@ CTLD_ENGINEERING = { -- @field #number ID ID of this cargo. -- @field #string Name Name for menu. -- @field #table Templates Table of #POSITIONABLE objects. --- @field #CTLD_CARGO.Enum Type Enumerator of Type. +-- @field #CTLD_CARGO.Enum CargoType Enumerator of Type. -- @field #boolean HasBeenMoved Flag for moving. -- @field #boolean LoadDirectly Flag for direct loading. -- @field #number CratesNeeded Crates needed to build. @@ -763,16 +763,20 @@ do -- -- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) -- local points = 10 --- local PlayerName = Unit:GetPlayerName() --- my_scoring:_AddPlayerFromUnit( Unit ) --- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) +-- if Unit then +-- local PlayerName = Unit:GetPlayerName() +-- my_scoring:_AddPlayerFromUnit( Unit ) +-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) +-- end -- end -- -- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) -- local points = 5 --- local PlayerName = Unit:GetPlayerName() --- my_scoring:_AddPlayerFromUnit( Unit ) --- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) +-- if Unit then + -- local PlayerName = Unit:GetPlayerName() + -- my_scoring:_AddPlayerFromUnit( Unit ) + -- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) +-- end -- end -- -- ## 4. F10 Menu structure @@ -803,7 +807,11 @@ do -- ## 4.6 Show hover parameters -- -- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. --- +-- +-- ## 4.7 List Inventory +-- +-- Lists invetory of available units to drop or build. +-- -- ## 5. Support for Hercules mod by Anubis -- -- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can @@ -826,6 +834,27 @@ do -- -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers -- +-- ## 6. Save and load back units - persistance +-- +-- You can save and later load back units dropped or build to make your mission persistent. +-- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts. +-- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you. +-- +-- Use the following options to manage your saves: +-- +-- my_ctld.enableLoadSave = true -- allow auto-saving and loading of files +-- my_ctld.saveinterval = 600 -- save every 10 minutes +-- my_ctld.filename = "missionsave.csv" -- example filename +-- my_ctld.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path +-- my_ctld.eventoninject = true -- fire OnAfterCratesBuild and OnAfterTroopsDeployed events when loading (uses Inject functions) +-- +-- Then use an initial load at the beginning of your mission: +-- +-- my_ctld:__Load(10) +-- +-- **Caveat:** +-- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved. +-- -- @field #CTLD CTLD = { ClassName = "CTLD", @@ -869,6 +898,7 @@ CTLD = { -- TODO: Add statics as cargo -- DONE: List cargo in stock -- DONE: Limit of troops, crates buildable? +-- DONE: Allow saving of Troops & Vehicles ------------------------------ --- Radio Beacons @@ -932,7 +962,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.8a2" +CTLD.version="0.2.1a2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -996,7 +1026,9 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. - self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. + self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. + self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "Save", "*") -- CTLD save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables @@ -1077,6 +1109,15 @@ function CTLD:New(Coalition, Prefixes, Alias) self.cratecountry = country.id.RUSSIA end + -- load and save dropped TROOPS + self.enableLoadSave = false + self.filepath = nil + self.saveinterval = 600 + self.eventoninject = true + + local AliaS = string.gsub(self.alias," ","_") + self.filename = string.format("CTLD_%s_Persist.csv",AliaS) + for i=1,100 do math.random() end @@ -1115,6 +1156,24 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #CTLD self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Load". + -- @function [parent=#CTLD] Load + -- @param #CTLD self + + --- Triggers the FSM event "Load" after a delay. + -- @function [parent=#CTLD] __Load + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Save". + -- @function [parent=#CTLD] Load + -- @param #CTLD self + + --- Triggers the FSM event "Save" after a delay. + -- @function [parent=#CTLD] __Save + -- @param #CTLD self + -- @param #number delay Delay in seconds. + --- FSM Function OnAfterTroopsPickedUp. -- @function [parent=#CTLD] OnAfterTroopsPickedUp -- @param #CTLD self @@ -1201,6 +1260,24 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. + --- FSM Function OnAfterLoad. + -- @function [parent=#CTLD] OnAfterLoad + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". + + --- FSM Function OnAfterSave. + -- @function [parent=#CTLD] OnAfterSave + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for saving. Default is "CTLD__Persist.csv". + return self end @@ -3483,7 +3560,6 @@ end return self end - -- DONE: Make inject respect existing cargo types --- (User) Pre-populate troops in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. @@ -3491,7 +3567,7 @@ end -- @return #CTLD self -- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone: -- -- create a matching #CTLD_CARGO type - -- local InjectTroopsType = CTLD_CARGO:New(nil,"Injected Infantry",{"Inf12"},CTLD_CARGO.Enum.TROOPS,true,true,12,nil,false,80) + -- local InjectTroopsType = CTLD_CARGO:New(nil,"Infantry",{"Inf12"},CTLD_CARGO.Enum.TROOPS,true,true,12,nil,false,80) -- -- get a #ZONE object -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: @@ -3517,16 +3593,18 @@ end if not IsTroopsMatch(cargo) then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter + cargo.Stock = 1 table.insert(self.Cargo_Troops,cargo) end local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - -- unload troops + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then + -- unload local name = cargo:GetName() or "none" local temptable = cargo:GetTemplates() or {} local factor = 1.5 local zone = Zone + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 @@ -3545,10 +3623,85 @@ end self.Engineers = self.Engineers + 1 local grpname = self.DroppedTroops[self.TroopCounter]:GetName() self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) - self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) + --self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) else - self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) + --self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) end + if self.eventoninject then + self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter]) + end + end -- if type end + return self + end + + --- (User) Pre-populate vehicles in the field. + -- @param #CTLD self + -- @param Core.Zone#ZONE Zone The zone where to drop the troops. + -- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn. + -- @return #CTLD self + -- @usage Use this function to pre-populate the field with Vehicles or FOB at a random coordinate in a zone: + -- -- create a matching #CTLD_CARGO type + -- local InjectVehicleType = CTLD_CARGO:New(nil,"Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000) + -- -- get a #ZONE object + -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE + -- -- and go: + -- my_ctld:InjectVehicles(dropzone,InjectVehicleType) + function CTLD:InjectVehicles(Zone,Cargo) + self:T(self.lid.." InjectVehicles") + local cargo = Cargo -- #CTLD_CARGO + + local function IsVehicMatch(cargo) + local match = false + local cgotbl = self.Cargo_Crates + local name = cargo:GetName() + for _,_cgo in pairs (cgotbl) do + local cname = _cgo:GetName() + if name == cname then + match = true + break + end + end + return match + end + + if not IsVehicMatch(cargo) then + self.CargoCounter = self.CargoCounter + 1 + cargo.ID = self.CargoCounter + cargo.Stock = 1 + table.insert(self.Cargo_Crates,cargo) + end + + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then + -- unload + local name = cargo:GetName() or "none" + local temptable = cargo:GetTemplates() or {} + local factor = 1.5 + local zone = Zone + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() + cargo:SetWasDropped(true) + local canmove = false + if type == CTLD_CARGO.Enum.VEHICLE then canmove = true 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) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + else -- don't random position of e.g. SAM units build as FOB + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + end + if self.movetroopstowpzone and canmove then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + if self.eventoninject then + self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter]) + end + end -- end loop end -- if type end return self end @@ -3581,6 +3734,14 @@ end self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) self:__Status(-5) + + -- AutoSave + 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 @@ -3757,6 +3918,289 @@ end return self end + --- On before "Save" event. Checks if io and lfs are available. + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for saving. Default is "CTLD__Persist.csv". + function CTLD:onbeforeSave(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) + if not self.enableLoadSave then + return self + end + -- Thanks to @FunkyFranky + -- Check io module is available. + if not io then + self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") + return false + end + + -- Check default path. + 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 + + --- On after "Save" event. Player data is saved to file. + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized. + -- @param #string filename (Optional) File name for saving. Default is Default is "CTLD__Persist.csv". + function CTLD:onafterSave(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) + -- Thanks to @FunkyFranky + if not self.enableLoadSave then + return self + end + --- Function that saves data to file + local function _savefile(filename, data) + local f = assert(io.open(filename, "wb")) + f:write(data) + f:close() + end + + -- Set path or default. + if lfs then + path=self.filepath or lfs.writedir() + end + + -- Set file name. + filename=filename or self.filename + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + local grouptable = self.DroppedTroops -- #table + local cgovehic = self.Cargo_Crates + local cgotable = self.Cargo_Troops + + -- find matching cargo + local function FindCargoType(name,table) + -- name matching a template in the table + local match = false + local cargo = nil + for _ind,_cargo in pairs (table) do + local thiscargo = _cargo -- #CTLD_CARGO + local template = thiscargo:GetTemplates() + if type(template) == "string" then + template = { template } + end + for _,_name in pairs (template) do + --self:I(string.format("*** Saving CTLD: Matching %s with %s",name,_name)) + if string.find(name,_name) then + match = true + cargo = thiscargo + end + end + if match then break end + end + return match, cargo + end + + + --local data = "LoadedData = {\n" + local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass\n" + local n = 0 + for _,_grp in pairs(grouptable) do + local group = _grp -- Wrapper.Group#GROUP + if group and group:IsAlive() then + -- get template name + local name = group:GetName() + local template = string.gsub(name,"-(.+)$","") + if string.find(template,"#") then + template = string.gsub(name,"#(%d+)$","") + end + + 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 -- #CTLD_CARGO + local cgoname = cargo.Name + local cgotemp = cargo.Templates + local cgotype = cargo.CargoType + local cgoneed = cargo.CratesNeeded + local cgomass = cargo.PerCrateMass + + local templates = "{" + for _,_tmpl in pairs(cgotemp) do + templates = templates .. _tmpl .. ";" + end + templates = templates .. "}" + --self:I({cgoname,templates,cgotype,cgoneed,cgomass}) + --CTLD01238.function({[1]=Infantry Squad,[2]={'Inf12',},[4]=12,[5]=80,}) + local location = group:GetVec3() + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" + ,template,location.x,location.y,location.z,cgoname,templates,cgotype,cgoneed,cgomass) + --BASE:I(txt) + data = data .. txt + end + end + end + --data = data .. "\n" + + _savefile(filename, data) + + -- AutoSave + 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 + + --- On before "Load" event. Checks if io and lfs and the file are available. + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". + function CTLD:onbeforeLoad(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) + if not self.enableLoadSave then + return self + end + --- Function that check if a file exists. + local function _fileexists(name) + local f=io.open(name,"r") + if f~=nil then + io.close(f) + return true + else + return false + end + end + + -- Check io module is available. + if not io then + self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") + return false + end + + -- Check default path. + 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 + + -- Set path or default. + if lfs then + path=path or lfs.writedir() + end + + -- Set file name. + filename=filename or self.filename + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + -- Check if file exists. + local exists=_fileexists(filename) + + if exists then + return true + else + self:E(self.lid..string.format("WARNING: State file %s does not exist.", filename)) + return false + end + + end + + --- On after "Load" event. Loads dropped units from file. + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". + function CTLD:onafterLoad(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) + if not self.enableLoadSave then + return self + end + --- Function that loads data from a file. + local function _loadfile(filename) + local f=assert(io.open(filename, "rb")) + local data=f:read("*all") + f:close() + return data + end + + -- Set path or default. + if lfs then + path=path or lfs.writedir() + end + + -- Set file name. + filename=filename or self.filename + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + -- Info message. + 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 + --self:I({line=type(line)}) + loadeddata[#loadeddata+1] = line + end + file:close() + + -- remove header + table.remove(loadeddata, 1) + + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") + -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass + local groupname = dataset[1] + local vec2 = {} + vec2.x = dataset[2] + vec2.y = dataset[4] + local cargoname = dataset[5] + if type(cargoname) == "string" then + local cargotemplates = dataset[6] + cargotemplates = string.gsub(cargotemplates,"{","") + cargotemplates = string.gsub(cargotemplates,"}","") + cargotemplates = UTILS.Split(cargotemplates,";") + local cargotype = dataset[7] + local size = dataset[8] + local mass = dataset[9] + --self:I({groupname,vec3,cargoname,cargotemplates,cargotype,size,mass}) + -- inject at Vec2 + local dropzone = ZONE_RADIUS:New("DropZone",vec2,100) + 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,false,mass) + self:InjectVehicles(dropzone,injectvehicle) + 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,false,mass) + self:InjectTroops(dropzone,injecttroops) + end + end + end + + return self + end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua From cfed6f515371535c9b62174938f9c1b53d908dc1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 21 Sep 2021 07:47:43 +0200 Subject: [PATCH 57/68] SET - Added SET_CLIENT:CountAlive() --- Moose Development/Moose/Core/Set.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 4ddac7939..44194929f 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -3998,7 +3998,25 @@ do -- SET_CLIENT return self end + + --- Iterate the SET_CLIENT and count alive units. + -- @param #SET_CLIENT self + -- @return #number count + function SET_CLIENT:CountAlive() + local Set = self:GetSet() + + local CountU = 0 + for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP + if UnitData and UnitData:IsAlive() then + CountU = CountU + 1 + end + + end + + return CountU + end + --- -- @param #SET_CLIENT self -- @param Wrapper.Client#CLIENT MClient From 663cd34aa3a475cdc831879f97d8fbab170428d0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 21 Sep 2021 07:48:09 +0200 Subject: [PATCH 58/68] CTLD - fix when using SAVE or LOAD w/o filename and path --- Moose Development/Moose/Ops/CTLD.lua | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 35a22e8cf..143c56d77 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -962,7 +962,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.2.1a2" +CTLD.version="0.2.1a3" --- Instantiate a new CTLD. -- @param #CTLD self @@ -4083,7 +4083,11 @@ end return false end end - + + -- Set file name and path + filename=filename or self.filename + path = path or self.filepath + -- Check io module is available. if not io then self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") @@ -4100,9 +4104,6 @@ end path=path or lfs.writedir() end - -- Set file name. - filename=filename or self.filename - -- Set path. if path~=nil then filename=path.."\\"..filename @@ -4114,8 +4115,9 @@ end if exists then return true else - self:E(self.lid..string.format("WARNING: State file %s does not exist.", filename)) + self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename)) return false + --return self end end @@ -4139,15 +4141,16 @@ end f:close() return data end - + + -- Set file name and path + filename=filename or self.filename + path = path or self.filepath + -- Set path or default. if lfs then path=path or lfs.writedir() end - -- Set file name. - filename=filename or self.filename - -- Set path. if path~=nil then filename=path.."\\"..filename From 69449430d144d37503339443c6ec96c8c6d70ba6 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 22 Sep 2021 15:54:37 +0200 Subject: [PATCH 59/68] CTLD - Added Statics as cargo (#1600) CTLD - Added Statics as cargo, and the ability to load and save them (alongside your dropped buildable crates). --- Moose Development/Moose/Ops/CTLD.lua | 298 ++++++++++++++++++++++----- 1 file changed, 245 insertions(+), 53 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 143c56d77..faef79da1 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -186,7 +186,8 @@ CTLD_ENGINEERING = { if number > 0 then -- get set of dropped only for _,_cargo in pairs (crates) do - if _cargo:WasDropped() then + 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 @@ -339,6 +340,7 @@ CTLD_CARGO = { ["CRATE"] = "Crate", -- #string crate ["REPAIR"] = "Repair", -- #string repair ["ENGINEERS"] = "Engineers", -- #string engineers + ["STATIC"] = "Static", -- #string engineers } --- Function to create new CTLD_CARGO object. @@ -509,6 +511,17 @@ CTLD_CARGO = { end end + --- Query crate type for STATIC + -- @param #CTLD_CARGO self + -- @param #boolean + function CTLD_CARGO:IsStatic() + if self.CargoType == "Static" then + return true + else + return false + end + end + function CTLD_CARGO:AddMark(Mark) self.Mark = Mark return self @@ -547,7 +560,8 @@ do -- * Object oriented refactoring of Ciribob\'s fantastic CTLD script. -- * No need for extra MIST loading. -- * Additional events to tailor your mission. --- * ANY late activated group can serve as cargo, either as troops or crates, which have to be build on-location. +-- * ANY late activated group can serve as cargo, either as troops, crates, which have to be build on-location, or static like ammo chests. +-- * Option to persist (save&load) your dropped troops, crates and vehicles. -- -- ## 0. Prerequisites -- @@ -597,6 +611,10 @@ do -- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair -- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) -- my_ctld.repairtime = 300 -- takes 300 seconds to repair something +-- +-- -- add static cargo objects, e.g ammo chests - the name needs to refer to a STATIC object in the mission editor, +-- -- here: it\'s the UNIT name (not the GROUP name!), the second parameter is the weight in kg. +-- my_ctld:AddStaticsCargo("Ammunition",500) -- -- ## 1.3 Add logistics zones -- @@ -630,11 +648,11 @@ do -- The following options are available (with their defaults). Only set the ones you want changed: -- -- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. --- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. +-- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only. -- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. --- my_ctld.forcehoverload = true -- Crates (not: troops) can only be loaded while hovering. +-- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering. -- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. -- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). -- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... @@ -642,7 +660,8 @@ do -- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) -- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. -- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. --- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Don\'t forget to change this matching your coalition! +-- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups. +-- my_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. -- -- ## 2.1 User functions -- @@ -875,7 +894,7 @@ CTLD = { Loaded_Cargo = {}, -- cargo aboard units Spawned_Crates = {}, -- Holds objects for crates spawned generally Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects - CrateDistance = 30, -- list crates in this radius + CrateDistance = 35, -- list crates in this radius debug = false, wpZones = {}, pickupZones = {}, @@ -895,7 +914,7 @@ CTLD = { -- TODO: Possibly - either/or loading crates and troops -- DONE: Make inject respect existing cargo types -- TODO: Drop beacons or flares/smoke --- TODO: Add statics as cargo +-- DONE: Add statics as cargo -- DONE: List cargo in stock -- DONE: Limit of troops, crates buildable? -- DONE: Allow saving of Troops & Vehicles @@ -962,7 +981,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.2.1a3" +CTLD.version="0.2.2a1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1055,6 +1074,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- Cargo self.Cargo_Crates = {} self.Cargo_Troops = {} + self.Cargo_Statics = {} self.Loaded_Cargo = {} self.Spawned_Crates = {} self.Spawned_Cargo = {} @@ -1118,6 +1138,9 @@ function CTLD:New(Coalition, Prefixes, Alias) local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CTLD_%s_Persist.csv",AliaS) + -- allow re-pickup crates + self.allowcratepickupagain = true + for i=1,100 do math.random() end @@ -1740,7 +1763,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) -- avoid crate spam local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities local canloadcratesno = capabilities.cratelimit - local loaddist = self.CrateDistance or 30 + local loaddist = self.CrateDistance or 35 local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) @@ -1753,6 +1776,12 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local cratesneeded = cargotype:GetCratesNeeded() --#number local cratename = cargotype:GetName() local cratetemplate = "Container"-- #string + local cgotype = cargotype:GetType() + local isstatic = false + if cgotype == CTLD_CARGO.Enum.STATIC then + cratetemplate = cargotype:GetTemplates() + isstatic = true + end -- get position and heading of heli local position = Unit:GetCoordinate() local heading = Unit:GetHeading() + 1 @@ -1773,8 +1802,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) cratedistance = (i-1)*2.5 + capabilities.length if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end -- altered heading logic - -- TODO: right standard deviation? - rheading = UTILS.RandomGaussian(0,25,-90,90,100) + -- DONE: right standard deviation? + rheading = UTILS.RandomGaussian(0,30,-90,90,100) rheading = math.fmod((heading + rheading + addon), 360) else local initialSpacing = IsHerc and 16 or 12 -- initial spacing of the first crates @@ -1799,6 +1828,10 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local cratecoord = position:Translate(cratedistance,rheading) local cratevec2 = cratecoord:GetVec2() self.CrateCounter = self.CrateCounter + 1 + local basetype = "container_cargo" + if isstatic then + basetype = cratetemplate + end if type(ship) == "string" then self:T("Spawning on ship "..ship) local Ship = UNIT:FindByName(ship) @@ -1808,12 +1841,12 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) dist = dist - (20 + math.random(1,10)) local width = width / 2 local Offy = math.random(-width,width) - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",self.cratecountry) + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) --:InitCoordinate(cratecoord) :InitLinkToUnit(Ship,dist,Offy,0) :Spawn(270,cratealias) else - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",self.cratecountry) + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) :InitCoordinate(cratecoord) --:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) :Spawn(270,cratealias) @@ -1826,7 +1859,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) table.insert(droppedcargo,realcargo) else - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],nil,cargotype.PerCrateMass) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) Cargo:RemoveStock() end table.insert(self.Spawned_Cargo, realcargo) @@ -1840,6 +1873,46 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) return self end +--- Inject crates and static cargo objects. +-- @param #CTLD self +-- @param Core.Zone#ZONE Zone Zone to spawn in. +-- @param #CTLD_CARGO Cargo The cargo type to spawn. +-- @return #CTLD self +function CTLD:InjectStatics(Zone, Cargo) + self:T(self.lid .. " InjectStatics") + local cratecoord = Zone:GetCoordinate() + local surface = cratecoord:GetSurfaceType() + if surface == land.SurfaceType.WATER then + return self + end + local cargotype = Cargo -- #CTLD_CARGO + --local number = 1 + local cratesneeded = cargotype:GetCratesNeeded() --#number + local cratetemplate = "Container"-- #string + local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) + local cratename = cargotype:GetName() + local cgotype = cargotype:GetType() + local isstatic = false + if cgotype == CTLD_CARGO.Enum.STATIC then + cratetemplate = cargotype:GetTemplates() + isstatic = true + end + local basetype = "container_cargo" + if isstatic then + basetype = cratetemplate + end + self.CrateCounter = self.CrateCounter + 1 + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + :InitCoordinate(cratecoord) + :Spawn(270,cratealias) + local templ = cargotype:GetTemplates() + local sorte = cargotype:GetType() + self.CargoCounter = self.CargoCounter + 1 + cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] + table.insert(self.Spawned_Cargo, cargotype) + return self +end + --- (Internal) Function to find and list nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -1847,7 +1920,7 @@ end -- @return #CTLD self function CTLD:_ListCratesNearby( _group, _unit) self:T(self.lid .. " _ListCratesNearby") - local finddist = self.CrateDistance or 30 + local finddist = self.CrateDistance or 35 local crates,number = self:_FindCratesNearby(_group,_unit, finddist) -- #table if number > 0 then local text = REPORT:New("Crates Found Nearby:") @@ -1977,16 +2050,16 @@ function CTLD:_LoadCratesNearby(Group, Unit) loaded.Cargo = {} end -- get nearby crates - local finddist = self.CrateDistance or 30 + local finddist = self.CrateDistance or 35 local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table if number == 0 and self.hoverautoloading then - return -- exit + return self -- exit elseif number == 0 then self:_SendMessage("Sorry no loadable crates nearby!", 10, false, Group) - return -- exit + return self -- exit elseif numberonboard == cratelimit then self:_SendMessage("Sorry no fully loaded!", 10, false, Group) - return -- exit + return self -- exit else -- go through crates and load local capacity = cratelimit - numberonboard @@ -1997,8 +2070,14 @@ function CTLD:_LoadCratesNearby(Group, Unit) local crateind = 0 -- get crate with largest index for _ind,_crate in pairs (nearcrates) do - if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then - crateind = _crate:GetID() + if self.allowcratepickupagain then + if _crate:GetID() > crateind and _crate.Positionable ~= nil then + crateind = _crate:GetID() + end + else + if not _crate:HasMoved() and _crate:WasDropped() and _crate:GetID() > crateind then + crateind = _crate:GetID() + end end end -- load one if we found one @@ -2006,12 +2085,14 @@ function CTLD:_LoadCratesNearby(Group, Unit) local crate = nearcrates[crateind] -- #CTLD_CARGO loaded.Cratesloaded = loaded.Cratesloaded + 1 crate:SetHasMoved(true) + crate:SetWasDropped(false) table.insert(loaded.Cargo, crate) table.insert(crateidsloaded,crate:GetID()) -- destroy crate crate:GetPositionable():Destroy(false) crate.Positionable = nil self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + table.remove(nearcrates,crate:GetID()) self:__CratesPickedUp(1, Group, Unit, crate) end end @@ -2100,7 +2181,7 @@ function CTLD:_ListCargo(Group, Unit) for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + 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 @@ -2113,7 +2194,7 @@ function CTLD:_ListCargo(Group, Unit) for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then report:Add(string.format("Crate: %s size 1",cargo:GetName())) cratecount = cratecount + 1 end @@ -2142,6 +2223,7 @@ function CTLD:_ListInventory(Group, Unit) 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 @@ -2153,12 +2235,13 @@ function CTLD:_ListInventory(Group, Unit) local crateno = countcargo(cgotypes) local troopno = countcargo(trptypes) + local staticno = countcargo(stctypes) if (crateno > 0 or troopno > 0) then local report = REPORT:New("Inventory Sheet") report:Add("------------------------------------------------------------") - report:Add(string.format("Troops: %d, Cratetypes: %d",troopno,crateno)) + report:Add(string.format("Troops: %d, Cratetypes: %d",troopno,crateno+staticno)) report:Add("------------------------------------------------------------") report:Add(" -- TROOPS --") for _,_cargo in pairs(trptypes) do @@ -2172,7 +2255,7 @@ function CTLD:_ListInventory(Group, Unit) elseif stockn > 0 then stock = tostring(stockn) end - report:Add(string.format("Unit: %s size: %d stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) + report:Add(string.format("Unit: %s | Soldiers: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) end end if report:GetCount() == 4 then @@ -2192,7 +2275,23 @@ function CTLD:_ListInventory(Group, Unit) elseif stockn > 0 then stock = tostring(stockn) end - report:Add(string.format("Type: %s crates: %d stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) + report:Add(string.format("Type: %s | Crates per Set: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock)) + cratecount = cratecount + 1 + end + end + -- Statics + for _,_cargo in pairs(stctypes) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + 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 @@ -2373,7 +2472,7 @@ function CTLD:_UnloadCrates(Group, Unit) for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and (not cargo:WasDropped() or self.allowcratepickupagain) then -- unload crates self:_GetCrates(Group, Unit, cargo, 1, true) cargo:SetWasDropped(true) @@ -2426,7 +2525,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) end end -- get nearby crates - local finddist = self.CrateDistance or 30 + local finddist = self.CrateDistance or 35 local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table local buildables = {} local foundbuilds = false @@ -2435,7 +2534,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) -- get dropped crates for _,_crate in pairs(crates) do local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() and not Crate:IsRepair() then + if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then -- we can build these - maybe local name = Crate:GetName() local required = Crate:GetCratesNeeded() @@ -2510,7 +2609,7 @@ end function CTLD:_RepairCrates(Group, Unit, Engineering) self:T(self.lid .. " _RepairCrates") -- get nearby crates - local finddist = self.CrateDistance or 30 + local finddist = self.CrateDistance or 35 local crates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table local buildables = {} local foundbuilds = false @@ -2519,7 +2618,7 @@ function CTLD:_RepairCrates(Group, Unit, Engineering) -- get dropped crates for _,_crate in pairs(crates) do local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() and Crate:IsRepair() then + if Crate:WasDropped() and Crate:IsRepair() and not Crate:IsStatic() then -- we can build these - maybe local name = Crate:GetName() local required = Crate:GetCratesNeeded() @@ -2599,10 +2698,16 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) local position = Unit:GetCoordinate() or Group:GetCoordinate() local unitname = Unit:GetName() or Group:GetName() local name = Build.Name - local type = Build.Type -- #CTLD_CARGO.Enum + local ctype = Build.Type -- #CTLD_CARGO.Enum local canmove = false - if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end + 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 = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) local randomcoord = zone:GetRandomCoordinate(35):GetVec2() if Repair then @@ -2688,6 +2793,7 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) found = found + 1 nowcrate:GetPositionable():Destroy(false) nowcrate.Positionable = nil + nowcrate.HasBeenDropped = false end if found == numberdest then break end -- got enough end @@ -2764,6 +2870,12 @@ function CTLD:_RefreshF10Menus() local menutext = string.format("Get crate for %s",entry.Name) menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) end + for _,_entry in pairs(self.Cargo_Statics) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Get crate for %s",entry.Name) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) @@ -2830,6 +2942,25 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock) return self end +--- User function - Add *generic* static-type loadable as cargo. This type will create cargo that needs to be loaded, moved and dropped. +-- @param #CTLD self +-- @param #string Name Unique name of this type of cargo as set in the mission editor (not: UNIT name!), e.g. "Ammunition-1". +-- @param #string Template Name of Wrapper.Static#STATIC to us as template. +-- @param #CTLD_CARGO.Enum Type Type of cargo, here STATIC. +-- @param #number NoCrates Number of crates needed to build this cargo. +-- @param #number Mass Mass in kg of each static in kg, e.g. 100. +-- @param #number Stock Number of groups in stock. Nil for unlimited. +function CTLD:AddStaticsCargo(Name,Mass,Stock) + self:T(self.lid .. " AddStaticsCargo") + self.CargoCounter = self.CargoCounter + 1 + local type = CTLD_CARGO.Enum.STATIC + local template = STATIC:FindByName(Name,true):GetTypeName() + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock) + table.insert(self.Cargo_Statics,cargo) + return self +end + --- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. -- @param #CTLD self -- @param #string Name Unique name of this type of cargo. E.g. "Humvee". @@ -3795,6 +3926,11 @@ end 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() @@ -3981,6 +4117,19 @@ end local grouptable = self.DroppedTroops -- #table local cgovehic = self.Cargo_Crates local cgotable = self.Cargo_Troops + local stcstable = self.Spawned_Cargo + + local statics = nil + local statics = {} + self:I(self.lid.."Bulding Statics Table for Saving") + for _,_cargo in pairs (stcstable) do + local cargo = _cargo -- #CTLD_CARGO + local object = cargo:GetPositionable() -- Wrapper.Static#STATIC + if object and object:IsAlive() and cargo:WasDropped() then + self:I({_cargo}) + statics[#statics+1] = cargo + end + end -- find matching cargo local function FindCargoType(name,table) @@ -3995,7 +4144,7 @@ end end for _,_name in pairs (template) do --self:I(string.format("*** Saving CTLD: Matching %s with %s",name,_name)) - if string.find(name,_name) then + if string.find(name,_name) and _cargo:GetType() ~= CTLD_CARGO.Enum.REPAIR then match = true cargo = thiscargo end @@ -4032,22 +4181,46 @@ end local cgoneed = cargo.CratesNeeded local cgomass = cargo.PerCrateMass - local templates = "{" - for _,_tmpl in pairs(cgotemp) do - templates = templates .. _tmpl .. ";" + if type(cgotemp) == "table" then + local templates = "{" + for _,_tmpl in pairs(cgotemp) do + templates = templates .. _tmpl .. ";" + end + templates = templates .. "}" + cgotemp = templates end - templates = templates .. "}" - --self:I({cgoname,templates,cgotype,cgoneed,cgomass}) - --CTLD01238.function({[1]=Infantry Squad,[2]={'Inf12',},[4]=12,[5]=80,}) + local location = group:GetVec3() local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" - ,template,location.x,location.y,location.z,cgoname,templates,cgotype,cgoneed,cgomass) - --BASE:I(txt) + ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass) data = data .. txt end end end - --data = data .. "\n" + + for _,_cgo in pairs(statics) do + local object = _cgo -- #CTLD_CARGO + 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 txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" + ,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass) + data = data .. txt + end _savefile(filename, data) @@ -4178,28 +4351,47 @@ end -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass local groupname = dataset[1] local vec2 = {} - vec2.x = dataset[2] - vec2.y = dataset[4] + vec2.x = tonumber(dataset[2]) + vec2.y = tonumber(dataset[4]) local cargoname = dataset[5] - if type(cargoname) == "string" then + local cargotype = dataset[7] + if type(groupname) == "string" and groupname ~= "STATIC" then local cargotemplates = dataset[6] cargotemplates = string.gsub(cargotemplates,"{","") cargotemplates = string.gsub(cargotemplates,"}","") cargotemplates = UTILS.Split(cargotemplates,";") - local cargotype = dataset[7] - local size = dataset[8] - local mass = dataset[9] + local size = tonumber(dataset[8]) + local mass = tonumber(dataset[9]) --self:I({groupname,vec3,cargoname,cargotemplates,cargotype,size,mass}) -- inject at Vec2 - local dropzone = ZONE_RADIUS:New("DropZone",vec2,100) + 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,false,mass) + local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) self:InjectVehicles(dropzone,injectvehicle) 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,false,mass) + local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) self:InjectTroops(dropzone,injecttroops) end - end + elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then + local cargotemplates = dataset[6] + local size = tonumber(dataset[8]) + local mass = tonumber(dataset[9]) + local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) + -- STATIC,-84037,154,834021,Humvee,{Humvee;},Vehicle,1,100 + -- STATIC,-84036,154,834018,Ammunition-1,ammo_cargo,Static,1,500 + 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) + 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) + end + if injectstatic then + self:InjectStatics(dropzone,injectstatic) + end + end end return self From 1c97eb6f3c75d7e380d279c654bcd1b2d7cce3c3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 24 Sep 2021 11:04:54 +0200 Subject: [PATCH 60/68] SPAWNSTATIC - bugfix on canCargo, mass could be set but not transported into the template spawn --- Moose Development/Moose/Core/SpawnStatic.lua | 34 +++++++++++--------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 95b41655d..5fde1c12c 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -138,24 +138,24 @@ SPAWNSTATIC = { -- @return #SPAWNSTATIC self function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC + local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) - - if TemplateStatic then - self.SpawnTemplatePrefix = SpawnTemplateName - self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) - self.CountryID = SpawnCountryID or CountryID - self.CategoryID = CategoryID - self.CoalitionID = CoalitionID - self.SpawnIndex = 0 - else - error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) - end + local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) + + if TemplateStatic then + self.SpawnTemplatePrefix = SpawnTemplateName + self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) + self.CountryID = SpawnCountryID or CountryID + self.CategoryID = CategoryID + self.CoalitionID = CoalitionID + self.SpawnIndex = 0 + else + error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) + end self:SetEventPriority( 5 ) - return self + return self end --- Creates the main object to spawn a @{Static} given a template table. @@ -422,7 +422,11 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) end if self.InitCargo~=nil then - Template.isCargo=self.InitCargo + Template.canCargo=self.InitCargo + end + + if self.InitCargoMass~=nil then + Template.mass=self.InitCargoMass end if self.InitLinkUnit then From 50c74d0852cda18af9ed65599f54fff7fef7f8f3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 24 Sep 2021 11:08:23 +0200 Subject: [PATCH 61/68] Added option for slingload: enableslingload --- Moose Development/Moose/Ops/CTLD.lua | 80 ++++++++++++++++------------ 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index faef79da1..97b33e734 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -383,6 +383,12 @@ CTLD_CARGO = { return self.ID end + --- Query Mass. + -- @param #CTLD_CARGO self + -- @return #number Mass in kg + function CTLD_CARGO:GetMass() + return self.PerCrateMass + end --- Query Name. -- @param #CTLD_CARGO self -- @return #string Name @@ -531,7 +537,7 @@ CTLD_CARGO = { return self.Mark end - function CTLD_CARGO:WipeMark() + function CTLD_CARGO:WipeMark() self.Mark = nil return self end @@ -662,6 +668,7 @@ do -- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. -- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups. -- my_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. +-- my_ctld.enableslingload = false -- allow cargos to be slingloaded - might not work for all cargo types -- -- ## 2.1 User functions -- @@ -887,7 +894,6 @@ CTLD = { FreeUHFFrequencies = {}, -- Table of UHF FreeFMFrequencies = {}, -- Table of FM CargoCounter = 0, - dropOffZones = {}, wpZones = {}, Cargo_Troops = {}, -- generic troops objects Cargo_Crates = {}, -- generic crate objects @@ -897,8 +903,8 @@ CTLD = { CrateDistance = 35, -- list crates in this radius debug = false, wpZones = {}, - pickupZones = {}, dropOffZones = {}, + pickupZones = {}, } ------------------------------ @@ -981,7 +987,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.2.2a1" +CTLD.version="0.2.2a4" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1141,6 +1147,9 @@ function CTLD:New(Coalition, Prefixes, Alias) -- allow re-pickup crates self.allowcratepickupagain = true + -- slingload + self.enableslingload = false + for i=1,100 do math.random() end @@ -1771,12 +1780,13 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) end -- spawn crates in front of helicopter local IsHerc = self:IsHercules(Unit) -- Herc - local cargotype = Cargo -- #CTLD_CARGO + local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number local cratename = cargotype:GetName() local cratetemplate = "Container"-- #string local cgotype = cargotype:GetType() + local cgomass = cargotype:GetMass() local isstatic = false if cgotype == CTLD_CARGO.Enum.STATIC then cratetemplate = cargotype:GetTemplates() @@ -1843,11 +1853,15 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local Offy = math.random(-width,width) self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) --:InitCoordinate(cratecoord) + :InitCargoMass(cgomass) + :InitCargo(self.enableslingload) :InitLinkToUnit(Ship,dist,Offy,0) :Spawn(270,cratealias) else self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) :InitCoordinate(cratecoord) + :InitCargoMass(cgomass) + :InitCargo(self.enableslingload) --:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) :Spawn(270,cratealias) end @@ -1892,6 +1906,7 @@ function CTLD:InjectStatics(Zone, Cargo) local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) local cratename = cargotype:GetName() local cgotype = cargotype:GetType() + local cgomass = cargotype:GetMass() local isstatic = false if cgotype == CTLD_CARGO.Enum.STATIC then cratetemplate = cargotype:GetTemplates() @@ -1903,6 +1918,8 @@ function CTLD:InjectStatics(Zone, Cargo) end self.CrateCounter = self.CrateCounter + 1 self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + :InitCargoMass(cgomass) + :InitCargo(self.enableslingload) :InitCoordinate(cratecoord) :Spawn(270,cratealias) local templ = cargotype:GetTemplates() @@ -1930,9 +1947,9 @@ function CTLD:_ListCratesNearby( _group, _unit) local name = entry:GetName() --#string local dropped = entry:WasDropped() if dropped then - text:Add(string.format("Dropped crate for %s",name)) + text:Add(string.format("Dropped crate for %s, %dkg",name, entry.PerCrateMass)) else - text:Add(string.format("Crate for %s, %d Kg",name, entry.PerCrateMass)) + text:Add(string.format("Crate for %s, %dkg",name, entry.PerCrateMass)) end end if text:GetCount() == 1 then @@ -2237,7 +2254,7 @@ function CTLD:_ListInventory(Group, Unit) local troopno = countcargo(trptypes) local staticno = countcargo(stctypes) - if (crateno > 0 or troopno > 0) then + if (crateno > 0 or troopno > 0 or staticno > 0) then local report = REPORT:New("Inventory Sheet") report:Add("------------------------------------------------------------") @@ -2852,35 +2869,14 @@ function CTLD:_RefreshF10Menus() local cancrates = capabilities.crates -- top menu local topmenu = MENU_GROUP:New(_group,"CTLD",nil) - local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) + local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) local invtry = MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu, self._ListInventory, self, _group, _unit) local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, false) local smokemenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, true):Refresh() -- sub menus - -- sub menu crates management - if cancrates then - local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) - local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) - for _,_entry in pairs(self.Cargo_Crates) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format("Get crate for %s",entry.Name) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) - end - for _,_entry in pairs(self.Cargo_Statics) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format("Get crate for %s",entry.Name) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) - end - listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) - local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) - local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) - local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() - end -- sub menu troops management if cantroops then local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) @@ -2892,6 +2888,27 @@ function CTLD:_RefreshF10Menus() local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() end + -- sub menu crates management + if cancrates then + local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) + local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) + for _,_entry in pairs(self.Cargo_Crates) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end + for _,_entry in pairs(self.Cargo_Statics) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end + listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) + local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) + local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() + end if unittype == "Hercules" then local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() else @@ -2945,9 +2962,6 @@ end --- User function - Add *generic* static-type loadable as cargo. This type will create cargo that needs to be loaded, moved and dropped. -- @param #CTLD self -- @param #string Name Unique name of this type of cargo as set in the mission editor (not: UNIT name!), e.g. "Ammunition-1". --- @param #string Template Name of Wrapper.Static#STATIC to us as template. --- @param #CTLD_CARGO.Enum Type Type of cargo, here STATIC. --- @param #number NoCrates Number of crates needed to build this cargo. -- @param #number Mass Mass in kg of each static in kg, e.g. 100. -- @param #number Stock Number of groups in stock. Nil for unlimited. function CTLD:AddStaticsCargo(Name,Mass,Stock) From 06dc9a732ecb0bf54a7711b452afa9d52e8e5fa4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 24 Sep 2021 18:37:13 +0200 Subject: [PATCH 62/68] bugfix --- Moose Development/Moose/Wrapper/Airbase.lua | 118 ++++++++++++-------- 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 223d459b6..8c6b7bfeb 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -17,6 +17,7 @@ -- @field #table CategoryName Names of airbase categories. -- @field #string AirbaseName Name of the airbase. -- @field #number AirbaseID Airbase ID. +-- @field Core.Zone#ZONE AirbaseZone Circular zone around the airbase with a radius of 2500 meters. For ships this is a ZONE_UNIT object. -- @field #number category Airbase category. -- @field #table descriptors DCS descriptors. -- @field #boolean isAirdrome Airbase is an airdrome. @@ -526,19 +527,19 @@ function AIRBASE:Register(AirbaseName) -- Inherit everything from positionable. local self=BASE:Inherit(self, POSITIONABLE:New(AirbaseName)) --#AIRBASE - + -- Set airbase name. self.AirbaseName=AirbaseName - + -- Set airbase ID. self.AirbaseID=self:GetID(true) - + -- Get descriptors. self.descriptors=self:GetDesc() - + -- Category. self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME - + -- Set category. if self.category==Airbase.Category.AIRDROME then self.isAirdrome=true @@ -549,17 +550,23 @@ function AIRBASE:Register(AirbaseName) else self:E("ERROR: Unknown airbase category!") end - + self:_InitParkingSpots() - + local vec2=self:GetVec2() - + -- Init coordinate. self:GetCoordinate() - + if vec2 then - -- TODO: For ships we need a moving zone. - self.AirbaseZone=ZONE_RADIUS:New( AirbaseName, vec2, 2500 ) + 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 @@ -693,7 +700,7 @@ function AIRBASE:GetID(unique) 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 -- Ships get a negative sign as their unit number might be the same as the ID of another airbase. @@ -932,16 +939,16 @@ function AIRBASE:_InitParkingSpots() -- Init table. self.parking={} self.parkingByID={} - + self.NparkingTotal=0 self.NparkingTerminal={} for _,terminalType in pairs(AIRBASE.TerminalType) do self.NparkingTerminal[terminalType]=0 - end + end -- Put coordinates of parking spots into table. for _,spot in pairs(parkingdata) do - + -- New parking spot. local park={} --#AIRBASE.ParkingSpot park.Vec3=spot.vTerminalPos @@ -952,15 +959,15 @@ function AIRBASE:_InitParkingSpots() park.TerminalID0=spot.Term_Index_0 park.TerminalType=spot.Term_Type park.TOAC=spot.TO_AC - + self.NparkingTotal=self.NparkingTotal+1 - + for _,terminalType in pairs(AIRBASE.TerminalType) do if self._CheckTerminalType(terminalType, park.TerminalType) then self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 end - end - + end + self.parkingByID[park.TerminalID]=park table.insert(self.parking, park) end @@ -984,7 +991,7 @@ function AIRBASE:GetParkingSpotsTable(termtype) -- Get parking data of all spots (free or occupied) local parkingdata=self:GetParkingData(false) - + -- Get parking data of all free spots. local parkingfree=self:GetParkingData(true) @@ -1001,17 +1008,26 @@ function AIRBASE:GetParkingSpotsTable(termtype) -- Put coordinates of parking spots into table. local spots={} for _,_spot in pairs(parkingdata) do - + if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then - + local spot=self:_GetParkingSpotByID(_spot.Term_Index) - - spot.Free=_isfree(_spot) -- updated - spot.TOAC=_spot.TO_AC -- updated - - table.insert(spots, spot) + + if spot then + + spot.Free=_isfree(_spot) -- updated + spot.TOAC=_spot.TO_AC -- updated + + table.insert(spots, spot) + + else + + self:E(string.format("ERROR: Parking spot %s is nil!", tostring(_spot.Term_Index))) + + end + end - + end return spots @@ -1032,14 +1048,14 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC) for _,_spot in pairs(parkingfree) do if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) and _spot.Term_Index>0 then if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then - + local spot=self:_GetParkingSpotByID(_spot.Term_Index) spot.Free=true -- updated spot.TOAC=_spot.TO_AC -- updated - + table.insert(freespots, spot) - + end end end @@ -1118,7 +1134,9 @@ end -- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function. -- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID. function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata) - + + if group and group:IsAlive() then + -- Init default scanradius=scanradius or 50 if scanunits==nil then @@ -1291,6 +1309,10 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, -- Retrun spots we found, even if there were not enough. return validspots + + else + return {} + end end --- Check black and white lists. @@ -1392,7 +1414,7 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Get spawn points on runway. These can be used to determine the runway heading. local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) - + -- Debug: For finding the numbers of the spawn points belonging to each runway. if false then for i,_coord in pairs(runwaycoords) do @@ -1411,7 +1433,7 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Airbase name. local name=self:GetName() - + -- Exceptions if name==AIRBASE.Nevada.Jean_Airport or @@ -1424,36 +1446,36 @@ function AIRBASE:GetRunwayData(magvar, mark) -- 1-->4, 2-->3, 3-->2, 4-->1 exception=1 - - elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and + + 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 - + -- 1-->3, 2-->4, 3-->1, 4-->2 exception=2 - + end - + --- Function returning the index of the runway coordinate belonding to the given index i. local function f(i) local j - + if exception==1 then - + j=N-(i-1) -- 1-->4, 2-->3 - + elseif exception==2 then - + if i<=N2 then j=i+N2 -- 1-->3, 2-->4 else j=i-N2 -- 3-->1, 4-->3 end - + else if i%2==0 then @@ -1461,9 +1483,9 @@ function AIRBASE:GetRunwayData(magvar, mark) else j=i+1 -- odd 1-->2, 3-->4 end - + end - + -- Special case where there is no obvious order. if name==AIRBASE.Syria.Beirut_Rafic_Hariri then if i==1 then @@ -1496,7 +1518,7 @@ function AIRBASE:GetRunwayData(magvar, mark) j=2 end end - + return j end @@ -1505,7 +1527,7 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Get the other spawn point coordinate. local j=f(i) - + -- Debug info. --env.info(string.format("Runway i=%s j=%s (N=%d #runwaycoord=%d)", tostring(i), tostring(j), N, #runwaycoords)) From ff8766669c9de5001fa2e985de60b70d05556656 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Sep 2021 09:51:14 +0200 Subject: [PATCH 63/68] Small fix for Airbase Parking Spot Finder --- Moose Development/Moose/Wrapper/Airbase.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 8c6b7bfeb..af6451db1 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1100,7 +1100,7 @@ function AIRBASE:MarkParkingSpots(termtype, mark) -- Get airbase name. local airbasename=self:GetName() - self:E(string.format("Parking spots at %s for termial type %s:", airbasename, tostring(termtype))) + self:E(string.format("Parking spots at %s for terminal type %s:", airbasename, tostring(termtype))) for _,_spot in pairs(parkingdata) do @@ -1135,8 +1135,6 @@ end -- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID. function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata) - if group and group:IsAlive() then - -- Init default scanradius=scanradius or 50 if scanunits==nil then @@ -1179,14 +1177,24 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype) -- Get the aircraft size, i.e. it's longest side of x,z. + local _aircraftsize, ax,ay,az + if group and group.ClassName == "GROUP" then local aircraft=group:GetUnit(1) - local _aircraftsize, ax,ay,az=aircraft:GetObjectSize() + _aircraftsize, ax,ay,az=aircraft:GetObjectSize() + else + -- SU27 dimensions + _aircraftsize = 23 + ax = 23 -- length + ay = 7 -- height + az = 17 -- width + end + -- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size! local _nspots=nspots or group:GetSize() -- Debug info. - self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at termial type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype))) + self:E(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))) -- Table of valid spots. local validspots={} @@ -1310,9 +1318,6 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, -- Retrun spots we found, even if there were not enough. return validspots - else - return {} - end end --- Check black and white lists. From db516a20776ffff4992ea97dfb715663b29a1a42 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 27 Sep 2021 15:48:45 +0200 Subject: [PATCH 64/68] Fix "local" error --- Moose Development/Moose/Wrapper/Airbase.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index af6451db1..0f4eda499 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1177,9 +1177,10 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype) -- Get the aircraft size, i.e. it's longest side of x,z. + local aircraft = nil local _aircraftsize, ax,ay,az if group and group.ClassName == "GROUP" then - local aircraft=group:GetUnit(1) + aircraft=group:GetUnit(1) _aircraftsize, ax,ay,az=aircraft:GetObjectSize() else -- SU27 dimensions From c311c40b7224156a37f6546308024b9bedd15eae Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 28 Sep 2021 16:53:53 +0200 Subject: [PATCH 65/68] GROUP:GetAmmunition() - fix to also return bomb count (#1606) GROUP:GetAmmunition() - fix to also return bomb count --- Moose Development/Moose/Wrapper/Group.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index a1ccdfa85..fd1a3f600 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1137,7 +1137,7 @@ end -- @return #number Number of shells left. -- @return #number Number of rockets left. -- @return #number Number of bombs left. --- @return #number Number of missiles left. +-- @return #number Number of missiles left. function GROUP:GetAmmunition() self:F( self.ControllableName ) @@ -1147,6 +1147,7 @@ function GROUP:GetAmmunition() local Nshells=0 local Nrockets=0 local Nmissiles=0 + local Nbombs=0 if DCSControllable then @@ -1155,18 +1156,19 @@ function GROUP:GetAmmunition() local Unit = UnitData -- Wrapper.Unit#UNIT -- Get ammo of the unit - local ntot, nshells, nrockets, nmissiles = Unit:GetAmmunition() + local ntot, nshells, nrockets, nbombs, nmissiles = Unit:GetAmmunition() Ntot=Ntot+ntot Nshells=Nshells+nshells Nrockets=Nrockets+nrockets - Nmissiles=Nmissiles+nmissiles + Nmissiles=Nmissiles+nmissiles + Nbombs=Nbombs+nbombs end end - return Ntot, Nshells, Nrockets, Nmissiles + return Ntot, Nshells, Nrockets, Nbombs, Nmissiles end From 18321250222b27956a8db427b0014076c96cf3d5 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 29 Sep 2021 08:58:46 +0200 Subject: [PATCH 66/68] Globals - Moved _DATABASE:_RegisterAirbases() to Globals.lua --- Moose Development/Moose/Core/Database.lua | 2 +- Moose Development/Moose/Globals.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index b30329ecf..50031e81b 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -136,7 +136,7 @@ function DATABASE:New() self:_RegisterGroupsAndUnits() self:_RegisterClients() self:_RegisterStatics() - self:_RegisterAirbases() + --self:_RegisterAirbases() --self:_RegisterPlayers() self.UNITS_Position = 0 diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index df7fafe36..f63f57e84 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -18,6 +18,7 @@ _DATABASE:_RegisterCargos() --- Register zones. _DATABASE:_RegisterZones() +_DATABASE:_RegisterAirbases() --- Check if os etc is available. BASE:I("Checking de-sanitization of os, io and lfs:") From 0daac876ea3794efe83e825b774dd4eb7dcea9a4 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 29 Sep 2021 09:00:53 +0200 Subject: [PATCH 67/68] Update Airbase.lua - Register oil rigs and gas platforms as helipads. DCS bug registers them as ship (Airbase.Category.SHIP instead of Airbase.Category.HELIPAD). --- Moose Development/Moose/Wrapper/Airbase.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 0f4eda499..bcf99074c 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -547,6 +547,12 @@ function AIRBASE:Register(AirbaseName) self.isHelipad=true elseif self.category==Airbase.Category.SHIP then self.isShip=true + -- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects() + 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) else self:E("ERROR: Unknown airbase category!") end From d8cb15a577cb82fd17d0f6e910a957637d9c7ecd Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 29 Sep 2021 09:01:47 +0200 Subject: [PATCH 68/68] Update Airbase.lua --- Moose Development/Moose/Wrapper/Airbase.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index bcf99074c..0e3a157bd 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -552,7 +552,8 @@ function AIRBASE:Register(AirbaseName) self.isHelipad=true self.isShip=false self.category=Airbase.Category.HELIPAD - _DATABASE:AddStatic(AirbaseName) + _DATABASE:AddStatic(AirbaseName) + end else self:E("ERROR: Unknown airbase category!") end