diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index c658a2a89..bf9bc3256 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -3962,6 +3962,30 @@ do function AI_A2A_DISPATCHER:SchedulerCAP( SquadronName ) self:CAP( SquadronName ) end + + --- Add resources to a Squadron + -- @param #AI_A2A_DISPATCHER self + -- @param #string Squadron The squadron name. + -- @param #number Amount Number of resources to add. + function AI_A2A_DISPATCHER:AddToSquadron(Squadron,Amount) + local Squadron = self:GetSquadron(Squadron) + if Squadron.ResourceCount then + Squadron.ResourceCount = Squadron.ResourceCount + Amount + end + self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) + end + + --- Remove resources from a Squadron + -- @param #AI_A2A_DISPATCHER self + -- @param #string Squadron The squadron name. + -- @param #number Amount Number of resources to remove. + function AI_A2A_DISPATCHER:RemoveFromSquadron(Squadron,Amount) + local Squadron = self:GetSquadron(Squadron) + if Squadron.ResourceCount then + Squadron.ResourceCount = Squadron.ResourceCount - Amount + end + self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) + end end diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index df2bafbf0..fdd9c6ccc 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -4732,3 +4732,26 @@ do end end + --- Add resources to a Squadron + -- @param #AI_A2G_DISPATCHER self + -- @param #string Squadron The squadron name. + -- @param #number Amount Number of resources to add. + function AI_A2G_DISPATCHER:AddToSquadron(Squadron,Amount) + local Squadron = self:GetSquadron(Squadron) + if Squadron.ResourceCount then + Squadron.ResourceCount = Squadron.ResourceCount + Amount + end + self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) + end + + --- Remove resources from a Squadron + -- @param #AI_A2G_DISPATCHER self + -- @param #string Squadron The squadron name. + -- @param #number Amount Number of resources to remove. + function AI_A2G_DISPATCHER:RemoveFromSquadron(Squadron,Amount) + local Squadron = self:GetSquadron(Squadron) + if Squadron.ResourceCount then + Squadron.ResourceCount = Squadron.ResourceCount - Amount + end + self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) + end \ No newline at end of file diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 43eb4af78..93327ab62 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -912,7 +912,7 @@ do -- COORDINATE -- 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 American aircraft we link to the imperial system - Degrees Farenheit (°F) -- -- A text containing a pressure will look like this: -- @@ -958,7 +958,7 @@ do -- COORDINATE -- The text will contain always the pressure in hPa and: -- -- - For Russian and European aircraft using the metric system - hPa and mmHg - -- - For Americain and European aircraft we link to the imperial system - hPa and inHg + -- - For American and European aircraft we link to the imperial system - hPa and inHg -- -- A text containing a pressure will look like this: -- @@ -1051,7 +1051,7 @@ do -- COORDINATE -- 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 American 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: -- @@ -1883,82 +1883,101 @@ do -- COORDINATE -- @param #COORDINATE self -- @param Utilities.Utils#BIGSMOKEPRESET preset Smoke preset (1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke). -- @param #number density (Optional) Smoke density. Number in [0,...,1]. Default 0.5. - function COORDINATE:BigSmokeAndFire( preset, density ) + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFire( preset, density, name ) self:F2( { preset=preset, density=density } ) density=density or 0.5 - trigger.action.effectSmokeBig( self:GetVec3(), preset, density ) + self.firename = name or "Fire-"..math.random(1,10000) + trigger.action.effectSmokeBig( self:GetVec3(), preset, density, self.firename ) + end + + --- Stop big smoke and fire at the coordinate. + -- @param #COORDINATE self + -- @param #string name (Optional) Name of the fire to stop it, if not using the same COORDINATE object. + function COORDINATE:StopBigSmokeAndFire( name ) + self:F2( { name = name } ) + name = name or self.firename + trigger.action.effectSmokeStop( name ) end --- Small smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireSmall( density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFireSmall( density, name ) self:F2( { density=density } ) density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, density) + self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, density, name) end --- Medium smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireMedium( density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFireMedium( density, name ) self:F2( { density=density } ) density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, density) + self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, density, name) end --- Large smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireLarge( density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFireLarge( density, name ) self:F2( { density=density } ) density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, density) + self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, density, name) end --- Huge smoke and fire at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeAndFireHuge( density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeAndFireHuge( density, name ) self:F2( { density=density } ) density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, density) + self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, density, name) end --- Small smoke at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeSmall( density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeSmall( density, name ) self:F2( { density=density } ) density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, density) + self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, density, name) end --- Medium smoke at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeMedium( density ) + -- @param number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeMedium( density, name ) self:F2( { density=density } ) density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, density) + self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, density, name) end --- Large smoke at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeLarge( density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeLarge( density, name ) self:F2( { density=density } ) density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, density) + self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, density,name) end --- Huge smoke at the coordinate. -- @param #COORDINATE self - -- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - function COORDINATE:BigSmokeHuge( density ) + -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + function COORDINATE:BigSmokeHuge( density, name ) self:F2( { density=density } ) density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, density) + self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, density,name) end --- Flares the point in a color. @@ -2105,7 +2124,7 @@ do -- COORDINATE --- Line to all. -- Creates a line on the F10 map from one point to another. -- @param #COORDINATE self - -- @param #COORDINATE Endpoint COORDIANTE to where the line is drawn. + -- @param #COORDINATE Endpoint COORDINATE to where the line is drawn. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. @@ -2162,7 +2181,7 @@ do -- COORDINATE --- Rectangle to all. Creates a rectangle on the map from the COORDINATE in one corner to the end COORDINATE in the opposite corner. -- Creates a line on the F10 map from one point to another. -- @param #COORDINATE self - -- @param #COORDINATE Endpoint COORDIANTE in the opposite corner. + -- @param #COORDINATE Endpoint COORDINATE in the opposite corner. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. @@ -2190,9 +2209,9 @@ do -- COORDINATE --- Creates a shape defined by 4 points on the F10 map. The first point is the current COORDINATE. The remaining three points need to be specified. -- @param #COORDINATE self - -- @param #COORDINATE Coord2 Second COORDIANTE of the quad shape. - -- @param #COORDINATE Coord3 Third COORDIANTE of the quad shape. - -- @param #COORDINATE Coord4 Fourth COORDIANTE of the quad shape. + -- @param #COORDINATE Coord2 Second COORDINATE of the quad shape. + -- @param #COORDINATE Coord3 Third COORDINATE of the quad shape. + -- @param #COORDINATE Coord4 Fourth COORDINATE of the quad shape. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. @@ -3007,7 +3026,7 @@ do -- POINT_VEC3 -- @type POINT_VEC3 -- @field #number x The x coordinate in 3D space. -- @field #number y The y coordinate in 3D space. - -- @field #number z The z coordiante in 3D space. + -- @field #number z The z COORDINATE in 3D space. -- @field Utilities.Utils#SMOKECOLOR SmokeColor -- @field Utilities.Utils#FLARECOLOR FlareColor -- @field #POINT_VEC3.RoutePointAltType RoutePointAltType diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 679ba9820..d89568336 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -326,7 +326,7 @@ do -- SET_BASE for _,Object in pairs(union.Set) do if self:IsIncludeObject(Object) and SetB:IsIncludeObject(Object) then - intersection:AddObject(intersection) + intersection:AddObject(Object) end end diff --git a/Moose Development/Moose/Functional/AICSAR.lua b/Moose Development/Moose/Functional/AICSAR.lua index 923b5c3ef..790ad4262 100644 --- a/Moose Development/Moose/Functional/AICSAR.lua +++ b/Moose Development/Moose/Functional/AICSAR.lua @@ -8,6 +8,7 @@ -- * Starting from a FARP or Airbase -- * Dedicated MASH zone -- * Some FSM functions to include in your mission scripts +-- * Limit number of available helos -- -- === -- @@ -45,7 +46,8 @@ -- @field #table rescued Track number of rescued pilot. -- @field #boolean autoonoff Only send a helo when no human heli pilots are available. -- @field Core.Set#SET_CLIENT playerset Track if alive heli pilots are available. --- +-- @field #boolean limithelos limit available number of helos going on mission (defaults to true) +-- @field #number helonumber number of helos available (default: 3) -- @extends Core.Fsm#FSM @@ -81,7 +83,9 @@ -- my_aicsar.maxdistance -- maximum operational distance in meters. Defaults to 50NM or 92.6km -- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m -- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true. --- my_aicsar.verbose -- text messages coalition side about ongoing operations. Defaults to true. +-- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true. +-- my_aicsarlimithelos -- limit available number of helos going on mission (defaults to true) +-- my_aicsar.helonumber -- number of helos available (default: 3) -- -- ## Radio options -- @@ -148,7 +152,7 @@ -- @field #AICSAR AICSAR = { ClassName = "AICSAR", - version = "0.0.3", + version = "0.0.4", lid = "", coalition = coalition.side.BLUE, template = "", @@ -176,6 +180,8 @@ AICSAR = { DCSFrequency = 243, DCSModulation = radio.modulation.AM, DCSRadioGroup = nil, + limithelos = true, + helonumber = 3, } -- TODO Messages @@ -290,6 +296,10 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) self.MGRS_Accuracy = 2 + -- limit number of available helos at the same time + self.limithelos = true + self.helonumber = 3 + -- 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") @@ -311,7 +321,9 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) self:__Start(math.random(2,5)) - self:I(self.lid .. " AI CSAR Starting") + local text = string.format("%sAICSAR Version %s Starting",self.lid,self.version) + + self:I(text) ------------------------ --- Pseudo Functions --- @@ -606,6 +618,18 @@ function AICSAR:_CheckHelos() return self end +--- [Internal] Count helos queue +-- @param #AICSAR self +-- @return #number Number of helos on mission +function AICSAR:_CountHelos() + self:T(self.lid .. "_CountHelos") + local count = 0 + for _index,_helo in pairs(self.helos) do + count = count + 1 + end + return count +end + --- [Internal] Check pilot queue for next mission -- @param #AICSAR self -- @return #AICSAR self @@ -614,11 +638,29 @@ function AICSAR:_CheckQueue() for _index, _pilot in pairs(self.pilotqueue) do local classname = _pilot.ClassName and _pilot.ClassName or "NONE" local name = _pilot.GroupName and _pilot.GroupName or "NONE" + local helocount = self:_CountHelos() --self:T("Looking at " .. classname .. " " .. name) -- find one w/o mission if _pilot and _pilot.ClassName and _pilot.ClassName == "GROUP" then + local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP + -- rescued? + if self:_CheckInMashZone(_pilot) then + self:T("Pilot" .. _pilot.GroupName .. " rescued!") + _pilot:Destroy(false) + self.pilotqueue[_index] = nil + self.rescued[_index] = true + self:__PilotRescued(2) + if flightgroup then + flightgroup.AICSARReserved = false + end + end -- end rescued -- has no mission assigned? if not _pilot.AICSAR then + -- helo available? + if self.limithelos and helocount >= self.helonumber then + -- none free + break + end -- end limit _pilot.AICSAR = {} _pilot.AICSAR.Status = "Initiated" _pilot.AICSAR.Boarded = false @@ -626,23 +668,14 @@ function AICSAR:_CheckQueue() break else -- update status from OPSGROUP - local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP if flightgroup then local state = flightgroup:GetState() _pilot.AICSAR.Status = state end --self:T("Flight for " .. _pilot.GroupName .. " in state " .. state) - if self:_CheckInMashZone(_pilot) then - self:T("Pilot" .. _pilot.GroupName .. " rescued!") - _pilot:Destroy(false) - self.pilotqueue[_index] = nil - self.rescued[_index] = true - self:__PilotRescued(2) - flightgroup.AICSARReserved = false - end - end - end - end + end -- end has mission + end -- end if pilot + end -- end loop return self end diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index fa1ec6efb..1aa831114 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -109,7 +109,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.0.10" +AUTOLASE.version = "0.0.11" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -736,20 +736,22 @@ function AUTOLASE:onafterMonitor(From, Event, To) local contact = _contact -- Ops.Intelligence#INTEL.Contact local grp = contact.group local coord = contact.position - local reccename = contact.recce + local reccename = contact.recce or "none" local reccegrp = UNIT:FindByName(reccename) - local reccecoord = reccegrp:GetCoordinate() - local distance = math.floor(reccecoord:Get3DDistance(coord)) - local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel) - report:Add(text) - self:T(text) - if self.debug then self:I(text) end - lines = lines + 1 - -- sort out groups beyond sight - local lasedistance = self:GetLosFromUnit(reccegrp) - if grp:IsGround() and lasedistance >= distance then - table.insert(groupsbythreat,{contact.group,contact.threatlevel}) - self.RecceNames[contact.groupname] = contact.recce + if reccegrp then + local reccecoord = reccegrp:GetCoordinate() + local distance = math.floor(reccecoord:Get3DDistance(coord)) + local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel) + report:Add(text) + self:T(text) + if self.debug then self:I(text) end + lines = lines + 1 + -- sort out groups beyond sight + local lasedistance = self:GetLosFromUnit(reccegrp) + if grp:IsGround() and lasedistance >= distance then + table.insert(groupsbythreat,{contact.group,contact.threatlevel}) + self.RecceNames[contact.groupname] = contact.recce + end end end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index eb9b2e982..3e2e124c7 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -714,7 +714,7 @@ do -- DETECTION_BASE if self.RejectZones then for RejectZoneID, RejectZone in pairs( self.RejectZones ) do local RejectZone = RejectZone -- Core.Zone#ZONE_BASE - if RejectZone:IsPointVec2InZone( DetectedObjectVec2 ) == true then + if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then DetectionAccepted = false end end @@ -759,7 +759,7 @@ do -- DETECTION_BASE local ZoneProbability = ZoneData[2] -- #number ZoneProbability = ZoneProbability * 30 / 300 - if ZoneObject:IsPointVec2InZone( DetectedObjectVec2 ) == true then + if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then local Probability = math.random() -- Selects a number between 0 and 1 --self:T( { Probability, ZoneProbability } ) if Probability > ZoneProbability then @@ -2485,13 +2485,13 @@ do -- DETECTION_AREAS -- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones -- -- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and - -- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Functional.Detection#DETECTION_AREAS}. + -- the methods to manage the DetectedItems[].Zone(s) are implemented in @{Functional.Detection#DETECTION_AREAS}. -- -- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned. -- - -- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}(). - -- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}(). - -- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index. + -- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZones}(). + -- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneCount}(). + -- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneByID}() with a given index. -- -- ## 4.4) Flare or Smoke detected units -- @@ -2535,7 +2535,49 @@ do -- DETECTION_AREAS return self end - + --- Retrieve set of detected zones. + -- @param #DETECTION_AREAS self + -- @return Core.Set#SET_ZONE The @{Set} of ZONE_UNIT objects detected. + function DETECTION_AREAS:GetDetectionZones() + local zoneset = SET_ZONE:New() + for _ID,_Item in pairs (self.DetectedItems) do + local item = _Item -- #DETECTION_BASE.DetectedItem + if item.Zone then + zoneset:AddZone(item.Zone) + end + end + return zoneset + end + + --- Retrieve a specific zone by its ID (number) + -- @param #DETECTION_AREAS self + -- @param #number ID + -- @return Core.Zone#ZONE_UNIT The zone or nil if it does not exist + function DETECTION_AREAS:GetDetectionZoneByID(ID) + local zone = nil + for _ID,_Item in pairs (self.DetectedItems) do + local item = _Item -- #DETECTION_BASE.DetectedItem + if item.ID == ID then + zone = item.Zone + break + end + end + return zone + end + + --- Retrieve number of detected zones. + -- @param #DETECTION_AREAS self + -- @return #number The number of zones. + function DETECTION_AREAS:GetDetectionZoneCount() + local zoneset = 0 + for _ID,_Item in pairs (self.DetectedItems) do + if _Item.Zone then + zoneset = zoneset + 1 + end + end + return zoneset + end + --- Report summary of a detected item using a given numeric index. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index fc4364bfe..9da579d6d 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -99,6 +99,9 @@ -- @field #string rangecontrolrelayname Name of relay unit. -- @field #string instructorrelayname Name of relay unit. -- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/". +-- @field #boolean targetsheet If true, players can save their target sheets. Rangeboss will not work if targetsheets do not save. +-- @field #string targetpath Path where to save the target sheets. +-- @field #string targetprefix File prefix for target sheet files. -- @extends Core.Fsm#FSM --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven @@ -267,6 +270,7 @@ -- -- The [476th - Air Weapons Range Objects mod](http://www.476vfightergroup.com/downloads.php?do=file&id=287) is (implicitly) used in this example. -- +-- -- # Debugging -- -- In case you have problems, it is always a good idea to have a look at your DCS log file. You find it in your "Saved Games" folder, so for example in @@ -333,7 +337,10 @@ RANGE={ instructor = nil, rangecontrolfreq = nil, rangecontrol = nil, - soundpath = "Range Soundfiles/" + soundpath = "Range Soundfiles/", + targetsheet = nil, + targetpath = nil, + targetprefix = nil, } --- Default range parameters. @@ -881,6 +888,22 @@ function RANGE:SetAutosaveOff() return self end +--- Enable saving of player's target sheets and specify an optional directory path. +-- @param #RANGE self +-- @param #string path (Optional) Path where to save the target sheets. +-- @param #string prefix (Optional) Prefix for target sheet files. File name will be saved as *prefix_aircrafttype-0001.csv*, *prefix_aircrafttype-0002.csv*, etc. +-- @return #RANGE self +function RANGE:SetTargetSheet(path, prefix) + if io then + self.targetsheet=true + self.targetpath=path + self.targetprefix=prefix + else + self:E(self.lid.."ERROR: io is not desanitized. Cannot save target sheet.") + end + return self +end + --- Set messages to examiner. The examiner will receive messages from all clients. -- @param #RANGE self -- @param #string examinergroupname Name of the group of the examiner. @@ -1877,7 +1900,7 @@ function RANGE:OnEventShot(EventData) result.roundsFired=0 --Rangeboss Edit result.roundsHit=0 --Rangeboss Edit result.roundsQuality="N/A" --Rangeboss Edit - result.rangename = self.rangename + result.rangename = self.rangename -- Add to table. table.insert(_results, result) @@ -1932,15 +1955,24 @@ function RANGE:_SaveTargetSheet(_playername, result) --RangeBoss Specific Functi end end - local path=lfs.writedir()..[[Logs\]] + + -- Set path or default. + local path=self.targetpath + if lfs then + path=path or lfs.writedir()..[[Logs\]] + end -- Create unused file name. local filename=nil for i=1,9999 do + -- Create file name + if self.targetprefix then + filename=string.format("%s_%s-%04d.csv", self.targetprefix, playerData.actype, i) + else local name=UTILS.ReplaceIllegalCharacters(_playername, "_") - filename=string.format("RANGERESULTS-%s_Targetsheet-%s-%04d.csv",self.rangename,name, i) - --end + filename=string.format("RANGERESULTS-%s_Targetsheet-%s-%04d.csv",self.rangename,name, i) + end -- Set path. if path~=nil then @@ -2975,9 +3007,12 @@ function RANGE:_CheckInZone(_unitName) Straferesult.roundsHit= _result.hits Straferesult.roundsQuality=_result.text Straferesult.strafeAccuracy=accur - Straferesult.rangename=self.rangename - + Straferesult.rangename=self.rangename + + -- Save trap sheet. + if playerData.targeton and self.targetsheet then self:_SaveTargetSheet(_playername, result) + end --RangeBoss edit for strafe data saved to file -- Voice over. @@ -3084,7 +3119,8 @@ function RANGE:_AddF10Commands(_unitName) -- MISSION LEVEL -- ------------------- - _rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10Root) + --_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10Root) + _rangePath = MENU_GROUP:New(group,"On the Range") else @@ -3094,55 +3130,57 @@ function RANGE:_AddF10Commands(_unitName) -- Main F10 menu: F10/On the Range// if RANGE.MenuF10[_gid] == nil then - RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range") + --RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid, "On the Range") + RANGE.MenuF10[_gid]=MENU_GROUP:New(group,"On the Range") end - _rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid]) - + --_rangePath = missionCommands.addSubMenuForGroup(_gid, self.rangename, RANGE.MenuF10[_gid]) + _rangePath = MENU_GROUP:New(group,self.rangename,RANGE.MenuF10[_gid]) end - - - local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Statistics", _rangePath) - local _markPath = missionCommands.addSubMenuForGroup(_gid, "Mark Targets", _rangePath) - local _settingsPath = missionCommands.addSubMenuForGroup(_gid, "My Settings", _rangePath) - local _infoPath = missionCommands.addSubMenuForGroup(_gid, "Range Info", _rangePath) + + local _statsPath = MENU_GROUP:New(group,"Statistics",_rangePath) + local _markPath = MENU_GROUP:New(group,"Mark Targets",_rangePath) + local _settingsPath = MENU_GROUP:New(group,"My Settings",_rangePath) + local _infoPath = MENU_GROUP:New(group,"Range Info",_rangePath) + -- F10/On the Range//My Settings/ - local _mysmokePath = missionCommands.addSubMenuForGroup(_gid, "Smoke Color", _settingsPath) - local _myflarePath = missionCommands.addSubMenuForGroup(_gid, "Flare Color", _settingsPath) + local _mysmokePath = MENU_GROUP:New(group,"Smoke Color",_settingsPath) + local _myflarePath = MENU_GROUP:New(group,"Flare Color",_settingsPath) - -- F10/On the Range//Mark Targets/ - missionCommands.addCommandForGroup(_gid, "Mark On Map", _markPath, self._MarkTargetsOnMap, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Illuminate Range", _markPath, self._IlluminateBombTargets, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Smoke Strafe Pits", _markPath, self._SmokeStrafeTargetBoxes, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Smoke Strafe Tgts", _markPath, self._SmokeStrafeTargets, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Smoke Bomb Tgts", _markPath, self._SmokeBombTargets, self, _unitName) + --F10/On the Range//Mark Targets/ + local _MoMap = MENU_GROUP_COMMAND:New(group,"Mark On Map",_markPath,self._MarkTargetsOnMap, self, _unitName) + local _IllRng = MENU_GROUP_COMMAND:New(group, "Illuminate Range", _markPath, self._IlluminateBombTargets, self, _unitName) + local _SSpit = MENU_GROUP_COMMAND:New(group, "Smoke Strafe Pits", _markPath, self._SmokeStrafeTargetBoxes, self, _unitName) + local _SStgts = MENU_GROUP_COMMAND:New(group, "Smoke Strafe Tgts", _markPath, self._SmokeStrafeTargets, self, _unitName) + local _SBtgts = MENU_GROUP_COMMAND:New(group, "Smoke Bomb Tgts", _markPath, self._SmokeBombTargets, self, _unitName) -- F10/On the Range//Stats/ - missionCommands.addCommandForGroup(_gid, "All Strafe Results", _statsPath, self._DisplayStrafePitResults, self, _unitName) - missionCommands.addCommandForGroup(_gid, "All Bombing Results", _statsPath, self._DisplayBombingResults, self, _unitName) - missionCommands.addCommandForGroup(_gid, "My Strafe Results", _statsPath, self._DisplayMyStrafePitResults, self, _unitName) - missionCommands.addCommandForGroup(_gid, "My Bomb Results", _statsPath, self._DisplayMyBombingResults, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Reset All Stats", _statsPath, self._ResetRangeStats, self, _unitName) + local _AllSR = MENU_GROUP_COMMAND:New(group, "All Strafe Results", _statsPath, self._DisplayStrafePitResults, self, _unitName) + local _AllBR = MENU_GROUP_COMMAND:New(group, "All Bombing Results", _statsPath, self._DisplayBombingResults, self, _unitName) + local _MySR = MENU_GROUP_COMMAND:New(group, "My Strafe Results", _statsPath, self._DisplayMyStrafePitResults, self, _unitName) + local _MyBR = MENU_GROUP_COMMAND:New(group, "My Bomb Results", _statsPath, self._DisplayMyBombingResults, self, _unitName) + local _ResetST = MENU_GROUP_COMMAND:New(group, "Reset All Stats", _statsPath, self._ResetRangeStats, self, _unitName) -- F10/On the Range//My Settings/Smoke Color/ - missionCommands.addCommandForGroup(_gid, "Blue Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Blue) - missionCommands.addCommandForGroup(_gid, "Green Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Green) - missionCommands.addCommandForGroup(_gid, "Orange Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Orange) - missionCommands.addCommandForGroup(_gid, "Red Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Red) - missionCommands.addCommandForGroup(_gid, "White Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.White) + local _BlueSM = MENU_GROUP_COMMAND:New(group, "Blue Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Blue) + local _GrSM = MENU_GROUP_COMMAND:New(group, "Green Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Green) + local _OrSM = MENU_GROUP_COMMAND:New(group, "Orange Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Orange) + local _ReSM = MENU_GROUP_COMMAND:New(group, "Red Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.Red) + local _WhSm = MENU_GROUP_COMMAND:New(group, "White Smoke", _mysmokePath, self._playersmokecolor, self, _unitName, SMOKECOLOR.White) -- F10/On the Range//My Settings/Flare Color/ - missionCommands.addCommandForGroup(_gid, "Green Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Green) - missionCommands.addCommandForGroup(_gid, "Red Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Red) - missionCommands.addCommandForGroup(_gid, "White Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.White) - missionCommands.addCommandForGroup(_gid, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow) + local _GrFl = MENU_GROUP_COMMAND:New(group, "Green Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Green) + local _ReFl = MENU_GROUP_COMMAND:New(group, "Red Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Red) + local _WhFl = MENU_GROUP_COMMAND:New(group, "White Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.White) + local _YeFl = MENU_GROUP_COMMAND:New(group, "Yellow Flares", _myflarePath, self._playerflarecolor, self, _unitName, FLARECOLOR.Yellow) -- F10/On the Range//My Settings/ - missionCommands.addCommandForGroup(_gid, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName) - missionCommands.addCommandForGroup(_gid, "All Messages On/Off", _settingsPath, self._MessagesToPlayerOnOff, self, _unitName) + local _SmDe = MENU_GROUP_COMMAND:New(group, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName) + local _SmIm = MENU_GROUP_COMMAND:New(group, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName) + local _FlHi = MENU_GROUP_COMMAND:New(group, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName) + local _AlMeA = MENU_GROUP_COMMAND:New(group, "All Messages On/Off", _settingsPath, self._MessagesToPlayerOnOff, self, _unitName) + local _TrpSh = MENU_GROUP_COMMAND:New(group, "Targetsheet On/Off", _settingsPath, self._TargetsheetOnOff, self, _unitName) -- F10/On the Range//Range Information - missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Bombing Targets", _infoPath, self._DisplayBombTargets, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName) + local _WeIn = MENU_GROUP_COMMAND:New(group, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName) + local _WeRe = MENU_GROUP_COMMAND:New(group, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName) + local _BoTgtgs = MENU_GROUP_COMMAND:New(group, "Bombing Targets", _infoPath, self._DisplayBombTargets, self, _unitName) + local _StrPits = MENU_GROUP_COMMAND:New(group, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName):Refresh() end else self:E(self.id.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName) @@ -3468,6 +3506,49 @@ function RANGE:_MessagesToPlayerOnOff(unitname) end +--- Targetsheet saves if player on or off. +-- @param #RANGE self +-- @param #string _unitname Name of the player unit. +function RANGE:_TargetsheetOnOff(_unitname) + self:F2(_unitname) + + -- Get player unit and player name. + local unit, playername = self:_GetPlayerUnitAndName(_unitname) + + -- Check if we have a player. + if unit and playername then + + -- Player data. + local playerData=self.PlayerSettings[playername] --#RANGE.PlayerData + + if playerData then + + -- Check if option is enabled at all. + local text="" + if self.targetsheet then + + -- Invert current setting. + playerData.targeton=not playerData.targeton + + -- Inform player. + if playerData.targeton==true then + text=string.format("roger, your targetsheets are now SAVED.") + else + text=string.format("affirm, your targetsheets are NOT SAVED.") + end + + else + text="negative, target sheet data recorder is broken on this range." + end + + -- Message to player. + --self:MessageToPlayer(playerData, text, nil, playerData.name, 5) + self:_DisplayMessageToGroup(unit,text,5,false,false) + end + end + +end + --- Toggle status of flaring direct hits of range targets. -- @param #RANGE self -- @param #string unitname Name of the player unit. diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 0ca2f1058..4a3253a42 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: Dec 2021 +-- Date: Feb 2022 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -244,10 +244,11 @@ CSAR.AircraftType["Mi-8MT"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 CSAR.AircraftType["Bell-47"] = 2 +CSAR.AircraftType["UH-60L"] = 10 --- CSAR class version. -- @field #string version -CSAR.version="1.0.1r1" +CSAR.version="1.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -696,7 +697,7 @@ 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 #string _zone Name of the zone. Can also be passed as a (normal, round) ZONE object. -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. @@ -707,7 +708,16 @@ end 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 + + local _triggerZone = nil + if type(_zone) == "string" then + _triggerZone = ZONE:New(_zone) -- trigger to use as reference position + elseif type(_zone) == "table" and _zone.ClassName then + if string.find(_zone.ClassName, "ZONE",1) then + _triggerZone = _zone -- is already a zone + end + end + if _triggerZone == nil then self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) return @@ -741,7 +751,7 @@ 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 #string Zone Name of the zone. Can also be passed as a (normal, round) ZONE object. -- @param #number Coalition Coalition. -- @param #string Description (optional) Description. -- @param #boolean RandomPoint (optional) Random yes or no. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index c626472b0..22de445b8 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,12 +22,13 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: Dec 2021 +-- Date: Feb 2022 do + ------------------------------------------------------ --- **CTLD_ENGINEERING** class, extends Core.Base#BASE --- @type CTLD_ENGINEERING +--- @type CTLD_ENGINEERING -- @field #string ClassName -- @field #string lid -- @field #string Name @@ -37,6 +38,9 @@ do -- @field Wrapper.Unit#UNIT HeliUnit -- @field #string State -- @extends Core.Base#BASE + +--- +-- @field #CTLD_ENGINEERING CTLD_ENGINEERING = { ClassName = "CTLD_ENGINEERING", lid = "", @@ -299,9 +303,14 @@ CTLD_ENGINEERING = { return -1 end end + +end + +do ------------------------------------------------------ --- **CTLD_CARGO** class, extends Core.Base#BASE -- @type CTLD_CARGO +-- @field #string ClassName Class name. -- @field #number ID ID of this cargo. -- @field #string Name Name for menu. -- @field #table Templates Table of #POSITIONABLE objects. @@ -311,9 +320,13 @@ CTLD_ENGINEERING = { -- @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 --- @field #number Stock Number of builds available, -1 for unlimited +-- @field #number PerCrateMass Mass in kg. +-- @field #number Stock Number of builds available, -1 for unlimited. +-- @field #string Subcategory Sub-category name. -- @extends Core.Base#BASE + +--- +-- @field #CTLD_CARGO CTLD_CARGO = { ClassName = "CTLD_CARGO", ID = 0, @@ -330,17 +343,17 @@ CTLD_CARGO = { Mark = nil, } + --- --- Define cargo types. - -- @type CTLD_CARGO.Enum - -- @field #string Type Type of Cargo. + -- @field Enum CTLD_CARGO.Enum = { - ["VEHICLE"] = "Vehicle", -- #string vehicles - ["TROOPS"] = "Troops", -- #string troops - ["FOB"] = "FOB", -- #string FOB - ["CRATE"] = "Crate", -- #string crate - ["REPAIR"] = "Repair", -- #string repair - ["ENGINEERS"] = "Engineers", -- #string engineers - ["STATIC"] = "Static", -- #string engineers + VEHICLE = "Vehicle", -- #string vehicles + TROOPS = "Troops", -- #string troops + FOB = "FOB", -- #string FOB + CRATE = "Crate", -- #string crate + REPAIR = "Repair", -- #string repair + ENGINEERS = "Engineers", -- #string engineers + STATIC = "Static", -- #string engineers } --- Function to create new CTLD_CARGO object. @@ -356,8 +369,9 @@ CTLD_CARGO = { -- @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 + -- @param #string Subcategory Name of subcategory, handy if using > 10 types to load. -- @return #CTLD_CARGO self - function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock) + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #CTLD self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) @@ -373,6 +387,7 @@ CTLD_CARGO = { self.PerCrateMass = PerCrateMass or 0 -- #number self.Stock = Stock or nil --#number self.Mark = nil + self.Subcategory = Subcategory or "Other" return self end @@ -383,12 +398,20 @@ CTLD_CARGO = { return self.ID end + --- Query Subcategory + -- @param #CTLD_CARGO self + -- @return #string SubCategory + function CTLD_CARGO:GetSubCat() + return self.Subcategory + 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 @@ -568,11 +591,12 @@ do -- * Additional events to tailor your mission. -- * 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. +-- * Weight checks on loaded cargo. -- -- ## 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. +-- Create the late-activated troops, vehicles, that will make up your deployable forces. -- -- ## 1. Basic Setup -- @@ -606,7 +630,7 @@ 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. +-- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. 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) @@ -614,7 +638,8 @@ do -- -- 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 +-- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair, +-- -- e.g. the "Humvee" here refers back to the "Humvee" crates cargo added above (same template!) -- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) -- my_ctld.repairtime = 300 -- takes 300 seconds to repair something -- @@ -624,9 +649,9 @@ do -- -- ## 1.3 Add logistics zones -- --- Add zones for loading troops and crates and dropping, building crates +-- Add (normal, round!) 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. +-- -- Add a zone of type LOAD to our setup. Players can load any troops and crates here as defined in 1.2 above. -- -- "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) @@ -646,7 +671,7 @@ do -- -- "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 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. +-- -- When the ship is moving, avoid forcing hoverload. -- my_ctld:AddCTLDZone("Tarawa",CTLD.CargoZoneType.SHIP,SMOKECOLOR.Blue,true,true,240,20) -- -- ## 2. Options @@ -670,8 +695,11 @@ do -- 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 -- my_ctld.pilotmustopendoors = false -- force opening of doors --- my_ctld.SmokeColor = SMOKECOLOR.Red -- color to use when dropping smoke from heli +-- my_ctld.SmokeColor = SMOKECOLOR.Red -- default color to use when dropping smoke from heli -- my_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli +-- my_ctld.basetype = "container_cargo" -- default shape of the cargo container +-- my_ctld.droppedbeacontimeout = 600 -- dropped beacon lasts 10 minutes +-- my_ctld.usesubcats = false -- use sub-category names for crates, adds an extra menu layer in "Get Crates", useful if you have > 10 crate types. -- -- ## 2.1 User functions -- @@ -680,22 +708,21 @@ do -- 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, 12) +-- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type, up to 4000 kgs: +-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12, 4000) -- --- -- Default unit type capabilities are: --- --- ["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}, --- +-- -- Default unit type capabilities are: +-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, +-- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, +-- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, +-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, +-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700}, +-- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, +-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, +-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, +-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, +-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, +-- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- -- ### 2.1.2 Activate and deactivate zones -- @@ -825,7 +852,7 @@ do -- -- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. -- --- ## 4.4 Smoke & Flare zones nearby or drop smoke or flare from Heli +-- ## 4.4 Smoke & Flare zones nearby or drop smoke, beacon or flare from Heli -- -- Does what it says. -- @@ -947,6 +974,7 @@ CTLD = { -- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon -- @field #number shiplength For ships - length of ship -- @field #number shipwidth For ships - width of ship +-- @field #number timestamp For dropped beacons - time this was created --- Zone Type Info. -- @type CTLD.CargoZoneType @@ -955,6 +983,7 @@ CTLD.CargoZoneType = { DROP = "drop", MOVE = "move", SHIP = "ship", + BEACON = "beacon", } --- Buildable table info. @@ -973,24 +1002,26 @@ CTLD.CargoZoneType = { -- @field #boolean troops Can transport troops. -- @field #number cratelimit Number of crates transportable. -- @field #number trooplimit Number of troop units transportable. +-- @field #number cargoweightlimit Max loadable kgs of cargo. CTLD.UnitTypes = { - ["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. + ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, + ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, + ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, + ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, + ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700}, + ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, + ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, + ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, + ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, + ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, + ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers. --Actually it's longer, but the center coord is off-center of the model. + ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats } --- CTLD class version. -- @field #string version -CTLD.version="1.0.1" +CTLD.version="1.0.6" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1079,6 +1110,9 @@ function CTLD:New(Coalition, Prefixes, Alias) self.dropOffZones = {} self.wpZones = {} self.shipZones = {} + self.droppedBeacons = {} + self.droppedbeaconref = {} + self.droppedbeacontimeout = 600 -- Cargo self.Cargo_Crates = {} @@ -1147,6 +1181,10 @@ function CTLD:New(Coalition, Prefixes, Alias) self.saveinterval = 600 self.eventoninject = true + -- sub categories + self.usesubcats = false + self.subcats = {} + local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CTLD_%s_Persist.csv",AliaS) @@ -1155,6 +1193,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- slingload self.enableslingload = false + self.basetype = "container_cargo" -- shape of the container -- Smokes and Flares self.SmokeColor = SMOKECOLOR.Red @@ -1345,6 +1384,7 @@ function CTLD:_GetUnitCapabilities(Unit) capabilities.trooplimit = 0 capabilities.type = "generic" capabilities.length = 20 + capabilities.cargoweightlimit = 0 end return capabilities end @@ -1400,6 +1440,7 @@ function CTLD:_EventHandler(EventData) -- Herc support --self:T_unit:GetTypeName()) if _unit:GetTypeName() == "Hercules" and self.enableHercules then + local unitname = event.IniUnitName or "none" self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end @@ -1856,7 +1897,7 @@ 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" + local basetype = self.basetype or "container_cargo" if isstatic then basetype = cratetemplate end @@ -1885,13 +1926,14 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) end local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() + local subcat = cargotype.Subcategory self.CargoCounter = self.CargoCounter + 1 local realcargo = nil if drop then - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat) table.insert(droppedcargo,realcargo) else - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat) Cargo:RemoveStock() end table.insert(self.Spawned_Cargo, realcargo) @@ -1934,7 +1976,7 @@ function CTLD:InjectStatics(Zone, Cargo, RandomCoord) cratetemplate = cargotype:GetTemplates() isstatic = true end - local basetype = "container_cargo" + local basetype = self.basetype or "container_cargo" if isstatic then basetype = cratetemplate end @@ -2040,16 +2082,25 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) -- cycle local index = 0 local found = {} + local loadedmass = self:_GetUnitCargoMass(_unit) + local unittype = _unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities + local maxmass = capabilities.cargoweightlimit + local maxloadable = maxmass - loadedmass + self:T(self.lid .. " Max loadable mass: " .. maxloadable) for _,_cargoobject in pairs (existingcrates) do local cargo = _cargoobject -- #CTLD_CARGO local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates local staticid = cargo:GetID() + local weight = cargo:GetMass() -- weight in kgs of this cargo + self:T(self.lid .. " Found cargo mass: " .. weight) if static and static:IsAlive() then local staticpos = static:GetCoordinate() local distance = self:_GetDistance(location,staticpos) - if distance <= finddist and static then + if distance <= finddist and static and weight <= maxloadable then index = index + 1 table.insert(found, staticid, cargo) + maxloadable = maxloadable - weight end end end @@ -2095,6 +2146,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) if self.Loaded_Cargo[unitname] then loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo numberonboard = loaded.Cratesloaded or 0 + massonboard = self:_GetUnitCargoMass(Unit) else loaded = {} -- #CTLD.LoadedCargo loaded.Troopsloaded = 0 @@ -2104,10 +2156,11 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- get nearby crates local finddist = self.CrateDistance or 35 local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + self:T(self.lid .. " Crates found: " .. number) if number == 0 and self.hoverautoloading then return self -- exit elseif number == 0 then - self:_SendMessage("Sorry no loadable crates nearby!", 10, false, Group) + self:_SendMessage("Sorry no loadable crates nearby or max cargo weight reached!", 10, false, Group) return self -- exit elseif numberonboard == cratelimit then self:_SendMessage("Sorry no fully loaded!", 10, false, Group) @@ -2896,6 +2949,16 @@ function CTLD:_RefreshF10Menus() end -- end for self.CtldUnits = _UnitList + -- subcats? + if self.usesubcats then + for _id,_cargo in pairs(self.Cargo_Crates) do + local entry = _cargo -- #CTLD_CARGO + if not self.subcats[entry.Subcategory] then + self.subcats[entry.Subcategory] = entry.Subcategory + end + end + end + -- build unit menus local menucount = 0 local menus = {} @@ -2917,11 +2980,17 @@ function CTLD:_RefreshF10Menus() 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 smoketopmenu = MENU_GROUP:New(_group,"Smokes & Flares",topmenu) + local smoketopmenu = MENU_GROUP:New(_group,"Smokes, Flares, Beacons",topmenu) local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) - local smokeself = MENU_GROUP_COMMAND:New(_group,"Drop smoke now",smoketopmenu, self.SmokePositionNow, self, _unit, false) + local smokeself = MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu) + local smokeselfred = MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Red) + local smokeselfblue = MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Blue) + local smokeselfgreen = MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Green) + local smokeselforange = MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Orange) + local smokeselfwhite = MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.White) local flaremenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) - local flareself = MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu, self.SmokePositionNow, self, _unit, true):Refresh() + local flareself = MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu, self.SmokePositionNow, self, _unit, true) + local beaconself = MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() -- sub menus -- sub menu troops management if cantroops then @@ -2938,11 +3007,26 @@ function CTLD:_RefreshF10Menus() 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) + + if self.usesubcats then + local subcatmenus = {} + for _name,_entry in pairs(self.subcats) do + subcatmenus[_name] = MENU_GROUP:New(_group,_name,cratesmenu) + end + for _,_entry in pairs(self.Cargo_Crates) do + local entry = _entry -- #CTLD_CARGO + local subcat = entry.Subcategory + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) + end + else + 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 end for _,_entry in pairs(self.Cargo_Statics) do local entry = _entry -- #CTLD_CARGO @@ -2970,6 +3054,25 @@ function CTLD:_RefreshF10Menus() return self end +--- [Internal] Function to check if a template exists in the mission. +-- @param #CTLD self +-- @param #table temptable Table of string names +-- @return #boolen outcome +function CTLD:_CheckTemplates(temptable) + self:T(self.lid .. " _CheckTemplates") + local outcome = true + if type(temptable) ~= "table" then + temptable = {temptable} + end + for _,_name in pairs(temptable) do + if not _DATABASE.Templates.Groups[_name] then + outcome = false + self:E(self.lid .. "ERROR: Template name " .. _name .. " is missing!") + end + end + return outcome +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". @@ -2981,6 +3084,10 @@ function CTLD:_RefreshF10Menus() function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock) self:T(self.lid .. " AddTroopsCargo") self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock}) + if not self:_CheckTemplates(Templates) then + self:E(self.lid .. "Troops Cargo for " .. Name .. " has missing template(s)!" ) + return self + end 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,Stock) @@ -2996,11 +3103,16 @@ end -- @param #number NoCrates Number of crates needed to build this cargo. -- @param #number PerCrateMass Mass in kg of each crate -- @param #number Stock Number of groups in stock. Nil for unlimited. -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock) +-- @param #string SubCategory Name of sub-category (optional). +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory) self:T(self.lid .. " AddCratesCargo") + if not self:_CheckTemplates(Templates) then + self:E(self.lid .. "Crates Cargo for " .. Name .. " has missing template(s)!" ) + return self + end 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,Stock) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory) table.insert(self.Cargo_Crates,cargo) return self end @@ -3040,16 +3152,21 @@ 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 #string Template Template of VEHICLE or FOB cargo that this can repair. MUST be the same as given in `AddCratesCargo(..)`! -- @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 -- @param #number Stock Number of groups in stock. Nil for unlimited. -function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock) +-- @param #string SubCategory Name of the sub-category (optional). +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory) self:T(self.lid .. " AddCratesRepair") + if not self:_CheckTemplates(Template) then + self:E(self.lid .. "Repair Cargo for " .. Name .. " has a missing template!" ) + return self + end 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,Stock) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory) table.insert(self.Cargo_Crates,cargo) return self end @@ -3065,7 +3182,9 @@ function CTLD:AddZone(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) + table.insert(self.shipZones,zone) + elseif zone.type == CTLD.CargoZoneType.BEACON then + table.insert(self.droppedBeacons,zone) else table.insert(self.wpZones,zone) end @@ -3078,7 +3197,7 @@ end -- @param #CTLD.CargoZoneType 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") + self:T(self.lid .. " ActivateZone") local newstate = true -- set optional in case we\'re deactivating if NewState ~= nil then @@ -3113,7 +3232,7 @@ end -- @param #string Name Name of the zone to change in the ME. -- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to. function CTLD:DeactivateZone(Name,ZoneType) - self:T(self.lid .. " AddZone") + self:T(self.lid .. " DeactivateZone") self:ActivateZone(Name,ZoneType,false) return self end @@ -3180,7 +3299,7 @@ function CTLD:_GetVHFBeacon(Name) end ---- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. +--- User function - Creates 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). @@ -3222,6 +3341,91 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Ship return self end +--- User function - Creates and adds a #CTLD.CargoZone zone for this CTLD instance from an Airbase or FARP name. +-- 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 AirbaseName Name of the Airbase, can be e.g. AIRBASE.Caucasus.Beslan or "Beslan". For FARPs, this will be the UNIT name. +-- @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:AddCTLDZoneFromAirbase(AirbaseName, Type, Color, Active, HasBeacon) + self:T(self.lid .. " AddCTLDZoneFromAirbase") + local AFB = AIRBASE:FindByName(AirbaseName) + local name = AFB:GetZone():GetName() + self:T(self.lid .. "AFB " .. AirbaseName .. " ZoneName " .. name) + self:AddCTLDZone(name, Type, Color, Active, HasBeacon) + return self +end + +--- (Internal) Function to create a dropped beacon +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:DropBeaconNow(Unit) + self:T(self.lid .. " DropBeaconNow") + + local ctldzone = {} -- #CTLD.CargoZone + ctldzone.active = true + ctldzone.color = math.random(0,4) -- random color + ctldzone.name = "Beacon " .. math.random(1,10000) + ctldzone.type = CTLD.CargoZoneType.BEACON -- #CTLD.CargoZoneType + ctldzone.hasbeacon = true + + ctldzone.fmbeacon = self:_GetFMBeacon(ctldzone.name) + ctldzone.uhfbeacon = self:_GetUHFBeacon(ctldzone.name) + ctldzone.vhfbeacon = self:_GetVHFBeacon(ctldzone.name) + ctldzone.timestamp = timer.getTime() + + self.droppedbeaconref[ctldzone.name] = Unit:GetCoordinate() + + self:AddZone(ctldzone) + + local FMbeacon = ctldzone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = ctldzone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = ctldzone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = ctldzone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency * 1000 -- KHz + local UHF = UHFbeacon.frequency -- MHz + local text = string.format("Dropped %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF) + + self:_SendMessage(text,15,false,Unit:GetGroup()) + + return self +end + +--- (Internal) Housekeeping dropped beacons. +-- @param #CTLD self +-- @return #CTLD self +function CTLD:CheckDroppedBeacons() + self:T(self.lid .. " CheckDroppedBeacons") + + -- check for timeout + local timeout = self.droppedbeacontimeout or 600 + local livebeacontable = {} + + for _,_beacon in pairs (self.droppedBeacons) do + local beacon = _beacon -- #CTLD.CargoZone + local T0 = beacon.timestamp + if timer.getTime() - T0 > timeout then + local name = beacon.name + self.droppedbeaconref[name] = nil + _beacon = nil + else + table.insert(livebeacontable,beacon) + end + end + + self.droppedBeacons = nil + self.droppedBeacons = livebeacontable + + return self +end + --- (Internal) Function to show list of radio beacons -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -3230,8 +3434,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, [4] = self.shipZones} - for i=1,4 do + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones, [5] = self.droppedBeacons} + for i=1,5 do for index,cargozone in pairs(zones[i]) do -- Get Beacon object from zone local czone = cargozone -- #CTLD.CargoZone @@ -3262,16 +3466,28 @@ end -- @param #number Mhz Frequency in Mhz. -- @param #number Modulation Modulation AM or FM. -- @param #boolean IsShip If true zone is a ship. -function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip) +-- @param #boolean IsDropped If true, this isn't a zone but a dropped beacon +function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) self:T(self.lid .. " _AddRadioBeacon") local Zone = nil if IsShip then Zone = UNIT:FindByName(Name) + elseif IsDropped then + Zone = self.droppedbeaconref[Name] else Zone = ZONE:FindByName(Name) + if not Zone then + Zone = AIRBASE:FindByName(Name):GetZone() + end end local Sound = Sound or "beacon.ogg" - if Zone then + if IsDropped and Zone then + local ZoneCoord = Zone + 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 + elseif Zone then local ZoneCoord = Zone:GetCoordinate() local ZoneVec3 = ZoneCoord:GetVec3() local Frequency = Mhz * 1000000 -- Freq in Hertz @@ -3286,10 +3502,12 @@ end function CTLD:_RefreshRadioBeacons() self:T(self.lid .. " _RefreshRadioBeacons") - local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones} - for i=1,4 do + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones, [5] = self.droppedBeacons} + for i=1,5 do local IsShip = false if i == 4 then IsShip = true end + local IsDropped = false + if i == 5 then IsDropped = true end for index,cargozone in pairs(zones[i]) do -- Get Beacon object from zone local czone = cargozone -- #CTLD.CargoZone @@ -3302,9 +3520,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, IsShip) - self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip) - self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip) + self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM, IsShip, IsDropped) + self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip, IsDropped) + self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip, IsDropped) end end end @@ -3357,8 +3575,15 @@ function CTLD:IsUnitInZone(Unit,Zonetype) zonecoord = zone:GetCoordinate() zoneradius = czone.shiplength zonewidth = czone.shipwidth - else + elseif ZONE:FindByName(zonename) then zone = ZONE:FindByName(zonename) + self:T("Checking Zone: "..zonename) + zonecoord = zone:GetCoordinate() + zoneradius = zone:GetRadius() + zonewidth = zoneradius + elseif AIRBASE:FindByName(zonename) then + zone = AIRBASE:FindByName(zonename):GetZone() + self:T("Checking Zone: "..zonename) zonecoord = zone:GetCoordinate() zoneradius = zone:GetRadius() zonewidth = zoneradius @@ -3386,9 +3611,13 @@ end -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The Unit. -- @param #boolean Flare If true, flare instead. -function CTLD:SmokePositionNow(Unit, Flare) +-- @param #number SmokeColor Color enumerator for smoke, e.g. SMOKECOLOR.Red +function CTLD:SmokePositionNow(Unit, Flare, SmokeColor) self:T(self.lid .. " SmokePositionNow") - local SmokeColor = self.SmokeColor or SMOKECOLOR.Red + local Smokecolor = self.SmokeColor or SMOKECOLOR.Red + if SmokeColor then + Smokecolor = SmokeColor + end local FlareColor = self.FlareColor or FLARECOLOR.Red -- table of #CTLD.CargoZone table local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE @@ -3398,7 +3627,7 @@ function CTLD:SmokePositionNow(Unit, Flare) else local height = unitcoord:GetLandHeight() + 2 unitcoord.y = height - unitcoord:Smoke(SmokeColor) + unitcoord:Smoke(Smokecolor) end return self end @@ -3424,6 +3653,9 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) zone = UNIT:FindByName(zonename) else zone = ZONE:FindByName(zonename) + if not zone then + zone = AIRBASE:FindByName(zonename):GetZone() + end end local zonecoord = zone:GetCoordinate() local active = CZone.active @@ -3459,7 +3691,8 @@ end -- @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) + -- @param #number Maxcargoweight Maxmimum weight in kgs this helo can carry. Default 0. + function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length, Maxcargoweight) self:T(self.lid .. " UnitCapabilities") local unittype = nil local unit = nil @@ -3479,6 +3712,7 @@ end capabilities.cratelimit = Cratelimit or 0 capabilities.trooplimit = Trooplimit or 0 capabilities.length = Length or 20 + capabilities.cargoweightlimit = Maxcargoweight or 0 self.UnitTypes[unittype] = capabilities return self end @@ -3983,6 +4217,7 @@ end self:T({From, Event, To}) self:CleanDroppedTroops() self:_RefreshF10Menus() + self:CheckDroppedBeacons() self:_RefreshRadioBeacons() self:CheckAutoHoverload() self:_CheckEngineers() @@ -4445,7 +4680,7 @@ end 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 + -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=SubCategory local groupname = dataset[1] local vec2 = {} vec2.x = tonumber(dataset[2]) @@ -4494,6 +4729,655 @@ end return self end end -- end do + +do +--- Hercules Cargo Drop Events by Anubis Yinepu +-- Moose CTLD OO refactoring by Applevangelist +-- +-- This script will only work for the Herculus mod by Anubis +-- Payloads carried by pylons 11, 12 and 13 need to be declared in the Herculus_Loadout.lua file +-- Except for Ammo pallets, this script will spawn whatever payload gets launched from pylons 11, 12 and 13 +-- Pylons 11, 12 and 13 are moveable within the Herculus cargobay area +-- Ammo pallets can only be jettisoned from these pylons with no benefit to DCS world +-- To benefit DCS world, Ammo pallets need to be off/on loaded using DCS arming and refueling window +-- Cargo_Container_Enclosed = true: Cargo enclosed in container with parachute, need to be dropped from 100m (300ft) or more, except when parked on ground +-- Cargo_Container_Enclosed = false: Open cargo with no parachute, need to be dropped from 10m (30ft) or less + +------------------------------------------------------ +--- **CTLD_HERCULES** class, extends Core.Base#BASE +-- @type CTLD_HERCULES +-- @field #string ClassName +-- @field #string lid +-- @field #string Name +-- @field #string Version +-- @extends Core.Base#BASE +CTLD_HERCULES = { + ClassName = "CTLD_HERCULES", + lid = "", + Name = "", + Version = "0.0.1", +} + +--- Define cargo types. +-- @type CTLD_HERCULES.Types +-- @field #table Type Name of cargo type, container (boolean) in container or not. +CTLD_HERCULES.Types = { + ["ATGM M1045 HMMWV TOW Air [7183lb]"] = {['name'] = "M1045 HMMWV TOW", ['container'] = true}, + ["ATGM M1045 HMMWV TOW Skid [7073lb]"] = {['name'] = "M1045 HMMWV TOW", ['container'] = false}, + ["APC M1043 HMMWV Armament Air [7023lb]"] = {['name'] = "M1043 HMMWV Armament", ['container'] = true}, + ["APC M1043 HMMWV Armament Skid [6912lb]"] = {['name'] = "M1043 HMMWV Armament", ['container'] = false}, + ["SAM Avenger M1097 Air [7200lb]"] = {['name'] = "M1097 Avenger", ['container'] = true}, + ["SAM Avenger M1097 Skid [7090lb]"] = {['name'] = "M1097 Avenger", ['container'] = false}, + ["APC Cobra Air [10912lb]"] = {['name'] = "Cobra", ['container'] = true}, + ["APC Cobra Skid [10802lb]"] = {['name'] = "Cobra", ['container'] = false}, + ["APC M113 Air [21624lb]"] = {['name'] = "M-113", ['container'] = true}, + ["APC M113 Skid [21494lb]"] = {['name'] = "M-113", ['container'] = false}, + ["Tanker M978 HEMTT [34000lb]"] = {['name'] = "M978 HEMTT Tanker", ['container'] = false}, + ["HEMTT TFFT [34400lb]"] = {['name'] = "HEMTT TFFT", ['container'] = false}, + ["SPG M1128 Stryker MGS [33036lb]"] = {['name'] = "M1128 Stryker MGS", ['container'] = false}, + ["AAA Vulcan M163 Air [21666lb]"] = {['name'] = "Vulcan", ['container'] = true}, + ["AAA Vulcan M163 Skid [21577lb]"] = {['name'] = "Vulcan", ['container'] = false}, + ["APC M1126 Stryker ICV [29542lb]"] = {['name'] = "M1126 Stryker ICV", ['container'] = false}, + ["ATGM M1134 Stryker [30337lb]"] = {['name'] = "M1134 Stryker ATGM", ['container'] = false}, + ["APC LAV-25 Air [22520lb]"] = {['name'] = "LAV-25", ['container'] = true}, + ["APC LAV-25 Skid [22514lb]"] = {['name'] = "LAV-25", ['container'] = false}, + ["M1025 HMMWV Air [6160lb]"] = {['name'] = "Hummer", ['container'] = true}, + ["M1025 HMMWV Skid [6050lb]"] = {['name'] = "Hummer", ['container'] = false}, + ["IFV M2A2 Bradley [34720lb]"] = {['name'] = "M-2 Bradley", ['container'] = false}, + ["IFV MCV-80 [34720lb]"] = {['name'] = "MCV-80", ['container'] = false}, + ["IFV BMP-1 [23232lb]"] = {['name'] = "BMP-1", ['container'] = false}, + ["IFV BMP-2 [25168lb]"] = {['name'] = "BMP-2", ['container'] = false}, + ["IFV BMP-3 [32912lb]"] = {['name'] = "BMP-3", ['container'] = false}, + ["ARV BRDM-2 Air [12320lb]"] = {['name'] = "BRDM-2", ['container'] = true}, + ["ARV BRDM-2 Skid [12210lb]"] = {['name'] = "BRDM-2", ['container'] = false}, + ["APC BTR-80 Air [23936lb]"] = {['name'] = "BTR-80", ['container'] = true}, + ["APC BTR-80 Skid [23826lb]"] = {['name'] = "BTR-80", ['container'] = false}, + ["APC BTR-82A Air [24998lb]"] = {['name'] = "BTR-82A", ['container'] = true}, + ["APC BTR-82A Skid [24888lb]"] = {['name'] = "BTR-82A", ['container'] = false}, + ["SAM ROLAND ADS [34720lb]"] = {['name'] = "Roland Radar", ['container'] = false}, + ["SAM ROLAND LN [34720b]"] = {['name'] = "Roland ADS", ['container'] = false}, + ["SAM SA-13 STRELA [21624lb]"] = {['name'] = "Strela-10M3", ['container'] = false}, + ["AAA ZSU-23-4 Shilka [32912lb]"] = {['name'] = "ZSU-23-4 Shilka", ['container'] = false}, + ["SAM SA-19 Tunguska 2S6 [34720lb]"] = {['name'] = "2S6 Tunguska", ['container'] = false}, + ["Transport UAZ-469 Air [3747lb]"] = {['name'] = "UAZ-469", ['container'] = true}, + ["Transport UAZ-469 Skid [3630lb]"] = {['name'] = "UAZ-469", ['container'] = false}, + ["AAA GEPARD [34720lb]"] = {['name'] = "Gepard", ['container'] = false}, + ["SAM CHAPARRAL Air [21624lb]"] = {['name'] = "M48 Chaparral", ['container'] = true}, + ["SAM CHAPARRAL Skid [21516lb]"] = {['name'] = "M48 Chaparral", ['container'] = false}, + ["SAM LINEBACKER [34720lb]"] = {['name'] = "M6 Linebacker", ['container'] = false}, + ["Transport URAL-375 [14815lb]"] = {['name'] = "Ural-375", ['container'] = false}, + ["Transport M818 [16000lb]"] = {['name'] = "M 818", ['container'] = false}, + ["IFV MARDER [34720lb]"] = {['name'] = "Marder", ['container'] = false}, + ["Transport Tigr Air [15900lb]"] = {['name'] = "Tigr_233036", ['container'] = true}, + ["Transport Tigr Skid [15730lb]"] = {['name'] = "Tigr_233036", ['container'] = false}, + ["IFV TPZ FUCH [33440lb]"] = {['name'] = "TPZ", ['container'] = false}, + ["IFV BMD-1 Air [18040lb]"] = {['name'] = "BMD-1", ['container'] = true}, + ["IFV BMD-1 Skid [17930lb]"] = {['name'] = "BMD-1", ['container'] = false}, + ["IFV BTR-D Air [18040lb]"] = {['name'] = "BTR_D", ['container'] = true}, + ["IFV BTR-D Skid [17930lb]"] = {['name'] = "BTR_D", ['container'] = false}, + ["EWR SBORKA Air [21624lb]"] = {['name'] = "Dog Ear radar", ['container'] = true}, + ["EWR SBORKA Skid [21624lb]"] = {['name'] = "Dog Ear radar", ['container'] = false}, + ["ART 2S9 NONA Air [19140lb]"] = {['name'] = "SAU 2-C9", ['container'] = true}, + ["ART 2S9 NONA Skid [19030lb]"] = {['name'] = "SAU 2-C9", ['container'] = false}, + ["ART GVOZDIKA [34720lb]"] = {['name'] = "SAU Gvozdika", ['container'] = false}, + ["APC MTLB Air [26400lb]"] = {['name'] = "MTLB", ['container'] = true}, + ["APC MTLB Skid [26290lb]"] = {['name'] = "MTLB", ['container'] = false}, + ["Generic Crate [20000lb]"] = {['name'] = "Hercules_Container_Parachute", ['container'] = true} --nothing generic in Moose CTLD +} + +--- Cargo Object +-- @type CTLD_HERCULES.CargoObject +-- @field #number Cargo_Drop_Direction +-- @field #table Cargo_Contents +-- @field #string Cargo_Type_name +-- @field #boolean Container_Enclosed +-- @field #boolean ParatrooperGroupSpawn +-- @field #number Cargo_Country +-- @field #boolean offload_cargo +-- @field #boolean all_cargo_survive_to_the_ground +-- @field #boolean all_cargo_gets_destroyed +-- @field #boolean destroy_cargo_dropped_without_parachute +-- @field Core.Timer#TIMER scheduleFunctionID + +--- [User] Instantiate a new object +-- @param #CTLD_HERCULES self +-- @param #string Coalition Coalition side, "red", "blue" or "neutral" +-- @param #string Alias Name of this instance +-- @param Ops.CTLD#CTLD CtldObject CTLD instance to link into +-- @return #CTLD_HERCULES self +-- @usage +-- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: +-- +-- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) +-- +-- You also need: +-- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate). +-- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable. +-- There's a **quick check output in the `dcs.log`** which tells you what's there and what not. +-- E.g.: +-- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING) +-- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING) +-- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING) +-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK) +-- +-- Expected template names are the ones in the rounded brackets. +function CTLD_HERCULES:New(Coalition, Alias, CtldObject) + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CTLD_HERCULES + + --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 Hercules" + elseif self.coalition==coalition.side.BLUE then + self.alias="Blue CTLD Hercules" + end + end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalitiontxt) + + self.infantrytemplate = "Infantry" -- template for a group of 10 paratroopers + self.CTLD = CtldObject -- Ops.CTLD#CTLD + + self.verbose = true + + self.j = 0 + self.carrierGroups = {} + self.Cargo = {} + self.ParatrooperCount = {} + + self.ObjectTracker = {} + + -- 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") + + --self:HandleEvent(EVENTS.Birth,self._HandleBirth) + self:HandleEvent(EVENTS.Shot, self._HandleShot) + + self:I(self.lid .. "Started") + + self:CheckTemplates() + + return self +end + +--- [Internal] Function to check availability of templates +-- @param #CTLD_HERCULES self +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:CheckTemplates() + self:T(self.lid .. 'CheckTemplates') + -- inject Paratroopers + self.Types["Paratroopers 10"] = { + name = self.infantrytemplate, + container = false, + available = false, + } + local missing = {} + local nomissing = 0 + local found = {} + local nofound = 0 + + -- list of groundcrew loadables + for _index,_tab in pairs (self.Types) do + local outcometxt = "MISSING" + if _DATABASE.Templates.Groups[_tab.name] then + outcometxt = "OK" + self.Types[_index].available= true + found[_tab.name] = true + else + self.Types[_index].available = false + missing[_tab.name] = true + end + if self.verbose then + self:I(string.format(self.lid .. "Checking template for %s (%s) ... %s", _index,_tab.name,outcometxt)) + end + end + for _,_name in pairs(found) do + nofound = nofound + 1 + end + for _,_name in pairs(missing) do + nomissing = nomissing + 1 + end + self:I(string.format(self.lid .. "Template Check Summary: Found %d, Missing %d, Total %d",nofound,nomissing,nofound+nomissing)) + return self +end + +--- [Internal] Function to spawn a soldier group of 10 units +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param #string Cargo_Type_name +-- @param #number CargoHeading +-- @param #number Cargo_Country +-- @param #number GroupSpacing +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, Cargo_Country, GroupSpacing) + --- TODO: Rework into Moose Spawns + self:T(self.lid .. 'Soldier_SpawnGroup') + self:T(Cargo_Drop_Position) + -- create a matching #CTLD_CARGO type + local InjectTroopsType = CTLD_CARGO:New(nil,self.infantrytemplate,{self.infantrytemplate},CTLD_CARGO.Enum.TROOPS,true,true,10,nil,false,80) + -- get a #ZONE object + local position = Cargo_Drop_Position:GetVec2() + local dropzone = ZONE_RADIUS:New("Infantry " .. math.random(1,10000),position,100) + -- and go: + self.CTLD:InjectTroops(dropzone,InjectTroopsType) + return self +end + +--- [Internal] Function to spawn a group +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param #string Cargo_Type_name +-- @param #number CargoHeading +-- @param #number Cargo_Country +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, Cargo_Country) + --- TODO: Rework into Moose Spawns + self:T(self.lid .. "Cargo_SpawnGroup") + self:T(Cargo_Type_name) + if Cargo_Type_name ~= 'Container red 1' then + -- create a matching #CTLD_CARGO type + local InjectVehicleType = CTLD_CARGO:New(nil,Cargo_Type_name,{Cargo_Type_name},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000) + -- get a #ZONE object + local position = Cargo_Drop_Position:GetVec2() + local dropzone = ZONE_RADIUS:New("Vehicle " .. math.random(1,10000),position,100) + -- and go: + self.CTLD:InjectVehicles(dropzone,InjectVehicleType) + end + return self +end + +--- [Internal] Function to spawn static cargo +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param #string Cargo_Type_name +-- @param #number CargoHeading +-- @param #boolean dead +-- @param #number Cargo_Country +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, dead, Cargo_Country) + --- TODO: Rework into Moose Static Spawns + self:T(self.lid .. "Cargo_SpawnStatic") + self:T("Static " .. Cargo_Type_name .. " Dead " .. tostring(dead)) + local position = Cargo_Drop_Position:GetVec2() + local Zone = ZONE_RADIUS:New("Cargo Static " .. math.random(1,10000),position,100) + if not dead then + -- CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock) + local injectstatic = CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1) + self.CTLD:InjectStatics(Zone,injectstatic,true) + else + --local static = SPAWNSTATIC:NewFromType("iso_container","Cargos",Cargo_Country) + --static.InitDead = true + --static:SpawnFromZone(Zone,CargoHeading) + end + return self +end + +--- [Internal] Spawn cargo objects +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param #number Cargo_Drop_Direction +-- @param Core.Point#COORDINATE Cargo_Content_position +-- @param #string Cargo_Type_name +-- @param #boolean Cargo_over_water +-- @param #boolean Container_Enclosed +-- @param #boolean ParatrooperGroupSpawn +-- @param #boolean offload_cargo +-- @param #boolean all_cargo_survive_to_the_ground +-- @param #boolean all_cargo_gets_destroyed +-- @param #boolean destroy_cargo_dropped_without_parachute +-- @param #number Cargo_Country +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direction, Cargo_Content_position, Cargo_Type_name, Cargo_over_water, Container_Enclosed, ParatrooperGroupSpawn, offload_cargo, all_cargo_survive_to_the_ground, all_cargo_gets_destroyed, destroy_cargo_dropped_without_parachute, Cargo_Country) + self:T(self.lid .. 'Cargo_SpawnObjects') + + local CargoHeading = self.CargoHeading + --local Cargo_Drop_Position = {} + + if offload_cargo == true or ParatrooperGroupSpawn == true then + if ParatrooperGroupSpawn == true then + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5) + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10) + else + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) + end + else + if all_cargo_gets_destroyed == true or Cargo_over_water == true then + if Container_Enclosed == true then + --self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + if ParatrooperGroupSpawn == false then + --self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, true, Cargo_Country) + end + else + --self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + end + else + if all_cargo_survive_to_the_ground == true then + if ParatrooperGroupSpawn == true then + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + else + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) + end + if Container_Enclosed == true then + if ParatrooperGroupSpawn == false then + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country) + end + end + end + if destroy_cargo_dropped_without_parachute == true then + if Container_Enclosed == true then + if ParatrooperGroupSpawn == true then + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) + else + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country) + end + else + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + end + end + end + end + return self +end + +--- [Internal] Function to calculate object height +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP group The group for which to calculate the height +-- @return #number height over ground +function CTLD_HERCULES:Calculate_Object_Height_AGL(group) + self:T(self.lid .. "Calculate_Object_Height_AGL") + if group.ClassName and group.ClassName == "GROUP" then + local gcoord = group:GetCoordinate() + local height = group:GetHeight() + local lheight = gcoord:GetLandHeight() + self:T(self.lid .. "Height " .. height - lheight) + return height - lheight + else + -- DCS object + --self:T({group}) + if group:isExist() then + local dcsposition = group:getPosition().p + local dcsvec2 = {x = dcsposition.x, y = dcsposition.z} -- Vec2 + local height = math.floor(group:getPosition().p.y - land.getHeight(dcsvec2)) + self.ObjectTracker[group.id_] = dcsposition -- Vec3 + self:T(self.lid .. "Height " .. height) + --self:T({group.id_,self.ObjectTracker[group.id_]}) + return height + else + return 0 + end + end +end + +--- [Internal] Function to check surface type +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP group The group for which to calculate the height +-- @return #number height over ground +function CTLD_HERCULES:Check_SurfaceType(object) + self:T(self.lid .. "Check_SurfaceType") + -- LAND,--1 SHALLOW_WATER,--2 WATER,--3 ROAD,--4 RUNWAY--5 + if object:isExist() then + return land.getSurfaceType({x = object:getPosition().p.x, y = object:getPosition().p.z}) + else + return 1 + end +end + +--- [Internal] Function to track cargo objects +-- @param #CTLD_HERCULES self +-- @param #CTLD_HERCULES.CargoObject cargo +-- @param Wrapper.Group#GROUP initiator +-- @return #number height over ground +function CTLD_HERCULES:Cargo_Track(cargo, initiator) + self:T(self.lid .. "Cargo_Track") + local Cargo_Drop_initiator = initiator + if cargo.Cargo_Contents ~= nil then + if self:Calculate_Object_Height_AGL(cargo.Cargo_Contents) < 10 then --pallet less than 5m above ground before spawning + if self:Check_SurfaceType(cargo.Cargo_Contents) == 2 or self:Check_SurfaceType(cargo.Cargo_Contents) == 3 then + cargo.Cargo_over_water = true--pallets gets destroyed in water + end + local dcsvec3 = self.ObjectTracker[cargo.Cargo_Contents.id_] -- last known position + self:T("SPAWNPOSITION: ") + self:T({dcsvec3}) + local Vec2 = { + x=dcsvec3.x, + y=dcsvec3.z, + } + local vec3 = COORDINATE:NewFromVec2(Vec2) + self.ObjectTracker[cargo.Cargo_Contents.id_] = nil + self:Cargo_SpawnObjects(Cargo_Drop_initiator,cargo.Cargo_Drop_Direction, vec3, cargo.Cargo_Type_name, cargo.Cargo_over_water, cargo.Container_Enclosed, cargo.ParatrooperGroupSpawn, cargo.offload_cargo, cargo.all_cargo_survive_to_the_ground, cargo.all_cargo_gets_destroyed, cargo.destroy_cargo_dropped_without_parachute, cargo.Cargo_Country) + if cargo.Cargo_Contents:isExist() then + cargo.Cargo_Contents:destroy()--remove pallet+parachute before hitting ground and replace with Cargo_SpawnContents + end + --timer.removeFunction(cargo.scheduleFunctionID) + cargo.scheduleFunctionID:Stop() + cargo = {} + end + end + return self +end + +--- [Internal] Function to calc north correction +-- @param #CTLD_HERCULES self +-- @param Core.Point#POINT_Vec3 point Position Vec3 +-- @return #number north correction +function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_NorthCorrection(point) + self:T(self.lid .. "Calculate_Cargo_Drop_initiator_NorthCorrection") + if not point.z then --Vec2; convert to Vec3 + point.z = point.y + point.y = 0 + end + local lat, lon = coord.LOtoLL(point) + local north_posit = coord.LLtoLO(lat + 1, lon) + return math.atan2(north_posit.z - point.z, north_posit.x - point.x) +end + +--- [Internal] Function to calc initiator heading +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @return #number north corrected heading +function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_Heading(Cargo_Drop_initiator) + self:T(self.lid .. "Calculate_Cargo_Drop_initiator_Heading") + local Heading = Cargo_Drop_initiator:GetHeading() + Heading = Heading + self:Calculate_Cargo_Drop_initiator_NorthCorrection(Cargo_Drop_initiator:GetVec3()) + if Heading < 0 then + Heading = Heading + (2 * math.pi)-- put heading in range of 0 to 2*pi + end + return Heading + 0.06 -- rad +end + +--- [Internal] Function to initialize dropped cargo +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Initiator +-- @param #table Cargo_Contents Table 'weapon' from event data +-- @param #string Cargo_Type_name Name of this cargo +-- @param #boolean Container_Enclosed Is container? +-- @param #boolean SoldierGroup Is soldier group? +-- @param #boolean ParatrooperGroupSpawnInit Is paratroopers? +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_Initialize(Initiator, Cargo_Contents, Cargo_Type_name, Container_Enclosed, SoldierGroup, ParatrooperGroupSpawnInit) + self:T(self.lid .. "Cargo_Initialize") + local Cargo_Drop_initiator = Initiator:GetName() + if Cargo_Drop_initiator ~= nil then + if ParatrooperGroupSpawnInit == true then + self:T("Paratrooper Drop") + -- Paratroopers + if not self.ParatrooperCount[Cargo_Drop_initiator] then + self.ParatrooperCount[Cargo_Drop_initiator] = 1 + else + self.ParatrooperCount[Cargo_Drop_initiator] = self.ParatrooperCount[Cargo_Drop_initiator] + 1 + end + + local Paratroopers = self.ParatrooperCount[Cargo_Drop_initiator] + + self:T("Paratrooper Drop Number " .. self.ParatrooperCount[Cargo_Drop_initiator]) + + local SpawnParas = false + + if math.fmod(Paratroopers,10) == 0 then + SpawnParas = true + end + + self.j = self.j + 1 + self.Cargo[self.j] = {} + self.Cargo[self.j].Cargo_Drop_Direction = self:Calculate_Cargo_Drop_initiator_Heading(Initiator) + self.Cargo[self.j].Cargo_Contents = Cargo_Contents + self.Cargo[self.j].Cargo_Type_name = Cargo_Type_name + self.Cargo[self.j].Container_Enclosed = Container_Enclosed + self.Cargo[self.j].ParatrooperGroupSpawn = SpawnParas + self.Cargo[self.j].Cargo_Country = Initiator:GetCountry() + + if self:Calculate_Object_Height_AGL(Initiator) < 5.0 then --aircraft on ground + self.Cargo[self.j].offload_cargo = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 10.0 then --aircraft less than 10m above ground + self.Cargo[self.j].all_cargo_survive_to_the_ground = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 100.0 then --aircraft more than 10m but less than 100m above ground + self.Cargo[self.j].all_cargo_gets_destroyed = true + else + self.Cargo[self.j].all_cargo_gets_destroyed = false + end + + local timer = TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) + self.Cargo[self.j].scheduleFunctionID = timer + timer:Start(5,2,600) + + else + -- no paras + self.j = self.j + 1 + self.Cargo[self.j] = {} + self.Cargo[self.j].Cargo_Drop_Direction = self:Calculate_Cargo_Drop_initiator_Heading(Initiator) + self.Cargo[self.j].Cargo_Contents = Cargo_Contents + self.Cargo[self.j].Cargo_Type_name = Cargo_Type_name + self.Cargo[self.j].Container_Enclosed = Container_Enclosed + self.Cargo[self.j].ParatrooperGroupSpawn = false + self.Cargo[self.j].Cargo_Country = Initiator:GetCountry() + + if self:Calculate_Object_Height_AGL(Initiator) < 5.0 then--aircraft on ground + self.Cargo[self.j].offload_cargo = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 10.0 then--aircraft less than 10m above ground + self.Cargo[self.j].all_cargo_survive_to_the_ground = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 100.0 then--aircraft more than 10m but less than 100m above ground + self.Cargo[self.j].all_cargo_gets_destroyed = true + else + self.Cargo[self.j].destroy_cargo_dropped_without_parachute = true --aircraft more than 100m above ground + end + + local timer = TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) + self.Cargo[self.j].scheduleFunctionID = timer + timer:Start(5,2,600) + end + end + return self +end + +--- [Internal] Function to change cargotype per group (Wrench) +-- @param #CTLD_HERCULES self +-- @param #number key Carrier key id +-- @param #string cargoType Type of cargo +-- @param #number cargoNum Number of cargo objects +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:SetType(key,cargoType,cargoNum) + self:T(self.lid .. "SetType") + self.carrierGroups[key]['cargoType'] = cargoType + self.carrierGroups[key]['cargoNum'] = cargoNum + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- EventHandlers +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +--- [Internal] Function to capture SHOT event +-- @param #CTLD_HERCULES self +-- @param Core.Event#EVENTDATA Cargo_Drop_Event The event data +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:_HandleShot(Cargo_Drop_Event) + self:T(self.lid .. "Shot Event ID:" .. Cargo_Drop_Event.id) + if Cargo_Drop_Event.id == EVENTS.Shot then + + local GT_Name = "" + local SoldierGroup = false + local ParatrooperGroupSpawnInit = false + + local GT_DisplayName = Weapon.getDesc(Cargo_Drop_Event.weapon).typeName:sub(15, -1)--Remove "weapons.bombs." from string + self:T(string.format("%sCargo_Drop_Event: %s", self.lid, Weapon.getDesc(Cargo_Drop_Event.weapon).typeName)) + + if (GT_DisplayName == "Squad 30 x Soldier [7950lb]") then + self:Cargo_Initialize(Cargo_Drop_Event.IniGroup, Cargo_Drop_Event.weapon, "Soldier M4 GRG", false, true, true) + end + + if self.Types[GT_DisplayName] then + local GT_Name = self.Types[GT_DisplayName]['name'] + local Cargo_Container_Enclosed = self.Types[GT_DisplayName]['container'] + self:Cargo_Initialize(Cargo_Drop_Event.IniGroup, Cargo_Drop_Event.weapon, GT_Name, Cargo_Container_Enclosed) + end + end + return self +end + +--- [Internal] Function to capture BIRTH event +-- @param #CTLD_HERCULES self +-- @param Core.Event#EVENTDATA event The event data +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:_HandleBirth(event) + -- not sure what this is needed for? I think this for setting generic crates "content" setting. + self:T(self.lid .. "Birth Event ID:" .. event.id) + --[[ + if event.id == EVENTS.Birth then + local desc = event.initiator:getDesc() + if desc["displayName"] == "Hercules" then + local grpTab = {} + grpTab['object'] = event.IniGroup + grpTab['name'] = event.IniGroupName + grpTab['cargoType'] = 'Container red 1' + grpTab['cargoNum'] = 1 + grpTab['key'] = #self.carrierGroups + 1 + + table.insert(self.carrierGroups,grpTab) + + local hercCargoMenu = MENU_GROUP:New(event.IniGroup,"CargoTypes",nil) + local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"MLRS",hercCargoMenu,self.SetType,self,grpTab['key'],'MLRS',1) + local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"Mortar",hercCargoMenu,self.SetType,self,grpTab['key'],'2B11 mortar',8) + local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"M-109",hercCargoMenu,self.SetType,self,grpTab['key'],'M-109',1) + local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"FOB Crate",hercCargoMenu,self.SetType,self,grpTab['key'],'Container red 1',1) + end + end + --]] + return self +end + +end + ------------------------------------------------------------------- -- End Ops.CTLD.lua ------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index e802a0f87..b5f624d52 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -400,7 +400,7 @@ function OPSZONE:SetMarkZone(Switch, ReadOnly) self.marker:Remove() end self.marker=nil - self.marker=false + --self.marker=false end return self end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 7aa8bcd75..8b7ebb2b6 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1,4 +1,4 @@ ---- This module contains derived utilities taken from the MIST framework, which are excellent tools to be reused in an OO environment. +--- This module contains derived utilities taken from the MIST framework, as well as a lot of added helpers from the MOOSE community. -- -- ### Authors: -- @@ -7,6 +7,7 @@ -- ### Contributions: -- -- * FlightControl : Rework to OO framework. +-- * And many more -- -- @module Utils -- @image MOOSE.JPG @@ -130,6 +131,62 @@ CALLSIGN={ Dublin=9, Perth=10, }, + F16={ + Viper=9, + Venom=10, + Lobo=11, + Cowboy=12, + Python=13, + Rattler=14, + Panther=15, + Wolf=16, + Weasel=17, + Wild=18, + Ninja=19, + Jedi=20, + }, + F18={ + Hornet=9, + Squid=10, + Ragin=11, + Roman=12, + Sting=13, + Jury=14, + Jokey=15, + Ram=16, + Hawk=17, + Devil=18, + Check=19, + Snake=20, + }, + F15E={ + Dude=9, + Thud=10, + Gunny=11, + Trek=12, + Sniper=13, + Sled=14, + Best=15, + Jazz=16, + Rage=17, + Tahoe=18, + }, + B1B={ + Bone=9, + Dark=10, + Vader=11 + }, + B52={ + Buff=9, + Dump=10, + Kenworth=11, + }, + TransportAircraft={ + Heavy=9, + Trash=10, + Cargo=11, + Ascot=12, + }, } --#CALLSIGN --- Utilities static class. @@ -1683,10 +1740,21 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " door is open") ret_val = true end + + if string.find(type_name, "UH-60L") and (unit:getDrawArgumentValue(401) == 1) or (unit:getDrawArgumentValue(402) == 1) then + BASE:T(unit_name .. " cargo door is open") + ret_val = true + end + if string.find(type_name, "UH-60L" ) and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(400) == 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