diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 38f980a88..5512dde73 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -8100,10 +8100,10 @@ function WAREHOUSE:_GetIDsFromGroup(group) end -- Debug info - self:T(self.lid..string.format("Group Name = %s", tostring(name))) - self:T(self.lid..string.format("Warehouse ID = %s", tostring(wid))) - self:T(self.lid..string.format("Asset ID = %s", tostring(aid))) - self:T(self.lid..string.format("Request ID = %s", tostring(rid))) + self:T3(self.lid..string.format("Group Name = %s", tostring(name))) + self:T3(self.lid..string.format("Warehouse ID = %s", tostring(wid))) + self:T3(self.lid..string.format("Asset ID = %s", tostring(aid))) + self:T3(self.lid..string.format("Request ID = %s", tostring(rid))) return wid,aid,rid else diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index a2024e1c2..4c3e62a66 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -787,6 +787,20 @@ function AIRWING:onafterStatus(From, Event, To) -- Check Rescue Helo missions. self:CheckRescuhelo() + ---------------- + -- Transport --- + ---------------- + + -- Check transport queue. + self:CheckTransportQueue() + + -------------- + -- Mission --- + -------------- + + -- Check mission queue. + self:CheckMissionQueue() + -- General info: if self.verbose>=1 then @@ -843,36 +857,6 @@ function AIRWING:onafterStatus(From, Event, To) self:I(self.lid..text) end - ---------------- - -- Transport --- - ---------------- - - -- Check if any transports should be cancelled. - --self:_CheckTransports() - - -- Get next mission. - local transport=self:_GetNextTransport() - - -- Request mission execution. - if transport then - self:TransportRequest(transport) - end - - -------------- - -- Mission --- - -------------- - - -- Check if any missions should be cancelled. - self:_CheckMissions() - - -- Get next mission. - local mission=self:_GetNextMission() - - -- Request mission execution. - if mission then - self:MissionRequest(mission) - end - end --- Get patrol data. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index eb0d99cb8..499548c3f 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -812,6 +812,44 @@ function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation, end +--- On before "Rearm" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Coordinate Coordinate where to rearm. +-- @param #number Formation Formation of the group. +function ARMYGROUP:onbeforeRearm(From, Event, To, Coordinate, Formation) + + local dt=nil + local allowed=true + + -- Pause current mission. + if self.currentmission and self.currentmission>0 then + self:T(self.lid.."Rearm command but have current mission ==> Pausing mission!") + self:PauseMission() + dt=-0.1 + allowed=false + end + + -- Disengage. + if self:IsEngaging() then + self:T(self.lid.."Rearm command but currently engaging ==> Disengage!") + self:Disengage() + dt=-0.1 + allowed=false + end + + -- Try again... + if dt then + self:T(self.lid..string.format("Trying Rearm again in %.2f sec", dt)) + self:__Rearm(dt, Coordinate, Formation) + allowed=false + end + + return allowed +end + --- On after "Rearm" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -821,6 +859,9 @@ end -- @param #number Formation Formation of the group. function ARMYGROUP:onafterRearm(From, Event, To, Coordinate, Formation) + -- Debug info. + self:I(self.lid..string.format("Group send to rearm")) + -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid @@ -832,6 +873,18 @@ function ARMYGROUP:onafterRearm(From, Event, To, Coordinate, Formation) end +--- On after "Rearmed" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterRearmed(From, Event, To) + self:I(self.lid.."Group rearmed") + + -- Check group done. + self:_CheckGroupDone(1) +end + --- On after "RTZ" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -991,14 +1044,32 @@ end -- @param Wrapper.Group#GROUP Group the group to be engaged. function ARMYGROUP:onbeforeEngageTarget(From, Event, To, Target) + local dt=nil + local allowed=true + local ammo=self:GetAmmoTot() if ammo.Total==0 then self:E(self.lid.."WARNING: Cannot engage TARGET because no ammo left!") return false end + + -- Pause current mission. + if self.currentmission and self.currentmission>0 then + self:T(self.lid.."Engage command but have current mission ==> Pausing mission!") + self:PauseMission() + dt=-0.1 + allowed=false + end - return true + -- Try again... + if dt then + self:T(self.lid..string.format("Trying Engage again in %.2f sec", dt)) + self:__EngageTarget(dt, Target) + allowed=false + end + + return allowed end --- On after "EngageTarget" event. @@ -1015,9 +1086,14 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target) else self.engage.Target=TARGET:New(Target) end - + -- Target coordinate. - self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) + self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) + + + local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) + + -- Backup ROE and alarm state. self.engage.roe=self:GetROE() @@ -1031,7 +1107,7 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target) local uid=self:GetWaypointCurrent().uid -- Add waypoint after current. - self.engage.Waypoint=self:AddWaypoint(self.engage.Coordinate, nil, uid, Formation, true) + self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true) -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=1 @@ -1063,9 +1139,11 @@ function ARMYGROUP:_UpdateEngageTarget() -- Remove current waypoint self:RemoveWaypointByID(self.engage.Waypoint.uid) + + local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) -- Add waypoint after current. - self.engage.Waypoint=self:AddWaypoint(self.engage.Coordinate, nil, uid, Formation, true) + self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true) -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=0 @@ -1102,19 +1180,6 @@ function ARMYGROUP:onafterDisengage(From, Event, To) self:_CheckGroupDone(1) end ---- On after "Rearmed" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function ARMYGROUP:onafterRearmed(From, Event, To) - self:I(self.lid.."Group rearmed") - - -- Check group done. - self:_CheckGroupDone(1) - -end - --- On after "DetourReached" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -1334,7 +1399,48 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Find the neares ammo supply group within a given radius. +-- @param #ARMYGROUP self +-- @param #number Radius Search radius in NM. Default 30 NM. +-- @return Wrapper.Group#GROUP Closest ammo supplying group or `nil` if no group is in the given radius. +function ARMYGROUP:FindNearestAmmoSupply(Radius) + -- Radius in meters. + Radius=UTILS.NMToMeters(Radius or 30) + + -- Current positon. + local coord=self:GetCoordinate() + + -- Scanned units. + local units=coord:ScanUnits(Radius) + + -- Find closest + local dmin=math.huge + local truck=nil --Wrapper.Unit#UNIT + for _,_unit in pairs(units.Set) do + local unit=_unit --Wrapper.Unit#UNIT + + -- Check coaliton and if unit can supply ammo. + if unit:GetCoalition()==self:GetCoalition() and unit:IsAmmoSupply() then + + -- Distance. + local d=unit:GetCoordinate():Get2DDistance(coord) + + -- Check if distance is smaller. + if d=1 then @@ -361,34 +393,21 @@ function BRIGADE:onafterStatus(From, Event, To) self:I(self.lid..text) end - ---------------- - -- Transport --- - ---------------- + ------------------- + -- Rearming Info -- + ------------------- + if self.verbose>=0 then + local text="Rearming Zones:" + for i,_rearmingzone in pairs(self.rearmingZones) do + local rearmingzone=_rearmingzone --#BRIGADE.RearmingZone + + local name=rearmingzone.zone:GetName() - -- Check if any transports should be cancelled. - --self:_CheckTransports() - - -- Get next mission. - local transport=self:_GetNextTransport() - - -- Request mission execution. - if transport then - self:TransportRequest(transport) - end - - -------------- - -- Mission --- - -------------- - - -- Check if any missions should be cancelled. - self:_CheckMissions() - - -- Get next mission. - local mission=self:_GetNextMission() - - -- Request mission execution. - if mission then - self:MissionRequest(mission) + -- Platoon text. + text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", name, rearmingzone.mission:GetState(), rearmingzone.mission:CountOpsGroups()) + + end + self:I(self.lid..text) end end diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index dab2b93cb..892987ceb 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -567,6 +567,18 @@ function CHIEF:AddOpsZone(OpsZone) return self end +--- Add a rearming zone. +-- @param #CHIEF self +-- @param Core.Zone#ZONE RearmingZone Rearming zone. +-- @return Ops.Brigade#BRIGADE.RearmingZone The rearming zone data. +function CHIEF:AddRearmingZone(RearmingZone) + + -- Hand over to commander. + local rearmingzone=self.commander:AddRearmingZone(RearmingZone) + + return rearmingzone +end + --- Set border zone set. -- @param #CHIEF self diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index d57bab942..cab981797 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -20,6 +20,7 @@ -- @field #table legions Table of legions which are commanded. -- @field #table missionqueue Mission queue. -- @field #table transportqueue Transport queue. +-- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.RearmingZone`. -- @field Ops.Chief#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM @@ -39,6 +40,7 @@ COMMANDER = { legions = {}, missionqueue = {}, transportqueue = {}, + rearmingZones = {}, } --- COMMANDER class version. @@ -330,6 +332,24 @@ function COMMANDER:RemoveTransport(Transport) return self end +--- Add a rearming zone. +-- @param #COMMANDER self +-- @param Core.Zone#ZONE RearmingZone Rearming zone. +-- @return Ops.Brigade#BRIGADE.RearmingZone The rearming zone data. +function COMMANDER:AddRearmingZone(RearmingZone) + + local rearmingzone={} --Ops.Brigade#BRIGADE.RearmingZone + + rearmingzone.zone=RearmingZone + rearmingzone.occupied=false + rearmingzone.mission=nil + rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Rearming Zone"):ToCoalition(self:GetCoalition()) + + table.insert(self.rearmingZones, rearmingzone) + + return rearmingzone +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -370,14 +390,25 @@ function COMMANDER:onafterStatus(From, Event, To) -- Status. if self.verbose>=1 then - local text=string.format("Status %s: Legions=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) + local text=string.format("Status %s: Legions=%d, Missions=%d, Transports", fsmstate, #self.legions, #self.missionqueue, #self.transportqueue) self:I(self.lid..text) end -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() - -- Check mission queue and assign one PLANNED mission + -- Check transport queue and assign one PLANNED transport. + self:CheckTransportQueue() + + -- Check rearming zones. + for _,_rearmingzone in pairs(self.rearmingZones) do + local rearmingzone=_rearmingzone --#BRIGADE.RearmingZone + -- Check if mission is nil or over. + if (not rearmingzone.mission) or rearmingzone.mission:IsOver() then + rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone) + self:AddMission(rearmingzone.mission) + end + end --- -- LEGIONS @@ -769,20 +800,31 @@ function COMMANDER:RecruitAssetsForMission(Mission) -- Debug info. env.info(string.format("FF recruiting assets for mission %s [%s]", Mission:GetName(), Mission:GetType())) - + -- Cohorts. - local Cohorts=Mission.squadrons - if not Cohorts then - Cohorts={} - for _,_legion in pairs(Mission.specialLegions or self.legions) do - local legion=_legion --Ops.Legion#LEGION - -- Loops over cohorts. + local Cohorts={} + for _,_legion in pairs(Mission.specialLegions or {}) do + local legion=_legion --Ops.Legion#LEGION + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + for _,_cohort in pairs(Mission.specialCohorts or {}) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + -- No special mission legions/cohorts found ==> take own legions. + if #Cohorts==0 then + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION for _,_cohort in pairs(legion.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT table.insert(Cohorts, cohort) end - end - end + end + end -- Number of required assets. local NreqMin, NreqMax=Mission:GetRequiredAssets() @@ -810,18 +852,30 @@ function COMMANDER:RecruitAssetsForEscort(Mission, Assets) if Mission.NescortMin and Mission.NescortMax and (Mission.NescortMin>0 or Mission.NescortMax>0) then -- Cohorts. - local Cohorts=Mission.squadrons - if not Cohorts then - Cohorts={} - for _,_legion in pairs(Mission.specialLegions or self.legions) do - local legion=_legion --Ops.Legion#LEGION - -- Loops over cohorts. + local Cohorts={} + for _,_legion in pairs(Mission.escortLegions or {}) do + local legion=_legion --Ops.Legion#LEGION + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + for _,_cohort in pairs(Mission.escortCohorts or {}) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + -- No special escort legions/cohorts found ==> take own legions. + if #Cohorts==0 then + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION for _,_cohort in pairs(legion.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT table.insert(Cohorts, cohort) end - end + end end + -- Call LEGION function but provide COMMANDER as self. local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMin) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 3ab6c3257..c59578513 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -3148,13 +3148,13 @@ function FLIGHTGROUP:FindNearestTanker(Radius) local istanker, refuelsystem=unit:IsTanker() - if istanker and self.refueltype==refuelsystem then + if istanker and self.refueltype==refuelsystem and unit:GetCoalition()==self:GetCoalition() then -- Distance. local d=unit:GetCoordinate():Get2DDistance(coord) if d Request and return. + self:TransportRequest(transport) + return true end end @@ -1736,9 +1733,25 @@ function LEGION:RecruitAssetsForMission(Mission) -- Payloads. local Payloads=Mission.payloads - - -- Cohorts. - local Cohorts=Mission.squadrons or self.cohorts + + -- Get special escort legions and/or cohorts. + local Cohorts={} + for _,_legion in pairs(Mission.specialLegions or {}) do + local legion=_legion --Ops.Legion#LEGION + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + for _,_cohort in pairs(Mission.specialCohorts or {}) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + -- No escort cohorts/legions given ==> take own cohorts. + if #Cohorts==0 then + Cohorts=self.cohorts + end -- Recuit assets. local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, Mission.engageRange, Mission.refuelSystem, nil) @@ -1776,6 +1789,8 @@ function LEGION:RecruitAssetsForTransport(Transport) end + -- TODO: Special transport cohorts/legions. + -- Target is the deploy zone. local TargetVec2=Transport:GetDeployZone():GetVec2() @@ -1802,10 +1817,27 @@ function LEGION:RecruitAssetsForEscort(Mission, Assets) -- Debug info. self:I(self.lid..string.format("Reqested escort for mission %s [%s]. Required assets=%d-%d", Mission:GetName(), Mission:GetType(), Mission.NescortMin,Mission.NescortMax)) - --TODO: Maybe add escortCohorts as mission option? + -- Get special escort legions and/or cohorts. + local Cohorts={} + for _,_legion in pairs(Mission.escortLegions or {}) do + local legion=_legion --Ops.Legion#LEGION + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + for _,_cohort in pairs(Mission.escortCohorts or {}) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + -- No escort cohorts/legions given ==> take own cohorts. + if #Cohorts==0 then + Cohorts=self.cohorts + end -- Call LEGION function but provide COMMANDER as self. - local assigned=LEGION.AssignAssetsForEscort(self, self.cohorts, Assets, Mission.NescortMin, Mission.NescortMin) + local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMin) return assigned end @@ -2047,15 +2079,17 @@ function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax) -- We want airplanes for airplanes and helos for everything else. local Categories={Group.Category.HELICOPTER} + local TargetTypes={"Ground Units"} if asset.category==Group.Category.AIRPLANE then Categories={Group.Category.AIRPLANE} + TargetTypes={"Air"} end -- Recruit escort asset for the mission asset. local Erecruited, eassets, elegions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.ESCORT, nil, NescortMin, NescortMax, TargetVec2, nil, nil, nil, nil, Categories) if Erecruited then - Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets} + Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets, ecategory=asset.category, TargetTypes=TargetTypes} else -- Could not find escort for this asset ==> Escort not possible ==> Break the loop. EscortAvail=false @@ -2071,12 +2105,22 @@ function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax) local Elegions=value.EscortLegions local Eassets=value.EscortAssets + local ecategory=value.ecategory for _,_legion in pairs(Elegions) do local legion=_legion --Ops.Legion#LEGION + local OffsetVector=nil --DCS#Vec3 + if ecategory==Group.Category.GROUND then + -- Overhead + OffsetVector={} + OffsetVector.x=0 + OffsetVector.y=UTILS.FeetToMeters(1000) + OffsetVector.z=0 + end + -- Create and ESCORT mission for this asset. - local escort=AUFTRAG:NewESCORT(groupname) + local escort=AUFTRAG:NewESCORT(groupname, OffsetVector, nil, value.TargetTypes) -- Reserve assts and add to mission. for _,_asset in pairs(Eassets) do diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 00b34d84f..4b7d1139a 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -5,7 +5,7 @@ -- * Let the group steam into the wind -- * Command a full stop -- * Patrol waypoints *ad infinitum* --- * Collision warning, if group is heading towards a land mass +-- * Collision warning, if group is heading towards a land mass or another obstacle -- * Automatic pathfinding, e.g. around islands -- * Let a submarine dive and surface -- * Manage TACAN and ICLS beacons @@ -48,9 +48,7 @@ --- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson -- -- === --- --- ![Banner Image](..\Presentations\OPS\NavyGroup\_Main.png) --- +-- -- # The NAVYGROUP Concept -- -- This class enhances naval groups. @@ -88,6 +86,9 @@ NAVYGROUP.version="0.7.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Add RTZ. +-- TODO: Add Retreat. +-- TODO: Add EngageTarget. -- TODO: Submaries. -- TODO: Extend, shorten turn into wind windows. -- TODO: Skipper menu. @@ -132,6 +133,20 @@ function NAVYGROUP:New(group) -- From State --> Event --> To State self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Hold position. + + self:AddTransition("*", "RTZ", "Returning") -- Group is returning to (home) zone. + self:AddTransition("Returning", "Returned", "Returned") -- Group is returned to (home) zone. + + self:AddTransition("*", "Detour", "Cruising") -- Make a detour to a coordinate and resume route afterwards. + self:AddTransition("*", "DetourReached", "*") -- Group reached the detour coordinate. + + self:AddTransition("*", "Retreat", "Retreating") -- Order a retreat. + self:AddTransition("Retreating", "Retreated", "Retreated") -- Group retreated. + + self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target from Cruising state + self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target from Holding state + self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target from OnDetour state + self:AddTransition("Engaging", "Disengage", "Cruising") -- Disengage and back to cruising. self:AddTransition("*", "TurnIntoWind", "Cruising") -- Command the group to turn into the wind. self:AddTransition("*", "TurnedIntoWind", "*") -- Group turned into wind. @@ -141,9 +156,6 @@ function NAVYGROUP:New(group) self:AddTransition("*", "TurningStarted", "*") -- Group started turning. self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning. - self:AddTransition("*", "Detour", "Cruising") -- Make a detour to a coordinate and resume route afterwards. - self:AddTransition("*", "DetourReached", "*") -- Group reached the detour coordinate. - self:AddTransition("*", "CollisionWarning", "*") -- Collision warning. self:AddTransition("*", "ClearAhead", "*") -- Clear ahead. @@ -154,15 +166,190 @@ function NAVYGROUP:New(group) --- Pseudo Functions --- ------------------------ - --- Triggers the FSM event "Stop". Stops the NAVYGROUP and all its event handlers. + --- Triggers the FSM event "TurnIntoWind". + -- @function [parent=#NAVYGROUP] TurnIntoWind -- @param #NAVYGROUP self + -- @param #NAVYGROUP.IntoWind Into wind parameters. - --- Triggers the FSM event "Stop" after a delay. Stops the NAVYGROUP and all its event handlers. - -- @function [parent=#NAVYGROUP] __Stop + --- Triggers the FSM event "TurnIntoWind" after a delay. + -- @function [parent=#NAVYGROUP] __TurnIntoWind -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. - - -- TODO: Add pseudo functions. + -- @param #NAVYGROUP.IntoWind Into wind parameters. + + --- On after "TurnIntoWind" event. + -- @function [parent=#NAVYGROUP] OnAfterTurnIntoWind + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #NAVYGROUP.IntoWind Into wind parameters. + + + --- Triggers the FSM event "TurnedIntoWind". + -- @function [parent=#NAVYGROUP] TurnedIntoWind + -- @param #NAVYGROUP self + + --- Triggers the FSM event "TurnedIntoWind" after a delay. + -- @function [parent=#NAVYGROUP] __TurnedIntoWind + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "TurnedIntoWind" event. + -- @function [parent=#NAVYGROUP] OnAfterTurnedIntoWind + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "TurnIntoWindStop". + -- @function [parent=#NAVYGROUP] TurnIntoWindStop + -- @param #NAVYGROUP self + + --- Triggers the FSM event "TurnIntoWindStop" after a delay. + -- @function [parent=#NAVYGROUP] __TurnIntoWindStop + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "TurnIntoWindStop" event. + -- @function [parent=#NAVYGROUP] OnAfterTurnIntoWindStop + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "TurnIntoWindOver". + -- @function [parent=#NAVYGROUP] TurnIntoWindOver + -- @param #NAVYGROUP self + -- @param #NAVYGROUP.IntoWind IntoWindData Data table. + + --- Triggers the FSM event "TurnIntoWindOver" after a delay. + -- @function [parent=#NAVYGROUP] __TurnIntoWindOver + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + -- @param #NAVYGROUP.IntoWind IntoWindData Data table. + + --- On after "TurnIntoWindOver" event. + -- @function [parent=#NAVYGROUP] OnAfterTurnIntoWindOver + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #NAVYGROUP.IntoWind IntoWindData Data table. + + + + --- Triggers the FSM event "TurningStarted". + -- @function [parent=#NAVYGROUP] TurningStarted + -- @param #NAVYGROUP self + + --- Triggers the FSM event "TurningStarted" after a delay. + -- @function [parent=#NAVYGROUP] __TurningStarted + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "TurningStarted" event. + -- @function [parent=#NAVYGROUP] OnAfterTurningStarted + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "TurningStopped". + -- @function [parent=#NAVYGROUP] TurningStopped + -- @param #NAVYGROUP self + + --- Triggers the FSM event "TurningStopped" after a delay. + -- @function [parent=#NAVYGROUP] __TurningStopped + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "TurningStopped" event. + -- @function [parent=#NAVYGROUP] OnAfterTurningStopped + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "CollisionWarning". + -- @function [parent=#NAVYGROUP] CollisionWarning + -- @param #NAVYGROUP self + + --- Triggers the FSM event "CollisionWarning" after a delay. + -- @function [parent=#NAVYGROUP] __CollisionWarning + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "CollisionWarning" event. + -- @function [parent=#NAVYGROUP] OnAfterCollisionWarning + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "ClearAhead". + -- @function [parent=#NAVYGROUP] ClearAhead + -- @param #NAVYGROUP self + + --- Triggers the FSM event "ClearAhead" after a delay. + -- @function [parent=#NAVYGROUP] __ClearAhead + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "ClearAhead" event. + -- @function [parent=#NAVYGROUP] OnAfterClearAhead + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Dive". + -- @function [parent=#NAVYGROUP] Dive + -- @param #NAVYGROUP self + -- @param #number Depth Dive depth in meters. Default 50 meters. + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- Triggers the FSM event "Dive" after a delay. + -- @function [parent=#NAVYGROUP] __Dive + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + -- @param #number Depth Dive depth in meters. Default 50 meters. + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- On after "Dive" event. + -- @function [parent=#NAVYGROUP] OnAfterDive + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number Depth Dive depth in meters. Default 50 meters. + -- @param #number Speed Speed in knots until next waypoint is reached. + + + --- Triggers the FSM event "Surface". + -- @function [parent=#NAVYGROUP] Surface + -- @param #NAVYGROUP self + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- Triggers the FSM event "Surface" after a delay. + -- @function [parent=#NAVYGROUP] __Surface + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- On after "Surface" event. + -- @function [parent=#NAVYGROUP] OnAfterSurface + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number Speed Speed in knots until next waypoint is reached. -- Init waypoints. @@ -385,7 +572,6 @@ function NAVYGROUP:RemoveTurnIntoWind(IntoWindData) -- Check if this is a window currently open. if self.intowind and self.intowind.Id==IntoWindData.Id then - --env.info("FF stop in remove") self:TurnIntoWindStop() return end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3769b30ae..dbe18ae14 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -393,7 +393,7 @@ OPSGROUP.TaskType={ -- @field #string name Waypoint description. Shown in the F10 map. -- @field #number x Waypoint x-coordinate. -- @field #number y Waypoint y-coordinate. --- @field #boolean detour If true, this waypoint is not part of the normal route. +-- @field #number detour Signifies that this waypoint is not part of the normal route: 0=Hold, 1=Resume Route. -- @field #boolean intowind If true, this waypoint is a turn into wind route point. -- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. -- @field #boolean temp If true, this is a temporary waypoint and will be deleted when passed. Also the passing waypoint FSM event is not triggered. @@ -2693,7 +2693,7 @@ end -- @return #number Expected speed in m/s. function OPSGROUP:GetExpectedSpeed() - if self:IsHolding() then + if self:IsHolding() or self:Is("Rearming") or self:IsWaiting() or self:IsRetreated() then return 0 else return self.speedWp or 0 @@ -2723,6 +2723,12 @@ end function OPSGROUP:RemoveWaypoint(wpindex) if self.waypoints then + + -- The waypoitn to be removed. + local wp=self:GetWaypoint(wpindex) + + -- Is this a temporary waypoint. + local istemp=wp.temp or wp.detour or wp.astar or wp.missionUID -- Number of waypoints before delete. local N=#self.waypoints @@ -2740,7 +2746,6 @@ function OPSGROUP:RemoveWaypoint(wpindex) end -- Remove waypoint marker. - local wp=self:GetWaypoint(wpindex) if wp and wp.marker then wp.marker:Remove() end @@ -2752,7 +2757,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) local n=#self.waypoints -- Debug info. - self:T(self.lid..string.format("Removing waypoint index %d, current wp index %d. N %d-->%d", wpindex, self.currentwp, N, n)) + self:T(self.lid..string.format("Removing waypoint UID=%d [temp=%s]: index=%d [currentwp=%d]. N %d-->%d", wp.uid, tostring(istemp), wpindex, self.currentwp, N, n)) -- Waypoint was not reached yet. if wpindex > self.currentwp then @@ -2764,7 +2769,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- TODO: patrol adinfinitum. Not sure this is handled correctly. If patrol adinfinitum and we have now only one WP left, we should at least go back. -- Could be that the waypoint we are currently moving to was the LAST waypoint. Then we now passed the final waypoint. - if self.currentwp>=n and not self.adinfinitum then + if self.currentwp>=n and not (self.adinfinitum or istemp) then self:_PassedFinalWaypoint(true, "Removed FUTURE waypoint we are currently moving to and that was the LAST waypoint") end @@ -2990,6 +2995,20 @@ function OPSGROUP:PushTask(DCSTask) if self:IsAlive() then + -- Inject enroute tasks. + if self.taskenroute and #self.taskenroute>0 then + if tostring(DCSTask.id)=="ComboTask" then + for _,task in pairs(self.taskenroute) do + table.insert(DCSTask.params.tasks, 1, task) + end + else + local tasks=UTILS.DeepCopy(self.taskenroute) + table.insert(tasks, DCSTask) + + DCSTask=self.group.TaskCombo(self, tasks) + end + end + -- Push task. self.controller:pushTask(DCSTask) @@ -3762,6 +3781,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) if self.currentmission and self.currentmission==Mission.auftragsnummer then self.currentmission=nil end + env.info("Remove mission waypoints") self:_RemoveMissionWaypoints(Mission, false) end @@ -5964,6 +5984,15 @@ function OPSGROUP:onafterStop(From, Event, To) self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") end +--- On after "OutOfAmmo" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterOutOfAmmo(From, Event, To) + self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Cargo Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -8091,32 +8120,54 @@ end -- @param #number delay Delay in seconds. function OPSGROUP:_CheckGroupDone(delay) + -- FSM state. + local fsmstate=self:GetState() + if self:IsAlive() and self.isAI then if delay and delay>0 then + -- Debug info. + self:T(self.lid..string.format("Check OPSGROUP [state=%s] done in %.3f seconds...", fsmstate, delay)) + -- Delayed call. self:ScheduleOnce(delay, self._CheckGroupDone, self) else -- Debug info. - self:T(self.lid.."Check OPSGROUP done?") + self:T(self.lid..string.format("Check OSGROUP [state=%s] done?", fsmstate)) -- Group is engaging something. if self:IsEngaging() then + self:T(self.lid.."Engaging! Group NOT done ==> UpdateRoute()") self:UpdateRoute() return end -- Group is returning if self:IsReturning() then + self:T(self.lid.."Returning! Group NOT done...") return end + + -- Group is returning + if self:IsRearming() then + self:T(self.lid.."Rearming! 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. + self:T(self.lid.."Waiting! Group NOT done...") return end + + -- First check if there is a paused mission that + if self.missionpaused then + self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...", self.missionpaused.name, self.missionpaused.type)) + self:UnpauseMission() + return + end -- Get current waypoint. local waypoint=self:GetWaypoint(self.currentwp) @@ -8226,7 +8277,7 @@ function OPSGROUP:_CheckStuck() local speed=self:GetVelocity() -- Check speed. - if speed<0.5 then + if speed<0.1 then if ExpectedSpeed>0 and not self.stuckTimestamp then self:T2(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected", speed, ExpectedSpeed)) @@ -8824,9 +8875,9 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) end -- Check if final waypoint was reached. - if opsgroup.currentwp==#opsgroup.waypoints and not opsgroup.adinfinitum then + if opsgroup.currentwp==#opsgroup.waypoints and not (opsgroup.adinfinitum or wpistemp) then -- Set passed final waypoint. - opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint currentwp==#waypoints and NOT adinfinitum") + opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint currentwp==#waypoints and NOT adinfinitum and NOT a temporary waypoint") end -- Trigger PassingWaypoint event. @@ -8934,7 +8985,8 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) elseif opsgroup:IsEngaging() then -- Nothing to do really. - + opsgroup:T(opsgroup.lid.."Passing engaging waypoint") + else -- Trigger DetourReached event. @@ -9574,6 +9626,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) Modulation=Modulation or self.radioDefault.Modu if self:IsFlightgroup() and not self.radio.On then + env.info("FF radio OFF") self.group:SetOption(AI.Option.Air.id.SILENCE, false) end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index e3e262d7a..8e40b8a4f 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -48,6 +48,8 @@ -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. +-- @field #number NcarrierDead Total number of dead carrier groups +-- @field #number NcargoDead Totalnumber of dead cargo groups. -- -- @field Ops.Auftrag#AUFTRAG mission The mission attached to this transport. -- @field #table assets Warehouse assets assigned for this transport. @@ -192,6 +194,7 @@ OPSTRANSPORT.version="0.5.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Special transport cohorts/legions. Similar to mission. -- TODO: Stop/abort transport. -- DONE: Allow multiple pickup/depoly zones. -- DONE: Add start conditions. @@ -232,6 +235,8 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self.Ncargo=0 self.Ncarrier=0 self.Ndelivered=0 + self.NcargoDead=0 + self.NcarrierDead=0 -- Set default TZC. self.tzcDefault=self:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) @@ -1648,6 +1653,7 @@ end function OPSTRANSPORT:onafterDeadCarrierGroup(From, Event, To, OpsGroup) self:I(self.lid..string.format("Carrier OPSGROUP %s dead!", OpsGroup:GetName())) -- Remove group from carrier list/table. + self.NdeadCarrier=self.NdeadCarrier+1 self:_DelCarrier(OpsGroup) end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index eb6d472d8..7c32bbb51 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -524,6 +524,63 @@ function UNIT:IsTanker() return tanker, system end +--- Check if the unit can supply ammo. Currently, we have +-- +-- * M 818 +-- * Ural-375 +-- * ZIL-135 +-- +-- This list needs to be extended, if DCS adds other units capable of supplying ammo. +-- +-- @param #UNIT self +-- @return #boolean If `true`, unit can supply ammo. +function UNIT:IsAmmoSupply() + + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename=self:GetTypeName() + + if typename=="M 818" then + -- Blue ammo truck. + return true + elseif typename=="Ural-375" then + -- Red ammo truck. + return true + elseif typename=="ZIL-135" then + -- Red ammo truck. Checked that it can also provide ammo. + return true + end + + return false +end + +--- Check if the unit can supply fuel. Currently, we have +-- +-- * M978 HEMTT Tanker +-- * ATMZ-5 +-- * ATMZ-10 +-- * ATZ-5 +-- +-- This list needs to be extended, if DCS adds other units capable of supplying fuel. +-- +-- @param #UNIT self +-- @return #boolean If `true`, unit can supply fuel. +function UNIT:IsFuelSupply() + + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename=self:GetTypeName() + + if typename=="M978 HEMTT Tanker" then + return true + elseif typename=="ATMZ-5" then + return true + elseif typename=="ATMZ-10" then + return true + elseif typename=="ATZ-5" then + return true + end + + return false +end --- Returns the unit's group if it exist and nil otherwise. -- @param Wrapper.Unit#UNIT self