diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 90a843e17..8143106e1 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -109,6 +109,9 @@ function ARMYGROUP:New(group) -- From State --> Event --> To State self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Cruise along the given route of waypoints. + + self:AddTransition("*", "RTZ", "Returning") -- Group is returning to (home) zone. + self:AddTransition("Returning", "Returned", "Returned") -- Group is returned to (home) zone. self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. @@ -626,7 +629,7 @@ end function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Debug info. - local text=string.format("Update route n=%s, Speed=%s, Formation=%s", tostring(n), tostring(Speed), tostring(Formation)) + local text=string.format("Update route state=%s: n=%s, Speed=%s, Formation=%s", self:GetState(), tostring(n), tostring(Speed), tostring(Formation)) self:T(self.lid..text) -- Update route from this waypoint number onwards. @@ -823,6 +826,60 @@ function ARMYGROUP:onafterRearm(From, Event, To, Coordinate, Formation) end +--- On after "RTZ" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone The zone to return to. +-- @param #number Formation Formation of the group. +function ARMYGROUP:onafterRTZ(From, Event, To, Zone, Formation) + + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid + + -- Zone. + local zone=Zone or self.homezone + + if zone then + + -- Debug info. + self:I(self.lid..string.format("RTZ to Zone %s", zone:GetName())) + + local Coordinate=zone:GetRandomCoordinate() + + -- Add waypoint after current. + local wp=self:AddWaypoint(Coordinate, nil, uid, Formation, true) + + -- Set if we want to resume route after reaching the detour waypoint. + wp.detour=0 + + else + self:E(self.lid.."ERROR: No RTZ zone given!") + end + +end + +--- On after "Returned" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterReturned(From, Event, To) + + -- Debug info. + self:T(self.lid..string.format("Group returned")) + + if self.legion then + -- Debug info. + self:T(self.lid..string.format("Adding group back to warehouse stock")) + + -- Add asset back in 10 seconds. + self.legion:__AddAsset(10, self.group, 1) + end + +end + --- On after "Rearming" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -1048,6 +1105,8 @@ end -- @param #string To To state. function ARMYGROUP:onafterFullStop(From, Event, To) + self:I(self.lid..string.format("Full stop!")) + -- Get current position. local pos=self:GetCoordinate() @@ -1125,7 +1184,8 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Update route. if Updateroute==nil or Updateroute==true then - self:_CheckGroupDone(1) + self:UpdateRoute() + --self:_CheckGroupDone(1) end return waypoint diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 65e65c6de..98d611dcc 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -71,7 +71,7 @@ function BRIGADE:New(WarehouseName, BrigadeName) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("*", "PlatoonOnMission", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "PlatoonAssetReturned", "*") -- An asset returned (from a mission) to the Brigade warehouse. return self end @@ -266,7 +266,23 @@ end -- FSM Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - +--- On after "ArmyOnMission". +-- @param #BRIGADE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission. +-- @param Ops.Auftrag#AUFTRAG Mission The requested mission. +function BRIGADE:onafterArmyOnMission(From, Event, To, ArmyGroup, Mission) + local armygroup=ArmyGroup --Ops.ArmyGroup#ARMYGROUP + local mission=Mission --Ops.Auftrag#AUFTRAG + + -- Debug info. + self:T(self.lid..string.format("Group %s on %s mission %s", armygroup:GetName(), mission:GetType(), mission:GetName())) + + + +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 2d28c1a1b..6651e14a0 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -579,8 +579,8 @@ end function COHORT:onafterStart(From, Event, To) -- Short info. - local text=string.format("Starting COHORT %s", self.name) - self:T(self.lid..text) + local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name) + self:I(self.lid..text) -- Start the status monitoring. self:__Status(-1) @@ -618,15 +618,18 @@ function COHORT:_CheckAssetStatus() text=text..", Flight: " if asset.flightgroup and asset.flightgroup:IsAlive() then local status=asset.flightgroup:GetState() - local fuelmin=asset.flightgroup:GetFuelMin() - local fuellow=asset.flightgroup:IsFuelLow() - local fuelcri=asset.flightgroup:IsFuelCritical() + text=text..string.format("%s", status) - text=text..string.format("%s Fuel=%d", status, fuelmin) - if fuelcri then - text=text.." (Critical!)" - elseif fuellow then - text=text.." (Low)" + if asset.flightgroup:IsFlightgroup() then + local fuelmin=asset.flightgroup:GetFuelMin() + local fuellow=asset.flightgroup:IsFuelLow() + local fuelcri=asset.flightgroup:IsFuelCritical() + text=text..string.format("Fuel=%d", fuelmin) + if fuelcri then + text=text.." (Critical!)" + elseif fuellow then + text=text.." (Low)" + end end local lifept, lifept0=asset.flightgroup:GetLifePoints() @@ -639,8 +642,10 @@ function COHORT:_CheckAssetStatus() end -- Payload info. - local payload=asset.payload and table.concat(self.legion:GetPayloadMissionTypes(asset.payload), ", ") or "None" - text=text..", Payload={"..payload.."}" + if asset.flightgroup:IsFlightgroup() then + local payload=asset.payload and table.concat(self.legion:GetPayloadMissionTypes(asset.payload), ", ") or "None" + text=text..", Payload={"..payload.."}" + end else diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 825801bd3..098aaecc9 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -23,8 +23,10 @@ -- === -- -- # The LEGION Concept --- --- An LEGION consists of multiple COHORTs. These cohorts "live" in a WAREHOUSE, i.e. a physical structure that is connected to an airbase (airdrome, FRAP or ship). +-- +-- The LEGION class contains all functions that are common for the AIRWING, BRIGADE and XXX classes, which inherit the LEGION class. +-- +-- An LEGION consists of multiple COHORTs. These cohorts "live" in a WAREHOUSE, i.e. a physical structure that can be destroyed or captured. -- -- @field #LEGION LEGION = { @@ -79,6 +81,8 @@ function LEGION:New(WarehouseName, LegionName) self:AddTransition("*", "ArmyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). self:AddTransition("*", "NavyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + self:AddTransition("*", "AssetReturned", "*") -- An asset returned (from a mission) to the Legion warehouse. + -- Defaults: -- TODO @@ -598,10 +602,16 @@ end -- @param Ops.OpsGroup#OPSGROUP OpsGroup Ops group on mission -- @param Ops.Auftrag#AUFTRAG Mission The requested mission. function LEGION:onafterOpsOnMission(From, Event, To, OpsGroup, Mission) + -- Debug info. + self:T2(self.lid..string.format("Group %s on %s mission %s", OpsGroup:GetName(), Mission:GetType(), Mission:GetName())) + if self:IsAirwing() then - + -- Trigger event for Airwings. + self:FlightOnMission(OpsGroup, Mission) + elseif self:IsBrigade() then + -- Trigger event for Brigades. + self:ArmyOnMission(OpsGroup, Mission) else - end end @@ -684,7 +694,7 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) else --env.info("FF squad asset returned") - self:SquadAssetReturned(squad, asset) + self:AssetReturned(squad, asset) end @@ -696,11 +706,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.Squadron#SQUADRON Squadron The asset squadron. +-- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to. -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. -function LEGION:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) +function LEGION:onafterAssetReturned(From, Event, To, Cohort, Asset) -- Debug message. - self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) + self:T(self.lid..string.format("Asset %s from Cohort %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Cohort.name, tostring(Asset.assignment))) -- Stop flightgroup. if Asset.flightgroup and not Asset.flightgroup:IsStopped() then @@ -708,15 +718,23 @@ function LEGION:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) end -- Return payload. - self:ReturnPayloadFromAsset(Asset) + if Asset.flightgroup:IsFlightgroup() then + self:ReturnPayloadFromAsset(Asset) + end -- Return tacan channel. if Asset.tacan then - Squadron:ReturnTacan(Asset.tacan) + Cohort:ReturnTacan(Asset.tacan) end -- Set timestamp. Asset.Treturned=timer.getAbsTime() + + if self:IsAirwing() then + self:SquadronAssetReturned(Cohort, Asset) + elseif self:IsBrigade() then + self:PlatoonAssetReturned(Cohort, Asset) + end end @@ -1252,8 +1270,6 @@ end -- @return #table Assets that can do the required mission. function LEGION:CanMission(Mission) - env.info("FF CanMission Classname "..self.ClassName) - -- Assume we CAN and NO assets are available. local Can=true local Assets={} diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index e6922d07a..8f489f430 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -70,6 +70,9 @@ -- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission. -- @field #number Ndestroyed Number of destroyed units. -- @field #number Nkills Number kills of this groups. +-- +-- @field Ops.Legion#LEGION legion Legion the group belongs to. +-- @field Ops.Cohort#COHORT cohort Cohort the group belongs to. -- -- @field Core.Point#COORDINATE coordinate Current coordinate. -- @@ -1329,7 +1332,7 @@ end --- Despawn the group. The whole group is despawned and (optionally) a "Remove Unit" event is generated for all current units of the group. -- @param #OPSGROUP self -- @param #number Delay Delay in seconds before the group will be despawned. Default immediately. --- @param #boolean NoEventRemoveUnit If true, no event "Remove Unit" is generated. +-- @param #boolean NoEventRemoveUnit If `true`, **no** event "Remove Unit" is generated. -- @return #OPSGROUP self function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) @@ -1337,8 +1340,15 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) self.scheduleIDDespawn=self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) else + -- Debug info. self:I(self.lid..string.format("Despawning Group!")) + + if self.legion and not NoEventRemoveUnit then + -- Add asset back in 10 seconds. + self.legion:AddAsset(self.group, 1) + end + -- DCS group obejct. local DCSGroup=self:GetDCSGroup() if DCSGroup then @@ -1819,6 +1829,14 @@ function OPSGROUP:IsRetreating() return is end +--- Check if the group is currently returning to a zone. +-- @param #OPSGROUP self +-- @return #boolean If true, group is returning. +function OPSGROUP:IsReturning() + local is=self:is("Returning") + return is +end + --- Check if the group is engaging another unit or group. -- @param #OPSGROUP self -- @return #boolean If true, group is engaging. @@ -2424,7 +2442,7 @@ function OPSGROUP:OnEventBirth(EventData) -- Get element. local element=self:GetElementByName(unitname) - if element then + if element and element.status~=OPSGROUP.ElementStatus.SPAWNED then -- Set element to spawned state. self:ElementSpawned(element) @@ -2715,7 +2733,7 @@ function OPSGROUP:AddTaskWaypoint(task, Waypoint, description, prio, duration) self:T3({newtask=newtask}) -- Update route. - self:__UpdateRoute(-1) + --self:__UpdateRoute(-1) return newtask end @@ -3269,7 +3287,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) if Task.description=="Engage_Target" then self:Disengage() end - + self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") self:_CheckGroupDone(1) end @@ -3429,7 +3447,7 @@ function OPSGROUP:_GetNextMission() for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - if mission:GetGroupStatus(self)==AUFTRAG.Status.SCHEDULED and (mission:IsReadyToGo() or self.airwing) and (mission.importance==nil or mission.importance<=vip) then + if mission:GetGroupStatus(self)==AUFTRAG.Status.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) then return mission end end @@ -3714,7 +3732,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) self:SwitchROE() end -- ROT to default - if Mission.optionROT then + if self:IsFlightgroup() and Mission.optionROT then self:SwitchROT() end -- Alarm state to default. @@ -3753,9 +3771,14 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) if Mission.icls then self:_SwitchICLS() end + + local delay=1 + if Mission.type==AUFTRAG.Type.ARTY then + delay=10 + end -- Check if group is done. - self:_CheckGroupDone(1) + self:_CheckGroupDone(delay) end @@ -3769,6 +3792,9 @@ function OPSGROUP:RouteToMission(mission, delay) -- Delayed call. self:ScheduleOnce(delay, OPSGROUP.RouteToMission, self, mission) else + + -- Debug info. + self:T(self.lid..string.format("Route To Mission")) if self:IsDead() or self:IsStopped() then return @@ -3907,6 +3933,12 @@ function OPSGROUP:RouteToMission(mission, delay) if mission.icls then self:SwitchICLS(mission.icls.Channel, mission.icls.Morse, mission.icls.UnitName) end + + if self:IsArmygroup() then + self:Cruise(mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed) or self:GetSpeedCruise()) + elseif self:IsNavygroup() then + self:Cruise(mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed) or self:GetSpeedCruise()) + end end end @@ -7165,6 +7197,11 @@ function OPSGROUP:_CheckGroupDone(delay) self:UpdateRoute() return end + + -- Group is returning + if self:IsReturning() then + return + end -- Group is waiting. We deny all updates. if self:IsWaiting() then @@ -7224,10 +7261,19 @@ function OPSGROUP:_CheckGroupDone(delay) -- Passed FINAL waypoint --- - -- No further waypoints. Command a full stop. - self:__FullStop(-1) + if self.legion then + + self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE, LEGION set ==> RTZ")) + self:RTZ(self.legion.spawnzone) + + else - self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE ==> Full Stop")) + -- No further waypoints. Command a full stop. + self:__FullStop(-1) + + self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE ==> Full Stop")) + + end else @@ -7237,7 +7283,6 @@ function OPSGROUP:_CheckGroupDone(delay) if #self.waypoints>0 then self:T(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) - --self:UpdateRoute() self:Cruise() else self:E(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) @@ -7810,6 +7855,10 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Trigger Retreated event. opsgroup:Retreated() + + elseif opsgroup:IsReturning() then + + opsgroup:Returned() elseif opsgroup:IsPickingup() then @@ -8008,23 +8057,27 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchROT(rot) - if self:IsAlive() or self:IsInUtero() then + if self:IsFlightgroup() then - self.option.ROT=rot or self.optionDefault.ROT - - if self:IsInUtero() then - self:T2(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED", self.option.ROT)) + if self:IsAlive() or self:IsInUtero() then + + self.option.ROT=rot or self.optionDefault.ROT + + if self:IsInUtero() then + self:T2(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED", self.option.ROT)) + else + + self.group:OptionROT(self.option.ROT) + + -- Debug info. + self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + end + + else - - self.group:OptionROT(self.option.ROT) - - -- Debug info. - self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + self:E(self.lid.."WARNING: Cannot switch ROT! Group is not alive") end - - - else - self:E(self.lid.."WARNING: Cannot switch ROT! Group is not alive") + end return self @@ -9545,7 +9598,7 @@ function OPSGROUP:_AddElementByName(unitname) end -- Trigger spawned event if alive. - if unit:IsAlive() then + if unit:IsAlive() and element.status~=OPSGROUP.ElementStatus.SPAWNED then -- This needs to be slightly delayed (or moved elsewhere) or the first element will always trigger the group spawned event as it is not known that more elements are in the group. self:__ElementSpawned(0.05, element) end diff --git a/Moose Development/Moose/Ops/Platoon.lua b/Moose Development/Moose/Ops/Platoon.lua index 4753cc80d..3c5e933d4 100644 --- a/Moose Development/Moose/Ops/Platoon.lua +++ b/Moose Development/Moose/Ops/Platoon.lua @@ -18,6 +18,7 @@ -- @type PLATOON -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. +-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType. -- @extends Ops.Cohort#COHORT --- *Some cool cohort quote* -- Known Author @@ -34,6 +35,7 @@ PLATOON = { ClassName = "PLATOON", verbose = 0, + weaponData = {}, } --- PLATOON class version. @@ -87,6 +89,29 @@ function PLATOON:GetBrigade() return self.legion end +--- Add a weapon range for ARTY auftrag. +-- @param #PLATOON self +-- @param #number RangeMin Minimum range in nautical miles. Default 0 NM. +-- @param #number RangeMax Maximum range in nautical miles. Default 10 NM. +-- @param #number BitType Bit mask of weapon type for which the given min/max ranges apply. Default is `ENUMS.WeaponFlag.Auto`, i.e. for all weapon types. +-- @return #PLATOON self +function PLATOON:AddWeaponRange(RangeMin, RangeMax, BitType) + + RangeMin=UTILS.NMToMeters(RangeMin or 0) + RangeMax=UTILS.NMToMeters(RangeMax or 10) + + local weapon={} --Ops.OpsGroup#OPSGROUP.WeaponData + + weapon.BitType=BitType or ENUMS.WeaponFlag.Auto + weapon.RangeMax=RangeMax + weapon.RangeMin=RangeMin + + self.weaponData=self.weaponData or {} + self.weaponData[weapon.BitType]=weapon + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -99,7 +124,7 @@ end function PLATOON:onafterStart(From, Event, To) -- Short info. - local text=string.format("Starting PLATOON %s", self.name) + local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name) self:I(self.lid..text) -- Start the status monitoring.