diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 8e1f8324f..067d609fb 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -10,6 +10,7 @@ -- * Send message to all players. -- * Send messages to a coalition. -- * Send messages to a specific group. +-- * Send messages to a specific unit or client. -- -- === -- @@ -35,6 +36,7 @@ -- -- * To a @{Client} using @{#MESSAGE.ToClient}(). -- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}() +-- * To a @{Wrapper.Unit} using @{#MESSAGE.ToUnit}() -- * To a coalition using @{#MESSAGE.ToCoalition}(). -- * To the red coalition using @{#MESSAGE.ToRed}(). -- * To the blue coalition using @{#MESSAGE.ToBlue}(). @@ -199,11 +201,14 @@ function MESSAGE:ToClient( Client, Settings ) self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageCategory = "" -- self.MessageType .. ": " end - + + local Unit = Client:GetClient() + if self.MessageDuration ~= 0 then local ClientGroupID = Client:GetClientGroupID() self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) + --trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) + trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) end end @@ -233,6 +238,31 @@ function MESSAGE:ToGroup( Group, Settings ) return self end + +--- Sends a MESSAGE to a Unit. +-- @param #MESSAGE self +-- @param Wrapper.Unit#UNIT Unit to which the message is displayed. +-- @return #MESSAGE Message object. +function MESSAGE:ToUnit( Unit, Settings ) + self:F( Unit.IdentifiableName ) + + if Unit then + + if self.MessageType then + local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS + self.MessageDuration = Settings:GetMessageTime( self.MessageType ) + self.MessageCategory = "" -- self.MessageType .. ": " + end + + if self.MessageDuration ~= 0 then + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) + end + end + + return self +end + --- Sends a MESSAGE to the Blue coalition. -- @param #MESSAGE self -- @return #MESSAGE diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 053c86560..d3df9c245 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2768,9 +2768,10 @@ do -- COORDINATE --- Create a BRAA NATO call string to this COORDINATE from the FromCOORDINATE. Note - BRA delivered if no aspect can be obtained and "Merged" if range < 3nm -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. + -- @param #boolean Bogey Add "Bogey" at the end if true (not yet declared hostile or friendly) -- @param #boolean Spades Add "Spades" at the end if true (no IFF/VID ID yet known) -- @return #string The BRAA text. - function COORDINATE:ToStringBRAANATO(FromCoordinate,Spades) + function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades) -- Thanks to @Pikey local BRAANATO = "Merged." @@ -2796,8 +2797,12 @@ do -- COORDINATE else BRAANATO = string.format("BRAA, %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track) end - if Spades then - BRAANATO = BRAANATO..", Spades." + if Bogey and Spades then + BRAANATO = BRAANATO..", Bogey, Spades." + elseif Bogey then + BRAANATO = BRAANATO..", Bogey." + elseif Spades then + BRAANATO = BRAANATO..", Spades." else BRAANATO = BRAANATO.."." end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index e5c77f044..9e1560431 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1085,16 +1085,22 @@ do -- SET_GROUP -- Note that for each unit in the group that is set, a default cargo bay limit is initialized. -- @param Core.Set#SET_GROUP self -- @param Wrapper.Group#GROUP group The group which should be added to the set. + -- @param #boolean DontSetCargoBayLimit If true, do not attempt to auto-add the cargo bay limit per unit in this group. -- @return Core.Set#SET_GROUP self - function SET_GROUP:AddGroup( group ) + function SET_GROUP:AddGroup( group, DontSetCargoBayLimit ) self:Add( group:GetName(), group ) - - -- I set the default cargo bay weight limit each time a new group is added to the set. - for UnitID, UnitData in pairs( group:GetUnits() ) do - UnitData:SetCargoBayWeightLimit() + + if not DontSetCargoBayLimit then + -- I set the default cargo bay weight limit each time a new group is added to the set. + -- TODO Why is this here in the first place? + for UnitID, UnitData in pairs( group:GetUnits() ) do + if UnitData and UnitData:IsAlive() then + UnitData:SetCargoBayWeightLimit() + end + end end - + return self end diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 255672be1..53b5ebba8 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -3010,7 +3010,7 @@ function RANGE:_CheckInZone(_unitName) Straferesult.rangename=self.rangename -- Save trap sheet. - if playerData.targeton and self.targetsheet then + if playerData and playerData.targeton and self.targetsheet then self:_SaveTargetSheet(_playername, result) end --RangeBoss edit for strafe data saved to file diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index e81fde9af..38e80ead8 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -100,6 +100,7 @@ __Moose.Include( 'Scripts/Moose/Ops/OpsZone.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Chief.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Flotilla.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Fleet.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Awacs.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 967f54e61..b6602752d 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -5713,7 +5713,7 @@ function AIRBOSS:_ScanCarrierZone() local putintomarshal = false -- Get flight group. - local flight = _DATABASE:GetFlightGroup( groupname ) + local flight = _DATABASE:GetOpsGroup( groupname ) if flight and flight:IsInbound() and flight.destbase:GetName() == self.carrier:GetName() then if flight.ishelo then diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index e6a5bb728..cb533ac77 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -1,25 +1,30 @@ --- **Ops** - AWACS -- -- === --- --- ## Main Features: --- --- * TBD --- +-- +-- **AWACS** - MOOSE based AI AWACS Fighter Engagement Zone Operations for Players and AI +-- -- === -- -- ## Example Missions: -- --- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/). --- +-- ### Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/). +-- +-- === +-- +-- ** Main Features: ** +-- +-- * WIP +-- * References from ARN33396 ATP 3-52.4 (Sep 2021) +-- * References from CNATRA P-877 (Rev. 12-20) +-- -- === -- -- ### Author: **applevangelist** -- Last Update April 2022 -- --- === -- @module Ops.AWACS --- @image MOOSE.JPG +-- @image OPS_AWACS.jpg do --- Ops AWACS Class @@ -28,9 +33,9 @@ do -- @field #string version Versioning. -- @field #string lid LID for log entries. -- @field #number coalition Colition side. --- @field #string coalitiontxt = "blue" +-- @field #string coalitiontxt e.g."blue" -- @field Core.Zone#ZONE OpsZone, --- @field Core.Zone#ZONE AnchorZone, +-- @field Core.Zone#ZONE StationZone, -- @field Core.Zone#ZONE BorderZone, -- @field #number Frequency -- @field #number Modulation @@ -85,10 +90,18 @@ do -- @field Utilities.FiFo#FIFO FlightGroups -- @field #number PictureInterval Interval in seconds for general picture -- @field #number PictureTimeStamp Interval timestamp +-- @field #number maxassigndistance Only assing AI/Pilots to targets max this far away +-- @field #boolean PlayerGuidance -- if true additional callouts to guide/warn players +-- @field #boolean ModernEra -- if true we get more intel on targets, and EPLR on the AIC +-- @field #boolean callsignshort -- if true use short (group) callsigns, e.g. "Ghost 1", else "Ghost 1 1" -- @extends Core.Fsm#FSM --- +--- *Of all men\'s miseries the bitterest is this: to know so much and to have control over nothing.* (Herodotus) -- +-- === +-- +-- -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string @@ -97,7 +110,7 @@ AWACS = { coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string OpsZone = nil, - AnchorZone = nil, + StationZone = nil, AirWing = nil, Frequency = 271, -- #number Modulation = radio.modulation.AM, -- #number @@ -151,6 +164,10 @@ AWACS = { PictureInterval = 300, PictureTimeStamp = 0, BorderZone = nil, + maxassigndistance = 80, + PlayerGuidance = true, + ModernEra = true, + callsignshort = true, } --- @@ -313,7 +330,7 @@ AWACS.THREATLEVEL = { -- @field Ops.Target#TARGET Target -- @field #number LinkedTask --> TID -- @field #number LinkedGroup --> GID --- @field #string Status - AWACS.TaskStatus.... +-- @field #string Status - #AWACS.TaskStatus -- @field #string TargetGroupNaming -- @field #string EngagementTag @@ -365,11 +382,13 @@ AWACS.TaskStatus = { --- -- @type AWACS.AnchorData -- @field #number AnchorBaseAngels --- @field Core.Zone#ZONE_RADIUS AnchorZone --- @field Core.Point#COORDINATE AnchorZoneCoordinate --- @field #string AnchorZoneCoordinateText +-- @field Core.Zone#ZONE_RADIUS StationZone +-- @field Core.Point#COORDINATE StationZoneCoordinate +-- @field #string StationZoneCoordinateText +-- @field #string StationName -- @field Utilities.FiFo#FIFO AnchorAssignedID FiFo of #AWACS.AnchorAssignedEntry -- @field Utilities.FiFo#FIFO Anchors FiFo of available stacks +-- @field Wrapper.Marker#MARKER AnchorMarker Tag for this station --- --@type RadioEntry @@ -383,21 +402,21 @@ AWACS.TaskStatus = { --@field #boolean FromAI ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO-List 0.0.9 +-- TODO-List 0.0.10 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- -- TODO - System for Players to VID contacts? And put data into contacst fifo --- TODO - TripWire +-- TODO - TripWire - Threat (35nm), Meld (45nm, on mission), Merged (<3nm) -- --- TODO - (LOW) LotATC / IFF -- TODO - Player tasking --- TODO - AI Tasking --- TODO - Missile launch callout -- TODO - Localization --- --- TODO - Event detection, Player joining, eject, crash, dead, leaving; AI shot -> DEFEND --- TODO - Optimizer --- +-- TODO - (LOW) LotATC / IFF +-- +-- TODO - SW Optimizer +-- +-- DEBUG - (WIP) Missile launch callout +-- DEBUG - Event detection, Player joining, eject, crash, dead, leaving; AI shot -> DEFEND +-- DEBUG - AI Tasking -- DEBUG - Multiple AIRWING connection? Can't really get recruit to work, switched to random round robin -- DEBUG - Shift Change, Change on asset RTB or dead or mission done (done for AWACS and Escorts) -- @@ -430,12 +449,13 @@ AWACS.TaskStatus = { -- @param #number Coalition Coalition, e.g. coalition.side.BLUE. Can also be passed as "blue", "red" or "neutral". -- @param #string AirbaseName Name of the home airbase. -- @param #string AwacsOrbit Name of the round, mission editor created zone where this AWACS orbits. --- @param #string OpsZone Name of the round, mission editor created operations zone this AWACS controls. Can be passed as #ZONE_POLYGON --- @param #string AnchorZone Name of the round, mission editor created anchor zone where CAP groups will be stacked. +-- @param #string OpsZone Name of the round, mission editor created Fighter Engagement operations zone (FEZ) this AWACS controls. Can be passed as #ZONE_POLYGON. +-- Will be used in referenc call, ensure a radio friendly name that does not collide with NATOPS keywords. +-- @param #string StationZone Name of the round, mission editor created anchor zone where CAP groups will be stationed. Usually a short city name. -- @param #number Frequency Radio frequency, e.g. 271. -- @param #number Modulation Radio modulation, e.g. radio.modulation.AM or radio.modulation.FM. -- @return #AWACS self -function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZone,Frequency,Modulation) +function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,StationZone,Frequency,Modulation) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) @@ -469,7 +489,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.AwacsFG = nil --self.AwacsPayload = PayLoad -- Ops.AirWing#AIRWING.Payload - self.ModernEra = true -- use of EPLRS + --self.ModernEra = true -- use of EPLRS self.RadarBlur = 10 -- 10% detection precision i.e. 90-110 reported group size if type(OpsZone) == "string" then self.OpsZone = ZONE:New(OpsZone) -- Core.Zone#ZONE @@ -481,16 +501,17 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ end self.AOCoordinate = self.OpsZone:GetCoordinate() + self.AOName = self.OpsZone:GetName() self.UseBullsAO = false -- as per NATOPS self.ControlZoneRadius = 100 -- nm - self.AnchorZone = ZONE:New(AnchorZone) -- Core.Zone#ZONE + self.StationZone = ZONE:New(StationZone) -- Core.Zone#ZONE self.Frequency = Frequency or 271 -- #number self.Modulation = Modulation or radio.modulation.AM self.Airbase = AIRBASE:FindByName(AirbaseName) self.AwacsAngels = 25 -- orbit at 25'000 ft self.OrbitZone = ZONE:New(AwacsOrbit) -- Core.Zone#ZONE self.BorderZone = nil - self.CallSign = CALLSIGN.AWACS.Magic -- #number + self.CallSign = CALLSIGN.AWACS.Darkstar -- #number self.CallSignNo = 1 -- #number self.NoHelos = true self.MaxAIonCAP = 4 @@ -514,6 +535,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ self.invisible = false self.immortal = false self.callsigntxt = "AWACS" + self.maxassigndistance = 80 --nm self.AwacsTimeOnStation = 2 self.AwacsTimeStamp = 0 @@ -554,12 +576,14 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ -- Client SET self.clientset = SET_GROUP:New():FilterCategoryAirplane():FilterCoalitions(self.coalitiontxt):FilterStart() + self.PlayerGuidance = true + self.ModernEra = true -- managed groups self.ManagedGrps = {} -- #table of #AWACS.ManagedGroup entries self.ManagedGrpID = 0 - self.AICAPCAllName = CALLSIGN.Aircraft.Colt + self.AICAPCAllName = CALLSIGN.Aircraft.Dodge self.AICAPCAllNumber = 0 -- Anchor stacks init @@ -647,13 +671,28 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZ -- debug zone markers if self.debug then self.OpsZone:DrawZone(-1,{1,0,0},1,{1,0,0},0.2,5,true) - MARKER:New(self.OpsZone:GetCoordinate(),"AO Zone"):ToAll() - self.AnchorZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) - MARKER:New(self.AnchorZone:GetCoordinate(),"Anchor Zone"):ToAll() + local Rocktag = string.format("FEZ: %s\nCoordinate: %s",self.AOName,self.OpsZone:GetCoordinate():ToStringLLDDM()) + MARKER:New(self.OpsZone:GetCoordinate(),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",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(),"Orbit Zone"):ToAll() + MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToAll() end + -- Events + -- Player joins + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + -- Player leaves + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:HandleEvent(EVENTS.Ejection, self._EventHandler) + self:HandleEvent(EVENTS.Crash, self._EventHandler) + self:HandleEvent(EVENTS.Dead, self._EventHandler) + self:HandleEvent(EVENTS.PilotDead, self._EventHandler) + -- Missile warning + self:HandleEvent(EVENTS.Shot, self._EventHandler) + return self end @@ -662,6 +701,153 @@ end -- Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- [Internal] Event handler +-- @param #AWACS self +-- @param Wrapper.Group#GROUP Group Group, can also be passed as #string group name +-- @return #boolean found +-- @return #number GID +-- @return #string CallSign +function AWACS:_GetGIDFromGroupOrName(Group) + local GID = 0 + local Outcome = false + local CallSign = "Ghost 1" + local nametocheck = CallSign + if Group and type(Group) == "string" then + nametocheck = Group + elseif Group and Group:IsInstanceOf("GROUP") then + nametocheck = Group:GetName() + else + return false, 0, CallSign + end + + local managedgrps = self.ManagedGrps or {} + for _,_managed in pairs (managedgrps) do + local managed = _managed -- #AWACS.ManagedGroup + if managed.GroupName == nametocheck then + GID = managed.GID + Outcome = true + CallSign = managed.CallSign + end + end + return Outcome, GID, CallSign +end + +--- [Internal] Event handler +-- @param #AWACS self +-- @param Core.Event#EVENTDATA EventData +-- @return #AWACS self +function AWACS:_EventHandler(EventData) + self:I(self.lid.."_EventHandler") + self:T({Event = EventData.id}) + + local Event = EventData -- Core.Event#EVENTDATA + + if Event.id == EVENTS.PlayerEnterAircraft or Event.id == EVENTS.PlayerEnterUnit then --player entered unit + --self:I("Player enter unit: " .. Event.IniPlayerName) + --self:I("Coalition = " .. UTILS.GetCoalitionName(Event.IniCoalition)) + if Event.IniCoalition == self.coalition then + self:_SetClientMenus() + end + end + + if Event.id == EVENTS.PlayerLeaveUnit then --player left unit + -- check known player? + --self:I("Player group left unit: " .. Event.IniGroupName) + --self:I("Player name left: " .. Event.IniPlayerName) + --self:I("Coalition = " .. UTILS.GetCoalitionName(Event.IniCoalition)) + if Event.IniCoalition == self.coalition then + local Outcome, GID, CallSign = self:_GetGIDFromGroupOrName(Event.IniGroupName) + if Outcome and GID > 0 then + self:_CheckOut(nil,GID,true) + end + end + end + + if Event.id == EVENTS.Ejection or Event.id == EVENTS.Crash or Event.id == EVENTS.Dead or Event.id == EVENTS.PilotDead then --unit or player dead + -- check known group? + if Event.IniCoalition == self.coalition then + --self:I("Ejection/Crash/Dead/PilotDead Group: " .. Event.IniGroupName) + --self:I("Coalition = " .. UTILS.GetCoalitionName(Event.IniCoalition)) + local Outcome, GID, CallSign = self:_GetGIDFromGroupOrName(Event.IniGroupName) + if Outcome and GID > 0 then + self:_CheckOut(nil,GID,true) + end + end + end + + if Event.id == EVENTS.Shot and self.PlayerGuidance then + if Event.IniCoalition ~= self.coalition then + self:I("Shot from: " .. Event.IniGroupName) + local position = Event.IniGroup:GetCoordinate() + --self:I("Coalition = " .. UTILS.GetCoalitionName(Event.IniCoalition)) + -- Check missile type + local Category = Event.WeaponCategory + local WeaponDesc = EventData.Weapon:getDesc() -- https://wiki.hoggitworld.com/view/DCS_enum_weapon + self:I({WeaponDesc}) + --self:I("Weapon = " .. tostring(WeaponDesc.displayName)) + if WeaponDesc.category == 1 and (WeaponDesc.missileCategory == 1 or WeaponDesc.missileCategory == 2) then + self:I("AAM or SAM Missile fired") + -- Missile fired + -- WIP Missile Callouts + local warndist = 25 + local Type = "SAM" + if WeaponDesc.category == 1 then + Type = "Missile" + -- AAM + local guidance = WeaponDesc.guidance -- IR=2, Radar Active=3, Radar Semi Active=4, Radar Passive = 5 + if guidance == 2 then + warndist = 10 + elseif guidance == 3 then + warndist = 25 + elseif guidance == 4 then + warndist = 15 + elseif guidance == 5 then + warndist = 10 + end -- guidance + end -- cat 1 + self:_MissileWarning(position,Type,warndist) + end -- cat 1 or 2 + + end -- end coalition + end -- end shot + + return self +end + +--- [Internal] Missile Warning Callout +-- @param #AWACS self +-- @param Core.Point#COORDINATE Coordinate Where the shot happened +-- @param #string Type Type to call out, e.i. "SAM" or "Missile" +-- @param #number Warndist Distance in NM to find friendly planes +-- @return #AWACS self +function AWACS:_MissileWarning(Coordinate,Type,Warndist) + self:I(self.lid.."_MissileWarning Type="..Type.." WarnDist="..Warndist) + local shotzone = ZONE_RADIUS:New("WarningZone",Coordinate:GetVec2(),UTILS.NMToMeters(Warndist)) + local targetgrpset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryAirplane():FilterActive():FilterZones({shotzone}):FilterOnce() + if targetgrpset:Count() > 0 then + local targets = targetgrpset:GetSetObjects() + for _,_grp in pairs (targets) do + -- TODO - player callouts only + if _grp and _grp:IsAlive() then + local isPlayer = _grp:GetUnit(1):IsPlayer() + if self.debug or isPlayer then + local callsign = self:_GetCallSign(_grp) + local text = string.format("%s, %s! %s! %s! Defend!",callsign,Type,Type,Type) + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.TextScreen = text + RadioEntry.GroupID = 0 + RadioEntry.ToScreen = self.debug + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 + self.RadioQueue:Push(RadioEntry) + end + end + end + end + return self +end + --- [User] Get AWACS Name -- @param #AWACS self -- @return #string Name of this instance @@ -765,13 +951,13 @@ function AWACS:_MessageVector(GID,Tag,Coordinate,Angels) if managedgroup and Coordinate then - local tocallsign = managedgroup.CallSign or "Ghost 1 1" + local tocallsign = managedgroup.CallSign or "Ghost 1" local group = managedgroup.Group local groupposition = group:GetCoordinate() local BRtext = Coordinate:ToStringBR(groupposition) - local text = string.format("%s, %s. Vector%s. %s",tocallsign, self.callsigntxt,Tag,BRtext) + local text = string.format("%s, %s. Vector%s %s",tocallsign, self.callsigntxt,Tag,BRtext) local textScreen = string.format("%s, %s, Vector%s %s",tocallsign, self.callsigntxt,Tag,BRtext) if Angels then @@ -858,7 +1044,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil,"AWACS") --self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) self.callsigntxt = string.format("%s",AWACS.CallSignClear[self.CallSign]) - --local text = string.format("%s starting for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") + --local text = string.format("%s starting for AO %s control.",self.callsigntxt,self.AOName or "AO") local text = string.format("%s. All stations, SUNRISE SUNRISE SUNRISE, %s.",self.callsigntxt,self.callsigntxt) self:T(self.lid..text) @@ -910,7 +1096,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) --self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo) self.callsigntxt = string.format("%s",AWACS.CallSignClear[self.CallSign]) - local text = string.format("%s shift change for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") + local text = string.format("%s shift change for %s control.",self.callsigntxt,self.AOName or "Rock") self:T(self.lid..text) AwacsFG:RadioTransmission(text,1,false) @@ -933,12 +1119,13 @@ function AWACS:_StartSettings(FlightGroup,Mission) return self end ---- [Internal] Return Bullseye BR for Alpha Check etc, returns e.g. "Bullseye 021, 16" +--- [Internal] Return Bullseye BR for Alpha Check etc, returns e.g. "Rock 021, 16" ("Rock" being the set BE name) -- @param #AWACS self -- @param Core.Point#COORDINATE Coordinate -- @return #string BullseyeBR function AWACS:ToStringBULLS( Coordinate ) -- local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.coalition ) ) + local bullseyename = self.AOName or "Rock" local BullsCoordinate = self.OpsZone:GetCoordinate() local DirectionVec3 = BullsCoordinate:GetDirectionVec3( Coordinate ) local AngleRadians = Coordinate:GetAngleRadians( DirectionVec3 ) @@ -946,10 +1133,10 @@ function AWACS:ToStringBULLS( Coordinate ) local AngleDegrees = UTILS.Round( UTILS.ToDegree( AngleRadians ), 0 ) local Bearing = string.format( '%03d', AngleDegrees ) local Distance = UTILS.Round( UTILS.MetersToNM( Distance ), 0 ) - return string.format("Bullseye %03d, %03d",Bearing,Distance) + return string.format("%s %03d, %03d",bullseyename,Bearing,Distance) end ---- [Internal] Chnage Bullseye string to be TTS friendly, "Bullseye 021, 16" returns e.g. "Bulls eye 0 2 1. 1 6" +--- [Internal] Change Bullseye string to be TTS friendly, "Bullseye 021, 16" returns e.g. "Bulls eye 0 2 1. 1 6" -- @param #AWACS self -- @param #string Text Input text -- @return #string BullseyeBRTTS @@ -957,7 +1144,7 @@ function AWACS:ToStringBullsTTS(Text) local text = Text text=string.gsub(text,"Bullseye","Bulls eye") text=string.gsub(text,"%d","%1 ") - text=string.gsub(text,".," ,"\.") + text=string.gsub(text," ," ,".") text=string.gsub(text," $","") return text end @@ -977,7 +1164,7 @@ function AWACS:_GetManagedGrpID(Group) self:T(self.lid.."_GetManagedGrpID for "..Group:GetName()) local GID = 0 local Outcome = false - local CallSign = "Ghost 1 1" + local CallSign = "Ghost 1" local nametocheck = Group:GetName() local managedgrps = self.ManagedGrps or {} for _,_managed in pairs (managedgrps) do @@ -1005,13 +1192,19 @@ function AWACS:_GetCallSign(Group,GID) return managedgroup.CallSign end - local callsign = "" - local shortcallsign = Group:GetCallsign() or "unknown11"-- e.g.Uzi11, but we want Uzi 1 1 - local callnumber = string.match(shortcallsign, "(%d+)$" ) or "unknown11" - local callnumbermajor = string.char(string.byte(callnumber,1)) - local callnumberminor = string.char(string.byte(callnumber,2)) - callsign = string.gsub(shortcallsign,callnumber,"").." "..callnumbermajor.." "..callnumberminor - self:T("Generated Callsign for TTS = " .. callsign) + local callsign = "Ghost 1" + if Group and Group:IsAlive() then + local shortcallsign = Group:GetCallsign() or "unknown11"-- e.g.Uzi11, but we want Uzi 1 1 + local callnumber = string.match(shortcallsign, "(%d+)$" ) or "unknown11" + local callnumbermajor = string.char(string.byte(callnumber,1)) + local callnumberminor = string.char(string.byte(callnumber,2)) + if self.callsignshort then + callsign = string.gsub(shortcallsign,callnumber,"").." "..callnumbermajor + else + callsign = string.gsub(shortcallsign,callnumber,"").." "..callnumbermajor.." "..callnumberminor + end + self:I("Generated Callsign for TTS = " .. callsign) + end return callsign end @@ -1150,7 +1343,7 @@ function AWACS:_TargetSelectionProcess(Untargeted) distance = self.OpsZone:Get2DDistance(contactcoord) end if contactcoord and self.OpsZone:IsVec2InZone(contactcoord:GetVec2()) then - -- TODO prefer heavy groups + -- DONE prefer heavy groups local groupsize = contact.Contact.group:CountAliveUnits() local threatlevel = contact.Contact.group:GetThreatLevel() if groupsize >= 3 then @@ -1179,7 +1372,7 @@ function AWACS:_TargetSelectionProcess(Untargeted) contactcoord.Heading = contact.Contact.group:GetHeading() end -- end heading if contactcoord:ToStringAspect(self.ControlZone:GetCoordinate()) == "Hot" then - -- TODO prefer heavy groups + -- DONE prefer heavy groups local groupsize = contact.Contact.group:CountAliveUnits() local threatlevel = contact.Contact.group:GetThreatLevel() if groupsize >= 3 then @@ -1206,7 +1399,7 @@ function AWACS:_TargetSelectionProcess(Untargeted) end --local distance = self.BorderZone:Get2DDistance(contactcoord) if contactcoord and self.BorderZone:IsVec2InZone(contactcoord:GetVec2()) then - -- TODO prefer heavy groups + -- DONE prefer heavy groups local groupsize = contact.Contact.group:CountAliveUnits() local threatlevel = contact.Contact.group:GetThreatLevel() if groupsize >= 3 then @@ -1242,7 +1435,7 @@ function AWACS:_TargetSelectionProcess(Untargeted) end sortedtargets:Clear() - --TODO - sort table - but max 3 anyway? + --DONE - sort table - but max 3 anyway? if targettable:Count() > 0 then HaveTargets = true @@ -1437,7 +1630,7 @@ function AWACS:_Picture(Group,IsGeneral) if IsGeneral then gcallsign = "All Stations" --else - --gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" + --gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1" end if not self.intel then @@ -1600,11 +1793,11 @@ function AWACS:_BogeyDope(Group) local text = "" local textScreen = "" local GID, Outcome = self:_GetManagedGrpID(Group) - local gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" + local gcallsign = self:_GetCallSign(Group,GID) or "Ghost 1" if not self.intel then -- no intel yet! - text = string.format("%s. %s. Clear.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Clear.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1653,7 +1846,7 @@ function AWACS:_BogeyDope(Group) if contactsAO == 0 then -- clear - text = string.format("%s. %s. Clear.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Clear.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) textScreen = text local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1668,7 +1861,7 @@ function AWACS:_BogeyDope(Group) else if contactsAO > 0 then - text = string.format("%s. %s. Bogey Dope. ",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Bogey Dope. ",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) if contactsAO == 1 then text = text .. "One group. " textScreen = text .. "\n" @@ -1686,13 +1879,13 @@ function AWACS:_BogeyDope(Group) RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 self.RadioQueue:Push(RadioEntry) - self:_CreateBogeyDope(self:_GetCallSign(Group,GID) or "Ghost 1 1",GID) + self:_CreateBogeyDope(self:_GetCallSign(Group,GID) or "Ghost 1",GID) 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 1", self.callsigntxt) + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true RadioEntry.TextTTS = text @@ -1797,7 +1990,7 @@ function AWACS:_Declare(Group) -- elseif self.AwacsFG then -- no, unknown - text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -1828,7 +2021,7 @@ function AWACS:_Commit(Group) --]] elseif self.AwacsFG then -- no, unknown - text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1860,7 +2053,7 @@ function AWACS:_Judy(Group) --]] elseif self.AwacsFG then -- no, unknown - text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1892,7 +2085,7 @@ function AWACS:_Unable(Group) --]] elseif self.AwacsFG then -- no, unknown - text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1924,7 +2117,7 @@ function AWACS:_TaskAbort(Group) --]] elseif self.AwacsFG then -- no, unknown - text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -1989,7 +2182,7 @@ function AWACS:_Showtask(Group) elseif self.AwacsFG then -- no, unknown - text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -2021,7 +2214,7 @@ function AWACS:_CheckIn(Group) managedgroup.GroupName = Group:GetName() managedgroup.IsPlayer = true managedgroup.IsAI = false - managedgroup.CallSign = self:_GetCallSign(Group,GID) or "Ghost 1 1" + managedgroup.CallSign = self:_GetCallSign(Group,GID) or "Ghost 1" managedgroup.CurrentAuftrag = 0 managedgroup.HasAssignedTask = false managedgroup.GID = self.ManagedGrpID @@ -2040,7 +2233,7 @@ function AWACS:_CheckIn(Group) self:__AssignAnchor(5,managedgroup.GID) elseif self.AwacsFG then - text = string.format("%s. %s. Negative. You are already checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Negative. You are already checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -2117,7 +2310,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr) self:__CheckedIn(1,managedgroup.GID) self:__AssignAnchor(5,managedgroup.GID) else - text = string.format("%s. %s. Negative. You are already checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + text = string.format("%s. %s. Negative. You are already checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) end local RadioEntry = {} -- #AWACS.RadioEntry @@ -2135,8 +2328,9 @@ end -- @param #AWACS self -- @param Wrapper.Group#GROUP Group Group to use -- @param #number GID GroupID +-- @param #boolean dead If true, group is dead crashed or otherwise n/a -- @return #AWACS self -function AWACS:_CheckOut(Group,GID) +function AWACS:_CheckOut(Group,GID,dead) self:T(self.lid.."_CheckOut") -- check if already known @@ -2144,7 +2338,7 @@ function AWACS:_CheckOut(Group,GID) local text = "" if Outcome then -- yes, known - text = string.format("%s. %s. Copy. Have a safe flight home.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + 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 @@ -2160,16 +2354,19 @@ function AWACS:_CheckOut(Group,GID) self:__CheckedOut(1,GID,Stack,Angels) else -- no, unknown - text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1 1", self.callsigntxt) + if not dead then + text = string.format("%s. %s. Negative. You are not checked in.",self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) + end end - local RadioEntry = {} -- #AWACS.RadioEntry - RadioEntry.IsNew = true - RadioEntry.TextTTS = text - RadioEntry.ToScreen = true - RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 - - self.RadioQueue:Push(RadioEntry) + if not dead then + local RadioEntry = {} -- #AWACS.RadioEntry + RadioEntry.IsNew = true + RadioEntry.TextTTS = text + RadioEntry.ToScreen = true + RadioEntry.Duration = STTS.getSpeechTime(text,0.95,false) or 8 + self.RadioQueue:Push(RadioEntry) + end return self end @@ -2313,45 +2510,55 @@ function AWACS:_CreateAnchorStack() AnchorStackOne.AnchorBaseAngels = self.AnchorBaseAngels AnchorStackOne.Anchors = FIFO:New() -- Utilities.FiFo#FIFO AnchorStackOne.AnchorAssignedID = FIFO:New() -- Utilities.FiFo#FIFO - local newname = "" + + local newname = self.StationZone:GetName() + for i=1,self.AnchorMaxStacks do AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels) end - if self.debug then - --AnchorStackOne.Anchors:Flush() - end + if stackscreated == 0 then - AnchorStackOne.AnchorZone = self.AnchorZone - AnchorStackOne.AnchorZoneCoordinate = self.AnchorZone:GetCoordinate() - AnchorStackOne.AnchorZoneCoordinateText = self.AnchorZone:GetCoordinate():ToStringLLDDM() + local newsubname = AWACS.AnchorNames[stackscreated+1] or tostring(stackscreated+1) + newname = self.StationZone:GetName() .. "-"..newsubname + AnchorStackOne.StationZone = self.StationZone + AnchorStackOne.StationZoneCoordinate = self.StationZone:GetCoordinate() + AnchorStackOne.StationZoneCoordinateText = self.StationZone:GetCoordinate():ToStringLLDDM() + AnchorStackOne.StationName = newname --push to AnchorStacks - self.AnchorStacks:Push(AnchorStackOne,"One") + 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) else local newsubname = AWACS.AnchorNames[stackscreated+1] or tostring(stackscreated+1) - newname = self.AnchorZone:GetName() .. "-"..newsubname + newname = self.StationZone:GetName() .. "-"..newsubname local anchorbasecoord = self.OpsZone:GetCoordinate() -- Core.Point#COORDINATE - -- OpsZone can be Polygon, so use distance to AnchorZone as radius - local anchorradius = anchorbasecoord:Get2DDistance(self.AnchorZone:GetCoordinate()) + -- OpsZone can be Polygon, so use distance to StationZone as radius + local anchorradius = anchorbasecoord:Get2DDistance(self.StationZone:GetCoordinate()) --local anchorradius = self.OpsZone:GetRadius() -- #number - --anchorradius = anchorradius + self.AnchorZone:GetRadius() - local angel = self.AnchorZone:GetCoordinate():GetAngleDegrees(self.OpsZone:GetVec3()) + --anchorradius = anchorradius + self.StationZone:GetRadius() + local angel = self.StationZone:GetCoordinate():GetAngleDegrees(self.OpsZone:GetVec3()) self:T("Angel Radians= " .. angel) local turn = math.fmod(self.AnchorTurn*stackscreated,360) -- #number if self.AnchorTurn < 0 then turn = -turn end local newanchorbasecoord = anchorbasecoord:Translate(anchorradius,turn+angel) -- Core.Point#COORDINATE - AnchorStackOne.AnchorZone = ZONE_RADIUS:New(newname, newanchorbasecoord:GetVec2(), self.AnchorZone:GetRadius()) - AnchorStackOne.AnchorZoneCoordinate = newanchorbasecoord - AnchorStackOne.AnchorZoneCoordinateText = newanchorbasecoord:ToStringLLDDM() + AnchorStackOne.StationZone = ZONE_RADIUS:New(newname, newanchorbasecoord:GetVec2(), self.StationZone:GetRadius()) + AnchorStackOne.StationZoneCoordinate = newanchorbasecoord + AnchorStackOne.StationZoneCoordinateText = newanchorbasecoord: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) end - if self.debug then - --self.AnchorStacks:Flush() - AnchorStackOne.AnchorZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true) - MARKER:New(AnchorStackOne.AnchorZone:GetCoordinate(),"Anchor Zone: "..newname):ToAll() - end - return true,self.AnchorStacks:GetSize() end @@ -2565,7 +2772,9 @@ function AWACS:_GetBRAfromBullsOrAO(clustercoordinate) local BRAText = "" if not self.UseBullsAO then -- get BR from AO - BRAText = "AO "..refcoord:ToStringBR(clustercoordinate) + local bullsname = self.AOName or "Rock" + BRAText = string.format("%s %s",bullsname,refcoord:ToStringBR(clustercoordinate)) + --BRAText = "AO "..refcoord:ToStringBR(clustercoordinate) else -- get BR from Bulls BRAText = self:ToStringBULLS(clustercoordinate) @@ -2734,8 +2943,10 @@ function AWACS:_UpdateContactEngagementTag(CID,Text) local text = Text or "" -- get contact local contact = self.Contacts:PullByID(CID) -- #AWACS.ManagedContact - contact.EngagementTag = text - self.Contacts:Push(contact,CID) + if contact then + contact.EngagementTag = text + self.Contacts:Push(contact,CID) + end return self end @@ -2760,35 +2971,39 @@ function AWACS:_CheckTaskQueue() local entry = data.data -- #AWACS.ManagedTask local target = entry.Target -- Ops.Target#TARGET local description = entry.ToDo - self:I("ToDo = "..description) if description == AWACS.TaskDescription.ANCHOR or description == AWACS.TaskDescription.REANCHOR then - self:I("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 local group = managedgroup.Group - local groupcoord = group:GetCoordinate() - 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) - -- made it - target:Stop() - -- add group to idle stack - if managedgroup.IsAI then - -- message AI on station - self:_MessageAIReadyForTasking(managedgroup.GID) - elseif managedgroup.IsPlayer then - --self.TaskedCAPHuman:PullByPointer(entry.AssignedGroupID) - --self.CAPIdleHuman:Push(entry.AssignedGroupID) + if group and group:IsAlive() then + local groupcoord = group:GetCoordinate() + 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) + -- made it + target:Stop() + -- add group to idle stack + if managedgroup.IsAI then + -- message AI on station + self:_MessageAIReadyForTasking(managedgroup.GID) + elseif managedgroup.IsPlayer then + --self.TaskedCAPHuman:PullByPointer(entry.AssignedGroupID) + --self.CAPIdleHuman:Push(entry.AssignedGroupID) + end -- end isAI + managedgroup.HasAssignedTask = false + self.ManagedGrps[entry.AssignedGroupID] = managedgroup + -- pull task from OpenTasks + self.ManagedTasks:PullByID(entry.TID) + else --inzone + -- not there yet + self:I("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID) end - managedgroup.HasAssignedTask = false - self.ManagedGrps[entry.AssignedGroupID] = managedgroup - -- pull task from OpenTasks - self.ManagedTasks:PullByPointer(_id) else - -- not there yet - self:I("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID) + -- group dead, pull task + self.ManagedTasks:PullByID(entry.TID) end end @@ -2797,10 +3012,17 @@ function AWACS:_CheckTaskQueue() ---------------------------------------- elseif description == AWACS.TaskDescription.INTERCEPT then - -- TODO + -- DONE self:I("Open Tasks INTERCEPT") local taskstatus = entry.Status local targetstatus = entry.Target:GetState() + + if taskstatus == AWACS.TaskStatus.UNASSIGNED then + -- thou shallst not be in this list! + self.ManagedTasks:PullByID(entry.TID) + break + end + local auftrag = entry.Auftrag -- Ops.Auftrag#AUFTRAG local auftragstatus = "Not Known" if auftrag then @@ -2844,7 +3066,7 @@ function AWACS:_CheckTaskQueue() end self.ManagedGrps[entry.AssignedGroupID] = managedgroup - self.ManagedTasks:PullByPointer(_id) + self.ManagedTasks:PullByID(entry.TID) self:__InterceptSuccess(1) self:__ReAnchor(5,managedgroup.GID) @@ -2872,7 +3094,7 @@ function AWACS:_CheckTaskQueue() entry.Auftrag = nil entry.Status = AWACS.TaskStatus.UNASSIGNED entry.AssignedGroupID = 0 - self.ManagedTasks:PullByPointer(_id) + self.ManagedTasks:PullByID(entry.TID) --self.ManagedTasks:Push(entry,entry.TID) self:__InterceptFailure(1) self:__ReAnchor(5,managedgroup.GID) @@ -2915,8 +3137,6 @@ end function AWACS:AddCAPAirWing(AirWing) self:T(self.lid.."AddCAPAirWing") if AirWing then - -- TODO - Test Install callback - -- DONE - add distance to AO as UniqueID AirWing:SetUsingOpsAwacs(self) local distance = self.AOCoordinate:Get2DDistance(AirWing:GetCoordinate()) self.CAPAirwings:Push(AirWing,distance) @@ -2997,11 +3217,11 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,Repo end local isGroup = false local GID = 0 - local grpcallsign = "Ghost 1 1" + local grpcallsign = "Ghost 1" if Group and Group:IsAlive() then GID, isGroup = self:_GetManagedGrpID(Group) self:T("GID="..GID.." CheckedIn = "..tostring(isGroup)) - grpcallsign = self:_GetCallSign(Group,GID) or "Ghost 1 1" + grpcallsign = self:_GetCallSign(Group,GID) or "Ghost 1" end local contact = Contact -- Ops.Intelligence#INTEL.Contact @@ -3318,7 +3538,8 @@ function AWACS:_CheckAICAPOnStation() local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP if OpsGroup then local OpsName = OpsGroup:GetName() or "Unknown" - local OpsCallSign = OpsGroup:GetCallsignName() or "Unknown" + --local OpsCallSign = OpsGroup:GetCallsignName() or "Unknown" + local found,GID,OpsCallSign = self:_GetGIDFromGroupOrName(OpsGroup) report:Add(string.format("Mission FG %s",OpsName)) report:Add(string.format("Callsign %s",OpsCallSign)) report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) @@ -3383,19 +3604,19 @@ function AWACS:_AssignPilotToTarget(Pilots,Target) -- Check Distance local targetgroupcoord = Target.Contact.position - local closest = UTILS.NMToMeters(101) + local closest = UTILS.NMToMeters(self.maxassigndistance+1) -- get closest pilot from target for _,_Pilot in pairs(Pilots) do local pilotcoord = _Pilot.Group:GetCoordinate() local targetdist = targetgroupcoord:Get2DDistance(pilotcoord) - if UTILS.MetersToNM(targetdist) < 100 and targetdist < closest then + 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)) inreach = true closest = targetdist Pilot = _Pilot else - self:I(self.lid .. "Target distance > 100NM! No Assignment!") + self:I(self.lid .. "Target distance > "..self.maxassigndistance.."NM! No Assignment!") end end @@ -3415,10 +3636,41 @@ function AWACS:_AssignPilotToTarget(Pilots,Target) self:I("Current Mission: " .. currmission:GetType()) end -- create one intercept Auftrag and one to return to CAP post this one + local ZoneSet = SET_ZONE:New() + ZoneSet:AddZone(self.ControlZone) + ZoneSet:AddZone(self.OrbitZone) + if self.BorderZone then + ZoneSet:AddZone(self.BorderZone) + end local intercept = AUFTRAG:NewINTERCEPT(Target.Target) intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL) intercept:SetWeaponType(ENUMS.WeaponFlag.Auto) + -- TODO + -- now this is going to be interesting... + -- Check if the target left the "hot" area or is dead already + intercept:AddConditionSuccess( + function(target,zoneset) + -- BASE:I("AUFTRAG Condition Succes Eval Running") + local success = true + local target = target -- Ops.Target#TARGET + if target:IsDestroyed() then return true end + local tgtcoord = target:GetCoordinate():GetVec2() + local zones = zoneset -- Core.Set#SET_ZONE + zones:ForEachZone( + function(zone) + -- BASE:I("AUFTRAG Condition Succes ZONE Eval Running") + if zone:IsVec2InZone(tgtcoord) then + success = false + end + end + ) + return success + end, + Target.Target, + ZoneSet + ) + Pilot.FlightGroup:AddMission(intercept) local Angels = Pilot.AnchorStackAngels @@ -3426,7 +3678,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Target) local AnchorSpeed = self.CapSpeedBase or 220 AnchorSpeed = UTILS.KnotsToAltKIAS(AnchorSpeed,Angels) local Anchor = self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) -- #AWACS.AnchorData - local capauftrag = AUFTRAG:NewCAP(Anchor.AnchorZone,Angels,AnchorSpeed,Anchor.AnchorZoneCoordinate,0,15,{}) + local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{}) capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) Pilot.FlightGroup:AddMission(capauftrag) @@ -3512,10 +3764,11 @@ function AWACS:onafterStart(From, Event, To) self:T({From, Event, To}) -- Set up control zone - self.ControlZone = ZONE_RADIUS:New(self.OpsZone:GetName(),self.OpsZone:GetVec2(),UTILS.NMToMeters(self.ControlZoneRadius)) + local controlzonename = "FEZ-"..self.AOName + self.ControlZone = ZONE_RADIUS:New(controlzonename,self.OpsZone:GetVec2(),UTILS.NMToMeters(self.ControlZoneRadius)) if self.debug then self.ControlZone:DrawZone(-1,{0,1,0},1,{1,0,0},0.05,3,true) - MARKER:New(self.ControlZone:GetCoordinate(),"Control Zone"):ToAll() + --MARKER:New(self.ControlZone:GetCoordinate(),"AIC Zone"):ToAll() end -- set up the AWACS and let it orbit @@ -3552,8 +3805,8 @@ function AWACS:_CheckAwacsStatus() -- arrived self.AwacsInZone = true self:I(self.lid.."Arrived in Orbit Zone: " .. orbitzone:GetName()) - local text = string.format("%s on station for A O %s control.",self.callsigntxt,self.OpsZone:GetName() or "A O") - local textScreen = string.format("%s on station for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO") + local text = string.format("%s on station for %s control.",self.callsigntxt,self.AOName or "Rock") + local textScreen = string.format("%s on station for %s control.",self.callsigntxt,self.AOName or "Rock") local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true RadioEntry.TextTTS = text @@ -3750,8 +4003,6 @@ function AWACS:_CheckAwacsStatus() end else - -- do other stuff, AWACS dead? - -- TODO AWACS dead, or per EVENT? -- Check on Awacs Mission Status local AWmission = self.AwacsMission -- Ops.Auftrag#AUFTRAG local awstatus = AWmission:GetState() @@ -3916,17 +4167,18 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo local isPlayer = managedgroup.IsPlayer local isAI = managedgroup.IsAI local Group = managedgroup.Group - local CallSign = managedgroup.CallSign or "Ghost 1 1" - local AnchorName = Anchor.AnchorZone:GetName() or "unknown" - local AnchorCoordTxt = Anchor.AnchorZoneCoordinateText or "unknown" + local CallSign = managedgroup.CallSign or "Ghost 1" + --local AnchorName = Anchor.StationZone:GetName() or "unknown" + local AnchorName = Anchor.StationName or "unknown" + local AnchorCoordTxt = Anchor.StationZoneCoordinateText or "unknown" local Angels = AnchorAngels or 25 local AnchorSpeed = self.CapSpeedBase or 220 local AuftragsNr = managedgroup.CurrentAuftrag - local textTTS = string.format("%s. %s. Anchor at %s at angels %d doing %d knots. Wait for task assignment.",CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed) + local textTTS = string.format("%s. %s. Station at %s at angels %d doing %d knots. Wait for task assignment.",CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed) local ROEROT = self.AwacsROE.." "..self.AwacsROT - local textScreen = string.format("%s. %s.\nAnchor at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s\nWait for task assignment.",CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) - local TextTasking = string.format("%s. %s.\nAnchor at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s",CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) + local textScreen = string.format("%s. %s.\nStation at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s\nWait for task assignment.",CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) + local TextTasking = string.format("%s. %s.\nStation at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s",CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT) local RadioEntry = {} -- #AWACS.RadioEntry RadioEntry.IsNew = true @@ -3939,7 +4191,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo self.RadioQueue:Push(RadioEntry) - managedgroup.CurrentTask = self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.AnchorZone) + managedgroup.CurrentTask = self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.StationZone) -- if isAI and AuftragsNr and AuftragsNr > 0 and self.AICAPMissions:HasUniqueID(AuftragsNr) then @@ -3950,7 +4202,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo local auftragtype = auftrag:GetType() if auftragtype == AUFTRAG.Type.ALERT5 then -- all correct - local capauftrag = AUFTRAG:NewCAP(Anchor.AnchorZone,Angels*1000,AnchorSpeed,Anchor.AnchorZone:GetCoordinate(),0,15,{}) + local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels*1000,AnchorSpeed,Anchor.StationZone:GetCoordinate(),0,15,{}) capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) self.CatchAllMissions[#self.CatchAllMissions+1] = capauftrag managedgroup.FlightGroup:AddMission(capauftrag) @@ -4226,8 +4478,8 @@ function AWACS:onafterReAnchor(From, Event, To, GID) -- re-establish anchor task -- get anchor zone data local Anchor = self.AnchorStacks:ReadByPointer(managedgroup.AnchorStackNo) -- #AWACS.AnchorData - local AnchorZone = Anchor.AnchorZone -- Core.Zone#ZONE - managedgroup.CurrentTask = self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,"Re-Anchor AI",AnchorZone) + local StationZone = Anchor.StationZone -- Core.Zone#ZONE + managedgroup.CurrentTask = self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,"Re-Station AI",StationZone) managedgroup.HasAssignedTask = true local mission = AIFG:GetMissionCurrent() -- Ops.Auftrag#AUFTRAG if mission then @@ -4237,7 +4489,7 @@ function AWACS:onafterReAnchor(From, Event, To, GID) end managedgroup.ContactCID = 0 self.ManagedGrps[GID] = managedgroup - self:_MessageVector(GID," to Anchor",Anchor.AnchorZoneCoordinate,managedgroup.AnchorStackAngels) + self:_MessageVector(GID," to Station",Anchor.StationZoneCoordinate,managedgroup.AnchorStackAngels) end else -- lost group, remove from known groups, declare vanished @@ -4267,105 +4519,7 @@ function AWACS:onafterReAnchor(From, Event, To, GID) return self end +end -- end do ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- END AWACS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -end -- end do - ---- Testing -_SETTINGS:SetLocale("en") -_SETTINGS:SetImperial() -_SETTINGS:SetPlayerMenuOff() - --- We need an AirWing -local AwacsAW = AIRWING:New("AirForce WH-1","AirForce One") -AwacsAW:SetReportOn() -AwacsAW:SetMarker(true) -AwacsAW:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Kutaisi)) -AwacsAW:SetRespawnAfterDestroyed(900) -AwacsAW:SetTakeoffHot() -AwacsAW:__Start(2) - --- And a couple of Squads --- AWACS itself -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) - --- Escorts -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:SetTakeoffHot() -Squad_Two:SetRadio(255,radio.modulation.AM) -AwacsAW:AddSquadron(Squad_Two) -AwacsAW:NewPayload("Escorts",-1,{AUFTRAG.Type.ESCORT},100) - --- CAP -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:SetTakeoffHot() -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) - --- We need a secondary AirWing for testing -local AwacsAW2 = AIRWING:New("AirForce WH-2","AirForce Two") -AwacsAW2:SetReportOn() -AwacsAW2:SetMarker(true) -AwacsAW2:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Senaki_Kolkhi)) -AwacsAW2:SetRespawnAfterDestroyed(900) -AwacsAW2:SetTakeoffHot() -AwacsAW2:__Start(2) - --- CAP2 -local Squad_ThreeOne = SQUADRON:New("CAP2",10,"CAP West") -Squad_ThreeOne:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},80) -Squad_ThreeOne:SetFuelLowRefuel(true) -Squad_ThreeOne:SetFuelLowThreshold(0.3) -Squad_ThreeOne:SetTurnoverTime(10,20) -Squad_ThreeOne:SetTakeoffHot() -Squad_Two:SetRadio(255,radio.modulation.AM) -AwacsAW2:AddSquadron(Squad_ThreeOne) -AwacsAW2:NewPayload("CAP 2-1",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},100) - --- Get AWACS started -local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("NW Zone"),"Anchor One",255,radio.modulation.AM ) -testawacs:SetEscort(2) -testawacs:SetAwacsDetails(CALLSIGN.AWACS.Magic,1,30,300,243,20) -testawacs:SetSRS("E:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010,nil) -testawacs:SetBorderZone(ZONE:FindByName("Blue Border")) -testawacs:AddCAPAirWing(AwacsAW2) -testawacs:__Start(5) - --- Red CAP -function GetRedCAP() - local caps = SPAWN:New("Red CAP") - :InitLimit(0,1) - :InitCleanUp(60) - :SpawnScheduled(300,0.1) - local TU22 = SPAWN:New("TU-22") - :InitLimit(0,1) - :InitCleanUp(60) - :SpawnScheduled(360,0.1) - local Fulcrum = SPAWN:New("Aerial-1") - :InitLimit(0,1) - :InitCleanUp(60) - :SpawnScheduled(420,0.1) - local Bears = SPAWN:New("Aerial-2") - :InitLimit(0,1) - :InitCleanUp(60) - :SpawnScheduled(480,0.1) -end - -local captimer = TIMER:New(GetRedCAP) -captimer:Start(600) \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 48cbfc739..a94a6568f 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -122,8 +122,11 @@ INTEL = { -- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this contact. -- @field Ops.Target#TARGET target The Target attached to this contact. -- @field #string recce The name of the recce unit that detected this contact. --- @field #string ctype Contact type. +-- @field #string ctype Contact type of #INTEL.Ctype. -- @field #string platform [AIR] Contact platform name, e.g. Foxbat, Flanker_E, defaults to Bogey if unknown +-- @field #number heading [AIR] Heading of the contact, if available. +-- @field #boolean maneuvering [AIR] Contact has changed direction by >10 deg. +-- @field #number altitude [AIR] Flight altitude of the contact in meters. --- Cluster info. -- @type INTEL.Cluster @@ -137,11 +140,12 @@ INTEL = { -- @field Wrapper.Marker#MARKER marker F10 marker. -- @field #number markerID Marker ID. -- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this cluster. --- @field #string ctype Cluster type. +-- @field #string ctype Cluster type of #INTEL.Ctype. +-- @field #number altitude [AIR] Average flight altitude of the cluster in meters. --- Contact or cluster type. -- @type INTEL.Ctype --- @field #string GROUND Ground. +-- @field #string GROUND Ground. -- @field #string NAVAL Ship. -- @field #string AIRCRAFT Airpane or helicopter. -- @field #string STRUCTURE Static structure. @@ -345,7 +349,7 @@ function INTEL:New(DetectionSet, Coalition, Alias) -- @param #INTEL self -- @param #number delay Delay in seconds. -- @param #INTEL.Cluster Cluster Detected cluster. - + --- On After "NewCluster" event. -- @function [parent=#INTEL] OnAfterNewCluster -- @param #INTEL self @@ -541,6 +545,7 @@ function INTEL:SetDetectStatics(Switch) else self.detectStatics=false end + return self end --- Set verbosity level for debugging. @@ -692,6 +697,7 @@ function INTEL:onafterStart(From, Event, To) -- Start the status monitoring. self:__Status(-math.random(10)) + return self end --- On after "Status" event. @@ -737,6 +743,7 @@ function INTEL:onafterStatus(From, Event, To) end self:__Status(self.statusupdate) + return self end @@ -746,10 +753,10 @@ function INTEL:UpdateIntel() -- Set of all detected units. local DetectedUnits={} - + -- Set of which units was detected by which recce local RecceDetecting = {} - + -- Loop over all units providing intel. for _,_group in pairs(self.detectionset.Set or {}) do local group=_group --Wrapper.Group#GROUP @@ -857,6 +864,7 @@ function INTEL:UpdateIntel() self:PaintPicture() end + return self end --- Update an #INTEL.Contact item. @@ -866,22 +874,34 @@ end function INTEL:_UpdateContact(Contact) if Contact.isStatic then - + -- Statics don't need to be updated. - + else - + if Contact.group and Contact.group:IsAlive() then Contact.Tdetected=timer.getAbsTime() Contact.position=Contact.group:GetCoordinate() Contact.velocity=Contact.group:GetVelocityVec3() Contact.speed=Contact.group:GetVelocityMPS() - + if Contact.group:IsAir() then + Contact.altitude=Contact.group:GetAltitude() + local oldheading = Contact.heading or 1 + local newheading = Contact.group:GetHeading() + if newheading == 0 then newheading = 1 end + local changeh = math.abs(((oldheading - newheading) + 360) % 360) + Contact.heading = newheading + if changeh > 10 then + Contact.maneuvering = true + else + Contact.maneuvering = false + end + end end - - end + end + return self end --- Create an #INTEL.Contact item from a given GROUP or STATIC object. @@ -895,11 +915,11 @@ function INTEL:_CreateContact(Positionable, RecceName) -- Create new contact. local item={} --#INTEL.Contact - + if Positionable:IsInstanceOf("GROUP") then - + local group=Positionable --Wrapper.Group#GROUP - + item.groupname=group:GetName() item.group=group item.Tdetected=timer.getAbsTime() @@ -917,9 +937,13 @@ function INTEL:_CreateContact(Positionable, RecceName) item.isStatic=false if group:IsAir() then item.platform=group:GetNatoReportingName() + item.heading = group:GetHeading() + item.maneuvering = false + item.altitude = group:GetAltitude() else -- TODO optionally add ground types? item.platform="Unknown" + item.altitude = group:GetAltitude(true) end if item.category==Group.Category.AIRPLANE or item.category==Group.Category.HELICOPTER then item.ctype=INTEL.Ctype.AIRCRAFT @@ -928,13 +952,13 @@ function INTEL:_CreateContact(Positionable, RecceName) elseif item.category==Group.Category.SHIP then item.ctype=INTEL.Ctype.NAVAL end - + return item - + elseif Positionable:IsInstanceOf("STATIC") then - + local static=Positionable --Wrapper.Static#STATIC - + item.groupname=static:GetName() item.group=static item.Tdetected=timer.getAbsTime() @@ -949,14 +973,14 @@ function INTEL:_CreateContact(Positionable, RecceName) item.recce=RecceName item.isground = true item.isship = false - item.isStatic=true + item.isStatic=true item.ctype=INTEL.Ctype.STRUCTURE - - return item + + return item else self:E(self.lid..string.format("ERROR: object needs to be a GROUP or STATIC!")) end - + end return nil @@ -981,7 +1005,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, DetectedStatics, RecceDetecti self:KnowObject(group, RecceDetecting[groupname]) end - + -- Loop over statics. for staticname,_static in pairs(DetectedStatics) do local static=_static --Wrapper.Static#STATIC @@ -1006,7 +1030,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, DetectedStatics, RecceDetecti end end - + return self end --- (Internal) Return the detected target groups of the controllable as a @{SET_GROUP}. @@ -1081,10 +1105,10 @@ function INTEL:onafterNewContact(From, Event, To, Contact) -- Debug text. self:F(self.lid..string.format("NEW contact %s", Contact.groupname)) - + -- Add to table of unknown contacts. table.insert(self.ContactsUnknown, Contact) - + return self end --- On after "LostContact" event. @@ -1097,10 +1121,10 @@ function INTEL:onafterLostContact(From, Event, To, Contact) -- Debug text. self:F(self.lid..string.format("LOST contact %s", Contact.groupname)) - + -- Add to table of lost contacts. table.insert(self.ContactsLost, Contact) - + return self end --- On after "NewCluster" event. @@ -1110,13 +1134,13 @@ end -- @param #string To To state. -- @param #INTEL.Cluster Cluster Detected cluster. function INTEL:onafterNewCluster(From, Event, To, Cluster) - + -- Debug text. self:F(self.lid..string.format("NEW cluster #%d [%s] of size %d", Cluster.index, Cluster.ctype, Cluster.size)) - + -- Add cluster to table. self:_AddCluster(Cluster) - + return self end --- On after "LostCluster" event. @@ -1130,14 +1154,14 @@ function INTEL:onafterLostCluster(From, Event, To, Cluster, Mission) -- Debug text. local text = self.lid..string.format("LOST cluster #%d [%s]", Cluster.index, Cluster.ctype) - + if Mission then local mission=Mission --Ops.Auftrag#AUFTRAG text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unknown") end - + self:T(text) - + return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1154,47 +1178,47 @@ function INTEL:KnowObject(Positionable, RecceName, Tdetected) local Tnow=timer.getAbsTime() Tdetected=Tdetected or Tnow - + if Positionable and Positionable:IsAlive() then if Tdetected>Tnow then -- Delay call. self:ScheduleOnce(Tdetected-Tnow, self.KnowObject, self, Positionable, RecceName) else - + -- Name of the object. local name=Positionable:GetName() - + -- Try to get the contact by name. local contact=self:GetContactByName(name) - + if contact then - + -- Update contact info. self:_UpdateContact(contact) - + else - + -- Create new contact. contact=self:_CreateContact(Positionable, RecceName) - + if contact then - + -- Debug info. self:T(string.format("%s contact detected by %s", contact.groupname, RecceName or "unknown")) - + -- Add contact to table. self:AddContact(contact) - + -- Trigger new contact event. self:NewContact(contact) - + end - + end end end - + return self end @@ -1244,7 +1268,7 @@ function INTEL:AddContact(Contact) self:T(self.lid..string.format("Adding new Contact %s to table", tostring(Contact.groupname))) table.insert(self.Contacts, Contact) end - + return self end @@ -1261,7 +1285,7 @@ function INTEL:RemoveContact(Contact) end end - + return self end --- Check if a contact was lost. @@ -1274,7 +1298,7 @@ function INTEL:_CheckContactLost(Contact) if Contact.group==nil or not Contact.group:IsAlive() then return true end - + -- We never forget statics as they don't move. if Contact.isStatic then return false @@ -1284,7 +1308,7 @@ function INTEL:_CheckContactLost(Contact) local dT=timer.getAbsTime()-Contact.Tdetected local dTforget=nil - + if Contact.category==Group.Category.GROUND then dTforget=60*60*2 -- 2 hours elseif Contact.category==Group.Category.AIRPLANE then @@ -1317,80 +1341,80 @@ function INTEL:PaintPicture() -- First remove all lost contacts from clusters. for _,_contact in pairs(self.ContactsLost) do local contact=_contact --#INTEL.Contact - + -- Get cluster this contact belongs to (if any). local cluster=self:GetClusterOfContact(contact) - + if cluster then self:RemoveContactFromCluster(contact, cluster) end end - + -- Clean up cluster table. local ClusterSet = {} - + -- Now check if whole clusters were lost. for _i,_cluster in pairs(self.Clusters) do local cluster=_cluster --#INTEL.Cluster - + if cluster.size>0 and self:ClusterCountUnits(cluster)>0 then -- This one has size>0 and units>0 table.insert(ClusterSet,_cluster) else - + -- This cluster is gone. - -- Remove marker. + -- Remove marker. if cluster.marker then cluster.marker:Remove() end - + -- Marker of the arrow. if cluster.markerID then COORDINATE:RemoveMark(cluster.markerID) end - + -- Lost cluster. self:LostCluster(cluster, cluster.mission) end end - + -- Set Clusters. self.Clusters = ClusterSet -- Update positions. self:_UpdateClusterPositions() - + for _,_contact in pairs(self.Contacts) do local contact=_contact --#INTEL.Contact - + -- Debug info. self:T(string.format("Paint Picture: checking for %s",contact.groupname)) -- Get the current cluster (if any) this contact belongs to. local currentcluster=self:GetClusterOfContact(contact) - if currentcluster then + if currentcluster then --- -- Contact is currently part of a cluster. --- -- Check if the contact is still connected to the cluster. local isconnected=self:IsContactConnectedToCluster(contact, currentcluster) - + if isconnected then - + else - + --- Not connected to current cluster any more. - + -- Remove from current cluster. self:RemoveContactFromCluster(contact, currentcluster) - + -- Find new cluster. local cluster=self:_GetClosestClusterOfContact(contact) - + if cluster then -- Add contact to cluster. self:AddContactToCluster(contact, cluster) @@ -1398,11 +1422,11 @@ function INTEL:PaintPicture() -- Create a new cluster. local newcluster=self:_CreateClusterFromContact(contact) - + -- Trigger new cluster event. self:NewCluster(newcluster) end - + end else @@ -1410,26 +1434,26 @@ function INTEL:PaintPicture() --- -- Contact is not in any cluster yet. --- - + -- Debug info. self:T(self.lid..string.format("Paint Picture: contact %s has NO current cluster", contact.groupname)) - + -- Get the closest existing cluster of this contact. local cluster=self:_GetClosestClusterOfContact(contact) if cluster then - + -- Debug info. self:T(self.lid..string.format("Paint Picture: contact %s has closest cluster #%d",contact.groupname, cluster.index)) - + -- Add contact to this cluster. self:AddContactToCluster(contact, cluster) - + else -- Create a brand new cluster. local newcluster=self:_CreateClusterFromContact(contact) - + -- Trigger event for a new cluster. self:NewCluster(newcluster) end @@ -1446,18 +1470,20 @@ function INTEL:PaintPicture() for _,_cluster in pairs(self.Clusters) do local cluster=_cluster --#INTEL.Cluster --local coordinate=self:GetClusterCoordinate(cluster) - + -- Update F10 marker. MESSAGE:New("Updating cluster marker and future position", 10):ToAll() - + -- Update cluster markers. self:UpdateClusterMarker(cluster) - + -- Extrapolate future position of the cluster. self:CalcClusterFuturePosition(cluster, 300) - + end end + + return self end --- Create a new cluster. @@ -1474,6 +1500,7 @@ function INTEL:_CreateCluster() cluster.threatlevelMax=0 cluster.size=0 cluster.Contacts={} + cluster.altitude=0 -- Increase counter. self.clustercounter=self.clustercounter+1 @@ -1488,15 +1515,15 @@ end function INTEL:_CreateClusterFromContact(Contact) local cluster=self:_CreateCluster() - + self:T(self.lid..string.format("Created NEW cluster #%d with first contact %s", cluster.index, Contact.groupname)) cluster.coordinate:UpdateFromCoordinate(Contact.position) cluster.ctype=Contact.ctype - + self:AddContactToCluster(Contact, cluster) - + return cluster end @@ -1510,6 +1537,7 @@ function INTEL:_AddCluster(Cluster) -- Add cluster. table.insert(self.Clusters, Cluster) + return self end --- Add a contact to the cluster. @@ -1519,7 +1547,7 @@ end function INTEL:AddContactToCluster(contact, cluster) if contact and cluster then - + -- Add neighbour to cluster contacts. table.insert(cluster.Contacts, contact) @@ -1528,11 +1556,15 @@ function INTEL:AddContactToCluster(contact, cluster) -- Increase size. cluster.size=cluster.size+1 - + + -- alt + self:GetClusterAltitude(cluster,true) + -- Debug info. self:T(self.lid..string.format("Adding contact %s to cluster #%d [%s] ==> New size=%d", contact.groupname, cluster.index, cluster.ctype, cluster.size)) end + return self end --- Remove a contact from a cluster. @@ -1550,7 +1582,7 @@ function INTEL:RemoveContactFromCluster(contact, cluster) -- Remove threat level sum. cluster.threatlevelSum=cluster.threatlevelSum-contact.threatlevel - + -- Decrease cluster size. cluster.size=cluster.size-1 @@ -1560,13 +1592,13 @@ function INTEL:RemoveContactFromCluster(contact, cluster) -- Debug info. self:T(self.lid..string.format("Removing contact %s from cluster #%d ==> New cluster size=%d", contact.groupname, cluster.index, cluster.size)) - return + return self end end end - + return self end --- Calculate cluster threat level sum. @@ -1630,20 +1662,22 @@ function INTEL:CalcClusterDirection(cluster) local n=0 for _,_contact in pairs(cluster.Contacts) do local contact=_contact --#INTEL.Contact - + if (not contact.isStatic) and contact.group:IsAlive() then direction = direction + contact.group:GetHeading() n=n+1 end end - + --TODO: This calculation is WRONG! -- Simple example for two groups: -- First group is going West, i.e. heading 090 -- Second group is going East, i.e. heading 270 -- Total is 360/2=180, i.e. South! -- It should not go anywhere as the two movements cancel each other. - + -- Correct, edge case for N=2^x, but when 2 pairs of groups drive in exact opposite directions, the cluster will split at some point? + -- maybe add the speed as weight to get a factor + if n==0 then return 0 else @@ -1660,17 +1694,17 @@ function INTEL:CalcClusterSpeed(cluster) local velocity = 0 ; local n=0 for _,_contact in pairs(cluster.Contacts) do local contact=_contact --#INTEL.Contact - + if (not contact.isStatic) and contact.group:IsAlive() then velocity = velocity + contact.group:GetVelocityMPS() n=n+1 end - + end - + if n==0 then return 0 - else + else return math.floor(velocity / n) end end @@ -1682,10 +1716,10 @@ end function INTEL:CalcClusterVelocityVec3(cluster) local v={x=0, y=0, z=0} --DCS#Vec3 - + for _,_contact in pairs(cluster.Contacts) do local contact=_contact --#INTEL.Contact - + if (not contact.isStatic) and contact.group:IsAlive() then local vec=contact.group:GetVelocityVec3() v.x=v.x+vec.x @@ -1693,7 +1727,7 @@ function INTEL:CalcClusterVelocityVec3(cluster) v.z=v.y+vec.z end end - + return v end @@ -1703,22 +1737,22 @@ end -- @param #number seconds Time interval in seconds. Default is `self.prediction`. -- @return Core.Point#COORDINATE Calculated future position of the cluster. function INTEL:CalcClusterFuturePosition(cluster, seconds) - + -- Get current position of the cluster. local p=self:GetClusterCoordinate(cluster) - + -- Velocity vector in m/s. local v=self:CalcClusterVelocityVec3(cluster) - + -- Time in seconds. local t=seconds or self.prediction - -- Extrapolated vec3. + -- Extrapolated vec3. local Vec3={x=p.x+v.x*t, y=p.y+v.y*t, z=p.z+v.z*t} - + -- Future position. local futureposition=COORDINATE:NewFromVec3(Vec3) - + -- Create an arrow pointing in the direction of the movement. if self.clustermarkers and self.verbose>1 then if cluster.markerID then @@ -1726,7 +1760,7 @@ function INTEL:CalcClusterFuturePosition(cluster, seconds) end cluster.markerID = p:ArrowToAll(futureposition, self.coalition, {1,0,0}, 1, {1,1,0}, 0.5, 2, true, "Position Calc") end - + return futureposition end @@ -1764,7 +1798,7 @@ function INTEL:IsContactConnectedToCluster(contact, cluster) if contact.ctype~=cluster.ctype then return false, math.huge end - + for _,_contact in pairs(cluster.Contacts) do local Contact=_contact --#INTEL.Contact @@ -1774,7 +1808,17 @@ function INTEL:IsContactConnectedToCluster(contact, cluster) --local dist=Contact.position:Get2DDistance(contact.position) local dist=Contact.position:DistanceFromPointVec2(contact.position) - if dist0 then + avgalt = newalt/n + end + + -- Update cluster coordinate. + Cluster.altitude = avgalt + + self:T(string.format("Updating Cluster Altitude: %d",Cluster.altitude)) + + return Cluster.altitude +end + --- Get the coordinate of a cluster. -- @param #INTEL self -- @param #INTEL.Cluster Cluster The cluster. @@ -1887,43 +1982,43 @@ function INTEL:GetClusterCoordinate(Cluster, Update) -- Init. local x=0 ; local y=0 ; local z=0 ; local n=0 - + -- Loop over all contacts. for _,_contact in pairs(Cluster.Contacts) do local contact=_contact --#INTEL.Contact - + local vec3=nil --DCS#Vec3 - + if Update and contact.group and contact.group:IsAlive() then vec3 = contact.group:GetVec3() end - + if not vec3 then vec3=contact.position end - + if vec3 then - + -- Sum up posits. x=x+vec3.x y=y+vec3.y z=z+vec3.z - + -- Increase counter. n=n+1 - + end end - + if n>0 then -- Average. local Vec3={x=x/n, y=y/n, z=z/n} --DCS#Vec3 - + -- Update cluster coordinate. Cluster.coordinate:UpdateFromVec3(Vec3) - + end return Cluster.coordinate @@ -1938,7 +2033,7 @@ end function INTEL:_CheckClusterCoordinateChanged(Cluster, Coordinate, Threshold) Threshold=Threshold or 100 - + Coordinate=Coordinate or Cluster.coordinate -- Positions of cluster. @@ -1960,13 +2055,15 @@ end function INTEL:_UpdateClusterPositions() for _,_cluster in pairs (self.Clusters) do local cluster=_cluster --#INTEL.Cluster - + -- Update cluster coordinate. local coord = self:GetClusterCoordinate(cluster, true) - + local alt = self:GetClusterAltitude(cluster,true) + -- Debug info. self:T(self.lid..string.format("Updating Cluster position size: %s", cluster.size)) end + return self end --- Count number of alive units in contact. @@ -1982,7 +2079,7 @@ function INTEL:ContactCountUnits(Contact) end else if Contact.group then - local n=Contact.group:CountAliveUnits() + local n=Contact.group:CountAliveUnits() return n else return 0 @@ -1996,7 +2093,7 @@ end -- @return #number unitcount function INTEL:ClusterCountUnits(Cluster) local unitcount = 0 - for _,_contact in pairs (Cluster.Contacts) do + for _,_contact in pairs (Cluster.Contacts) do local contact=_contact --#INTEL.Contact unitcount = unitcount + self:ContactCountUnits(contact) end @@ -2014,10 +2111,10 @@ function INTEL:UpdateClusterMarker(cluster) local text=string.format("Cluster #%d: %s\nSize %d\nUnits %d\nTLsum=%d", cluster.index, cluster.ctype, cluster.size, unitcount, cluster.threatlevelSum) if not cluster.marker then - + -- First time ==> need to create a new marker object. cluster.marker=MARKER:New(cluster.coordinate, text):ToCoalition(self.coalition) - + else -- Need to refresh? @@ -2030,7 +2127,7 @@ function INTEL:UpdateClusterMarker(cluster) end -- Check if coordinate changed. - local coordchange=self:_CheckClusterCoordinateChanged(cluster, cluster.marker.coordinate) + local coordchange=self:_CheckClusterCoordinateChanged(cluster, cluster.marker.coordinate) if coordchange then cluster.marker.coordinate:UpdateFromCoordinate(cluster.coordinate) refresh=true @@ -2109,22 +2206,22 @@ INTEL_DLINK.version = "0.0.1" -- Contact duplicates are removed. Clusters might contain duplicates (Might fix that later, WIP). -- -- Basic setup: --- +-- -- local datalink = INTEL_DLINK:New({myintel1,myintel2}), "FSB", 20, 300) -- datalink:__Start(2) -- -- Add an Intel while running: --- +-- -- datalink:AddIntel(myintel3) -- -- Gather the data: --- +-- -- datalink:GetContactTable() -- #table of #INTEL.Contact contacts. -- datalink:GetClusterTable() -- #table of #INTEL.Cluster clusters. -- datalink:GetDetectedItemCoordinates() -- #table of contact coordinates, to be compatible with @{Functional.Detection#DETECTION}. -- -- Gather data with the event function: --- +-- -- function datalink:OnAfterCollected(From, Event, To, Contacts, Clusters) -- ... ... -- end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index da46d8230..c95c7ecea 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2085,7 +2085,7 @@ function OPSGROUP:RadioTransmission(Text, Delay, SayCallsign, Frequency) end -- Debug info. - self:I(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) + self:T(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) self.msrs:PlayText(Text) end diff --git a/Moose Development/Moose/Sound/UserSound.lua b/Moose Development/Moose/Sound/UserSound.lua index 8b94ad114..cbccffe12 100644 --- a/Moose Development/Moose/Sound/UserSound.lua +++ b/Moose Development/Moose/Sound/UserSound.lua @@ -40,7 +40,7 @@ do -- UserSound -- @param #USERSOUND self -- @param #string UserSoundFileName The filename of the usersound. -- @return #USERSOUND - function USERSOUND:New( UserSoundFileName ) --R2.3 + function USERSOUND:New( UserSoundFileName ) local self = BASE:Inherit( self, BASE:New() ) -- #USERSOUND @@ -58,7 +58,7 @@ do -- UserSound -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- BlueVictory:SetFileName( "BlueVictoryLoud.ogg" ) -- Set the BlueVictory to change the file name to play a louder sound. -- - function USERSOUND:SetFileName( UserSoundFileName ) --R2.3 + function USERSOUND:SetFileName( UserSoundFileName ) self.UserSoundFileName = UserSoundFileName @@ -75,7 +75,7 @@ do -- UserSound -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- BlueVictory:ToAll() -- Play the sound that Blue has won. -- - function USERSOUND:ToAll() --R2.3 + function USERSOUND:ToAll() trigger.action.outSound( self.UserSoundFileName ) @@ -91,7 +91,7 @@ do -- UserSound -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- BlueVictory:ToCoalition( coalition.side.BLUE ) -- Play the sound that Blue has won to the blue coalition. -- - function USERSOUND:ToCoalition( Coalition ) --R2.3 + function USERSOUND:ToCoalition( Coalition ) trigger.action.outSoundForCoalition(Coalition, self.UserSoundFileName ) @@ -107,7 +107,7 @@ do -- UserSound -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- BlueVictory:ToCountry( country.id.USA ) -- Play the sound that Blue has won to the USA country. -- - function USERSOUND:ToCountry( Country ) --R2.3 + function USERSOUND:ToCountry( Country ) trigger.action.outSoundForCountry( Country, self.UserSoundFileName ) @@ -123,9 +123,9 @@ do -- UserSound -- @usage -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) -- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player. - -- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group. + -- BlueVictory:ToGroup( PlayerGroup ) -- Play the victory sound to the player group. -- - function USERSOUND:ToGroup( Group, Delay ) --R2.3 + function USERSOUND:ToGroup( Group, Delay ) Delay=Delay or 0 if Delay>0 then @@ -136,5 +136,27 @@ do -- UserSound return self end + + --- Play the usersound to the given @{Wrapper.Unit}. + -- @param #USERSOUND self + -- @param Wrapper.Unit#UNIT Unit The @{Wrapper.Unit} to play the usersound to. + -- @param #number Delay (Optional) Delay in seconds, before the sound is played. Default 0. + -- @return #USERSOUND The usersound instance. + -- @usage + -- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" ) + -- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active unit named "PlayerUnit", a human player. + -- BlueVictory:ToUnit( PlayerUnit ) -- Play the victory sound to the player unit. + -- + function USERSOUND:ToUnit( Unit, Delay ) + + Delay=Delay or 0 + if Delay>0 then + SCHEDULER:New(nil, USERSOUND.ToUnit,{self, Unit}, Delay) + else + trigger.action.outSoundForUnit( Unit:GetID(), self.UserSoundFileName ) + end + + return self + end end \ No newline at end of file diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 21d01762a..18d959928 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -509,11 +509,12 @@ ENUMS.ReportingName = Atlas = "A400", Lancer = "B1-B", Stratofortress = "B-52H", - Hercules = "C-130", -- modded version has type name "Hercules", unfortunately + Hercules = "C-130", + Super_Hercules = "Hercules", Globemaster = "C-17", Greyhound = "C-2A", Galaxy = "C-5", - Hawkexe = "E-2D", + Hawkeye = "E-2D", Sentry = "E-3A", Stratotanker = "KC-135", Extender = "KC-10", diff --git a/Moose Development/Moose/Utilities/FiFo.lua b/Moose Development/Moose/Utilities/FiFo.lua index 38b318a2d..b004de791 100644 --- a/Moose Development/Moose/Utilities/FiFo.lua +++ b/Moose Development/Moose/Utilities/FiFo.lua @@ -249,7 +249,11 @@ end -- @return #boolean exists function FIFO:HasUniqueID(UniqueID) self:T(self.lid.."HasUniqueID") - return self.stackbyid[UniqueID] and true or false + if self.stackbyid[UniqueID] ~= nil then + return true + else + return false + end end --- FIFO Get the data stack by UniqueID diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index a9cdea8b7..266e4a483 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -631,7 +631,7 @@ function GROUP:GetUnits() local DCSGroup = self:GetDCSObject() if DCSGroup then - local DCSUnits = DCSGroup:getUnits() + local DCSUnits = DCSGroup:getUnits() or {} local Units = {} for Index, UnitData in pairs( DCSUnits ) do Units[#Units+1] = UNIT:Find( UnitData ) @@ -667,6 +667,29 @@ function GROUP:GetPlayerUnits() return nil end +--- Check if an (air) group is a client or player slot. Information is retrieved from the group template. +-- @param #GROUP self +-- @return #boolean If true, group is associated with a client or player slot. +function GROUP:IsPlayer() + + -- Get group. + -- local group=self:GetGroup() + + -- Units of template group. + local units=self:GetTemplate().units + + -- Get numbers. + for _,unit in pairs(units) do + + -- Check if unit name matach and skill is Client or Player. + if unit.name==self:GetName() and (unit.skill=="Client" or unit.skill=="Player") then + return true + end + + end + + return false +end --- Returns the UNIT wrapper class with number UnitNumber. -- If the underlying DCS Unit does not exist, the method will return nil. . @@ -1027,16 +1050,25 @@ end -- @param Wrapper.Group#GROUP self -- @return Core.Point#COORDINATE The COORDINATE of the GROUP. function GROUP:GetCoordinate() - - local FirstUnit = self:GetUnit(1) + + + local Units = self:GetUnits() or {} - if FirstUnit then - local FirstUnitCoordinate = FirstUnit:GetCoordinate() - local Heading = self:GetHeading() - FirstUnitCoordinate.Heading = Heading - return FirstUnitCoordinate + for _,_unit in pairs(Units) do + local FirstUnit = _unit -- Wrapper.Unit#UNIT + + if FirstUnit then + + local FirstUnitCoordinate = FirstUnit:GetCoordinate() + + if FirstUnitCoordinate then + local Heading = self:GetHeading() + FirstUnitCoordinate.Heading = Heading + return FirstUnitCoordinate + end + + end end - BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } ) return nil diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index b0055720f..66df70473 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1187,6 +1187,33 @@ function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) return nil end +--- Send a message to a @{Wrapper.Unit}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param DCS#Duration Duration The duration of the message. +-- @param Wrapper.Unit#UNIT MessageUnit The UNIT object receiving the message. +-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +function POSITIONABLE:MessageToUnit( Message, Duration, MessageUnit, Name ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:isExist() then + if MessageUnit:IsAlive() then + self:GetMessage( Message, Duration, Name ):ToUnit( MessageUnit ) + else + BASE:E( { "Message not sent to Unit; Unit is not alive...", Message = Message, MessageUnit = MessageUnit } ) + end + else + BASE:E( { "Message not sent to Unit; Positionable is not alive ...", Message = Message, Positionable = self, MessageUnit = MessageUnit } ) + end + end + + + return nil +end + --- Send a message of a message type to a @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self @@ -1231,6 +1258,30 @@ function POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Nam return nil end +--- Send a message to a @{Core.Set#SET_UNIT}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param DCS#Duration Duration The duration of the message. +-- @param Core.Set#SET_UNIT MessageSetUnit The SET_UNIT collection receiving the message. +-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. +function POSITIONABLE:MessageToSetUnit( Message, Duration, MessageSetUnit, Name ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:isExist() then + MessageSetUnit:ForEachUnit( + function( MessageGroup ) + self:GetMessage( Message, Duration, Name ):ToUnit( MessageGroup ) + end + ) + end + end + + return nil +end + --- Send a message to the players in the @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index e6dddf6ee..4ade13812 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -95,6 +95,7 @@ Ops/Commander.lua Ops/Chief.lua Ops/CSAR.lua Ops/CTLD.lua +Ops/Awacs.lua AI/AI_Balancer.lua AI/AI_Air.lua