From 8ad20b57fda51d9b52f552810c681f908a4a4e9e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 19 May 2022 14:36:54 +0200 Subject: [PATCH 1/7] TARGET - small typo --- Moose Development/Moose/Ops/Awacs.lua | 462 +++++++++++++++++++++---- Moose Development/Moose/Ops/Target.lua | 2 +- 2 files changed, 405 insertions(+), 59 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 05e019653..6605d804b 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -117,6 +117,7 @@ do -- @field #string AOName -- name of the FEZ, e.g. Rock -- @field Core.Point#COORDINATE AOCoordinate -- Coordinate of bulls eye -- @field Utilities.FiFo#FIFO clientmenus +-- @field #number RadarBlur -- Radar blur in % -- @extends Core.Fsm#FSM @@ -132,7 +133,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "alpha 0.0.19", -- #string + version = "alpha 0.0.20", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -190,6 +191,7 @@ AWACS = { CatchAllMissions = {}, CatchAllFGs = {}, PictureInterval = 300, + ReassignTime = 120, PictureTimeStamp = 0, BorderZone = nil, RejectZone = nil, @@ -203,6 +205,7 @@ AWACS = { AOName = "Rock", AOCoordinate = nil, clientmenus = nil, + RadarBlur = 15, } --- @@ -237,7 +240,7 @@ AWACS.IFF = SPADES = "Spades", NEUTRAL = "Neutral", FRIENDLY = "Friendly", - ENEMY = "Enemy", + ENEMY = "Hostile", BOGEY = "Bogey", } @@ -295,9 +298,9 @@ AWACS.ROE = { --- -- @field AWACS.ROT AWACS.ROT = { + BYPASSESCAPE = "Bypass and Escpae", + EVADE = "Evade Fire", PASSIVE = "Passive Defense", - ACTIVE = "Active Defense", - LOCK = "Lock", RETURNFIRE = "Return Fire", OPENFIRE = "Open Fire", } @@ -354,6 +357,10 @@ AWACS.CapVoices = { -- @field Core.Menu#MENU_GROUP_COMMAND unable -- @field Core.Menu#MENU_GROUP_COMMAND abort -- @field Core.Menu#MENU_GROUP_COMMAND commit +-- @field Core.Menu#MENU_GROUP vid +-- @field Core.Menu#MENU_GROUP_COMMAND neutral +-- @field Core.Menu#MENU_GROUP_COMMAND hostile +-- @field Core.Menu#MENU_GROUP_COMMAND friendly --- Group Data -- @type AWACS.ManagedGroup @@ -371,6 +378,7 @@ AWACS.CapVoices = { -- @field #number AnchorStackAngels -- @field #number ContactCID -- @field Core.Point#COORDINATE LastKnownPosition +-- @field #number LastTasking TimeStamp --- Contact Data -- @type AWACS.ManagedContact @@ -457,11 +465,9 @@ AWACS.TaskStatus = { --@field #boolean FromAI ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO-List 0.0.19 +-- TODO-List 0.0.20 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- --- TODO - System for Players to VID contacts? --- TODO - Task reassignment - if a player reject a task, don't choose him again for x minutes -- DEBUG - WIP - Player tasking -- TODO - Maybe check in AI only when airborne -- DONE - remove SSML tag when not on google (currently sometimes spoken) @@ -469,13 +475,15 @@ AWACS.TaskStatus = { -- TODO - (LOW) LotATC / IFF -- TODO - SW Optimizer -- TODO - Assign specific number of AI CAP to a station +-- DEBUG - Multiple AIRWING connection? Can't really get recruit to work, switched to random round robin -- +-- DONE - System for Players to VID contacts? +-- DONE - Task reassignment - if a player reject a task, don't choose him again for 3 minutes -- DONE - added SSML tags to make google readouts nicer -- DONE - 2nd audio queue for priority messages -- DONE - (WIP) Missile launch callout -- DONE - Event detection, Player joining, eject, crash, dead, leaving; AI shot -> DEFEND -- DONE - AI Tasking --- DEBUG - Multiple AIRWING connection? Can't really get recruit to work, switched to random round robin -- DONE - Shift Change, Change on asset RTB or dead or mission done (done for AWACS and Escorts) -- DONE - TripWire - WIP - Threat (35nm), Meld (45nm, on mission), Merged (<3nm) -- @@ -587,6 +595,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self.PictureInterval = 300 -- picture every 5s mins self.PictureTimeStamp = 0 -- timestamp + self.ReassignTime = 120 -- time for player re-assignment self.intelstarted = false self.sunrisedone = false @@ -969,6 +978,61 @@ function AWACS:_MissileWarning(Coordinate,Type,Warndist) return self end +--- [User] Set AWACS Radar Blur - the radar contact count per group/cluster will be distored up or down by this number percent. Defaults to 15 in Modern Era and 25 in Cold War. +-- @param #AWACS self +-- @param #number Percent +-- @return #AWACS self +function AWACS:SetRadarBlue(Percent) + local percent = Percent or 15 + if percent < 0 then percent = 0 end + if percent > 100 then percent = 100 end + self.RadarBlur = Percent + return self +end + +--- [User] Set AWACS to Cold War standards - ROE to VID, ROT to Passive (bypass and escape) +-- @param #AWACS self +-- @return #AWACS self +function AWACS:SetColdWar() + self.ModernEra = false + self.AwacsROT = AWACS.ROT.PASSIVE + self.AwacsROE = AWACS.ROE.VID + self.RadarBlur = 25 + return self +end + +--- [User] Set AWACS to Modern Era standards - ROE to IFF, ROT to Active (evade fire) +-- @param #AWACS self +-- @return #AWACS self +function AWACS:SetModernEra() + self.ModernEra = true + self.AwacsROT = AWACS.ROT.EVADE + self.AwacsROE = AWACS.ROE.IFF + self.RadarBlur = 15 + return self +end + +--- [User] Set AWACS to Policing standards - ROE to VID, ROT to Lock (bypass and escape) +-- @param #AWACS self +-- @return #AWACS self +function AWACS:SetPolicingModern() + self.ModernEra = true + self.AwacsROT = AWACS.ROT.BYPASSESCAPE + self.AwacsROE = AWACS.ROE.VID + self.RadarBlur = 15 + return self +end + +--- [User] Set AWACS to Policing standards - ROE to VID, ROT to Lock (bypass and escape) +-- @param #AWACS self +-- @return #AWACS self +function AWACS:SetPolicingColdWar() + self.ModernEra = false + self.AwacsROT = AWACS.ROT.BYPASSESCAPE + self.AwacsROE = AWACS.ROE.VID + return self +end + --- [User] Get AWACS Name -- @param #AWACS self -- @return #string Name of this instance @@ -1487,24 +1551,28 @@ function AWACS:_GetIdlePilots() for _name,_entry in pairs (self.ManagedGrps) do local entry = _entry -- #AWACS.ManagedGroup - self:I("Looking at entry "..entry.GID.." Name "..entry.GroupName) + self:T("Looking at entry "..entry.GID.." Name "..entry.GroupName) local managedtask = self:_ReadAssignedTaskFromGID(entry.GID) -- #AWACS.ManagedTask local overridetask = false if managedtask then - self:I("Current task = "..(managedtask.ToDo or "Unknown")) + self:T("Current task = "..(managedtask.ToDo or "Unknown")) if managedtask.ToDo == AWACS.TaskDescription.ANCHOR then overridetask = true end end if entry.IsAI then if entry.FlightGroup:IsAirborne() and ((not entry.HasAssignedTask) or overridetask) then -- must be idle, or? - self:I("Adding AI with Callsign: "..entry.CallSign) + self:T("Adding AI with Callsign: "..entry.CallSign) AIPilots[#AIPilots+1] = _entry end elseif entry.IsPlayer and not entry.Blocked then if (not entry.HasAssignedTask) or overridetask then -- must be idle, or? - self:I("Adding Human with Callsign: "..entry.CallSign) - HumanPilots[#HumanPilots+1] = _entry + -- check last assignment + local TNow = timer.getTime() + if entry.LastTasking and (TNow-entry.LastTasking > self.ReassignTime) then + self:T("Adding Human with Callsign: "..entry.CallSign) + HumanPilots[#HumanPilots+1] = _entry + end end end end @@ -1538,7 +1606,14 @@ function AWACS:_TargetSelectionProcess(Untargeted) function (Contact) local contact = Contact -- #AWACS.ManagedContact if contact.Contact.group:IsAlive() and (contact.Status == AWACS.TaskStatus.IDLE or contact.Status == AWACS.TaskStatus.UNASSIGNED) then - prefiltered:Push(contact,contact.CID) + if self.AwacsROE == AWACS.ROE.POLICE or self.AwacsROE == AWACS.ROE.VID then + -- filter out VID'd non-hostiles + if not (contact.IFF == AWACS.IFF.FRIENDLY or contact.IFF == AWACS.IFF.NEUTRAL) then + prefiltered:Push(contact,contact.CID) + end + else + prefiltered:Push(contact,contact.CID) + end end end ) @@ -1717,7 +1792,7 @@ function AWACS:_CreatePicture(AO,Callsign,GID,MaxEntries,IsGeneral) refBRAATTS = self:ToStringBULLS(coordinate, true) else --refBRAATTS = self:ToStringBullsTTS(refBRAA) - refBRAATTS = self:ToStringBULLS(refBRAA,false,true) + refBRAATTS = self:ToStringBULLS(coordinate,false,true) end local alt = contact.Contact.group:GetAltitude() or 8000 alt = UTILS.Round(UTILS.MetersToFeet(alt)/1000,0) @@ -1733,6 +1808,10 @@ function AWACS:_CreatePicture(AO,Callsign,GID,MaxEntries,IsGeneral) if self.PathToGoogleKey then refBRAATTS = coordinate:ToStringBRAANATO(groupcoord,true,true,true) end + if contact.IFF ~= AWACS.IFF.BOGEY then + refBRAA = string.gsub(refBRAA,"Bogey", contact.IFF) + refBRAATTS = string.gsub(refBRAATTS,"Bogey", contact.IFF) + end text = text .. " "..refBRAATTS textScreen = textScreen .." "..refBRAA end @@ -1909,7 +1988,7 @@ function AWACS:_Picture(Group,IsGeneral) else if clustersAO > 0 then - if IsGeneral then + if general then text = string.format("%s, %s. ",gcallsign, self.callsigntxt) textScreen = string.format("%s, %s. ",gcallsign, self.callsigntxt) else @@ -1923,7 +2002,7 @@ function AWACS:_Picture(Group,IsGeneral) text = text .. clustersAO .. " groups. " textScreen = textScreen .. clustersAO .. " groups.\n" end - self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) + self:_NewRadioEntry(text,textScreen,GID,Outcome,true,true,false) self:_CreatePicture(true,gcallsign,GID,3,general) @@ -2049,6 +2128,74 @@ function AWACS:_ShowAwacsInfo(Group) return self end +--- [Internal] AWACS Menu for VID +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group to use +-- @param #string Declaration Text declaration the player used +-- @return #AWACS self +function AWACS:_VID(Group,Declaration) + self:I(self.lid.."_VID") + + local GID, Outcome, Callsign = self:_GetManagedGrpID(Group) + local text = "" + local TextTTS = "" + + if Outcome then + --yes, known + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + local group = managedgroup.Group + local position = group:GetCoordinate() + local radius = UTILS.NMToMeters(self.DeclareRadius) or UTILS.NMToMeters(5) + + -- find tasked contact + local TID = managedgroup.CurrentTask or 0 + if TID > 0 then + local task = self.ManagedTasks:ReadByID(TID) -- #AWACS.ManagedTask + -- correct task? + if task.ToDo ~= AWACS.TaskDescription.VID then + return self + end + -- already done? + if task.Status ~= AWACS.TaskStatus.ASSIGNED then + return self + end + local CID = task.Cluster.CID + local cluster = self.Contacts:ReadByID(CID) -- #AWACS.ManagedContact + if cluster then + local gposition = cluster.Contact.group:GetCoordinate() + local cposition = gposition or cluster.Cluster.coordinate or cluster.Contact.position + local distance = cposition:Get2DDistance(position) + distance = UTILS.Round(distance,0) + 1 + if distance <= radius or self.debug then + -- we can VID + self:I("Contact VID as "..Declaration) + -- update + cluster.IFF = Declaration + task.Status = AWACS.TaskStatus.SUCCESS + self.ManagedTasks:PullByID(TID) + self.ManagedTasks:Push(task,TID) + self.Contacts:PullByID(CID) + self.Contacts:Push(cluster,CID) + text = string.format("%s. %s. Copy, target identified as %s.",Callsign,self.callsigntxt, Declaration) + self:I(text) + else + -- too far away + self:I("Contact VID not close enough") + text = string.format("%s. %s. Negative, get closer to target.",Callsign,self.callsigntxt) + self:I(text) + end + self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) + end + end + -- + elseif self.AwacsFG then + -- no, unknown + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) + self:_NewRadioEntry(text,text,GID,Outcome,true,true,false) + end + return self +end + --- [Internal] AWACS Menu for Declare -- @param #AWACS self -- @param Wrapper.Group#GROUP Group Group to use @@ -2057,7 +2204,7 @@ function AWACS:_Declare(Group) self:T(self.lid.."_Declare") local GID, Outcome, Callsign = self:_GetManagedGrpID(Group) - local text = "Declare Not yet implemented" + local text = "" local TextTTS = "" if Outcome then @@ -2240,6 +2387,7 @@ function AWACS:_Unable(Group) -- unlink group from task Pilot.HasAssignedTask = false Pilot.CurrentTask = 0 + Pilot.LastTasking = timer.getTime() self.ManagedGrps[GID] = Pilot text = string.format("%s. %s. Copy.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) local EngagementTag = "" @@ -2276,7 +2424,7 @@ function AWACS:_TaskAbort(Group) -- got a task, status? self:T(string.format("ABORT for TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status)) if managedtask.Status == AWACS.TaskStatus.ASSIGNED then - -- ok let's commit this one + -- ok let's un-commit this one managedtask = self.ManagedTasks:PullByID(currtaskid) managedtask.Status = AWACS.TaskStatus.UNASSIGNED managedtask.AssignedGroupID = 0 @@ -2287,6 +2435,7 @@ function AWACS:_TaskAbort(Group) -- unlink group from task Pilot.HasAssignedTask = false Pilot.CurrentTask = 0 + Pilot.LastTasking = timer.getTime() self.ManagedGrps[GID] = Pilot text = string.format("%s. %s. Copy.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) local EngagementTag = "" @@ -2348,7 +2497,7 @@ function AWACS:_Showtask(Group) end local pposition = managedgroup.Group:GetCoordinate() or managedgroup.LastKnownPosition - if currenttask.ToDo == AWACS.TaskDescription.INTERCEPT then + if currenttask.ToDo == AWACS.TaskDescription.INTERCEPT or currenttask.ToDo == AWACS.TaskDescription.VID then local targetpos = currenttask.Target:GetCoordinate() if pposition and targetpos then local alti = currenttask.Cluster.altitude or currenttask.Contact.altitude or currenttask.Contact.group:GetAltitude() @@ -2401,6 +2550,7 @@ function AWACS:_CheckIn(Group) managedgroup.GID = self.ManagedGrpID --managedgroup.TaskQueue = FIFO:New() managedgroup.LastKnownPosition = Group:GetCoordinate() + managedgroup.LastTasking = timer.getTime() GID = managedgroup.GID self.ManagedGrps[self.ManagedGrpID]=managedgroup @@ -2577,6 +2727,13 @@ function AWACS:_SetClientMenus() local abort = MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp) --local judy = MENU_GROUP_COMMAND:New(cgrp,"Judy",tasking,self._Judy,self,cgrp) + if self.AwacsROE == AWACS.ROE.POLICE or self.AwacsROE == AWACS.ROE.VID then + local vid = MENU_GROUP:New(cgrp,"VID as",tasking) + local hostile = MENU_GROUP_COMMAND:New(cgrp,"Hostile",vid,self._VID,self,cgrp,AWACS.IFF.ENEMY) + local neutral = MENU_GROUP_COMMAND:New(cgrp,"Neutral",vid,self._VID,self,cgrp,AWACS.IFF.NEUTRAL) + local friendly = MENU_GROUP_COMMAND:New(cgrp,"Friendly",vid,self._VID,self,cgrp,AWACS.IFF.FRIENDLY) + end + local picture = MENU_GROUP_COMMAND:New(cgrp,"Picture",basemenu,self._Picture,self,cgrp) local bogeydope = MENU_GROUP_COMMAND:New(cgrp,"Bogey Dope",basemenu,self._BogeyDope,self,cgrp) @@ -2637,6 +2794,14 @@ function AWACS:_SetClientMenus() local unable = MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp) local abort = MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp) --local judy = MENU_GROUP_COMMAND:New(cgrp,"Judy",tasking,self._Judy,self,cgrp) + + if self.AwacsROE == AWACS.ROE.POLICE or self.AwacsROE == AWACS.ROE.VID then + local vid = MENU_GROUP:New(cgrp,"VID as",tasking) + local hostile = MENU_GROUP_COMMAND:New(cgrp,"Hostile",vid,self._VID,self,cgrp,AWACS.IFF.ENEMY) + local neutral = MENU_GROUP_COMMAND:New(cgrp,"Neutral",vid,self._VID,self,cgrp,AWACS.IFF.NEUTRAL) + local friendly = MENU_GROUP_COMMAND:New(cgrp,"Friendly",vid,self._VID,self,cgrp,AWACS.IFF.FRIENDLY) + end + local declare = MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp) local checkin = MENU_GROUP_COMMAND:New(cgrp,"Check In",basemenu,self._CheckIn,self,cgrp) local checkout = MENU_GROUP_COMMAND:New(cgrp,"Check Out",basemenu,self._CheckOut,self,cgrp) @@ -2840,7 +3005,7 @@ function AWACS:_StartIntel(awacs) local intel = INTEL:New(self.DetectionSet,self.coalition,self.callsigntxt) --intel:SetVerbosity(2) --intel:SetClusterRadius(UTILS.NMToMeters(5)) - intel:SetClusterAnalysis(true,true,true) + intel:SetClusterAnalysis(true,false,false) local acceptzoneset = SET_ZONE:New() acceptzoneset:AddZone(self.ControlZone) @@ -3219,7 +3384,8 @@ function AWACS:_CheckTaskQueue() if group.Group and group.Group:IsAlive() then local coordinate = group.Group:GetCoordinate() if coordinate then - group.LastKnownPosition = coordinate + local NewCoordinate = COORDINATE:New(0,0,0) + group.LastKnownPosition = group.LastKnownPosition:UpdateFromCoordinate(coordinate) self.ManagedGrps[_id] = group end end @@ -3392,6 +3558,7 @@ function AWACS:_CheckTaskQueue() managedgroup.HasAssignedTask = false managedgroup.ContactCID = 0 + managedgroup.LastTasking = timer.getTime() if managedgroup.IsAI then managedgroup.CurrentAuftrag = 0 @@ -3412,6 +3579,7 @@ function AWACS:_CheckTaskQueue() managedgroup.HasAssignedTask = false self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) managedgroup.ContactCID = 0 + managedgroup.LastTasking = timer.getTime() if managedgroup.IsAI then managedgroup.CurrentAuftrag = 0 else @@ -3421,17 +3589,11 @@ function AWACS:_CheckTaskQueue() entry.IsPlayerTask = false end self.ManagedGrps[entry.AssignedGroupID] = managedgroup + self:__ReAnchor(5,managedgroup.GID) end - -- re-assign, if possible. FG state? Issue re-anchor - entry.IsUnassigned = true - entry.CurrentAuftrag = 0 - entry.Auftrag = nil - entry.Status = AWACS.TaskStatus.UNASSIGNED - entry.AssignedGroupID = 0 + -- remove self.ManagedTasks:PullByID(entry.TID) - --self.ManagedTasks:Push(entry,entry.TID) self:__InterceptFailure(1) - self:__ReAnchor(5,managedgroup.GID) elseif entry.Status == AWACS.TaskStatus.REQUESTED then -- requested - player tasks only! @@ -3440,15 +3602,196 @@ function AWACS:_CheckTaskQueue() local Tnow = timer.getTime() local Trunning = (Tnow-created) / 60 -- mins local text = string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning) + if Trunning > 180 then + -- reassign if player didn't react within 3 mins + entry.Status = AWACS.TaskStatus.UNASSIGNED + self.ManagedTasks:PullByID(entry.TID) + end self:I(text) end ---------------------------------------- - -- OTHER + -- VID/POLICE ---------------------------------------- - elseif description == AWACS.TaskDescription.RTB then - -- TODO + elseif description == AWACS.TaskDescription.VID then + -- TODO - how to do this with AI? + -- humans only ATM + local managedgroup = self.ManagedGrps[entry.AssignedGroupID] -- #AWACS.ManagedGroup + -- check we're alive + if (not managedgroup) or (not managedgroup.Group:IsAlive()) then + self.ManagedTasks:PullByID(entry.TID) + --entry.Status = AWACS.TaskStatus.FAILED + return self + end + + -- target dead or out of bounds? + if entry.Target:IsDead() or entry.Target:IsDestroyed() then + -- success! + entry.Status = AWACS.TaskStatus.SUCCESS + elseif entry.Target:IsAlive() then + -- still alive + -- out of zones? + self:I("Checking VID target out of bounds") + local targetpos = entry.Target:GetCoordinate() + -- success == out of our controlled zones + local outofzones = false + self.RejectZoneSet:ForEachZone( + function(Zone,Position) + local zone = Zone -- Core.Zone#ZONE + local pos = Position -- Core.Point#VEC2 + if pos and zone:IsVec2InZone(pos) then + -- crossed the border + outofzones = true + end + end, + targetpos:GetVec2() + ) + if not outofzones then + outofzones = true + self.ZoneSet:ForEachZone( + function(Zone,Position) + local zone = Zone -- Core.Zone#ZONE + local pos = Position -- Core.Point#VEC2 + if pos and zone:IsVec2InZone(pos) then + -- in any zone + outofzones = false + end + end, + targetpos:GetVec2() + ) + end + if outofzones then + entry.Status = AWACS.TaskStatus.SUCCESS + self:I("Out of bounds - SUCCESS") + end + end + + if entry.Status == AWACS.TaskStatus.REQUESTED then + -- requested - player tasks only! + self:I("Open Tasks VID REQUESTED for GroupID "..entry.AssignedGroupID) + local created = entry.RequestedTimestamp or timer.getTime() - 120 + local Tnow = timer.getTime() + local Trunning = (Tnow-created) / 60 -- mins + local text = string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning) + if Trunning > 180 then + -- reassign if player didn't react within 3 mins + entry.Status = AWACS.TaskStatus.UNASSIGNED + self.ManagedTasks:PullByID(entry.TID) + end + self:I(text) + elseif entry.Status == AWACS.TaskStatus.ASSIGNED then + self:I("Open Tasks VID ASSIGNED for GroupID "..entry.AssignedGroupID) + -- check TAC/MELD ranges + local targetgrp = entry.Contact.group + local position = entry.Contact.position or entry.Cluster.coordinate + if targetgrp and targetgrp:IsAlive() and managedgroup then + --position = targetgrp:GetCoordinate() + if position and managedgroup.Group and managedgroup.Group:IsAlive() then + local grouposition = managedgroup.Group:GetCoordinate() or managedgroup.Group:GetCoordinate() + local distance = 1000 + if grouposition then + distance = grouposition:Get2DDistance(position) + distance = UTILS.Round(UTILS.MetersToNM(distance),0) + end + self:I("TAC/MELD distance check: "..distance.."NM!") + if distance <= self.TacDistance and distance >= self.MeldDistance then + -- TAC distance + self:I("TAC distance: "..distance.."NM!") + local Contact = self.Contacts:ReadByID(entry.Contact.CID) + self:_TACRangeCall(entry.AssignedGroupID,Contact) + elseif distance <= self.MeldDistance and distance >= self.ThreatDistance then + -- MELD distance + self:I("MELD distance: "..distance.."NM!") + local Contact = self.Contacts:ReadByID(entry.Contact.CID) + self:_MeldRangeCall(entry.AssignedGroupID,Contact) + end + end + end + elseif entry.Status == AWACS.TaskStatus.SUCCESS then + self:I("Open Tasks VID success for GroupID "..entry.AssignedGroupID) + -- outcomes - player ID'd + -- target dead or left zones handled above + -- target ID'd --> if hostile, assign INTERCEPT TASK + self.ManagedTasks:PullByID(entry.TID) + local Contact = self.Contacts:ReadByID(entry.Contact.CID) -- #AWACS.ManagedContact + if Contact and (Contact.IFF == AWACS.IFF.FRIENDLY or Contact.IFF == AWACS.IFF.NEUTRAL) then + self:I("IFF outcome friendly/neutral for GroupID "..entry.AssignedGroupID) + -- nothing todo, re-anchor + if managedgroup then + managedgroup.HasAssignedTask = false + self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) + managedgroup.ContactCID = 0 + managedgroup.LastTasking = timer.getTime() + if managedgroup.IsAI then + managedgroup.CurrentAuftrag = 0 + else + managedgroup.CurrentTask = 0 + end + if managedgroup.IsPlayer then + entry.IsPlayerTask = false + end + --self.ManagedTasks:PullByID(entry.TID) + self.ManagedGrps[entry.AssignedGroupID] = managedgroup + self:__ReAnchor(5,managedgroup.GID) + end + elseif Contact and Contact.IFF == AWACS.IFF.ENEMY then + self:I("IFF outcome hostile for GroupID "..entry.AssignedGroupID) + -- change to intercept + --self.ManagedTasks:PullByID(entry.TID) + entry.ToDo = AWACS.TaskDescription.INTERCEPT + entry.Status = AWACS.TaskStatus.ASSIGNED + local cname = Contact.TargetGroupNaming + entry.ScreenText = string.format("Engage hostile %s group.",cname) + self.ManagedTasks:Push(entry,entry.TID) + local TextTTS = string.format("%s, %s. Engage hostile target!",managedgroup.CallSign,self.callsigntxt) + self:_NewRadioEntry(TextTTS,TextTTS,managedgroup.GID,true,self.debug,true,false,true) + elseif not Contact then + self:I("IFF outcome target DEAD for GroupID "..entry.AssignedGroupID) + -- nothing todo, re-anchor + if managedgroup then + managedgroup.HasAssignedTask = false + self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) + managedgroup.ContactCID = 0 + managedgroup.LastTasking = timer.getTime() + if managedgroup.IsAI then + managedgroup.CurrentAuftrag = 0 + else + managedgroup.CurrentTask = 0 + end + if managedgroup.IsPlayer then + entry.IsPlayerTask = false + end + --self.ManagedTasks:PullByID(entry.TID) + self.ManagedGrps[entry.AssignedGroupID] = managedgroup + self:__ReAnchor(5,managedgroup.GID) + end + end + elseif entry.Status == AWACS.TaskStatus.FAILED then + -- outcomes - player unable/abort + -- Player dead managed above + -- Remove task + self:I("Open Tasks VID failed for GroupID "..entry.AssignedGroupID) + if managedgroup then + managedgroup.HasAssignedTask = false + self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) + managedgroup.ContactCID = 0 + managedgroup.LastTasking = timer.getTime() + if managedgroup.IsAI then + managedgroup.CurrentAuftrag = 0 + else + managedgroup.CurrentTask = 0 + end + if managedgroup.IsPlayer then + entry.IsPlayerTask = false + end + self.ManagedGrps[entry.AssignedGroupID] = managedgroup + self:__ReAnchor(5,managedgroup.GID) + end + -- remove + self.ManagedTasks:PullByID(entry.TID) + self:__InterceptFailure(1) + end end end @@ -3902,12 +4245,12 @@ function AWACS:_SetAIROE(FlightGroup,Group) elseif ROE == AWACS.ROE.BVR then FlightGroup:SetDefaultROE(ENUMS.ROE.OpenFire) end - if ROT == AWACS.ROT.PASSIVE or ROT == AWACS.ROT.RETURNFIRE then - FlightGroup:SetDefaultROT(ENUMS.ROT.BypassAndEscape) - elseif ROT == AWACS.ROT.ACTIVE or ROT == AWACS.ROT.OPENFIRE then - FlightGroup:SetDefaultROT(ENUMS.ROT.EvadeFire) - elseif ROT == AWACS.ROT.LOCK then + if ROT == AWACS.ROT.BYPASSESCAPE or ROT == AWACS.ROT.PASSIVE then FlightGroup:SetDefaultROT(ENUMS.ROT.PassiveDefense) + elseif ROT == AWACS.ROT.OPENFIRE or ROT == AWACS.ROT.RETURNFIRE then + FlightGroup:SetDefaultROT(ENUMS.ROT.BypassAndEscape) + elseif ROT == AWACS.ROT.EVADE then + FlightGroup:SetDefaultROT(ENUMS.ROT.EvadeFire) end FlightGroup:SetFuelLowRTB(true) FlightGroup:SetFuelLowThreshold(0.2) @@ -4046,8 +4389,15 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) local PlayerPositon = Pilot.LastKnownPosition local TargetAlt = Target.Contact.altitude or Target.Cluster.altitude or Target.Contact.group:GetAltitude() local TargetDirections, TargetDirectionsTTS = self:_ToStringBRA(PlayerPositon,TargetPosition,TargetAlt) - local ScreenText = string.format("Intercept %s group.",Target.TargetGroupNaming) - Pilot.CurrentTask = self:_CreateTaskForGroup(Pilot.GID,AWACS.TaskDescription.INTERCEPT,ScreenText,Target.Target,AWACS.TaskStatus.REQUESTED,nil,Target.Cluster,Target.Contact) + local ScreenText = "" + local TaskType = AWACS.TaskDescription.INTERCEPT + if self.AwacsROE == AWACS.ROE.POLICE or self.AwacsROE == AWACS.ROE.VID then + ScreenText = string.format("Intercept and VID %s group.",Target.TargetGroupNaming) + TaskType = AWACS.TaskDescription.VID + else + ScreenText = string.format("Intercept %s group.",Target.TargetGroupNaming) + end + Pilot.CurrentTask = self:_CreateTaskForGroup(Pilot.GID,TaskType,ScreenText,Target.Target,AWACS.TaskStatus.REQUESTED,nil,Target.Cluster,Target.Contact) Pilot.ContactCID = Target.CID @@ -4068,12 +4418,6 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) self:_NewRadioEntry(text,textScreen,Pilot.GID,true,self.debug,true,false,true) - local text = string.format("%s. Commit.",Pilot.CallSign) - - self:_NewRadioEntry(text,text,Pilot.GID,true,self.debug,true,true,true) - - --self:__Intercept(2) - elseif inreach and Pilot and Pilot.IsAI then -- Target information local callsign = Pilot.CallSign @@ -5042,20 +5386,22 @@ function AWACS:onafterReAnchor(From, Event, To, GID) } -- DONE - need to save last known coordinate - local faded = textoptions[math.random(1,4)] - local text = string.format("All stations. %s. %s %s.",self.callsigntxt, faded, savedcallsign) - local textScreen = string.format("All stations, %s. %s %s.", self.callsigntxt, faded, savedcallsign) - local brtext = self:ToStringBULLS(managedgroup.LastKnownPosition) - local brtexttts = self:ToStringBULLS(brtext,false,true) - if self.PathToGoogleKey then - brtexttts = self:ToStringBULLS(managedgroup.LastKnownPosition,true) + if managedgroup.LastKnownPosition then + local faded = textoptions[math.random(1,4)] + local text = string.format("All stations. %s. %s %s.",self.callsigntxt, faded, savedcallsign) + local textScreen = string.format("All stations, %s. %s %s.", self.callsigntxt, faded, savedcallsign) + + local brtext = self:ToStringBULLS(managedgroup.LastKnownPosition) + local brtexttts = self:ToStringBULLS(brtext,false,true) + if self.PathToGoogleKey then + brtexttts = self:ToStringBULLS(managedgroup.LastKnownPosition,true) + end + text = text .. " "..brtexttts.." miles." + textScreen = textScreen .. " "..brtext.." miles." + + self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) end - text = text .. " "..brtexttts.." miles." - textScreen = textScreen .. " "..brtext.." miles." - - self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) - self.ManagedGrps[GID] = nil end elseif managedgroup.IsPlayer then diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 994ea64b2..e06e7df43 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -387,7 +387,7 @@ function TARGET:onafterStatus(From, Event, To) end if life==0 then - self:I(self.lid..string.format("FF life is zero but no object dead event fired ==> object dead now for traget object %s!", tostring(target.Name))) + self:I(self.lid..string.format("FF life is zero but no object dead event fired ==> object dead now for target object %s!", tostring(target.Name))) self:ObjectDead(target) end From aa94ae8759b6650e5c9228633ffce57683efb152 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 20 May 2022 15:51:54 +0200 Subject: [PATCH 2/7] AWACS moving to beta 1.22 - added documentation, some fixes --- Moose Development/Moose/Ops/Awacs.lua | 541 +++++++++++++++++++------- 1 file changed, 403 insertions(+), 138 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 6605d804b..c95236275 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -2,9 +2,13 @@ -- -- ## Main Features: -- --- * WIP --- * References from ARN33396 ATP 3-52.4 (Sep 2021) (Combined Forces) --- * References from CNATRA P-877 (Rev. 12-20) (NAVY) +-- * WIP (beta) +-- * AWACS replacement for the in-game AWACS +-- * Will control a fighter engagement zone and assign tasks to AI and human CAP flights +-- * Concentrates on threat-based call outs +-- * Callouts referenced from: +-- ** References from ARN33396 ATP 3-52.4 (Sep 2021) (Combined Forces) +-- ** References from CNATRA P-877 (Rev. 12-20) (NAVY) -- * Many additional events that the mission designer can hook into -- -- === @@ -22,7 +26,6 @@ -- @module Ops.AWACS -- @image OPS_AWACS.jpg - --- -- -- === @@ -37,8 +40,6 @@ -- -- === -- --- ** Main Features: ** --- do @@ -118,6 +119,7 @@ do -- @field Core.Point#COORDINATE AOCoordinate -- Coordinate of bulls eye -- @field Utilities.FiFo#FIFO clientmenus -- @field #number RadarBlur -- Radar blur in % +-- @field #number ReassignmentPause -- Wait this many seconds before re-assignment of a player -- @extends Core.Fsm#FSM @@ -128,12 +130,166 @@ do -- -- # AWACS AI Air Controller -- --- This class provides Fighter Engagement Zone surveillance and tasking. +-- * WIP (beta) +-- * AWACS replacement for the in-game AWACS +-- * Will control a fighter engagement zone and assign tasks to AI and human CAP flights +-- * Callouts referenced from: +-- ** References from ARN33396 ATP 3-52.4 (Sep 2021) (Combined Forces) +-- ** References from CNATRA P-877 (Rev 12-20) (NAVY) +-- * FSM events that the mission designer can hook into +-- +-- ## 1 Prerequisites +-- +-- The radio callouts in this class are ***exclusively*** created with Text-To-Speech (TTS), based on the Moose @{Sound.SRS} Class, and output is via [Ciribob's SRS system](https://github.com/ciribob/DCS-SimpleRadioStandalone/releases) +-- Ensure you have this covered and working before tackling this class. TTS generation can thus be done via the Windows built-in system or via Google TTS; +-- the latter offers a wider range of voices and options, but you need to set up your own Google product account for this to work correctly. +-- +-- ## 2 Mission Design - Zones +-- +-- Basic operational target of the AWACS is to control a Fighter Engagement Zone, or FEZ, and defend itself. +-- +-- ## 3 Airwing(s) +-- +-- The AWACS plane, the optional escort planes, and the AI CAP planes work based on the @(Ops.AirWing) class. Read and understand the manual for this class in +-- order to set everything up correctly. You will at least need one Squadron containing the AWACS plane itself. +-- +-- Set up the AirWing +-- +-- local AwacsAW = AIRWING:New("AirForce WH-1","AirForce One") +-- AwacsAW:SetMarker(false) +-- AwacsAW:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Kutaisi)) +-- AwacsAW:SetRespawnAfterDestroyed(900) +-- AwacsAW:SetTakeoffAir() +-- AwacsAW:__Start(2) +-- +-- Add the AWACS template Squadron - **Note**: remove the task AWACS in the mission editor und "Advanced Waypoint Actions" from the template to remove the DCS F10 AWACS menu +-- +-- local Squad_One = SQUADRON:New("Awacs One",2,"Awacs North") +-- Squad_One:AddMissionCapability({AUFTRAG.Type.ORBIT},100) +-- Squad_One:SetFuelLowRefuel(true) +-- Squad_One:SetFuelLowThreshold(0.2) +-- Squad_One:SetTurnoverTime(10,20) +-- AwacsAW:AddSquadron(Squad_One) +-- AwacsAW:NewPayload("Awacs One One",-1,{AUFTRAG.Type.ORBIT},100) +-- +-- Add Escorts Squad (recommended, optional) +-- +-- local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North") +-- Squad_Two:AddMissionCapability({AUFTRAG.Type.ESCORT}) +-- Squad_Two:SetFuelLowRefuel(true) +-- Squad_Two:SetFuelLowThreshold(0.3) +-- Squad_Two:SetTurnoverTime(10,20) +-- Squad_Two:SetTakeoffAir() +-- Squad_Two:SetRadio(255,radio.modulation.AM) +-- AwacsAW:AddSquadron(Squad_Two) +-- AwacsAW:NewPayload("Escorts",-1,{AUFTRAG.Type.ESCORT},100) +-- +-- Add CAP Squad (recommended, optional) +-- +-- local Squad_Three = SQUADRON:New("CAP",10,"CAP North") +-- Squad_Three:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},80) +-- Squad_Three:SetFuelLowRefuel(true) +-- Squad_Three:SetFuelLowThreshold(0.3) +-- Squad_Three:SetTurnoverTime(10,20) +-- Squad_Three:SetTakeoffAir() +-- Squad_Two:SetRadio(255,radio.modulation.AM) +-- AwacsAW:AddSquadron(Squad_Three) +-- AwacsAW:NewPayload("Aerial-1-2",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},100) +-- +-- ## 4 Zones +-- +-- For the setup, you need to set up a couple of zones: +-- +-- * An Orbit Zone, where your AWACS will orbit +-- * A Fighter Engagement Zone or FEZ +-- * A zone where your CAP flights will be stationed, waiting for assignments +-- * Optionally, an additional zone you wish to defend +-- * Optionally, a border of the opposing party +-- * Also, and move your BullsEye in the mission accordingly - this will be the key reference point for most AWACS callouts +-- +-- ### 4.1 Strategic considerations +-- +-- Your AWACS is an HVT or high-value-target. Thus it makes sense to position the Orbit Zone in a way that your FEZ and thus your CAP flights defend it. +-- It should hence be positioned behind the FEZ, away from the direction of enemy engagement. +-- The zone for CAP stations should be close to the FEZ, but not inside it. +-- The optional additional defense zone can be anywhere, but keep an eye on the location so your CAP flight don't take ages to get there. +-- The optional border is useful for e.g. "cold war" scenarios - planes across the border will not be considered as targets by AWACS. +-- +-- ## 5 Set up AWACS +-- +-- -- Set up AWACS called "AWACS North". It will use the AwacsAW AirWing set up above and be of the "blue" coalition. Homebase is Kutaisi. +-- -- The AWACS Orbit Zone is a round zone set in the mission editor named "Awacs Orbit", the FEZ is a Polygon-Zone called "Rock" we have also +-- -- set up in the mission editor with a late activated helo named "Rock#ZONE_POLYGON". Note this also sets the BullsEye to be referenced as "Rock". +-- -- The CAP station zone is called "Fremont". We will be on 255 AM. +-- local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM ) +-- -- set two escorts +-- testawacs:SetEscort(2) +-- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a lenght of 25nm. +-- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25) +-- -- Set up SRS on port 5010 - change the below to your path and port +-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010) +-- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "REd Border#ZONE_POLYGON" +-- testawacs:SetRejectionZone(ZONE:FindByName("Red Border")) +-- -- Our CAP flight will have callsign "Ford", we want 4 AI planes, Time-On-Station 4 hours, doing 300 kn IAS. +-- testawacs:SetAICAPDetails(CALLSIGN.Aircraft.Ford,4,4,300) + -- We're modern (default), e.g. we have EPLRS and get more fill-in information on detections +-- testawacs:SetModernEra() + -- And start +-- testawacs:__Start(5) +-- +-- ## 6 Menu entries +-- +-- **Note on Radio Menu entries**: Due to a DCS limitation, these are on GROUP level and not individual (UNIT level). Hence, either put each player in his/her own group, +-- or ensure that only the flight lead will use the menu. Recommend the 1st option, unless you have a disciplined team. +-- +-- ### 6.1 Check-in +-- +-- In the base setup, you need to check in to the AWACS to get the full menu. This can be done once the AWACS is airborne. You will get an Alpha Check callout +-- and be assigned a CAP station. +-- +-- ### 6.2 Check-out +-- +-- You can check-ou anytime, of course. +-- +-- ### 6.3 Picture +-- +-- Get a picture from the AWACS. It will call out the three most important groups. References are BRAA to the Player position. +-- **Note** that AWACS will do a regular picture call to all stations every five minutes. Here, references are to the (named) BullsEye position. +-- +-- ### 6.4 Bogey Dope +-- +-- Get bogey dope from the AWACS. It will call out the three most important groups. References are BRAA to the Player position. +-- +-- ### 6.5 Declare +-- +-- AWACS will declare, if the bogey closest to the calling player in a 3nm circle is hostile, friendly or neutral. +-- +-- ### 6.6 Tasking +-- +-- Tasking will show you the current task with "Showtask". Updated directions are shown, also. +-- You can decline a **requested** task with "unable", and abort **any task but CAP station** with "abort". +-- You can "commit" to a requested task within 3 minutes. +-- "VID" - if AWACS is set to Visial ID or VID oncoming planes first, there will also be an "VID" entry. Similar to "Declare" you can declare the requested contact +-- to be hostile, friendly or neutral if you are close enough to it (3nm). If hostile, at the time of writing, an engagement task will be assigned to you (not: requested). +-- If neutral/friendly, contact will be excluded from further tasking. +-- +-- ## 7 Air-to-Air Timeline Support +-- +-- To support your engagement timeline, AWACS will make Tac-Range, Meld, Merge and Threat call-outs to the player/group (Figure 7-3, CNATRA P-877). Default settings in NM are +-- +-- Tac Distance = 45 +-- Meld Distance = 35 +-- Threat Distance = 25 +-- Merge Distance =3 +-- +-- ## 8 Discussion +-- +-- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) #ops-awacs channel. -- -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "alpha 0.0.20", -- #string + version = "beta 0.1.22", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -206,6 +362,7 @@ AWACS = { AOCoordinate = nil, clientmenus = nil, RadarBlur = 15, + ReassignmentPause = 180, } --- @@ -298,7 +455,7 @@ AWACS.ROE = { --- -- @field AWACS.ROT AWACS.ROT = { - BYPASSESCAPE = "Bypass and Escpae", + BYPASSESCAPE = "Bypass and Escape", EVADE = "Evade Fire", PASSIVE = "Passive Defense", RETURNFIRE = "Return Fire", @@ -465,18 +622,17 @@ AWACS.TaskStatus = { --@field #boolean FromAI ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO-List 0.0.20 +-- TODO-List 0.1.22 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- --- DEBUG - WIP - Player tasking --- TODO - Maybe check in AI only when airborne --- DONE - remove SSML tag when not on google (currently sometimes spoken) +-- DEBUG - WIP - Player tasking, VID -- TODO - Localization -- TODO - (LOW) LotATC / IFF --- TODO - SW Optimizer --- TODO - Assign specific number of AI CAP to a station --- DEBUG - Multiple AIRWING connection? Can't really get recruit to work, switched to random round robin --- +-- TODO - SW Optimization +-- WONTDO - Maybe check in AI only when airborne +-- DONE - remove SSML tag when not on google (currently sometimes spoken) +-- DONE - Maybe - Assign specific number of AI CAP to a station +-- DONE - Multiple AIRWING connection? Can't really get recruit to work, switched to random round robin -- DONE - System for Players to VID contacts? -- DONE - Task reassignment - if a player reject a task, don't choose him again for 3 minutes -- DONE - added SSML tags to make google readouts nicer @@ -627,6 +783,8 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self.CAPGender = "male" self.CAPCulture = "en-US" self.CAPVoice = nil + + self.ReassignmentPause = 180 self.DeclareRadius = 5 -- NM @@ -634,8 +792,8 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self.AwacsInZone = false -- not yet arrived or gone again self.AwacsReady = false - self.AwacsROE = AWACS.ROE.POLICE - self.AwacsROT = AWACS.ROT.PASSIVE + self.AwacsROE = AWACS.ROE.IFF + self.AwacsROT = AWACS.ROT.BYPASSESCAPE self.MenuStrict = true @@ -822,6 +980,15 @@ function AWACS:SetBullsEyeAlias(Name) return self end +--- [User] Change number of seconds AWACS waits until a Player is re-assigned a different task. Defaults to 180. +-- @param #AWACS self +-- @param #number Seconds +-- @return #AWACS self +function AWACS:SetReassignmentPause(Seconds) + self.ReassignmentPause = Seconds or 180 + return self +end + --- [User] Do not show messages on screen -- @param #AWACS self -- @param #boolean Switch If true, no messages will be shown on screen. @@ -982,7 +1149,7 @@ end -- @param #AWACS self -- @param #number Percent -- @return #AWACS self -function AWACS:SetRadarBlue(Percent) +function AWACS:SetRadarBlur(Percent) local percent = Percent or 15 if percent < 0 then percent = 0 end if percent > 100 then percent = 100 end @@ -990,7 +1157,8 @@ function AWACS:SetRadarBlue(Percent) return self end ---- [User] Set AWACS to Cold War standards - ROE to VID, ROT to Passive (bypass and escape) +--- [User] Set AWACS to Cold War standards - ROE to VID, ROT to Passive (bypass and escape). Radar blur 25%. +-- Sets TAC/Meld/Threat call distances to 35, 25 and 15 nm. -- @param #AWACS self -- @return #AWACS self function AWACS:SetColdWar() @@ -998,13 +1166,14 @@ function AWACS:SetColdWar() self.AwacsROT = AWACS.ROT.PASSIVE self.AwacsROE = AWACS.ROE.VID self.RadarBlur = 25 + self:SetInterceptTimeline(35, 25, 15) return self end ---- [User] Set AWACS to Modern Era standards - ROE to IFF, ROT to Active (evade fire) +--- [User] Set AWACS to Modern Era standards - ROE to IFF, ROT to defensive (evade fire). Radar blur 15%. -- @param #AWACS self -- @return #AWACS self -function AWACS:SetModernEra() +function AWACS:SetModernEraDefensive() self.ModernEra = true self.AwacsROT = AWACS.ROT.EVADE self.AwacsROE = AWACS.ROE.IFF @@ -1012,7 +1181,18 @@ function AWACS:SetModernEra() return self end ---- [User] Set AWACS to Policing standards - ROE to VID, ROT to Lock (bypass and escape) +--- [User] Set AWACS to Modern Era standards - ROE to BVR, ROT to return fire. Radar blur 15%. +-- @param #AWACS self +-- @return #AWACS self +function AWACS:SetModernEraAgressive() + self.ModernEra = true + self.AwacsROT = AWACS.ROT.RETURNFIRE + self.AwacsROE = AWACS.ROE.BVR + self.RadarBlur = 15 + return self +end + +--- [User] Set AWACS to Policing standards - ROE to VID, ROT to Lock (bypass and escape). Radar blur 15%. -- @param #AWACS self -- @return #AWACS self function AWACS:SetPolicingModern() @@ -1023,13 +1203,16 @@ function AWACS:SetPolicingModern() return self end ---- [User] Set AWACS to Policing standards - ROE to VID, ROT to Lock (bypass and escape) +--- [User] Set AWACS to Policing standards - ROE to VID, ROT to Lock (bypass and escape). Radar blur 25%. +-- Sets TAC/Meld/Threat call distances to 35, 25 and 15 nm. -- @param #AWACS self -- @return #AWACS self function AWACS:SetPolicingColdWar() self.ModernEra = false self.AwacsROT = AWACS.ROT.BYPASSESCAPE self.AwacsROE = AWACS.ROE.VID + self.RadarBlur = 25 + self:SetInterceptTimeline(35, 25, 15) return self end @@ -1040,6 +1223,19 @@ function AWACS:GetName() return self.Name or "not set" end +--- [User] Set AWACS intercept timeline support distance. +-- @param #AWACS self +-- @param #number TacDistance Distance for TAC call, default 45nm +-- @param #number MeldDistance Distance for Meld call, default 35nm +-- @param #number ThreatDistance Distance for Threat call, default 25nm +-- @return #AWACS self +function AWACS:SetInterceptTimeline(TacDistance, MeldDistance, ThreatDistance) + self.TacDistance = TacDistance or 45 + self.MeldDistance = MeldDistance or 35 + self.ThreatDistance = ThreatDistance or 25 + return self +end + --- [User] Set additional defensive zone, e.g. the zone behind the FEZ to also be defended -- @param #AWACS self -- @param Core.Zone#ZONE Zone @@ -1126,7 +1322,7 @@ function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey return self end ---- [User] Set AWACS Voice Details for AI CAP Planes - SRS TTS - see @{#MSRS} for details +--- [User] Set AWACS Voice Details for AI CAP Planes - SRS TTS - see @{Sound.SRS} for details -- @param #AWACS self -- @param #string Gender Defaults to "male" -- @param #string Culture Defaults to "en-US" @@ -1293,7 +1489,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) self:Started() elseif self.ShiftChangeAwacsRequested and self.AwacsMissionReplacement and self.AwacsMissionReplacement:GetName() == Mission:GetName() then - self:I("Setting up Awacs Replacement") + self:T("Setting up Awacs Replacement") -- manage AWACS Replacement AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) AwacsFG:SwitchRadio(self.Frequency,self.Modulation) @@ -1349,7 +1545,7 @@ end -- @param #boolean ssml Add SSML tag -- @param #boolean TTS For non-Alpha checks, hand back in format "Rock 0 2 1, 16" -- @return #string BullseyeBR -function AWACS:ToStringBULLS( Coordinate, ssml, TTS ) +function AWACS:_ToStringBULLS( Coordinate, ssml, TTS ) -- local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.coalition ) ) local bullseyename = self.AOName or "Rock" --local BullsCoordinate = self.OpsZone:GetCoordinate() @@ -1363,7 +1559,7 @@ function AWACS:ToStringBULLS( Coordinate, ssml, TTS ) if ssml then return string.format("%s %03d, %d",bullseyename,Bearing,Distance) elseif TTS then - Bearing = self:ToStringBullsTTS(Bearing) + Bearing = self:_ToStringBullsTTS(Bearing) return string.format("%s %s, %d",bullseyename,Bearing,Distance) else return string.format("%s %s, %d",bullseyename,Bearing,Distance) @@ -1374,7 +1570,7 @@ end -- @param #AWACS self -- @param #string Text Input text -- @return #string BullseyeBRTTS -function AWACS:ToStringBullsTTS(Text) +function AWACS:_ToStringBullsTTS(Text) local text = Text text=string.gsub(text,"Bullseye","Bulls eye") text=string.gsub(text,"%d","%1 ") @@ -1787,12 +1983,12 @@ function AWACS:_CreatePicture(AO,Callsign,GID,MaxEntries,IsGeneral) if IsGeneral then -- AO/BE Reference - refBRAA=self:ToStringBULLS(coordinate) + refBRAA=self:_ToStringBULLS(coordinate) if self.PathToGoogleKey then - refBRAATTS = self:ToStringBULLS(coordinate, true) + refBRAATTS = self:_ToStringBULLS(coordinate, true) else - --refBRAATTS = self:ToStringBullsTTS(refBRAA) - refBRAATTS = self:ToStringBULLS(coordinate,false,true) + --refBRAATTS = self:__ToStringBullsTTS(refBRAA) + refBRAATTS = self:_ToStringBULLS(coordinate,false,true) end local alt = contact.Contact.group:GetAltitude() or 8000 alt = UTILS.Round(UTILS.MetersToFeet(alt)/1000,0) @@ -2110,7 +2306,7 @@ end -- @param Wrapper.Group#GROUP Group Group to use -- @return #AWACS self function AWACS:_ShowAwacsInfo(Group) - self:I(self.lid.."_ShowAwacsInfo") + self:T(self.lid.."_ShowAwacsInfo") local report = REPORT:New("Info") report:Add("====================") report:Add(string.format("AWACS %s",self.callsigntxt)) @@ -2134,7 +2330,7 @@ end -- @param #string Declaration Text declaration the player used -- @return #AWACS self function AWACS:_VID(Group,Declaration) - self:I(self.lid.."_VID") + self:T(self.lid.."_VID") local GID, Outcome, Callsign = self:_GetManagedGrpID(Group) local text = "" @@ -2168,7 +2364,7 @@ function AWACS:_VID(Group,Declaration) distance = UTILS.Round(distance,0) + 1 if distance <= radius or self.debug then -- we can VID - self:I("Contact VID as "..Declaration) + self:T("Contact VID as "..Declaration) -- update cluster.IFF = Declaration task.Status = AWACS.TaskStatus.SUCCESS @@ -2177,12 +2373,12 @@ function AWACS:_VID(Group,Declaration) self.Contacts:PullByID(CID) self.Contacts:Push(cluster,CID) text = string.format("%s. %s. Copy, target identified as %s.",Callsign,self.callsigntxt, Declaration) - self:I(text) + self:T(text) else -- too far away - self:I("Contact VID not close enough") + self:T("Contact VID not close enough") text = string.format("%s. %s. Negative, get closer to target.",Callsign,self.callsigntxt) - self:I(text) + self:T(text) end self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) end @@ -2379,9 +2575,9 @@ function AWACS:_Unable(Group) if managedtask.Status == AWACS.TaskStatus.REQUESTED then -- ok let's commit this one managedtask = self.ManagedTasks:PullByID(currtaskid) - managedtask.AssignedGroupID = 0 + --managedtask.AssignedGroupID = 0 managedtask.IsUnassigned = true - managedtask.Status = AWACS.TaskStatus.UNASSIGNED + managedtask.Status = AWACS.TaskStatus.FAILED self.ManagedTasks:Push(managedtask,currtaskid) self:T(string.format("REJECTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status)) -- unlink group from task @@ -2426,8 +2622,8 @@ function AWACS:_TaskAbort(Group) if managedtask.Status == AWACS.TaskStatus.ASSIGNED then -- ok let's un-commit this one managedtask = self.ManagedTasks:PullByID(currtaskid) - managedtask.Status = AWACS.TaskStatus.UNASSIGNED - managedtask.AssignedGroupID = 0 + managedtask.Status = AWACS.TaskStatus.FAILED + --managedtask.AssignedGroupID = 0 managedtask.IsUnassigned = true self.ManagedTasks:Push(managedtask,currtaskid) -- unlink group @@ -2555,8 +2751,8 @@ function AWACS:_CheckIn(Group) GID = managedgroup.GID self.ManagedGrps[self.ManagedGrpID]=managedgroup - local alphacheckbulls = self:ToStringBULLS(Group:GetCoordinate()) - local alphacheckbullstts = self:ToStringBullsTTS(alphacheckbulls)-- make tts friendly + local alphacheckbulls = self:_ToStringBULLS(Group:GetCoordinate()) + local alphacheckbullstts = self:_ToStringBullsTTS(alphacheckbulls)-- make tts friendly self.ManagedGrps[self.ManagedGrpID]=managedgroup text = string.format("%s. %s. Alpha Check. %s",managedgroup.CallSign,self.callsigntxt,alphacheckbulls) @@ -2624,12 +2820,18 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) self:_NewRadioEntry(text,text,managedgroup.GID,Outcome,false,true,true) - local alphacheckbulls = self:ToStringBULLS(Group:GetCoordinate()) - alphacheckbulls = self:ToStringBullsTTS(alphacheckbulls)-- make tts friendly + local alphacheckbulls = self:_ToStringBULLS(Group:GetCoordinate()) + alphacheckbulls = self:_ToStringBullsTTS(alphacheckbulls)-- make tts friendly text = string.format("%s. %s. Alpha Check. %s",managedgroup.CallSign,self.callsigntxt,alphacheckbulls) self:__CheckedIn(1,managedgroup.GID) - self:__AssignAnchor(5,managedgroup.GID) + + local AW = FlightGroup:GetAirWing() + if AW.HasOwnStation then + self:__AssignAnchor(5,managedgroup.GID,AW.HasOwnStation,AW.StationName) + else + self:__AssignAnchor(5,managedgroup.GID) + end else text = string.format("%s. %s. Negative. You are already checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) end @@ -2656,18 +2858,22 @@ function AWACS:_CheckOut(Group,GID,dead) text = string.format("%s. %s. Copy. Have a safe flight home.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) self:T(text) -- grab some data before we nil the entry - local AnchorAssigned = self.ManagedGrps[GID] -- #AWACS.ManagedGroup - local Stack = AnchorAssigned.AnchorStackNo - local Angels = AnchorAssigned.AnchorStackAngels + local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + local Stack = managedgroup.AnchorStackNo + local Angels = managedgroup.AnchorStackAngels -- remove menus - if AnchorAssigned.IsPlayer then - -- TODO Move to FIFO - if self.clientmenus:HasUniqueID(AnchorAssigned.GroupName) then - local menus = self.clientmenus:PullByID(AnchorAssigned.GroupName) --#AWACS.MenuStructure + if managedgroup.IsPlayer then + -- DONE Move to FIFO + if self.clientmenus:HasUniqueID(managedgroup.GroupName) then + local menus = self.clientmenus:PullByID(managedgroup.GroupName) --#AWACS.MenuStructure menus.basemenu:Remove() --self.clientmenus[AnchorAssigned.GroupName] = nil end end + -- delete open tasks + if managedgroup.CurrentTask and managedgroup.CurrentTask > 0 then + self.ManagedTasks:PullByID(managedgroup.CurrentTask ) + end self.ManagedGrps[GID] = nil self:__CheckedOut(1,GID,Stack,Angels) else @@ -2738,7 +2944,7 @@ function AWACS:_SetClientMenus() local bogeydope = MENU_GROUP_COMMAND:New(cgrp,"Bogey Dope",basemenu,self._BogeyDope,self,cgrp) local declare = MENU_GROUP_COMMAND:New(cgrp,"Declare",basemenu,self._Declare,self,cgrp) - local declare = MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp) + local ainfo = MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp) local checkout = MENU_GROUP_COMMAND:New(cgrp,"Check Out",basemenu,self._CheckOut,self,cgrp) --basemenu:Set() @@ -2802,7 +3008,7 @@ function AWACS:_SetClientMenus() local friendly = MENU_GROUP_COMMAND:New(cgrp,"Friendly",vid,self._VID,self,cgrp,AWACS.IFF.FRIENDLY) end - local declare = MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp) + local ainfo = MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp) local checkin = MENU_GROUP_COMMAND:New(cgrp,"Check In",basemenu,self._CheckIn,self,cgrp) local checkout = MENU_GROUP_COMMAND:New(cgrp,"Check Out",basemenu,self._CheckOut,self,cgrp) @@ -2937,30 +3143,41 @@ end --- [Internal] AWACS Assign Anchor Position to a Group -- @param #AWACS self --- @return #number GID Managed Group ID +-- @param #number GID Managed Group ID +-- @param #boolean HasOwnStation +-- @param #string StationName -- @return #AWACS self -function AWACS:_AssignAnchorToID(GID) +function AWACS:_AssignAnchorToID(GID, HasOwnStation, StationName) self:T(self.lid.."_AssignAnchorToID") - local AnchorStackNo, Free = self:_GetFreeAnchorStack() - if Free then - -- get the Anchor from the stack - local Anchor = self.AnchorStacks:PullByPointer(AnchorStackNo) -- #AWACS.AnchorData + if not HasOwnStation then + local AnchorStackNo, Free = self:_GetFreeAnchorStack() + if Free then + -- get the Anchor from the stack + local Anchor = self.AnchorStacks:PullByPointer(AnchorStackNo) -- #AWACS.AnchorData + -- pull one free angels + local freeangels = Anchor.Anchors:Pull() + -- push GID on anchor + Anchor.AnchorAssignedID:Push(GID) + -- push back to AnchorStacks + self.AnchorStacks:Push(Anchor,Anchor.StationName) + self:T({Anchor,freeangels}) + self:__AssignedAnchor(5,GID,Anchor,AnchorStackNo,freeangels) + else + self:E(self.lid .. "Cannot assign free anchor stack to GID ".. GID .. " Trying again in 10secs.") + -- try again ... + self:__AssignAnchor(10,GID) + end + else + local Anchor = self.AnchorStacks:PullByID(StationName) -- #AWACS.AnchorData -- pull one free angels - local freeangels = Anchor.Anchors:Pull() + local freeangels = Anchor.Anchors:Pull() or 25 -- push GID on anchor Anchor.AnchorAssignedID:Push(GID) - if self.debug then - --Anchor.AnchorAssignedID:Flush() - --Anchor.Anchors:Flush() - end -- push back to AnchorStacks - self.AnchorStacks:Push(Anchor) + self.AnchorStacks:Push(Anchor,StationName) self:T({Anchor,freeangels}) - self:__AssignedAnchor(5,GID,Anchor,AnchorStackNo,freeangels) - else - self:E(self.lid .. "Cannot assign free anchor stack to GID ".. GID .. " Trying again in 10secs.") - -- try again ... - self:__AssignAnchor(10,GID) + local StackNo = self.AnchorStacks.stackbyid[StationName].pointer + self:__AssignedAnchor(5,GID,Anchor,StackNo,freeangels) end return self end @@ -3397,7 +3614,7 @@ function AWACS:_CheckTaskQueue() if self.ManagedTasks:IsNotEmpty() then opentasks = self.ManagedTasks:GetSize() - self:I("Assigned Tasks: " .. opentasks) + self:T("Assigned Tasks: " .. opentasks) local taskstack = self.ManagedTasks:GetPointerStack() for _id,_entry in pairs(taskstack) do local data = _entry -- Utilities.FiFo#FIFO.IDEntry @@ -3405,7 +3622,7 @@ function AWACS:_CheckTaskQueue() local target = entry.Target -- Ops.Target#TARGET local description = entry.ToDo if description == AWACS.TaskDescription.ANCHOR or description == AWACS.TaskDescription.REANCHOR then - --self:I("Open Task ANCHOR/REANCHOR") + self:T("Open Task ANCHOR/REANCHOR") -- see if we have reached the anchor zone local managedgroup = self.ManagedGrps[entry.AssignedGroupID] -- #AWACS.ManagedGroup if managedgroup then @@ -3415,7 +3632,7 @@ function AWACS:_CheckTaskQueue() local zone = target:GetObject() -- Core.Zone#ZONE self:T({zone}) if group:IsInZone(zone) then - self:I("Open Task ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) + self:T("Open Task ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) -- made it target:Stop() -- add group to idle stack @@ -3432,7 +3649,7 @@ function AWACS:_CheckTaskQueue() self.ManagedTasks:PullByID(entry.TID) else --inzone -- not there yet - self:I("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID) + self:T("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID) end else -- group dead, pull task @@ -3446,7 +3663,7 @@ function AWACS:_CheckTaskQueue() elseif description == AWACS.TaskDescription.INTERCEPT then -- DONE - self:I("Open Tasks INTERCEPT") + self:T("Open Tasks INTERCEPT") local taskstatus = entry.Status local targetstatus = entry.Target:GetState() @@ -3472,15 +3689,15 @@ function AWACS:_CheckTaskQueue() distance = grouposition:Get2DDistance(position) distance = UTILS.Round(UTILS.MetersToNM(distance),0) end - self:I("TAC/MELD distance check: "..distance.."NM!") + self:T("TAC/MELD distance check: "..distance.."NM!") if distance <= self.TacDistance and distance >= self.MeldDistance then -- TAC distance - self:I("TAC distance: "..distance.."NM!") + self:T("TAC distance: "..distance.."NM!") local Contact = self.Contacts:ReadByID(entry.Contact.CID) self:_TACRangeCall(entry.AssignedGroupID,Contact) elseif distance <= self.MeldDistance and distance >= self.ThreatDistance then -- MELD distance - self:I("MELD distance: "..distance.."NM!") + self:T("MELD distance: "..distance.."NM!") local Contact = self.Contacts:ReadByID(entry.Contact.CID) self:_MeldRangeCall(entry.AssignedGroupID,Contact) end @@ -3493,7 +3710,7 @@ function AWACS:_CheckTaskQueue() auftragstatus = auftrag:GetState() end local text = string.format("ID=%d | Status=%s | TargetState=%s | AuftragState=%s",entry.TID,taskstatus,targetstatus,auftragstatus) - self:I(text) + self:T(text) if auftrag then if auftrag:IsExecuting() then entry.Status = AWACS.TaskStatus.EXECUTING @@ -3551,7 +3768,7 @@ function AWACS:_CheckTaskQueue() end if entry.Status == AWACS.TaskStatus.SUCCESS then - self:I("Open Tasks INTERCEPT success for GroupID "..entry.AssignedGroupID) + self:T("Open Tasks INTERCEPT success for GroupID "..entry.AssignedGroupID) if managedgroup then self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",true,true,AWACS.TaskStatus.SUCCESS) @@ -3574,7 +3791,7 @@ function AWACS:_CheckTaskQueue() end elseif entry.Status == AWACS.TaskStatus.FAILED then - self:I("Open Tasks INTERCEPT failed for GroupID "..entry.AssignedGroupID) + self:T("Open Tasks INTERCEPT failed for GroupID "..entry.AssignedGroupID) if managedgroup then managedgroup.HasAssignedTask = false self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) @@ -3589,7 +3806,9 @@ function AWACS:_CheckTaskQueue() entry.IsPlayerTask = false end self.ManagedGrps[entry.AssignedGroupID] = managedgroup - self:__ReAnchor(5,managedgroup.GID) + if managedgroup.Group:IsAlive() or managedgroup.FlightGroup:IsAlive() then + self:__ReAnchor(5,managedgroup.GID) + end end -- remove self.ManagedTasks:PullByID(entry.TID) @@ -3597,17 +3816,17 @@ function AWACS:_CheckTaskQueue() elseif entry.Status == AWACS.TaskStatus.REQUESTED then -- requested - player tasks only! - self:I("Open Tasks INTERCEPT REQUESTED for GroupID "..entry.AssignedGroupID) + self:T("Open Tasks INTERCEPT REQUESTED for GroupID "..entry.AssignedGroupID) local created = entry.RequestedTimestamp or timer.getTime() - 120 local Tnow = timer.getTime() local Trunning = (Tnow-created) / 60 -- mins local text = string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning) - if Trunning > 180 then + if Trunning > self.ReassignmentPause then -- reassign if player didn't react within 3 mins entry.Status = AWACS.TaskStatus.UNASSIGNED self.ManagedTasks:PullByID(entry.TID) end - self:I(text) + self:T(text) end ---------------------------------------- @@ -3632,7 +3851,7 @@ function AWACS:_CheckTaskQueue() elseif entry.Target:IsAlive() then -- still alive -- out of zones? - self:I("Checking VID target out of bounds") + self:T("Checking VID target out of bounds") local targetpos = entry.Target:GetCoordinate() -- success == out of our controlled zones local outofzones = false @@ -3663,25 +3882,25 @@ function AWACS:_CheckTaskQueue() end if outofzones then entry.Status = AWACS.TaskStatus.SUCCESS - self:I("Out of bounds - SUCCESS") + self:T("Out of bounds - SUCCESS") end end if entry.Status == AWACS.TaskStatus.REQUESTED then -- requested - player tasks only! - self:I("Open Tasks VID REQUESTED for GroupID "..entry.AssignedGroupID) + self:T("Open Tasks VID REQUESTED for GroupID "..entry.AssignedGroupID) local created = entry.RequestedTimestamp or timer.getTime() - 120 local Tnow = timer.getTime() local Trunning = (Tnow-created) / 60 -- mins local text = string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning) - if Trunning > 180 then + if Trunning > self.ReassignmentPause then -- reassign if player didn't react within 3 mins entry.Status = AWACS.TaskStatus.UNASSIGNED self.ManagedTasks:PullByID(entry.TID) end - self:I(text) + self:T(text) elseif entry.Status == AWACS.TaskStatus.ASSIGNED then - self:I("Open Tasks VID ASSIGNED for GroupID "..entry.AssignedGroupID) + self:T("Open Tasks VID ASSIGNED for GroupID "..entry.AssignedGroupID) -- check TAC/MELD ranges local targetgrp = entry.Contact.group local position = entry.Contact.position or entry.Cluster.coordinate @@ -3694,29 +3913,29 @@ function AWACS:_CheckTaskQueue() distance = grouposition:Get2DDistance(position) distance = UTILS.Round(UTILS.MetersToNM(distance),0) end - self:I("TAC/MELD distance check: "..distance.."NM!") + self:T("TAC/MELD distance check: "..distance.."NM!") if distance <= self.TacDistance and distance >= self.MeldDistance then -- TAC distance - self:I("TAC distance: "..distance.."NM!") + self:T("TAC distance: "..distance.."NM!") local Contact = self.Contacts:ReadByID(entry.Contact.CID) self:_TACRangeCall(entry.AssignedGroupID,Contact) elseif distance <= self.MeldDistance and distance >= self.ThreatDistance then -- MELD distance - self:I("MELD distance: "..distance.."NM!") + self:T("MELD distance: "..distance.."NM!") local Contact = self.Contacts:ReadByID(entry.Contact.CID) self:_MeldRangeCall(entry.AssignedGroupID,Contact) end end end elseif entry.Status == AWACS.TaskStatus.SUCCESS then - self:I("Open Tasks VID success for GroupID "..entry.AssignedGroupID) + self:T("Open Tasks VID success for GroupID "..entry.AssignedGroupID) -- outcomes - player ID'd -- target dead or left zones handled above -- target ID'd --> if hostile, assign INTERCEPT TASK self.ManagedTasks:PullByID(entry.TID) local Contact = self.Contacts:ReadByID(entry.Contact.CID) -- #AWACS.ManagedContact if Contact and (Contact.IFF == AWACS.IFF.FRIENDLY or Contact.IFF == AWACS.IFF.NEUTRAL) then - self:I("IFF outcome friendly/neutral for GroupID "..entry.AssignedGroupID) + self:T("IFF outcome friendly/neutral for GroupID "..entry.AssignedGroupID) -- nothing todo, re-anchor if managedgroup then managedgroup.HasAssignedTask = false @@ -3736,7 +3955,7 @@ function AWACS:_CheckTaskQueue() self:__ReAnchor(5,managedgroup.GID) end elseif Contact and Contact.IFF == AWACS.IFF.ENEMY then - self:I("IFF outcome hostile for GroupID "..entry.AssignedGroupID) + self:T("IFF outcome hostile for GroupID "..entry.AssignedGroupID) -- change to intercept --self.ManagedTasks:PullByID(entry.TID) entry.ToDo = AWACS.TaskDescription.INTERCEPT @@ -3747,7 +3966,7 @@ function AWACS:_CheckTaskQueue() local TextTTS = string.format("%s, %s. Engage hostile target!",managedgroup.CallSign,self.callsigntxt) self:_NewRadioEntry(TextTTS,TextTTS,managedgroup.GID,true,self.debug,true,false,true) elseif not Contact then - self:I("IFF outcome target DEAD for GroupID "..entry.AssignedGroupID) + self:T("IFF outcome target DEAD for GroupID "..entry.AssignedGroupID) -- nothing todo, re-anchor if managedgroup then managedgroup.HasAssignedTask = false @@ -3764,14 +3983,16 @@ function AWACS:_CheckTaskQueue() end --self.ManagedTasks:PullByID(entry.TID) self.ManagedGrps[entry.AssignedGroupID] = managedgroup - self:__ReAnchor(5,managedgroup.GID) + if managedgroup.Group:IsAlive() or managedgroup.FlightGroup:IsAlive() then + self:__ReAnchor(5,managedgroup.GID) + end end end elseif entry.Status == AWACS.TaskStatus.FAILED then -- outcomes - player unable/abort -- Player dead managed above -- Remove task - self:I("Open Tasks VID failed for GroupID "..entry.AssignedGroupID) + self:T("Open Tasks VID failed for GroupID "..entry.AssignedGroupID) if managedgroup then managedgroup.HasAssignedTask = false self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) @@ -3786,7 +4007,9 @@ function AWACS:_CheckTaskQueue() entry.IsPlayerTask = false end self.ManagedGrps[entry.AssignedGroupID] = managedgroup - self:__ReAnchor(5,managedgroup.GID) + if managedgroup.Group:IsAlive() or managedgroup.FlightGroup:IsAlive() then + self:__ReAnchor(5,managedgroup.GID) + end end -- remove self.ManagedTasks:PullByID(entry.TID) @@ -3816,15 +4039,52 @@ function AWACS:_LogStatistics() return self end ---- [User] Add another AirWing for CAP Flights under management +--- [User] Add another AirWing for AI CAP Flights under management -- @param #AWACS self -- @param Ops.AirWing#AIRWING AirWing The AirWing to (also) obtain CAP flights from +-- @param Core.Zone#ZONE_RADIUS Zone (optional) This AirWing has it's own station zone, AI CAP will be send there -- @return #AWACS self -function AWACS:AddCAPAirWing(AirWing) +function AWACS:AddCAPAirWing(AirWing,Zone) self:T(self.lid.."AddCAPAirWing") if AirWing then AirWing:SetUsingOpsAwacs(self) local distance = self.AOCoordinate:Get2DDistance(AirWing:GetCoordinate()) + if Zone then + -- create AnchorStack + local stackscreated = self.AnchorStacks:GetSize() + if stackscreated == self.AnchorMaxAnchors then + -- only create self.AnchorMaxAnchors Anchors + self:E(self.lid.."Max number of stacks already created!") + else + local AnchorStackOne = {} -- #AWACS.AnchorData + AnchorStackOne.AnchorBaseAngels = self.AnchorBaseAngels + AnchorStackOne.Anchors = FIFO:New() -- Utilities.FiFo#FIFO + AnchorStackOne.AnchorAssignedID = FIFO:New() -- Utilities.FiFo#FIFO + + local newname = Zone:GetName() + + for i=1,self.AnchorMaxStacks do + AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) + end + + local newsubname = AWACS.AnchorNames[stackscreated+1] or tostring(stackscreated+1) + newname = Zone:GetName() .. "-"..newsubname + AnchorStackOne.StationZone = Zone + AnchorStackOne.StationZoneCoordinate = Zone:GetCoordinate() + AnchorStackOne.StationZoneCoordinateText = Zone:GetCoordinate():ToStringLLDDM() + AnchorStackOne.StationName = newname + --push to AnchorStacks + if self.debug then + --self.AnchorStacks:Flush() + AnchorStackOne.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + end + self.AnchorStacks:Push(AnchorStackOne,newname) + AirWing.HasOwnStation = true + AirWing.StationName = newname + end + end self.CAPAirwings:Push(AirWing,distance) end return self @@ -4116,10 +4376,11 @@ function AWACS:_CheckAICAPOnStation() self.CatchAllMissions[#self.CatchAllMissions+1] = mission local availableAWS = self.CAPAirwings:Count() local AWS = self.CAPAirwings:GetDataTable() - -- random - local selectedAW = AWS[math.random(1,availableAWS)] - selectedAW:AddMission(mission) + -- round robin self.AIRequested = self.AIRequested + 1 + --print(((i-1) % divideby)+1) + local selectedAW = AWS[(((self.AIRequested-1) % availableAWS)+1)] + selectedAW:AddMission(mission) self:T("CAP="..capmissions.." ALERT5="..alert5missions.." Requested="..self.AIRequested) end end @@ -4265,7 +4526,7 @@ end -- @param #AWACS.ManagedContact Contact -- @return #AWACS self function AWACS:_TACRangeCall(GID,Contact) - self:I(self.lid.."_TACRangeCall") + self:T(self.lid.."_TACRangeCall") -- AIC: “Enforcer 11, single group, 30 miles.” local pilotcallsign = self:_GetCallSign(nil,GID) local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup @@ -4290,7 +4551,7 @@ end -- @param #AWACS.ManagedContact Contact -- @return #AWACS self function AWACS:_MeldRangeCall(GID,Contact) - self:I(self.lid.."_MeldRangeCall") + self:T(self.lid.."_MeldRangeCall") -- AIC: “Heat 11, single group, BRAA 089/28, 32 thousand, hot, hostile, crow.” local pilotcallsign = self:_GetCallSign(nil,GID) local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup @@ -4318,7 +4579,7 @@ end -- @param #AWACS self -- @return #AWACS self function AWACS:_ThreatRangeCall(GID,Contact) - self:I(self.lid.."_ThreatRangeCall") + self:T(self.lid.."_ThreatRangeCall") -- AIC: “Enforcer 11 12, east group, THREAT, BRAA 260/15, 29 thousand, hot, hostile, robin.” local pilotcallsign = self:_GetCallSign(nil,GID) local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup @@ -4347,7 +4608,7 @@ end -- @param Utilities.FiFo#FIFO Targets FiFo of #AWACS.ManagedContact Targets -- @return #AWACS self function AWACS:_AssignPilotToTarget(Pilots,Targets) - self:I(self.lid.."_AssignPilotToTarget") + self:T(self.lid.."_AssignPilotToTarget") local inreach = false local Pilot = nil -- #AWACS.ManagedGroup @@ -4365,7 +4626,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) local pilotcoord = _Pilot.Group:GetCoordinate() local targetdist = targetgroupcoord:Get2DDistance(pilotcoord) if UTILS.MetersToNM(targetdist) < self.maxassigndistance and targetdist < closest then - self:I(string.format("%sTarget distance %d! Assignment %s!",self.lid,UTILS.Round(UTILS.MetersToNM(targetdist),0),_Pilot.CallSign)) + self:T(string.format("%sTarget distance %d! Assignment %s!",self.lid,UTILS.Round(UTILS.MetersToNM(targetdist),0),_Pilot.CallSign)) inreach = true closest = targetdist Pilot = _Pilot @@ -4373,7 +4634,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) Targets:PullByID(_target.CID) break else - self:I(self.lid .. "Target distance > "..self.maxassigndistance.."NM! No Assignment!") + self:T(self.lid .. "Target distance > "..self.maxassigndistance.."NM! No Assignment!") end end end @@ -4422,15 +4683,15 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) -- Target information local callsign = Pilot.CallSign local FGStatus = Pilot.FlightGroup:GetState() - self:I("Pilot AI Callsign: " .. callsign) - self:I("Pilot FG State: " .. FGStatus) + self:T("Pilot AI Callsign: " .. callsign) + self:T("Pilot FG State: " .. FGStatus) local targetstatus = Target.Target:GetState() - self:I("Target State: " .. targetstatus) + self:T("Target State: " .. targetstatus) -- local currmission = Pilot.FlightGroup:GetMissionCurrent() if currmission then - self:I("Current Mission: " .. currmission:GetType()) + self:T("Current Mission: " .. currmission:GetType()) end -- create one intercept Auftrag and one to return to CAP post this one local ZoneSet = self.ZoneSet @@ -4832,7 +5093,7 @@ function AWACS:_CheckAwacsStatus() local awstatus = AWmission:GetState() if AWmission:IsOver() then -- yup we're dead - self:I(self.lid.."*****AWACS is dead!*****") + self:T(self.lid.."*****AWACS is dead!*****") self.ShiftChangeAwacsFlag = true self:__AwacsShiftChange(2) end @@ -4950,10 +5211,12 @@ end -- @param #string Event -- @param #string To -- @param #number GID Group ID +-- @param #boolean HasOwnStation +-- @param #string HasOwnStation -- @return #AWACS self -function AWACS:onafterAssignAnchor(From, Event, To, GID) +function AWACS:onafterAssignAnchor(From, Event, To, GID, HasOwnStation, StationName) self:T({From, Event, To, "GID = " .. GID}) - self:_AssignAnchorToID(GID) + self:_AssignAnchorToID(GID, HasOwnStation, StationName) return self end @@ -5183,7 +5446,7 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) -- do we have messages queued? local nextcall = 10 - if self.RadioQueue:IsNotEmpty() or self.PrioRadioQueue:IsNotEmpty() then + if (self.RadioQueue:IsNotEmpty() or self.PrioRadioQueue:IsNotEmpty()) and self.clientset:CountAlive() > 0 then local RadioEntry = nil @@ -5388,14 +5651,15 @@ function AWACS:onafterReAnchor(From, Event, To, GID) -- DONE - need to save last known coordinate if managedgroup.LastKnownPosition then + local lastknown = UTILS.DeepCopy(managedgroup.LastKnownPosition) local faded = textoptions[math.random(1,4)] local text = string.format("All stations. %s. %s %s.",self.callsigntxt, faded, savedcallsign) local textScreen = string.format("All stations, %s. %s %s.", self.callsigntxt, faded, savedcallsign) - local brtext = self:ToStringBULLS(managedgroup.LastKnownPosition) - local brtexttts = self:ToStringBULLS(brtext,false,true) + local brtext = self:_ToStringBULLS(lastknown) + local brtexttts = self:_ToStringBULLS(brtext,false,true) if self.PathToGoogleKey then - brtexttts = self:ToStringBULLS(managedgroup.LastKnownPosition,true) + brtexttts = self:_ToStringBULLS(lastknown,true) end text = text .. " "..brtexttts.." miles." textScreen = textScreen .. " "..brtext.." miles." @@ -5441,17 +5705,18 @@ function AWACS:onafterReAnchor(From, Event, To, GID) local faded = textoptions[math.random(1,4)] local text = string.format("All stations. %s. %s %s.",self.callsigntxt, faded, savedcallsign) local textScreen = string.format("All stations, %s. %s %s.", self.callsigntxt, faded, savedcallsign) - - local brtext = self:ToStringBULLS(managedgroup.LastKnownPosition) - local brtexttts = self:ToStringBULLS(brtext,false,true) - if self.PathToGoogleKey then - brtexttts = self:ToStringBULLS(managedgroup.LastKnownPosition,true) + if managedgroup.LastKnownPosition then + local lastknown = UTILS.DeepCopy(managedgroup.LastKnownPosition) + local brtext = self:_ToStringBULLS(lastknown) + local brtexttts = self:_ToStringBULLS(brtext,false,true) + if self.PathToGoogleKey then + brtexttts = self:_ToStringBULLS(lastknown,true) + end + text = text .. " "..brtexttts.." miles." + textScreen = textScreen .. " "..brtext.." miles." + + self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) end - text = text .. " "..brtexttts.." miles." - textScreen = textScreen .. " "..brtext.." miles." - - self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true) - self.ManagedGrps[GID] = nil end end From f49cf43fb130291ddfb78045d384192dbe3dfd49 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 20 May 2022 16:19:51 +0200 Subject: [PATCH 3/7] Awacs - Documentation --- Moose Development/Moose/Ops/Awacs.lua | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index c95236275..9e1e84f9e 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -124,7 +124,9 @@ do --- ---- *Of all men\'s miseries the bitterest is this: to know so much and to have control over nothing.* (Herodotus) +-- === +-- +-- *Of all men\'s miseries the bitterest is this: to know so much and to have control over nothing.* (Herodotus) -- -- === -- @@ -144,13 +146,13 @@ do -- Ensure you have this covered and working before tackling this class. TTS generation can thus be done via the Windows built-in system or via Google TTS; -- the latter offers a wider range of voices and options, but you need to set up your own Google product account for this to work correctly. -- --- ## 2 Mission Design - Zones +-- ## 2 Mission Design - Operational Priorities -- -- Basic operational target of the AWACS is to control a Fighter Engagement Zone, or FEZ, and defend itself. -- -- ## 3 Airwing(s) -- --- The AWACS plane, the optional escort planes, and the AI CAP planes work based on the @(Ops.AirWing) class. Read and understand the manual for this class in +-- The AWACS plane, the optional escort planes, and the AI CAP planes work based on the @{Ops.AirWing} class. Read and understand the manual for this class in -- order to set everything up correctly. You will at least need one Squadron containing the AWACS plane itself. -- -- Set up the AirWing @@ -162,7 +164,7 @@ do -- AwacsAW:SetTakeoffAir() -- AwacsAW:__Start(2) -- --- Add the AWACS template Squadron - **Note**: remove the task AWACS in the mission editor und "Advanced Waypoint Actions" from the template to remove the DCS F10 AWACS menu +-- Add the AWACS template Squadron - **Note**: remove the task AWACS in the mission editor under "Advanced Waypoint Actions" from the template to remove the DCS F10 AWACS menu -- -- local Squad_One = SQUADRON:New("Awacs One",2,"Awacs North") -- Squad_One:AddMissionCapability({AUFTRAG.Type.ORBIT},100) @@ -212,7 +214,7 @@ do -- Your AWACS is an HVT or high-value-target. Thus it makes sense to position the Orbit Zone in a way that your FEZ and thus your CAP flights defend it. -- It should hence be positioned behind the FEZ, away from the direction of enemy engagement. -- The zone for CAP stations should be close to the FEZ, but not inside it. --- The optional additional defense zone can be anywhere, but keep an eye on the location so your CAP flight don't take ages to get there. +-- The optional additional defense zone can be anywhere, but keep an eye on the location so your CAP flights don't take ages to get there. -- The optional border is useful for e.g. "cold war" scenarios - planes across the border will not be considered as targets by AWACS. -- -- ## 5 Set up AWACS @@ -224,17 +226,17 @@ do -- local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM ) -- -- set two escorts -- testawacs:SetEscort(2) --- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a lenght of 25nm. +-- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm. -- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25) -- -- Set up SRS on port 5010 - change the below to your path and port -- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010) --- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "REd Border#ZONE_POLYGON" +-- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON" -- testawacs:SetRejectionZone(ZONE:FindByName("Red Border")) --- -- Our CAP flight will have callsign "Ford", we want 4 AI planes, Time-On-Station 4 hours, doing 300 kn IAS. +-- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS. -- testawacs:SetAICAPDetails(CALLSIGN.Aircraft.Ford,4,4,300) - -- We're modern (default), e.g. we have EPLRS and get more fill-in information on detections +-- -- We're modern (default), e.g. we have EPLRS and get more fill-in information on detections -- testawacs:SetModernEra() - -- And start +-- -- And start -- testawacs:__Start(5) -- -- ## 6 Menu entries @@ -249,7 +251,7 @@ do -- -- ### 6.2 Check-out -- --- You can check-ou anytime, of course. +-- You can check-out anytime, of course. -- -- ### 6.3 Picture -- @@ -280,7 +282,7 @@ do -- Tac Distance = 45 -- Meld Distance = 35 -- Threat Distance = 25 --- Merge Distance =3 +-- Merge Distance = 3 -- -- ## 8 Discussion -- From d169d60d8d46d4c7b12df9435e5f31688d9a504c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 20 May 2022 19:31:11 +0200 Subject: [PATCH 4/7] AWACS BETA 0.1.22 - added some QOL functions, corrected standard TOS for AWACS and CAP, now 4 hours. --- Moose Development/Moose/Ops/Awacs.lua | 157 +++++++++++++++----------- 1 file changed, 91 insertions(+), 66 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 9e1e84f9e..1aa3fd14f 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -8,7 +8,7 @@ -- * Concentrates on threat-based call outs -- * Callouts referenced from: -- ** References from ARN33396 ATP 3-52.4 (Sep 2021) (Combined Forces) --- ** References from CNATRA P-877 (Rev. 12-20) (NAVY) +-- ** References from CNATRA P-877 (Rev 12-20) (NAVY) -- * Many additional events that the mission designer can hook into -- -- === @@ -26,8 +26,8 @@ -- @module Ops.AWACS -- @image OPS_AWACS.jpg + --- --- -- === -- -- **AWACS** - MOOSE based AI AWACS Fighter Engagement Zone Operations for Players and AI @@ -305,8 +305,8 @@ AWACS = { OrbitZone = nil, CallSign = CALLSIGN.AWACS.Magic, -- #number CallSignNo = 1, -- #number - debug = true, - verbose = true, + debug = false, + verbose = false, ManagedGrps = {}, ManagedGrpID = 0, -- #number ManagedTaskID = 0, -- #number @@ -324,9 +324,9 @@ AWACS = { ContactsAO = {}, -- Utilities.FiFo#FIFO RadioQueue = {}, -- Utilities.FiFo#FIFO PrioRadioQueue = {}, -- Utilities.FiFo#FIFO - AwacsTimeOnStation = 1, + AwacsTimeOnStation = 4, AwacsTimeStamp = 0, - EscortsTimeOnStation = 0.5, + EscortsTimeOnStation = 4, EscortsTimeStamp = 0, CAPTimeOnStation = 4, AwacsROE = "", @@ -736,6 +736,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self.UseBullsAO = true -- as per NATOPS self.ControlZoneRadius = 100 -- nm self.StationZone = ZONE:New(StationZone) -- Core.Zone#ZONE + self.StationZoneName = StationZone self.Frequency = Frequency or 271 -- #number self.Modulation = Modulation or radio.modulation.AM self.Airbase = AIRBASE:FindByName(AirbaseName) @@ -769,9 +770,9 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self.callsigntxt = "AWACS" self.maxassigndistance = 100 --nm - self.AwacsTimeOnStation = 2 + self.AwacsTimeOnStation = 4 self.AwacsTimeStamp = 0 - self.EscortsTimeOnStation = 2 + self.EscortsTimeOnStation = 4 self.EscortsTimeStamp = 0 self.ShiftChangeTime = 0.25 -- 15mins self.ShiftChangeAwacsFlag = false @@ -910,15 +911,6 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self:I(text) - -- debug zone markers - if self.debug then - self.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) - local stationtag = string.format("Station: %s\nCoordinate: %s",StationZone,self.StationZone:GetCoordinate():ToStringLLDDM()) - MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToAll() - self.OrbitZone:DrawZone(-1,{0,1,0},1,{0,1,0},0.2,5,true) - MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToAll() - end - -- Events -- Player joins self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) @@ -982,6 +974,18 @@ function AWACS:SetBullsEyeAlias(Name) return self end +--- [User] Set TOS Time-on-Station in Hours +-- @param #AWACS self +-- @param #number AICHours AWACS stays this number of hours on station before shift change, default is 4. +-- @param #number CapHours (optional) CAP stays this number of hours on station before shift change, default is 4. +-- @return #AWACS self +function AWACS:SetTOS(AICHours,CapHours) + self:T(self.lid.."SetTOS") + self.AwacsTimeOnStation = AICHours or 4 + self.CAPTimeOnStation = CapHours or 4 + return self +end + --- [User] Change number of seconds AWACS waits until a Player is re-assigned a different task. Defaults to 180. -- @param #AWACS self -- @param #number Seconds @@ -1301,13 +1305,13 @@ function AWACS:AddGroupToDetection(Group) return self end ---- [User] Set AWACS SRS TTS details - see @{#MSRS} for details +--- [User] Set AWACS SRS TTS details - see @{Sound.SRS} for details -- @param #AWACS self -- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- @param #string Gender Defaults to "male" -- @param #string Culture Defaults to "en-US" -- @param #number Port Defaults to 5002 --- @param #string Voice (Optional) Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. +-- @param #string Voice (Optional) Use a specifc voice with the @{Sound.SRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. -- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS. -- @param #number Volume Volume - between 0.0 (silent) and 1.0 (loudest) -- @param #string PathToGoogleKey Path to your google key if you want to use google TTS @@ -1491,7 +1495,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) self:Started() elseif self.ShiftChangeAwacsRequested and self.AwacsMissionReplacement and self.AwacsMissionReplacement:GetName() == Mission:GetName() then - self:T("Setting up Awacs Replacement") + self:I("Setting up Awacs Replacement") -- manage AWACS Replacement AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false) AwacsFG:SwitchRadio(self.Frequency,self.Modulation) @@ -2308,7 +2312,7 @@ end -- @param Wrapper.Group#GROUP Group Group to use -- @return #AWACS self function AWACS:_ShowAwacsInfo(Group) - self:T(self.lid.."_ShowAwacsInfo") + self:I(self.lid.."_ShowAwacsInfo") local report = REPORT:New("Info") report:Add("====================") report:Add(string.format("AWACS %s",self.callsigntxt)) @@ -2332,7 +2336,7 @@ end -- @param #string Declaration Text declaration the player used -- @return #AWACS self function AWACS:_VID(Group,Declaration) - self:T(self.lid.."_VID") + self:I(self.lid.."_VID") local GID, Outcome, Callsign = self:_GetManagedGrpID(Group) local text = "" @@ -2366,7 +2370,7 @@ function AWACS:_VID(Group,Declaration) distance = UTILS.Round(distance,0) + 1 if distance <= radius or self.debug then -- we can VID - self:T("Contact VID as "..Declaration) + self:I("Contact VID as "..Declaration) -- update cluster.IFF = Declaration task.Status = AWACS.TaskStatus.SUCCESS @@ -2375,12 +2379,12 @@ function AWACS:_VID(Group,Declaration) self.Contacts:PullByID(CID) self.Contacts:Push(cluster,CID) text = string.format("%s. %s. Copy, target identified as %s.",Callsign,self.callsigntxt, Declaration) - self:T(text) + self:I(text) else -- too far away - self:T("Contact VID not close enough") + self:I("Contact VID not close enough") text = string.format("%s. %s. Negative, get closer to target.",Callsign,self.callsigntxt) - self:T(text) + self:I(text) end self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) end @@ -3080,6 +3084,9 @@ function AWACS:_CreateAnchorStack() AnchorStackOne.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + else + local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() end self.AnchorStacks:Push(AnchorStackOne,newname) else @@ -3105,6 +3112,9 @@ function AWACS:_CreateAnchorStack() AnchorStackOne.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + else + local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() end self.AnchorStacks:Push(AnchorStackOne,newname) end @@ -3593,7 +3603,7 @@ end -- @param #AWACS self -- @return #AWACS self function AWACS:_CheckTaskQueue() - self:T(self.lid.."_CheckTaskQueue") + self:I(self.lid.."_CheckTaskQueue") local opentasks = 0 local assignedtasks = 0 @@ -3616,7 +3626,7 @@ function AWACS:_CheckTaskQueue() if self.ManagedTasks:IsNotEmpty() then opentasks = self.ManagedTasks:GetSize() - self:T("Assigned Tasks: " .. opentasks) + self:I("Assigned Tasks: " .. opentasks) local taskstack = self.ManagedTasks:GetPointerStack() for _id,_entry in pairs(taskstack) do local data = _entry -- Utilities.FiFo#FIFO.IDEntry @@ -3624,7 +3634,7 @@ function AWACS:_CheckTaskQueue() local target = entry.Target -- Ops.Target#TARGET local description = entry.ToDo if description == AWACS.TaskDescription.ANCHOR or description == AWACS.TaskDescription.REANCHOR then - self:T("Open Task ANCHOR/REANCHOR") + self:I("Open Task ANCHOR/REANCHOR") -- see if we have reached the anchor zone local managedgroup = self.ManagedGrps[entry.AssignedGroupID] -- #AWACS.ManagedGroup if managedgroup then @@ -3634,7 +3644,7 @@ function AWACS:_CheckTaskQueue() local zone = target:GetObject() -- Core.Zone#ZONE self:T({zone}) if group:IsInZone(zone) then - self:T("Open Task ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) + self:I("Open Task ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID) -- made it target:Stop() -- add group to idle stack @@ -3651,7 +3661,7 @@ function AWACS:_CheckTaskQueue() self.ManagedTasks:PullByID(entry.TID) else --inzone -- not there yet - self:T("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID) + self:I("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID) end else -- group dead, pull task @@ -3665,7 +3675,7 @@ function AWACS:_CheckTaskQueue() elseif description == AWACS.TaskDescription.INTERCEPT then -- DONE - self:T("Open Tasks INTERCEPT") + self:I("Open Tasks INTERCEPT") local taskstatus = entry.Status local targetstatus = entry.Target:GetState() @@ -3691,15 +3701,15 @@ function AWACS:_CheckTaskQueue() distance = grouposition:Get2DDistance(position) distance = UTILS.Round(UTILS.MetersToNM(distance),0) end - self:T("TAC/MELD distance check: "..distance.."NM!") + self:I("TAC/MELD distance check: "..distance.."NM!") if distance <= self.TacDistance and distance >= self.MeldDistance then -- TAC distance - self:T("TAC distance: "..distance.."NM!") + self:I("TAC distance: "..distance.."NM!") local Contact = self.Contacts:ReadByID(entry.Contact.CID) self:_TACRangeCall(entry.AssignedGroupID,Contact) elseif distance <= self.MeldDistance and distance >= self.ThreatDistance then -- MELD distance - self:T("MELD distance: "..distance.."NM!") + self:I("MELD distance: "..distance.."NM!") local Contact = self.Contacts:ReadByID(entry.Contact.CID) self:_MeldRangeCall(entry.AssignedGroupID,Contact) end @@ -3712,7 +3722,7 @@ function AWACS:_CheckTaskQueue() auftragstatus = auftrag:GetState() end local text = string.format("ID=%d | Status=%s | TargetState=%s | AuftragState=%s",entry.TID,taskstatus,targetstatus,auftragstatus) - self:T(text) + self:I(text) if auftrag then if auftrag:IsExecuting() then entry.Status = AWACS.TaskStatus.EXECUTING @@ -3770,7 +3780,7 @@ function AWACS:_CheckTaskQueue() end if entry.Status == AWACS.TaskStatus.SUCCESS then - self:T("Open Tasks INTERCEPT success for GroupID "..entry.AssignedGroupID) + self:I("Open Tasks INTERCEPT success for GroupID "..entry.AssignedGroupID) if managedgroup then self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",true,true,AWACS.TaskStatus.SUCCESS) @@ -3793,7 +3803,7 @@ function AWACS:_CheckTaskQueue() end elseif entry.Status == AWACS.TaskStatus.FAILED then - self:T("Open Tasks INTERCEPT failed for GroupID "..entry.AssignedGroupID) + self:I("Open Tasks INTERCEPT failed for GroupID "..entry.AssignedGroupID) if managedgroup then managedgroup.HasAssignedTask = false self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) @@ -3818,7 +3828,7 @@ function AWACS:_CheckTaskQueue() elseif entry.Status == AWACS.TaskStatus.REQUESTED then -- requested - player tasks only! - self:T("Open Tasks INTERCEPT REQUESTED for GroupID "..entry.AssignedGroupID) + self:I("Open Tasks INTERCEPT REQUESTED for GroupID "..entry.AssignedGroupID) local created = entry.RequestedTimestamp or timer.getTime() - 120 local Tnow = timer.getTime() local Trunning = (Tnow-created) / 60 -- mins @@ -3828,7 +3838,7 @@ function AWACS:_CheckTaskQueue() entry.Status = AWACS.TaskStatus.UNASSIGNED self.ManagedTasks:PullByID(entry.TID) end - self:T(text) + self:I(text) end ---------------------------------------- @@ -3853,7 +3863,7 @@ function AWACS:_CheckTaskQueue() elseif entry.Target:IsAlive() then -- still alive -- out of zones? - self:T("Checking VID target out of bounds") + self:I("Checking VID target out of bounds") local targetpos = entry.Target:GetCoordinate() -- success == out of our controlled zones local outofzones = false @@ -3884,13 +3894,13 @@ function AWACS:_CheckTaskQueue() end if outofzones then entry.Status = AWACS.TaskStatus.SUCCESS - self:T("Out of bounds - SUCCESS") + self:I("Out of bounds - SUCCESS") end end if entry.Status == AWACS.TaskStatus.REQUESTED then -- requested - player tasks only! - self:T("Open Tasks VID REQUESTED for GroupID "..entry.AssignedGroupID) + self:I("Open Tasks VID REQUESTED for GroupID "..entry.AssignedGroupID) local created = entry.RequestedTimestamp or timer.getTime() - 120 local Tnow = timer.getTime() local Trunning = (Tnow-created) / 60 -- mins @@ -3900,9 +3910,9 @@ function AWACS:_CheckTaskQueue() entry.Status = AWACS.TaskStatus.UNASSIGNED self.ManagedTasks:PullByID(entry.TID) end - self:T(text) + self:I(text) elseif entry.Status == AWACS.TaskStatus.ASSIGNED then - self:T("Open Tasks VID ASSIGNED for GroupID "..entry.AssignedGroupID) + self:I("Open Tasks VID ASSIGNED for GroupID "..entry.AssignedGroupID) -- check TAC/MELD ranges local targetgrp = entry.Contact.group local position = entry.Contact.position or entry.Cluster.coordinate @@ -3915,29 +3925,29 @@ function AWACS:_CheckTaskQueue() distance = grouposition:Get2DDistance(position) distance = UTILS.Round(UTILS.MetersToNM(distance),0) end - self:T("TAC/MELD distance check: "..distance.."NM!") + self:I("TAC/MELD distance check: "..distance.."NM!") if distance <= self.TacDistance and distance >= self.MeldDistance then -- TAC distance - self:T("TAC distance: "..distance.."NM!") + self:I("TAC distance: "..distance.."NM!") local Contact = self.Contacts:ReadByID(entry.Contact.CID) self:_TACRangeCall(entry.AssignedGroupID,Contact) elseif distance <= self.MeldDistance and distance >= self.ThreatDistance then -- MELD distance - self:T("MELD distance: "..distance.."NM!") + self:I("MELD distance: "..distance.."NM!") local Contact = self.Contacts:ReadByID(entry.Contact.CID) self:_MeldRangeCall(entry.AssignedGroupID,Contact) end end end elseif entry.Status == AWACS.TaskStatus.SUCCESS then - self:T("Open Tasks VID success for GroupID "..entry.AssignedGroupID) + self:I("Open Tasks VID success for GroupID "..entry.AssignedGroupID) -- outcomes - player ID'd -- target dead or left zones handled above -- target ID'd --> if hostile, assign INTERCEPT TASK self.ManagedTasks:PullByID(entry.TID) local Contact = self.Contacts:ReadByID(entry.Contact.CID) -- #AWACS.ManagedContact if Contact and (Contact.IFF == AWACS.IFF.FRIENDLY or Contact.IFF == AWACS.IFF.NEUTRAL) then - self:T("IFF outcome friendly/neutral for GroupID "..entry.AssignedGroupID) + self:I("IFF outcome friendly/neutral for GroupID "..entry.AssignedGroupID) -- nothing todo, re-anchor if managedgroup then managedgroup.HasAssignedTask = false @@ -3957,7 +3967,7 @@ function AWACS:_CheckTaskQueue() self:__ReAnchor(5,managedgroup.GID) end elseif Contact and Contact.IFF == AWACS.IFF.ENEMY then - self:T("IFF outcome hostile for GroupID "..entry.AssignedGroupID) + self:I("IFF outcome hostile for GroupID "..entry.AssignedGroupID) -- change to intercept --self.ManagedTasks:PullByID(entry.TID) entry.ToDo = AWACS.TaskDescription.INTERCEPT @@ -3968,7 +3978,7 @@ function AWACS:_CheckTaskQueue() local TextTTS = string.format("%s, %s. Engage hostile target!",managedgroup.CallSign,self.callsigntxt) self:_NewRadioEntry(TextTTS,TextTTS,managedgroup.GID,true,self.debug,true,false,true) elseif not Contact then - self:T("IFF outcome target DEAD for GroupID "..entry.AssignedGroupID) + self:I("IFF outcome target DEAD for GroupID "..entry.AssignedGroupID) -- nothing todo, re-anchor if managedgroup then managedgroup.HasAssignedTask = false @@ -3994,7 +4004,7 @@ function AWACS:_CheckTaskQueue() -- outcomes - player unable/abort -- Player dead managed above -- Remove task - self:T("Open Tasks VID failed for GroupID "..entry.AssignedGroupID) + self:I("Open Tasks VID failed for GroupID "..entry.AssignedGroupID) if managedgroup then managedgroup.HasAssignedTask = false self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED) @@ -4081,6 +4091,9 @@ function AWACS:AddCAPAirWing(AirWing,Zone) AnchorStackOne.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() + else + local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToAll() end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation = true @@ -4528,7 +4541,7 @@ end -- @param #AWACS.ManagedContact Contact -- @return #AWACS self function AWACS:_TACRangeCall(GID,Contact) - self:T(self.lid.."_TACRangeCall") + self:I(self.lid.."_TACRangeCall") -- AIC: “Enforcer 11, single group, 30 miles.” local pilotcallsign = self:_GetCallSign(nil,GID) local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup @@ -4553,7 +4566,7 @@ end -- @param #AWACS.ManagedContact Contact -- @return #AWACS self function AWACS:_MeldRangeCall(GID,Contact) - self:T(self.lid.."_MeldRangeCall") + self:I(self.lid.."_MeldRangeCall") -- AIC: “Heat 11, single group, BRAA 089/28, 32 thousand, hot, hostile, crow.” local pilotcallsign = self:_GetCallSign(nil,GID) local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup @@ -4581,7 +4594,7 @@ end -- @param #AWACS self -- @return #AWACS self function AWACS:_ThreatRangeCall(GID,Contact) - self:T(self.lid.."_ThreatRangeCall") + self:I(self.lid.."_ThreatRangeCall") -- AIC: “Enforcer 11 12, east group, THREAT, BRAA 260/15, 29 thousand, hot, hostile, robin.” local pilotcallsign = self:_GetCallSign(nil,GID) local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup @@ -4610,7 +4623,7 @@ end -- @param Utilities.FiFo#FIFO Targets FiFo of #AWACS.ManagedContact Targets -- @return #AWACS self function AWACS:_AssignPilotToTarget(Pilots,Targets) - self:T(self.lid.."_AssignPilotToTarget") + self:I(self.lid.."_AssignPilotToTarget") local inreach = false local Pilot = nil -- #AWACS.ManagedGroup @@ -4628,7 +4641,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) local pilotcoord = _Pilot.Group:GetCoordinate() local targetdist = targetgroupcoord:Get2DDistance(pilotcoord) if UTILS.MetersToNM(targetdist) < self.maxassigndistance and targetdist < closest then - self:T(string.format("%sTarget distance %d! Assignment %s!",self.lid,UTILS.Round(UTILS.MetersToNM(targetdist),0),_Pilot.CallSign)) + self:I(string.format("%sTarget distance %d! Assignment %s!",self.lid,UTILS.Round(UTILS.MetersToNM(targetdist),0),_Pilot.CallSign)) inreach = true closest = targetdist Pilot = _Pilot @@ -4636,7 +4649,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) Targets:PullByID(_target.CID) break else - self:T(self.lid .. "Target distance > "..self.maxassigndistance.."NM! No Assignment!") + self:I(self.lid .. "Target distance > "..self.maxassigndistance.."NM! No Assignment!") end end end @@ -4685,15 +4698,15 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) -- Target information local callsign = Pilot.CallSign local FGStatus = Pilot.FlightGroup:GetState() - self:T("Pilot AI Callsign: " .. callsign) - self:T("Pilot FG State: " .. FGStatus) + self:I("Pilot AI Callsign: " .. callsign) + self:I("Pilot FG State: " .. FGStatus) local targetstatus = Target.Target:GetState() - self:T("Target State: " .. targetstatus) + self:I("Target State: " .. targetstatus) -- local currmission = Pilot.FlightGroup:GetMissionCurrent() if currmission then - self:T("Current Mission: " .. currmission:GetType()) + self:I("Current Mission: " .. currmission:GetType()) end -- create one intercept Auftrag and one to return to CAP post this one local ZoneSet = self.ZoneSet @@ -4834,8 +4847,20 @@ function AWACS:onafterStart(From, Event, To) local AOCoordString = self.AOCoordinate:ToStringLLDDM() local Rocktag = string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) MARKER:New(self.AOCoordinate,Rocktag):ToAll() + self.StationZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) + local stationtag = string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) + MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToAll() + self.OrbitZone:DrawZone(-1,{0,1,0},1,{0,1,0},0.2,5,true) + MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToAll() + else + local AOCoordString = self.AOCoordinate:ToStringLLDDM() + local Rocktag = string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) + MARKER:New(self.AOCoordinate,Rocktag):ToAll() + MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToAll() + local stationtag = string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) + MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToAll() end - + -- set up the AWACS and let it orbit local AwacsAW = self.AirWing -- Ops.AirWing#AIRWING local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) @@ -5095,7 +5120,7 @@ function AWACS:_CheckAwacsStatus() local awstatus = AWmission:GetState() if AWmission:IsOver() then -- yup we're dead - self:T(self.lid.."*****AWACS is dead!*****") + self:I(self.lid.."*****AWACS is dead!*****") self.ShiftChangeAwacsFlag = true self:__AwacsShiftChange(2) end @@ -5111,7 +5136,7 @@ end -- @param #string To -- @return #AWACS self function AWACS:onafterStatus(From, Event, To) - self:T({From, Event, To}) + self:I({From, Event, To}) self:_SetClientMenus() From 61f3b87dae3aadd56de721a767d65c8dbf178e01 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 20 May 2022 20:15:11 +0200 Subject: [PATCH 5/7] Update Airboss.lua - Wind is calculated at 15 m (not 50 m) --- Moose Development/Moose/Ops/Airboss.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index b6602752d..bc0838da5 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -11249,7 +11249,7 @@ end --- Get wind direction and speed at carrier position. -- @param #AIRBOSS self --- @param #number alt Altitude ASL in meters. Default 50 m. +-- @param #number alt Altitude ASL in meters. Default 15 m. -- @param #boolean magnetic Direction including magnetic declination. -- @param Core.Point#COORDINATE coord (Optional) Coordinate at which to get the wind. Default is current carrier position. -- @return #number Direction the wind is blowing **from** in degrees. @@ -11260,7 +11260,7 @@ function AIRBOSS:GetWind( alt, magnetic, coord ) local cv = coord or self:GetCoordinate() -- Wind direction and speed. By default at 50 meters ASL. - local Wdir, Wspeed = cv:GetWind( alt or 50 ) + local Wdir, Wspeed = cv:GetWind( alt or 15 ) -- Include magnetic declination. if magnetic then From 06d509b5ac3f01604a5472a9ac86cfd6d8719824 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 22 May 2022 22:36:43 +0200 Subject: [PATCH 6/7] Update Airboss.lua - Improved Case III entry waypoint --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index bc0838da5..2324577b8 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -6059,7 +6059,7 @@ function AIRBOSS:_MarshalAI( flight, nstack, respawn ) local radial = self:GetRadial( case, false, true ) -- Point in the middle of the race track and a 5 NM more port perpendicular. - p0 = p2:Translate( UTILS.NMToMeters( 5 ), radial + 90 ):Translate( UTILS.NMToMeters( 5 ), radial, true ) + p0 = p2:Translate( UTILS.NMToMeters( 5 ), radial + 90, true ):Translate( UTILS.NMToMeters( 5 ), radial, true ) -- Entering Case II/III marshal pattern waypoint. wp[#wp + 1] = p0:WaypointAirTurningPoint( nil, speedTransit, { TaskArrivedHolding }, "Entering Case II/III Marshal Pattern" ) From 62725b1930ef445ece9b370ed9259fa97796cf2d Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 23 May 2022 23:11:23 +0200 Subject: [PATCH 7/7] OPS **AUFTRAG** - Added *invisible* and *immortal* options **TARGET** - Added `AddResource` function **OPSGROUP** - Added *invisible* and *immortal* options **LEGION** - Fixed bug in properties requirement **COMMANDER** - Added `AddTarget` function (still **WIP**) **ARMYGROUP** - Fixed routing bug after teleporting --- Moose Development/Moose/Ops/ArmyGroup.lua | 15 +- Moose Development/Moose/Ops/Auftrag.lua | 41 ++++- Moose Development/Moose/Ops/Commander.lua | 143 +++++++++++++++++ Moose Development/Moose/Ops/FlightGroup.lua | 8 +- Moose Development/Moose/Ops/Legion.lua | 8 +- Moose Development/Moose/Ops/NavyGroup.lua | 13 +- Moose Development/Moose/Ops/OpsGroup.lua | 160 +++++++++++++++++--- Moose Development/Moose/Ops/OpsZone.lua | 34 ++++- Moose Development/Moose/Ops/Target.lua | 58 +++++++ 9 files changed, 448 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 35ba2606f..7102a7aa0 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -63,7 +63,7 @@ ARMYGROUP = { --- Army Group version. -- @field #string version -ARMYGROUP.version="0.7.3" +ARMYGROUP.version="0.7.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -880,6 +880,12 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- 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() @@ -943,6 +949,9 @@ function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation) 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. @@ -960,7 +969,7 @@ function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation) 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") + 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))) @@ -1117,7 +1126,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) self.speedWp=wp.speed -- Debug output. - if self.verbose>=10 then + if self.verbose>=10 or true then for i,_wp in pairs(waypoints) do local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index a1e46b10b..2361a21d5 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -111,6 +111,9 @@ -- @field #number artyAngle Shooting angle in degrees (for Barrage). -- -- @field #string alert5MissionType Alert 5 mission type. This is the mission type, the alerted assets will be able to carry out. +-- +-- @field #table attributes Generalized attribute(s) of assets. +-- @field #table properties DCS attribute(s) of assets. -- -- @field Ops.Chief#CHIEF chief The CHIEF managing this mission. -- @field Ops.Commander#COMMANDER commander The COMMANDER managing this mission. @@ -168,6 +171,8 @@ -- @field #number optionRTBfuel RTB on out-of-fuel. -- @field #number optionECM ECM. -- @field #boolean optionEmission Emission is on or off. +-- @field #boolean optionInvisible Invisible is on/off. +-- @field #boolean optionImmortal Immortal is on/off. -- -- @extends Core.Fsm#FSM @@ -1763,7 +1768,7 @@ end --- **[GROUND, NAVAL]** Create an ARTY mission. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target Center of the firing solution. --- @param #number Nshots Number of shots to be fired. Default 3. +-- @param #number Nshots Number of shots to be fired. Default `#nil`. -- @param #number Radius Radius of the shells in meters. Default 100 meters. -- @param #number Altitude Altitude in meters. Can be used to setup a Barrage. Default `#nil`. -- @return #AUFTRAG self @@ -2196,7 +2201,9 @@ function AUFTRAG:NewFromTarget(Target, MissionType) elseif MissionType==AUFTRAG.Type.STRIKE then mission=self:NewSTRIKE(Target, Altitude) elseif MissionType==AUFTRAG.Type.ARMORATTACK then - mission=self:NewARMORATTACK(Target,Speed) + mission=self:NewARMORATTACK(Target, Speed) + elseif MissionType==AUFTRAG.Type.GROUNDATTACK then + mission=self:NewGROUNDATTACK(Target, Speed, Formation) else return nil end @@ -2970,6 +2977,36 @@ function AUFTRAG:SetEmission(OnOffSwitch) return self end +--- Set invisibility setting for this mission. +-- @param #AUFTRAG self +-- @param #boolean OnOffSwitch If `true` or `nil`, invisible is on. If `false`, invisible is off. +-- @return #AUFTRAG self +function AUFTRAG:SetInvisible(OnOffSwitch) + + if OnOffSwitch==nil then + self.optionInvisible=true + else + self.optionInvisible=OnOffSwitch + end + + return self +end + +--- Set immortality setting for this mission. +-- @param #AUFTRAG self +-- @param #boolean OnOffSwitch If `true` or `nil`, immortal is on. If `false`, immortal is off. +-- @return #AUFTRAG self +function AUFTRAG:SetImmortal(OnOffSwitch) + + if OnOffSwitch==nil then + self.optionImmortal=true + else + self.optionImmortal=OnOffSwitch + end + + return self +end + --- Set formation for this mission. -- @param #AUFTRAG self -- @param #number Formation Formation. diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 5049e05e9..0ec0e9886 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -24,6 +24,7 @@ -- @field #table legions Table of legions which are commanded. -- @field #table missionqueue Mission queue. -- @field #table transportqueue Transport queue. +-- @field #table targetqueue Target queue. -- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.SupplyZone`. -- @field #table refuellingZones Refuelling zones. Each element is of type `#BRIGADE.SupplyZone`. -- @field #table capZones CAP zones. Each element is of type `#AIRWING.PatrolZone`. @@ -125,6 +126,7 @@ COMMANDER = { legions = {}, missionqueue = {}, transportqueue = {}, + targetqueue = {}, rearmingZones = {}, refuellingZones = {}, capZones = {}, @@ -514,6 +516,55 @@ function COMMANDER:RemoveTransport(Transport) return self end +--- Add target. +-- @param #COMMANDER self +-- @param Ops.Target#TARGET Target Target object to be added. +-- @return #COMMANDER self +function COMMANDER:AddTarget(Target) + + if not self:IsTarget(Target) then + table.insert(self.targetqueue, Target) + end + + return self +end + +--- Check if a TARGET is already in the queue. +-- @param #COMMANDER self +-- @param Ops.Target#TARGET Target Target object to be added. +-- @return #boolean If `true`, target exists in the target queue. +function COMMANDER:IsTarget(Target) + + for _,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + if target.uid==Target.uid or target:GetName()==Target:GetName() then + return true + end + end + + return false +end + +--- Remove target from queue. +-- @param #COMMANDER self +-- @param Ops.Target#TARGET Target The target. +-- @return #COMMANDER self +function COMMANDER:RemoveTarget(Target) + + for i,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + + if target.uid==Target.uid then + self:T(self.lid..string.format("Removing target %s from queue", Target.name)) + table.remove(self.targetqueue, i) + break + end + + end + + return self +end + --- Add a rearming zone. -- @param #COMMANDER self -- @param Core.Zone#ZONE RearmingZone Rearming zone. @@ -789,6 +840,9 @@ function COMMANDER:onafterStatus(From, Event, To) local text=string.format("Status %s: Legions=%d, Missions=%d, Transports", fsmstate, #self.legions, #self.missionqueue, #self.transportqueue) self:T(self.lid..text) end + + -- Check target queue and add missions. + self:CheckTargetQueue() -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() @@ -1148,6 +1202,95 @@ end -- Mission Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Check target queue and assign ONE valid target by adding it to the mission queue of the COMMANDER. +-- @param #COMMANDER self +function COMMANDER:CheckTargetQueue() + + -- Number of missions. + local Ntargets=#self.targetqueue + + -- Treat special cases. + if Ntargets==0 then + return nil + end + + -- Check if total number of missions is reached. + local NoLimit=self:_CheckMissionLimit("Total") + if NoLimit==false then + return nil + end + + -- Sort results table wrt prio and threatlevel. + local function _sort(a, b) + local taskA=a --Ops.Target#TARGET + local taskB=b --Ops.Target#TARGET + return (taskA.priotaskB.threatlevel0) + end + table.sort(self.targetqueue, _sort) + + -- Get the lowest importance value (lower means more important). + -- If a target with importance 1 exists, targets with importance 2 will not be assigned. Targets with no importance (nil) can still be selected. + local vip=math.huge + for _,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + if target:IsAlive() and target.importance and target.importance Creating mission type %s: Nmin=%d, Nmax=%d", target:GetName(), missionType, resource.Nmin, resource.Nmax)) + + -- Create a mission. + local mission=AUFTRAG:NewFromTarget(target, missionType) + + if mission then + mission:SetRequiredAssets(resource.Nmin, resource.Nmax) + mission:SetRequiredAttribute(resource.Attributes) + mission:SetRequiredProperty(resource.Properties) + + resource.mission=mission + + -- Add mission to queue. + self:AddMission(resource.mission) + + end + + end + + end + + end + end + +end + + --- Check mission queue and assign ONE planned mission. -- @param #COMMANDER self function COMMANDER:CheckMissionQueue() diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 50f9116fa..f94433a41 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -183,7 +183,7 @@ FLIGHTGROUP.RadioMessage = { --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="0.7.3" +FLIGHTGROUP.version="0.7.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1549,6 +1549,12 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) -- 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 Formation self:SwitchFormation(self.option.Formation) diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 66888cb66..b0076b4f7 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -2246,7 +2246,7 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, local cohort=_cohort --Ops.Cohort#COHORT if Properties and #Properties>0 then for _,Property in pairs(Properties) do - for _,property in pairs(cohort.properties) do + for property,value in pairs(cohort.properties) do if Property==property then return true end @@ -2277,8 +2277,8 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, else return true end - end - + end + -- Loops over cohorts. for _,_cohort in pairs(Cohorts) do local cohort=_cohort --Ops.Cohort#COHORT @@ -2329,7 +2329,7 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, end -- Debug info. - cohort:T2(cohort.lid..string.format("State=%s: Capable=%s, InRange=%s, Refuel=%s, CanCarry=%s, Category=%s, Attribute=%s, Property=%s, Weapon=%s", + cohort:I(cohort.lid..string.format("State=%s: Capable=%s, InRange=%s, Refuel=%s, CanCarry=%s, Category=%s, Attribute=%s, Property=%s, Weapon=%s", cohort:GetState(), tostring(Capable), tostring(InRange), tostring(Refuel), tostring(CanCarry), tostring(RightCategory), tostring(RightAttribute), tostring(RightProperty), tostring(RightWeapon))) -- Check OnDuty, capable, in range and refueling type (if TANKER). diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index f0d5268e0..f0ca112df 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -90,7 +90,7 @@ NAVYGROUP = { --- NavyGroup version. -- @field #string version -NAVYGROUP.version="0.7.3" +NAVYGROUP.version="0.7.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -987,8 +987,17 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) + -- Set emission. + self:SwitchEmission(self.option.Emission) + -- Set default EPLRS. - self:SwitchEPLRS(self.option.EPLRS) + self:SwitchEPLRS(self.option.EPLRS) + + -- Set default Invisible. + self:SwitchInvisible(self.option.Invisible) + + -- Set default Immortal. + self:SwitchImmortal(self.option.Immortal) -- Set TACAN beacon. self:_SwitchTACAN() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ebbfdf6ef..181c371b0 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -308,6 +308,18 @@ OPSGROUP.TaskType={ -- @field Core.UserFlag#USERFLAG stopflag If flag is set to 1 (=true), the task is stopped. -- @field #number backupROE Rules of engagement that are restored once the task is over. +--- Option data. +-- @type OPSGROUP.Option +-- @field #number ROE Rule of engagement. +-- @field #number ROT Reaction on threat. +-- @field #number Alarm Alarm state. +-- @field #number Formation Formation. +-- @field #boolean EPLRS data link. +-- @field #boolean Disperse Disperse under fire. +-- @field #boolean Emission Emission on/off. +-- @field #boolean Invisible Invisible on/off. +-- @field #boolean Immortal Immortal on/off. + --- Beacon data. -- @type OPSGROUP.Beacon -- @field #number Channel Channel. @@ -329,16 +341,6 @@ OPSGROUP.TaskType={ -- @field #number NumberGroup Group number. First number after name, e.g. "Uzi-**1**-1". -- @field #string NameSquad Name of the squad, e.g. "Uzi". ---- Option data. --- @type OPSGROUP.Option --- @field #number ROE Rule of engagement. --- @field #number ROT Reaction on threat. --- @field #number Alarm Alarm state. --- @field #number Formation Formation. --- @field #boolean EPLRS data link. --- @field #boolean Disperse Disperse under fire. --- @field #boolean Emission Emission on/off. - --- Weapon range data. -- @type OPSGROUP.WeaponData -- @field #number BitType Type of weapon. @@ -469,21 +471,21 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="0.7.8" +OPSGROUP.version="0.7.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: AI on/off. --- TODO: Emission on/off. --- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. -- TODO: Afterburner restrict. -- TODO: What more options? -- TODO: Shot events? -- TODO: Marks to add waypoints/tasks on-the-fly. +-- DONE: Invisible/immortal. +-- DONE: Emission on/off -- DONE: Damage? -- DONE: Options EPLRS @@ -4997,6 +4999,14 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) if Mission.optionEmission then self:SwitchEmission() end + -- Invisible to default. + if Mission.optionInvisible then + self:SwitchInvisible() + end + -- Immortal to default. + if Mission.optionImmortal then + self:SwitchImmortal() + end -- Formation to default. if Mission.optionFormation and self:IsFlightgroup() then self:SwitchFormation() @@ -5103,9 +5113,13 @@ function OPSGROUP:RouteToMission(mission, delay) self:MissionExecute(mission) return end + + if self.speedMax<=3.6 or mission.teleport then + --self:ClearWaypoints() + end -- ID of current waypoint. - local uid=self:GetWaypointCurrent().uid + local uid=self:GetWaypointCurrentUID() -- Ingress waypoint coordinate where the mission is executed. local waypointcoord=nil --Core.Point#COORDINATE @@ -5113,6 +5127,8 @@ function OPSGROUP:RouteToMission(mission, delay) -- Current coordinate of the group. local currentcoord=self:GetCoordinate() + currentcoord:MarkToAll(mission:GetName(),ReadOnly,Text) + -- Road connection. local roadcoord=currentcoord:GetClosestPointToRoad() @@ -5173,6 +5189,7 @@ function OPSGROUP:RouteToMission(mission, delay) -- Random coordinate. waypointcoord=targetzone:GetRandomCoordinate(nil , nil, surfacetypes) + waypointcoord:MarkToAll(mission:GetName(),ReadOnly,Text) elseif mission.type==AUFTRAG.Type.ONGUARD or mission.type==AUFTRAG.Type.ARMOREDGUARD then --- -- Guard @@ -5237,7 +5254,7 @@ function OPSGROUP:RouteToMission(mission, delay) waypointcoord=CarrierCoordinate:Translate(10000, heading-180):SetAltitude(2000) - waypointcoord:MarkToAll("Recoverytanker",ReadOnly,Text) + waypointcoord:MarkToAll("Recoverytanker") else --- @@ -5466,9 +5483,17 @@ function OPSGROUP:_SetMissionOptions(mission) self:SwitchEPLRS(mission.optionEPLRS) end -- Emission - if mission.optionEPLRS then + if mission.optionEmission then self:SwitchEmission(mission.optionEmission) - end + end + -- Invisible + if mission.optionInvisible then + self:SwitchInvisible(mission.optionInvisible) + end + -- Immortal + if mission.optionImmortal then + self:SwitchImmortal(mission.optionImmortal) + end -- Formation if mission.optionFormation and self:IsFlightgroup() then self:SwitchFormation(mission.optionFormation) @@ -6762,6 +6787,10 @@ function OPSGROUP:Teleport(Coordinate, Delay, NoPauseMission) -- Set waypoint in air for flighgroups. if self:IsFlightgroup() then Template.route.points[1]=Coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, 300, true, nil, nil, "Spawnpoint") + elseif self:IsArmygroup() then + Template.route.points[1]=Coordinate:WaypointGround() + elseif self:IsNavygroup() then + Template.route.points[1]=Coordinate:WaypointNaval() end -- Template units. @@ -10790,6 +10819,103 @@ function OPSGROUP:GetEmission() return self.option.Emission or self.optionDefault.Emission end +--- Set the default invisible for the group. +-- @param #OPSGROUP self +-- @param #boolean OnOffSwitch If `true`, group is ivisible by default. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultInvisible(OnOffSwitch) + + if OnOffSwitch==nil then + self.optionDefault.Invisible=true + else + self.optionDefault.Invisible=OnOffSwitch + end + + return self +end + +--- Switch invisibility on or off. +-- @param #OPSGROUP self +-- @param #boolean OnOffSwitch If `true` or `nil`, switch invisibliity on. If `false` invisibility switched off. +-- @return #OPSGROUP self +function OPSGROUP:SwitchInvisible(OnOffSwitch) + + if self:IsAlive() or self:IsInUtero() then + + if OnOffSwitch==nil then + + self.option.Invisible=self.optionDefault.Invisible + + else + + self.option.Invisible=OnOffSwitch + + end + + if self:IsInUtero() then + self:T2(self.lid..string.format("Setting current INVISIBLE=%s when GROUP is SPAWNED", tostring(self.option.Invisible))) + else + + self.group:SetCommandInvisible(self.option.Invisible) + self:T(self.lid..string.format("Setting current INVISIBLE=%s", tostring(self.option.Invisible))) + + end + else + self:E(self.lid.."WARNING: Cannot switch Invisible! Group is not alive") + end + + return self +end + + +--- Set the default immortal for the group. +-- @param #OPSGROUP self +-- @param #boolean OnOffSwitch If `true`, group is immortal by default. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultImmortal(OnOffSwitch) + + if OnOffSwitch==nil then + self.optionDefault.Immortal=true + else + self.optionDefault.Immortal=OnOffSwitch + end + + return self +end + +--- Switch immortality on or off. +-- @param #OPSGROUP self +-- @param #boolean OnOffSwitch If `true` or `nil`, switch immortality on. If `false` immortality switched off. +-- @return #OPSGROUP self +function OPSGROUP:SwitchImmortal(OnOffSwitch) + + if self:IsAlive() or self:IsInUtero() then + + if OnOffSwitch==nil then + + self.option.Immortal=self.optionDefault.Immortal + + else + + self.option.Immortal=OnOffSwitch + + end + + if self:IsInUtero() then + self:T2(self.lid..string.format("Setting current IMMORTAL=%s when GROUP is SPAWNED", tostring(self.option.Immortal))) + else + + self.group:SetCommandImmortal(self.option.Immortal) + self:T(self.lid..string.format("Setting current IMMORTAL=%s", tostring(self.option.Immortal))) + + end + else + self:E(self.lid.."WARNING: Cannot switch Immortal! Group is not alive") + end + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- SETTINGS FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 8e1a79068..ce8f3227b 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -2,8 +2,9 @@ -- -- **Main Features:** -- --- * Monitor if a zone is captured. --- * Monitor if an airbase is captured. +-- * Monitor if a zone is captured +-- * Monitor if an airbase is captured +-- * Define conditions under which zones are captured/held -- -- === -- @@ -79,6 +80,7 @@ OPSZONE.version="0.3.0" -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Capturing based on (total) threat level threshold. Unarmed units do not pose a threat and should not be able to hold a zone. -- TODO: Pause/unpause evaluations. -- TODO: Capture time, i.e. time how long a single coalition has to be inside the zone to capture it. -- TODO: Differentiate between ground attack and boming by air or arty. @@ -348,7 +350,7 @@ end --- Set categories of units that can capture or hold the zone. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). -- @param #OPSZONE self -- @param #table Categories Table of unit categories. Default `{Unit.Category.GROUND_UNIT}`. --- @return #OPSZONE +-- @return #OPSZONE self function OPSZONE:SetUnitCategories(Categories) -- Ensure table. @@ -362,6 +364,32 @@ function OPSZONE:SetUnitCategories(Categories) return self end +--- Set threat level threshold that the defending units must have to hold a zone. +-- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to hold a zone as they do not pose a threat. +-- @param #OPSZONE self +-- @param #number Threatlevel Threat level threshod. Default 0. +-- @return #OPSZONE self +function OPSZONE:SetThreatlevelDefinding(Threatlevel) + + self.threatlevelDefending=Threatlevel or 0 + + return self +end + + +--- Set threat level threshold that the offending units must have to capture a zone. +-- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to capture a zone as they do not pose a threat. +-- @param #OPSZONE self +-- @param #number Threatlevel Threat level threshod. Default 0. +-- @return #OPSZONE self +function OPSZONE:SetThreatlevelOffending(Threatlevel) + + self.threatlevelOffending=Threatlevel or 0 + + return self +end + + --- Set whether *neutral* units can capture the zone. -- @param #OPSZONE self -- @param #boolean CanCapture If `true`, neutral units can. diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index e06e7df43..faba46a41 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -36,6 +36,7 @@ -- @field Ops.Auftrag#AUFTRAG mission Mission attached to this target. -- @field Ops.Intelligence#INTEL.Contact contact Contact attached to this target. -- @field #boolean isDestroyed If true, target objects were destroyed. +-- @field #table resources Resource list. -- @extends Core.Fsm#FSM --- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D Eisenhower @@ -113,6 +114,16 @@ TARGET.ObjectStatus={ ALIVE="Alive", DEAD="Dead", } + +--- Resource. +-- @type TARGET.Resource +-- @field #string MissionType Mission type, e.g. `AUFTRAG.Type.BAI`. +-- @field #number Nmin Min number of assets. +-- @field #number Nmax Max number of assets. +-- @field #table Attributes Generalized attribute, e.g. `{GROUP.Attribute.GROUND_INFANTRY}`. +-- @field #table Properties Properties ([DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes)), e.g. `"Attack helicopters"` or `"Mobile AAA"`. +-- @field Ops.Auftrag#AUFTRAG mission Attached mission. + --- Target object. -- @type TARGET.Object -- @field #number ID Target unique ID. @@ -307,6 +318,53 @@ function TARGET:SetImportance(Importance) return self end +--- Add mission type and number of required assets to resource. +-- @param #TARGET self +-- @param #string MissionType Mission Type. +-- @param #number Nmin Min number of required assets. +-- @param #number Nmax Max number of requried assets. +-- @param #table Attributes Generalized attribute(s). +-- @param #table Properties DCS attribute(s). Default `nil`. +-- @return #TARGET.Resource The resource table. +function TARGET:AddResource(MissionType, Nmin, Nmax, Attributes, Properties) + + -- Ensure table. + if Attributes and type(Attributes)~="table" then + Attributes={Attributes} + end + + -- Ensure table. + if Properties and type(Properties)~="table" then + Properties={Properties} + end + + -- Create new resource table. + local resource={} --#TARGET.Resource + resource.MissionType=MissionType + resource.Nmin=Nmin or 1 + resource.Nmax=Nmax or 1 + resource.Attributes=Attributes or {} + resource.Properties=Properties or {} + + -- Init resource table. + self.resources=self.resources or {} + + -- Add to table. + table.insert(self.resources, resource) + + -- Debug output. + if self.verbose>10 then + local text="Resource:" + for _,_r in pairs(self.resources) do + local r=_r --#TARGET.Resource + text=text..string.format("\nmission=%s, Nmin=%d, Nmax=%d, attribute=%s, properties=%s", r.MissionType, r.Nmin, r.Nmax, tostring(r.Attributes[1]), tostring(r.Properties[1])) + end + self:I(self.lid..text) + end + + return resource +end + --- Check if TARGET is alive. -- @param #TARGET self -- @return #boolean If true, target is alive.