diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index ae408b71e..b09a22fc1 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1623,6 +1623,10 @@ WAREHOUSE = { -- @field #number rid The request ID of this asset. -- @field #boolean arrived If true, asset arrived at its destination. -- @field #number damage Damage of asset group in percent. +-- @field Ops.AirWing#AIRWING.Payload payload The payload of the asset. +-- @field Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup object. +-- @field #string squadname Name of the squadron this asset belongs to. +-- @field #number Treturned Time stamp when asset returned to the airwing. --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem @@ -1842,8 +1846,8 @@ WAREHOUSE.version="1.0.2" --- The WAREHOUSE constructor. Creates a new WAREHOUSE object from a static object. Parameters like the coalition and country are taken from the static object structure. -- @param #WAREHOUSE self --- @param Wrapper.Static#STATIC warehouse The physical structure representing the warehouse. --- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static +-- @param Wrapper.Static#STATIC warehouse The physical structure representing the warehouse. Can also be a @{Wrapper.Unit#UNIT}. +-- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static/unit representing the warehouse. -- @return #WAREHOUSE self function WAREHOUSE:New(warehouse, alias) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 4081249a0..b44f10f6b 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -81,8 +81,12 @@ __Moose.Include( 'Scripts/Moose/Ops/OpsGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Cohort.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Platoon.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Legion.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Brigade.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) __Moose.Include( 'Scripts/Moose/Ops/WingCommander.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ChiefOfStaff.lua' ) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 65e01063b..22737c5dc 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -125,10 +125,6 @@ AIRWING = { --- Squadron asset. -- @type AIRWING.SquadronAsset --- @field #AIRWING.Payload payload The payload of the asset. --- @field Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup object. --- @field #string squadname Name of the squadron this asset belongs to. --- @field #number Treturned Time stamp when asset returned to the airwing. -- @extends Functional.Warehouse#WAREHOUSE.Assetitem --- Payload data. @@ -251,7 +247,7 @@ function AIRWING:New(warehousename, airwingname) -- @param #string Event Event. -- @param #string To To state. -- @param Ops.Squadron#SQUADRON Squadron The asset squadron. - -- @param #AIRWING.SquadronAsset Asset The asset that returned. + -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. return self end @@ -513,7 +509,7 @@ end --- Return payload from asset back to stock. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset The squadron asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The squadron asset. function AIRWING:ReturnPayloadFromAsset(asset) local payload=asset.payload @@ -596,7 +592,7 @@ end --- Get squadron of an asset. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset Asset The squadron asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The squadron asset. -- @return Ops.Squadron#SQUADRON The squadron object. function AIRWING:GetSquadronOfAsset(Asset) return self:GetSquadron(Asset.squadname) @@ -604,7 +600,7 @@ end --- Remove asset from squadron. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset Asset The squad asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The squad asset. function AIRWING:RemoveAssetFromSquadron(Asset) local squad=self:GetSquadronOfAsset(Asset) if squad then @@ -908,7 +904,7 @@ function AIRWING:onafterStatus(From, Event, To) 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) + text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssets(true), #squadron.assets, callsign, modex, skill) end self:I(self.lid..text) end @@ -1105,7 +1101,7 @@ end --- Check how many AWACS missions are assigned and add number of missing missions. -- @param #AIRWING self -- @param Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup. --- @return #AIRWING.SquadronAsset The tanker asset. +-- @return Functional.Warehouse#WAREHOUSE.Assetitem The tanker asset. function AIRWING:GetTankerForFlight(flightgroup) local tankers=self:GetAssetsOnMission(AUFTRAG.Type.TANKER) @@ -1114,7 +1110,7 @@ function AIRWING:GetTankerForFlight(flightgroup) local tankeropt={} for _,_tanker in pairs(tankers) do - local tanker=_tanker --#AIRWING.SquadronAsset + local tanker=_tanker --Functional.Warehouse#WAREHOUSE.Assetitem -- Check that donor and acceptor use the same refuelling system. if flightgroup.refueltype and flightgroup.refueltype==tanker.flightgroup.tankertype then @@ -1214,7 +1210,7 @@ function AIRWING:_GetNextMission() local remove={} local gotpayload={} for i=1,#assets do - local asset=assets[i] --#AIRWING.SquadronAsset + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem -- Get payload for the asset. if not asset.payload then @@ -1231,7 +1227,7 @@ function AIRWING:_GetNextMission() -- Now remove assets for which we don't have a payload. for i=#assets,1,-1 do - local asset=assets[i] --#AIRWING.SquadronAsset + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem for _,uid in pairs(remove) do if uid==asset.uid then table.remove(assets, i) @@ -1255,7 +1251,7 @@ function AIRWING:_GetNextMission() -- Assign assets to mission. for i=1,mission.nassets do - local asset=assets[i] --#AIRWING.SquadronAsset + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem -- Should not happen as we just checked! if not asset.payload then @@ -1268,7 +1264,7 @@ function AIRWING:_GetNextMission() -- Now return the remaining payloads. for i=mission.nassets+1,#assets do - local asset=assets[i] --#AIRWING.SquadronAsset + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem for _,uid in pairs(gotpayload) do if uid==asset.uid then self:ReturnPayloadFromAsset(asset) @@ -1288,7 +1284,7 @@ end --- Calculate the mission score of an asset. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset Asset +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset -- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -- @return #number Mission score. @@ -1351,7 +1347,7 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) local distmin=math.huge local distmax=0 for _,_asset in pairs(assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if asset.spawned then local group=GROUP:FindByName(asset.spawngroupname) @@ -1373,15 +1369,15 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) -- Calculate the mission score of all assets. for _,_asset in pairs(assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem --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. local function optimize(a, b) - local assetA=a --#AIRWING.SquadronAsset - local assetB=b --#AIRWING.SquadronAsset + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem -- Higher score wins. If equal score ==> closer wins. -- TODO: Need to include the distance in a smarter way! @@ -1392,7 +1388,7 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) -- Remove distance parameter. local text=string.format("Optimized assets for %s mission (payload=%s):", Mission.type, tostring(includePayload)) for i,Asset in pairs(assets) do - local asset=Asset --#AIRWING.SquadronAsset + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem text=text..string.format("\n%s %s: score=%d, distance=%.1f km", asset.squadname, asset.spawngroupname, asset.score, asset.dist/1000) asset.dist=nil asset.score=nil @@ -1425,7 +1421,7 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) local Assetlist={} for _,_asset in pairs(Mission.assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if asset.spawned then @@ -1452,7 +1448,7 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) --local text=string.format("Requesting assets for mission %s:", Mission.name) for i,_asset in pairs(Assetlist) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem -- Set asset to requested! Important so that new requests do not use this asset! asset.requested=true @@ -1493,7 +1489,7 @@ function AIRWING:onafterMissionCancel(From, Event, To, Mission) else for _,_asset in pairs(Mission.assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem local flightgroup=asset.flightgroup @@ -1519,7 +1515,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #AIRWING.SquadronAsset asset The asset that has just been added. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that has just been added. -- @param #string assignment The (optional) assignment for the asset. function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) @@ -1604,7 +1600,7 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Ops.Squadron#SQUADRON Squadron The asset squadron. --- @param #AIRWING.SquadronAsset Asset The asset that returned. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. 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))) @@ -1634,7 +1630,7 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Group#GROUP group The group spawned. --- @param #AIRWING.SquadronAsset asset The asset that was spawned. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that was spawned. -- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) @@ -1731,7 +1727,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #AIRWING.SquadronAsset asset The asset that is dead. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that is dead. -- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. function AIRWING:onafterAssetDead(From, Event, To, asset, request) @@ -1793,7 +1789,7 @@ function AIRWING:onafterRequest(From, Event, To, Request) if Mission and assets then for _,_asset in pairs(assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem -- This would be the place to modify the asset table before the asset is spawned. end @@ -1820,7 +1816,7 @@ function AIRWING:onafterSelfRequest(From, Event, To, groupset, request) local mission=self:GetMissionByID(request.assignment) for _,_asset in pairs(request.assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem end for _,_group in pairs(groupset:GetSet()) do @@ -1835,7 +1831,7 @@ end --- Create a new flight group after an asset was spawned. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset The asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. -- @return Ops.FlightGroup#FLIGHTGROUP The created flightgroup object. function AIRWING:_CreateFlightGroup(asset) @@ -1857,7 +1853,7 @@ end --- Check if an asset is currently on a mission (STARTED or EXECUTING). -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset The asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. -- @param #table MissionTypes Types on mission to be checked. Default all. -- @return #boolean If true, asset has at least one mission of that type in the queue. function AIRWING:IsAssetOnMission(asset, MissionTypes) @@ -1900,7 +1896,7 @@ function AIRWING:IsAssetOnMission(asset, MissionTypes) if mission:IsNotOver() then for _,_asset in pairs(mission.assets) do - local sqasset=_asset --#AIRWING.SquadronAsset + local sqasset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if sqasset.uid==asset.uid then return true @@ -1917,7 +1913,7 @@ end --- Get the current mission of the asset. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset The asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. -- @return Ops.Auftrag#AUFTRAG Current mission or *nil*. function AIRWING:GetAssetCurrentMission(asset) @@ -2026,33 +2022,19 @@ function AIRWING:CountMissionsInQueue(MissionTypes) return N end ---- Count total number of assets. This is the sum of all squadron assets. --- @param #AIRWING self --- @return #number Amount of asset groups. -function AIRWING:CountAssets() - - local N=0 - - for _,_squad in pairs(self.squadrons) do - local squad=_squad --Ops.Squadron#SQUADRON - N=N+#squad.assets - end - - return N -end - - --- Count total number of assets that are in the warehouse stock (not spawned). -- @param #AIRWING self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. -- @return #number Amount of asset groups in stock. -function AIRWING:CountAssetsInStock(MissionTypes) +function AIRWING:CountAssets(InStock, MissionTypes, Attributes) local N=0 for _,_squad in pairs(self.squadrons) do local squad=_squad --Ops.Squadron#SQUADRON - N=N+squad:CountAssetsInStock(MissionTypes) + N=N+squad:CountAssets(InStock, MissionTypes, Attributes) end return N @@ -2077,7 +2059,7 @@ function AIRWING:CountAssetsOnMission(MissionTypes, Squadron) if self:CheckMissionType(mission.type, MissionTypes or AUFTRAG.Type) then for _,_asset in pairs(mission.assets or {}) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if Squadron==nil or Squadron.name==asset.squadname then @@ -2115,7 +2097,7 @@ function AIRWING:GetAssetsOnMission(MissionTypes) if self:CheckMissionType(mission.type, MissionTypes) then for _,_asset in pairs(mission.assets or {}) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem table.insert(assets, asset) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 700bf2986..abda972be 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1224,8 +1224,10 @@ end -- @param Core.Set#SET_ZONE ZoneSet The recon zones. -- @param #number Speed Speed in knots. -- @param #number Altitude Altitude in feet. Only for airborne units. Default 2000 feet ASL. +-- @param #boolean Adinfinitum If true, the group will start over again after reaching the final zone. +-- @param #boolean Randomly If true, the group will select a random zone. -- @return #AUFTRAG self -function AUFTRAG:NewRECON(ZoneSet, Speed, Altitude) +function AUFTRAG:NewRECON(ZoneSet, Speed, Altitude, Adinfinitum, Randomly) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) @@ -1238,8 +1240,10 @@ function AUFTRAG:NewRECON(ZoneSet, Speed, Altitude) mission.missionFraction=0.5 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude) or UTILS.FeetToMeters(2000) - + mission.DCStask=mission:GetDCSMissionTask() + mission.DCStask.params.adinfitum=Adinfinitum + mission.DCStask.params.randomly=Randomly return mission end diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua new file mode 100644 index 000000000..65e65c6de --- /dev/null +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -0,0 +1,273 @@ +--- **Ops** - Brigade Warehouse. +-- +-- **Main Features:** +-- +-- * Manage platoons +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- @module Ops.Brigade +-- @image OPS_Brigade.png + + +--- BRIGADE class. +-- @type BRIGADE +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity of output. +-- @field Ops.General#GENERAL general The genral responsible for this brigade. +-- @extends Ops.Legion#LEGION + +--- Be surprised! +-- +-- === +-- +-- # The BRIGADE Concept +-- +-- An BRIGADE consists of multiple PLATOONS. These platoons "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed. +-- +-- +-- @field #BRIGADE +BRIGADE = { + ClassName = "BRIGADE", + verbose = 3, + genral = nil, +} + + +--- BRIGADE class version. +-- @field #string version +BRIGADE.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot! + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new BRIGADE class object. +-- @param #BRIGADE self +-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse. +-- @param #string BrigadeName Name of the brigade. +-- @return #BRIGADE self +function BRIGADE:New(WarehouseName, BrigadeName) + + -- Inherit everything from LEGION class. + local self=BASE:Inherit(self, LEGION:New(WarehouseName, BrigadeName)) -- #BRIGADE + + -- Nil check. + if not self then + BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName)) + return nil + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("BRIGADE %s | ", self.alias) + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "PlatoonOnMission", "*") -- Add a (mission) request to the warehouse. + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add a platoon to the brigade. +-- @param #BRIGADE self +-- @param Ops.Platoon#PLATOON Platoon The platoon object. +-- @return #BRIGADE self +function BRIGADE:AddPlatoon(Platoon) + + -- Add squadron to airwing. + table.insert(self.cohorts, Platoon) + + -- Add assets to squadron. + self:AddAssetToPlatoon(Platoon, Platoon.Ngroups) + + -- Set airwing to squadron. + Platoon:SetBrigade(self) + + -- Start squadron. + if Platoon:IsStopped() then + Platoon:Start() + end + + return self +end + + + +--- Add asset group(s) to platoon. +-- @param #BRIGADE self +-- @param Ops.Platoon#PLATOON Platoon The platoon object. +-- @param #number Nassets Number of asset groups to add. +-- @return #BRIGADE self +function BRIGADE:AddAssetToPlatoon(Platoon, Nassets) + + if Platoon then + + -- Get the template group of the squadron. + local Group=GROUP:FindByName(Platoon.templatename) + + if Group then + + -- Debug text. + local text=string.format("Adding asset %s to platoon %s", Group:GetName(), Platoon.name) + self:T(self.lid..text) + + -- Add assets to airwing warehouse. + self:AddAsset(Group, Nassets, nil, nil, nil, nil, Platoon.skill, Platoon.livery, Platoon.name) + + else + self:E(self.lid.."ERROR: Group does not exist!") + end + + else + self:E(self.lid.."ERROR: Squadron does not exit!") + end + + return self +end + +--- Get platoon by name. +-- @param #BRIGADE self +-- @param #string PlatoonName Name of the platoon. +-- @return Ops.Platoon#PLATOON The Platoon object. +function BRIGADE:GetPlatoon(PlatoonName) + + local platoon=self:_GetCohort(PlatoonName) + + return platoon +end + +--- Get platoon of an asset. +-- @param #BRIGADE self +-- @param Ops.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset. +-- @return Ops.Platoon#PLATOON The platoon object. +function BRIGADE:GetSquadronOfAsset(Asset) + local platoon=self:GetPlatoon(Asset.squadname) + return platoon +end + +--- Remove asset from squadron. +-- @param #BRIGADE self +-- @param #BRIGADE.SquadronAsset Asset The squad asset. +function BRIGADE:RemoveAssetFromSquadron(Asset) + local squad=self:GetSquadronOfAsset(Asset) + if squad then + squad:DelAsset(Asset) + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start BRIGADE FSM. +-- @param #BRIGADE self +function BRIGADE:onafterStart(From, Event, To) + + -- Start parent Warehouse. + self:GetParent(self, BRIGADE).onafterStart(self, From, Event, To) + + -- Info. + self:I(self.lid..string.format("Starting BRIGADE v%s", BRIGADE.version)) + +end + +--- Update status. +-- @param #BRIGADE self +function BRIGADE:onafterStatus(From, Event, To) + + -- Status of parent Warehouse. + self:GetParent(self).onafterStatus(self, From, Event, To) + + local fsmstate=self:GetState() + + env.info("FF Brigade status "..fsmstate) + + -- General info: + if self.verbose>=1 then + + -- Count missions not over yet. + local Nmissions=self:CountMissionsInQueue() + + -- 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, Squads=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets) + self:I(self.lid..text) + end + + ------------------ + -- Mission Info -- + ------------------ + if self.verbose>=2 then + local text=string.format("Missions Total=%d:", #self.missionqueue) + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --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 -- + ------------------- + if self.verbose>=3 then + local text="Platoons:" + for i,_squadron in pairs(self.cohorts) 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:CountAssets(true), #squadron.assets, callsign, modex, skill) + end + self:I(self.lid..text) + end + + -------------- + -- Mission --- + -------------- + + -- Check if any missions should be cancelled. + self:_CheckMissions() + + -- Get next mission. + local mission=self:_GetNextMission() + + -- Request mission execution. + if mission then + self:MissionRequest(mission) + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index fcb26abc6..963886449 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -66,6 +66,19 @@ CHIEF.DEFCON = { RED="Red", } +--- Strategy. +-- @type CHIEF.Strategy +-- @field #string DEFENSIVE Only target in our own terretory are engaged. +-- @field #string OFFENSIVE Targets in own terretory and yellow zones are engaged. +-- @field #string AGGRESSIVE Targets in own terretroy, yellow zones and engage zones are engaged. +-- @field #string TOTALWAR Anything is engaged anywhere. +CHIEF.Strategy = { + DEFENSIVE="Defensive", + OFFENSIVE="Offensive", + AGGRESSIVE="Aggressive", + TOTALWAR="Total War" +} + --- CHIEF class version. -- @field #string version CHIEF.version="0.0.1" @@ -74,6 +87,10 @@ CHIEF.version="0.0.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Get list of own assets and capabilities. +-- TODO: Get list/overview of enemy assets etc. +-- TODO: Put all contacts into target list. Then make missions from them. +-- TODO: Set of interesting zones. -- TODO: Define A2A and A2G parameters. -- DONE: Add/remove spawned flightgroups to detection set. -- DONE: Borderzones. @@ -113,6 +130,8 @@ function CHIEF:New(AgentSet, Coalition) self:AddTransition("*", "Defcon", "*") -- Change defence condition. + self:AddTransition("*", "Stategy", "*") -- Change strategy condition. + self:AddTransition("*", "DeclareWar", "*") -- Declare War. ------------------------ @@ -393,6 +412,7 @@ function CHIEF:onafterStatus(From, Event, To) if contact.mission and contact.mission:IsNotOver() then + -- Debug info. local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name) MESSAGE:New(text, 120, "CHIEF"):ToAll() self:I(self.lid..text) @@ -473,24 +493,58 @@ function CHIEF:onafterStatus(From, Event, To) -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() - local text=string.format("Defcon=%s Missions=%d, Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) + --- + -- Mission Queue + --- + + local Nassets=self.wingcommander:CountAssets() + + local text=string.format("Defcon=%s Assets=%d, Missions=%d, Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, Nassets, #self.missionqueue, #self.Contacts, Nyellow, Nred) + self:I(self.lid..text) + + --- + -- Assets + --- + + local text="Assets:" + for _,missiontype in pairs(AUFTRAG.Type) do + local N=self.wingcommander:CountAssets(nil, missiontype) + if N>0 then + text=text..string.format("\n- %s %d", missiontype, N) + end + end + self:I(self.lid..text) + local text="Assets:" + for _,attribute in pairs(WAREHOUSE.Attribute) do + local N=self.wingcommander:CountAssets(nil, nil, attribute) + if N>0 or self.verbose>=10 then + text=text..string.format("\n- %s %d", attribute, N) + end + end self:I(self.lid..text) --- -- Target Queue --- - for _,_target in pairs(self.targetqueue) do - local target=_target --Ops.Target#TARGET - - if target:IsAlive() then - - if self:CheckTargetInZones(target, self.borderzoneset) then + if #self.targetqueue>0 then + local text="Targets:" + for i,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + + text=text..string.format("\n[%d] %s: Category=%s, alive=%s [%.1f/%.1f]", i, target:GetName(), target.category, tostring(target:IsAlive()), target:GetLife(), target:GetLife0()) + + + if target:IsAlive() then + + if self:CheckTargetInZones(target, self.borderzoneset) then + + end end end - + self:I(self.lid..text) end --- diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua new file mode 100644 index 000000000..2d28c1a1b --- /dev/null +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -0,0 +1,992 @@ +--- **Ops** - Cohort encompassed all characteristics of SQUADRONs, PLATOONs and FLOTILLAs. +-- +-- **Main Features:** +-- +-- * Set parameters like livery, skill valid for all platoon members. +-- * Define modex and callsigns. +-- * Define mission types, this platoon can perform (see Ops.Auftrag#AUFTRAG). +-- * Pause/unpause platoon operations. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Cohort +-- @image OPS_Cohort.png + + +--- COHORT class. +-- @type COHORT +-- @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 #string name Name of the platoon. +-- @field #string templatename Name of the template group. +-- @field #string aircrafttype Type of the airframe the platoon is using. +-- @field Wrapper.Group#GROUP templategroup Template group. +-- @field #table assets Cohort assets. +-- @field #table missiontypes Capabilities (mission types and performances) of the platoon. +-- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight. +-- @field #number repairtime Time in seconds for each +-- @field #string livery Livery of the platoon. +-- @field #number skill Skill of platoon members. +-- @field Ops.Legion#LEGION legion The LEGION object the cohort belongs to. +-- @field #number Ngroups Number of asset flight groups this platoon has. +-- @field #number engageRange Mission range in meters. +-- @field #string attribute Generalized attribute of the platoon template group. +-- @field #table tacanChannel List of TACAN channels available to the platoon. +-- @field #number radioFreq Radio frequency in MHz the cohort uses. +-- @field #number radioModu Radio modulation the cohort uses. +-- @field #table tacanChannel List of TACAN channels available to the cohort. +-- @extends Core.Fsm#FSM + +--- *It is unbelievable what a platoon of twelve aircraft did to tip the balance.* -- Adolf Galland +-- +-- === +-- +-- ![Banner Image](..\Presentations\OPS\Cohort\_Main.png) +-- +-- # The COHORT Concept +-- +-- A COHORT is essential part of an BRIGADE and consists of **one** type of aircraft. +-- +-- +-- +-- @field #COHORT +COHORT = { + ClassName = "COHORT", + verbose = 0, + lid = nil, + name = nil, + templatename = nil, + assets = {}, + missiontypes = {}, + repairtime = 0, + maintenancetime= 0, + livery = nil, + skill = nil, + legion = nil, + Ngroups = nil, + engageRange = nil, + tacanChannel = {}, +} + +--- COHORT class version. +-- @field #string version +COHORT.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot! +-- TODO: Make general so that PLATOON and SQUADRON can inherit this class. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new COHORT object and start the FSM. +-- @param #COHORT self +-- @param #string TemplateGroupName Name of the template group. +-- @param #number Ngroups Number of asset groups of this platoon. Default 3. +-- @param #string CohortName Name of the platoon, e.g. "VFA-37". +-- @return #COHORT self +function COHORT:New(TemplateGroupName, Ngroups, CohortName) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #COHORT + + -- Name of the template group. + self.templatename=TemplateGroupName + + -- Cohort name. + self.name=tostring(CohortName or TemplateGroupName) + + -- Set some string id for output to DCS.log file. + self.lid=string.format("COHORT %s | ", self.name) + + -- Template group. + self.templategroup=GROUP:FindByName(self.templatename) + + -- Check if template group exists. + if not self.templategroup then + self:E(self.lid..string.format("ERROR: Template group %s does not exist!", tostring(self.templatename))) + return nil + end + + -- Defaults. + self.Ngroups=Ngroups or 3 + self:SetMissionRange() + self:SetSkill(AI.Skill.GOOD) + + -- Everyone can ORBIT. + --self:AddMissionCapability(AUFTRAG.Type.ORBIT) + + -- Generalized attribute. + self.attribute=self.templategroup:GetAttribute() + + -- Aircraft type. + self.aircrafttype=self.templategroup:GetTypeName() + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "OnDuty") -- Start FSM. + self:AddTransition("*", "Status", "*") -- Status update. + + self:AddTransition("OnDuty", "Pause", "Paused") -- Pause platoon. + self:AddTransition("Paused", "Unpause", "OnDuty") -- Unpause platoon. + + self:AddTransition("*", "Stop", "Stopped") -- Stop platoon. + + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the COHORT. Initializes parameters and starts event handlers. + -- @function [parent=#COHORT] Start + -- @param #COHORT self + + --- Triggers the FSM event "Start" after a delay. Starts the COHORT. Initializes parameters and starts event handlers. + -- @function [parent=#COHORT] __Start + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the COHORT and all its event handlers. + -- @param #COHORT self + + --- Triggers the FSM event "Stop" after a delay. Stops the COHORT and all its event handlers. + -- @function [parent=#COHORT] __Stop + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#COHORT] Status + -- @param #COHORT self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#COHORT] __Status + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set livery painted on all platoon aircraft. +-- Note that the livery name in general is different from the name shown in the mission editor. +-- +-- Valid names are the names of the **livery directories**. Check out the folder in your DCS installation for: +-- +-- * Full modules: `DCS World OpenBeta\CoreMods\aircraft\\Liveries\\` +-- * AI units: `DCS World OpenBeta\Bazar\Liveries\\` +-- +-- The folder name `` is the string you want. +-- +-- Or personal liveries you have installed somewhere in your saved games folder. +-- +-- @param #COHORT self +-- @param #string LiveryName Name of the livery. +-- @return #COHORT self +function COHORT:SetLivery(LiveryName) + self.livery=LiveryName + return self +end + +--- Set skill level of all platoon team members. +-- @param #COHORT self +-- @param #string Skill Skill of all flights. +-- @usage myplatoon:SetSkill(AI.Skill.EXCELLENT) +-- @return #COHORT self +function COHORT:SetSkill(Skill) + self.skill=Skill + return self +end + +--- Set verbosity level. +-- @param #COHORT self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #COHORT self +function COHORT:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + +--- Set turnover and repair time. If an asset returns from a mission, it will need some time until the asset is available for further missions. +-- @param #COHORT self +-- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 0 min. +-- @param #number RepairTime Time in minutes it takes to repair a flight for each life point taken. Default is 0 min. +-- @return #COHORT self +function COHORT:SetTurnoverTime(MaintenanceTime, RepairTime) + self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0 + self.repairtime=RepairTime and RepairTime*60 or 0 + return self +end + +--- Set radio frequency and modulation the cohort uses. +-- @param #COHORT self +-- @param #number Frequency Radio frequency in MHz. Default 251 MHz. +-- @param #number Modulation Radio modulation. Default 0=AM. +-- @usage myplatoon:SetSkill(AI.Skill.EXCELLENT) +-- @return #COHORT self +function COHORT:SetRadio(Frequency, Modulation) + self.radioFreq=Frequency or 251 + self.radioModu=Modulation or radio.modulation.AM + return self +end + +--- Set number of units in groups. +-- @param #COHORT self +-- @param #number nunits Number of units. Must be >=1 and <=4. Default 2. +-- @return #COHORT self +function COHORT:SetGrouping(nunits) + self.ngrouping=nunits or 2 + if self.ngrouping<1 then self.ngrouping=1 end + if self.ngrouping>4 then self.ngrouping=4 end + return self +end + +--- Set mission types this platoon is able to perform. +-- @param #COHORT self +-- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type. +-- @param #number Performance Performance describing how good this mission can be performed. Higher is better. Default 50. Max 100. +-- @return #COHORT self +function COHORT:AddMissionCapability(MissionTypes, Performance) + + -- Ensure Missiontypes is a table. + if MissionTypes and type(MissionTypes)~="table" then + MissionTypes={MissionTypes} + end + + -- Set table. + self.missiontypes=self.missiontypes or {} + + for _,missiontype in pairs(MissionTypes) do + + -- Check not to add the same twice. + if self:CheckMissionCapability(missiontype, self.missiontypes) then + self:E(self.lid.."WARNING: Mission capability already present! No need to add it twice.") + -- TODO: update performance. + else + + local capability={} --Ops.Auftrag#AUFTRAG.Capability + capability.MissionType=missiontype + capability.Performance=Performance or 50 + table.insert(self.missiontypes, capability) + + end + end + + -- Debug info. + self:T2(self.missiontypes) + + return self +end + +--- Get mission types this platoon is able to perform. +-- @param #COHORT self +-- @return #table Table of mission types. Could be empty {}. +function COHORT:GetMissionTypes() + + local missiontypes={} + + for _,Capability in pairs(self.missiontypes) do + local capability=Capability --Ops.Auftrag#AUFTRAG.Capability + table.insert(missiontypes, capability.MissionType) + end + + return missiontypes +end + +--- Get mission capabilities of this platoon. +-- @param #COHORT self +-- @return #table Table of mission capabilities. +function COHORT:GetMissionCapabilities() + return self.missiontypes +end + +--- Get mission performance for a given type of misson. +-- @param #COHORT self +-- @param #string MissionType Type of mission. +-- @return #number Performance or -1. +function COHORT:GetMissionPeformance(MissionType) + + for _,Capability in pairs(self.missiontypes) do + local capability=Capability --Ops.Auftrag#AUFTRAG.Capability + if capability.MissionType==MissionType then + return capability.Performance + end + end + + return -1 +end + +--- Set max mission range. Only missions in a circle of this radius around the platoon airbase are executed. +-- @param #COHORT self +-- @param #number Range Range in NM. Default 100 NM. +-- @return #COHORT self +function COHORT:SetMissionRange(Range) + self.engageRange=UTILS.NMToMeters(Range or 100) + return self +end + +--- Set call sign. +-- @param #COHORT self +-- @param #number Callsign Callsign from CALLSIGN.Aircraft, e.g. "Chevy" for CALLSIGN.Aircraft.CHEVY. +-- @param #number Index Callsign index, Chevy-**1**. +-- @return #COHORT self +function COHORT:SetCallsign(Callsign, Index) + self.callsignName=Callsign + self.callsignIndex=Index + return self +end + +--- Set modex. +-- @param #COHORT self +-- @param #number Modex A number like 100. +-- @param #string Prefix A prefix string, which is put before the `Modex` number. +-- @param #string Suffix A suffix string, which is put after the `Modex` number. +-- @return #COHORT self +function COHORT:SetModex(Modex, Prefix, Suffix) + self.modex=Modex + self.modexPrefix=Prefix + self.modexSuffix=Suffix + return self +end + +--- Set Legion. +-- @param #COHORT self +-- @param Ops.Legion#LEGION Legion The Legion. +-- @return #COHORT self +function COHORT:SetLegion(Legion) + self.legion=Legion + return self +end + +--- Add asset to cohort. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The warehouse asset. +-- @return #COHORT self +function COHORT:AddAsset(Asset) + self:T(self.lid..string.format("Adding asset %s of type %s", Asset.spawngroupname, Asset.unittype)) + Asset.squadname=self.name + table.insert(self.assets, Asset) + return self +end + +--- Remove asset from chort. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. +-- @return #COHORT self +function COHORT:DelAsset(Asset) + for i,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + if Asset.uid==asset.uid then + self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname)) + table.remove(self.assets, i) + break + end + end + return self +end + +--- Remove asset group from cohort. +-- @param #COHORT self +-- @param #string GroupName Name of the asset group. +-- @return #COHORT self +function COHORT:DelGroup(GroupName) + for i,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + if GroupName==asset.spawngroupname then + self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname)) + table.remove(self.assets, i) + break + end + end + return self +end + +--- Get name of the platoon +-- @param #COHORT self +-- @return #string Name of the platoon. +function COHORT:GetName() + return self.name +end + +--- Get radio frequency and modulation. +-- @param #COHORT self +-- @return #number Radio frequency in MHz. +-- @return #number Radio Modulation (0=AM, 1=FM). +function COHORT:GetRadio() + return self.radioFreq, self.radioModu +end + +--- Create a callsign for the asset. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The warehouse asset. +-- @return #COHORT self +function COHORT:GetCallsign(Asset) + + if self.callsignName then + + Asset.callsign={} + + for i=1,Asset.nunits do + + local callsign={} + + callsign[1]=self.callsignName + callsign[2]=math.floor(self.callsigncounter / 10) + callsign[3]=self.callsigncounter % 10 + if callsign[3]==0 then + callsign[3]=1 + self.callsigncounter=self.callsigncounter+2 + else + self.callsigncounter=self.callsigncounter+1 + end + + Asset.callsign[i]=callsign + + self:T3({callsign=callsign}) + + --TODO: there is also a table entry .name, which is a string. + end + + + end + +end + +--- Create a modex for the asset. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The warehouse asset. +-- @return #COHORT self +function COHORT:GetModex(Asset) + + if self.modex then + + Asset.modex={} + + for i=1,Asset.nunits do + + Asset.modex[i]=string.format("%03d", self.modex+self.modexcounter) + + self.modexcounter=self.modexcounter+1 + + self:T3({modex=Asset.modex[i]}) + + end + + end + +end + + +--- Add TACAN channels to the platoon. Note that channels can only range from 1 to 126. +-- @param #COHORT self +-- @param #number ChannelMin Channel. +-- @param #number ChannelMax Channel. +-- @return #COHORT self +-- @usage mysquad:AddTacanChannel(64,69) -- adds channels 64, 65, 66, 67, 68, 69 +function COHORT:AddTacanChannel(ChannelMin, ChannelMax) + + ChannelMax=ChannelMax or ChannelMin + + if ChannelMin>126 then + self:E(self.lid.."ERROR: TACAN Channel must be <= 126! Will not add to available channels") + return self + end + if ChannelMax>126 then + self:E(self.lid.."WARNING: TACAN Channel must be <= 126! Adjusting ChannelMax to 126") + ChannelMax=126 + end + + for i=ChannelMin,ChannelMax do + self.tacanChannel[i]=true + end + + return self +end + +--- Get an unused TACAN channel. +-- @param #COHORT self +-- @return #number TACAN channel or *nil* if no channel is free. +function COHORT:FetchTacan() + + -- Get the smallest free channel if there is one. + local freechannel=nil + for channel,free in pairs(self.tacanChannel) do + if free then + if freechannel==nil or channel=2 and #self.assets>0 then + + local text="" + for j,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Text. + text=text..string.format("\n[%d] %s (%s*%d): ", j, asset.spawngroupname, asset.unittype, asset.nunits) + + if asset.spawned then + + --- + -- Spawned + --- + + -- Mission info. + local mission=self.legion and self.legion:GetAssetCurrentMission(asset) or false + if mission then + local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate())) or 0 + text=text..string.format("Mission %s - %s: Status=%s, Dist=%.1f NM", mission.name, mission.type, mission.status, distance) + else + text=text.."Mission None" + end + + -- Flight status. + text=text..", Flight: " + if asset.flightgroup and asset.flightgroup:IsAlive() then + local status=asset.flightgroup:GetState() + local fuelmin=asset.flightgroup:GetFuelMin() + local fuellow=asset.flightgroup:IsFuelLow() + local fuelcri=asset.flightgroup:IsFuelCritical() + + text=text..string.format("%s Fuel=%d", status, fuelmin) + if fuelcri then + text=text.." (Critical!)" + elseif fuellow then + text=text.." (Low)" + end + + local lifept, lifept0=asset.flightgroup:GetLifePoints() + text=text..string.format(", Life=%d/%d", lifept, lifept0) + + local ammo=asset.flightgroup:GetAmmoTot() + text=text..string.format(", Ammo=%d [G=%d, R=%d, B=%d, M=%d]", ammo.Total,ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles) + else + text=text.."N/A" + end + + -- Payload info. + local payload=asset.payload and table.concat(self.legion:GetPayloadMissionTypes(asset.payload), ", ") or "None" + text=text..", Payload={"..payload.."}" + + else + + --- + -- In Stock + --- + + text=text..string.format("In Stock") + + if self:IsRepaired(asset) then + text=text..", Combat Ready" + else + + text=text..string.format(", Repaired in %d sec", self:GetRepairTime(asset)) + + if asset.damage then + text=text..string.format(" (Damage=%.1f)", asset.damage) + end + end + + if asset.Treturned then + local T=timer.getAbsTime()-asset.Treturned + text=text..string.format(", Returned for %d sec", T) + end + + end + end + self:I(self.lid..text) + end + +end + +--- On after "Stop" event. +-- @param #COHORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function COHORT:onafterStop(From, Event, To) + + -- Debug info. + self:I(self.lid.."STOPPING Cohort and removing all assets!") + + -- Remove all assets. + for i=#self.assets,1,-1 do + local asset=self.assets[i] + self:DelAsset(asset) + end + + -- Clear call scheduler. + self.CallScheduler:Clear() + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check if there is a platoon that can execute a given mission. +-- We check the mission type, the refuelling system, engagement range +-- @param #COHORT self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #boolean If true, Cohort can do that type of mission. +function COHORT:CanMission(Mission) + + local cando=true + + -- On duty?= + if not self:IsOnDuty() then + self:T(self.lid..string.format("Cohort in not OnDuty but in state %s. Cannot do mission %s with target %s", self:GetState(), Mission.name, Mission:GetTargetName())) + return false + end + + -- Check mission type. WARNING: This assumes that all assets of the squad can do the same mission types! + if not self:CheckMissionType(Mission.type, self:GetMissionTypes()) then + self:T(self.lid..string.format("INFO: Squad cannot do mission type %s (%s, %s)", Mission.type, Mission.name, Mission:GetTargetName())) + return false + end + + -- Check that tanker mission has the correct refuelling system. + if Mission.type==AUFTRAG.Type.TANKER then + + if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then + -- Correct refueling system. + else + self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem))) + return false + end + + end + + -- Distance to target. + local TargetDistance=Mission:GetTargetDistance(self.legion:GetCoordinate()) + + -- Max engage range. + local engagerange=Mission.engageRange and math.max(self.engageRange, Mission.engageRange) or self.engageRange + + -- Set range is valid. Mission engage distance can overrule the squad engage range. + if TargetDistance>engagerange then + self:I(self.lid..string.format("INFO: Squad is not in range. Target dist=%d > %d NM max mission Range", UTILS.MetersToNM(TargetDistance), UTILS.MetersToNM(engagerange))) + return false + end + + return true +end + +--- Count assets in legion warehouse stock. +-- @param #COHORT self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Number of assets. +function COHORT:CountAssets(InStock, MissionTypes, Attributes) + + local N=0 + for _,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then + if Attributes==nil or self:CheckAttribute(Attributes) then + if asset.spawned then + if not InStock then + N=N+1 --Spawned but we also count the spawned ones. + end + else + N=N+1 --This is in stock. + end + end + end + end + + return N +end + +--- Get assets for a mission. +-- @param #COHORT self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @param #number Nplayloads Number of payloads available. +-- @return #table Assets that can do the required mission. +function COHORT:RecruitAssets(Mission, Npayloads) + + -- Number of payloads available. + Npayloads=Npayloads or self.legion:CountPayloadsInStock(Mission.type, self.aircrafttype, Mission.payloads) + + local assets={} + + -- Loop over assets. + for _,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + + -- Check if asset is currently on a mission (STARTED or QUEUED). + if self.legion:IsAssetOnMission(asset) then + + --- + -- Asset is already on a mission. + --- + + -- Check if this asset is currently on a GCICAP mission (STARTED or EXECUTING). + if self.legion:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and Mission.type==AUFTRAG.Type.INTERCEPT then + + -- Check if the payload of this asset is compatible with the mission. + -- Note: we do not check the payload as an asset that is on a GCICAP mission should be able to do an INTERCEPT as well! + self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") + table.insert(assets, asset) + + end + + else + + --- + -- Asset as NO current mission + --- + + if asset.spawned then + + --- + -- Asset is already SPAWNED (could be uncontrolled on the airfield or inbound after another mission) + --- + + local flightgroup=asset.flightgroup + + -- Firstly, check if it has the right payload. + if self:CheckMissionCapability(Mission.type, asset.payload.capabilities) and flightgroup and flightgroup:IsAlive() then + + -- Assume we are ready and check if any condition tells us we are not. + local combatready=true + + if Mission.type==AUFTRAG.Type.INTERCEPT then + combatready=flightgroup:CanAirToAir() + else + local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET or Mission.type==AUFTRAG.Type.SEAD or Mission.type==AUFTRAG.Type.ANTISHIP + combatready=flightgroup:CanAirToGround(excludeguns) + end + + -- No more attacks if fuel is already low. Safety first! + if flightgroup:IsFuelLow() then + combatready=false + end + + -- Check if in a state where we really do not want to fight any more. + if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() or flightgroup:IsDead() or flightgroup:IsStopped() then + combatready=false + end + + -- This asset is "combatready". + if combatready then + self:I(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY") + table.insert(assets, asset) + end + + end + + else + + --- + -- Asset is still in STOCK + --- + + -- Check that asset is not already requested for another mission. + if Npayloads>0 and self:IsRepaired(asset) and (not asset.requested) then + + -- Add this asset to the selection. + table.insert(assets, asset) + + -- Reduce number of payloads so we only return the number of assets that could do the job. + Npayloads=Npayloads-1 + + end + + end + end + end -- loop over assets + + return assets +end + + +--- Get the time an asset needs to be repaired. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. +-- @return #number Time in seconds until asset is repaired. +function COHORT:GetRepairTime(Asset) + + if Asset.Treturned then + + local t=self.maintenancetime + t=t+Asset.damage*self.repairtime + + -- Seconds after returned. + local dt=timer.getAbsTime()-Asset.Treturned + + local T=t-dt + + return T + else + return 0 + end + +end + +--- Checks if a mission type is contained in a table of possible types. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function COHORT:IsRepaired(Asset) + + if Asset.Treturned then + local Tnow=timer.getAbsTime() + local Trepaired=Asset.Treturned+self.maintenancetime + if Tnow>=Trepaired then + return true + else + return false + end + + else + return true + end + +end + + +--- Checks if a mission type is contained in a table of possible types. +-- @param #COHORT self +-- @param #string MissionType The requested mission type. +-- @param #table PossibleTypes A table with possible mission types. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function COHORT:CheckMissionType(MissionType, PossibleTypes) + + if type(PossibleTypes)=="string" then + PossibleTypes={PossibleTypes} + end + + for _,canmission in pairs(PossibleTypes) do + if canmission==MissionType then + return true + end + end + + return false +end + +--- Check if a mission type is contained in a list of possible capabilities. +-- @param #COHORT self +-- @param #table MissionTypes The requested mission type. Can also be passed as a single mission type `#string`. +-- @param #table Capabilities A table with possible capabilities. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function COHORT:CheckMissionCapability(MissionTypes, Capabilities) + + if type(MissionTypes)~="table" then + MissionTypes={MissionTypes} + end + + for _,cap in pairs(Capabilities) do + local capability=cap --Ops.Auftrag#AUFTRAG.Capability + for _,MissionType in pairs(MissionTypes) do + if capability.MissionType==MissionType then + return true + end + end + end + + return false +end + +--- Check if the platoon attribute matches the given attribute(s). +-- @param #COHORT self +-- @param #table Attributes The requested attributes. See `WAREHOUSE.Attribute` enum. Can also be passed as a single attribute `#string`. +-- @return #boolean If true, the squad has the requested attribute. +function COHORT:CheckAttribute(Attributes) + + if type(Attributes)~="table" then + Attributes={Attributes} + end + + for _,attribute in pairs(Attributes) do + if attribute==self.attribute then + return true + end + end + + return false +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 42a0da3ed..1fa0c414b 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -346,6 +346,13 @@ function FLIGHTGROUP:SetAirwing(airwing) return self end +--- Get airwing the flight group belongs to. +-- @param #FLIGHTGROUP self +-- @return Ops.AirWing#AIRWING The AIRWING object. +function FLIGHTGROUP:GetAirWing() + return self.airwing +end + --- Set if aircraft is VTOL capable. Unfortunately, there is no DCS way to determine this via scripting. -- @param #FLIGHTGROUP self -- @return #FLIGHTGROUP self @@ -354,13 +361,6 @@ function FLIGHTGROUP:SetVTOL() return self end ---- Get airwing the flight group belongs to. --- @param #FLIGHTGROUP self --- @return Ops.AirWing#AIRWING The AIRWING object. -function FLIGHTGROUP:GetAirWing() - return self.airwing -end - --- Set the FLIGHTCONTROL controlling this flight group. -- @param #FLIGHTGROUP self -- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol The FLIGHTCONTROL object. diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua new file mode 100644 index 000000000..825801bd3 --- /dev/null +++ b/Moose Development/Moose/Ops/Legion.lua @@ -0,0 +1,1439 @@ +--- **Ops** - Legion Warehouse. +-- +-- Parent class of Airwings and Brigades. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Legion +-- @image OPS_Legion.png + + +--- LEGION class. +-- @type LEGION +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity of output. +-- @field #string lid Class id string for output to DCS log file. +-- @field #table missionqueue Mission queue table. +-- @field #table cohorts Cohorts of this legion. +-- @extends Functional.Warehouse#WAREHOUSE + +--- Be surprised! +-- +-- === +-- +-- # The LEGION Concept +-- +-- An LEGION consists of multiple COHORTs. These cohorts "live" in a WAREHOUSE, i.e. a physical structure that is connected to an airbase (airdrome, FRAP or ship). +-- +-- @field #LEGION +LEGION = { + ClassName = "LEGION", + verbose = 0, + lid = nil, + missionqueue = {}, + cohorts = {}, +} + +--- LEGION class version. +-- @field #string version +LEGION.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. +-- TODO: Make general so it can be inherited by AIRWING and BRIGADE classes. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new LEGION class object. +-- @param #LEGION self +-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse. +-- @param #string LegionName Name of the legion. +-- @return #LEGION self +function LEGION:New(WarehouseName, LegionName) + + -- Inherit everything from WAREHOUSE class. + local self=BASE:Inherit(self, WAREHOUSE:New(WarehouseName, LegionName)) -- #LEGION + + -- Nil check. + if not self then + BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName)) + return nil + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("LEGION %s | ", self.alias) + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + + self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + self:AddTransition("*", "FlightOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + self:AddTransition("*", "ArmyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + self:AddTransition("*", "NavyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + + -- Defaults: + -- TODO + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the LEGION. Initializes parameters and starts event handlers. + -- @function [parent=#LEGION] Start + -- @param #LEGION self + + --- Triggers the FSM event "Start" after a delay. Starts the LEGION. Initializes parameters and starts event handlers. + -- @function [parent=#LEGION] __Start + -- @param #LEGION self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the LEGION and all its event handlers. + -- @param #LEGION self + + --- Triggers the FSM event "Stop" after a delay. Stops the LEGION and all its event handlers. + -- @function [parent=#LEGION] __Stop + -- @param #LEGION self + -- @param #number delay Delay in seconds. + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set verbosity level. +-- @param #LEGION self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #LEGION self +function LEGION:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + +--- Add a mission for the airwing. The airwing will pick the best available assets for the mission and lauch it when ready. +-- @param #LEGION self +-- @param Ops.Auftrag#AUFTRAG Mission Mission for this airwing. +-- @return #LEGION self +function LEGION: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", + 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 + +--- Remove mission from queue. +-- @param #LEGION self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. +-- @return #LEGION self +function LEGION:RemoveMission(Mission) + + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission.auftragsnummer==Mission.auftragsnummer then + mission.airwing=nil + table.remove(self.missionqueue, i) + break + end + + end + + return self +end + +--- Get cohort by name. +-- @param #LEGION self +-- @param #string CohortName Name of the platoon. +-- @return Ops.Cohort#COHORT The Cohort object. +function LEGION:_GetCohort(CohortName) + + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + if cohort.name==CohortName then + return cohort + end + + end + + return nil +end + +--- Get cohort of an asset. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The squadron asset. +-- @return Ops.Cohort#COHORT The Cohort object. +function LEGION:_GetCohortOfAsset(Asset) + local cohort=self:_GetCohort(Asset.squadname) + return cohort +end + + +--- Check if a BRIGADE class is calling. +-- @param #LEGION self +-- @return #boolean If true, this is a BRIGADE. +function LEGION:IsBrigade() + local is=self.ClassName==BRIGADE.ClassName + return is +end + +--- Check if the AIRWING class is calling. +-- @param #LEGION self +-- @return #boolean If true, this is an AIRWING. +function LEGION:IsAirwing() + local is=self.ClassName==AIRWING.ClassName + return is +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start LEGION FSM. +-- @param #LEGION self +function LEGION:onafterStart(From, Event, To) + + -- Start parent Warehouse. + self:GetParent(self, LEGION).onafterStart(self, From, Event, To) + + -- Info. + self:I(self.lid..string.format("Starting LEGION v%s", LEGION.version)) + +end + + + +--- Check if mission is not over and ready to cancel. +-- @param #LEGION self +function LEGION:_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 + mission:Cancel() + end + end + +end +--- Get next mission. +-- @param #LEGION self +-- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. +function LEGION:_GetNextMission() + + -- Number of missions. + local Nmissions=#self.missionqueue + + -- Treat special cases. + if Nmissions==0 then + return nil + end + + -- Sort results table wrt prio and start time. + local function _sort(a, b) + local taskA=a --Ops.Auftrag#AUFTRAG + local taskB=b --Ops.Auftrag#AUFTRAG + 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] --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Should not happen as we just checked! + if self:IsAirwing() and 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. + if self:IsAirwing() then + for i=mission.nassets+1,#assets do + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + for _,uid in pairs(gotpayload) do + if uid==asset.uid then + self:ReturnPayloadFromAsset(asset) + break + end + end + end + end + + return mission + end + + end -- mission due? + end -- mission loop + + return nil +end + +--- Calculate the mission score of an asset. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset +-- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. +-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. +-- @return #number Mission score. +function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) + + local score=0 + + -- Prefer highly skilled assets. + if asset.skill==AI.Skill.AVERAGE then + score=score+0 + elseif asset.skill==AI.Skill.GOOD then + score=score+10 + elseif asset.skill==AI.Skill.HIGH then + score=score+20 + elseif asset.skill==AI.Skill.EXCELLENT then + score=score+30 + end + + -- Add mission performance to score. + local squad=self:_GetCohortOfAsset(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 + self:T(self.lid.."Adding 25 to asset because it is spawned") + 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? + + return score +end + +--- Optimize chosen assets for the mission at hand. +-- @param #LEGION self +-- @param #table assets Table of (unoptimized) assets. +-- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. +-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. +function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) + + local TargetVec2=Mission:GetTargetVec2() + + 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 --Functional.Warehouse#WAREHOUSE.Assetitem + + if asset.spawned then + local group=GROUP:FindByName(asset.spawngroupname) + asset.dist=UTILS.VecDist2D(group:GetVec2(), TargetVec2) + 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 --Functional.Warehouse#WAREHOUSE.Assetitem + asset.score=self:CalculateAssetMissionScore(asset, Mission, includePayload) + end + + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(a, b) + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem + + -- 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 --Functional.Warehouse#WAREHOUSE.Assetitem + + -- 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 + +end + +--- On after "MissionCancel" event. Cancels the missions of all flightgroups. Deletes request from warehouse queue. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission to be cancelled. +function LEGION: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 --Functional.Warehouse#WAREHOUSE.Assetitem + + 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 "OpsOnMission". +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup Ops group on mission +-- @param Ops.Auftrag#AUFTRAG Mission The requested mission. +function LEGION:onafterOpsOnMission(From, Event, To, OpsGroup, Mission) + if self:IsAirwing() then + + else + + end + +end + +--- On after "NewAsset" event. Asset is added to the given squadron (asset assignment). +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that has just been added. +-- @param #string assignment The (optional) assignment for the asset. +function LEGION:onafterNewAsset(From, Event, To, asset, assignment) + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).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) + local squad=self:_GetCohort(asset.assignment) + + -- Check if asset is already part of the squadron. If an asset returns, it will be added again! We check that asset.assignment is also 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 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 + + -- Set parking IDs. + asset.parkingIDs=squad.parkingIDs + + -- 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 + +--- On after "AssetReturned" event. Triggered when an asset group returned to its airwing. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Squadron#SQUADRON Squadron The asset squadron. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. +function LEGION: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. +-- Creates a new flightgroup element and adds the mission to the flightgroup queue. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP group The group spawned. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that was spawned. +-- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. +function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).onafterAssetSpawned(self, From, Event, To, group, asset, request) + + -- Get the SQUADRON of the asset. + local squadron=self:_GetCohortOfAsset(asset) + + -- Check if we have a squadron or if this was some other request. + if squadron 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 + asset.tacan=Tacan + --flightgroup:SetDefaultTACAN(Tacan,Morse,UnitName,Band,OffSwitch) + flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + 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. + 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:__OpsOnMission(5, 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 + +end + +--- On after "AssetDead" event triggered when an asset group died. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that is dead. +-- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. +function LEGION:onafterAssetDead(From, Event, To, asset, request) + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).onafterAssetDead(self, From, Event, To, asset, request) + + -- Add group to the detection set of the WINGCOMMANDER. + 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 + +--- On after "Destroyed" event. Remove assets from squadrons. Stop squadrons. Remove airwing from wingcommander. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function LEGION:onafterDestroyed(From, Event, To) + + -- Debug message. + self:I(self.lid.."Legion warehouse destroyed!") + + -- Cancel all missions. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + mission:Cancel() + end + + -- Remove all squadron assets. + for _,_squadron in pairs(self.cohorts) do + local squadron=_squadron --Ops.Squadron#SQUADRON + -- Stop Squadron. This also removes all assets. + squadron:Stop() + end + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).onafterDestroyed(self, From, Event, To) + +end + + +--- On after "Request" event. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Functional.Warehouse#WAREHOUSE.Queueitem Request Information table of the request. +function LEGION: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 --Functional.Warehouse#WAREHOUSE.Assetitem + -- 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, LEGION).onafterRequest(self, From, Event, To, Request) + +end + +--- On after "SelfRequest" event. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Set#SET_GROUP groupset The set of asset groups that was delivered to the warehouse itself. +-- @param Functional.Warehouse#WAREHOUSE.Pendingitem request Pending self request. +function LEGION:onafterSelfRequest(From, Event, To, groupset, request) + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).onafterSelfRequest(self, From, Event, To, groupset, request) + + -- Get Mission + local mission=self:GetMissionByID(request.assignment) + + for _,_asset in pairs(request.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + end + + for _,_group in pairs(groupset:GetSet()) do + local group=_group --Wrapper.Group#GROUP + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new flight group after an asset was spawned. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. +-- @return Ops.FlightGroup#FLIGHTGROUP The created flightgroup object. +function LEGION:_CreateFlightGroup(asset) + + -- Create flightgroup. + local flightgroup=nil --Ops.OpsGroup#OPSGROUP + if self:IsAirwing() then + flightgroup=FLIGHTGROUP:New(asset.spawngroupname) + elseif self:IsBrigade() then + flightgroup=ARMYGROUP:New(asset.spawngroupname) + else + self:E(self.lid.."ERROR: not airwing or brigade!") + end + + -- Set airwing. + flightgroup:_SetLegion(self) + + -- Set squadron. + flightgroup.squadron=self:_GetCohortOfAsset(asset) + + -- Set home base. + flightgroup.homebase=self.airbase + + return flightgroup +end + + +--- Check if an asset is currently on a mission (STARTED or EXECUTING). +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. +-- @param #table MissionTypes Types on mission to be checked. Default all. +-- @return #boolean If true, asset has at least one mission of that type in the queue. +function LEGION:IsAssetOnMission(asset, MissionTypes) + + if MissionTypes then + if type(MissionTypes)~="table" then + MissionTypes={MissionTypes} + end + else + -- Check all possible types. + MissionTypes=AUFTRAG.Type + 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 --Functional.Warehouse#WAREHOUSE.Assetitem + + if sqasset.uid==asset.uid then + return true + end + + end + end + + end + ]] + + return false +end + +--- Get the current mission of the asset. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. +-- @return Ops.Auftrag#AUFTRAG Current mission or *nil*. +function LEGION:GetAssetCurrentMission(asset) + + if asset.flightgroup then + return asset.flightgroup:GetMissionCurrent() + end + + return nil +end + +--- Count payloads in stock. +-- @param #LEGION self +-- @param #table MissionTypes Types on mission to be checked. Default *all* possible types `AUFTRAG.Type`. +-- @param #table UnitTypes Types of units. +-- @param #table Payloads Specific payloads to be counted only. +-- @return #number Count of available payloads in stock. +function LEGION:CountPayloadsInStock(MissionTypes, UnitTypes, Payloads) + + if MissionTypes then + if type(MissionTypes)=="string" then + MissionTypes={MissionTypes} + end + end + + if UnitTypes then + if type(UnitTypes)=="string" then + UnitTypes={UnitTypes} + end + end + + local function _checkUnitTypes(payload) + if UnitTypes then + for _,unittype in pairs(UnitTypes) do + if unittype==payload.aircrafttype then + return true + end + end + else + -- Unit type was not specified. + return true + end + return false + end + + local function _checkPayloads(payload) + if Payloads then + for _,Payload in pairs(Payloads) do + if Payload.uid==payload.uid then + return true + end + end + else + -- Payload was not specified. + return nil + end + return false + end + + local n=0 + for _,_payload in pairs(self.payloads or {}) do + local payload=_payload --#LEGION.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 + + return n +end + +--- Count missions in mission queue. +-- @param #LEGION self +-- @param #table MissionTypes Types on mission to be checked. Default *all* possible types `AUFTRAG.Type`. +-- @return #number Number of missions that are not over yet. +function LEGION:CountMissionsInQueue(MissionTypes) + + MissionTypes=MissionTypes or AUFTRAG.Type + + 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 +end + +--- Count total number of assets that are in the warehouse stock (not spawned). +-- @param #LEGION self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups in stock. +function LEGION:CountAssets(InStock, MissionTypes, Attributes) + + local N=0 + + for _,_squad in pairs(self.cohorts) do + local squad=_squad --Ops.Squadron#SQUADRON + N=N+squad:CountAssets(InStock, MissionTypes, Attributes) + end + + return N +end + +--- Count assets on mission. +-- @param #LEGION self +-- @param #table MissionTypes Types on mission to be checked. Default all. +-- @param Ops.Squadron#SQUADRON Squadron Only count assets of this squadron. Default count assets of all squadrons. +-- @return #number Number of pending and queued assets. +-- @return #number Number of pending assets. +-- @return #number Number of queued assets. +function LEGION: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 --Functional.Warehouse#WAREHOUSE.Assetitem + + 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 + + --env.info(string.format("FF N=%d Np=%d, Nq=%d", Np+Nq, Np, Nq)) + return Np+Nq, Np, Nq +end + +--- Count assets on mission. +-- @param #LEGION self +-- @param #table MissionTypes Types on mission to be checked. Default all. +-- @return #table Assets on pending requests. +function LEGION: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 --Functional.Warehouse#WAREHOUSE.Assetitem + + table.insert(assets, asset) + + end + end + end + + return assets +end + +--- Get the aircraft types of this airwing. +-- @param #LEGION self +-- @param #boolean onlyactive Count only the active ones. +-- @param #table squadrons Table of squadrons. Default all. +-- @return #table Table of unit types. +function LEGION: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.cohorts) do + local squadron=_squadron --Ops.Squadron#SQUADRON + + if (not onlyactive) or squadron:IsOnDuty() then + + local gotit=false + for _,unittype in pairs(unittypes) do + if squadron.aircrafttype==unittype then + gotit=true + break + end + end + if not gotit then + table.insert(unittypes, squadron.aircrafttype) + end + + end + end + + return unittypes +end + +--- Check if assets for a given mission type are available. +-- @param #LEGION self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #boolean If true, enough assets are available. +-- @return #table Assets that can do the required mission. +function LEGION:CanMission(Mission) + + env.info("FF CanMission Classname "..self.ClassName) + + -- 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.cohorts + + -- Get aircraft unit types for the job. + local unittypes=self:GetAircraftTypes(true, squadrons) + + -- Count all payloads in stock. + if self:IsAirwing() then + local Npayloads=self:CountPayloadsInStock(Mission.type, unittypes, Mission.payloads) + + if Npayloads#Assets then + self:T(self.lid..string.format("INFO: Not enough assets available! Got %d but need at least %d", #Assets, Mission.nassets)) + Can=false + end + + return Can, Assets +end + +--- Check if assets for a given mission type are available. +-- @param #LEGION self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #table Assets that can do the required mission. +function LEGION:RecruitAssets(Mission) + +end + + +--- Check if a mission type is contained in a list of possible types. +-- @param #LEGION self +-- @param #string MissionType The requested mission type. +-- @param #table PossibleTypes A table with possible mission types. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function LEGION:CheckMissionType(MissionType, PossibleTypes) + + if type(PossibleTypes)=="string" then + PossibleTypes={PossibleTypes} + end + + for _,canmission in pairs(PossibleTypes) do + if canmission==MissionType then + return true + end + end + + return false +end + +--- Check if a mission type is contained in a list of possible capabilities. +-- @param #LEGION self +-- @param #string MissionType The requested mission type. +-- @param #table Capabilities A table with possible capabilities. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function LEGION:CheckMissionCapability(MissionType, Capabilities) + + for _,cap in pairs(Capabilities) do + local capability=cap --Ops.Auftrag#AUFTRAG.Capability + if capability.MissionType==MissionType then + return true + end + end + + return false +end + +--- Get payload performance for a given type of misson type. +-- @param #LEGION self +-- @param #LEGION.Payload Payload The payload table. +-- @param #string MissionType Type of mission. +-- @return #number Performance or -1. +function LEGION:GetPayloadPeformance(Payload, MissionType) + + if Payload then + + for _,Capability in pairs(Payload.capabilities) do + local capability=Capability --Ops.Auftrag#AUFTRAG.Capability + if capability.MissionType==MissionType then + return capability.Performance + end + end + + else + self:E(self.lid.."ERROR: Payload is nil!") + end + + return -1 +end + +--- Get mission types a payload can perform. +-- @param #LEGION self +-- @param #LEGION.Payload Payload The payload table. +-- @return #table Mission types. +function LEGION:GetPayloadMissionTypes(Payload) + + local missiontypes={} + + for _,Capability in pairs(Payload.capabilities) do + local capability=Capability --Ops.Auftrag#AUFTRAG.Capability + table.insert(missiontypes, capability.MissionType) + end + + return missiontypes +end + +--- Returns the mission for a given mission ID (Autragsnummer). +-- @param #LEGION self +-- @param #number mid Mission ID (Auftragsnummer). +-- @return Ops.Auftrag#AUFTRAG Mission table. +function LEGION:GetMissionByID(mid) + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission.auftragsnummer==tonumber(mid) then + return mission + end + + end + + return nil +end + +--- Returns the mission for a given request ID. +-- @param #LEGION self +-- @param #number RequestID Unique ID of the request. +-- @return Ops.Auftrag#AUFTRAG Mission table or *nil*. +function LEGION:GetMissionFromRequestID(RequestID) + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + if mission.requestID and mission.requestID==RequestID then + return mission + end + end + return nil +end + +--- Returns the mission for a given request. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Queueitem Request The warehouse request. +-- @return Ops.Auftrag#AUFTRAG Mission table or *nil*. +function LEGION:GetMissionFromRequest(Request) + return self:GetMissionFromRequestID(Request.uid) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2ba6a7322..e6922d07a 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -701,6 +701,15 @@ function OPSGROUP:SetVerbosity(VerbosityLevel) return self end +--- Set legion this ops group belongs to. +-- @param #OPSGROUP self +-- @param Ops.Legion#LEGION Legion The Legion. +-- @return #OPSGROUP self +function OPSGROUP:_SetLegion(Legion) + self.legion=Legion + return self +end + --- Set default cruise speed. -- @param #OPSGROUP self -- @param #number Speed Speed in knots. diff --git a/Moose Development/Moose/Ops/Platoon.lua b/Moose Development/Moose/Ops/Platoon.lua new file mode 100644 index 000000000..4753cc80d --- /dev/null +++ b/Moose Development/Moose/Ops/Platoon.lua @@ -0,0 +1,155 @@ +--- **Ops** - Brigade Platoon. +-- +-- **Main Features:** +-- +-- * Set parameters like livery, skill valid for all platoon members. +-- * Define modex and callsigns. +-- * Define mission types, this platoon can perform (see Ops.Auftrag#AUFTRAG). +-- * Pause/unpause platoon operations. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Platoon +-- @image OPS_Platoon.png + + +--- PLATOON class. +-- @type PLATOON +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @extends Ops.Cohort#COHORT + +--- *Some cool cohort quote* -- Known Author +-- +-- === +-- +-- # The PLATOON Concept +-- +-- A PLATOON is essential part of an BRIGADE. +-- +-- +-- +-- @field #PLATOON +PLATOON = { + ClassName = "PLATOON", + verbose = 0, +} + +--- PLATOON class version. +-- @field #string version +PLATOON.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot! + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new PLATOON object and start the FSM. +-- @param #PLATOON self +-- @param #string TemplateGroupName Name of the template group. +-- @param #number Ngroups Number of asset groups of this platoon. Default 3. +-- @param #string PlatoonName Name of the platoon, e.g. "VFA-37". +-- @return #PLATOON self +function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, PlatoonName)) -- #PLATOON + + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + -- TODO: Platoon specific user functions. + +--- Set brigade of this platoon. +-- @param #PLATOON self +-- @param Ops.Brigade#BRIGADE Brigade The brigade. +-- @return #PLATOON self +function PLATOON:SetBrigade(Brigade) + self.legion=Brigade + return self +end + +--- Get brigade of this platoon. +-- @param #PLATOON self +-- @return Ops.Brigade#BRIGADE The brigade. +function PLATOON:GetBrigade() + return self.legion +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. +-- @param #PLATOON self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function PLATOON:onafterStart(From, Event, To) + + -- Short info. + local text=string.format("Starting PLATOON %s", self.name) + self:I(self.lid..text) + + -- Start the status monitoring. + self:__Status(-1) +end + +--- On after "Status" event. +-- @param #PLATOON self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function PLATOON:onafterStatus(From, Event, To) + + if self.verbose>=1 then + + -- FSM state. + local fsmstate=self:GetState() + + local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A" + local modex=self.modex and self.modex or -1 + local skill=self.skill and tostring(self.skill) or "N/A" + + local NassetsTot=#self.assets + local NassetsInS=self:CountAssets(true) + local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0 + if self.brigade then + NassetsQP, NassetsP, NassetsQ=self.brigade:CountAssetsOnMission(nil, self) + end + + -- Short info. + local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]", + fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ) + self:I(self.lid..text) + + -- Check if group has detected any units. + self:_CheckAssetStatus() + + end + + if not self:IsStopped() then + self:__Status(-60) + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Misc functions. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index cb95b93fa..74cd5cc31 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -704,7 +704,7 @@ function SQUADRON:onafterStatus(From, Event, To) local skill=self.skill and tostring(self.skill) or "N/A" local NassetsTot=#self.assets - local NassetsInS=self:CountAssetsInStock() + local NassetsInS=self:CountAssets(true) local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0 if self.airwing then NassetsQP, NassetsP, NassetsQ=self.airwing:CountAssetsOnMission(nil, self) @@ -888,18 +888,25 @@ end --- Count assets in airwing (warehous) stock. -- @param #SQUADRON self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. --- @return #number Assets not spawned. -function SQUADRON:CountAssetsInStock(MissionTypes) +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Number of assets. +function SQUADRON:CountAssets(InStock, MissionTypes, Attributes) local N=0 for _,_asset in pairs(self.assets) do local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset - if asset.spawned then - else - if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then - N=N+1 + if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then + if Attributes==nil or self:CheckAttribute(Attributes) then + if asset.spawned then + if not InStock then + N=N+1 --Spawned but we also count the spawned ones. + end + else + N=N+1 --This is in stock. + end end end end @@ -1099,6 +1106,26 @@ function SQUADRON:CheckMissionCapability(MissionTypes, Capabilities) return false end +--- Check if the squadron attribute matches the given attribute(s). +-- @param #SQUADRON self +-- @param #table Attributes The requested attributes. See `WAREHOUSE.Attribute` enum. Can also be passed as a single attribute `#string`. +-- @return #boolean If true, the squad has the requested attribute. +function SQUADRON:CheckAttribute(Attributes) + + if type(Attributes)~="table" then + Attributes={Attributes} + end + + for _,attribute in pairs(Attributes) do + if attribute==self.attribute then + return true + end + end + + return false +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua index 5074de99d..144ae33c1 100644 --- a/Moose Development/Moose/Ops/WingCommander.lua +++ b/Moose Development/Moose/Ops/WingCommander.lua @@ -223,10 +223,10 @@ function WINGCOMMANDER:onafterStatus(From, Event, To) for _,_airwing in pairs(self.airwings) do local airwing=_airwing --Ops.AirWing#AIRWING local Nassets=airwing:CountAssets() - local Nastock=airwing:CountAssetsInStock() + local Nastock=airwing:CountAssets(true) text=text..string.format("\n* %s [%s]: Assets=%s stock=%s", airwing.alias, airwing:GetState(), Nassets, Nastock) for _,aname in pairs(AUFTRAG.Type) do - local na=airwing:CountAssetsInStock({aname}) + local na=airwing:CountAssets(true, {aname}) local np=airwing:CountPayloadsInStock({aname}) local nm=airwing:CountAssetsOnMission({aname}) if na>0 or np>0 then @@ -405,6 +405,22 @@ function WINGCOMMANDER:GetAirwingForMission(Mission) return nil end +--- Check mission queue and assign ONE planned mission. +-- @param #WINGCOMMANDER self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups in stock. +function WINGCOMMANDER:CountAssets(InStock, MissionTypes, Attributes) + local N=0 + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + N=N+airwing:CountAssets(InStock, MissionTypes, Attributes) + end + + return N +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file