MOOSE/Moose Development/Moose/Ops/PlayerRecce.lua
Applevangelist ca92d7d569 #PLAYERRECCE
* Some nicefications
2022-09-30 19:07:12 +02:00

1042 lines
37 KiB
Lua

--- **Ops** - Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others.
--
-- ## Features:
--
-- * Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others.
-- * Implements visual detection from the helo
-- * Implements optical detection via the Vivianne system and lasing
--
-- ===
--
-- # Demo Missions
--
-- ### Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/).
--
-- ===
--
--
-- ### Authors:
--
-- * Applevengelist (Design & Programming)
--
-- ===
--
-- @module Ops.PlayerRecce
-- @image @image Detection.JPG
-------------------------------------------------------------------------------------------------------------------
-- PLAYERRECCE
-- TODO: PLAYERRECCE
-- TODO: A lot...
-------------------------------------------------------------------------------------------------------------------
--- PLAYERRECCE class.
-- @type PLAYERRECCE
-- @field #string ClassName Name of the class.
-- @field #boolean verbose Switch verbosity.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string version
-- @field #table ViewZone
-- @field #table ViewZoneVisual
-- @field Core.Set#SET_CLIENT PlayerSet
-- @field #string Name
-- @field #number Coalition
-- @field #string CoalitionName
-- @field #boolean debug
-- @field #table LaserSpots
-- @field #table UnitLaserCodes
-- @field #table LaserCodes
-- @field #table ClientMenus
-- @field #table OnStation
-- @field #number minthreatlevel
-- @field #number lasingtime
-- @field #table AutoLase
-- @field Core.Set#SET_CLIENT AttackSet
-- @extends Core.Fsm#FSM
---
--
-- *It is our attitude at the beginning of a difficult task which, more than anything else, which will affect its successful outcome.* (William James)
--
-- ===
--
-- # PLAYERRECCE
--
-- * Allow a player in the Gazelle to detect, smoke, flare, lase and report ground units to others.
-- * Implements visual detection from the helo
-- * Implements optical detection via the Vivianne system and lasing
--
-- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) channel.
--
--
-- @field #PLAYERRECCE
PLAYERRECCE = {
ClassName = "PLAYERRECCE",
verbose = true,
lid = nil,
version = "0.0.1",
ViewZone = {},
ViewZoneVisual = {},
PlayerSet = nil,
debug = true,
LaserSpots = {},
UnitLaserCodes = {},
LaserCodes = {},
ClientMenus = {},
OnStation = {},
minthreatlevel = 0,
lasingtime = 60,
AutoLase = {},
AttackSet = nil,
}
---
-- @type LaserRelativePos
-- @field #string typename Unit type name
PLAYERRECCE.LaserRelativePos = {
["SA342M"] = { x = 1.7, y = 1.2, z = 0 },
["SA342Mistral"] = { x = 1.7, y = 1.2, z = 0 },
["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 },
["SA342L"] = { x = 1.7, y = 1.2, z = 0 },
}
---
-- @type MaxViewDistance
-- @field #string typename Unit type name
PLAYERRECCE.MaxViewDistance = {
["SA342M"] = 5000,
["SA342Mistral"] = 5000,
["SA342Minigun"] = 5000,
["SA342L"] = 5000,
}
---
-- @type Cameraheight
-- @field #string typename Unit type name
PLAYERRECCE.Cameraheight = {
["SA342M"] = 2.85,
["SA342Mistral"] = 2.85,
["SA342Minigun"] = 2.85,
["SA342L"] = 2.85,
}
---
-- @type CanLase
-- @field #string typename Unit type name
PLAYERRECCE.CanLase = {
["SA342M"] = true,
["SA342Mistral"] = true,
["SA342Minigun"] = false,
["SA342L"] = true,
}
---
-- @param #PLAYERRECCE self
-- @return #PLAYERRECCE self
function PLAYERRECCE:New(Name, Coalition, PlayerSet)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #PLAYERRECCE
self.Name = Name or "Blue FACA"
self.Coalition = Coalition or coalition.side.BLUE
self.CoalitionName = UTILS.GetCoalitionName(Coalition)
self.PlayerSet = PlayerSet
self.lid=string.format("PlayerForwardController %s %s | ", self.Name, self.version)
self:SetLaserCodes( { 1688, 1130, 4785, 6547, 1465, 4578 } ) -- set self.LaserCodes
self.lasingtime = 60
self.minthreatlevel = 0
-- FSM start state is STOPPED.
self:SetStartState("Stopped")
self:AddTransition("Stopped", "Start", "Running")
self:AddTransition("*", "Status", "*")
self:AddTransition("*", "RecceOnStation", "*")
self:AddTransition("*", "RecceOffStation", "*")
self:AddTransition("*", "TargetDetected", "*")
self:AddTransition("*", "TargetsSmoked", "*")
self:AddTransition("*", "TargetsFlared", "*")
self:AddTransition("*", "TargetLasing", "*")
self:AddTransition("*", "TargetLOSLost", "*")
self:AddTransition("*", "TargetReport", "*")
self:AddTransition("*", "TargetReportSent", "*")
self:AddTransition("Running", "Stop", "Stopped")
-- Player Events
self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler)
self:HandleEvent(EVENTS.Ejection, self._EventHandler)
self:HandleEvent(EVENTS.Crash, self._EventHandler)
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
self:__Start(-1)
local starttime = math.random(5,10)
self:__Status(-starttime)
return self
end
--- [Internal] Event handling
-- @param #PLAYERRECCE self
-- @param Core.Event#EVENTDATA EventData
-- @return #PLAYERRECCE self
function PLAYERRECCE:_EventHandler(EventData)
self:T(self.lid.."_EventHandler: "..EventData.id)
if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then
if EventData.IniPlayerName then
self:I(self.lid.."Event for player: "..EventData.IniPlayerName)
if self.ClientMenus[EventData.IniPlayerName] then
self.ClientMenus[EventData.IniPlayerName]:Remove()
end
self.ClientMenus[EventData.IniPlayerName] = nil
self.LaserSpots[EventData.IniPlayerName] = nil
self.OnStation[EventData.IniPlayerName] = false
end
elseif EventData.id == EVENTS.PlayerEnterAircraft and EventData.IniCoalition == self.Coalition then
if EventData.IniPlayerName and EventData.IniGroup and self.UseSRS then
self:I(self.lid.."Event for player: "..EventData.IniPlayerName)
self.UnitLaserCodes[EventData.IniPlayerName] = 1688
self.ClientMenus[EventData.IniPlayerName] = nil
self.LaserSpots[EventData.IniPlayerName] = nil
self.OnStation[EventData.IniPlayerName] = false
self:_BuildMenus()
end
end
return self
end
--- [User] Set a table of possible laser codes.
-- Each new RECCE can select a code from this table, default is 1688.
-- @param #PLAYERRECCE self
-- @param #list<#number> LaserCodes
-- @return #PLAYERRECCE
function PLAYERRECCE:SetLaserCodes( LaserCodes )
self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes }
return self
end
--- [User] Set a set of clients which will receive target reports
-- @param #PLAYERRECCE self
-- @param Core.Set#SET_CLIENT AttackSet
-- @return #PLAYERRECCE
function PLAYERRECCE:SetAttackSet(AttackSet)
self.AttackSet = AttackSet
return self
end
--- [Internal] Get the view parameters from a Gazelle camera
-- @param #PLAYERRECCE self
-- @param Wrapper.Unit#UNIT Gazelle
-- @return #number cameraheading in degrees.
-- @return #number cameranodding in degrees.
-- @return #number maxview in meters.
-- @return #boolean cameraison If true, camera is on, else off.
function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle)
self:I(self.lid.."GetGazelleVivianneSight")
local unit = Gazelle -- Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
local dcsunit = Unit.getByName(Gazelle:GetName())
local vivihorizontal = dcsunit:getDrawArgumentValue(215) or 0 -- (not in MiniGun) 1 to -1 -- zero is straight ahead, 1/-1 = 180 deg
local vivivertical = dcsunit:getDrawArgumentValue(216) or 0 -- L/Mistral/Minigun model has no 216, ca 10deg up (=1) and down (=-1)
local vivioff = false
-- -1 = -180, 1 = 180
-- Actual view -0,66 to 0,66
-- Nick view -0,98 to 0,98 for +/- 30°
if vivihorizontal < -0.7 then
vivihorizontal = -0.7
vivioff = true
return 0,0,0,false
elseif vivihorizontal > 0.7 then
vivihorizontal = 0.7
vivioff = true
return 0,0,0,false
end
local horizontalview = vivihorizontal * -180
local verticalview = vivivertical * -30 -- ca +/- 30°
local heading = unit:GetHeading()
local viviheading = (heading+horizontalview)%360
local maxview = self:_GetActualMaxLOSight(unit,viviheading, verticalview,vivioff)
return viviheading, verticalview, maxview, not vivioff
end
return 0,0,0,false
end
--- [Internal] Get the max line of sight based on unit head and camera nod via trigonometrie. Returns 0 if camera is off.
-- @param #PLAYERRECCE self
-- @param Wrapper.Unit#UNIT unit The unit which LOS we want
-- @param #number vheading Heading where the unit or camera is looking
-- @param #number vnod Nod down in degrees
-- @param #boolean vivoff Camera on or off
-- @return #number maxview Max view distance in meters
function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff)
self:I(self.lid.."_GetActualMaxLOSight")
if vivoff then return 0 end
local maxview = 0
if unit and unit:IsAlive() then
local typename = unit:GetTypeName()
maxview = self.MaxViewDistance[typename] or 5000
local CamHeight = self.Cameraheight[typename] or 0
if vnod > 0 then
-- Looking down
-- determine max distance we're looking at
local beta = 90
local gamma = math.floor(90-vnod)
local alpha = math.floor(180-beta-gamma)
local a = unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight
local b = a / math.sin(math.rad(alpha))
local c = b * math.sin(math.rad(gamma))
maxview = c*1.2 -- +20%
end
end
return maxview
end
--- [Internal] Build a ZONE_POLYGON from a given viewport of a unit
-- @param #PLAYERRECCE self
-- @param Wrapper.Unit#UNIT unit The unit which is looking
-- @param #number vheading Heading where the unit or camera is looking
-- @param #number vnod Nod down in degrees
-- @param #number maxview Max line of sight, depending on height
-- @param #number angle Angle left/right to be added to heading to form a triangle
-- @param #boolean camon Camera is switched on
-- @param #boolean draw Draw the zone on the F10 map
-- @return Core.Zone#ZONE_POLYGON ViewZone or nil if camera is off
function PLAYERRECCE:_GetViewZone(unit, vheading, vnod, maxview, angle, camon, draw)
self:I(self.lid.."_GetViewZone")
local viewzone = nil
if not camon then return nil end
if unit and unit:IsAlive() then
local unitname = unit:GetName()
if self.ViewZone[unitname] then
self.ViewZone[unitname]:UndrawZone()
end
--local vheading, vnod, maxview, vivon = self:GetGazelleVivianneSight(unit)
local startpos = unit:GetCoordinate()
local heading1 = (vheading+angle)%360
local heading2 = (vheading-angle)%360
local pos1 = startpos:Translate(maxview,heading1)
local pos2 = startpos:Translate(maxview,heading2)
local array = {}
table.insert(array,startpos:GetVec2())
table.insert(array,pos1:GetVec2())
table.insert(array,pos2:GetVec2())
viewzone = ZONE_POLYGON:NewFromPointsArray(unitname,array)
if draw then
viewzone:DrawZone(-1,{0,0,1},nil,nil,nil,1)
self.ViewZone[unitname] = viewzone
end
end
return viewzone
end
--- [Internal]
--@param #PLAYERRECCE self
--@param Wrapper.Unit#UNIT unit The FACA unit
--@param #boolean camera If true, use the unit's camera for targets in sight
--@return Core.Set#SET_UNIT Set of targets, can be empty!
--@return #number count Count of targets
function PLAYERRECCE:_GetTargetSet(unit,camera)
self:I(self.lid.."_GetTargetSet")
local finaltargets = SET_UNIT:New()
local finalcount = 0
local heading,nod,maxview,angle = 0,30,5000,10
local camon = true
local typename = unit:GetTypeName()
local name = unit:GetName()
if string.find(typename,"SA342") and camera then
heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit)
angle=10
else
-- visual
heading = unit:GetHeading()
nod,maxview,camon = 10,1000,true
angle = 45
end
local zone = self:_GetViewZone(unit,heading,nod,maxview,angle,camon)
if zone then
local redcoalition = "red"
if self.Coalition == coalition.side.RED then
redcoalition = "blue"
end
-- determine what we can see
local startpos = unit:GetCoordinate()
local targetset = SET_UNIT:New():FilterCategories("ground"):FilterActive(true):FilterZones({zone}):FilterCoalitions(redcoalition):FilterOnce()
self:I("Prefilter Target Count = "..targetset:CountAlive())
-- TODO - Threat level filter?
-- TODO - Min distance from unit?
targetset:ForEach(
function(_unit)
local _unit = _unit -- Wrapper.Unit#UNIT
local _unitpos = _unit:GetCoordinate()
if startpos:IsLOS(_unitpos) then
self:I("Adding to final targets: ".._unit:GetName())
finaltargets:Add(_unit:GetName(),_unit)
end
end
)
finalcount = finaltargets:CountAlive()
self:I(string.format("%s Unit: %s | Targets in view %s",self.lid,name,finalcount))
end
return finaltargets, finalcount, zone
end
---[Internal]
--@param #PLAYERRECCE self
--@param Core.Set#SET_UNIT targetset Set of targets, can be empty!
--@return Wrapper.Unit#UNIT Target
function PLAYERRECCE:_GetHVTTarget(targetset)
self:I(self.lid.."_GetHVTTarget")
-- get one target
-- local target = targetset:GetRandom() -- Wrapper.Unit#UNIT
-- sort units
local unitsbythreat = {}
local minthreat = self.minthreatlevel or 0
for _,_unit in pairs(targetset.Set) do
local unit = _unit -- Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
local threat = unit:GetThreatLevel()
if threat >= minthreat then
-- prefer radar units
if unit:HasAttribute("RADAR_BAND1_FOR_ARM") or unit:HasAttribute("RADAR_BAND2_FOR_ARM") or unit:HasAttribute("Optical Tracker") then
threat = 11
end
table.insert(unitsbythreat,{unit,threat})
end
end
end
table.sort(unitsbythreat, function(a,b)
local aNum = a[2] -- Coin value of a
local bNum = b[2] -- Coin value of b
return aNum > bNum -- Return their comparisons, < for ascending, > for descending
end)
return unitsbythreat[1][1]
end
--- [Internal]
--@param #PLAYERRECCE self
--@param Wrapper.Client#CLIENT client The FACA unit
--@param Core.Set#SET_UNIT targetset Set of targets, can be empty!
--@return #PLAYERRECCE self
function PLAYERRECCE:_LaseTarget(client,targetset)
self:I(self.lid.."_LaseTarget")
-- get one target
local target = self:_GetHVTTarget(targetset) -- Wrapper.Unit#UNIT
local playername = client:GetPlayerName()
local laser = nil -- Core.Spot#SPOT
-- set laser
if not self.LaserSpots[playername] then
laser = SPOT:New(client)
if not self.UnitLaserCodes[playername] then
self.UnitLaserCodes[playername] = 1688
end
laser.LaserCode = self.UnitLaserCodes[playername] or 1688
--function laser:OnAfterLaseOff(From,Event,To)
--MESSAGE:New("Finished lasing",15,"Info"):ToClient(client)
--end
self.LaserSpots[playername] = laser
else
laser = self.LaserSpots[playername]
end
if not laser:IsLasing() and target then
local relativecam = self.LaserRelativePos[client:GetTypeName()]
laser:SetRelativeStartPosition(relativecam)
local lasercode = self.UnitLaserCodes[playername] or laser.LaserCode or 1688
local lasingtime = self.lasingtime or 60
local targettype = target:GetTypeName()
laser:LaseOn(target,lasercode,lasingtime)
--MESSAGE:New(string.format("Lasing Target %s with Code %d",targettype,lasercode),15,"Info"):ToClient(client)
self:__TargetLasing(-1,client,target,lasercode,lasingtime)
else
-- still looking at target?
local oldtarget=laser.Target
if targetset:IsNotInSet(oldtarget) then
-- lost LOS
local targettype = oldtarget:GetTypeName()
laser:LaseOff()
self:__TargetLOSLost(-1,client,oldtarget)
--MESSAGE:New(string.format("Lost LOS on target %s!",targettype),15,"Info"):ToClient(client)
end
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param Wrapper.Client#CLIENT client
-- @param Wrapper.Group#GROUP group
-- @param #string playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:_SetClientLaserCode(client,group,playername,code)
self:I(self.lid.."_SetClientLaserCode")
self.UnitLaserCodes[playername] = code or 1688
if self.ClientMenus[playername] then
self.ClientMenus[playername]:Remove()
self.ClientMenus[playername]=nil
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param Wrapper.Client#CLIENT client
-- @param Wrapper.Group#GROUP group
-- @param #string playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:_SwitchOnStation(client,group,playername)
self:I(self.lid.."_SwitchOnStation")
if not self.OnStation[playername] then
self.OnStation[playername] = true
self:__RecceOnStation(-1,client,playername)
else
self.OnStation[playername] = false
self:__RecceOffStation(-1,client,playername)
end
if self.ClientMenus[playername] then
self.ClientMenus[playername]:Remove()
self.ClientMenus[playername]=nil
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param Wrapper.Client#CLIENT client
-- @param Wrapper.Group#GROUP group
-- @param #string playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:_SwitchLasing(client,group,playername)
self:I(self.lid.."_SwitchLasing")
if not self.AutoLase[playername] then
self.AutoLase[playername] = true
MESSAGE:New("Lasing is now ON",10,self.Name or "FACA"):ToClient(client)
else
self.AutoLase[playername] = false
MESSAGE:New("Lasing is now OFF",10,self.Name or "FACA"):ToClient(client)
end
if self.ClientMenus[playername] then
self.ClientMenus[playername]:Remove()
self.ClientMenus[playername]=nil
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param Wrapper.Client#CLIENT client
-- @param Wrapper.Group#GROUP group
-- @param #string playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:_WIP(client,group,playername)
self:I(self.lid.."_WIP")
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param Wrapper.Client#CLIENT client
-- @param Wrapper.Group#GROUP group
-- @param #string playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:_SmokeTargets(client,group,playername)
self:I(self.lid.."_SmokeTargets")
local cameraset = self:_GetTargetSet(client,true) -- Core.Set#SET_UNIT
local visualset = self:_GetTargetSet(client,false) -- Core.Set#SET_UNIT
cameraset:AddSet(visualset)
self:__TargetsSmoked(-1,client,playername,cameraset)
local highsmoke = SMOKECOLOR.Orange
local medsmoke = SMOKECOLOR.White
local lowsmoke = SMOKECOLOR.Green
local lasersmoke = SMOKECOLOR.Red
local laser = self.LaserSpots[playername] -- Core.Spot#SPOT
-- laser targer gets extra smoke
if laser and laser.Target and laser.Target:IsAlive() then
laser.Target:GetCoordinate():Smoke(lasersmoke)
if cameraset:IsInSet(laser.Target) then
cameraset:Remove(laser.Target:GetName(),true)
end
end
-- smoke everything else
for _,_unit in pairs(cameraset.Set) do
local unit = _unit --Wrapper.Unit#UNIT
if unit then
local coord = unit:GetCoordinate()
local threat = unit:GetThreatLevel()
if coord then
local color = lowsmoke
if threat > 7 then
color = medsmoke
elseif threat > 2 then
color = lowsmoke
end
coord:Smoke(color)
end
end
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param Wrapper.Client#CLIENT client
-- @param Wrapper.Group#GROUP group
-- @param #string playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:_FlareTargets(client,group,playername)
self:I(self.lid.."_SmokeTargets")
local cameraset = self:_GetTargetSet(client,true) -- Core.Set#SET_UNIT
local visualset = self:_GetTargetSet(client,false) -- Core.Set#SET_UNIT
cameraset:AddSet(visualset)
self:__TargetsFlared(-1,client,playername,cameraset)
local highsmoke = FLARECOLOR.Yellow
local medsmoke = FLARECOLOR.White
local lowsmoke = FLARECOLOR.Green
local lasersmoke = FLARECOLOR.Red
local laser = self.LaserSpots[playername] -- Core.Spot#SPOT
-- laser targer gets extra smoke
if laser and laser.Target and laser.Target:IsAlive() then
laser.Target:GetCoordinate():Flare(lasersmoke)
if cameraset:IsInSet(laser.Target) then
cameraset:Remove(laser.Target:GetName(),true)
end
end
-- smoke everything else
for _,_unit in pairs(cameraset.Set) do
local unit = _unit --Wrapper.Unit#UNIT
if unit then
local coord = unit:GetCoordinate()
local threat = unit:GetThreatLevel()
if coord then
local color = lowsmoke
if threat > 7 then
color = medsmoke
elseif threat > 2 then
color = lowsmoke
end
coord:Flare(color)
end
end
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param Wrapper.Client#CLIENT client
-- @param Wrapper.Group#GROUP group
-- @param #string playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:_ReportLaserTargets(client,group,playername)
self:I(self.lid.."_ReportLaserTargets")
local targetset, number = self:_GetTargetSet(client,true)
if number > 0 and self.AutoLase[playername] then
local Settings = ( client and _DATABASE:GetPlayerSettings( playername ) ) or _SETTINGS
local target = self:_GetHVTTarget(targetset) -- the one we're lasing
local ThreatLevel = target:GetThreatLevel()
local ThreatLevelText = "high"
if ThreatLevel > 3 and ThreatLevel < 8 then
ThreatLevelText = "medium"
elseif ThreatLevel <= 3 then
ThreatLevelText = "low"
end
local ThreatGraph = "[" .. string.rep( "", ThreatLevel ) .. string.rep( "", 10 - ThreatLevel ) .. "]: "..ThreatLevel
local report = REPORT:New("Lasing Report")
report:Add(string.rep("-",15))
report:Add("Target type: "..target:GetTypeName())
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
--report:Add("Location: "..target:GetCoordinate():ToStringA2G(client,Settings))
report:Add("Laser Code: "..self.UnitLaserCodes[playername] or 1688)
report:Add(string.rep("-",15))
local text = report:Text()
self:__TargetReport(-1,client,targetset,target,text)
else
local report = REPORT:New("Lasing Report")
report:Add(string.rep("-",15))
report:Add("N O T A R G E T S")
report:Add(string.rep("-",15))
local text = report:Text()
self:__TargetReport(-1,client,nil,nil,text)
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param Wrapper.Client#CLIENT client
-- @param Wrapper.Group#GROUP group
-- @param #string playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:_ReportVisualTargets(client,group,playername)
self:I(self.lid.."_ReportVisualTargets")
local targetset, number = self:_GetTargetSet(client,false)
if number > 0 then
local Settings = ( client and _DATABASE:GetPlayerSettings( playername ) ) or _SETTINGS
local ThreatLevel = targetset:CalculateThreatLevelA2G()
local ThreatLevelText = "high"
if ThreatLevel > 3 and ThreatLevel < 8 then
ThreatLevelText = "medium"
elseif ThreatLevel <= 3 then
ThreatLevelText = "low"
end
local ThreatGraph = "[" .. string.rep( "", ThreatLevel ) .. string.rep( "", 10 - ThreatLevel ) .. "]: "..ThreatLevel
local report = REPORT:New("Target Report")
report:Add(string.rep("-",15))
report:Add("Target count: "..number)
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
--report:Add("Location: "..client:GetCoordinate():ToStringA2G(client,Settings))
report:Add(string.rep("-",15))
local text = report:Text()
self:__TargetReport(-1,client,targetset,nil,text)
else
local report = REPORT:New("Target Report")
report:Add(string.rep("-",15))
report:Add("N O T A R G E T S")
report:Add(string.rep("-",15))
local text = report:Text()
self:__TargetReport(-1,client,nil,nil,text)
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #PLAYERRECCE self
function PLAYERRECCE:_BuildMenus()
self:I(self.lid.."_BuildMenus")
local clients = self.PlayerSet -- Core.Set#SET_CLIENT
local clientset = clients:GetSetObjects()
for _,_client in pairs(clientset) do
local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then
local playername = client:GetPlayerName()
if not self.UnitLaserCodes[playername] then
self:_SetClientLaserCode(nil,nil,playername,1688)
end
local group = client:GetGroup()
if not self.ClientMenus[playername] then
local canlase = self.CanLase[client:GetTypeName()]
self.ClientMenus[playername] = MENU_GROUP:New(group,self.Name or "RECCE")
local txtonstation = self.OnStation[playername] and "ON" or "OFF"
local text = string.format("Switch On-Station (%s)",txtonstation)
local onstationmenu = MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchOnStation,self,client,group,playername)
if self.OnStation[playername] then
local smokemenu = MENU_GROUP_COMMAND:New(group,"Smoke Targets",self.ClientMenus[playername],self._SmokeTargets,self,client,group,playername)
local smokemenu = MENU_GROUP_COMMAND:New(group,"Flare Targets",self.ClientMenus[playername],self._FlareTargets,self,client,group,playername)
if canlase then
local txtonstation = self.AutoLase[playername] and "ON" or "OFF"
local text = string.format("Switch Lasing (%s)",txtonstation)
local lasemenu = MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchLasing,self,client,group,playername)
end
local targetmenu = MENU_GROUP:New(group,"Target Report",self.ClientMenus[playername])
if canlase then
local reportL = MENU_GROUP_COMMAND:New(group,"Laser Target",targetmenu,self._ReportLaserTargets,self,client,group,playername)
end
local reportV = MENU_GROUP_COMMAND:New(group,"Visual Targets",targetmenu,self._ReportVisualTargets,self,client,group,playername)
if canlase then
local lasecodemenu = MENU_GROUP:New(group,"Set Laser Code",self.ClientMenus[playername])
local codemenu = {}
for _,_code in pairs(self.LaserCodes) do
--self._SetClientLaserCode,self,client,group,playername)
if _code == self.UnitLaserCodes[playername] then
_code = tostring(_code).."(*)"
end
codemenu[playername.._code] = MENU_GROUP_COMMAND:New(group,tostring(_code),lasecodemenu,self._SetClientLaserCode,self,client,group,playername,_code)
end
end
end
end
end
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param Core.Set#SET_UNIT targetset
-- @param Wrapper.Client#CLIENT client
-- @param #string playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:_CheckNewTargets(targetset,client,playername)
self:I(self.lid.."_CheckNewTargets")
targetset:ForEachUnit(
function(unit)
if unit and unit:IsAlive() then
if not unit.PlayerRecceDetected then
unit.PlayerRecceDetected = {
detected = true,
recce = client,
playername = playername,
timestamp = timer.getTime()
}
self:__TargetDetected(-1,unit,client,playername)
end
end
end
)
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterStatus(From, Event, To)
self:I({From, Event, To})
self:_BuildMenus()
self.PlayerSet:ForEachClient(
function(Client)
local client = Client -- Wrapper.Client#CLIENT
local playername = client:GetPlayerName()
if client and client:IsAlive() and self.OnStation[playername] then
-- targets on camera
local targetset, targetcount, tzone = self:_GetTargetSet(client,true)
if targetset then
if self.ViewZone[playername] then
self.ViewZone[playername]:UndrawZone()
end
if self.debug and tzone then
self.ViewZone[playername]=tzone:DrawZone(self.Coalition,{0,0,1},nil,nil,nil,1)
end
end
self:I({targetcount=targetcount})
-- visual targets
local vistargetset, vistargetcount, viszone = self:_GetTargetSet(client,false)
if vistargetset then
if self.ViewZoneVisual[playername] then
self.ViewZoneVisual[playername]:UndrawZone()
end
if self.debug and viszone then
self.ViewZoneVisual[playername]=viszone:DrawZone(self.Coalition,{1,0,0},nil,nil,nil,3)
end
end
self:I({targetcount=targetcount})
-- lase targets on camera
if targetcount > 0 then
if self.CanLase[client:GetTypeName()] and self.AutoLase[playername] then
-- DONE move to lase at will
self:_LaseTarget(client,targetset)
end
end
-- Report new targets
targetset:AddSet(vistargetset)
self:_CheckNewTargets(targetset,client,playername)
end
end
)
self:__Status(-10)
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Client#CLIENT Client
-- @param #string Playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername)
self:I({From, Event, To})
local callsign = Client:GetGroup():GetCustomCallSign(true,true)
local coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition)
local text = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext)
MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition)
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Client#CLIENT Client
-- @param #string Playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername)
self:I({From, Event, To})
local callsign = Client:GetGroup():GetCustomCallSign(true,true)
local coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition)
local text = string.format("All stations, FACA %s leaving station\nat %s, going home!",callsign, coordtext)
MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition)
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT Target
-- @param Wrapper.Client#CLIENT Client
-- @param #string Playername
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetDetected(From, Event, To, Target, Client, Playername)
self:I({From, Event, To})
if self.debug then
local text = string.format("New target %s detected by %s!",Target:GetTypeName(),Playername)
MESSAGE:New(text,10,self.Name or "FACA"):ToCoalition(self.Coalition)
end
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Client#CLIENT Client
-- @param #string Playername
-- @param Core.Set#SET_UNIT TargetSet
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet)
self:I({From, Event, To})
local callsign = Client:GetGroup():GetCustomCallSign(true,true)
local coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition)
local text = string.format("All stations, FACA %s smoked targets\nat %s!",callsign, coordtext)
MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition)
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Client#CLIENT Client
-- @param #string Playername
-- @param Core.Set#SET_UNIT TargetSet
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet)
self:I({From, Event, To})
local callsign = Client:GetGroup():GetCustomCallSign(true,true)
local coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition)
local text = string.format("All stations, FACA %s flared\ntargets at %s!",callsign, coordtext)
MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition)
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Client#CLIENT Client
-- @param Wrapper.Unit#UNIT Target
-- @param #number Lasercode
-- @param #number Lasingtime
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime)
self:I({From, Event, To})
local callsign = Client:GetGroup():GetCustomCallSign(true,true)
local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
local coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
local targettype = Target:GetTypeName()
local text = string.format("All stations, FACA %s lasing %s\nat %s!\nCode %d, Duration %d seconds!",callsign, targettype, coordtext, Lasercode, Lasingtime)
MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition)
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Client#CLIENT Client
-- @param Wrapper.Unit#UNIT Target
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target)
self:I({From, Event, To})
local callsign = Client:GetGroup():GetCustomCallSign(true,true)
local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
local coord = Client:GetCoordinate()
local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
local targettype = Target:GetTypeName()
local text = string.format("All stations, FACA %s lost sight of %s\nat %s!",callsign, targettype, coordtext)
MESSAGE:New(text,15,self.Name or "FACA"):ToCoalition(self.Coalition)
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Client#CLIENT Client
-- @param Core.Set#SET_UNIT TargetSet
-- @param Wrapper.Unit#UNIT Target
-- @param #string Text
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetReport(From, Event, To, Client, TargetSet, Target, Text)
self:I({From, Event, To})
MESSAGE:New(Text,45,self.Name or "FACA"):ToClient(Client)
if self.AttackSet then
-- send message to AttackSet
for _,_client in pairs(self.AttackSet.Set) do
local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then
MESSAGE:New(Text,45,self.Name or "FACA"):ToClient(client)
end
end
end
self:__TargetReportSent(-2,Client, TargetSet, Target, Text)
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Client#CLIENT Client
-- @param Core.Set#SET_UNIT TargetSet
-- @param Wrapper.Unit#UNIT Target
-- @param #string Text
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterTargetReportSent(From, Event, To, Client, TargetSet, Target, Text)
self:I({From, Event, To})
return self
end
--- [Internal]
-- @param #PLAYERRECCE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #PLAYERRECCE self
function PLAYERRECCE:onafterStop(From, Event, To)
self:I({From, Event, To})
-- Player Events
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
return self
end
--[[ test script
local PlayerSet = SET_CLIENT:New():FilterCoalitions("blue"):FilterActive(true):FilterCategories("helicopter"):FilterStart()
local Attackers = SET_CLIENT:New():FilterCoalitions("blue"):FilterActive(true):FilterStart()
local myrecce = PLAYERRECCE:New("1st Forward FACA",coalition.side.BLUE,PlayerSet)
myrecce:SetAttackSet(Attackers)
--]]