diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 18c7986fc..b78ade334 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1607,13 +1607,12 @@ do -- COORDINATE roadtype="railroads" end local x,y = land.getClosestPointOnRoads(roadtype, self.x, self.z) + local coord=nil if x and y then local vec2={ x = x, y = y } - local coord=COORDINATE:NewFromVec2(vec2) - return coord - else - return nil + coord=COORDINATE:NewFromVec2(vec2) end + return coord end @@ -1780,7 +1779,7 @@ do -- COORDINATE Power=Power or 1000 if Delay and Delay>0 then self:ScheduleOnce(Delay, self.IlluminationBomb, self, Power) - else + else trigger.action.illuminationBomb(self:GetVec3(), Power) end return self diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0d4f074c4..1d4e76d2e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -5790,21 +5790,32 @@ end -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @param #table parking Parking data for this asset. -- @param #boolean uncontrolled Spawn aircraft in uncontrolled state. --- @param #boolean hotstart Spawn aircraft with engines already on. Default is a cold start with engines off. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled, hotstart) +function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled) if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then -- Prepare the spawn template. local template=self:_SpawnAssetPrepareTemplate(asset, alias) + -- Cold start (default). + local _type=COORDINATE.WaypointType.TakeOffParking + local _action=COORDINATE.WaypointAction.FromParkingArea + + -- Hot start. + if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then + _type=COORDINATE.WaypointType.TakeOffParkingHot + _action=COORDINATE.WaypointAction.FromParkingAreaHot + uncontrolled=false + end + + -- Set route points. if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then -- Get flight path if the group goes to another warehouse by itself. if request.toself then - local wp=self.airbase:GetCoordinate():WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, 0, false, self.airbase, {}, "Parking") + local wp=self.airbase:GetCoordinate():WaypointAir("RADIO", _type, _action, 0, false, self.airbase, {}, "Parking") template.route.points={wp} else template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase) @@ -5812,18 +5823,8 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol else - -- Cold start (default). - local _type=COORDINATE.WaypointType.TakeOffParking - local _action=COORDINATE.WaypointAction.FromParkingArea - - -- Hot start. - if hotstart then - _type=COORDINATE.WaypointType.TakeOffParkingHot - _action=COORDINATE.WaypointAction.FromParkingAreaHot - end - -- First route point is the warehouse airbase. - template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action, 0, true, self.airbase, nil, "Spawnpoint") + template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO", _type, _action, 0, true, self.airbase, nil, "Spawnpoint") end @@ -9040,9 +9041,23 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) local wp={} local c={} + -- Cold start (default). + local _type=COORDINATE.WaypointType.TakeOffParking + local _action=COORDINATE.WaypointAction.FromParkingArea + + -- Hot start. + if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then + env.info("FF hot") + _type=COORDINATE.WaypointType.TakeOffParkingHot + _action=COORDINATE.WaypointAction.FromParkingAreaHot + else + env.info("FF cold") + end + + --- Departure/Take-off c[#c+1]=Pdeparture - wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb*3.6, true, departure, nil, "Departure") + wp[#wp+1]=Pdeparture:WaypointAir("RADIO", _type, _action, VxClimb*3.6, true, departure, nil, "Departure") --- Begin of Cruise local Pcruise=Pdeparture:Translate(d_climb, heading) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index e97e34888..ebdedcfba 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -86,6 +86,7 @@ __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 60d412343..62207fbb6 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -19,7 +19,7 @@ -- @field #table menu Table of menu items. -- @field #table squadrons Table of squadrons. -- @field #table missionqueue Mission queue table. --- @field #table payloads Playloads for specific aircraft and mission types. +-- @field #table payloads Playloads for specific aircraft and mission types. -- @field #number payloadcounter Running index of payloads. -- @field Core.Set#SET_ZONE zonesetCAP Set of CAP zones. -- @field Core.Set#SET_ZONE zonesetTANKER Set of TANKER zones. @@ -27,16 +27,17 @@ -- @field #number nflightsCAP Number of CAP flights constantly in the air. -- @field #number nflightsAWACS Number of AWACS flights constantly in the air. -- @field #number nflightsTANKERboom Number of TANKER flights with BOOM constantly in the air. --- @field #number nflightsTANKERprobe Number of TANKER flights with PROBE constantly in the air. +-- @field #number nflightsTANKERprobe Number of TANKER flights with PROBE constantly in the air. -- @field #number nflightsRescueHelo Number of Rescue helo flights constantly in the air. -- @field #table pointsCAP Table of CAP points. -- @field #table pointsTANKER Table of Tanker points. -- @field #table pointsAWACS Table of AWACS points. +-- @field #boolean markpoints Display markers on the F10 map. -- @field Ops.WingCommander#WINGCOMMANDER wingcommander The wing commander responsible for this airwing. --- +-- -- @field Ops.RescueHelo#RESCUEHELO rescuehelo The rescue helo. -- @field Ops.RecoveryTanker#RECOVERYTANKER recoverytanker The recoverytanker. --- +-- -- @extends Functional.Warehouse#WAREHOUSE --- Be surprised! @@ -46,62 +47,62 @@ -- ![Banner Image](..\Presentations\OPS\AirWing\_Main.png) -- -- # The AIRWING Concept --- +-- -- An AIRWING consists of multiple SQUADRONS. These squadrons "live" in a WAREHOUSE, i.e. a physical structure that is connected to an airbase (airdrome, FRAP or ship). -- For an airwing to be operational, it needs airframes, weapons/fuel and an airbase. --- +-- -- # Create an Airwing --- +-- -- ## Constructing the Airwing --- +-- -- airwing=AIRWING:New("Warehouse Batumi", "8th Fighter Wing") -- airwing:Start() --- +-- -- The first parameter specified the warehouse, i.e. the static building housing the airwing (or the name of the aircraft carrier). The second parameter is optional -- and sets an alias. --- +-- -- ## Adding Squadrons --- +-- -- At this point the airwing does not have any assets (aircraft). In order to add these, one needs to first define SQUADRONS. --- +-- -- VFA151=SQUADRON:New("F-14 Group", 8, "VFA-151 (Vigilantes)") -- VFA151:AddMissionCapability({AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT}) --- +-- -- airwing:AddSquadron(VFA151) --- +-- -- This adds eight Tomcat groups beloning to VFA-151 to the airwing. This squadron has the ability to perform combat air patrols and intercepts. --- +-- -- ## Adding Payloads --- +-- -- Adding pure airframes is not enough. The aircraft also need weapons (and fuel) for certain missions. These must be given to the airwing from template groups -- defined in the Mission Editor. --- +-- -- -- F-14 payloads for CAP and INTERCEPT. Phoenix are first, sparrows are second choice. -- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-54C"), 2, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCICAP}, 80) -- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-7M"), 20, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCICAP}) --- +-- -- This will add two AIM-54C and 20 AIM-7M payloads. --- +-- -- If the airwing gets an intercept or patrol mission assigned, it will first use the AIM-54s. Once these are consumed, the AIM-7s are attached to the aircraft. --- +-- -- When an airwing does not have a payload for a certain mission type, the mission cannot be carried out. --- +-- -- You can set the number of payloads to "unlimited" by setting its quantity to -1. --- +-- -- # Adding Missions --- +-- -- Various mission types can be added easily via the AUFTRAG class. --- +-- -- Once you created an AUFTRAG you can add it to the AIRWING with the :AddMission(mission) function. --- +-- -- This mission will be put into the AIRWING queue. Once the mission start time is reached and all resources (airframes and pylons) are available, the mission is started. -- If the mission stop time is over (and the mission is not finished), it will be cancelled and removed from the queue. This applies also to mission that were not even -- started. --- +-- -- # Command an Airwing --- +-- -- An airwing can receive missions from a WINGCOMMANDER. See docs of that class for details. --- +-- -- However, you are still free to add missions at anytime. -- -- @@ -198,9 +199,9 @@ function AIRWING:New(warehousename, airwingname) -- From State --> Event --> To State self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. - + self:AddTransition("*", "SquadAssetReturned", "*") -- Flight was spawned with a mission. - + self:AddTransition("*", "FlightOnMission", "*") -- Flight was spawned with a mission. -- Defaults: @@ -211,7 +212,7 @@ function AIRWING:New(warehousename, airwingname) self.nflightsTANKERprobe=0 self.nflightsRecoveryTanker=0 self.nflightsRescueHelo=0 - self.markpoints = false + self.markpoints=false ------------------------ --- Pseudo Functions --- @@ -233,7 +234,7 @@ function AIRWING:New(warehousename, airwingname) -- @function [parent=#AIRWING] __Stop -- @param #AIRWING self -- @param #number delay Delay in seconds. - + --- On after "FlightOnMission" event. Triggered when an asset group starts a mission. -- @function [parent=#AIRWING] OnAfterFlightOnMission -- @param #AIRWING self @@ -242,7 +243,7 @@ function AIRWING:New(warehousename, airwingname) -- @param #string To The To state -- @param Ops.FlightGroup#FLIGHTGROUP Flightgroup The Flightgroup on mission -- @param Ops.Auftrag#AUFTRAG Mission The Auftrag of the Flightgroup - + --- On after "AssetReturned" event. Triggered when an asset group returned to its airwing. -- @function [parent=#AIRWING] OnAfterAssetReturned -- @param #AIRWING self @@ -267,10 +268,10 @@ function AIRWING:AddSquadron(Squadron) -- Add squadron to airwing. table.insert(self.squadrons, Squadron) - + -- Add assets to squadron. self:AddAssetToSquadron(Squadron, Squadron.Ngroups) - + -- Tanker and AWACS get unlimited payloads. if Squadron.attribute==GROUP.Attribute.AIR_AWACS then self:NewPayload(Squadron.templategroup, -1, AUFTRAG.Type.AWACS) @@ -280,7 +281,7 @@ function AIRWING:AddSquadron(Squadron) -- Set airwing to squadron. Squadron:SetAirwing(self) - + -- Start squadron. if Squadron:IsStopped() then Squadron:Start() @@ -315,17 +316,17 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) if Unit:IsInstanceOf("GROUP") then Unit=Unit:GetUnit(1) end - + -- Ensure Missiontypes is a table. if MissionTypes and type(MissionTypes)~="table" then MissionTypes={MissionTypes} end - + -- Create payload. local payload={} --#AIRWING.Payload payload.uid=self.payloadcounter payload.unitname=Unit:GetName() - payload.aircrafttype=Unit:GetTypeName() + payload.aircrafttype=Unit:GetTypeName() payload.pylons=Unit:GetTemplatePayload() payload.unlimited=Npayloads<0 if payload.unlimited then @@ -333,7 +334,7 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) else payload.navail=Npayloads or 99 end - + payload.capabilities={} for _,missiontype in pairs(MissionTypes) do local capability={} --Ops.Auftrag#AUFTRAG.Capability @@ -341,27 +342,27 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) capability.Performance=Performance table.insert(payload.capabilities, capability) end - - -- Add ORBIT for all. + + -- Add ORBIT for all. if not self:CheckMissionType(AUFTRAG.Type.ORBIT, MissionTypes) then local capability={} --Ops.Auftrag#AUFTRAG.Capability capability.MissionType=AUFTRAG.Type.ORBIT capability.Performance=50 table.insert(payload.capabilities, capability) - end - + end + -- Info - self:T(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s", + self:T(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s", payload.unitname, payload.aircrafttype, payload.uid, payload.navail, tostring(payload.unlimited), Performance, table.concat(MissionTypes, ", "))) -- Add payload table.insert(self.payloads, payload) - + -- Increase counter self.payloadcounter=self.payloadcounter+1 - + return payload - + end self:E(self.lid.."ERROR: No UNIT found to create PAYLOAD!") @@ -382,15 +383,15 @@ function AIRWING:AddPayloadCapability(Payload, MissionTypes, Performance) end Payload.capabilities=Payload.capabilities or {} - + for _,missiontype in pairs(MissionTypes) do - + local capability={} --Ops.Auftrag#AUFTRAG.Capability capability.MissionType=missiontype capability.Performance=Performance - + --TODO: check that capability does not already exist! - + table.insert(Payload.capabilities, capability) end @@ -411,7 +412,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) self:T(self.lid.."WARNING: No payloads in stock!") return nil end - + -- Debug. if self.verbose>=4 then self:I(self.lid..string.format("Looking for payload for unit type=%s and mission type=%s", UnitType, MissionType)) @@ -424,7 +425,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) --- Sort payload wrt the following criteria: -- 1) Highest performance is the main selection criterion. - -- 2) If payloads have the same performance, unlimited payloads are preferred over limited ones. + -- 2) If payloads have the same performance, unlimited payloads are preferred over limited ones. -- 3) If payloads have the same performance _and_ are limited, the more abundant one is preferred. local function sortpayloads(a,b) local pA=a --#AIRWING.Payload @@ -457,7 +458,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) return nil end return false - end + end -- Pre-selection: filter out only those payloads that are valid for the airframe and mission type and are available. local payloads={} @@ -466,14 +467,14 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) local specialpayload=_checkPayloads(payload) local compatible=self:CheckMissionCapability(MissionType, payload.capabilities) - + local goforit = specialpayload or (specialpayload==nil and compatible) if payload.aircrafttype==UnitType and payload.navail>0 and goforit then table.insert(payloads, payload) end end - + -- Debug. if self.verbose>=4 then self:I(self.lid..string.format("Sorted payloads for mission type X and aircraft type=Y:")) @@ -485,7 +486,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) end end end - + -- Cases: if #payloads==0 then -- No payload available. @@ -504,10 +505,10 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) local payload=payloads[1] --#AIRWING.Payload if not payload.unlimited then payload.navail=payload.navail-1 - end + end return payload end - + end --- Return payload from asset back to stock. @@ -516,9 +517,9 @@ end function AIRWING:ReturnPayloadFromAsset(asset) local payload=asset.payload - + if payload then - + -- Increase count if not unlimited. if not payload.unlimited then payload.navail=payload.navail+1 @@ -526,11 +527,11 @@ function AIRWING:ReturnPayloadFromAsset(asset) -- Remove asset payload. asset.payload=nil - + else self:E(self.lid.."ERROR: asset had no payload attached!") end - + end @@ -542,23 +543,23 @@ end function AIRWING:AddAssetToSquadron(Squadron, Nassets) if Squadron then - + -- Get the template group of the squadron. local Group=GROUP:FindByName(Squadron.templatename) - + if Group then - + -- Debug text. local text=string.format("Adding asset %s to squadron %s", Group:GetName(), Squadron.name) self:T(self.lid..text) - + -- Add assets to airwing warehouse. self:AddAsset(Group, Nassets, nil, nil, nil, nil, Squadron.skill, Squadron.livery, Squadron.name) - + else self:E(self.lid.."ERROR: Group does not exist!") end - + else self:E(self.lid.."ERROR: Squadron does not exit!") end @@ -574,11 +575,11 @@ function AIRWING:GetSquadron(SquadronName) for _,_squadron in pairs(self.squadrons) do local squadron=_squadron --Ops.Squadron#SQUADRON - + if squadron.name==SquadronName then return squadron end - + end return nil @@ -616,18 +617,18 @@ end -- @param Ops.Auftrag#AUFTRAG Mission for this group. -- @return #AIRWING self function AIRWING:AddMission(Mission) - + -- Set status to QUEUED. This also attaches the airwing to this mission. Mission:Queued(self) - + -- Add mission to queue. table.insert(self.missionqueue, Mission) - + -- Info text. - local text=string.format("Added mission %s (type=%s). Starting at %s. Stopping at %s", + local text=string.format("Added mission %s (type=%s). Starting at %s. Stopping at %s", tostring(Mission.name), tostring(Mission.type), UTILS.SecondsToClock(Mission.Tstart, true), Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop, true) or "INF") self:T(self.lid..text) - + return self end @@ -639,12 +640,12 @@ function AIRWING:RemoveMission(Mission) for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - + if mission.auftragsnummer==Mission.auftragsnummer then table.remove(self.missionqueue, i) break end - + end return self @@ -708,13 +709,13 @@ function AIRWING:SetNumberRescuehelo(n) return self end ---- +--- -- @param #AIRWING self -- @param #AIRWING.PatrolData point Patrol point table. -- @return #string Marker text. function AIRWING:_PatrolPointMarkerText(point) - local text=string.format("%s Occupied=%d, \nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", + local text=string.format("%s Occupied=%d, \nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed) return text @@ -724,9 +725,9 @@ end -- @param #AIRWING.PatrolData point Patrol point table. function AIRWING:UpdatePatrolPointMarker(point) if self.markpoints then -- sometimes there's a direct call from #OPSGROUP - local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", + local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed) - + point.marker:UpdateText(text, 1) end end @@ -753,12 +754,12 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL patrolpoint.speed=Speed or 350 patrolpoint.noccupied=0 patrolpoint.refuelsystem=RefuelSystem - + if self.markpoints then patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll() AIRWING.UpdatePatrolPointMarker(patrolpoint) end - + return patrolpoint end @@ -771,7 +772,7 @@ end -- @param #number LegLength Length of race-track orbit in NM. -- @return #AIRWING self function AIRWING:AddPatrolPointCAP(Coordinate, Altitude, Speed, Heading, LegLength) - + local patrolpoint=self:NewPatrolPoint("CAP", Coordinate, Altitude, Speed, Heading, LegLength) table.insert(self.pointsCAP, patrolpoint) @@ -789,7 +790,7 @@ end -- @param #number RefuelSystem Set refueling system of tanker: 0=boom, 1=probe. Default any (=nil). -- @return #AIRWING self function AIRWING:AddPatrolPointTANKER(Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) - + local patrolpoint=self:NewPatrolPoint("Tanker", Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) table.insert(self.pointsTANKER, patrolpoint) @@ -806,7 +807,7 @@ end -- @param #number LegLength Length of race-track orbit in NM. -- @return #AIRWING self function AIRWING:AddPatrolPointAWACS(Coordinate, Altitude, Speed, Heading, LegLength) - + local patrolpoint=self:NewPatrolPoint("AWACS", Coordinate, Altitude, Speed, Heading, LegLength) table.insert(self.pointsAWACS, patrolpoint) @@ -838,39 +839,39 @@ function AIRWING:onafterStatus(From, Event, To) self:GetParent(self).onafterStatus(self, From, Event, To) local fsmstate=self:GetState() - + -- Check CAP missions. self:CheckCAP() - + -- Check TANKER missions. self:CheckTANKER() - + -- Check AWACS missions. self:CheckAWACS() - + -- Check Rescue Helo missions. self:CheckRescuhelo() - - + + -- General info: if self.verbose>=1 then -- Count missions not over yet. local Nmissions=self:CountMissionsInQueue() - + -- Count ALL payloads in stock. If any payload is unlimited, this gives 999. local Npayloads=self:CountPayloadsInStock(AUFTRAG.Type) - + -- Assets tot local Npq, Np, Nq=self:CountAssetsOnMission() - + local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)", self:CountAssets(), Npq, Np, Nq) -- Output. local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s", fsmstate, Nmissions, Npayloads, #self.payloads, #self.squadrons, assets) self:I(self.lid..text) end - + ------------------ -- Mission Info -- ------------------ @@ -878,16 +879,16 @@ function AIRWING:onafterStatus(From, Event, To) local text=string.format("Missions Total=%d:", #self.missionqueue) for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - + local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.nassets) local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage()) - + text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target) end self:I(self.lid..text) end - + ------------------- -- Squadron Info -- ------------------- @@ -895,17 +896,17 @@ function AIRWING:onafterStatus(From, Event, To) local text="Squadrons:" for i,_squadron in pairs(self.squadrons) do local squadron=_squadron --Ops.Squadron#SQUADRON - + local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName) or "N/A" local modex=squadron.modex and squadron.modex or -1 local skill=squadron.skill and tostring(squadron.skill) or "N/A" - + -- Squadron text text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssetsInStock(), #squadron.assets, callsign, modex, skill) end self:I(self.lid..text) end - + -------------- -- Mission --- -------------- @@ -916,7 +917,7 @@ function AIRWING:onafterStatus(From, Event, To) -- Get next mission. local mission=self:_GetNextMission() - -- Request mission execution. + -- Request mission execution. if mission then self:MissionRequest(mission) end @@ -936,19 +937,19 @@ function AIRWING:_GetPatrolData(PatrolPoints, RefuelSystem) end if PatrolPoints and #PatrolPoints>0 then - + -- Sort data wrt number of flights at that point. table.sort(PatrolPoints, sort) - + for _,_patrolpoint in pairs(PatrolPoints) do local patrolpoint=_patrolpoint --#AIRWING.PatrolData if (RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem) or RefuelSystem==nil or patrolpoint.refuelsystem==nil then return patrolpoint end end - + end - + -- Return a new point. return self:NewPatrolPoint() end @@ -959,25 +960,25 @@ end function AIRWING:CheckCAP() local Ncap=self:CountMissionsInQueue({AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT}) - + for i=1,self.nflightsCAP-Ncap do - + local patrol=self:_GetPatrolData(self.pointsCAP) - + local altitude=patrol.altitude+1000*patrol.noccupied - + local missionCAP=AUFTRAG:NewGCICAP(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg) - + missionCAP.patroldata=patrol - + patrol.noccupied=patrol.noccupied+1 - + if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end - + self:AddMission(missionCAP) - + end - + return self end @@ -988,60 +989,60 @@ function AIRWING:CheckTANKER() local Nboom=0 local Nprob=0 - + -- Count tanker missions. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - + if mission:IsNotOver() and mission.type==AUFTRAG.Type.TANKER then if mission.refuelSystem==Unit.RefuelingSystem.BOOM_AND_RECEPTACLE then Nboom=Nboom+1 elseif mission.refuelSystem==Unit.RefuelingSystem.PROBE_AND_DROGUE then Nprob=Nprob+1 end - + end - + end - -- Check missing boom tankers. + -- Check missing boom tankers. for i=1,self.nflightsTANKERboom-Nboom do - + local patrol=self:_GetPatrolData(self.pointsTANKER) - + local altitude=patrol.altitude+1000*patrol.noccupied - + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, Unit.RefuelingSystem.BOOM_AND_RECEPTACLE) - + mission.patroldata=patrol - + patrol.noccupied=patrol.noccupied+1 - + if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end - + self:AddMission(mission) - + end - + -- Check missing probe tankers. for i=1,self.nflightsTANKERprobe-Nprob do - + local patrol=self:_GetPatrolData(self.pointsTANKER) - + local altitude=patrol.altitude+1000*patrol.noccupied - + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, Unit.RefuelingSystem.PROBE_AND_DROGUE) - + mission.patroldata=patrol - + patrol.noccupied=patrol.noccupied+1 - + if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end - + self:AddMission(mission) - - end - + + end + return self end @@ -1051,25 +1052,25 @@ end function AIRWING:CheckAWACS() local N=self:CountMissionsInQueue({AUFTRAG.Type.AWACS}) - + for i=1,self.nflightsAWACS-N do - + local patrol=self:_GetPatrolData(self.pointsAWACS) - + local altitude=patrol.altitude+1000*patrol.noccupied - + local mission=AUFTRAG:NewAWACS(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg) - + mission.patroldata=patrol - + patrol.noccupied=patrol.noccupied+1 - + if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end - + self:AddMission(mission) - + end - + return self end @@ -1079,19 +1080,19 @@ end function AIRWING:CheckRescuhelo() local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO}) - + local name=self.airbase:GetName() - + local carrier=UNIT:FindByName(name) - + for i=1,self.nflightsRescueHelo-N do - + local mission=AUFTRAG:NewRESCUEHELO(carrier) - + self:AddMission(mission) - + end - + return self end @@ -1102,32 +1103,32 @@ end function AIRWING:GetTankerForFlight(flightgroup) local tankers=self:GetAssetsOnMission(AUFTRAG.Type.TANKER) - + if #tankers>0 then - + local tankeropt={} for _,_tanker in pairs(tankers) do local tanker=_tanker --#AIRWING.SquadronAsset - + -- Check that donor and acceptor use the same refuelling system. if flightgroup.refueltype and flightgroup.refueltype==tanker.flightgroup.tankertype then - + local tankercoord=tanker.flightgroup.group:GetCoordinate() local assetcoord=flightgroup.group:GetCoordinate() - + local dist=assetcoord:Get2DDistance(tankercoord) - + -- Ensure that the flight does not find itself. Asset could be a tanker! if dist>5 then table.insert(tankeropt, {tanker=tanker, dist=dist}) end - + end end - + -- Sort tankers wrt to distance. table.sort(tankeropt, function(a,b) return a.dist0 then return tankeropt[1].tanker @@ -1147,12 +1148,12 @@ function AIRWING:_CheckMissions() -- Loop over missions in queue. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission:IsNotOver() and mission:IsReadyToCancel() then + + if mission:IsNotOver() and mission:IsReadyToCancel() then mission:Cancel() end end - + end --- Get next mission. -- @param #AIRWING self @@ -1174,41 +1175,41 @@ function AIRWING:_GetNextMission() return (taskA.prio0 then self:E(self.lid..string.format("ERROR: mission %s of type %s has already assets attached!", mission.name, mission.type)) end mission.assets={} - + -- Assign assets to mission. for i=1,mission.nassets do local asset=assets[i] --#AIRWING.SquadronAsset - + -- Should not happen as we just checked! if not asset.payload then self:E(self.lid.."ERROR: No payload for asset! This should not happen!") end - + -- Add asset to mission. mission:AddAsset(asset) end - + -- Now return the remaining payloads. for i=mission.nassets+1,#assets do local asset=assets[i] --#AIRWING.SquadronAsset @@ -1269,7 +1270,7 @@ function AIRWING:_GetNextMission() end end end - + return mission end @@ -1288,7 +1289,7 @@ end function AIRWING:CalculateAssetMissionScore(asset, Mission, includePayload) local score=0 - + -- Prefer highly skilled assets. if asset.skill==AI.Skill.AVERAGE then score=score+0 @@ -1299,17 +1300,17 @@ function AIRWING:CalculateAssetMissionScore(asset, Mission, includePayload) elseif asset.skill==AI.Skill.EXCELLENT then score=score+30 end - + -- Add mission performance to score. local squad=self:GetSquadronOfAsset(asset) local missionperformance=squad:GetMissionPeformance(Mission.type) score=score+missionperformance - + -- Add payload performance to score. if includePayload and asset.payload then score=score+self:GetPayloadPeformance(asset.payload, Mission.type) end - + -- Intercepts need to be carried out quickly. We prefer spawned assets. if Mission.type==AUFTRAG.Type.INTERCEPT then if asset.spawned then @@ -1317,13 +1318,13 @@ function AIRWING:CalculateAssetMissionScore(asset, Mission, includePayload) score=score+25 end end - + -- TODO: This could be vastly improved. Need to gather ideas during testing. -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. -- Max speed of assets. -- Fuel amount? - -- Range of assets? - + -- Range of assets? + return score end @@ -1337,15 +1338,15 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) local TargetVec2=Mission:GetTargetVec2() --local dStock=self:GetCoordinate():Get2DDistance(TargetCoordinate) - + local dStock=UTILS.VecDist2D(TargetVec2, self:GetVec2()) - + -- Calculate distance to mission target. local distmin=math.huge local distmax=0 for _,_asset in pairs(assets) do local asset=_asset --#AIRWING.SquadronAsset - + if asset.spawned then local group=GROUP:FindByName(asset.spawngroupname) --asset.dist=group:GetCoordinate():Get2DDistance(TargetCoordinate) @@ -1353,35 +1354,35 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) else asset.dist=dStock end - + if asset.distdistmax then distmax=asset.dist end - + end - + -- Calculate the mission score of all assets. for _,_asset in pairs(assets) do local asset=_asset --#AIRWING.SquadronAsset --self:I(string.format("FF asset %s has payload %s", asset.spawngroupname, asset.payload and "yes" or "no!")) asset.score=self:CalculateAssetMissionScore(asset, Mission, includePayload) end - - --- Sort assets wrt to their mission score. Higher is better. + + --- Sort assets wrt to their mission score. Higher is better. local function optimize(a, b) local assetA=a --#AIRWING.SquadronAsset local assetB=b --#AIRWING.SquadronAsset - + -- Higher score wins. If equal score ==> closer wins. -- TODO: Need to include the distance in a smarter way! return (assetA.score>assetB.score) or (assetA.score==assetB.score and assetA.dist0 then - + --local text=string.format("Requesting assets for mission %s:", Mission.name) for i,_asset in pairs(Assetlist) do local asset=_asset --#AIRWING.SquadronAsset - + -- Set asset to requested! Important so that new requests do not use this asset! asset.requested=true - + if Mission.missionTask then asset.missionTask=Mission.missionTask end - + end - + -- Add request to airwing warehouse. -- TODO: better Assignment string. self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, tostring(Mission.auftragsnummer)) - + -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. Mission.requestID=self.queueid end @@ -1473,38 +1474,38 @@ end -- @param #string To To state. -- @param Ops.Auftrag#AUFTRAG Mission The mission to be cancelled. function AIRWING:onafterMissionCancel(From, Event, To, Mission) - + -- Info message. self:I(self.lid..string.format("Cancel mission %s", Mission.name)) - + local Ngroups = Mission:CountOpsGroups() - + if Mission:IsPlanned() or Mission:IsQueued() or Mission:IsRequested() or Ngroups == 0 then - + Mission:Done() - + else - + for _,_asset in pairs(Mission.assets) do local asset=_asset --#AIRWING.SquadronAsset - + local flightgroup=asset.flightgroup - + if flightgroup then flightgroup:MissionCancel(Mission) end - + -- Not requested any more (if it was). asset.requested=nil end - + end - + -- Remove queued request (if any). if Mission.requestID then self:_DeleteQueueItemByID(Mission.requestID, self.queue) end - + end --- On after "NewAsset" event. Asset is added to the given squadron (asset assignment). @@ -1518,11 +1519,11 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) -- Call parent warehouse function first. self:GetParent(self).onafterNewAsset(self, From, Event, To, asset, assignment) - + -- Debug text. local text=string.format("New asset %s with assignment %s and request assignment %s", asset.spawngroupname, tostring(asset.assignment), tostring(assignment)) self:T3(self.lid..text) - + -- Get squadron. local squad=self:GetSquadron(asset.assignment) @@ -1530,58 +1531,61 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) if squad then if asset.assignment==assignment then - + local nunits=#asset.template.units - + -- Debug text. local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s, nunits=%d %s", squad.name, assignment, asset.unittype, asset.attribute, nunits, tostring(squad.ngrouping)) self:T(self.lid..text) - + -- Adjust number of elements in the group. if squad.ngrouping then local template=asset.template - + local N=math.max(#template.units, squad.ngrouping) - + -- Handle units. for i=1,N do - + -- Unit template. local unit = template.units[i] - - -- If grouping is larger than units present, copy first unit. + + -- If grouping is larger than units present, copy first unit. if i>nunits then table.insert(template.units, UTILS.DeepCopy(template.units[1])) end - + -- Remove units if original template contains more than in grouping. if squad.ngroupingnunits then unit=nil end end - + asset.nunits=squad.ngrouping end + -- Set takeoff type. + asset.takeoffType=squad.takeoffType + -- Create callsign and modex (needs to be after grouping). squad:GetCallsign(asset) squad:GetModex(asset) - + -- Set spawn group name. This has to include "AID-" for warehouse. asset.spawngroupname=string.format("%s_AID-%d", squad.name, asset.uid) -- Add asset to squadron. squad:AddAsset(asset) - + -- TODO --asset.terminalType=AIRBASE.TerminalType.OpenBig else - + --env.info("FF squad asset returned") self:SquadAssetReturned(squad, asset) - + end - + end end @@ -1595,26 +1599,26 @@ end function AIRWING:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) -- Debug message. self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) - + -- Stop flightgroup. if Asset.flightgroup and not Asset.flightgroup:IsStopped() then Asset.flightgroup:Stop() end - + -- Return payload. self:ReturnPayloadFromAsset(Asset) - + -- Return tacan channel. if Asset.tacan then Squadron:ReturnTacan(Asset.tacan) end - + -- Set timestamp. Asset.Treturned=timer.getAbsTime() end ---- On after "AssetSpawned" event triggered when an asset group is spawned into the cruel world. +--- On after "AssetSpawned" event triggered when an asset group is spawned into the cruel world. -- Creates a new flightgroup element and adds the mission to the flightgroup queue. -- @param #AIRWING self -- @param #string From From state. @@ -1628,85 +1632,87 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- Call parent warehouse function first. self:GetParent(self).onafterAssetSpawned(self, From, Event, To, group, asset, request) - -- Create a flight group. - local flightgroup=self:_CreateFlightGroup(asset) - - - --- - -- Asset - --- - - -- Set asset flightgroup. - asset.flightgroup=flightgroup - - -- Not requested any more. - asset.requested=nil - - -- Did not return yet. - asset.Treturned=nil - - --- - -- Squadron - --- - -- Get the SQUADRON of the asset. local squadron=self:GetSquadronOfAsset(asset) - - -- Get TACAN channel. - local Tacan=squadron:FetchTacan() - if Tacan then - asset.tacan=Tacan - end - - -- Set radio frequency and modulation - local radioFreq, radioModu=squadron:GetRadio() - if radioFreq then - flightgroup:SwitchRadio(radioFreq, radioModu) - end - - if squadron.fuellow then - flightgroup:SetFuelLowThreshold(squadron.fuellow) - end - - if squadron.fuellowRefuel then - flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) - end - --- - -- Mission - --- - - -- Get Mission (if any). - local mission=self:GetMissionByID(request.assignment) + -- Check if we have a squadron or if this was some other request. + if squadron then - -- Add mission to flightgroup queue. - if mission then - + -- Create a flight group. + local flightgroup=self:_CreateFlightGroup(asset) + + --- + -- Asset + --- + + -- Set asset flightgroup. + asset.flightgroup=flightgroup + + -- Not requested any more. + asset.requested=nil + + -- Did not return yet. + asset.Treturned=nil + + --- + -- Squadron + --- + + -- Get TACAN channel. + local Tacan=squadron:FetchTacan() if Tacan then - mission:SetTACAN(Tacan, Morse, UnitName, Band) + asset.tacan=Tacan end - + + -- Set radio frequency and modulation + local radioFreq, radioModu=squadron:GetRadio() + if radioFreq then + flightgroup:SwitchRadio(radioFreq, radioModu) + end + + if squadron.fuellow then + flightgroup:SetFuelLowThreshold(squadron.fuellow) + end + + if squadron.fuellowRefuel then + flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) + end + + --- + -- Mission + --- + + -- Get Mission (if any). + local mission=self:GetMissionByID(request.assignment) + -- Add mission to flightgroup queue. - asset.flightgroup:AddMission(mission) - - -- Trigger event. - self:FlightOnMission(flightgroup, mission) - - else - - if Tacan then - flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + if mission then + + if Tacan then + mission:SetTACAN(Tacan, Morse, UnitName, Band) + end + + -- Add mission to flightgroup queue. + asset.flightgroup:AddMission(mission) + + -- Trigger event. + self:FlightOnMission(flightgroup, mission) + + else + + if Tacan then + flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + end + end - + + -- Add group to the detection set of the WINGCOMMANDER. + if self.wingcommander and self.wingcommander.chief then + self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) + end + end - - - - -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander and self.wingcommander.chief then - self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) - end - + end --- On after "AssetDead" event triggered when an asset group died. @@ -1725,7 +1731,7 @@ function AIRWING:onafterAssetDead(From, Event, To, asset, request) if self.wingcommander and self.wingcommander.chief then self.wingcommander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) end - + -- Remove asset from mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function -- Remove asset from squadron same end @@ -1768,22 +1774,22 @@ function AIRWING:onafterRequest(From, Event, To, Request) -- Assets local assets=Request.cargoassets - + -- Get Mission local Mission=self:GetMissionByID(Request.assignment) - + if Mission and assets then - + for _,_asset in pairs(assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --#AIRWING.SquadronAsset -- This would be the place to modify the asset table before the asset is spawned. end - + end -- Call parent warehouse function after assets have been adjusted. self:GetParent(self).onafterRequest(self, From, Event, To, Request) - + end --- On after "SelfRequest" event. @@ -1800,13 +1806,13 @@ function AIRWING:onafterSelfRequest(From, Event, To, groupset, request) -- Get Mission local mission=self:GetMissionByID(request.assignment) - + for _,_asset in pairs(request.assets) do local asset=_asset --#AIRWING.SquadronAsset end - + for _,_group in pairs(groupset:GetSet()) do - local group=_group --Wrapper.Group#GROUP + local group=_group --Wrapper.Group#GROUP end end @@ -1826,41 +1832,13 @@ function AIRWING:_CreateFlightGroup(asset) -- Set airwing. flightgroup:SetAirwing(self) - + -- Set squadron. flightgroup.squadron=self:GetSquadronOfAsset(asset) -- Set home base. flightgroup.homebase=self.airbase - - --[[ - - --- Check if out of missiles. For A2A missions ==> RTB. - function flightgroup:OnAfterOutOfMissiles() - local airwing=flightgroup:GetAirWing() - - end - - --- Check if out of missiles. For A2G missions ==> RTB. But need to check A2G missiles, rockets as well. - function flightgroup:OnAfterOutOfBombs() - local airwing=flightgroup:GetAirWing() - - end - --- Mission started. - function flightgroup:OnAfterMissionStart(From, Event, To, Mission) - local airwing=flightgroup:GetAirWing() - - end - - --- Flight is DEAD. - function flightgroup:OnAfterFlightDead(From, Event, To) - local airwing=flightgroup:GetAirWing() - - end - - ]] - return flightgroup end @@ -1882,46 +1860,46 @@ function AIRWING:IsAssetOnMission(asset, MissionTypes) end if asset.flightgroup and asset.flightgroup:IsAlive() then - + -- Loop over mission queue. for _,_mission in pairs(asset.flightgroup.missionqueue or {}) do local mission=_mission --Ops.Auftrag#AUFTRAG - + if mission:IsNotOver() then - + -- Get flight status. local status=mission:GetGroupStatus(asset.flightgroup) - + -- Only if mission is started or executing. if (status==AUFTRAG.GroupStatus.STARTED or status==AUFTRAG.GroupStatus.EXECUTING) and self:CheckMissionType(mission.type, MissionTypes) then return true end - + end - + end - + end - + -- Alternative: run over all missions and compare to mission assets. --[[ for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - + if mission:IsNotOver() then for _,_asset in pairs(mission.assets) do local sqasset=_asset --#AIRWING.SquadronAsset - + if sqasset.uid==asset.uid then return true end - + end end - + end ]] - + return false end @@ -1931,8 +1909,8 @@ end -- @return Ops.Auftrag#AUFTRAG Current mission or *nil*. function AIRWING:GetAssetCurrentMission(asset) - if asset.flightgroup then - return asset.flightgroup:GetMissionCurrent() + if asset.flightgroup then + return asset.flightgroup:GetMissionCurrent() end return nil @@ -1957,7 +1935,7 @@ function AIRWING:CountPayloadsInStock(MissionTypes, UnitTypes, Payloads) UnitTypes={UnitTypes} end end - + local function _checkUnitTypes(payload) if UnitTypes then for _,unittype in pairs(UnitTypes) do @@ -1971,7 +1949,7 @@ function AIRWING:CountPayloadsInStock(MissionTypes, UnitTypes, Payloads) end return false end - + local function _checkPayloads(payload) if Payloads then for _,Payload in pairs(Payloads) do @@ -1984,30 +1962,30 @@ function AIRWING:CountPayloadsInStock(MissionTypes, UnitTypes, Payloads) return nil end return false - end + end local n=0 for _,_payload in pairs(self.payloads) do local payload=_payload --#AIRWING.Payload - + for _,MissionType in pairs(MissionTypes) do - + local specialpayload=_checkPayloads(payload) local compatible=self:CheckMissionCapability(MissionType, payload.capabilities) - + local goforit = specialpayload or (specialpayload==nil and compatible) - + if goforit and _checkUnitTypes(payload) then - + if payload.unlimited then -- Payload is unlimited. Return a BIG number. return 999 else n=n+payload.navail end - + end - + end end @@ -2025,12 +2003,12 @@ function AIRWING:CountMissionsInQueue(MissionTypes) local N=0 for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - + -- Check if this mission type is requested. if mission:IsNotOver() and self:CheckMissionType(mission.type, MissionTypes) then N=N+1 end - + end return N @@ -2042,7 +2020,7 @@ end function AIRWING:CountAssets() local N=0 - + for _,_squad in pairs(self.squadrons) do local squad=_squad --Ops.Squadron#SQUADRON N=N+#squad.assets @@ -2059,32 +2037,32 @@ end -- @return #number Number of pending assets. -- @return #number Number of queued assets. function AIRWING:CountAssetsOnMission(MissionTypes, Squadron) - + local Nq=0 local Np=0 for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - + -- Check if this mission type is requested. if self:CheckMissionType(mission.type, MissionTypes or AUFTRAG.Type) then - + for _,_asset in pairs(mission.assets or {}) do local asset=_asset --#AIRWING.SquadronAsset - + if Squadron==nil or Squadron.name==asset.squadname then - + local request, isqueued=self:GetRequestByID(mission.requestID) - + if isqueued then Nq=Nq+1 else Np=Np+1 end - + end - - end + + end end end @@ -2097,22 +2075,22 @@ end -- @param #table MissionTypes Types on mission to be checked. Default all. -- @return #table Assets on pending requests. function AIRWING:GetAssetsOnMission(MissionTypes) - + local assets={} local Np=0 for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - + -- Check if this mission type is requested. if self:CheckMissionType(mission.type, MissionTypes) then - + for _,_asset in pairs(mission.assets or {}) do local asset=_asset --#AIRWING.SquadronAsset - table.insert(assets, asset) - - end + table.insert(assets, asset) + + end end end @@ -2128,13 +2106,13 @@ function AIRWING:GetAircraftTypes(onlyactive, squadrons) -- Get all unit types that can do the job. local unittypes={} - + -- Loop over all squadrons. for _,_squadron in pairs(squadrons or self.squadrons) do local squadron=_squadron --Ops.Squadron#SQUADRON - - if (not onlyactive) or squadron:IsOnDuty() then - + + if (not onlyactive) or squadron:IsOnDuty() then + local gotit=false for _,unittype in pairs(unittypes) do if squadron.aircrafttype==unittype then @@ -2145,9 +2123,9 @@ function AIRWING:GetAircraftTypes(onlyactive, squadrons) if not gotit then table.insert(unittypes, squadron.aircrafttype) end - + end - end + end return unittypes end @@ -2162,16 +2140,16 @@ function AIRWING:CanMission(Mission) -- Assume we CAN and NO assets are available. local Can=true local Assets={} - + -- Squadrons for the job. If user assigned to mission or simply all. local squadrons=Mission.squadrons or self.squadrons -- Get aircraft unit types for the job. local unittypes=self:GetAircraftTypes(true, squadrons) - + -- Count all payloads in stock. local Npayloads=self:CountPayloadsInStock(Mission.type, unittypes, Mission.payloads) - + if Npayloads1 then - text=text..string.format(" Marshal radial %03d°.", radial) + if case==1 then + text=text..string.format(" BRC %03d°.", self:GetBRC()) + elseif case==2 then + text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC()) + elseif case==3 then + text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false)) end self:T(self.lid..text) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 8d6e7292c..a42384db2 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -14,7 +14,7 @@ -- -- **Main Features:** -- --- * MOOSE based Helicopter CSAR Operations. +-- * MOOSE-based Helicopter CSAR Operations for Players. -- -- === -- @@ -25,7 +25,7 @@ -- Date: June 2021 ------------------------------------------------------------------------- ---- **CSAR** class, extends #Core.Base#BASE, #Core.Fsm#FSM +--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM -- @type CSAR -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. @@ -41,13 +41,14 @@ -- -- # CSAR Concept -- --- * Object oriented refactoring of Ciribob's fantastic CSAR script. +-- * MOOSE-based Helicopter CSAR Operations for Players. +-- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. -- * No need for extra MIST loading. -- * Additional events to tailor your mission. -- -- ## 0. Prerequisites -- --- You need to load an .ogg soundfile for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. -- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". -- -- ## 1. Basic Setup @@ -58,7 +59,7 @@ -- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") -- -- options -- my_csar.immortalcrew = true -- downed pilot spawn is immortal --- my_csar.invisiblevrew = false -- downed pilot spawn is visible +-- my_csar.invisiblecrew = false -- downed pilot spawn is visible -- -- start the FSM -- my_csar:__Start(5) -- @@ -66,25 +67,28 @@ -- -- The following options are available (with their defaults). Only set the ones you want changed: -- --- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. +-- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. -- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! --- self.autosmoke = false -- automatically smoke a downed pilot's location when a heli is near. +-- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. --- self.csarOncrash = true -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. --- self.enableForAI = true -- set to false to disable AI pilots from being rescued. +-- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. +-- self.enableForAI = false -- set to false to disable AI pilots from being rescued. -- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. -- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- self.immortalcrew = true -- Set to true to make wounded crew immortal. -- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. --- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. +-- self.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. -- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. -- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. -- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. -- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- self.verbose = 0 -- set to > 1 for stats output for debugging. +-- -- (added 0.1.4) limit amount of downed pilots spawned by ejection events +-- self.limitmaxdownedpilots = true, +-- self.maxdownedpilots = 10, -- -- ## 2.1 Experimental Features -- @@ -153,7 +157,7 @@ -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition --- my_csar:_SpawnCsarAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -- -- -- @field #CSAR @@ -183,10 +187,12 @@ CSAR = { useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below csarPrefix = {}, template = nil, - bluemash = {}, + mash = {}, smokecolor = 4, rescues = 0, rescuedpilots = 0, + limitmaxdownedpilots = true, + maxdownedpilots = 10, } --- Downed pilots info. @@ -202,18 +208,21 @@ CSAR = { -- @field Wrapper.Group#GROUP group Spawned group object. -- @field #number timestamp Timestamp for approach process ---- Known beacons from the available maps +--- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. -- @field #CSAR.SkipFrequencies CSAR.SkipFrequencies = { - 745,381,384,300.50,312.5,1175,342,735,300.50,353.00, - 440,795,525,520,690,625,291.5,300.50, - 435,309.50,920,1065,274,312.50, - 580,602,297.50,750,485,950,214, - 1025,730,995,455,307,670,329,395,770, - 380,705,300.5,507,740,1030,515,330,309.5,348,462,905,352,1210,942,435, - 324,320,420,311,389,396,862,680,297.5,920,662,866,907,309.5,822,515,470,342,1182,309.5,720,528, - 337,312.5,830,740,309.5,641,312,722,682,1050, - 1116,935,1000,430,577,540,550,560,570, + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580,602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 } --- All slot / Limit settings @@ -225,19 +234,20 @@ CSAR.AircraftType["SA342Minigun"] = 2 CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 CSAR.AircraftType["UH-1H"] = 8 -CSAR.AircraftType["Mi-8MT"] = 12 +CSAR.AircraftType["Mi-8MTV2"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r3" +CSAR.version="0.1.5r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- DONE: SRS Integration (to be tested) +-- TODO: Maybe - add option to smoke/flare closest MASH ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -325,9 +335,9 @@ function CSAR:New(Coalition, Template, Alias) -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH self.rescuedpilots = 0 -- counter for saved pilots - self.csarOncrash = true -- If set to true, will generate a csar when a plane crashes as well. - self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. - self.enableForAI = true -- set to false to disable AI units from being rescued. + self.csarOncrash = false -- If set to true, will generate a csar when a plane crashes as well. + self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined arms. + self.enableForAI = false -- set to false to disable AI units from being rescued. self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal @@ -339,18 +349,21 @@ function CSAR:New(Coalition, Template, Alias) self.loadtimemax = 135 -- seconds self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase - self.max_units = 6 --number of pilots that can be carried + self.max_units = 6 --max number of pilots that can be carried self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below - self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON'T use # in names! + self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.template = Template or "generic" -- template for downed pilot self.mashprefix = {"MASH"} -- prefixes used to find MASHes - self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? + self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near + -- added 0.1.4 + self.limitmaxdownedpilots = true + self.maxdownedpilots = 25 - -- WARNING - here'll be dragons + -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) - self.useSRS = false -- Use FF's SRS integration + self.useSRS = false -- Use FF\'s SRS integration self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation @@ -403,7 +416,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. --- On After "Boarded" event. Downed pilot boarded heli. -- @function [parent=#CSAR] OnAfterBoarded @@ -412,7 +425,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning @@ -421,7 +434,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued @@ -440,7 +453,7 @@ end --- Helper Functions --- ------------------------ ---- Function to insert downed pilot tracker object. +--- (Internal) Function to insert downed pilot tracker object. -- @param #CSAR self -- @param Wrapper.Group#GROUP Group The #GROUP object -- @param #string Groupname Name of the spawned group. @@ -476,9 +489,10 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript self.downedPilots = PilotTable -- Increase counter self.downedpilotcounter = self.downedpilotcounter+1 + return self end ---- Count pilots on board. +--- (Internal) Count pilots on board. -- @param #CSAR self -- @param #string _heliName -- @return #number count @@ -493,7 +507,7 @@ function CSAR:_PilotsOnboard(_heliName) return count end ---- Function to check for dupe eject events. +--- (Internal) Function to check for dupe eject events. -- @param #CSAR self -- @param #string _unitname Name of unit. -- @return #boolean Outcome @@ -509,7 +523,7 @@ function CSAR:_DoubleEjection(_unitname) return false end ---- Spawn a downed pilot +--- (Internal) Spawn a downed pilot -- @param #CSAR self -- @param #number country Country for template. -- @param Core.Point#COORDINATE point Coordinate to spawn at. @@ -535,7 +549,7 @@ function CSAR:_SpawnPilotInField(country,point) return _spawnedGroup, alias -- Wrapper.Group#GROUP object end ---- Add options to a downed pilot +--- (Internal) Add options to a downed pilot -- @param #CSAR self -- @param Wrapper.Group#GROUP group Group to use. function CSAR:_AddSpecialOptions(group) @@ -566,10 +580,10 @@ function CSAR:_AddSpecialOptions(group) group:OptionAlarmStateGreen() group:OptionROEHoldFire() - + return self end ---- Function to spawn a CSAR object into the scene. +--- (Internal) Function to spawn a CSAR object into the scene. -- @param #CSAR self -- @param #number _coalition Coalition -- @param DCS#country.id _country Country ID @@ -589,7 +603,8 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) local _typeName = _typeName or "PoW" if not noMessage then - local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) + --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) end if not _freq then @@ -620,15 +635,16 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) + return self end ---- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +--- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. -- @param #CSAR self -- @param #string _zone Name of the zone. -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. --- @param #boolean _nomessage (optional) If true, don't send a message to SAR. +-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() @@ -658,10 +674,28 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ end self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) + + return self +end + +--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +-- @param #CSAR self +-- @param #string Zone Name of the zone. +-- @param #number Coalition Coalition. +-- @param #string Description (optional) Description. +-- @param #boolean RandomPoint (optional) Random yes or no. +-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. +-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so: +-- +-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) + return self end -- TODO: Split in functions per Event type ---- Event handler. +--- (Internal) Event handler. -- @param #CSAR self function CSAR:_EventHandler(EventData) self:T(self.lid .. " _EventHandler") @@ -731,8 +765,9 @@ function CSAR:_EventHandler(EventData) if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then if self:_DoubleEjection(_unitname) then return - end - local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) + end + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!", self.coalition, 10) + --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) else self:T(self.lid .. " Pilot has not taken off, ignore") end @@ -770,7 +805,13 @@ function CSAR:_EventHandler(EventData) if self:_DoubleEjection(_unitname) then return end - + + -- limit no of pilots in the field. + if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then + return + end + + -- all checks passed, get going. local _freq = self:_GenerateADFFrequency() self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, 0) @@ -815,10 +856,10 @@ function CSAR:_EventHandler(EventData) return true end - + return self end ---- Initialize the action for a pilot. +--- (Internal) Initialize the action for a pilot. -- @param #CSAR self -- @param Wrapper.Group#GROUP _downedGroup The group to rescue. -- @param #string _GroupName Name of the Group @@ -844,9 +885,11 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) -- trigger FSM event self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) + + return self end ---- Check if a name is in downed pilot table +--- (Internal) Check if a name is in downed pilot table -- @param #CSAR self -- @param #string name Name to search for. -- @return #boolean Outcome. @@ -865,7 +908,7 @@ function CSAR:_CheckNameInDownedPilots(name) return found, table end ---- Check if a name is in downed pilot table and remove it. +--- (Internal) Check if a name is in downed pilot table and remove it. -- @param #CSAR self -- @param #string name Name to search for. -- @param #boolean force Force removal. @@ -877,7 +920,7 @@ function CSAR:_RemoveNameFromDownedPilots(name,force) if _pilot.name == name then local group = _pilot.group -- Wrapper.Group#GROUP if group then - if (not group:IsAlive()) or ( force == true) then -- don't delete groups which still exist + if (not group:IsAlive()) or ( force == true) then -- don\'t delete groups which still exist found = true _pilot.desc = nil _pilot.frequency = nil @@ -896,7 +939,7 @@ function CSAR:_RemoveNameFromDownedPilots(name,force) return found end ---- Check state of wounded group. +--- (Internal) Check state of wounded group. -- @param #CSAR self -- @param #string heliname heliname -- @param #string woundedgroupname woundedgroupname @@ -933,13 +976,13 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local _distance = self:_GetDistance(_heliCoord,_leaderCoord) if _distance < 3000 and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then - -- we're close, reschedule + -- we\'re close, reschedule _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-5,heliname,woundedgroupname) end else self.heliVisibleMessage[_lookupKeyHeli] = nil - --reschedule as units aren't dead yet , schedule for a bit slower though as we're far away + --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-10,heliname,woundedgroupname) end @@ -947,9 +990,10 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...Downed Pilot KIA?!") self:_RemoveNameFromDownedPilots(_downedpilot.name) end + return self end ---- Function to pop a smoke at a wounded pilot's positions. +--- (Internal) Function to pop a smoke at a wounded pilot\'s positions. -- @param #CSAR self -- @param #string _woundedGroupName Name of the group. -- @param Wrapper.Group#GROUP _woundedLeader Object of the group. @@ -964,9 +1008,10 @@ function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) _smokecoord:Smoke(_smokecolor) self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time end + return self end ---- Function to pickup the wounded pilot from the ground. +--- (Internal) Function to pickup the wounded pilot from the ground. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heliUnit Object of the group. -- @param #string _pilotName Name of the pilot. @@ -985,13 +1030,13 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _groups = self.inTransitGroups[_heliName] end - -- if the heli can't pick them up, show a message and return + -- if the heli can\'t pick them up, show a message and return local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] if _maxUnits == nil then _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) return true end @@ -1010,14 +1055,14 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _woundedGroup:Destroy() self:_RemoveNameFromDownedPilots(_woundedGroupName,true) - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) self:__Boarded(5,_heliName,_woundedGroupName) return true end ---- Move group to destination. +--- (Internal) Move group to destination. -- @param #CSAR self -- @param Wrapper.Group#GROUP _leader -- @param Core.Point#COORDINATE _destination @@ -1028,9 +1073,10 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) group:SetAIOn() group:RouteToVec2(coordinate,5) + return self end ---- Function to check if heli is close to group. +--- (Internal) Function to check if heli is close to group. -- @param #CSAR self -- @param #number _distance -- @param Wrapper.Unit#UNIT _heliUnit @@ -1068,9 +1114,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if self.heliCloseMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) end --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true @@ -1079,7 +1125,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG -- have we landed close enough? if not _heliUnit:InAir() then - -- if you land on them, doesnt matter if they were heading to someone else as you're closer, you win! :) + -- if you land on them, doesnt matter if they were heading to someone else as you\'re closer, you win! :) if self.pilotRuntoExtractPoint == true then if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] @@ -1134,7 +1180,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _time > 0 then - self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) else self.hoverStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1161,7 +1207,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end end ---- Check if group not KIA. +--- (Internal) Check if group not KIA. -- @param #CSAR self -- @param Wrapper.Group#GROUP _woundedGroup -- @param #string _woundedGroupName @@ -1191,43 +1237,44 @@ function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _he return inTransit end ---- Monitor in-flight returning groups. +--- (Internal) Monitor in-flight returning groups. -- @param #CSAR self -- @param #string heliname Heli name -- @param #string groupname Group name function CSAR:_ScheduledSARFlight(heliname,groupname) self:T(self.lid .. " _ScheduledSARFlight") self:T({heliname,groupname}) - local _heliUnit = self:_GetSARHeli(heliname) - local _woundedGroupName = groupname + local _heliUnit = self:_GetSARHeli(heliname) + local _woundedGroupName = groupname - if (_heliUnit == nil) then - --helicopter crashed? - self.inTransitGroups[heliname] = nil - return - end + if (_heliUnit == nil) then + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return + end - if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then - -- Groups already rescued - return - end + if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then + -- Groups already rescued + return + end - local _dist = self:_GetClosestMASH(_heliUnit) + local _dist = self:_GetClosestMASH(_heliUnit) - if _dist == -1 then - return - end + if _dist == -1 then + return + end - if _dist < 200 and _heliUnit:InAir() == false then - self:_RescuePilots(_heliUnit) - return - end + if _dist < 200 and _heliUnit:InAir() == false then + self:_RescuePilots(_heliUnit) + return + end - --queue up - self:__Returning(-5,heliname,_woundedGroupName) + --queue up + self:__Returning(-5,heliname,_woundedGroupName) + return self end ---- Mark pilot as rescued and remove from tables. +--- (Internal) Mark pilot as rescued and remove from tables. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heliUnit function CSAR:_RescuePilots(_heliUnit) @@ -1250,9 +1297,10 @@ function CSAR:_RescuePilots(_heliUnit) self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) -- trigger event self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) + return self end ---- Check and return Wrappe.Unit#UNIT based on the name if alive. +--- (Internal) Check and return Wrappe.Unit#UNIT based on the name if alive. -- @param #CSAR self -- @param #string _unitname Name of Unit -- @return #UNIT or nil @@ -1266,7 +1314,7 @@ function CSAR:_GetSARHeli(_unitName) end end ---- Display message to single Unit. +--- (Internal) Display message to single Unit. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _unit Unit #UNIT to display to. -- @param #string _text Text of message. @@ -1288,9 +1336,10 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) local msrs = MSRS:New(path,channel,modulation) msrs:PlaySoundText(srstext, 2) end + return self end ---- Function to get string of a group's position. +--- (Internal) Function to get string of a group\'s position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. -- @return #string Coordinates as Text @@ -1316,7 +1365,7 @@ function CSAR:_GetPositionOfWounded(_woundedGroup) return _coordinatesText end ---- Display active SAR tasks to player. +--- (Internal) Display active SAR tasks to player. -- @param #CSAR self -- @param #string _unitName Unit to display to function CSAR:_DisplayActiveSAR(_unitName) @@ -1366,9 +1415,10 @@ function CSAR:_DisplayActiveSAR(_unitName) end self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) + return self end ---- Find the closest downed pilot to a heli. +--- (Internal) Find the closest downed pilot to a heli. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @return #table Table of results @@ -1402,7 +1452,7 @@ function CSAR:_GetClosestDownedPilot(_heli) return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } end ---- Fire a flare at the point of a downed pilot. +--- (Internal) Fire a flare at the point of a downed pilot. -- @param #CSAR self -- @param #string _unitName Name of the unit. function CSAR:_SignalFlare(_unitName) @@ -1435,9 +1485,10 @@ function CSAR:_SignalFlare(_unitName) end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end + return self end ---- Display info to all SAR groups. +--- (Internal) Display info to all SAR groups. -- @param #CSAR self -- @param #string _message Message to display. -- @param #number _side Coalition of message. @@ -1452,9 +1503,10 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) end end end + return self end ----Request smoke at closest downed pilot. +---(Internal) Request smoke at closest downed pilot. --@param #CSAR self --@param #string _unitName Name of the helicopter function CSAR:_Reqsmoke( _unitName ) @@ -1484,15 +1536,16 @@ function CSAR:_Reqsmoke( _unitName ) end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end + return self end ---- Determine distance to closest MASH. +--- (Internal) Determine distance to closest MASH. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @retunr function CSAR:_GetClosestMASH(_heli) self:T(self.lid .. " _GetClosestMASH") - local _mashset = self.bluemash -- Core.Set#SET_GROUP + local _mashset = self.mash -- Core.Set#SET_GROUP local _mashes = _mashset:GetSetObjects() -- #table local _shortestDistance = -1 local _distance = 0 @@ -1541,7 +1594,7 @@ function CSAR:_GetClosestMASH(_heli) end end ---- Display onboarded rescued pilots. +--- (Internal) Display onboarded rescued pilots. -- @param #CSAR self -- @param #string _unitName Name of the chopper function CSAR:_CheckOnboard(_unitName) @@ -1561,9 +1614,10 @@ function CSAR:_CheckOnboard(_unitName) end self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2) end + return self end ---- Populate F10 menu for CSAR players. +--- (Internal) Populate F10 menu for CSAR players. -- @param #CSAR self function CSAR:_AddMedevacMenuItem() self:T(self.lid .. " _AddMedevacMenuItem") @@ -1603,10 +1657,10 @@ function CSAR:_AddMedevacMenuItem() end end end - return + return self end ---- Return distance in meters between two coordinates. +--- (Internal) Return distance in meters between two coordinates. -- @param #CSAR self -- @param Core.Point#COORDINATE _point1 Coordinate one -- @param Core.Point#COORDINATE _point2 Coordinate two @@ -1621,7 +1675,7 @@ function CSAR:_GetDistance(_point1, _point2) end end ---- Populate table with available beacon frequencies. +--- (Internal) Populate table with available beacon frequencies. -- @param #CSAR self function CSAR:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") @@ -1672,7 +1726,7 @@ function CSAR:_GenerateVHFrequencies() -- third range _start = 850000 - while _start <= 1250000 do + while _start <= 999000 do -- updated for Gazelle -- skip existing NDB frequencies local _found = false @@ -1690,9 +1744,10 @@ function CSAR:_GenerateVHFrequencies() _start = _start + 50000 end self.FreeVHFFrequencies = FreeVHFFrequencies + return self end ---- Pop frequency from prepopulated table. +--- (Internal) Pop frequency from prepopulated table. -- @param #CSAR self -- @return #number frequency function CSAR:_GenerateADFFrequency() @@ -1706,7 +1761,7 @@ function CSAR:_GenerateADFFrequency() return _vhf end ---- Function to determine clockwise direction for flares. +--- (Internal) Function to determine clockwise direction for flares. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli The Helicopter -- @param Wrapper.Group#GROUP _group The downed Group @@ -1730,7 +1785,7 @@ function CSAR:_GetClockDirection(_heli, _group) return clock end ---- Function to add beacon to downed pilot. +--- (Internal) Function to add beacon to downed pilot. -- @param #CSAR self -- @param Wrapper.Group#GROUP _group Group #GROUP object. -- @param #number _freq Frequency to use @@ -1754,29 +1809,63 @@ function CSAR:_AddBeaconToGroup(_group, _freq) local Sound = "l10n/DEFAULT/"..self.radioSound trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight end + return self end ---- Helper function to (re-)add beacon to downed pilot. +--- (Internal) Helper function to (re-)add beacon to downed pilot. -- @param #CSAR self -- @param #table _args Arguments function CSAR:_RefreshRadioBeacons() self:T(self.lid .. " _RefreshRadioBeacons") - local PilotTable = self.downedPilots - for _,_pilot in pairs (PilotTable) do - local pilot = _pilot -- #CSAR.DownedPilot - local group = pilot.group - local frequency = pilot.frequency - if frequency and frequency > 0 then - self:_AddBeaconToGroup(group,frequency) + if self:_CountActiveDownedPilots() > 0 then + local PilotTable = self.downedPilots + for _,_pilot in pairs (PilotTable) do + local pilot = _pilot -- #CSAR.DownedPilot + local group = pilot.group + local frequency = pilot.frequency or 0.0 -- thanks to @Thrud + if group:IsAlive() and frequency > 0.0 then + self:_AddBeaconToGroup(group,frequency) + end end end + return self +end + +--- (Internal) Helper function to count active downed pilots. +-- @param #CSAR self +-- @return #number Number of pilots in the field. +function CSAR:_CountActiveDownedPilots() + self:T(self.lid .. " _CountActiveDownedPilots") + local PilotsInFieldN = 0 + for _, _unitName in pairs(self.downedPilots) do + self:T({_unitName}) + if _unitName.name ~= nil then + PilotsInFieldN = PilotsInFieldN + 1 + end + end + return PilotsInFieldN +end + +--- (Internal) Helper to decide if we're over max limit. +-- @param #CSAR self +-- @return #boolean True or false. +function CSAR:_ReachedPilotLimit() + self:T(self.lid .. " _ReachedPilotLimit") + local limit = self.maxdownedpilots + local islimited = self.limitmaxdownedpilots + local count = self:_CountActiveDownedPilots() + if islimited and (count >= limit) then + return true + else + return false + end end ------------------------------ --- FSM internal Functions --- ------------------------------ ---- Function called after Start() event. +--- (Internal) Function called after Start() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1802,7 +1891,7 @@ function CSAR:onafterStart(From, Event, To) return self end ---- Function called before Status() event. +--- (Internal) Function called before Status() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1819,7 +1908,7 @@ function CSAR:onbeforeStatus(From, Event, To) local name = entry.name local timestamp = entry.timestamp or 0 local now = timer.getAbsTime() - if now - timestamp > 17 then -- only check if we're not in approach mode, which is iterations of 5 and 10. + if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. self:_CheckWoundedGroupStatus(_sar,name) end end @@ -1827,7 +1916,7 @@ function CSAR:onbeforeStatus(From, Event, To) return self end ---- Function called after Status() event. +--- (Internal) Function called after Status() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1840,13 +1929,7 @@ function CSAR:onafterStatus(From, Event, To) NumberOfSARPilots = NumberOfSARPilots + 1 end - local PilotsInFieldN = 0 - for _, _unitName in pairs(self.downedPilots) do - self:T({_unitName}) - if _unitName.name ~= nil then - PilotsInFieldN = PilotsInFieldN + 1 - end - end + local PilotsInFieldN = self:_CountActiveDownedPilots() local PilotsBoarded = 0 for _, _unitName in pairs(self.inTransitGroups) do @@ -1856,8 +1939,8 @@ function CSAR:onafterStatus(From, Event, To) end if self.verbose > 0 then - local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", - self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues,self.rescuedpilots) + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", + self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots) self:T(text) if self.verbose < 2 then self:I(text) @@ -1870,7 +1953,7 @@ function CSAR:onafterStatus(From, Event, To) return self end ---- Function called after Stop() event. +--- (Internal) Function called after Stop() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1888,46 +1971,46 @@ function CSAR:onafterStop(From, Event, To) return self end ---- Function called before Approach() event. +--- (Internal) Function called before Approach() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) return self end ---- Function called before Boarded() event. +--- (Internal) Function called before Boarded() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_ScheduledSARFlight(Heliname,Woundedgroupname) return self end ---- Function called before Returning() event. +--- (Internal) Function called before Returning() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_ScheduledSARFlight(Heliname,Woundedgroupname) return self end ---- Function called before Rescued() event. +--- (Internal) Function called before Rescued() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1942,7 +2025,7 @@ function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) return self end ---- Function called before PilotDown() event. +--- (Internal) Function called before PilotDown() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua new file mode 100644 index 000000000..78edc5a75 --- /dev/null +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -0,0 +1,2532 @@ +--- **Ops** -- Combat Troops & Logistics Deployment. +-- +-- === +-- +-- **CTLD** - MOOSE based Helicopter CTLD Operations. +-- +-- === +-- +-- ## Missions: +-- +-- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CTLD) +-- +-- === +-- +-- **Main Features:** +-- +-- * MOOSE-based Helicopter CTLD Operations for Players. +-- +-- === +-- +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- @module Ops.CTLD +-- @image OPS_CTLD.jpg + +-- Date: July 2021 + +do +------------------------------------------------------ +--- **CTLD_CARGO** class, extends #Core.Base#BASE +-- @type CTLD_CARGO +-- @field #number ID ID of this cargo. +-- @field #string Name Name for menu. +-- @field #table Templates Table of #POSITIONABLE objects. +-- @field #CTLD_CARGO.Enum Type Enumerator of Type. +-- @field #boolean HasBeenMoved Flag for moving. +-- @field #boolean LoadDirectly Flag for direct loading. +-- @field #number CratesNeeded Crates needed to build. +-- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. +-- @field #boolean HasBeenDropped True if dropped from heli. +-- @extends Core.Fsm#FSM +CTLD_CARGO = { + ClassName = "CTLD_CARGO", + ID = 0, + Name = "none", + Templates = {}, + CargoType = "none", + HasBeenMoved = false, + LoadDirectly = false, + CratesNeeded = 0, + Positionable = nil, + HasBeenDropped = false, + } + + --- Define cargo types. + -- @type CTLD_CARGO.Enum + -- @field #string Type Type of Cargo. + CTLD_CARGO.Enum = { + VEHICLE = "Vehicle", -- #string vehicles + TROOPS = "Troops", -- #string troops + FOB = "FOB", -- #string FOB + CRATE = "CRATE", -- #string crate + } + + --- Function to create new CTLD_CARGO object. + -- @param #CTLD_CARGO self + -- @param #number ID ID of this #CTLD_CARGO + -- @param #string Name Name for menu. + -- @param #table Templates Table of #POSITIONABLE objects. + -- @param #CTLD_CARGO.Enum Sorte Enumerator of Type. + -- @param #boolean HasBeenMoved Flag for moving. + -- @param #boolean LoadDirectly Flag for direct loading. + -- @param #number CratesNeeded Crates needed to build. + -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. + -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. + -- @return #CTLD_CARGO self + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped) + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #CTLD + self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) + self.ID = ID or math.random(100000,1000000) + self.Name = Name or "none" -- #string + self.Templates = Templates or {} -- #table + self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum + self.HasBeenMoved = HasBeenMoved or false -- #booolean + self.LoadDirectly = LoadDirectly or false -- #booolean + self.CratesNeeded = CratesNeeded or 0 -- #number + self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE + self.HasBeenDropped = Dropped or false --#boolean + return self + end + + --- Query ID. + -- @param #CTLD_CARGO self + -- @return #number ID + function CTLD_CARGO:GetID() + return self.ID + end + + --- Query Name. + -- @param #CTLD_CARGO self + -- @return #string Name + function CTLD_CARGO:GetName() + return self.Name + end + + --- Query Templates. + -- @param #CTLD_CARGO self + -- @return #table Templates + function CTLD_CARGO:GetTemplates() + return self.Templates + end + + --- Query has moved. + -- @param #CTLD_CARGO self + -- @return #boolean Has moved + function CTLD_CARGO:HasMoved() + return self.HasBeenMoved + end + + --- Query was dropped. + -- @param #CTLD_CARGO self + -- @return #boolean Has been dropped. + function CTLD_CARGO:WasDropped() + return self.HasBeenDropped + end + + --- Query directly loadable. + -- @param #CTLD_CARGO self + -- @return #boolean loadable + function CTLD_CARGO:CanLoadDirectly() + return self.LoadDirectly + end + + --- Query number of crates or troopsize. + -- @param #CTLD_CARGO self + -- @return #number Crates or size of troops. + function CTLD_CARGO:GetCratesNeeded() + return self.CratesNeeded + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return #CTLD_CARGO.Enum Type + function CTLD_CARGO:GetType() + return self.CargoType + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return Wrapper.Positionable#POSITIONABLE Positionable + function CTLD_CARGO:GetPositionable() + return self.Positionable + end + + --- Set HasMoved. + -- @param #CTLD_CARGO self + -- @param #boolean moved + function CTLD_CARGO:SetHasMoved(moved) + self.HasBeenMoved = moved or false + end + + --- Query if cargo has been loaded. + -- @param #CTLD_CARGO self + -- @param #boolean loaded + function CTLD_CARGO:Isloaded() + if self.HasBeenMoved and not self.WasDropped() then + return true + else + return false + end + end + --- Set WasDropped. + -- @param #CTLD_CARGO self + -- @param #boolean dropped + function CTLD_CARGO:SetWasDropped(dropped) + self.HasBeenDropped = dropped or false + end + +end + +do +------------------------------------------------------------------------- +--- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM +-- @type CTLD +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @extends Core.Fsm#FSM + +--- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there\'s POG stuff to be done.* (Mil Saying) +-- +-- === +-- +-- ![Banner Image](OPS_CTLD.jpg) +-- +-- # CTLD Concept +-- +-- * MOOSE-based CTLD for Players. +-- * Object oriented refactoring of Ciribob\'s fantastic CTLD script. +-- * No need for extra MIST loading. +-- * Additional events to tailor your mission. +-- * ANY late activated group can serve as cargo, either as troops or crates, which have to be build on-location. +-- +-- ## 0. Prerequisites +-- +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces. +-- +-- ## 1. Basic Setup +-- +-- ## 1.1 Create and start a CTLD instance +-- +-- A basic setup example is the following: +-- +-- -- Instantiate and start a CTLD for the blue side, using helicopter groups named "Helicargo" and alias "Lufttransportbrigade I" +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo"},"Lufttransportbrigade I") +-- my_ctld:__Start(5) +-- +-- ## 1.2 Add cargo types available +-- +-- Add *generic* cargo types that you need for your missions, here infantry units, vehicles and a FOB. These need to be late-activated Wrapper.Group#GROUP objects: +-- +-- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3 +-- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed) +-- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3) +-- +-- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4 +-- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4) +-- +-- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build +-- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) +-- +-- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: +-- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) +-- +-- ## 1.3 Add logistics zones +-- +-- Add zones for loading troops and crates and dropping, building crates +-- +-- -- Add a zone of type LOAD to our setup. Players can load troops and crates. +-- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside of the zone. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) +-- +-- -- Add a zone of type DROP. Players can drop crates here. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- -- NOTE: Troops can be unloaded anywhere, also when hovering in parameters. +-- my_ctld:AddCTLDZone("Dropzone",CTLD.CargoZoneType.DROP,SMOKECOLOR.Red,true,true) +-- +-- -- Add two zones of type MOVE. Dropped troops and vehicles will move to the nearest one. See options. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Movezone",CTLD.CargoZoneType.MOVE,SMOKECOLOR.Orange,false,false) +-- +-- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true) +-- +-- +-- ## 2. Options +-- +-- The following options are available (with their defaults). Only set the ones you want changed: +-- +-- my_ctld.useprefix = true -- Adjust *before* starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. +-- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. +-- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. +-- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. +-- my_ctld.forcehoverload = true -- Crates (not: troops) can only be loaded while hovering. +-- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. +-- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). +-- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... +-- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) +-- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) +-- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. +-- +-- ## 2.1 User functions +-- +-- ### 2.1.1 Adjust or add chopper unit-type capabilities +-- +-- Use this function to adjust what a heli type can or cannot do: +-- +-- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. +-- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: +-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8) +-- +-- Default unit type capabilities are: +-- +-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, +-- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, +-- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, +-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, +-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, +-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, +-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- +-- +-- ### 2.1.2 Activate and deactivate zones +-- +-- Activate a zone: +-- +-- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:ActivateZone(Name,ZoneType) +-- +-- Deactivate a zone: +-- +-- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:DeactivateZone(Name,ZoneType) +-- +-- ## 3. Events +-- +-- The class comes with a number of FSM-based events that missions designers can use to shape their mission. +-- These are: +-- +-- ## 3.1 OnAfterTroopsPickedUp +-- +-- This function is called when a player has loaded Troops: +-- +-- function CTLD:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.2 OnAfterCratesPickedUp +-- +-- This function is called when a player has picked up crates: +-- +-- function CTLD:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.3 OnAfterTroopsDeployed +-- +-- This function is called when a player has deployed troops into the field: +-- +-- function CTLD:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) +-- ... your code here ... +-- end +-- +-- ## 3.4 OnAfterCratesDropped +-- +-- This function is called when a player has deployed crates to a DROP zone: +-- +-- function CTLD:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- ... your code here ... +-- end +-- +-- ## 3.5 OnAfterCratesBuild +-- +-- This function is called when a player has build a vehicle or FOB: +-- +-- function CTLD:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- ... your code here ... +-- end +-- +-- ## 4. F10 Menu structure +-- +-- CTLD management menu is under the F10 top menu and called "CTLD" +-- +-- ## 4.1 Manage Crates +-- +-- Use this entry to get, load, list nearby, drop, and build crates. Also @see options. +-- +-- ## 4.2 Manage Troops +-- +-- Use this entry to load and drop troops. +-- +-- ## 4.3 List boarded cargo +-- +-- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. +-- +-- ## 4.4 Smoke & Flare zones nearby +-- +-- Does what it says. +-- +-- ## 4.5 List active zone beacons +-- +-- Lists active radio beacons for all zones, where zones are both active and have a beacon. @see `CTLD:AddCTLDZone()` +-- +-- ## 4.6 Show hover parameters +-- +-- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. +-- +-- ## 5. Support for Hercules mod by Anubis +-- +-- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can +-- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class. +-- +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") +-- +-- Enable these options for Hercules support: +-- +-- my_ctld.enableHercules = true +-- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft +-- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- +-- Also, the following options need to be set to `true`: +-- +-- my_ctld.useprefix = true -- this is true by default +-- +-- Standard transport capabilities as per the real Hercules are: +-- +-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers +-- +-- @field #CTLD +CTLD = { + ClassName = "CTLD", + verbose = 0, + lid = "", + coalition = 1, + coalitiontxt = "blue", + PilotGroups = {}, -- #GROUP_SET of heli pilots + CtldUnits = {}, -- Table of helicopter #GROUPs + FreeVHFFrequencies = {}, -- Table of VHF + FreeUHFFrequencies = {}, -- Table of UHF + FreeFMFrequencies = {}, -- Table of FM + CargoCounter = 0, + dropOffZones = {}, + wpZones = {}, + Cargo_Troops = {}, -- generic troops objects + Cargo_Crates = {}, -- generic crate objects + Loaded_Cargo = {}, -- cargo aboard units + Spawned_Crates = {}, -- Holds objects for crates spawned generally + Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects + CrateDistance = 30, -- list crates in this radius + debug = false, + wpZones = {}, + pickupZones = {}, + dropOffZones = {}, +} + +------------------------------ +-- DONE: Zone Checks +-- DONE: TEST Hover load and unload +-- DONE: Crate unload +-- DONE: Hover (auto-)load +-- TODO: (More) Housekeeping +-- DONE: Troops running to WP Zone +-- DONE: Zone Radio Beacons +-- DONE: Stats Running +-- DONE: Added support for Hercules +------------------------------ + +--- Radio Beacons +-- @type CTLD.ZoneBeacon +-- @field #string name -- Name of zone for the coordinate +-- @field #number frequency -- in mHz +-- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM + +--- Zone Info. +-- @type CTLD.CargoZone +-- @field #string name Name of Zone. +-- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. +-- @field #boolean active Active or not. +-- @field #string type Type of zone, i.e. load,drop,move +-- @field #boolean hasbeacon Create and run radio beacons if active. +-- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon + +--- Zone Type Info. +-- @type CTLD. +CTLD.CargoZoneType = { + LOAD = "load", + DROP = "drop", + MOVE = "move", +} + +--- Buildable table info. +-- @type CTLD.Buildable +-- @field #string Name Name of the object. +-- @field #number Required Required crates. +-- @field #number Found Found crates. +-- @field #table Template Template names for this build. +-- @field #boolean CanBuild Is buildable or not. +-- @field #CTLD_CARGO.Enum Type Type enumerator (for moves). + +--- Unit capabilities. +-- @type CTLD.UnitCapabilities +-- @field #string type Unit type. +-- @field #boolean crates Can transport crate. +-- @field #boolean troops Can transport troops. +-- @field #number cratelimit Number of crates transportable. +-- @field #number trooplimit Number of troop units transportable. +CTLD.UnitTypes = { + ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, + ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, + ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, + ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, + ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, + ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, + ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, + ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, + ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, + ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers +} + +--- Updated and sorted known NDB beacons (in kHz!) from the available maps +-- @field #CTLD.SkipFrequencies +CTLD.SkipFrequencies = { + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580,602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 + } + +--- CTLD class version. +-- @field #string version +CTLD.version="0.1.2b1" + +--- Instantiate a new CTLD. +-- @param #CTLD self +-- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL +-- @param #table Prefixes Table of pilot prefixes. +-- @param #string Alias Alias of this CTLD for logging. +-- @return #CTLD self +function CTLD:New(Coalition, Prefixes, Alias) + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CTLD + + BASE:T({Coalition, Prefixes, Alias}) + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition=="red" then + self.coalition=coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL + self.coalitiontxt = Coalition + else + self:E("ERROR: Unknown coalition in CTLD!") + end + else + self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) + end + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="UNHCR" + if self.coalition then + if self.coalition==coalition.side.RED then + self.alias="Red CTLD" + elseif self.coalition==coalition.side.BLUE then + self.alias="Blue CTLD" + end + end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- CTLD status update. + self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + -- tables + self.PilotGroups ={} + self.CtldUnits = {} + + -- Beacons + self.FreeVHFFrequencies = {} + self.FreeUHFFrequencies = {} + self.FreeFMFrequencies = {} + self.UsedVHFFrequencies = {} + self.UsedUHFFrequencies = {} + self.UsedFMFrequencies = {} + --self.jtacGeneratedLaserCodes = {} + + -- radio beacons + self.RadioSound = "beacon.ogg" + + -- zones stuff + self.pickupZones = {} + self.dropOffZones = {} + self.wpZones = {} + + -- Cargo + self.Cargo_Crates = {} + self.Cargo_Troops = {} + self.Loaded_Cargo = {} + self.Spawned_Crates = {} + self.Spawned_Cargo = {} + self.MenusDone = {} + self.DroppedTroops = {} + self.DroppedCrates = {} + self.CargoCounter = 0 + self.CrateCounter = 0 + self.TroopCounter = 0 + + -- setup + self.CrateDistance = 30 -- list/load crates in this radius + self.prefixes = Prefixes or {"Cargoheli"} + --self.I({prefixes = self.prefixes}) + self.useprefix = true + + self.maximumHoverHeight = 15 + self.minimumHoverHeight = 4 + self.forcehoverload = true + self.hoverautoloading = true + + self.smokedistance = 2000 + self.movetroopstowpzone = true + self.movetroopsdistance = 5000 + + -- added support Hercules Mod + self.enableHercules = false + self.HercMinAngels = 165 -- for troop/cargo drop via chute + self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + + -- message suppression + self.suppressmessages = false + + for i=1,100 do + math.random() + end + + self:_GenerateVHFrequencies() + self:_GenerateUHFrequencies() + self:_GenerateFMFrequencies() + --self:_GenerateLaserCodes() -- curr unused + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] Start + -- @param #CTLD self + + --- Triggers the FSM event "Start" after a delay. Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] __Start + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CTLD and all its event handlers. + -- @param #CTLD self + + --- Triggers the FSM event "Stop" after a delay. Stops the CTLD and all its event handlers. + -- @function [parent=#CTLD] __Stop + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CTLD] Status + -- @param #CTLD self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CTLD] __Status + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- FSM Function OnAfterTroopsPickedUp. + -- @function [parent=#CTLD] OnAfterTroopsPickedUp + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self + + --- FSM Function OnAfterCratesPickedUp. + -- @function [parent=#CTLD] OnAfterCratesPickedUp + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsDeployed. + -- @function [parent=#CTLD] OnAfterTroopsDeployed + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + + --- FSM Function OnAfterCratesDropped. + -- @function [parent=#CTLD] OnAfterCratesDropped + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + + --- FSM Function OnAfterCratesBuild. + -- @function [parent=#CTLD] OnAfterCratesBuild + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsRTB. + -- @function [parent=#CTLD] OnAfterTroopsRTB + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + + return self +end + +------------------------------------------------------------------- +-- Helper and User Functions +------------------------------------------------------------------- + +--- (Internal) Function to generate valid UHF Frequencies +-- @param #CTLD self +function CTLD:_GenerateUHFrequencies() + self:T(self.lid .. " _GenerateUHFrequencies") + self.FreeUHFFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + table.insert(self.FreeUHFFrequencies, _start) + _start = _start + 500000 + end + + return self +end + +--- (Internal) Function to generate valid FM Frequencies +-- @param #CTLD sel +function CTLD:_GenerateFMFrequencies() + self:T(self.lid .. " _GenerateFMrequencies") + self.FreeFMFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + + _start = _start + 500000 + end + + for _first = 3, 7 do + for _second = 0, 5 do + for _third = 0, 9 do + local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit + table.insert(self.FreeFMFrequencies, _frequency) + end + end + end + + return self +end + +--- (Internal) Populate table with available VHF beacon frequencies. +-- @param #CTLD self +function CTLD:_GenerateVHFrequencies() + self:T(self.lid .. " _GenerateVHFrequencies") + local _skipFrequencies = self.SkipFrequencies + + self.FreeVHFFrequencies = {} + self.UsedVHFFrequencies = {} + + -- first range + local _start = 200000 + while _start < 400000 do + + -- skip existing NDB frequencies# + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(self.FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- second range + _start = 400000 + while _start < 850000 do + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(self.FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- third range + _start = 850000 + while _start <= 999000 do -- adjusted for Gazelle + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(self.FreeVHFFrequencies, _start) + end + _start = _start + 50000 + end + + return self +end + +--- (Internal) Function to generate valid laser codes. +-- @param #CTLD self +function CTLD:_GenerateLaserCodes() + self:T(self.lid .. " _GenerateLaserCodes") + self.jtacGeneratedLaserCodes = {} + -- generate list of laser codes + local _code = 1111 + local _count = 1 + while _code < 1777 and _count < 30 do + while true do + _code = _code + 1 + if not self:_ContainsDigit(_code, 8) + and not self:_ContainsDigit(_code, 9) + and not self:_ContainsDigit(_code, 0) then + table.insert(self.jtacGeneratedLaserCodes, _code) + break + end + end + _count = _count + 1 + end +end + +--- (Internal) Helper function to generate laser codes. +-- @param #CTLD self +-- @param #number _number +-- @param #number _numberToFind +function CTLD:_ContainsDigit(_number, _numberToFind) + self:T(self.lid .. " _ContainsDigit") + local _thisNumber = _number + local _thisDigit = 0 + while _thisNumber ~= 0 do + _thisDigit = _thisNumber % 10 + _thisNumber = math.floor(_thisNumber / 10) + if _thisDigit == _numberToFind then + return true + end + end + return false +end + +--- (Internal) Event handler function +-- @param #CTLD self +-- @param Core.Event#EVENTDATA EventData +function CTLD:_EventHandler(EventData) + -- TODO: events dead and playerleaveunit - nil table entries + self:T(string.format("%s Event = %d",self.lid, EventData.id)) + local event = EventData -- Core.Event#EVENTDATA + if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then + local _coalition = event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + -- check is Helicopter + local _unit = event.IniUnit + local _group = event.IniGroup + if _unit:IsHelicopter() or _group:IsHelicopter() then + self:_RefreshF10Menus() + end + -- Herc support + --self:T_unit:GetTypeName()) + if _unit:GetTypeName() == "Hercules" and self.enableHercules then + self:_RefreshF10Menus() + end + return + elseif event.id == EVENTS.PlayerLeaveUnit then + -- remove from pilot table + local unitname = event.IniUnitName or "none" + self.CtldUnits[unitname] = nil + end + return self +end + +--- (Internal) Function to message a group. +-- @param #CTLD self +-- @param #string Text The text to display. +-- @param #number Time Number of seconds to display the message. +-- @param #boolean Clearscreen Clear screen or not. +-- @param Wrapper.Group#GROUP Group The group receiving the message. +function CTLD:_SendMessage(Text, Time, Clearscreen, Group) + self:T(self.lid .. " _SendMessage") + if not self.suppressmessages then + local m = MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group) + end + return self +end + +--- (Internal) Function to load troops into a heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargotype +function CTLD:_LoadTroops(Group, Unit, Cargotype) + self:T(self.lid .. " _LoadTroops") + -- landed or hovering over load zone? + local grounded = not self:IsUnitInAir(Unit) + local hoverload = self:CanHoverLoad(Unit) + -- check if we are in LOAD zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if not inzone then + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + if not self.debug then return self end + elseif not grounded and not hoverload then + self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + --local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + end + -- load troops into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + local cargotype = Cargotype -- #CTLD_CARGO + local cratename = cargotype:GetName() -- #string + --self:Tself.lid .. string.format("Troops %s requested", cratename)) + -- see if this heli can load troops + local unittype = unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cantroops = capabilities.troops -- #boolean + local trooplimit = capabilities.trooplimit -- #number + local troopsize = cargotype:GetCratesNeeded() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + if troopsize + numberonboard > trooplimit then + self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + --local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) + return + else + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert(loaded.Cargo,Cargotype) + self.Loaded_Cargo[unitname] = loaded + self:_SendMessage("Troops boarded!", 10, false, Group) + --local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) + self:__TroopsPickedUp(1,Group, Unit, Cargotype) + end + return self +end + +--- (Internal) Function to spawn crates in front of the heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargo +-- @param #number number Number of crates to generate (for dropping) +-- @param #boolean drop If true we\'re dropping from heli rather than loading. +function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) + self:T(self.lid .. " _GetCrates") + local cgoname = Cargo:GetName() + --self:T{cgoname, number, drop}) + -- check if we are in LOAD zone + local inzone = true + + if drop then + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + else + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + end + + if not inzone then + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + end + + -- avoid crate spam + local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities + local canloadcratesno = capabilities.cratelimit + local loaddist = self.CrateDistance or 30 + local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) + if numbernearby >= canloadcratesno and not drop then + self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) + --local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) + return self + end + -- spawn crates in front of helicopter + local IsHerc = self:IsHercules(Unit) -- Herc + local cargotype = Cargo -- #CTLD_CARGO + local number = number or cargotype:GetCratesNeeded() --#number + local cratesneeded = cargotype:GetCratesNeeded() --#number + local cratename = cargotype:GetName() + --self:Tself.lid .. string.format("Crate %s requested", cratename)) + local cratetemplate = "Container"-- #string + -- get position and heading of heli + local position = Unit:GetCoordinate() + local heading = Unit:GetHeading() + 1 + local height = Unit:GetHeight() + local droppedcargo = {} + -- loop crates needed + for i=1,number do + local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) + local cratedistance = i*4 + 6 + if IsHerc then + -- wider radius + cratedistance = i*4 + 12 + end + local rheading = math.floor(math.random(90,270) * heading + 1 / 360) + if not IsHerc then + rheading = rheading + 180 -- mirror for Helis + end + if rheading > 360 then rheading = rheading - 360 end -- catch > 360 + local cratecoord = position:Translate(cratedistance,rheading) + local cratevec2 = cratecoord:GetVec2() + self.CrateCounter = self.CrateCounter + 1 + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",country.id.GERMANY) + :InitCoordinate(cratecoord) + :Spawn(270,cratealias) + + local templ = cargotype:GetTemplates() + local sorte = cargotype:GetType() + self.CargoCounter = self.CargoCounter +1 + local realcargo = nil + if drop then + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true) + table.insert(droppedcargo,realcargo) + else + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter]) + end + table.insert(self.Spawned_Cargo, realcargo) + end + local text = string.format("Crates for %s have been positioned near you!",cratename) + if drop then + text = string.format("Crates for %s have been dropped!",cratename) + self:__CratesDropped(1, Group, Unit, droppedcargo) + end + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) + return self +end + +--- (Internal) Function to find and list nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCratesNearby( _group, _unit) + self:T(self.lid .. " _ListCratesNearby") + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(_group,_unit, finddist) -- #table + if number > 0 then + local text = REPORT:New("Crates Found Nearby:") + text:Add("------------------------------------------------------------") + for _,_entry in pairs (crates) do + local entry = _entry -- #CTLD_CARGO + local name = entry:GetName() --#string + -- TODO Meaningful sorting/aggregation + local dropped = entry:WasDropped() + if dropped then + text:Add(string.format("Dropped crate for %s",name)) + else + text:Add(string.format("Crate for %s",name)) + end + end + if text:GetCount() == 1 then + text:Add("--------- N O N E ------------") + end + text:Add("------------------------------------------------------------") + self:_SendMessage(text:Text(), 30, true, _group) + --local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) + else + self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) + --local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) + end + return self +end + +--- (Internal) Return distance in meters between two coordinates. +-- @param #CTLD self +-- @param Core.Point#COORDINATE _point1 Coordinate one +-- @param Core.Point#COORDINATE _point2 Coordinate two +-- @return #number Distance in meters +function CTLD:_GetDistance(_point1, _point2) + self:T(self.lid .. " _GetDistance") + if _point1 and _point2 then + local distance = _point1:DistanceFromPointVec2(_point2) + return distance + else + return -1 + end +end + +--- (Internal) Function to find and return nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP _group Group +-- @param Wrapper.Unit#UNIT _unit Unit +-- @param #number _dist Distance +-- @return #table Table of crates +-- @return #number Number Number of crates found +function CTLD:_FindCratesNearby( _group, _unit, _dist) + self:T(self.lid .. " _FindCratesNearby") + local finddist = _dist + local location = _group:GetCoordinate() + local existingcrates = self.Spawned_Cargo -- #table + -- cycle + local index = 0 + local found = {} + for _,_cargoobject in pairs (existingcrates) do + local cargo = _cargoobject -- #CTLD_CARGO + local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates + local staticid = cargo:GetID() + if static and static:IsAlive() then + local staticpos = static:GetCoordinate() + local distance = self:_GetDistance(location,staticpos) + if distance <= finddist and static then + index = index + 1 + table.insert(found, staticid, cargo) + end + end + end + --self:Tstring.format("Found crates = %d",index)) + -- table.sort(found) + --self:T({found}) + return found, index +end + +--- (Internal) Function to get and load nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_LoadCratesNearby(Group, Unit) + self:T(self.lid .. " _LoadCratesNearby") + -- load crates into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load crates + local unittype = unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + local grounded = not self:IsUnitInAir(Unit) + local canhoverload = self:CanHoverLoad(Unit) + --- cases ------------------------------- + -- Chopper can\'t do crates - bark & return + -- Chopper can do crates - + -- --> hover if forcedhover or bark and return + -- --> hover or land if not forcedhover + ----------------------------------------- + if not cancrates then + self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + --local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) + elseif self.forcehoverload and not canhoverload then + self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) + --local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + elseif not grounded and not canhoverload then + self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) + --local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + else + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + -- get nearby crates + local finddist = self.CrateDistance or 30 + local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + if number == 0 or numberonboard == cratelimit then + self:_SendMessage("Sorry no loadable crates nearby or fully loaded!", 10, false, Group) + --local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) + return -- exit + else + -- go through crates and load + local capacity = cratelimit - numberonboard + local crateidsloaded = {} + local loops = 0 + while loaded.Cratesloaded < cratelimit and loops < number do + --for _ind,_crate in pairs (nearcrates) do + loops = loops + 1 + local crateind = 0 + -- get crate with largest index + for _ind,_crate in pairs (nearcrates) do + if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then + --crate = _crate + crateind = _crate:GetID() + end + end + -- load one if we found one + if crateind > 0 then + local crate = nearcrates[crateind] -- #CTLD_CARGO + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved(true) + table.insert(loaded.Cargo, crate) + table.insert(crateidsloaded,crate:GetID()) + -- destroy crate + crate:GetPositionable():Destroy() + crate.Positionable = nil + self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + --local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) + self:__CratesPickedUp(1, Group, Unit, crate) + end + --if loaded.Cratesloaded == cratelimit then break end + end + self.Loaded_Cargo[unitname] = loaded + -- clean up real world crates + local existingcrates = self.Spawned_Cargo -- #table + local newexcrates = {} + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + for _,_ID in pairs(crateidsloaded) do + if ID ~= _ID then + table.insert(newexcrates,_crate) + end + end + end + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + end + end + return self +end + +--- (Internal) Function to list loaded cargo. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCargo(Group, Unit) + self:T(self.lid .. " _ListCargo") + local unitname = Unit:GetName() + local unittype = Unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local trooplimit = capabilities.trooplimit -- #boolean + local cratelimit = capabilities.cratelimit -- #numbe + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + if self.Loaded_Cargo[unitname] then + local no_troops = loadedcargo.Troopsloaded or 0 + local no_crates = loadedcargo.Cratesloaded or 0 + local cargotable = loadedcargo.Cargo or {} -- #table + local report = REPORT:New("Transport Checkout Sheet") + report:Add("------------------------------------------------------------") + report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) + report:Add("------------------------------------------------------------") + report:Add("-- TROOPS --") + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) + end + end + if report:GetCount() == 4 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + report:Add("-- CRATES --") + local cratecount = 0 + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS then + report:Add(string.format("Crate: %s size 1",cargo:GetName())) + cratecount = cratecount + 1 + end + end + if cratecount == 0 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + local text = report:Text() + self:_SendMessage(text, 30, true, Group) + --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + else + self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) + --local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) + end + return self +end + +--- (Internal) Function to check if a unit is a Hercules C-130. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #boolean Outcome +function CTLD:IsHercules(Unit) + if Unit:GetTypeName() == "Hercules" then + return true + else + return false + end +end + +--- (Internal) Function to unload troops from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_UnloadTroops(Group, Unit) + self:T(self.lid .. " _UnloadTroops") + -- check if we are in LOAD zone + local droppingatbase = false + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if inzone then + droppingatbase = true + end + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + if not droppingatbase or self.debug then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for troops + local cargotable = loadedcargo.Cargo + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + -- unload troops + local name = cargo:GetName() or "none" + local temptable = cargo:GetTemplates() or {} + local position = Group:GetCoordinate() + local zoneradius = 100 -- drop zone radius + local factor = 1 + if IsHerc then + factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping + zoneradius = Unit:GetVelocityMPS() or 100 + end + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor) + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + end -- template loop + cargo:SetWasDropped(true) + self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) + --local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) + self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) + end -- if type end + end -- cargotable loop + else -- droppingatbase + self:_SendMessage("Troops have returned to base!", 10, false, Group) + --local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) + self:__TroopsRTB(1, Group, Unit) + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local cargotable = loadedcargo.Cargo or {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local dropped = cargo:WasDropped() + --local moved = cargo:HasMoved() + if type ~= CTLD_CARGO.Enum.TROOP and not dropped then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Cratesloaded + 1 + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + if IsHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end + end + return self +end + +--- (Internal) Function to unload crates from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_UnloadCrates(Group, Unit) + self:T(self.lid .. " _UnloadCrates") + -- check if we are in DROP zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then + return self + end + end + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for troops + local cargotable = loadedcargo.Cargo + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + -- unload crates + self:_GetCrates(Group, Unit, cargo, 1, true) + cargo:SetWasDropped(true) + cargo:SetHasMoved(true) + --local name cargo:GetName() + --local m = MESSAGE:New(string.format("Dropped Crate for %s!",name),10,"CTLD"):ToGroup(Group) + end + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local size = cargo:GetCratesNeeded() + if type == CTLD_CARGO.Enum.TROOP then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Troopsloaded + size + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + if IsHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end + end + return self +end + +--- (Internal) Function to build nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_BuildCrates(Group, Unit) + self:T(self.lid .. " _BuildCrates") + -- get nearby crates + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _,_crate in pairs(crates) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + buildables[name] = object + foundbuilds = true + else + buildables[name].Found = buildables[name].Found + 1 + if buildables[name].Found >= buildables[name].Required then + buildables[name].CanBuild = true + canbuild = true + end + foundbuilds = true + end + --self:T{buildables = buildables}) + end -- end dropped + end -- end crate loop + -- ok let\'s list what we have + local report = REPORT:New("Checklist Buildable Crates") + report:Add("------------------------------------------------------------") + for _,_build in pairs(buildables) do + local build = _build -- Object table from above + local name = build.Name + local needed = build.Required + local found = build.Found + local txtok = "NO" + if build.CanBuild then + txtok = "YES" + end + --self:T{name,needed,found,txtok}) + local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) + report:Add(text) + end -- end list buildables + if not foundbuilds then report:Add(" --- None Found ---") end + report:Add("------------------------------------------------------------") + local text = report:Text() + self:_SendMessage(text, 30, true, Group) + --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + -- let\'s get going + if canbuild then + -- loop again + for _,_build in pairs(buildables) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_CleanUpCrates(crates,build,number) + self:_BuildObjectFromCrates(Group,Unit,build) + end + end + end + else + self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + --local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) + end -- number > 0 + return self +end + +--- (Internal) Function to actually SPAWN buildables in the mission. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Group#UNIT Unit +-- @param #CTLD.Buildable Build +function CTLD:_BuildObjectFromCrates(Group,Unit,Build) + self:T(self.lid .. " _BuildObjectFromCrates") + -- Spawn-a-crate-content + local position = Unit:GetCoordinate() or Group:GetCoordinate() + local unitname = Unit:GetName() or Group:GetName() + local name = Build.Name + local type = Build.Type -- #CTLD_CARGO.Enum + local canmove = false + if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end + local temptable = Build.Template or {} + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) + local randomcoord = zone:GetRandomCoordinate(20,50):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone and canmove then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + end -- template loop + return self +end + +--- (Internal) Function to move group to WP zone. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The Group to move. +function CTLD:_MoveGroupToZone(Group) + self:T(self.lid .. " _MoveGroupToZone") + local groupname = Group:GetName() or "none" + local groupcoord = Group:GetCoordinate() + --self:Tself.lid .. " _MoveGroupToZone for " .. groupname) + -- Get closest zone of type + local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) + --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) + if (distance <= self.movetroopsdistance) and zone then + -- yes, we can ;) + local groupname = Group:GetName() + --self:Tstring.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) + local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE + local coordinate = zonecoord:GetVec2() + --self:T{coordinate=coordinate}) + Group:SetAIOn() + Group:OptionAlarmStateAuto() + Group:OptionDisperseOnAttack(30) + Group:OptionROEOpenFirePossible() + Group:RouteToVec2(coordinate,5) + end + return self +end + +--- (Internal) Housekeeping - Cleanup crates when build +-- @param #CTLD self +-- @param #table Crates Table of #CTLD_CARGO objects near the unit. +-- @param #CTLD.Buildable Build Table build object. +-- @param #number Number Number of objects in Crates (found) to limit search. +function CTLD:_CleanUpCrates(Crates,Build,Number) + self:T(self.lid .. " _CleanUpCrates") + -- clean up real world crates + local build = Build -- #CTLD.Buildable + --self:T{Build = Build}) + local existingcrates = self.Spawned_Cargo -- #table of exising crates + local newexcrates = {} + -- get right number of crates to destroy + local numberdest = Build.Required + local nametype = Build.Name + local found = 0 + local rounds = Number + local destIDs = {} + + -- loop and find matching IDs in the set + for _,_crate in pairs(Crates) do + local nowcrate = _crate -- #CTLD_CARGO + local name = nowcrate:GetName() + --self:Tstring.format("Looking for Crate for %s", name)) + local thisID = nowcrate:GetID() + if name == nametype then -- matching crate type + table.insert(destIDs,thisID) + found = found + 1 + nowcrate:GetPositionable():Destroy() + nowcrate.Positionable = nil + --self:Tstring.format("%s Found %d Need %d", name, found, numberdest)) + end + if found == numberdest then break end -- got enough + end + --self:T{destIDs}) + -- loop and remove from real world representation + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + for _,_ID in pairs(destIDs) do + if ID ~= _ID then + table.insert(newexcrates,_crate) + end + end + end + + -- reset Spawned_Cargo + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + return self +end + +--- (Internal) Housekeeping - Function to refresh F10 menus. +-- @param #CTLD self +-- @return #CTLD self +function CTLD:_RefreshF10Menus() + self:T(self.lid .. " _RefreshF10Menus") + local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP + local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects + --self:T({PlayerTable=PlayerTable}) + -- rebuild units table + local _UnitList = {} + for _key, _group in pairs (PlayerTable) do + local _unit = _group:GetUnit(1) -- Asume that there is only one unit in the flight for players + if _unit then + if _unit:IsAlive() then + local unitName = _unit:GetName() + _UnitList[unitName] = unitName + end -- end isAlive + end -- end if _unit + end -- end for + self.CtldUnits = _UnitList + + -- build unit menus + + local menucount = 0 + local menus = {} + for _, _unitName in pairs(self.CtldUnits) do + if not self.MenusDone[_unitName] then + local _unit = UNIT:FindByName(_unitName) -- Wrapper.Unit#UNIT + if _unit then + local _group = _unit:GetGroup() -- Wrapper.Group#GROUP + if _group then + -- get chopper capabilities + local unittype = _unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cantroops = capabilities.troops + local cancrates = capabilities.crates + -- top menu + local topmenu = MENU_GROUP:New(_group,"CTLD",nil) + local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) + local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) + local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, true):Refresh() + -- sub menus + -- sub menu crates management + if cancrates then + local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) + local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) + for _,_entry in pairs(self.Cargo_Crates) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Get crate for %s",entry.Name) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end + listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) + local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit):Refresh() + end + -- sub menu troops management + if cantroops then + local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) + for _,_entry in pairs(self.Cargo_Troops) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) + end + local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() + end + local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) + if unittype == "Hercules" then + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + else + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + end + self.MenusDone[_unitName] = true + end -- end group + end -- end unit + else -- menu build check + self:T(self.lid .. " Menus already done for this group!") + end -- end menu build check + end -- end for + return self + end + +--- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. +-- @param #CTLD self +-- @param #Name Name Unique name of this type of troop. E.g. "Anti-Air Small". +-- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. +-- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. +-- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops) + self:T(self.lid .. " AddTroopsCargo") + self.CargoCounter = self.CargoCounter + 1 + -- Troops are directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops) + table.insert(self.Cargo_Troops,cargo) + return self +end + +--- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. +-- @param #CTLD self +-- @param #Name Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. +-- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. +-- @param #number NoCrates Number of crates needed to build this cargo. +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates) + self:T(self.lid .. " AddCratesCargo") + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates) + table.insert(self.Cargo_Crates,cargo) + return self +end + +--- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance. +-- @param #CTLD self +-- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone. +function CTLD:AddZone(Zone) + self:T(self.lid .. " AddZone") + local zone = Zone -- #CTLD.CargoZone + if zone.type == CTLD.CargoZoneType.LOAD then + table.insert(self.pickupZones,zone) + --self:T"Registered LOAD zone " .. zone.name) + elseif zone.type == CTLD.CargoZoneType.DROP then + table.insert(self.dropOffZones,zone) + --self:T"Registered DROP zone " .. zone.name) + else + table.insert(self.wpZones,zone) + --self:T"Registered MOVE zone " .. zone.name) + end + return self +end + +--- User function - Activate Name #CTLD.CargoZone.Type ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneTyp ZoneType Type of zone this belongs to. +-- @param #boolean NewState (Optional) Set to true to activate, false to switch off. +function CTLD:ActivateZone(Name,ZoneType,NewState) + self:T(self.lid .. " AddZone") + local newstate = true + -- set optional in case we\'re deactivating + if not NewState or NewState == false then + newstate = false + end + -- get correct table + local zone = ZoneType -- #CTLD.CargoZone + local table = {} + if zone.type == CTLD.CargoZoneType.LOAD then + table = self.pickupZones + elseif zone.type == CTLD.CargoZoneType.DROP then + table = self.dropOffZones + else + table = self.wpZones + end + -- loop table + for _,_zone in pairs(table) do + local thiszone = _zone --#CTLD.CargoZone + if thiszone.name == Name then + thiszone.active = newstate + break + end + end + return self +end + +--- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneTyp ZoneType Type of zone this belongs to. +function CTLD:DeactivateZone(Name,ZoneType) + self:T(self.lid .. " AddZone") + self:ActivateZone(Name,ZoneType,false) + return self +end + +--- (Internal) Function to obtain a valid FM frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetFMBeacon(Name) + self:T(self.lid .. " _GetFMBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeFMFrequencies <= 1 then + self.FreeFMFrequencies = self.UsedFMFrequencies + self.UsedFMFrequencies = {} + end + --random + local FM = table.remove(self.FreeFMFrequencies, math.random(#self.FreeFMFrequencies)) + table.insert(self.UsedFMFrequencies, FM) + beacon.name = Name + beacon.frequency = FM / 1000000 + beacon.modulation = radio.modulation.FM + + return beacon +end + +--- (Internal) Function to obtain a valid UHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetUHFBeacon(Name) + self:T(self.lid .. " _GetUHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeUHFFrequencies <= 1 then + self.FreeUHFFrequencies = self.UsedUHFFrequencies + self.UsedUHFFrequencies = {} + end + --random + local UHF = table.remove(self.FreeUHFFrequencies, math.random(#self.FreeUHFFrequencies)) + table.insert(self.UsedUHFFrequencies, UHF) + beacon.name = Name + beacon.frequency = UHF / 1000000 + beacon.modulation = radio.modulation.AM + + return beacon +end + +--- (Internal) Function to obtain a valid VHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetVHFBeacon(Name) + self:T(self.lid .. " _GetVHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeVHFFrequencies <= 3 then + self.FreeVHFFrequencies = self.UsedVHFFrequencies + self.UsedVHFFrequencies = {} + end + --get random + local VHF = table.remove(self.FreeVHFFrequencies, math.random(#self.FreeVHFFrequencies)) + table.insert(self.UsedVHFFrequencies, VHF) + beacon.name = Name + beacon.frequency = VHF / 1000000 + beacon.modulation = radio.modulation.FM + return beacon +end + + +--- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. +-- Zones of type LOAD: Players load crates and troops here. +-- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. +-- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). +-- @param #CTLD self +-- @param #string Name Name of this zone, as in Mission Editor. +-- @param #string Type Type of this zone, #CTLD.CargoZoneType +-- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red +-- @param #string Active Is this zone currently active? +-- @param #string HasBeacon Does this zone have a beacon if it is active? +-- @return #CTLD self +function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon) + self:T(self.lid .. " AddCTLDZone") + + local ctldzone = {} -- #CTLD.CargoZone + ctldzone.active = Active or false + ctldzone.color = Color or SMOKECOLOR.Red + ctldzone.name = Name or "NONE" + ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType + ctldzone.hasbeacon = HasBeacon or false + + if HasBeacon then + ctldzone.fmbeacon = self:_GetFMBeacon(Name) + ctldzone.uhfbeacon = self:_GetUHFBeacon(Name) + ctldzone.vhfbeacon = self:_GetVHFBeacon(Name) + else + ctldzone.fmbeacon = nil + ctldzone.uhfbeacon = nil + ctldzone.vhfbeacon = nil + end + + self:AddZone(ctldzone) + return self +end + +--- (Internal) Function to show list of radio beacons +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_ListRadioBeacons(Group, Unit) + self:T(self.lid .. " _ListRadioBeacons") + local report = REPORT:New("Active Zone Beacons") + report:Add("------------------------------------------------------------") + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency * 1000 -- KHz + local UHF = UHFbeacon.frequency -- MHz + report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF),"|") + end + end + end + if report:GetCount() == 1 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + self:_SendMessage(report:Text(), 30, true, Group) + --local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) + return self +end + +--- (Internal) Add radio beacon to zone. Runs 30 secs. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @param #string Sound Name of soundfile. +-- @param #number Mhz Frequency in Mhz. +-- @param #number Modulation Modulation AM or FM. +function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation) + self:T(self.lid .. " _AddRadioBeacon") + local Zone = ZONE:FindByName(Name) + local Sound = Sound or "beacon.ogg" + if Zone then + local ZoneCoord = Zone:GetCoordinate() + local ZoneVec3 = ZoneCoord:GetVec3() + local Frequency = Mhz * 1000000 -- Freq in Hertz + local Sound = "l10n/DEFAULT/"..Sound + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight + end + return self +end + +--- (Internal) Function to refresh radio beacons +-- @param #CTLD self +function CTLD:_RefreshRadioBeacons() + self:T(self.lid .. " _RefreshRadioBeacons") + + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + local Sound = self.RadioSound + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency -- KHz + local UHF = UHFbeacon.frequency -- MHz + self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM) + self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM) + self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM) + end + end + end + return self +end + +--- (Internal) Function to see if a unit is in a specific zone type. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit Unit +-- @param #CTLD.CargoZoneType Zonetype Zonetype +-- @return #boolean Outcome Is in zone or not +-- @return #string name Closest zone name +-- @return #string zone Closest Core.Zone#ZONE object +-- @return #number distance Distance to closest zone +function CTLD:IsUnitInZone(Unit,Zonetype) + self:T(self.lid .. " IsUnitInZone") + local unitname = Unit:GetName() + --self:Tstring.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) + local zonetable = {} + local outcome = false + if Zonetype == CTLD.CargoZoneType.LOAD then + zonetable = self.pickupZones -- #table + elseif Zonetype == CTLD.CargoZoneType.DROP then + zonetable = self.dropOffZones -- #table + else + zonetable = self.wpZones -- #table + end + --- now see if we\'re in + local zonecoord = nil + local colorret = nil + local maxdist = 1000000 -- 100km + local zoneret = nil + local zonenameret = nil + for _,_cargozone in pairs(zonetable) do + local czone = _cargozone -- #CTLD.CargoZone + local unitcoord = Unit:GetCoordinate() + local zonename = czone.name + local zone = ZONE:FindByName(zonename) + zonecoord = zone:GetCoordinate() + local active = czone.active + local color = czone.color + local zoneradius = zone:GetRadius() + local distance = self:_GetDistance(zonecoord,unitcoord) + --self:Tstring.format("Check distance: %d",distance)) + if distance <= zoneradius and active then + outcome = true + end + if maxdist > distance then + maxdist = distance + zoneret = zone + zonenameret = zonename + colorret = color + end + end + --self:T{outcome, zonenameret, zoneret, maxdist}) + return outcome, zonenameret, zoneret, maxdist +end + +--- User function - Start smoke in a zone close to the Unit. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The Unit. +-- @param #boolean Flare If true, flare instead. +function CTLD:SmokeZoneNearBy(Unit, Flare) + self:T(self.lid .. " SmokeZoneNearBy") + -- table of #CTLD.CargoZone table + local unitcoord = Unit:GetCoordinate() + local Group = Unit:GetGroup() + local smokedistance = self.smokedistance + local smoked = false + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + local CZone = cargozone --#CTLD.CargoZone + local zonename = CZone.name + local zone = ZONE:FindByName(zonename) + local zonecoord = zone:GetCoordinate() + local active = CZone.active + local color = CZone.color + local distance = self:_GetDistance(zonecoord,unitcoord) + if distance < smokedistance and active then + -- smoke zone since we\'re nearby + if not Flare then + zonecoord:Smoke(color or SMOKECOLOR.White) + else + if color == SMOKECOLOR.Blue then color = FLARECOLOR.White end + zonecoord:Flare(color or FLARECOLOR.White) + end + local txt = "smoking" + if Flare then txt = "flaring" end + self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) + --local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) + smoked = true + end + end + end + if not smoked then + local distance = UTILS.MetersToNM(self.smokedistance) + self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) + --local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) + end + return self +end + + --- User - Function to add/adjust unittype capabilities. + -- @param #CTLD self + -- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission. + -- @param #boolean Cancrates Unit can load crates. + -- @param #boolean Cantroops Unit can load troops. + -- @param #number Cratelimit Unit can carry number of crates. + -- @param #number Trooplimit Unit can carry number of troops. + function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit) + self:T(self.lid .. " UnitCapabilities") + local unittype = nil + local unit = nil + if type(Unittype) == "string" then + unittype = Unittype + elseif type(Unittype) == "table" then + unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT + unittype = unit:GetTypeName() + else + return self + end + -- set capabilities + local capabilities = {} -- #CTLD.UnitCapabilities + capabilities.type = unittype + capabilities.crates = Cancrates or false + capabilities.troops = Cantroops or false + capabilities.cratelimit = Cratelimit or 0 + capabilities.trooplimit = Trooplimit or 0 + self.UnitTypes[unittype] = capabilities + return self + end + + --- (Internal) Check if a unit is hovering *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsCorrectHover(Unit) + self:T(self.lid .. " IsCorrectHover") + local outcome = false + -- see if we are in air and within parameters. + if self:IsUnitInAir(Unit) then + -- get speed and height + local uspeed = Unit:GetVelocityMPS() + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + local maxh = self.maximumHoverHeight -- 15 + local minh = self.minimumHoverHeight -- 5 + local mspeed = 2 -- 2 m/s + --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then + -- yep within parameters + outcome = true + end + end + return outcome + end + + --- (Internal) Check if a Hercules is flying *in parameters* for air drops. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsCorrectFlightParameters(Unit) + self:T(self.lid .. " IsCorrectFlightParameters") + local outcome = false + -- see if we are in air and within parameters. + if self:IsUnitInAir(Unit) then + -- get speed and height + local uspeed = Unit:GetVelocityMPS() + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + local maxh = self.HercMinAngels-- 1500m + local minh = self.HercMaxAngels -- 5000m + local mspeed = 2 -- 2 m/s + -- TODO:Add speed test for Herc, should not be above 280kph/150kn + --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + if (aheight <= maxh) and (aheight >= minh) then + -- yep within parameters + outcome = true + end + end + return outcome + end + + --- (Internal) List if a unit is hovering *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_ShowHoverParams(Group,Unit) + local inhover = self:IsCorrectHover(Unit) + local htxt = "true" + if not inhover then htxt = "false" end + local text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + return self + end + + --- (Internal) List if a Herc unit is flying *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_ShowFlightParams(Group,Unit) + local inhover = self:IsCorrectFlightParameters(Unit) + local htxt = "true" + if not inhover then htxt = "false" end + local minheight = UTILS.MetersToFeet(self.HercMinAngels) + local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + local text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) + return self + end + + + --- (Internal) Check if a unit is in a load zone and is hovering in parameters. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:CanHoverLoad(Unit) + self:T(self.lid .. " CanHoverLoad") + if self:IsHercules(Unit) then return false end + local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) + return outcome + end + + --- (Internal) Check if a unit is above ground. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsUnitInAir(Unit) + -- get speed and height + local minheight = self.minimumHoverHeight + if self.enableHercules and Unit:GetTypeName() == "Hercules" then + minheight = 5.1 -- herc is 5m AGL on the ground + end + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + if aheight >= minheight then + return true + else + return false + end + end + + --- (Internal) Autoload if we can do crates, have capacity free and are in a load zone. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #CTLD self + function CTLD:AutoHoverLoad(Unit) + self:T(self.lid .. " AutoHoverLoad") + -- get capabilities and current load + local unittype = Unit:GetTypeName() + local unitname = Unit:GetName() + local Group = Unit:GetGroup() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + if cancrates then + -- get load + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 + end + local load = cratelimit - numberonboard + local canload = self:CanHoverLoad(Unit) + if canload and load > 0 then + self:_LoadCratesNearby(Group,Unit) + end + end + return self + end + + --- (Internal) Run through all pilots and see if we autoload. + -- @param #CTLD self + -- @return #CTLD self + function CTLD:CheckAutoHoverload() + if self.hoverautoloading then + for _,_pilot in pairs (self.CtldUnits) do + local Unit = UNIT:FindByName(_pilot) + self:AutoHoverLoad(Unit) + end + end + return self + end + +------------------------------------------------------------------- +-- FSM functions +------------------------------------------------------------------- + + --- (Internal) FSM Function onafterStart. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStart(From, Event, To) + self:T({From, Event, To}) + if self.useprefix then + local prefix = self.prefixes + --self:T{prefix=prefix}) + if self.enableHercules then + --self:T("CTLD with prefixes and Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() + else + --self:T("CTLD with prefixes NO Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategoryHelicopter():FilterStart() + end + else + --self:T("CTLD NO prefixes NO Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() + end + -- Events + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:__Status(-5) + return self + end + + --- (Internal) FSM Function onbeforeStatus. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + self:_RefreshF10Menus() + self:_RefreshRadioBeacons() + self:CheckAutoHoverload() + return self + end + + --- (Internal) FSM Function onafterStatus. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStatus(From, Event, To) + self:T({From, Event, To}) + -- gather some stats + -- pilots + local pilots = 0 + for _,_pilot in pairs (self.CtldUnits) do + + pilots = pilots + 1 + end + + -- spawned cargo boxes curr in field + local boxes = 0 + for _,_pilot in pairs (self.Spawned_Cargo) do + boxes = boxes + 1 + end + + local cc = self.CargoCounter + local tc = self.TroopCounter + + if self.debug or self.verbose > 0 then + local text = string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc) + local m = MESSAGE:New(text,10,"CTLD"):ToAll() + end + self:__Status(-30) + return self + end + + --- (Internal) FSM Function onafterStop. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStop(From, Event, To) + self:T({From, Event, To}) + self:UnhandleEvent(EVENTS.PlayerEnterAircraft) + self:UnhandleEvent(EVENTS.PlayerEnterUnit) + self:UnhandleEvent(EVENTS.PlayerLeaveUnit) + return self + end + + --- (Internal) FSM Function onbeforeTroopsPickedUp. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + function CTLD:onbeforeTroopsPickedUp(From, Event, To, Group, Unit, Cargo) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeCratesPickedUp. + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsDeployed. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeCratesDropped. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeCratesBuild. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. + -- @return #CTLD self + function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsRTB. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit) + self:T({From, Event, To}) + return self + end + +end -- end do +------------------------------------------------------------------- +-- End Ops.CTLD.lua +------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 5ed71fba7..c0f53d960 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -45,6 +45,7 @@ -- @field #table tacanChannel List of TACAN channels available to the squadron. -- @field #number radioFreq Radio frequency in MHz the squad uses. -- @field #number radioModu Radio modulation the squad uses. +-- @field #number takeoffType Take of type. -- @extends Core.Fsm#FSM --- *It is unbelievable what a squadron of twelve aircraft did to tip the balance.* -- Adolf Galland @@ -87,12 +88,13 @@ SQUADRON = { --- SQUADRON class version. -- @field #string version -SQUADRON.version="0.5.0" +SQUADRON.version="0.5.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Parking spots for squadrons? -- DONE: Engage radius. -- DONE: Modex. -- DONE: Call signs. @@ -282,6 +284,41 @@ function SQUADRON:SetGrouping(nunits) return self end + +--- Set takeoff type. All assets of this squadron will be spawned with cold (default) or hot engines. +-- Spawning on runways is not supported. +-- @param #SQUADRON self +-- @param #string TakeoffType Take off type: "Cold" (default) or "Hot" with engines on. +-- @return #SQUADRON self +function SQUADRON:SetTakeoffType(TakeoffType) + TakeoffType=TakeoffType or "Cold" + if TakeoffType:lower()=="hot" then + self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot + elseif TakeoffType:lower()=="cold" then + self.takeoffType=COORDINATE.WaypointType.TakeOffParking + else + self.takeoffType=COORDINATE.WaypointType.TakeOffParking + end + return self +end + +--- Set takeoff type cold (default). +-- @param #SQUADRON self +-- @return #SQUADRON self +function SQUADRON:SetTakeoffCold() + self:SetTakeoffType("Cold") + return self +end + +--- Set takeoff type hot. +-- @param #SQUADRON self +-- @return #SQUADRON self +function SQUADRON:SetTakeoffHot() + self:SetTakeoffType("Hot") + return self +end + + --- Set mission types this squadron is able to perform. -- @param #SQUADRON self -- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type. diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index e7bec2087..077e75168 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -81,6 +81,8 @@ Ops/NavyGroup.lua Ops/Squadron.lua Ops/AirWing.lua Ops/Intelligence.lua +Ops/CSAR.lua +Ops/CTLD.lua AI/AI_Balancer.lua AI/AI_Air.lua