diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua index 37dba8c76..cdd311372 100644 --- a/Moose Development/Moose/Core/Beacon.lua +++ b/Moose Development/Moose/Core/Beacon.lua @@ -170,6 +170,8 @@ end function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration}) + Mode=Mode or "Y" + -- Get frequency. local Frequency=UTILS.TACANToFrequency(Channel, Mode) @@ -187,11 +189,16 @@ function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) -- Check if unit is an aircraft and set system accordingly. local AA=self.Positionable:IsAir() + + if AA then System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER -- Check if "Y" mode is selected for aircraft. - if Mode~="Y" then - self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable}) + if Mode=="X" then + --self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y!", self.Positionable}) + System=BEACON.System.TACAN_TANKER_X + else + System=BEACON.System.TACAN_TANKER_Y end end @@ -267,13 +274,12 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) IsValid = false end - -- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing - -- or 14 (TACAN_AA_MODE_Y) if it does not + -- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing or 14 (TACAN_AA_MODE_Y) if it does not local System if Bearing then - System = 5 + System = BEACON.System.TACAN_TANKER_Y else - System = 14 + System = BEACON.System.TACAN_AA_MODE_Y end if IsValid then -- Starts the BEACON @@ -281,10 +287,13 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) self.Positionable:SetCommand({ id = "ActivateBeacon", params = { - type = 4, + type = BEACON.Type.TACAN, system = System, callsign = Message, + AA = true, frequency = Frequency, + bearing = Bearing, + modeChannel = "Y", } }) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index e7359808f..4ce180947 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -60,6 +60,7 @@ -- @field #number DrawID Unique ID of the drawn zone on the F10 map. -- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. -- @field #number ZoneID ID of zone. Only zones defined in the ME have an ID! +-- @field #number Surface Type of surface. Only determined at the center of the zone! -- @extends Core.Fsm#FSM @@ -111,6 +112,7 @@ ZONE_BASE = { DrawID=nil, Color={}, ZoneID=nil, + Sureface=nil, } @@ -335,15 +337,22 @@ end -- @param #ZONE_BASE self -- @return #nil The bounding square. function ZONE_BASE:GetBoundingSquare() - --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } return nil end +--- Get surface type of the zone. +-- @param #ZONE_BASE self +-- @return DCS#SurfaceType Type of surface. +function ZONE_BASE:GetSurfaceType() + local coord=self:GetCoordinate() + local surface=coord:GetSurfaceType() + return surface +end + --- Bound the zone boundaries with a tires. -- @param #ZONE_BASE self function ZONE_BASE:BoundZone() self:F2() - end @@ -1281,8 +1290,8 @@ end --- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone. -- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @return Core.Point#COORDINATE The random coordinate. function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 102955153..c8fbb1524 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -299,6 +299,9 @@ function AIRWING:AddSquadron(Squadron) elseif Squadron.attribute==GROUP.Attribute.AIR_TANKER then self:NewPayload(Squadron.templategroup, -1, AUFTRAG.Type.TANKER) end + + -- Relocate mission. + self:NewPayload(Squadron.templategroup, -1, AUFTRAG.Type.RELOCATECOHORT, 0) -- Set airwing to squadron. Squadron:SetAirwing(self) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index b506021c6..d4ffc672d 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -33,7 +33,6 @@ -- @field #boolean formationPerma Formation that is used permanently and overrules waypoint formations. -- @field #boolean isMobile If true, group is mobile. -- @field #ARMYGROUP.Target engage Engage target. --- @field #boolean retreatOnOutOfAmmo If true, the group will automatically retreat when out of ammo. Needs a retreat zone! -- @field Core.Set#SET_ZONE retreatZones Set of retreat zones. -- @extends Ops.OpsGroup#OPSGROUP @@ -710,7 +709,7 @@ function ARMYGROUP:Status() local ammo=self:GetAmmoTot().Total -- Detected units. - local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "OFF" + local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "Off" -- Get cargo weight. local cargo=0 diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 87c481c84..30c778494 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -71,6 +71,8 @@ -- @field Core.Point#COORDINATE orbitRaceTrack Race-track orbit coordinate. -- -- @field Ops.Target#TARGET engageTarget Target data to engage. +-- +-- @field #boolean teleport Groups are teleported to the mission ingress waypoint. -- -- @field Core.Zone#ZONE_RADIUS engageZone *Circular* engagement zone. -- @field #table engageTargetTypes Table of target types that are engaged in the engagement zone. @@ -162,6 +164,7 @@ -- @field #number optionRTBammo RTB on out-of-ammo. -- @field #number optionRTBfuel RTB on out-of-fuel. -- @field #number optionECM ECM. +-- @field #boolean optionEmission Emission is on or off. -- -- @extends Core.Fsm#FSM @@ -312,7 +315,16 @@ -- -- ## Commander Level -- --- Assigning an AUFTRAG to acommander is done via the @{Ops.Commander#COMMANDER.AddMission} function. See COMMANDER docs for details. +-- Assigning an AUFTRAG to a commander is done via the @{Ops.Commander#COMMANDER.AddMission} function. +-- The commander will select the best assets available from all the legions under his command. See COMMANDER docs for details. +-- +-- ## Chief Level +-- +-- Assigning an AUFTRAG to a commander is done via the @{Ops.Chief#CHIEF.AddMission} function. The chief will simply pass on the mission to his/her commander. +-- +-- # Transportation +-- +-- TODO -- -- -- # Events @@ -393,6 +405,8 @@ _AUFTRAGSNR=0 -- @field #string GROUNDATTACK Ground attack. -- @field #string CARGOTRANSPORT Cargo transport. -- @field #string RELOCATECOHORT Relocate a cohort from one legion to another. +-- @field #string AIRDEFENSE Air defense. +-- @field #string EWR Early Warning Radar. -- @field #string NOTHING Nothing. AUFTRAG.Type={ ANTISHIP="Anti Ship", @@ -430,8 +444,10 @@ AUFTRAG.Type={ HOVER="Hover", GROUNDATTACK="Ground Attack", CARGOTRANSPORT="Cargo Transport", + RELOCATECOHORT="Relocate Cohort", + AIRDEFENSE="Air Defence", + EWR="Early Warning Radar", NOTHING="Nothing", - RELOCATECOHORT="Relocate Cohort", } --- Special task description. @@ -448,8 +464,9 @@ AUFTRAG.Type={ -- @field #string HOVER Hover. -- @field #string GROUNDATTACK Ground attack. -- @field #string FERRY Ferry mission. --- @field #string NOTHING Nothing. -- @field #string RELOCATECOHORT Relocate cohort. +-- @field #string AIRDEFENSE Air defense. +-- @field #string NOTHING Nothing. AUFTRAG.SpecialTask={ FORMATION="Formation", PATROLZONE="PatrolZone", @@ -464,8 +481,10 @@ AUFTRAG.SpecialTask={ HOVER="Hover", GROUNDATTACK="Ground Attack", FERRY="Ferry", + RELOCATECOHORT="Relocate Cohort", + AIRDEFENSE="Air Defense", + EWR="Early Warning Radar", NOTHING="Nothing", - RELOCATECOHORT="Relocate Cohort", } --- Mission status. @@ -586,7 +605,7 @@ AUFTRAG.Category={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.9.3" +AUFTRAG.version="0.9.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -647,7 +666,6 @@ function AUFTRAG:New(Type) self:SetPriority() self:SetTime() self:SetRequiredAssets() - --self:SetRequiredCarriers() self.engageAsGroup=true self.dTevaluate=5 @@ -1341,7 +1359,7 @@ function AUFTRAG:NewBAI(Target, Altitude) mission:_TargetFromObject(Target) -- DCS Task options: - mission.engageWeaponType=ENUMS.WeaponFlag.AnyAG + mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 5000) @@ -1362,7 +1380,7 @@ end --- **[AIR]** Create a SEAD mission. -- @param #AUFTRAG self -- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP or UNIT object. --- @param #number Altitude Engage altitude in feet. Default 8000 ft. +-- @param #number Altitude Engage altitude in feet. Default 25000 ft. -- @return #AUFTRAG self function AUFTRAG:NewSEAD(Target, Altitude) @@ -1371,9 +1389,9 @@ function AUFTRAG:NewSEAD(Target, Altitude) mission:_TargetFromObject(Target) -- DCS Task options: - mission.engageWeaponType=ENUMS.WeaponFlag.AnyAG --ENUMS.WeaponFlag.Cannons + mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL - mission.engageAltitude=UTILS.FeetToMeters(Altitude or 8000) + mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) -- Mission options: mission.missionTask=ENUMS.MissionTask.SEAD @@ -1402,7 +1420,7 @@ function AUFTRAG:NewSTRIKE(Target, Altitude) mission:_TargetFromObject(Target) -- DCS Task options: - mission.engageWeaponType=ENUMS.WeaponFlag.AnyAG + mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) @@ -1432,7 +1450,7 @@ function AUFTRAG:NewBOMBING(Target, Altitude) mission:_TargetFromObject(Target) -- DCS task options: - mission.engageWeaponType=ENUMS.WeaponFlag.AnyBomb + mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) @@ -1470,7 +1488,7 @@ function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) mission:_TargetFromObject(Airdrome) -- DCS task options: - mission.engageWeaponType=ENUMS.WeaponFlag.AnyBomb + mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) @@ -1505,7 +1523,7 @@ function AUFTRAG:NewBOMBCARPET(Target, Altitude, CarpetLength) mission:_TargetFromObject(Target) -- DCS task options: - mission.engageWeaponType=ENUMS.WeaponFlag.AnyBomb + mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) mission.engageCarpetLength=CarpetLength or 500 @@ -1996,10 +2014,55 @@ function AUFTRAG:NewONGUARD(Coordinate) return mission end ---- **[PRIVATE, AIR, GROUND, NAVAL]** Create a mission to relocate assets to another LEGION. +--- **[GROUND, NAVAL]** Create an AIRDEFENSE mission. +-- @param #AUFTRAG self +-- @param Core.Zone#ZONE Zone Zone where the air defense group(s) should be stationed. +-- @return #AUFTRAG self +function AUFTRAG:NewAIRDEFENSE(Zone) + + local mission=AUFTRAG:New(AUFTRAG.Type.AIRDEFENSE) + + mission:_TargetFromObject(Zone) + + mission.optionROE=ENUMS.ROE.OpenFire + mission.optionAlarm=ENUMS.AlarmState.Auto + + mission.missionFraction=1.0 + + mission.categories={AUFTRAG.Category.GROUND, AUFTRAG.Category.NAVAL} + + mission.DCStask=mission:GetDCSMissionTask() + + return mission +end + +--- **[GROUND]** Create an EWR mission. +-- @param #AUFTRAG self +-- @param Core.Zone#ZONE Zone Zone where the Early Warning Radar group(s) should be stationed. +-- @return #AUFTRAG self +function AUFTRAG:NewEWR(Zone) + + local mission=AUFTRAG:New(AUFTRAG.Type.EWR) + + mission:_TargetFromObject(Zone) + + mission.optionROE=ENUMS.ROE.WeaponHold + mission.optionAlarm=ENUMS.AlarmState.Auto + + mission.missionFraction=1.0 + + mission.categories={AUFTRAG.Category.GROUND} + + mission.DCStask=mission:GetDCSMissionTask() + + return mission +end + + +--- **[PRIVATE, AIR, GROUND, NAVAL]** Create a mission to relocate all cohort assets to another LEGION. -- @param #AUFTRAG self -- @param Ops.Legion#LEGION Legion The new legion. --- @param Ops.Cohort#COHORT Cohort The new cohort. +-- @param Ops.Cohort#COHORT Cohort The cohort to be relocated. -- @return #AUFTRAG self function AUFTRAG:_NewRELOCATECOHORT(Legion, Cohort) @@ -2312,6 +2375,14 @@ function AUFTRAG:SetDuration(Duration) return self end +--- Set that mission assets are teleported to the mission execution waypoint. +-- @param #AUFTRAG self +-- @return #AUFTRAG self +function AUFTRAG:SetTeleport() + self.teleport=true + return self +end + --- Set mission push time. This is the time the mission is executed. If the push time is not passed, the group will wait at the mission execution waypoint. -- @param #AUFTRAG self @@ -2637,6 +2708,29 @@ function AUFTRAG:AddTransportCarriers(Carriers) end +--- Set required attribute(s) the assets must have. +-- @param #AUFTRAG self +-- @param #table Attributes Generalized attribute(s). +-- @return #AUFTRAG self +function AUFTRAG:SetRequiredAttribute(Attributes) + if Attributes and type(Attributes)~="table" then + Attributes={Attributes} + end + self.attributes=Attributes +end + +--- Set required property or properties the assets must have. +-- These are DCS attributes. +-- @param #AUFTRAG self +-- @param #table Properties Property or table of properties. +-- @return #AUFTRAG self +function AUFTRAG:SetRequiredProperty(Properties) + if Properties and type(Properties)~="table" then + Properties={Properties} + end + self.properties=Properties +end + --- Set number of required carrier groups if an OPSTRANSPORT assignment is required. -- @param #AUFTRAG self -- @param #number NcarriersMin Number of carriers *at least* required. Default 1. @@ -2806,6 +2900,21 @@ function AUFTRAG:SetEPLRS(OnOffSwitch) return self end +--- Set emission setting for this mission. +-- @param #AUFTRAG self +-- @param #boolean OnOffSwitch If `true` or `nil`, emission is on. If `false`, emission is off. +-- @return #AUFTRAG self +function AUFTRAG:SetEmission(OnOffSwitch) + + if OnOffSwitch==nil then + self.optionEmission=true + else + self.optionEmission=OnOffSwitch + end + + return self +end + --- Set formation for this mission. -- @param #AUFTRAG self -- @param #number Formation Formation. @@ -3765,12 +3874,19 @@ function AUFTRAG:RemoveLegion(Legion) -- Loop over legions for i=#self.legions,1,-1 do local legion=self.legions[i] --Ops.Legion#LEGION + if legion.alias==Legion.alias then + -- Debug info. self:T(self.lid..string.format("Removing legion %s", Legion.alias)) table.remove(self.legions, i) + + -- Set legion status to nil. + self.statusLegion[Legion.alias]=nil + return self end + end self:T(self.lid..string.format("ERROR: Legion %s not found and could not be removed!", Legion.alias)) @@ -4392,15 +4508,15 @@ function AUFTRAG:onafterRepeat(From, Event, To) if self.chief then + -- Set status for chief. self.statusChief=AUFTRAG.Status.PLANNED -- Remove mission from wingcommander because Chief will assign it again. if self.commander then - self.commander:RemoveMission(self) self.statusCommander=AUFTRAG.Status.PLANNED end - -- Remove mission from airwing because WC will assign it again but maybe to a different wing. + -- Remove mission from legions because commander will assign it again but maybe to different legion(s). for _,_legion in pairs(self.legions) do local legion=_legion --Ops.Legion#LEGION legion:RemoveMission(self) @@ -4408,9 +4524,10 @@ function AUFTRAG:onafterRepeat(From, Event, To) elseif self.commander then + -- Set status for commander. self.statusCommander=AUFTRAG.Status.PLANNED - -- Remove mission from airwing because WC will assign it again but maybe to a different wing. + -- Remove mission from legion(s) because commander will assign it again but maybe to different legion(s). for _,_legion in pairs(self.legions) do local legion=_legion --Ops.Legion#LEGION legion:RemoveMission(self) @@ -5440,19 +5557,6 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) DCStask.params=param - --[[ Task script. - local DCSScript = {} - - local altitude = self.hoverAltitude - DCSScript[#DCSScript+1] = 'local group = ...' - DCSScript[#DCSScript+1] = 'local helo = GROUP:Find(group)' - DCSScript[#DCSScript+1] = 'helo:SetSpeed(0.1,true)' - DCSScript[#DCSScript+1] = string.format('helo:SetAltitude(UTILS.FeetToMeters(%d),true,"BARO")',altitude) -- Call the function, e.g. myfunction.(warehouse,mygroup) - - -- Create task. - local DCSTask=CONTROLLABLE.TaskWrappedAction(self, CONTROLLABLE.CommandDoScript(self, table.concat(DCSScript))) - --]] - table.insert(DCStasks, DCStask) elseif self.type==AUFTRAG.Type.ONGUARD or self.type==AUFTRAG.Type.ARMOREDGUARD then @@ -5473,6 +5577,46 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) table.insert(DCStasks, DCStask) + elseif self.type==AUFTRAG.Type.AIRDEFENSE then + + ------------------------ + -- AIRDEFENSE Mission -- + ------------------------ + + local DCStask={} + + DCStask.id=AUFTRAG.SpecialTask.AIRDEFENSE + + -- We create a "fake" DCS task and pass the parameters to the OPSGROUP. + local param={} + param.zone=self:GetObjective() + + DCStask.params=param + + table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.EWR then + + ----------------- + -- EWR Mission -- + ----------------- + + local DCStask={} + + DCStask.id=AUFTRAG.SpecialTask.EWR + + -- We create a "fake" DCS task and pass the parameters to the OPSGROUP. + local param={} + param.zone=self:GetObjective() + + DCStask.params=param + + table.insert(DCStasks, DCStask) + + -- EWR is an enroute task + local Enroutetask=CONTROLLABLE.EnRouteTaskEWR() + table.insert(self.enrouteTasks, Enroutetask) + else self:T(self.lid..string.format("ERROR: Unknown mission task!")) return nil diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 18536827f..bdbb9da57 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -153,17 +153,12 @@ -- The default mission types and number of assets can be customized for the two scenarious (zone empty or zone occupied by the enemy). -- -- In order to do this, you need to create resource lists (one for each scenario) via the @{#CHIEF.CreateResource}() function. --- These list can than be used to replace the default resources employed with --- --- * @{CHIEF.SetStrategicZoneResourceOccupied}(*StrateticZone, ResourceOccupied*) for the case that the zone is occupied by the enemy and --- * @{CHIEF.SetStrategicZoneResourceEmpty}(*StrateticZone, ResourceEmpty*) for the case that the zone is empty. --- --- The first parameter *StrateticZone* is the strategic zone object that is returned by the @{#CHIEF.AddStrategicZone}() function. --- The second parameter is the resource list created with the @{#CHIEF.CreateResource}() function. +-- These lists can than passed as additional parameters to the @{#CHIEF.AddStrategicZone} function. -- -- For example: -- --- -- Create a resource list of mission types and required assets for the case that the zone is occupied. +-- --- Create a resource list of mission types and required assets for the case that the zone is OCCUPIED. +-- -- -- -- Here, we create an enhanced CAS mission and employ at least on and at most two asset groups. -- local ResourceOccupied=myChief:CreateResource(AUFTRAG.Type.CASENHANCED, 1, 2) -- -- We also add ARTY missions with at least one and at most two assets. We additionally require these to be MLRS groups (and not howitzers). @@ -173,11 +168,8 @@ -- -- Add at least one but at most two BOMBCARPET missions. -- myChief:AddToResource(ResourceOccupied, AUFTRAG.Type.BOMBCARPET, 1, 2) -- --- -- Replace the default list with the customized one. --- myChief:SetStrategicZoneResourceOccupied(myStratZone, ResourceOccupied) --- --- --- -- Create a resource list of mission types and required assets for the case that the zone is empty. +-- --- Create a resource list of mission types and required assets for the case that the zone is EMPTY. +-- -- -- -- Here, we create an ONGUARD mission and employ at least on and at most five infantry assets. -- local ResourceEmpty=myChief:CreateResource(AUFTRAG.Type.ONGUARD, 1, 5, GROUP.Attribute.GROUND_INFANTRY) -- -- Additionally, we send up to three tank groups. @@ -185,10 +177,10 @@ -- -- Finally, we send two groups that patrol the zone. -- myChief:AddToResource(ResourceEmpty, AUFTRAG.Type.PATROLZONE, 2) -- --- -- Set this to be the resources employed when the zone is empty. --- myChief:SetStrategicZoneResourceEmpty(myStratZone, ResourceEmpty) +-- -- Add stratetic zone with customized reaction. +-- myChief:AddStrategicZone(myOpsZone, nil , 2, ResourceOccupied, ResourceEmpty) -- --- As the location of the enemies is not known, only mission types that don't require and explicit target group are possible. These are +-- As the location of the enemies is not known, only mission types that don't require an explicit target group are possible. These are -- -- * `AUFTRAG.Type.CASENHANCED` -- * `AUFTRAG.Type.ARTY` @@ -284,7 +276,7 @@ CHIEF.Strategy = { --- CHIEF class version. -- @field #string version -CHIEF.version="0.3.0" +CHIEF.version="0.3.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1006,23 +998,28 @@ function CHIEF:RemoveTarget(Target) end --- Add strategically important zone. --- By default two resource lists are created. One for the case that the zone is empty and the other for the case that the zone is occupied --- Empty: +-- By default two resource lists are created. One for the case that the zone is empty and the other for the case that the zone is occupied. +-- +-- Occupied: -- -- * `AUFTRAG.Type.ARTY` with Nmin=1, Nmax=2 -- * `AUFTRAG.Type.CASENHANCED` with Nmin=1, Nmax=2 -- --- Occupied: +-- Empty: -- -- * `AUFTRAG.Type.ONGUARD` with Nmin=1 and Nmax=3 assets, Attribute=`GROUP.Attribute.GROUND_INFANTRY`. -- * `AUFTRAG.Type.ONGURAD` with Nmin=1 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_TANK`. -- +-- Resources can be created with the @{#CHIEF.CreateResource} and @{#CHIEF.AddToResource} functions. +-- -- @param #CHIEF self -- @param Ops.OpsZone#OPSZONE OpsZone OPS zone object. --- @param #number Priority Priority. --- @param #number Importance Importance. +-- @param #number Priority Priority. Default 50. +-- @param #number Importance Importance. Default nil. +-- @param #CHIEF.Resource ResourceOccupied (Optional) Resources used then zone is occupied by the enemy. +-- @param #CHIEF.Resource ResourceEmpty (Optional) Resources used then zone is empty. -- @return #CHIEF.StrategicZone The strategic zone. -function CHIEF:AddStrategicZone(OpsZone, Priority, Importance) +function CHIEF:AddStrategicZone(OpsZone, Priority, Importance, ResourceOccupied, ResourceEmpty) local stratzone={} --#CHIEF.StrategicZone @@ -1038,12 +1035,20 @@ function CHIEF:AddStrategicZone(OpsZone, Priority, Importance) end -- Add resources if zone is occupied. - stratzone.resourceOccup=self:CreateResource(AUFTRAG.Type.ARTY, 1, 2) - self:AddToResource(stratzone.resourceOccup, AUFTRAG.Type.CASENHANCED, 1, 2) + if ResourceOccupied then + stratzone.resourceOccup=UTILS.DeepCopy(ResourceOccupied) + else + stratzone.resourceOccup=self:CreateResource(AUFTRAG.Type.ARTY, 1, 2) + self:AddToResource(stratzone.resourceOccup, AUFTRAG.Type.CASENHANCED, 1, 2) + end -- Add resources if zone is empty - stratzone.resourceEmpty=self:CreateResource(AUFTRAG.Type.ONGUARD, 1, 3, GROUP.Attribute.GROUND_INFANTRY) - self:AddToResource(stratzone.resourceEmpty, AUFTRAG.Type.ONGUARD, 1, 1, GROUP.Attribute.GROUND_TANK) + if ResourceEmpty then + stratzone.resourceEmpty=UTILS.DeepCopy(ResourceEmpty) + else + stratzone.resourceEmpty=self:CreateResource(AUFTRAG.Type.ONGUARD, 1, 3, GROUP.Attribute.GROUND_INFANTRY) + self:AddToResource(stratzone.resourceEmpty, AUFTRAG.Type.ONGUARD, 1, 1, GROUP.Attribute.GROUND_TANK) + end -- Add to table. table.insert(self.zonequeue, stratzone) @@ -2442,14 +2447,15 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD, 100)) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK, 50)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30)) elseif attribute==GROUP.Attribute.GROUND_EWR then -- EWR - - table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD, 100)) - table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 90)) + + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK, 50)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30)) elseif attribute==GROUP.Attribute.GROUND_AAA then @@ -2484,11 +2490,13 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.CAS, 100)) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK, 50)) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK, 40)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30)) else table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK, 50)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30)) end diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 7da4b9eaa..e5626c065 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -26,6 +26,9 @@ -- @field #string aircrafttype Type of the units the cohort is using. -- @field #number category Group category of the assets: `Group.Category.AIRPLANE`, `Group.Category.HELICOPTER`, `Group.Category.GROUND`, `Group.Category.SHIP`, `Group.Category.TRAIN`. -- @field Wrapper.Group#GROUP templategroup Template group. +-- @field #boolean isAir +-- @field #boolean isGround Is ground. +-- @field #boolean isNaval Is naval. -- @field #table assets Cohort assets. -- @field #table missiontypes Capabilities (mission types and performances) of the cohort. -- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight. @@ -83,7 +86,7 @@ COHORT = { --- COHORT class version. -- @field #string version -COHORT.version="0.3.2" +COHORT.version="0.3.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -226,6 +229,74 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- @param #COHORT self -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Pause". + -- @function [parent=#COHORT] Pause + -- @param #COHORT self + + --- Triggers the FSM event "Pause" after a delay. + -- @function [parent=#COHORT] __Pause + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + --- On after "Pause" event. + -- @function [parent=#AUFTRAG] OnAfterPause + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Unpause". + -- @function [parent=#COHORT] Unpause + -- @param #COHORT self + + --- Triggers the FSM event "Unpause" after a delay. + -- @function [parent=#COHORT] __Unpause + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + --- On after "Unpause" event. + -- @function [parent=#AUFTRAG] OnAfterUnpause + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Relocate". + -- @function [parent=#COHORT] Relocate + -- @param #COHORT self + + --- Triggers the FSM event "Relocate" after a delay. + -- @function [parent=#COHORT] __Relocate + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + --- On after "Relocate" event. + -- @function [parent=#AUFTRAG] OnAfterRelocate + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Relocated". + -- @function [parent=#COHORT] Relocated + -- @param #COHORT self + + --- Triggers the FSM event "Relocated" after a delay. + -- @function [parent=#COHORT] __Relocated + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + --- On after "Relocated" event. + -- @function [parent=#AUFTRAG] OnAfterRelocated + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + return self end @@ -905,9 +976,9 @@ end -- @return #table Assets that can do the required mission. -- @return #number Number of payloads still available after recruiting the assets. function COHORT:RecruitAssets(MissionType, Npayloads) - self:T("RecruitAssets for " .. MissionType .. " with " ..Npayloads) + -- Debug info. - self:T3(self.lid..string.format("Recruiting asset for Mission type=%s", MissionType)) + self:T2(self.lid..string.format("Recruiting asset for Mission type=%s", MissionType)) -- Recruited assets. local assets={} @@ -916,32 +987,60 @@ function COHORT:RecruitAssets(MissionType, Npayloads) for _,_asset in pairs(self.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - --self:I("Looking at Asset " .. asset.spawngroupname) + -- Get info. + local isRequested=asset.requested + local isReserved=asset.isReserved + local isSpawned=asset.spawned + local isOnMission=self.legion:IsAssetOnMission(asset) + + local opsgroup=asset.flightgroup + + -- Debug info. + self:T(self.lid..string.format("Asset %s: requested=%s, reserved=%s, spawned=%s, onmission=%s", + asset.spawngroupname, tostring(isRequested), tostring(isReserved), tostring(isSpawned), tostring(isOnMission))) -- First check that asset is not requested or reserved. This could happen if multiple requests are processed simultaniously. - if not (asset.requested or asset.isReserved) then + if not (isRequested or isReserved) then - --self:I("Not requested or reserved") -- Check if asset is currently on a mission (STARTED or QUEUED). - if self.legion:IsAssetOnMission(asset) then + 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 MissionType==AUFTRAG.Type.INTERCEPT then + if MissionType==AUFTRAG.Type.RELOCATECOHORT then + + -- Relocation: Take all assets. Mission will be cancelled. + table.insert(assets, asset) + + elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and MissionType==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:T(self.lid..string.format("Adding asset on GCICAP mission for an INTERCEPT mission")) table.insert(assets, asset) + elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ONGUARD) and (MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK) then + + if not opsgroup:IsOutOfAmmo() then + self:T(self.lid..string.format("Adding asset on ONGUARD mission for an XXX mission")) + table.insert(assets, asset) + end + + elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.PATROLZONE) and (MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK) then + + if not opsgroup:IsOutOfAmmo() then + self:T(self.lid..string.format("Adding asset on PATROLZONE mission for an XXX mission")) + table.insert(assets, asset) + end + elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and AUFTRAG.CheckMissionCapability(MissionType, asset.payload.capabilities) 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)) - table.insert(assets, asset) - + table.insert(assets, asset) + end else diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 3aeabeb10..856c7e1d1 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -2,7 +2,7 @@ -- -- **Main Features:** -- --- * Manages AIRWINGS, BRIGADEs and FLOTILLAs +-- * Manages AIRWINGS, BRIGADEs and FLEETs -- * Handles missions (AUFTRAG) and finds the best assets for the job -- -- === @@ -95,7 +95,7 @@ -- -- The COMMANDER will -- --- # OPSGROUP on Mission +-- ## OPSGROUP on Mission -- -- Whenever an OPSGROUP (FLIGHTGROUP, ARMYGROUP or NAVYGROUP) is send on a mission, the `OnAfterOpsOnMission()` event is triggered. -- Mission designers can hook into the event with the @{#COMMANDER.OnAfterOpsOnMission}() function @@ -104,7 +104,7 @@ -- -- Your code -- end -- --- # Canceling a Mission +-- ## Canceling a Mission -- -- A mission can be cancelled with the @{#COMMMANDER.MissionCancel}() function -- @@ -136,7 +136,7 @@ COMMANDER = { --- COMMANDER class version. -- @field #string version -COMMANDER.version="0.1.1" +COMMANDER.version="0.1.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -398,7 +398,7 @@ function COMMANDER:AddAirwing(Airwing) return self end ---- Add an BRIGADE to the commander. +--- Add a BRIGADE to the commander. -- @param #COMMANDER self -- @param Ops.Brigade#BRIGADE Brigade The brigade to add. -- @return #COMMANDER self @@ -410,6 +410,19 @@ function COMMANDER:AddBrigade(Brigade) return self end +--- Add a FLEET to the commander. +-- @param #COMMANDER self +-- @param Ops.Fleet#FLEET Fleet The fleet to add. +-- @return #COMMANDER self +function COMMANDER:AddFleet(Fleet) + + -- Add legion. + self:AddLegion(Fleet) + + return self +end + + --- Add a LEGION to the commander. -- @param #COMMANDER self -- @param Ops.Legion#LEGION Legion The legion to add. @@ -653,6 +666,83 @@ function COMMANDER:IsMission(Mission) return false end +--- Relocate a cohort to another legion. +-- Assets in stock are spawned and routed to the new legion. +-- If assets are spawned, running missions will be cancelled. +-- Cohort assets will not be available until relocation is finished. +-- @param #COMMANDER self +-- @param Ops.Cohort#COHORT Cohort The cohort to be relocated. +-- @param Ops.Legion#LEGION Legion The legion where the cohort is relocated to. +-- @param #number Delay Delay in seconds before relocation takes place. Default `nil`, *i.e.* ASAP. +-- @param #number NcarriersMin Min number of transport carriers in case the troops should be transported. Default `nil` for no transport. +-- @param #number NcarriersMax Max number of transport carriers. +-- @param #table TransportLegions Legion(s) assigned for transportation. Default is all legions of the commander. +-- @return #COMMANDER self +function COMMANDER:RelocateCohort(Cohort, Legion, Delay, NcarriersMin, NcarriersMax, TransportLegions) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, COMMANDER.RelocateCohort, self, Cohort, Legion, 0, NcarriersMin, NcarriersMax, TransportLegions) + else + + -- Add cohort to legion. + if Legion:IsCohort(Cohort.name) then + self:E(self.lid..string.format("ERROR: Cohort %s is already part of new legion %s ==> CANNOT Relocate!", Cohort.name, Legion.alias)) + return self + else + table.insert(Legion.cohorts, Cohort) + end + + -- Old legion. + local LegionOld=Cohort.legion + + -- Check that cohort is part of this legion + if not LegionOld:IsCohort(Cohort.name) then + self:E(self.lid..string.format("ERROR: Cohort %s is NOT part of this legion %s ==> CANNOT Relocate!", Cohort.name, self.alias)) + return self + end + + -- Check that legions are different. + if LegionOld.alias==Legion.alias then + self:E(self.lid..string.format("ERROR: old legion %s is same as new legion %s ==> CANNOT Relocate!", LegionOld.alias, Legion.alias)) + return self + end + + -- Trigger Relocate event. + Cohort:Relocate() + + -- Create a relocation mission. + local mission=AUFTRAG:_NewRELOCATECOHORT(Legion, Cohort) + + -- Assign cohort to mission. + mission:AssignCohort(Cohort) + + -- All assets required. + mission:SetRequiredAssets(#Cohort.assets) + + -- Set transportation. + if NcarriersMin and NcarriersMin>0 then + mission:SetRequiredTransport(Legion.spawnzone, NcarriersMin, NcarriersMax) + end + + -- Assign transport legions. + if TransportLegions then + for _,legion in pairs(TransportLegions) do + mission:AssignTransportLegion(legion) + end + else + for _,legion in pairs(self.legions) do + mission:AssignTransportLegion(legion) + end + end + + -- Add mission. + self:AddMission(mission) + + end + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -974,7 +1064,7 @@ function COMMANDER:onafterMissionCancel(From, Event, To, Mission) end ---- On after "TransportAssign" event. Transport is added to a LEGION mission queue. +--- On after "TransportAssign" event. Transport is added to a LEGION transport queue. -- @param #COMMANDER self -- @param #string From From state. -- @param #string Event Event. @@ -1215,7 +1305,8 @@ function COMMANDER:RecruitAssetsForMission(Mission) local Payloads=Mission.payloads -- Recruite assets. - local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, Mission.engageRange, Mission.refuelSystem) + local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, + Mission.engageRange, Mission.refuelSystem, nil, nil, nil, Mission.attributes, Mission.properties, {Mission.engageWeaponType}) return recruited, assets, legions end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 530d5413c..aa0f1f860 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -867,7 +867,7 @@ function FLIGHTGROUP:Status() local ammo=self:GetAmmoTot().Total -- Detected units. - local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "OFF" + local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "Off" -- Get cargo weight. local cargo=0 diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 8a7963164..477934925 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -430,13 +430,16 @@ end -- Cohort assets will not be available until relocation is finished. -- @param #LEGION self -- @param Ops.Cohort#COHORT Cohort The cohort to be relocated. --- @param Ops.Legion#LEGION Legion. --- @param #number Delay Delay in seconds before relocation takes place. Default 0 sec. +-- @param Ops.Legion#LEGION Legion The legion where the cohort is relocated to. +-- @param #number Delay Delay in seconds before relocation takes place. Default `nil`, *i.e.* ASAP. +-- @param #number NcarriersMin Min number of transport carriers in case the troops should be transported. Default `nil` for no transport. +-- @param #number NcarriersMax Max number of transport carriers. +-- @param #table TransportLegions Legion(s) assigned for transportation. Default is that transport assets can only be recruited from this legion. -- @return #LEGION self -function LEGION:RelocateCohort(Cohort, Legion, Delay) +function LEGION:RelocateCohort(Cohort, Legion, Delay, NcarriersMin, NcarriersMax, TransportLegions) if Delay and Delay>0 then - self:ScheduleOnce(Delay, LEGION.RelocateCohort, self, Cohort, Legion, 0) + self:ScheduleOnce(Delay, LEGION.RelocateCohort, self, Cohort, Legion, 0, NcarriersMin, NcarriersMax, TransportLegions) else -- Add cohort to legion. @@ -465,17 +468,45 @@ function LEGION:RelocateCohort(Cohort, Legion, Delay) -- Create a relocation mission. local mission=AUFTRAG:_NewRELOCATECOHORT(Legion, Cohort) - -- Add assets to mission. - mission:_AddAssets(Cohort.assets) - - -- Debug info. - self:I(self.lid..string.format("Relocating Cohort %s [nassets=%d] to legion %s", Cohort.name, #Cohort.assets, Legion.alias)) + if false then + --- Disabled for now. - -- Assign mission to this legion. - self:MissionAssign(mission, {self}) + -- Add assets to mission. + mission:_AddAssets(Cohort.assets) + + -- Debug info. + self:I(self.lid..string.format("Relocating Cohort %s [nassets=%d] to legion %s", Cohort.name, #Cohort.assets, Legion.alias)) + + -- Assign mission to this legion. + self:MissionAssign(mission, {self}) + + else + + -- Assign cohort to mission. + mission:AssignCohort(Cohort) + + -- All assets required. + mission:SetRequiredAssets(#Cohort.assets) + + -- Set transportation. + if NcarriersMin and NcarriersMin>0 then + mission:SetRequiredTransport(Legion.spawnzone, NcarriersMin, NcarriersMax) + end + + -- Assign transport legions. + if TransportLegions then + for _,legion in pairs(TransportLegions) do + mission:AssignTransportLegion(legion) + end + end + + -- Add mission. + self:AddMission(mission) + end end + return self end --- Get cohort by name. @@ -810,7 +841,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) --- if asset.flightgroup then - + -- Add new mission. asset.flightgroup:AddMission(Mission) @@ -822,27 +853,29 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) local currM=asset.flightgroup:GetMissionCurrent() if currM then + + -- Cancel? local cancel=false + -- Pause? + local pause=false -- Check if mission is INTERCEPT and asset is currently on GCI mission. If so, GCI is paused. if currM.type==AUFTRAG.Type.GCICAP and Mission.type==AUFTRAG.Type.INTERCEPT then - self:T(self.lid..string.format("Pausing %s mission %s to send flight on intercept mission %s", currM.type, currM.name, Mission.name)) - asset.flightgroup:PauseMission() + pause=true + elseif (currM.type==AUFTRAG.Type.ONGUARD or currM.type==AUFTRAG.Type.PATROLZONE) and (Mission.type==AUFTRAG.Type.ARTY or Mission.type==AUFTRAG.Type.GROUNDATTACK) then + pause=true end - -- Cancel current ALERT5 mission + -- Cancel current ALERT5 mission. if currM.type==AUFTRAG.Type.ALERT5 then cancel=true end - - -- Cancel the current mission. - if currM.type==AUFTRAG.Type.ONGUARD or currM.type==AUFTRAG.Type.ARMOREDGUARD then - cancel=true - end - + + -- Cancel current mission for relcation. if Mission.type==AUFTRAG.Type.RELOCATECOHORT then cancel=true + -- Get request ID. local requestID=currM.requestID[self.alias] -- Get request. @@ -853,18 +886,26 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) request.cargogroupset:Remove(asset.spawngroupname, true) else self:E(self.lid.."ERROR: no request for spawned asset!") - end + end + end - + -- Cancel mission. if cancel then + self:T(self.lid..string.format("Cancel current mission %s [%s] to send group on mission %s [%s]", currM.name, currM.type, Mission.name, Mission.type)) asset.flightgroup:MissionCancel(currM) + elseif pause then + self:T(self.lid..string.format("Pausing current mission %s [%s] to send group on mission %s [%s]", currM.name, currM.type, Mission.name, Mission.type)) + asset.flightgroup:PauseMission() end + + -- Not reserved any more. + asset.isReserved=false end -- Trigger event. - self:__OpsOnMission(5, asset.flightgroup, Mission) + self:__OpsOnMission(2, asset.flightgroup, Mission) else self:E(self.lid.."ERROR: OPSGROUP for asset does NOT exist but it seems to be SPAWNED (asset.spawned=true)!") @@ -893,6 +934,13 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) -- Set asset to requested! Important so that new requests do not use this asset! asset.requested=true + + -- Spawned asset are not requested. + if asset.spawned then + asset.requested=false + end + + -- Not reserved and more. asset.isReserved=false -- Set mission task so that the group is spawned with the right one. @@ -1344,7 +1392,6 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) local Tacan=cohort:FetchTacan() if Tacan then asset.tacan=Tacan - --flightgroup:SetDefaultTACAN(Tacan,Morse,UnitName,Band,OffSwitch) flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) end @@ -1400,6 +1447,11 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) -- Add mission to flightgroup queue. If mission has an OPSTRANSPORT attached, all added OPSGROUPS are added as CARGO for a transport. flightgroup:AddMission(mission) + + -- RTZ on out of ammo. + if self:IsBrigade() or self:IsFleet() then + flightgroup:SetReturnOnOutOfAmmo() + end -- Trigger event. self:__OpsOnMission(5, flightgroup, mission) @@ -1669,26 +1721,7 @@ function LEGION:IsAssetOnMission(asset, MissionTypes) 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 @@ -1907,7 +1940,6 @@ function LEGION:CountAssetsOnMission(MissionTypes, Cohort) end end - --env.info(string.format("FF N=%d Np=%d, Nq=%d", Np+Nq, Np, Nq)) return Np+Nq, Np, Nq end @@ -2043,11 +2075,11 @@ function LEGION:RecruitAssetsForMission(Mission) -- No escort cohorts/legions given ==> take own cohorts. if #Cohorts==0 then Cohorts=self.cohorts - end - + end + -- Recuit assets. local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, - Mission.engageRange, Mission.refuelSystem, nil, nil, nil, nil, nil, {Mission.engageWeaponType}) + Mission.engageRange, Mission.refuelSystem, nil, nil, nil, Mission.attributes, Mission.properties, {Mission.engageWeaponType}) return recruited, assets, legions end @@ -2226,10 +2258,14 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, local cohort=_cohort --Ops.Cohort#COHORT if WeaponTypes and #WeaponTypes>0 then for _,WeaponType in pairs(WeaponTypes) do - for _,_weaponData in pairs(cohort.weaponData or {}) do - local weaponData=_weaponData --Ops.OpsGroup#OPSGROUP.WeaponData - if weaponData.BitType==WeaponType then - return true + if WeaponType==ENUMS.WeaponFlag.Auto then + return true + else + for _,_weaponData in pairs(cohort.weaponData or {}) do + local weaponData=_weaponData --Ops.OpsGroup#OPSGROUP.WeaponData + if weaponData.BitType==WeaponType then + return true + end end end end @@ -2262,9 +2298,7 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, Refuel=false end end - - --env.info(string.format("Cohort=%s: RefuelSystem=%s, TankerSystem=%s ==> Refuel=%s", cohort.name, tostring(RefuelSystem), tostring(cohort.tankerSystem), tostring(Refuel))) - + -- Is capable of the mission type? local Capable=AUFTRAG.CheckMissionCapability({MissionTypeRecruit}, cohort.missiontypes) @@ -2283,12 +2317,19 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, -- Right weapon type. local RightWeapon=CheckWeapon(cohort) + -- Cohort ready to execute mission. + local Ready=cohort:IsOnDuty() + if MissionTypeRecruit==AUFTRAG.Type.RELOCATECOHORT then + Ready=cohort:IsRelocating() + Capable=true + end + -- Debug info. cohort:T2(cohort.lid..string.format("State=%s: Capable=%s, InRange=%s, Refuel=%s, CanCarry=%s, Category=%s, Attribute=%s, Property=%s, Weapon=%s", cohort:GetState(), tostring(Capable), tostring(InRange), tostring(Refuel), tostring(CanCarry), tostring(RightCategory), tostring(RightAttribute), tostring(RightProperty), tostring(RightWeapon))) -- Check OnDuty, capable, in range and refueling type (if TANKER). - if cohort:IsOnDuty() and Capable and InRange and Refuel and CanCarry and RightCategory and RightAttribute and RightProperty and RightWeapon then + if Ready and Capable and InRange and Refuel and CanCarry and RightCategory and RightAttribute and RightProperty and RightWeapon then -- Recruit assets from cohort. local assets, npayloads=cohort:RecruitAssets(MissionTypeRecruit, 999) @@ -2702,9 +2743,10 @@ function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu -- Reduce score for legions that are futher away. score=score-distance - -- Intercepts need to be carried out quickly. We prefer spawned assets. + -- Check for spawned assets. if asset.spawned and asset.flightgroup and asset.flightgroup:IsAlive() then + -- Get current mission. local currmission=asset.flightgroup:GetMissionCurrent() if currmission then @@ -2712,10 +2754,13 @@ function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu if currmission.type==AUFTRAG.Type.ALERT5 and currmission.alert5MissionType==MissionType then -- Prefer assets that are on ALERT5 for this mission type. score=score+25 - elseif currmission==AUFTRAG.Type.GCICAP and MissionType==AUFTRAG.Type.INTERCEPT then + elseif currmission.type==AUFTRAG.Type.GCICAP and MissionType==AUFTRAG.Type.INTERCEPT then -- Prefer assets that are on GCICAP to perform INTERCEPTS score=score+25 + elseif (currmission.type==AUFTRAG.Type.ONGUARD or currmission.type==AUFTRAG.Type.PATROLZONE) and (MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK) then + score=score+25 end + end if MissionType==AUFTRAG.Type.OPSTRANSPORT or MissionType==AUFTRAG.Type.AMMOSUPPLY or MissionType==AUFTRAG.Type.AWACS or MissionType==AUFTRAG.Type.FUELSUPPLY or MissionType==AUFTRAG.Type.TANKER then @@ -2742,6 +2787,10 @@ function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu -- Max speed of assets. -- Fuel amount? -- Range of assets? + + if asset.legion and asset.legion.verbose>=2 then + asset.legion:I(asset.legion.lid..string.format("Asset %s [spawned=%s] score=%d", asset.spawngroupname, tostring(asset.spawned), score)) + end return score end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index d276d70f1..176b22fb6 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -828,7 +828,7 @@ function NAVYGROUP:Status(From, Event, To) local ammo=self:GetAmmoTot().Total -- Detected units. - local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "OFF" + local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "Off" -- Get cargo weight. local cargo=0 diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8907ad8b6..2ccd06b4d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -18,6 +18,7 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #string groupname Name of the group. -- @field Wrapper.Group#GROUP group Group object. +-- @field DCS#Group dcsgroup The DCS group object. -- @field DCS#Controller controller The DCS controller of the group. -- @field DCS#Template template Template table of the group. -- @field #table elements Table of elements, i.e. units of the group. @@ -57,6 +58,7 @@ -- @field #number speedMax Max speed in km/h. -- @field #number speedCruise Cruising speed in km/h. -- @field #number speedWp Speed to the next waypoint in m/s. +-- @field #boolean isMobile If `true`, group is mobile (speed > 1 m/s) -- @field #boolean passedfinalwp Group has passed the final waypoint. -- @field #number wpcounter Running number counting waypoints. -- @field Core.Set#SET_ZONE checkzones Set of zones. @@ -200,6 +202,7 @@ OPSGROUP = { -- @field Wrapper.Unit#UNIT unit The UNIT object. -- @field Wrapper.Group#GROUP group The GROUP object. -- @field DCS#Unit DCSunit The DCS unit object. +-- @field DCS#Controller controller The DCS controller of the unit. -- @field #boolean ai If true, element is AI. -- @field #string skill Skill level. -- @@ -466,7 +469,7 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="0.7.7" +OPSGROUP.version="0.7.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1045,6 +1048,108 @@ function OPSGROUP:SetDetection(Switch) return self end +--- Get DCS group object. +-- @param #OPSGROUP self +-- @return DCS#Group DCS group object. +function OPSGROUP:GetDCSObject() + return self.dcsgroup +end + +--- Set detection on or off. +-- If detection is on, detected targets of the group will be evaluated and FSM events triggered. +-- @param #OPSGROUP self +-- @param Wrapper.Positionable#POSITIONABLE TargetObject The target object. +-- @param #boolean KnowType Make type known. +-- @param #boolean KnowDist Make distance known. +-- @param #number Delay Delay in seconds before the target is known. +-- @return #OPSGROUP self +function OPSGROUP:KnowTarget(TargetObject, KnowType, KnowDist, Delay) + + if Delay and Delay>0 then + -- Delayed call. + self:ScheduleOnce(Delay, OPSGROUP.KnowTarget, self, TargetObject, KnowType, KnowDist, 0) + else + + if TargetObject:IsInstanceOf("GROUP") then + TargetObject=TargetObject:GetUnit(1) + elseif TargetObject:IsInstanceOf("OPSGROUP") then + TargetObject=TargetObject.group:GetUnit(1) + end + + -- Get the DCS object. + local object=TargetObject:GetDCSObject() + + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element.controller then + element.controller:knowTarget(object, true, true) + --self:T(self.lid..string.format("Element %s should now know target %s", element.name, TargetObject:GetName())) + end + end + + -- Debug info. + self:T(self.lid..string.format("We should now know target %s", TargetObject:GetName())) + + end + + return self +end + +--- Check if target is detected. +-- @param #OPSGROUP self +-- @param Wrapper.Positionable#POSITIONABLE TargetObject The target object. +-- @return #boolean If `true`, target was detected. +function OPSGROUP:IsTargetDetected(TargetObject) + + local objects={} + + if TargetObject:IsInstanceOf("GROUP") then + for _,unit in pairs(TargetObject:GetUnits()) do + table.insert(objects, unit:GetDCSObject()) + end + elseif TargetObject:IsInstanceOf("OPSGROUP") then + for _,unit in pairs(TargetObject.group:GetUnits()) do + table.insert(objects, unit:GetDCSObject()) + end + elseif TargetObject:IsInstanceOf("UNIT") or TargetObject:IsInstanceOf("STATIC") then + table.insert(objects, TargetObject:GetDCSObject()) + end + + for _,object in pairs(objects or {}) do + + -- Check group controller. + local detected, visible, lastTime, type, distance, lastPos, lastVel = self.controller:isTargetDetected(object, 1, 2, 4, 8, 16, 32) + + --env.info(self.lid..string.format("Detected target %s: %s", TargetObject:GetName(), tostring(detected))) + + if detected then + return true + end + + + -- Check all elements. + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element.controller then + + -- Check. + local detected, visible, lastTime, type, distance, lastPos, lastVel= + element.controller:isTargetDetected(object, 1, 2, 4, 8, 16, 32) + + --env.info(self.lid..string.format("Element %s detected target %s: %s", element.name, TargetObject:GetName(), tostring(detected))) + + if detected then + return true + end + + end + end + + end + + return false +end + --- Set LASER parameters. -- @param #OPSGROUP self -- @param #number Code Laser code. Default 1688. @@ -1285,6 +1390,23 @@ function OPSGROUP:SetRearmOnOutOfAmmo() return self end +--- Set that group is retreating once it runs out of ammo. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:SetRetreatOnOutOfAmmo() + self.retreatOnOutOfAmmo=true + return self +end + +--- Set that group is return to legion once it runs out of ammo. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:SetReturnOnOutOfAmmo() + self.rtzOnOutOfAmmo=true + return self +end + + --- Check if an element of the group has line of sight to a coordinate. -- @param #OPSGROUP self -- @param Core.Point#COORDINATE Coordinate The position to which we check the LoS. @@ -3633,6 +3755,29 @@ function OPSGROUP:onbeforeTaskExecute(From, Event, To, Task) end end + + if Mission and Mission.opstransport then + + local delivered=Mission.opstransport:IsCargoDelivered(self.groupname) + + if not delivered then + + local dt=30 + + -- Debug info. + self:T(self.lid..string.format("Mission %s task execute suspended for %d seconds because we were not delivered", Mission.name, dt)) + + -- Reexecute task. + self:__TaskExecute(-dt, Task) + + if (self:IsArmygroup() or self:IsNavygroup()) and self:IsCruising() then + self:FullStop() + end + + -- Deny transition. + return false + end + end return true end @@ -3817,6 +3962,21 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- FLIGHTGROUP not implemented (intended!) for this AUFTRAG type. end + elseif Task.dcstask.id==AUFTRAG.SpecialTask.AIRDEFENSE or Task.dcstask.id==AUFTRAG.SpecialTask.EWR then + + --- + -- Task "AIRDEFENSE" or "EWR" Mission. + --- + + -- Just stay put. + --TODO: Change ALARM STATE + + if self:IsArmygroup() or self:IsNavygroup() then + self:FullStop() + else + -- FLIGHTGROUP not implemented (intended!) for this AUFTRAG type. + end + elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then --- @@ -3903,20 +4063,67 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- If task is scheduled (not waypoint) set task. if Task.type==OPSGROUP.TaskType.SCHEDULED or Task.ismission then - local DCSTask=nil --UTILS.DeepCopy(Task.dcstask) + -- DCS task. + local DCSTask=nil -- BARRAGE is special! if Task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then + --- + -- BARRAGE + + -- Current vec2. local vec2=self:GetVec2() + + -- Task parameters. local param=Task.dcstask.params + + -- Set heading and altitude. local heading=param.heading or math.random(1, 360) local Altitude=param.altitude or 500 local Alpha=param.angle or math.random(45, 85) local distance=Altitude/math.tan(math.rad(Alpha)) local tvec2=UTILS.Vec2Translate(vec2, distance, heading) + + -- Debug info. self:T(self.lid..string.format("Barrage: Shots=%s, Altitude=%d m, Angle=%d°, heading=%03d°, distance=%d m", tostring(param.shots), Altitude, Alpha, heading, distance)) + + -- Set fire at point task. DCSTask=CONTROLLABLE.TaskFireAtPoint(nil, tvec2, param.radius, param.shots, param.weaponType, Altitude) + + elseif Task.ismission and Task.dcstask.id=='FireAtPoint' then + + -- Copy DCS task. + DCSTask=UTILS.DeepCopy(Task.dcstask) + + -- Get current ammo. + local ammo=self:GetAmmoTot() + + -- Number of ammo avail. + local nAmmo=ammo.Total + + if DCSTask.params.weaponType then + --TODO: use weapon type infor, e.g. for cruise missiles + end + + --TODO: Update target location while we're at it anyway. + --TODO: Adjust mission result evaluation time? E.g. cruise missiles can fly a long time depending on target distance. + + -- Number of shots to be fired. + local nShots=DCSTask.params.expendQty or 1 + + -- Debug info. + self:T(self.lid..string.format("Fire at point with nshots=%d of %d", nShots, nAmmo)) + + -- Only fire number of avail shots. + nShots=math.min(nShots, nAmmo) + + -- Set quantity of task. + DCSTask.params.expendQty=nShots + else + --- + -- Take DCS task + --- DCSTask=Task.dcstask end @@ -4126,12 +4333,12 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) else if Task.description=="Engage_Target" then - self:T(self.lid.."Taske DONE Engage_Target ==> Cruise") + self:T(self.lid.."Task DONE Engage_Target ==> Cruise") self:Disengage() end if Task.description==AUFTRAG.SpecialTask.ONGUARD or Task.description==AUFTRAG.SpecialTask.ARMOREDGUARD then - self:T(self.lid.."Taske DONE OnGuard ==> Cruise") + self:T(self.lid.."Task DONE OnGuard ==> Cruise") self:Cruise() end @@ -4173,7 +4380,6 @@ function OPSGROUP:AddMission(Mission) table.insert(self.missionqueue, Mission) -- ad infinitum? - self.adinfinitum = Mission.DCStask.params.adinfinitum and Mission.DCStask.params.adinfinitum or false -- Info text. @@ -4335,7 +4541,7 @@ function OPSGROUP:_GetNextMission() for _,_opsgroup in pairs(cargos) do local opscargo=_opsgroup --Ops.OpsGroup#OPSGROUP if opscargo.groupname==self.groupname then - isTransport=false + --isTransport=false break end end @@ -4345,9 +4551,16 @@ function OPSGROUP:_GetNextMission() local isScheduled=mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED local isReadyToGo=(mission:IsReadyToGo() or self.legion) local isImportant=(mission.importance==nil or mission.importance<=vip) + + -- Everything on go? + local go=isScheduled and isReadyToGo and isImportant and isTransport and isEscort + + -- Debug info. + self:T3(self.lid..string.format("Mission %s [%s]: Go=%s [Scheduled=%s, Ready=%s, Important=%s, Transport=%s, Escort=%s]", mission:GetName(), mission:GetType(), tostring(go), + tostring(isScheduled), tostring(isReadyToGo), tostring(isImportant), tostring(isTransport), tostring(isEscort))) -- Check necessary conditions. - if isScheduled and isReadyToGo and isImportant and isTransport and isEscort then + if go then return mission end @@ -4483,7 +4696,7 @@ function OPSGROUP:onafterMissionStart(From, Event, To, Mission) Mission:__Started(3) -- Route group to mission zone. - if self.speedMax>3.6 then + if self.speedMax>3.6 or true then self:RouteToMission(Mission, 3) @@ -4492,10 +4705,12 @@ function OPSGROUP:onafterMissionStart(From, Event, To, Mission) -- IMMOBILE Group --- - --env.info("FF Immobile GROUP") + env.info(self.lid.."FF Immobile GROUP") -- Add waypoint task. UpdateRoute is called inside. local Clock=Mission.Tpush and UTILS.SecondsToClock(Mission.Tpush) or 5 + + -- Add mission task. local Task=self:AddTask(Mission.DCStask, Clock, Mission.name, Mission.prio, Mission.duration) Task.ismission=true @@ -4554,6 +4769,8 @@ function OPSGROUP:onafterPauseMission(From, Event, To) -- Cancelling the mission is actually cancelling the current task. self:TaskCancel(Task) + + self:_RemoveMissionWaypoints(Mission) -- Set mission to pause so we can unpause it later. self.missionpaused=Mission @@ -4603,8 +4820,14 @@ function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) --- -- Alert 5 missoins dont have a task set, which could be cancelled. - if Mission.type==AUFTRAG.Type.ALERT5 or Mission.type==AUFTRAG.Type.ONGUARD or Mission.type==AUFTRAG.Type.ARMOREDGUARD then + if Mission.type==AUFTRAG.Type.ALERT5 or + Mission.type==AUFTRAG.Type.ONGUARD or + Mission.type==AUFTRAG.Type.ARMOREDGUARD or + Mission.type==AUFTRAG.Type.AIRDEFENSE or + Mission.type==AUFTRAG.Type.EWR then + self:MissionDone(Mission) + return end @@ -4705,10 +4928,14 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) if Mission.optionAlarm then self:SwitchAlarmstate() end - -- Alarm state to default. + -- EPLRS to default. if Mission.optionEPLRS then self:SwitchEPLRS() end + -- Emission to default. + if Mission.optionEmission then + self:SwitchEmission() + end -- Formation to default. if Mission.optionFormation then self:SwitchFormation() @@ -4821,7 +5048,10 @@ function OPSGROUP:RouteToMission(mission, delay) -- Ingress waypoint coordinate where the mission is executed. local waypointcoord=nil --Core.Point#COORDINATE - + + -- Target zone. + local targetzone=nil --Core.Zone#ZONE + -- Random radius of 1000 meters. local randomradius=mission.missionWaypointRadius or 1000 @@ -4832,27 +5062,81 @@ function OPSGROUP:RouteToMission(mission, delay) elseif self:IsNavygroup() then surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} end + - -- Get ingress waypoint. - if mission.type==AUFTRAG.Type.PATROLZONE or mission.type==AUFTRAG.Type.BARRAGE or mission.type==AUFTRAG.Type.AMMOSUPPLY or mission.type.FUELSUPPLY then - local zone=mission.engageTarget:GetObject() --Core.Zone#ZONE - waypointcoord=zone:GetRandomCoordinate(nil , nil, surfacetypes) + -- Get ingress waypoint. + if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname) then + + --env.info(self.lid.."FF mission waypoint in embark zone") + + -- Get transport zone combo. + local tzc=mission.opstransport:GetTZCofCargo(self.groupname) + + local pickupzone=tzc.PickupZone + + if self:IsInZone(pickupzone) then + -- We are already in the pickup zone. + self:PauseMission() + self:FullStop() + return + else + -- Get a random coordinate inside the pickup zone. + waypointcoord=pickupzone:GetRandomCoordinate() + --waypointcoord:MarkToAll(self.lid.." embark here") + end + + elseif mission.type==AUFTRAG.Type.PATROLZONE or + mission.type==AUFTRAG.Type.BARRAGE or + mission.type==AUFTRAG.Type.AMMOSUPPLY or + mission.type==AUFTRAG.Type.FUELSUPPLY or + mission.type==AUFTRAG.Type.AIRDEFENSE or + mission.type==AUFTRAG.Type.EWR then + --- + -- Missions with ZONE as target + --- + + -- Get the zone. + targetzone=mission.engageTarget:GetObject() --Core.Zone#ZONE + + -- Random coordinate. + waypointcoord=targetzone:GetRandomCoordinate(nil , nil, surfacetypes) + elseif mission.type==AUFTRAG.Type.ONGUARD or mission.type==AUFTRAG.Type.ARMOREDGUARD then + --- + -- Guard + --- + + -- Mission waypoint waypointcoord=mission:GetMissionWaypointCoord(self.group, nil, surfacetypes) - elseif mission.type==AUFTRAG.Type.RELOCATECOHORT then + + elseif mission.type==AUFTRAG.Type.HOVER then + --- + -- Hover + --- + + local zone=mission.engageTarget:GetObject() --Core.Zone#ZONE + waypointcoord=zone:GetCoordinate() + + elseif mission.type==AUFTRAG.Type.RELOCATECOHORT then + --- + -- Relocation + --- + + -- Roughly go to the new legion. local ToCoordinate=mission.DCStask.params.legion:GetCoordinate() + if self.isFlightgroup then waypointcoord=self:GetCoordinate():GetIntermediateCoordinate(ToCoordinate, 0.2):SetAltitude(self.altitudeCruise) else waypointcoord=self:GetCoordinate():GetIntermediateCoordinate(ToCoordinate, 0.05) end + else - waypointcoord=mission:GetMissionWaypointCoord(self.group, randomradius, surfacetypes) - end - - if mission.type==AUFTRAG.Type.HOVER then - local zone=mission.engageTarget:GetObject() --Core.Zone#ZONE - waypointcoord=zone:GetCoordinate() + --- + -- Default case + --- + + waypointcoord=mission:GetMissionWaypointCoord(self.group, randomradius, surfacetypes) end -- Add enroute tasks. @@ -4962,6 +5246,7 @@ function OPSGROUP:RouteToMission(mission, delay) end end + -- Add waypoint. local waypoint=nil --#OPSGROUP.Waypoint @@ -5001,18 +5286,53 @@ function OPSGROUP:RouteToMission(mission, delay) mission:SetGroupEgressWaypointUID(self, Ewaypoint.uid) end + + -- Get current pos. + local coord=self:GetCoordinate() + + -- Distance to waypoint coordinate. + local d=coord:Get2DDistance(waypointcoord) + + -- Debug info. + self:T(self.lid..string.format("FF distance to ingress waypoint=%.1f m", d)) + + -- Check if we are already where we want to be. + if targetzone and self:IsInZone(targetzone) then + self:T(self.lid.."Already in mission zone ==> TaskExecute()") + self:TaskExecute(waypointtask) + return + elseif d<25 then + self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") + self:TaskExecute(waypointtask) + return + end + + -- Check if group is mobile. Note that some immobile units report a speed of 1 m/s = 3.6 km/h. + if self.speedMax<=3.6 or mission.teleport then + + -- Teleport to waypoint coordinate. Mission will not be paused. + self:Teleport(waypointcoord, nil, true) + + -- Execute task in one second. + self:__TaskExecute(-1, waypointtask) + + else + + -- Give cruise command/update route. + if self:IsArmygroup() then + self:Cruise(SpeedToMission) + elseif self:IsNavygroup() then + self:Cruise(SpeedToMission) + elseif self:IsFlightgroup() then + self:UpdateRoute() + end + + end + --- -- Mission Specific Settings --- - self:_SetMissionOptions(mission) - - if self:IsArmygroup() then - self:Cruise(SpeedToMission) - elseif self:IsNavygroup() then - self:Cruise(SpeedToMission) - elseif self:IsFlightgroup() then - self:UpdateRoute() - end + self:_SetMissionOptions(mission) end end @@ -5038,6 +5358,10 @@ function OPSGROUP:_SetMissionOptions(mission) if mission.optionEPLRS then self:SwitchEPLRS(mission.optionEPLRS) end + -- Emission + if mission.optionEPLRS then + self:SwitchEmission(mission.optionEmission) + end -- Formation if mission.optionFormation and self:IsFlightgroup() then self:SwitchFormation(mission.optionFormation) @@ -5441,6 +5765,13 @@ function OPSGROUP:_SetWaypointTasks(Waypoint) -- Check if there is mission task if missiontask then self:T(self.lid.."Executing mission task") + local mission=self:GetMissionByTaskID(missiontask.id) + if mission then + if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname) then + self:PauseMission() + return + end + end self:TaskExecute(missiontask) return 1 end @@ -5497,8 +5828,6 @@ end -- @param #number Speed (Optional) Speed to waypoint in knots. function OPSGROUP:onafterGotoWaypoint(From, Event, To, UID, Speed) - --env.info("FF goto waypoint uid="..tostring(UID)) - local n=self:GetWaypointIndex(UID) if n then @@ -6263,11 +6592,12 @@ end -- @param #OPSGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate where the group is teleported to. -- @param #number Delay Delay in seconds before respawn happens. Default 0. +-- @param #boolean NoPauseMission If `true`, dont pause a running mission. -- @return #OPSGROUP self -function OPSGROUP:Teleport(Coordinate, Delay) +function OPSGROUP:Teleport(Coordinate, Delay, NoPauseMission) if Delay and Delay>0 then - self:ScheduleOnce(Delay, OPSGROUP.Teleport, self, Coordinate) + self:ScheduleOnce(Delay, OPSGROUP.Teleport, self, Coordinate, 0, NoPauseMission) else -- Debug message. @@ -6275,8 +6605,8 @@ function OPSGROUP:Teleport(Coordinate, Delay) --Coordinate:MarkToAll("Teleport "..self.groupname) -- Check if we have a mission running. - if self:IsOnMission() then - self:T(self.lid.."Pausing current mission") + if self:IsOnMission() and not NoPauseMission then + self:T(self.lid.."Pausing current mission for telport") self:PauseMission() end @@ -6285,6 +6615,14 @@ function OPSGROUP:Teleport(Coordinate, Delay) -- Set late activation of template to current state. Template.lateActivation=self:IsLateActivated() + + -- Not uncontrolled. + Template.uncontrolled=false + + -- Set waypoint in air for flighgroups. + if self:IsFlightgroup() then + Template.route.points[1]=Coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, 300, true, nil, nil, "Spawnpoint") + end -- Template units. local units=Template.units @@ -7840,17 +8178,36 @@ function OPSGROUP:onafterLoading(From, Event, To) local cargos={} for _,_cargo in pairs(self.cargoTZC.Cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + -- Check if this group can carry the cargo. + local canCargo=self:CanCargo(cargo.opsgroup) + -- Check if this group is currently acting as carrier. local isCarrier=cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading() - - local isOnMission=cargo.opsgroup:IsOnMission() - + + -- 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 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() + + -- Check if current mission is using this ops transport. + 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() + end + end -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() - if self:CanCargo(cargo.opsgroup) and inzone and cargo.opsgroup:IsNotCargo(true) and (not (cargo.delivered or cargo.opsgroup:IsDead() or isCarrier or isOnMission)) then + if canCargo and inZone and isNotCargo and isHolding and (not (cargo.delivered or cargo.opsgroup:IsDead() or isCarrier or isOnMission)) then table.insert(cargos, cargo) end end @@ -8482,6 +8839,10 @@ function OPSGROUP:onafterUnloaded(From, Event, To, OpsGroupCargo) OpsGroupCargo:Returned() end + if OpsGroupCargo.missionpaused then + OpsGroupCargo:UnpauseMission() + end + end @@ -9037,6 +9398,11 @@ function OPSGROUP:_CheckGroupDone(delay) return end + if self:IsBoarding() then + self:T(self.lid.."Boarding! Group NOT done...") + return + end + -- Group is waiting. We deny all updates. if self:IsWaiting() then -- If group is waiting, we assume that is the way it is meant to be. @@ -11740,6 +12106,7 @@ function OPSGROUP:_AddElementByName(unitname) element.gid=element.DCSunit:getNumber() element.uid=element.DCSunit:getID() --element.group=unit:GetGroup() + element.controller=element.DCSunit:getController() element.opsgroup=self -- Skill etc. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 7817b3e2a..03f31488c 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -656,58 +656,6 @@ function OPSTRANSPORT:GetEmbarkZone(TransportZoneCombo) return TransportZoneCombo.EmbarkZone end ---[[ - ---- Set transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. --- @param #OPSTRANSPORT self --- @param Core.Set#SET_GROUP Carriers Carrier set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. --- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. --- @return #OPSTRANSPORT self -function OPSTRANSPORT:SetEmbarkCarriers(Carriers, TransportZoneCombo) - - -- Debug info. - self:T(self.lid.."Setting embark carriers!") - - -- Use default TZC if no transport zone combo is provided. - TransportZoneCombo=TransportZoneCombo or self.tzcDefault - - if Carriers:IsInstanceOf("GROUP") or Carriers:IsInstanceOf("OPSGROUP") then - - local carrier=self:_GetOpsGroupFromObject(Carriers) - if carrier then - table.insert(TransportZoneCombo.EmbarkCarriers, carrier) - end - - elseif Carriers:IsInstanceOf("SET_GROUP") or Carriers:IsInstanceOf("SET_OPSGROUP") then - - for _,object in pairs(Carriers:GetSet()) do - local carrier=self:_GetOpsGroupFromObject(object) - if carrier then - table.insert(TransportZoneCombo.EmbarkCarriers, carrier) - end - end - - else - self:E(self.lid.."ERROR: Carriers must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") - end - - return self -end - ---- Get embark transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. --- @param #OPSTRANSPORT self --- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. --- @return #table Table of carrier OPS groups. -function OPSTRANSPORT:GetEmbarkCarriers(TransportZoneCombo) - - -- Use default TZC if no transport zone combo is provided. - TransportZoneCombo=TransportZoneCombo or self.tzcDefault - - return TransportZoneCombo.EmbarkCarriers -end - -]] - --- Set disembark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE DisembarkZone Zone where the troops are disembarked. @@ -1366,6 +1314,25 @@ function OPSTRANSPORT:AddAssetCargo(Asset, TransportZoneCombo) return self end +--- Get transport zone combo of cargo group. +-- @param #OPSTRANSPORT self +-- @param #string GroupName Group name of cargo. +-- @return #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +function OPSTRANSPORT:GetTZCofCargo(GroupName) + + for _,_tzc in pairs(self.tzCombos) do + local tzc=_tzc --#OPSTRANSPORT.TransportZoneCombo + for _,_cargo in pairs(tzc.Cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + if cargo.opsgroup:GetName()==GroupName then + return tzc + end + end + end + + return nil +end + --- Add LEGION to the transport. -- @param #OPSTRANSPORT self -- @param Ops.Legion#LEGION Legion The legion. @@ -1638,6 +1605,24 @@ function OPSTRANSPORT:onafterStatusUpdate(From, Event, To) end end +--- Check if a cargo group was delivered. +-- @param #OPSTRANSPORT self +-- @param #string GroupName Name of the group. +-- @return #boolean If `true`, cargo was delivered. +function OPSTRANSPORT:IsCargoDelivered(GroupName) + + for _,_cargo in pairs(self:GetCargos()) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.opsgroup:GetName()==GroupName then + return cargo.delivered + end + + end + + return nil +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Event Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index c738d9608..019e7fdf0 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -428,6 +428,21 @@ function OPSZONE:GetCoordinate() return coordinate end +--- Returns a random coordinate in the zone. +-- @param #OPSZONE self +-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone. +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! +-- @return Core.Point#COORDINATE The random coordinate. +function OPSZONE:GetRandomCoordinate(inner, outer, surfacetypes) + + local zone=self:GetZone() + + local coord=zone:GetRandomCoordinate(inner, outer, surfacetypes) + + return coord +end + --- Get zone name. -- @param #OPSZONE self -- @return #string Name of the zone. diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index c368c7418..be56c16e4 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -31,7 +31,7 @@ -- @field #string tankergroupname Name of the late activated tanker template group. -- @field Wrapper.Group#GROUP tanker Tanker group. -- @field Wrapper.Airbase#AIRBASE airbase The home airbase object of the tanker. Normally the aircraft carrier. --- @field Core.Radio#BEACON beacon Tanker TACAN beacon. +-- @field Core.Beacon#BEACON beacon Tanker TACAN beacon. -- @field #number TACANchannel TACAN channel. Default 1. -- @field #string TACANmode TACAN mode, i.e. "X" or "Y". Default "Y". Use only "Y" for AA TACAN stations! -- @field #string TACANmorse TACAN morse code. Three letters identifying the TACAN station. Default "TKR". @@ -784,10 +784,11 @@ end -- @param #RECOVERYTANKER self -- @param #number channel TACAN channel. Default 1. -- @param #string morse TACAN morse code identifier. Three letters. Default "TKR". +-- @param #string mode TACAN mode, which can be either "Y" (default) or "X". -- @return #RECOVERYTANKER self -function RECOVERYTANKER:SetTACAN(channel, morse) +function RECOVERYTANKER:SetTACAN(channel, morse, mode) self.TACANchannel=channel or 1 - self.TACANmode="Y" + self.TACANmode=mode or "Y" self.TACANmorse=morse or "TKR" self.TACANon=true return self @@ -1625,7 +1626,6 @@ function RECOVERYTANKER:_ActivateTACAN(delay) if delay and delay>0 then -- Schedule TACAN activation. - --SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay) self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self) else diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index bd5ac2a48..a35caa40f 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -529,26 +529,32 @@ function GROUP:HasAttribute(attribute, all) -- Get all units of the group. local _units=self:GetUnits() - local _allhave=true - local _onehas=false + if _units then - for _,_unit in pairs(_units) do - local _unit=_unit --Wrapper.Unit#UNIT - if _unit then - local _hastit=_unit:HasAttribute(attribute) - if _hastit==true then - _onehas=true - else - _allhave=false - end - end + local _allhave=true + local _onehas=false + + for _,_unit in pairs(_units) do + local _unit=_unit --Wrapper.Unit#UNIT + if _unit then + local _hastit=_unit:HasAttribute(attribute) + if _hastit==true then + _onehas=true + else + _allhave=false + end + end + end + + if all==true then + return _allhave + else + return _onehas + end + end - if all==true then - return _allhave - else - return _onehas - end + return nil end --- Returns the maximum speed of the group. @@ -563,16 +569,19 @@ function GROUP:GetSpeedMax() local Units=self:GetUnits() - local speedmax=0 + local speedmax=nil for _,unit in pairs(Units) do local unit=unit --Wrapper.Unit#UNIT + local speed=unit:GetSpeedMax() - if speedmax==0 then - speedmax=speed - elseif speed