diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index b8e5d1c49..3ce1e6be1 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -92,7 +92,15 @@ function BRIGADE:New(WarehouseName, BrigadeName) -- Defaults self:SetRetreatZones() - + + -- Turn ship into NAVYGROUP. + if self:IsShip() then + local wh=self.warehouse --Wrapper.Unit#UNIT + local group=wh:GetGroup() + self.warehouseOpsGroup=NAVYGROUP:New(group) --Ops.NavyGroup#NAVYGROUP + self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName()) + end + -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("*", "ArmyOnMission", "*") -- An ARMYGROUP was send on a Mission (AUFTRAG). diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 9471147ec..e602a1831 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -256,6 +256,17 @@ CHIEF.Strategy = { -- @field #string MissionType Mission Type. -- @field #number Performance Performance: a number between 0 and 100, where 100 is best performance. +--- Asset numbers for detected targets. +-- @type CHIEF.AssetNumber +-- @field #number nAssetMin Min number of assets. +-- @field #number nAssetMax Max number of assets. +-- @field #number threatlevel Threat level. +-- @field #string targetCategory Target category. +-- @field #string missionType Mission type. +-- @field #number nUnits Number of enemy units. +-- @field #string defcon Defense condition. +-- @field #string strategy Strategy. + --- Strategic zone. -- @type CHIEF.StrategicZone -- @field Ops.OpsZone#OPSZONE opszone OPS zone. @@ -773,6 +784,153 @@ function CHIEF:DeleteFromResource(Resource, MissionType) return self end +--- Set number of assets requested for detected targets. +-- @param #CHIEF self +-- @param #number NassetsMin Min number of assets. Should be at least 1. Default 1. +-- @param #number NassetsMax Max number of assets. Default is same as `NassetsMin`. +-- @param #number ThreatLevel Only apply this setting if the target threat level is greater or equal this number. Default 0. +-- @param #string TargetCategory Only apply this setting if the target is of this category, e.g. `TARGET.Category.AIRCRAFT`. +-- @param #string MissionType Only apply this setting for this mission type, e.g. `AUFTRAG.Type.INTERCEPT`. +-- @param #string Nunits Only apply this setting if the number of enemy units is greater or equal this number. +-- @param #string Defcon Only apply this setting if this defense condition is in place. +-- @param #string Strategy Only apply this setting if this strategy is in currently. place. +-- @return #CHIEF self +function CHIEF:SetResponseOnTarget(NassetsMin, NassetsMax, ThreatLevel, TargetCategory, MissionType, Nunits, Defcon, Strategy) + + local bla={} --#CHIEF.AssetNumber + + bla.nAssetMin=NassetsMin or 1 + bla.nAssetMax=NassetsMax or bla.nAssetMin + bla.threatlevel=ThreatLevel or 0 + bla.targetCategory=TargetCategory + bla.missionType=MissionType + bla.nUnits=Nunits or 1 + bla.defcon=Defcon + bla.strategy=Strategy + + self.assetNumbers=self.assetNumbers or {} + + -- Add to table. + table.insert(self.assetNumbers, bla) + +end + +--- Add mission type and number of required assets to resource. +-- @param #CHIEF self +-- @param Ops.Target#TARGET Target The target. +-- @param #string MissionType Mission type. +-- @return #number Number of min assets. +-- @return #number Number of max assets. +function CHIEF:_GetAssetsForTarget(Target, MissionType) + + -- Threat level. + local threatlevel=Target:GetThreatLevelMax() + + -- Number of units. + local nUnits=Target.N0 + + -- Target category. + local targetcategory=Target:GetCategory() + + -- Debug info. + self:T(self.lid..string.format("Getting number of assets for target with TL=%d, Category=%s, nUnits=%s, MissionType=%s", threatlevel, targetcategory, nUnits, tostring(MissionType))) + + -- Candidates. + local candidates={} + + local threatlevelMatch=nil + for _,_assetnumber in pairs(self.assetNumbers or {}) do + local assetnumber=_assetnumber --#CHIEF.AssetNumber + + if (threatlevelMatch==nil and threatlevel>=assetnumber.threatlevel) or (threatlevelMatch~=nil and threatlevelMatch==threatlevel) then + + if threatlevelMatch==nil then + threatlevelMatch=threatlevel + end + + -- Number of other parameters matching. + local nMatch=0 + + -- Assume cand. + local cand=true + + if assetnumber.targetCategory~=nil then + if assetnumber.targetCategory==targetcategory then + nMatch=nMatch+1 + else + cand=false + end + end + + if MissionType and assetnumber.missionType~=nil then + if assetnumber.missionType==MissionType then + nMatch=nMatch+1 + else + cand=false + end + end + + if assetnumber.nUnits~=nil then + if assetnumber.nUnits>=nUnits then + nMatch=nMatch+1 + else + cand=false + end + end + + if assetnumber.defcon~=nil then + if assetnumber.defcon==self.Defcon then + nMatch=nMatch+1 + else + cand=false + end + end + + if assetnumber.strategy~=nil then + if assetnumber.strategy==self.strategy then + nMatch=nMatch+1 + else + cand=false + end + end + + -- Add to candidates. + if cand then + table.insert(candidates, {assetnumber=assetnumber, nMatch=nMatch}) + end + + end + + end + + if #candidates>0 then + + -- Return greater match. + local function _sort(a,b) + return a.nMatch>b.nMatch + end + + -- Sort table by matches. + table.sort(candidates, _sort) + + -- Pick the candidate with most matches. + local candidate=candidates[1] + + -- Asset number. + local an=candidate.assetnumber --#CHIEF.AssetNumber + + -- Debug message. + self:T(self.lid..string.format("Picking candidate with %d matches: NassetsMin=%d, NassetsMax=%d, ThreatLevel=%d, TargetCategory=%s, MissionType=%s, Defcon=%s, Strategy=%s", + candidate.nMatch, an.nAssetMin, an.nAssetMax, an.threatlevel, tostring(an.targetCategory), tostring(an.missionType), tostring(an.defcon), tostring(an.strategy))) + + -- Return number of assetes. + return an.nAssetMin, an.nAssetMax + else + return 1, 1 + end + +end + --- Get defence condition. -- @param #CHIEF self -- @param #string Current Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`. @@ -2084,24 +2242,6 @@ function CHIEF:CheckTargetQueue() local Legions=nil if #MissionPerformances>0 then - - --TODO: Number of required assets. How many do we want? Should depend on: - -- * number of enemy units - -- * target threatlevel - -- * how many assets are still in stock - -- * is it inside of our border - -- * add damping factor - - local NassetsMin=1 - local NassetsMax=1 - - if threatlevel>=8 and target.N0 >=10 then - NassetsMax=3 - elseif threatlevel>=5 then - NassetsMax=2 - else - NassetsMax=1 - end for _,_mp in pairs(MissionPerformances) do local mp=_mp --#CHIEF.MissionPerformance @@ -2112,6 +2252,9 @@ function CHIEF:CheckTargetQueue() --env.info(string.format("FF chief %s nolimit=%s", mp.MissionType, tostring(NoLimit))) if notlimited then + + -- Get min/max number of assets. + local NassetsMin, NassetsMax=self:_GetAssetsForTarget(target, mp.MissionType) -- Debug info. self:T2(self.lid..string.format("Recruiting assets for mission type %s [performance=%d] of target %s", mp.MissionType, mp.Performance, target:GetName())) diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 8a130a982..4c059b639 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -1084,7 +1084,7 @@ function COHORT:RecruitAssets(MissionType, Npayloads) table.insert(assets, asset) end - elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and AUFTRAG.CheckMissionCapability(MissionType, asset.payload.capabilities) then + elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and AUFTRAG.CheckMissionCapability(MissionType, asset.payload.capabilities) and MissionType~=AUFTRAG.Type.ALERT5 then -- Check if the payload of this asset is compatible with the mission. self:T(self.lid..string.format("Adding asset on ALERT 5 mission for %s mission", MissionType)) diff --git a/Moose Development/Moose/Ops/Fleet.lua b/Moose Development/Moose/Ops/Fleet.lua index 515e5cf14..24bffb79d 100644 --- a/Moose Development/Moose/Ops/Fleet.lua +++ b/Moose Development/Moose/Ops/Fleet.lua @@ -108,6 +108,15 @@ function FLEET:New(WarehouseName, FleetName) -- Defaults self:SetRetreatZones() + + -- Turn ship into NAVYGROUP. + if self:IsShip() then + local wh=self.warehouse --Wrapper.Unit#UNIT + local group=wh:GetGroup() + self.warehouseOpsGroup=NAVYGROUP:New(group) --Ops.NavyGroup#NAVYGROUP + self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName()) + end + -- Add FSM transitions. -- From State --> Event --> To State diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1d5b27eb3..a1182ed7d 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -4322,12 +4322,14 @@ function FLIGHTGROUP:_UpdateMenu(delay) if player and player.status~=OPSGROUP.ElementStatus.DEAD then -- Debug text. - local text=string.format("Updating MENU: State=%s, ATC=%s [%s]", self:GetState(), - self.flightcontrol and self.flightcontrol.airbasename or "None", self.flightcontrol and self.flightcontrol:GetFlightStatus(self) or "Unknown") - - -- Message to group. - MESSAGE:New(text, 5):ToGroup(self.group) - self:I(self.lid..text) + if self.verbose>=2 then + local text=string.format("Updating MENU: State=%s, ATC=%s [%s]", self:GetState(), + self.flightcontrol and self.flightcontrol.airbasename or "None", self.flightcontrol and self.flightcontrol:GetFlightStatus(self) or "Unknown") + + -- Message to group. + MESSAGE:New(text, 5):ToGroup(self.group) + self:I(self.lid..text) + end -- Get current position of player. local position=self:GetCoordinate(nil, player.name) diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 0ce4ed242..e5c0056e7 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1191,6 +1191,12 @@ function LEGION:onafterOpsOnMission(From, Event, To, OpsGroup, Mission) self:NavyOnMission(OpsGroup, Mission) end + -- Load group as cargo because it cannot swim! We pause the mission. + if self:IsBrigade() and self:IsShip() then + OpsGroup:PauseMission() + self.warehouseOpsGroup:Load(OpsGroup, self.warehouseOpsElement) + end + -- Trigger event for chief. if self.chief then self.chief:OpsOnMission(OpsGroup, Mission) @@ -2689,15 +2695,15 @@ function LEGION:AssignAssetsForTransport(Legions, CargoAssets, NcarriersMin, Nca -- Set pickup zone to spawn zone or airbase if the legion has one that is operational. local pickupzone=legion.spawnzone - if legion.airbase and legion:IsRunwayOperational() then - --pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) - end -- Add TZC from legion spawn zone to deploy zone. local tpz=Transport:AddTransportZoneCombo(nil, pickupzone, Transport:GetDeployZone()) - tpz.PickupAirbase=legion:IsRunwayOperational() and legion.airbase or nil - Transport:SetEmbarkZone(legion.spawnzone, tpz) + -- Set pickup airbase if the legion has an airbase. Could also be the ship itself. + tpz.PickupAirbase=legion:IsRunwayOperational() and legion.airbase or nil + + -- Set embark zone to spawn zone. + Transport:SetEmbarkZone(legion.spawnzone, tpz) -- Add cargo assets to transport. for _,_asset in pairs(CargoAssets) do diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index f3f4932bb..56f8eab4b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -462,7 +462,7 @@ OPSGROUP.CarrierStatus={ -- @type OPSGROUP.CargoStatus -- @field #string AWAITING Group is awaiting carrier. -- @field #string NOTCARGO This group is no cargo yet. --- @field #string ASSIGNED Cargo is assigned to a carrier. +-- @field #string ASSIGNED Cargo is assigned to a carrier. (Not used!) -- @field #string BOARDING Cargo is boarding a carrier. -- @field #string LOADED Cargo is loaded into a carrier. OPSGROUP.CargoStatus={ @@ -5397,6 +5397,12 @@ function OPSGROUP:RouteToMission(mission, delay) self:T(self.lid..string.format("Route To Mission: I am DEAD or STOPPED! Ooops...")) return end + + -- Check if this group is cargo. + if self:IsCargo() then + self:T(self.lid..string.format("Route To Mission: I am CARGO! You cannot route me...")) + return + end -- OPSTRANSPORT: Just add the ops transport to the queue. if mission.type==AUFTRAG.Type.OPSTRANSPORT then @@ -8661,12 +8667,12 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Check if cargo is not already cargo. local isNotCargo=cargo.opsgroup:IsNotCargo(true) - -- Check if cargo is holding. - local isHolding=cargo.opsgroup:IsHolding() + -- Check if cargo is holding or loaded + local isHolding=cargo.opsgroup:IsHolding() or cargo.opsgroup:IsLoaded() -- Check if cargo is in embark/pickup zone. -- Added InUtero here, if embark zone is moving (ship) and cargo has been spawned late activated and its position is not updated. Not sure if that breaks something else! - local inZone=cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone) --or cargo.opsgroup:IsInUtero() + local inZone=cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone) or cargo.opsgroup:IsInUtero() -- Check if cargo is currently on a mission. local isOnMission=cargo.opsgroup:IsOnMission() @@ -8675,9 +8681,13 @@ function OPSGROUP:onafterLoading(From, Event, To) if isOnMission then local mission=cargo.opsgroup:GetMissionCurrent() if mission and mission.opstransport and mission.opstransport.uid==self.cargoTransport.uid then - isOnMission=not cargo.opsgroup:IsHolding() + isOnMission=not isHolding end - end + end + + -- Debug message. + self:T(self.lid..string.format("Loading: canCargo=%s, isCarrier=%s, isNotCargo=%s, isHolding=%s, isOnMission=%s", + tostring(canCargo), tostring(isCarrier), tostring(isNotCargo), tostring(isHolding), tostring(isOnMission))) -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() if canCargo and inZone and isNotCargo and isHolding and (not (cargo.delivered or cargo.opsgroup:IsDead() or isCarrier or isOnMission)) then @@ -8701,10 +8711,7 @@ function OPSGROUP:onafterLoading(From, Event, To) local carrier=self:FindCarrierForCargo(cargo.opsgroup) if carrier then - - -- Set cargo status. - cargo.opsgroup:_NewCargoStatus(OPSGROUP.CargoStatus.ASSIGNED) - + -- Order cargo group to board the carrier. cargo.opsgroup:Board(self, carrier) @@ -9312,7 +9319,10 @@ function OPSGROUP:onafterUnloaded(From, Event, To, OpsGroupCargo) OpsGroupCargo:Returned() end - if self:_CountPausedMissions()>0 then + -- Check if there is a paused mission. + local paused=OpsGroupCargo:_CountPausedMissions()>0 + + if paused then OpsGroupCargo:UnpauseMission() end @@ -9586,9 +9596,6 @@ end -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) - -- Set cargo status. - self:_NewCargoStatus(OPSGROUP.CargoStatus.BOARDING) - -- Army or Navy group. local CarrierIsArmyOrNavy=CarrierGroup:IsArmygroup() or CarrierGroup:IsNavygroup() local CargoIsArmyOrNavy=self:IsArmygroup() or self:IsNavygroup() @@ -9605,7 +9612,21 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) board=false end - if board then + if self:IsLoaded() then + + -- Debug info. + self:T(self.lid..string.format("Group is loaded currently ==> Moving directly to new carrier - No Unload(), Disembart() events triggered!")) + + -- Remove my carrier. + self:_RemoveMyCarrier() + + -- Trigger Load event. + CarrierGroup:Load(self) + + elseif board then + + -- Set cargo status. + self:_NewCargoStatus(OPSGROUP.CargoStatus.BOARDING) -- Debug info. self:T(self.lid..string.format("Boarding group=%s [%s], carrier=%s", CarrierGroup:GetName(), CarrierGroup:GetState(), tostring(Carrier.name))) diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 03f31488c..633c3c81f 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -2171,7 +2171,7 @@ function OPSTRANSPORT:_GetTransportZoneCombo(Carrier) return nil end ---- Get an OPSGROUP from a given OPSGROUP or GROUP object. If the object is a GROUUP, an OPSGROUP is created automatically. +--- Get an OPSGROUP from a given OPSGROUP or GROUP object. If the object is a GROUP, an OPSGROUP is created automatically. -- @param #OPSTRANSPORT self -- @param Core.Base#BASE Object The object, which can be a GROUP or OPSGROUP. -- @return Ops.OpsGroup#OPSGROUP Ops Group.