--- **Ops** - Enhanced Ground Group. -- -- ## Main Features: -- -- * Patrol waypoints *ad infinitum* -- * Easy change of ROE and alarm state, formation and other settings -- * Dynamically add and remove waypoints -- * Sophisticated task queueing system (know when DCS tasks start and end) -- * Convenient checks when the group enters or leaves a zone -- * Detection events for new, known and lost units -- * Simple LASER and IR-pointer setup -- * Compatible with AUFTRAG class -- * Many additional events that the mission designer can hook into -- -- === -- -- ## Example Missions: -- -- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Armygroup). -- -- === -- -- ### Author: **funkyfranky** -- -- == -- @module Ops.ArmyGroup -- @image OPS_ArmyGroup.png --- ARMYGROUP class. -- @type ARMYGROUP -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. -- @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 Core.Set#SET_ZONE retreatZones Set of retreat zones. -- @extends Ops.OpsGroup#OPSGROUP --- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B Sledge -- -- === -- -- # The ARMYGROUP Concept -- -- This class enhances ground groups. -- -- @field #ARMYGROUP ARMYGROUP = { ClassName = "ARMYGROUP", formationPerma = nil, engage = {}, } --- Engage Target. -- @type ARMYGROUP.Target -- @field Ops.Target#TARGET Target The target. -- @field Core.Point#COORDINATE Coordinate Last known coordinate of the target. -- @field Ops.OpsGroup#OPSGROUP.Waypoint Waypoint the waypoint created to go to the target. -- @field #number Speed Speed in knots. -- @field #string Formation Formation used in the engagement. -- @field #number roe ROE backup. -- @field #number alarmstate Alarm state backup. --- Army Group version. -- @field #string version ARMYGROUP.version="0.7.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Suppression of fire. -- TODO: Check if group is mobile. -- TODO: F10 menu. -- DONE: Retreat. -- DONE: Rearm. Specify a point where to go and wait until ammo is full. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new ARMYGROUP class object. -- @param #ARMYGROUP self -- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`. -- @return #ARMYGROUP self function ARMYGROUP:New(group) -- First check if we already have an OPS group for this group. local og=_DATABASE:GetOpsGroup(group) if og then og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) return og end -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #ARMYGROUP -- Set some string id for output to DCS.log file. self.lid=string.format("ARMYGROUP %s | ", self.groupname) -- Defaults self:SetDefaultROE() self:SetDefaultAlarmstate() self:SetDefaultEPLRS(self.isEPLRS) self:SetDefaultEmission() self:SetDetection() self:SetPatrolAdInfinitum(false) self:SetRetreatZones() -- Add FSM transitions. -- 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("Holding", "Returned", "Returned") -- Group is returned to (home) zone, e.g. when unloaded from carrier. 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. 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("*", "Rearm", "Rearm") -- Group is send to a coordinate and waits until ammo is refilled. self:AddTransition("Rearm", "Rearming", "Rearming") -- Group has arrived at the rearming coodinate and is waiting to be fully rearmed. self:AddTransition("Rearming", "Rearmed", "Cruising") -- Group was rearmed. ------------------------ --- Pseudo Functions --- ------------------------ --- Triggers the FSM event "Cruise". -- @function [parent=#ARMYGROUP] Cruise -- @param #ARMYGROUP self -- @param #number Speed Speed in knots until next waypoint is reached. -- @param #number Formation Formation. --- Triggers the FSM event "Cruise" after a delay. -- @function [parent=#ARMYGROUP] __Cruise -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. -- @param #number Speed Speed in knots until next waypoint is reached. -- @param #number Formation Formation. --- On after "Cruise" event. -- @function [parent=#ARMYGROUP] OnAfterCruise -- @param #ARMYGROUP 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. -- @param #number Formation Formation. --- Triggers the FSM event "FullStop". -- @function [parent=#ARMYGROUP] FullStop -- @param #ARMYGROUP self --- Triggers the FSM event "FullStop" after a delay. -- @function [parent=#ARMYGROUP] __FullStop -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "FullStop" event. -- @function [parent=#ARMYGROUP] OnAfterFullStop -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "RTZ". -- @function [parent=#ARMYGROUP] RTZ -- @param #ARMYGROUP self --- Triggers the FSM event "RTZ" after a delay. -- @function [parent=#ARMYGROUP] __RTZ -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "RTZ" event. -- @function [parent=#ARMYGROUP] OnAfterRTZ -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "Returned". -- @function [parent=#ARMYGROUP] Returned -- @param #ARMYGROUP self --- Triggers the FSM event "Returned" after a delay. -- @function [parent=#ARMYGROUP] __Returned -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "Returned" event. -- @function [parent=#ARMYGROUP] OnAfterReturned -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "Detour". -- @function [parent=#ARMYGROUP] Detour -- @param #ARMYGROUP self --- Triggers the FSM event "Detour" after a delay. -- @function [parent=#ARMYGROUP] __Detour -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "Detour" event. -- @function [parent=#ARMYGROUP] OnAfterDetour -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "DetourReached". -- @function [parent=#ARMYGROUP] DetourReached -- @param #ARMYGROUP self --- Triggers the FSM event "DetourReached" after a delay. -- @function [parent=#ARMYGROUP] __DetourReached -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "DetourReached" event. -- @function [parent=#ARMYGROUP] OnAfterDetourReached -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "Retreat". -- @function [parent=#ARMYGROUP] Retreat -- @param #ARMYGROUP self --- Triggers the FSM event "Retreat" after a delay. -- @function [parent=#ARMYGROUP] __Retreat -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "Retreat" event. -- @function [parent=#ARMYGROUP] OnAfterRetreat -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "Retreated". -- @function [parent=#ARMYGROUP] Retreated -- @param #ARMYGROUP self --- Triggers the FSM event "Retreated" after a delay. -- @function [parent=#ARMYGROUP] __Retreated -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "Retreated" event. -- @function [parent=#ARMYGROUP] OnAfterRetreated -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "EngageTarget". -- @function [parent=#ARMYGROUP] EngageTarget -- @param #ARMYGROUP self -- @param Wrapper.Group#GROUP Group the group to be engaged. -- @param #string Formation Formation used in the engagement. --- Triggers the FSM event "EngageTarget" after a delay. -- @function [parent=#ARMYGROUP] __EngageTarget -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. -- @param Wrapper.Group#GROUP Group the group to be engaged. -- @param #string Formation Formation used in the engagement. --- On after "EngageTarget" event. -- @function [parent=#ARMYGROUP] OnAfterEngageTarget -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Group#GROUP Group the group to be engaged. -- @param #string Formation Formation used in the engagement. --- Triggers the FSM event "Disengage". -- @function [parent=#ARMYGROUP] Disengage -- @param #ARMYGROUP self --- Triggers the FSM event "Disengage" after a delay. -- @function [parent=#ARMYGROUP] __Disengage -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "Disengage" event. -- @function [parent=#ARMYGROUP] OnAfterDisengage -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "Rearm". -- @function [parent=#ARMYGROUP] Rearm -- @param #ARMYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate where to rearm. -- @param #number Formation Formation of the group. --- Triggers the FSM event "Rearm" after a delay. -- @function [parent=#ARMYGROUP] __Rearm -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. -- @param Core.Point#COORDINATE Coordinate Coordinate where to rearm. -- @param #number Formation Formation of the group. --- On after "Rearm" event. -- @function [parent=#ARMYGROUP] OnAfterRearm -- @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. --- Triggers the FSM event "Rearming". -- @function [parent=#ARMYGROUP] Rearming -- @param #ARMYGROUP self --- Triggers the FSM event "Rearming" after a delay. -- @function [parent=#ARMYGROUP] __Rearming -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "Rearming" event. -- @function [parent=#ARMYGROUP] OnAfterRearming -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "Rearmed". -- @function [parent=#ARMYGROUP] Rearmed -- @param #ARMYGROUP self --- Triggers the FSM event "Rearmed" after a delay. -- @function [parent=#ARMYGROUP] __Rearmed -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. --- On after "Rearmed" event. -- @function [parent=#ARMYGROUP] OnAfterRearmed -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- TODO: Add pseudo functions. -- Init waypoints. self:_InitWaypoints() -- Initialize the group. self:_InitGroup() -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Dead, self.OnEventDead) self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) --self:HandleEvent(EVENTS.Hit, self.OnEventHit) -- Start the status monitoring. self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) -- Start queue update timer. self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) -- Start check zone timer. self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 30) -- Add OPSGROUP to _DATABASE. _DATABASE:AddOpsGroup(self) return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Group patrols ad inifintum. If the last waypoint is reached, it will go to waypoint one and repeat its route. -- @param #ARMYGROUP self -- @param #boolean switch If true or nil, patrol until the end of time. If false, go along the waypoints once and stop. -- @return #ARMYGROUP self function ARMYGROUP:SetPatrolAdInfinitum(switch) if switch==false then self.adinfinitum=false else self.adinfinitum=true end return self end --- Get coordinate of the closest road. -- @param #ARMYGROUP self -- @return Core.Point#COORDINATE Coordinate of a road closest to the group. function ARMYGROUP:GetClosestRoad() local coord=self:GetCoordinate():GetClosestPointToRoad() return coord end --- Get 2D distance to the closest road. -- @param #ARMYGROUP self -- @return #number Distance in meters to the closest road. function ARMYGROUP:GetClosestRoadDist() local road=self:GetClosestRoad() if road then local dist=road:Get2DDistance(self:GetCoordinate()) return dist end return math.huge end --- Add a *scheduled* task to fire at a given coordinate. -- @param #ARMYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. -- @param #string Clock Time when to start the attack. -- @param #number Radius Radius in meters. Default 100 m. -- @param #number Nshots Number of shots to fire. Default 3. -- @param #number WeaponType Type of weapon. Default auto. -- @param #number Prio Priority of the task. -- @return Ops.OpsGroup#OPSGROUP.Task The task table. function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Clock, Radius, Nshots, WeaponType, Prio) Coordinate=self:_CoordinateFromObject(Coordinate) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) local task=self:AddTask(DCStask, Clock, nil, Prio) return task end --- Add a *scheduled* task to fire at a given coordinate. -- @param #ARMYGROUP self -- @param #string Clock Time when to start the attack. -- @param #number Heading Heading min in Degrees. -- @param #number Alpha Shooting angle in Degrees. -- @param #number Altitude Altitude in meters. -- @param #number Radius Radius in meters. Default 100 m. -- @param #number Nshots Number of shots to fire. Default nil. -- @param #number WeaponType Type of weapon. Default auto. -- @param #number Prio Priority of the task. -- @return Ops.OpsGroup#OPSGROUP.Task The task table. function ARMYGROUP:AddTaskBarrage(Clock, Heading, Alpha, Altitude, Radius, Nshots, WeaponType, Prio) Heading=Heading or 0 Alpha=Alpha or 60 Altitude=Altitude or 100 local distance=Altitude/math.tan(math.rad(Alpha)) local a=self:GetVec2() local vec2=UTILS.Vec2Translate(a, distance, Heading) --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("Fire At Point",ReadOnly,Text) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, vec2, Radius, Nshots, WeaponType, Altitude) local task=self:AddTask(DCStask, Clock, nil, Prio) return task end --- Add a *waypoint* task to fire at a given coordinate. -- @param #ARMYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. -- @param Ops.OpsGroup#OPSGROUP.Waypoint Waypoint Where the task is executed. Default is next waypoint. -- @param #number Radius Radius in meters. Default 100 m. -- @param #number Nshots Number of shots to fire. Default 3. -- @param #number WeaponType Type of weapon. Default auto. -- @param #number Prio Priority of the task. -- @return Ops.OpsGroup#OPSGROUP.Task The task table. function ARMYGROUP:AddTaskWaypointFireAtPoint(Coordinate, Waypoint, Radius, Nshots, WeaponType, Prio) Coordinate=self:_CoordinateFromObject(Coordinate) Waypoint=Waypoint or self:GetWaypointNext() local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) local task=self:AddTaskWaypoint(DCStask, Waypoint, nil, Prio) return task end --- Add a *scheduled* task. -- @param #ARMYGROUP self -- @param Wrapper.Group#GROUP TargetGroup Target group. -- @param #number WeaponExpend How much weapons does are used. -- @param #number WeaponType Type of weapon. Default auto. -- @param #string Clock Time when to start the attack. -- @param #number Prio Priority of the task. -- @return Ops.OpsGroup#OPSGROUP.Task The task table. function ARMYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clock, Prio) local DCStask=CONTROLLABLE.TaskAttackGroup(nil, TargetGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit, GroupAttack) local task=self:AddTask(DCStask, Clock, nil, Prio) return task end --- Add a *scheduled* task to transport group(s). -- @param #ARMYGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of cargo groups. Can also be a singe @{Wrapper.Group#GROUP} object. -- @param Core.Zone#ZONE PickupZone Zone where the cargo is picked up. -- @param Core.Zone#ZONE DeployZone Zone where the cargo is delivered to. -- @param #string Clock Time when to start the attack. -- @param #number Prio Priority of the task. -- @return Ops.OpsGroup#OPSGROUP.Task The task table. function ARMYGROUP:AddTaskCargoGroup(GroupSet, PickupZone, DeployZone, Clock, Prio) local DCStask={} DCStask.id="CargoTransport" DCStask.params={} DCStask.params.cargoqueu=1 local task=self:AddTask(DCStask, Clock, nil, Prio) return task end --- Define a set of possible retreat zones. -- @param #ARMYGROUP self -- @param Core.Set#SET_ZONE RetreatZoneSet The retreat zone set. Default is an empty set. -- @return #ARMYGROUP self function ARMYGROUP:SetRetreatZones(RetreatZoneSet) self.retreatZones=RetreatZoneSet or SET_ZONE:New() return self end --- Add a zone to the retreat zone set. -- @param #ARMYGROUP self -- @param Core.Zone#ZONE_BASE RetreatZone The retreat zone. -- @return #ARMYGROUP self function ARMYGROUP:AddRetreatZone(RetreatZone) self.retreatZones:AddZone(RetreatZone) return self end --- Check if the group is currently holding its positon. -- @param #ARMYGROUP self -- @return #boolean If true, group was ordered to hold. function ARMYGROUP:IsHolding() return self:Is("Holding") end --- Check if the group is currently cruising. -- @param #ARMYGROUP self -- @return #boolean If true, group cruising. function ARMYGROUP:IsCruising() return self:Is("Cruising") end --- Check if the group is currently on a detour. -- @param #ARMYGROUP self -- @return #boolean If true, group is on a detour. function ARMYGROUP:IsOnDetour() return self:Is("OnDetour") end --- Check if the group is ready for combat. I.e. not reaming, retreating, retreated, out of ammo or engaging. -- @param #ARMYGROUP self -- @return #boolean If true, group is on a combat ready. function ARMYGROUP:IsCombatReady() local combatready=true if self:IsRearming() or self:IsRetreating() or self:IsOutOfAmmo() or self:IsEngaging() or self:IsDead() or self:IsStopped() or self:IsInUtero() then combatready=false end if self:IsPickingup() or self:IsLoading() or self:IsTransporting() or self:IsLoaded() or self:IsCargo() or self:IsCarrier() then combatready=false end return combatready end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Update status. -- @param #ARMYGROUP self function ARMYGROUP:Status() -- FSM state. local fsmstate=self:GetState() -- Is group alive? local alive=self:IsAlive() -- Check that group EXISTS and is ACTIVE. if alive then -- Update position etc. self:_UpdatePosition() -- Check if group has detected any units. self:_CheckDetectedUnits() -- Check ammo status. self:_CheckAmmoStatus() -- Check damage of elements and group. self:_CheckDamage() -- Check if group got stuck. self:_CheckStuck() -- Update engagement. if self:IsEngaging() then self:_UpdateEngageTarget() end -- Check if group is waiting. if self:IsWaiting() then if self.Twaiting and self.dTwait then if timer.getAbsTime()>self.Twaiting+self.dTwait then self.Twaiting=nil self.dTwait=nil self:Cruise() end end end else -- Check damage of elements and group. self:_CheckDamage() end -- Check that group EXISTS. if alive~=nil then if self.verbose>=1 then -- Number of elements. local nelem=self:CountElements() local Nelem=#self.elements -- Get number of tasks and missions. local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() -- ROE and Alarm State. local roe=self:GetROE() or -1 local als=self:GetAlarmstate() or -1 -- Waypoint stuff. local wpidxCurr=self.currentwp local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr) or 0 local wpidxNext=self:GetWaypointIndexNext() or 0 local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext) or 0 local wpN=#self.waypoints or 0 local wpF=tostring(self.passedfinalwp) -- Speed. local speed=UTILS.MpsToKnots(self.velocity or 0) local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) -- Altitude. local alt=self.position and self.position.y or 0 -- Heading in degrees. local hdg=self.heading or 0 -- TODO: GetFormation function. local formation=self.option.Formation or "unknown" -- Life points. local life=self.life or 0 -- Total ammo. local ammo=self:GetAmmoTot().Total -- Detected units. local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "Off" -- Get cargo weight. local cargo=0 for _,_element in pairs(self.elements) do local element=_element --Ops.OpsGroup#OPSGROUP.Element cargo=cargo+element.weightCargo end -- Info text. local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f", fsmstate, nelem, Nelem, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, wpN, wpF, life, speed, speedEx, hdg, ammo, ndetected, cargo) self:I(self.lid..text) end else -- Info text. if self.verbose>=1 then local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) self:I(self.lid..text) end end --- -- Elements --- if self.verbose>=2 then local text="Elements:" for i,_element in pairs(self.elements) do local element=_element --Ops.OpsGroup#OPSGROUP.Element local name=element.name local status=element.status local unit=element.unit local life,life0=self:GetLifePoints(element) local life0=element.life0 -- Get ammo. local ammo=self:GetAmmoElement(element) -- Output text for element. text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg", i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, element.weightCargo, element.weightMaxCargo) end if #self.elements==0 then text=text.." none!" end self:T(self.lid..text) end --- -- Engage Detected Targets --- if self:IsCruising() and self.detectionOn and self.engagedetectedOn then local targetgroup, targetdist=self:_GetDetectedTarget() -- If we found a group, we engage it. if targetgroup then self:T(self.lid..string.format("Engaging target group %s at distance %d meters", targetgroup:GetName(), targetdist)) self:EngageTarget(targetgroup) end end --- -- Cargo --- self:_CheckCargoTransport() --- -- Tasks & Missions --- self:_PrintTaskAndMissionStatus() end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- DCS Events ==> See OPSGROUP ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Event function handling when a unit is hit. -- @param #ARMYGROUP self -- @param Core.Event#EVENTDATA EventData Event data. function ARMYGROUP:OnEventHit(EventData) -- Check that this is the right group. if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then local unit=EventData.IniUnit local group=EventData.IniGroup local unitname=EventData.IniUnitName -- TODO: suppression end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after "ElementSpawned" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.OpsGroup#OPSGROUP.Element Element The group element. function ARMYGROUP:onafterElementSpawned(From, Event, To, Element) self:T(self.lid..string.format("Element spawned %s", Element.name)) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.SPAWNED) end --- On after "Spawned" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Group spawned!")) -- Debug info. if self.verbose>=1 then local text=string.format("Initialized Army Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Has EPLRS = %s\n", tostring(self.isEPLRS)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) self:I(self.lid..text) end -- Update position. self:_UpdatePosition() -- Not dead or destroyed yet. self.isDead=false self.isDestroyed=false if self.isAI then -- Set default ROE. self:SwitchROE(self.option.ROE) -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) -- Set emission. self:SwitchEmission(self.option.Emission) -- Set default EPLRS. self:SwitchEPLRS(self.option.EPLRS) -- Set default Invisible. self:SwitchInvisible(self.option.Invisible) -- Set default Immortal. self:SwitchImmortal(self.option.Immortal) -- Set TACAN to default. self:_SwitchTACAN() -- Turn on the radio. if self.radioDefault then self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) else self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, true) end -- Formation if not self.option.Formation then -- Will be set in update route. --self.option.Formation=self.optionDefault.Formation end -- Number of waypoints. local Nwp=#self.waypoints -- Update route. if Nwp>1 and self.isMobile then self:T(self.lid..string.format("Got %d waypoints on spawn ==> Cruise in -1.0 sec!", Nwp)) self:__Cruise(-1, nil, self.option.Formation) else self:T(self.lid.."No waypoints on spawn ==> Full Stop!") self:FullStop() end end end --- On before "UpdateRoute" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. -- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. -- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Formation Formation of the group. function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Is transition allowed? We assume yes until proven otherwise. local allowed=true local trepeat=nil if self:IsWaiting() then self:T(self.lid.."Update route denied. Group is WAITING!") return false elseif self:IsInUtero() then self:T(self.lid.."Update route denied. Group is INUTERO!") return false elseif self:IsDead() then self:T(self.lid.."Update route denied. Group is DEAD!") return false elseif self:IsStopped() then self:T(self.lid.."Update route denied. Group is STOPPED!") return false elseif self:IsHolding() then self:T(self.lid.."Update route denied. Group is holding position!") return false elseif self:IsEngaging() then self:T(self.lid.."Update route allowed. Group is engaging!") return true end -- Check for a current task. if self.taskcurrent>0 then -- Get the current task. Must not be executing already. local task=self:GetTaskByID(self.taskcurrent) if task then if task.dcstask.id=="PatrolZone" then -- For patrol zone, we need to allow the update as we insert new waypoints. self:T2(self.lid.."Allowing update route for Task: PatrolZone") elseif task.dcstask.id=="ReconMission" then -- For recon missions, we need to allow the update as we insert new waypoints. self:T2(self.lid.."Allowing update route for Task: ReconMission") elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then -- For relocate self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") else local taskname=task and task.description or "No description" self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s", self.taskcurrent, tostring(taskname))) allowed=false end else -- Now this can happen, if we directly use TaskExecute as the task is not in the task queue and cannot be removed. Therefore, also directly executed tasks should be added to the queue! self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!", self.taskcurrent)) -- Anyhow, a task is running so we do not allow to update the route! allowed=false end end -- Not good, because mission will never start. Better only check if there is a current task! --if self.currentmission then --end -- Only AI flights. if not self.isAI then allowed=false end -- Debug info. self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)", self:GetState(), tostring(allowed), tostring(trepeat))) -- Try again? if trepeat then self:__UpdateRoute(trepeat, n) end return allowed end --- On after "UpdateRoute" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. -- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. -- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Formation Formation of the group. function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Update route from this waypoint number onwards. n=n or self:GetWaypointIndexNext(self.adinfinitum) -- Max index. N=N or #self.waypoints N=math.min(N, #self.waypoints) -- Debug info. local text=string.format("Update route state=%s: n=%s, N=%s, Speed=%s, Formation=%s", self:GetState(), tostring(n), tostring(N), tostring(Speed), tostring(Formation)) self:T(self.lid..text) -- Waypoints including addtional wp onroad. local waypoints={} -- Next waypoint. local wp=self.waypoints[n] --Ops.OpsGroup#OPSGROUP.Waypoint -- Formation at the current position. local formation0=wp.action if formation0==ENUMS.Formation.Vehicle.OnRoad then if wp.roadcoord then if wp.roaddist>10 then formation0=ENUMS.Formation.Vehicle.OffRoad end else formation0=ENUMS.Formation.Vehicle.OffRoad end end -- Current point. local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speedWp), formation0) --ENUMS.Formation.Vehicle.OffRoad) table.insert(waypoints, 1, current) -- Loop over waypoints. for j=n, N do -- Index of previous waypoint. local i=j-1 -- If we go to the first waypoint j=1 ==> i=0, so we take the last waypoint passed. E.g. when adinfinitum and passed final waypoint. if i==0 then i=self.currentwp end -- Next waypoint. local wp=UTILS.DeepCopy(self.waypoints[j]) --Ops.OpsGroup#OPSGROUP.Waypoint -- Previous waypoint. Index is i and not i-1 because we added the current position. local wp0=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint --local text=string.format("FF Update: i=%d, wp[i]=%s, wp[i-1]=%s", i, wp.action, wp0.action) --env.info(text) -- Speed. if Speed then wp.speed=UTILS.KnotsToMps(tonumber(Speed)) else -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. if wp.speed<0.1 then wp.speed=UTILS.KmphToMps(self.speedCruise) end end -- Formation. if self.formationPerma then wp.action=self.formationPerma elseif Formation then wp.action=Formation end -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp0.roaddist>=0 then --env.info("FF adding waypoint0 on road #"..i) -- Add "On Road" waypoint in between. local wproad=wp0.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint -- Insert road waypoint. table.insert(waypoints, wproad) end -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>=0 then --env.info("FF adding waypoint on road #"..i) -- The real waypoint is actually off road. wp.action=ENUMS.Formation.Vehicle.OffRoad -- Add "On Road" waypoint in between. local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint -- Insert road waypoint. table.insert(waypoints, wproad) end -- Add waypoint. table.insert(waypoints, wp) end -- First (next wp). local wp=waypoints[1] --Ops.OpsGroup#OPSGROUP.Waypoint -- Current set formation. self.option.Formation=wp.action -- Current set speed in m/s. self.speedWp=wp.speed -- Debug output. if self.verbose>=10 or true then for i,_wp in pairs(waypoints) do local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint local text=string.format("WP #%d UID=%d Formation=%s: Speed=%d m/s, Alt=%d m, Type=%s", i, wp.uid and wp.uid or -1, wp.action, wp.speed, wp.alt, wp.type) local coord=COORDINATE:NewFromWaypoint(wp):MarkToAll(text) self:I(text) end end if self:IsEngaging() or not self.passedfinalwp then -- Debug info. self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Formation=%s", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), tostring(self.option.Formation))) -- Route group to all defined waypoints remaining. self:Route(waypoints) else --- -- Passed final WP ==> Full Stop --- self:T(self.lid..string.format("WARNING: Passed final WP when UpdateRoute() ==> Full Stop!")) self:FullStop() end end --- On after "GotoWaypoint" event. Group will got to the given waypoint and execute its route from there. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number UID The goto waypoint unique ID. -- @param #number Speed (Optional) Speed to waypoint in knots. -- @param #number Formation (Optional) Formation to waypoint. function ARMYGROUP:onafterGotoWaypoint(From, Event, To, UID, Speed, Formation) local n=self:GetWaypointIndex(UID) if n then -- Speed to waypoint. Speed=Speed or self:GetSpeedToWaypoint(n) -- Update the route. self:__UpdateRoute(-0.01, n, nil, Speed, Formation) end end --- On after "Detour" 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 go. -- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Formation Formation of the group. -- @param #number ResumeRoute If true, resume route after detour point was reached. If false, the group will stop at the detour point and wait for futher commands. function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation, ResumeRoute) for _,_wp in pairs(self.waypoints) do local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint if wp.detour then self:RemoveWaypointByID(wp.uid) end end -- Speed in knots. Speed=Speed or self:GetSpeedCruise() -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid -- Add waypoint after current. local wp=self:AddWaypoint(Coordinate, Speed, uid, Formation, true) -- Set if we want to resume route after reaching the detour waypoint. if ResumeRoute then wp.detour=1 else wp.detour=0 end end --- On after "OutOfAmmo" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterOutOfAmmo(From, Event, To) self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) -- Fist, check if we want to rearm once out-of-ammo. --TODO: IsMobile() check if self.rearmOnOutOfAmmo then local truck, dist=self:FindNearestAmmoSupply(30) if truck then self:T(self.lid..string.format("Found Ammo Truck %s [%s]", truck:GetName(), truck:GetTypeName())) local Coordinate=truck:GetCoordinate() self:__Rearm(-1, Coordinate) return end end -- Second, check if we want to retreat once out of ammo. if self.retreatOnOutOfAmmo then self:__Retreat(-1) return end -- Third, check if we want to RTZ once out of ammo. if self.rtzOnOutOfAmmo then self:__RTZ(-1) end -- Get current task. local task=self:GetTaskCurrent() if task then if task.dcstask.id=="FireAtPoint" or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then self:T(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id)) self:TaskCancel(task) end end 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:IsOnMission() 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. -- @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:onafterRearm(From, Event, To, Coordinate, Formation) -- Debug info. self:T(self.lid..string.format("Group send to rearm")) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid -- 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 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:T(self.lid.."Group rearmed") -- Check group done. self:_CheckGroupDone(1) end --- On before "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:onbeforeRTZ(From, Event, To, Zone, Formation) -- Zone. local zone=Zone or self.homezone if zone then if (not self.isMobile) and (not self:IsInZone(zone)) then self:Teleport(zone:GetCoordinate(), 0, true) self:__RTZ(-1, Zone, Formation) return false end else return false end return true 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) -- Zone. local zone=Zone or self.homezone -- Cancel all missions in the queue. self:CancelAllMissions() if zone then if self:IsInZone(zone) then self:Returned() else -- Debug info. self:T(self.lid..string.format("RTZ to Zone %s", zone:GetName())) local Coordinate=zone:GetRandomCoordinate() -- ID of current waypoint. local uid=self:GetWaypointCurrentUID() -- 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 end else self:T(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. -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterRearming(From, Event, To) -- Get current position. local pos=self:GetCoordinate() -- Create a new waypoint. local wp=pos:WaypointGround(0) -- Create new route consisting of only this position ==> Stop! self:Route({wp}) end --- On before "Retreat" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Core.Zone#ZONE_BASE Zone (Optional) Zone where to retreat. Default is the closest retreat zone. -- @param #number Formation (Optional) Formation of the group. function ARMYGROUP:onbeforeRetreat(From, Event, To, Zone, Formation) if not Zone then local a=self:GetVec2() local distmin=math.huge local zonemin=nil for _,_zone in pairs(self.retreatZones:GetSet()) do local zone=_zone --Core.Zone#ZONE_BASE local b=zone:GetVec2() local dist=UTILS.VecDist2D(a, b) if dist Stop! self:Route({wp}) end --- On after "EngageTarget" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Group#GROUP Group the group to be engaged. -- @param #number Speed Speed in knots. -- @param #string Formation Formation used in the engagement. Default `ENUMS.Formation.Vehicle.Vee`. function ARMYGROUP:onbeforeEngageTarget(From, Event, To, Target, Speed, Formation) local dt=nil local allowed=true local ammo=self:GetAmmoTot() if ammo.Total==0 then self:T(self.lid.."WARNING: Cannot engage TARGET because no ammo left!") return false end -- Pause current mission. local mission=self:GetMissionCurrent() if mission and mission.type~=AUFTRAG.Type.GROUNDATTACK then self:T(self.lid.."Engage command but have current mission ==> Pausing mission!") self:PauseMission() dt=-0.1 allowed=false end -- 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. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Group#GROUP Group the group to be engaged. -- @param #number Speed Attack speed in knots. -- @param #string Formation Formation used in the engagement. Default `ENUMS.Formation.Vehicle.Vee`. function ARMYGROUP:onafterEngageTarget(From, Event, To, Target, Speed, Formation) self:T(self.lid.."Engaging Target") -- Make sure this is a target. if Target:IsInstanceOf("TARGET") then self.engage.Target=Target else self.engage.Target=TARGET:New(Target) end -- Target coordinate. self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) -- Get a coordinate close to the target. local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) -- Backup ROE and alarm state. self.engage.roe=self:GetROE() self.engage.alarmstate=self:GetAlarmstate() -- Switch ROE and alarm state. self:SwitchAlarmstate(ENUMS.AlarmState.Auto) self:SwitchROE(ENUMS.ROE.OpenFire) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid -- Set formation. self.engage.Formation=Formation or ENUMS.Formation.Vehicle.Vee -- Set speed. self.engage.Speed=Speed -- Add waypoint after current. self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Formation, true) -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=1 end --- Update engage target. -- @param #ARMYGROUP self function ARMYGROUP:_UpdateEngageTarget() if self.engage.Target and self.engage.Target:IsAlive() then -- Get current position vector. local vec3=self.engage.Target:GetVec3() if vec3 then -- Distance to last known position of target. local dist=UTILS.VecDist3D(vec3, self.engage.Coordinate:GetVec3()) -- Check if target moved more than 100 meters. if dist>100 then --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName()) -- Update new position. self.engage.Coordinate:UpdateFromVec3(vec3) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid -- 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(intercoord, self.engage.Speed, uid, self.engage.Formation, true) -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=0 end else -- Could not get position of target (not alive any more?) ==> Disengage. self:Disengage() end else -- Target not alive any more ==> Disengage. self:Disengage() end end --- On after "Disengage" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterDisengage(From, Event, To) self:T(self.lid.."Disengage Target") -- Restore previous ROE and alarm state. self:SwitchROE(self.engage.roe) self:SwitchAlarmstate(self.engage.alarmstate) -- Get current task local task=self:GetTaskCurrent() -- Get if current task is ground attack. if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!") self:TaskDone(task) end -- Remove current waypoint if self.engage.Waypoint then self:RemoveWaypointByID(self.engage.Waypoint.uid) end -- Check group is done self:_CheckGroupDone(1) end --- On after "DetourReached" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterDetourReached(From, Event, To) self:T(self.lid.."Group reached detour coordinate") end --- On after "FullStop" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterFullStop(From, Event, To) -- Debug info. self:T(self.lid..string.format("Full stop!")) -- Get current position. local pos=self:GetCoordinate() -- Create a new waypoint. local wp=pos:WaypointGround(0) -- Create new route consisting of only this position ==> Stop! self:Route({wp}) end --- On after "Cruise" event. -- @param #ARMYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number Speed Speed in knots. -- @param #number Formation Formation. function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) -- Not waiting anymore. self.Twaiting=nil self.dTwait=nil -- Debug info. self:T(self.lid.."Cruise ==> Update route in 0.01 sec") -- Update route. self:__UpdateRoute(-0.01, nil, nil, Speed, Formation) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Add an a waypoint to the route. -- @param #ARMYGROUP self -- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. -- @param #number Speed Speed in knots. Default is default cruise speed or 70% of max speed. -- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. -- @param #string Formation Formation the group will use. -- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) -- Debug info. self:T(self.lid..string.format("AddWaypoint Formation = %s",tostring(Formation) or "none")) -- Create coordinate. local coordinate=self:_CoordinateFromObject(Coordinate) -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) -- Speed in knots. Speed=Speed or self:GetSpeedCruise() -- Formation. if not Formation then if self.formationPerma then Formation = self.formationPerma elseif self.option.Formation then Formation = self.option.Formation else Formation = "On Road" end end -- Create a Ground waypoint. local wp=coordinate:WaypointGround(UTILS.KnotsToKmph(Speed), Formation) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) -- Add waypoint to table. self:_AddWaypoint(waypoint, wpnumber) -- Get closest point to road. waypoint.roadcoord=coordinate:GetClosestPointToRoad(false) if waypoint.roadcoord then waypoint.roaddist=coordinate:Get2DDistance(waypoint.roadcoord) else waypoint.roaddist=1000*1000 --1000 km. end -- Debug info. self:T(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s", waypoint.uid, wpnumber, Speed, waypoint.roaddist, waypoint.action)) -- Update route. if Updateroute==nil or Updateroute==true then self:__UpdateRoute(-0.01) end return waypoint end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. -- @param #ARMYGROUP self -- @param #table Template Template used to init the group. Default is `self.template`. -- @return #ARMYGROUP self function ARMYGROUP:_InitGroup(Template) -- First check if group was already initialized. if self.groupinitialized then self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end -- Get template of group. local template=Template or self:_GetTemplate() -- Ground are always AI. self.isAI=true -- Is (template) group late activated. self.isLateActivated=template.lateActivation -- Ground groups cannot be uncontrolled. self.isUncontrolled=false -- Max speed in km/h. self.speedMax=self.group:GetSpeedMax() -- Is group mobile? if self.speedMax>3.6 then self.isMobile=true else self.isMobile=false end -- Cruise speed in km/h self.speedCruise=self.speedMax*0.7 -- Group ammo. self.ammo=self:GetAmmoTot() -- Radio parameters from template. self.radio.On=false -- Radio is always OFF for ground. self.radio.Freq=133 self.radio.Modu=radio.modulation.AM -- Set default radio. self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) -- Set default formation from first waypoint. self.optionDefault.Formation=template.route.points[1].action --self:GetWaypoint(1).action -- Default TACAN off. self:SetDefaultTACAN(nil, nil, nil, nil, true) self.tacan=UTILS.DeepCopy(self.tacanDefault) -- Units of the group. local units=self.group:GetUnits() -- DCS group. local dcsgroup=Group.getByName(self.groupname) local size0=dcsgroup:getInitialSize() -- Quick check. if #units~=size0 then self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0)) end -- Add elemets. for _,unit in pairs(units) do local unitname=unit:GetName() self:_AddElementByName(unitname) end -- Init done. self.groupinitialized=true return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Option Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Switch to a specific formation. -- @param #ARMYGROUP self -- @param #number Formation New formation the group will fly in. Default is the setting of `SetDefaultFormation()`. -- @param #boolean Permanently If true, formation always used from now on. -- @param #boolean NoRouteUpdate If true, route is not updated. -- @return #ARMYGROUP self function ARMYGROUP:SwitchFormation(Formation, Permanently, NoRouteUpdate) if self:IsAlive() or self:IsInUtero() then Formation=Formation or (self.optionDefault.Formation or "Off road") Permanently = Permanently or false if Permanently then self.formationPerma=Formation else self.formationPerma=nil end -- Set current formation. self.option.Formation=Formation or "Off road" if self:IsInUtero() then self:T(self.lid..string.format("Will switch formation to %s (permanently=%s) when group is spawned", tostring(self.option.Formation), tostring(Permanently))) else -- Update route with the new formation. if NoRouteUpdate then else self:__UpdateRoute(-1, nil, nil, Formation) end -- Debug info. self:T(self.lid..string.format("Switching formation to %s (permanently=%s)", tostring(self.option.Formation), tostring(Permanently))) end end return self 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. -- @return #number Distance to closest group in meters. function ARMYGROUP:FindNearestAmmoSupply(Radius) -- Radius in meters. Radius=UTILS.NMToMeters(Radius or 30) -- Current positon. local coord=self:GetCoordinate() -- Get my coalition. local myCoalition=self:GetCoalition() -- 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:IsAlive() and unit:GetCoalition()==myCoalition and unit:IsAmmoSupply() and unit:GetVelocityKMH()<1 then -- Distance. local d=coord:Get2DDistance(unit:GetCoord()) -- Check if distance is smaller. if d