Jump to content

Recon mission in DCS?


jackmckay

Recommended Posts

If you're using Fog of War map setting you can use Visual Recon Mode to spot targets:

https://www.youtube.com/watch?v=DMPX-rgR-vs

Other than this, I guess you could initially spawn hidden units and then replace them with copies that are visible on the map when they enter the moving zone. If you want to output specific location information you'll have to use scripting.

  • Like 1

Awaiting: DCS F-15C

Win 10 i5-9600KF 4.6 GHz 64 GB RAM RTX2080Ti 11GB -- Win 7 64 i5-6600K 3.6 GHz 32 GB RAM GTX970 4GB -- A-10C, F-5E, Su-27, F-15C, F-14B, F-16C missions in User Files

 

Link to comment
Share on other sites

Tnx man. I'll check it.

Edit: I was thinking of like not using visual recon mode from cockpit rather scripted solution that just places markers on map weather AI or player driven plane or even ground unit is scanning thru. All based on moving detection zone. Think it needs some scripting approach. Tx on reply anyway.


Edited by jackmckay
Link to comment
Share on other sites

It should be fairly straightforward with Lua-scripting. While moving, simply check every second or so which units are within detection range of the scout, and if in range, check if the scout has LOS to the unit in range, perhaps add some detection probability. If detected and wasn't detected before, mark it on the map with trigger.action.markToCoalition() , and add the unit itself to the table of detected units so it won't be marked again. You don't even need moving zones for that; probably two hours worth of coding to add niceties like audio feedback and/or messages. Add half an hour to add the ability top remove marks after timeout 🙂

 

-ch


Edited by cfrag
Link to comment
Share on other sites

OK, here's a mission that does what you want. I've thrown the recon script together in a rather short time, so it's not really debugged. The script loads at mission start (note: I load another script, dcsCommon, which is my library of common DCS mission methods. That one loads first).

In this mission, the AI flight "Recon" does the detecting, it's turned on by the the ONCE "Enable recon mode on Recon" trigger that invokes cfxReconMode.addScout("Recon"). That adds the units called "Recon" (the F-14) to the pool of reconnaissance Planes that the script watches, and starts reporting and marking all enemy units it detects on the F-10 Map. If you click on a mark, it expands to the enemy group's name. 

image.png

image.png

 

The marks disappear after 10 minutes

You can configure the min and max visibility; the script makes it so that max visibility increases with the scout's altitude.

To see what it does, run the mission, sit in the A-10A plane on ground (if you don't have F3, simply change to any plane you may have), and simply watch the F10 map with the F-14. 

Accelerate time to see the marks disappear after 10 minutes.

Hope this helps. 

-ch

reconnai demonstray.miz


Edited by cfrag
  • Like 3
  • Thanks 1
Link to comment
Share on other sites

And here's the raw script. Note that although I'm having ideas about blacklisting groups (so they are never reported) and prioListing (so they are marked different), they are not in the script because I ran out of time 🙂. Invocations starting with dcsCommon are to my library. For brevity I won't discuss them here, the calls are obvious, and it's included in the mission.

 

cfxReconMode = {}
cfxReconMode.version = "1.0.0"

--[[--
VERSION HISTORY
 1.0.0 - initial version 
 
 cfxReconMode is a script that allows units to perform reconnaissance
 missions and, after detecting units on, marks them on the map with 
 markers 
 
--]]--
cfxReconMode.ups = 1 -- updates per second
cfxReconMode.scouts = {} -- units that are performing scouting. 
cfxReconMode.detectedGroups = {} -- so we know which have been detected
cfxReconMode.marksFadeAfter = 600 -- after detection, marks disappear after
                     -- this amount of seconds. -1 means no fade
					 -- 600 is ten minutes
cfxReconMode.prioList = {} -- group names that are high prio
cfxReconMode.blackList = {} -- group names athat are never detected
cfxReconMode.detectionMinRange = 3000 -- meters at ground level
cfxReconMode.detectionMaxRange = 4000 -- meters at max alt (10'000m)
cfxReconMode.maxAlt = 10000 -- alt for maxrange

cfxReconMode.callbacks = {} -- sig: cb(reason, side, scout, group)
cfxReconMode.uuidCount = 0 -- for unique marks 

function cfxReconMode.uuid()
	cfxReconMode.uuidCount = cfxReconMode.uuidCount + 1
	return cfxReconMode.uuidCount
end

function cfxReconMode.addCallback(theCB)
	table.insert(cfxReconMode.callbacks, theCB)
end

function cfxReconMode.invokeCallbacks(reason, theSide, theSout, theGroup)
	for idx, theCB in pairs(cfxReconMode.callbacks) do 
		theCB(reason, theSide, theScout, theGroup)
	end
end

function cfxReconMode.addScout(theUnit)
	if not theUnit then 
		trigger.action.outText("+++cfxRecon: nil Unit on add", 30)
		return
	end
	if type(theUnit) == "string" then 
		trigger.action.outText("+++cfxRecon: will access vby name: " .. theUnit, 30)
		local u = Unit.getByName(theUnit) 
		theUnit = u
	end 
	
	if not theUnit then 
		trigger.action.outText("+++cfxRecon: did not find unit on add", 30)
		return 
	end	
	cfxReconMode.scouts[theUnit:getName()] = theUnit
end

function cfxReconMode.removeScout(theUnit)
	if type(theUnit) == "string" then 
		theUnit = Unit:getByName(theUnit) 
	end 
	if not theUnit then return end	
	cfxReconMode.scouts[theUnit:getName()] = nil
end

function cfxReconMode.canDetect(scoutPos, theGroup, visRange)
	-- determine if a member of theGroup can be seen from 
	-- scoutPos at visRange 
	-- returns true and pos when detected
	local allUnits = theGroup:getUnits()
	for idx, aUnit in pairs(allUnits) do
		if aUnit:isExist() and aUnit:getLife() >= 1 then 
			local uPos = aUnit:getPoint()
			uPos.y = uPos.y + 3 -- raise my 3 meters
			local d = dcsCommon.distFlat(scoutPos, uPos) 
			if d < visRange then 
				-- is in visual range. do we have LOS?
				if land.isVisible(scoutPos, uPos) then 
					-- group is visible, stop here, return true
					return true, uPos
				end
			end
		end		
	end
	return false, nil -- nothing visible
end

function cfxReconMode.placeMarkForUnit(location, theSide, theGroup) 
	local theID = cfxReconMode.uuid()
	trigger.action.markToCoalition(
					theID, 
					"Contact: "..theGroup:getName(), 
					location, 
					theSide, 
					false, 
					nil)
	return theID
end

function cfxReconMode.removeMarkForArgs(args)
	local theSide = args[1]
	local theScout = args[2]
	local theGroup = args[3]
	local theID = args[4]
	
	trigger.action.removeMark(theID)
	cfxReconMode.detectedGroups[theGroup:getName()] = nil 
	
	-- invoke callbacks
	cfxReconMode.invokeCallbacks("removed", theSide, theScout, theGroup)
end 


function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
	-- put a mark on the map 
	local theID = cfxReconMode.placeMarkForUnit(theLoc, mySide, theGroup)
	
	-- schedule removal if desired 
	if cfxReconMode.marksFadeAfter > 0 then 
		args = {mySide, theScout, theGroup, theID}
		timer.scheduleFunction(cfxReconMode.removeMarkForArgs, args, timer.getTime() + cfxReconMode.marksFadeAfter)
	end
	
	-- say something
	trigger.action.outTextForCoalition(
			mySide,
			theScout:getName() .. " reports new ground contact " .. theGroup:getName(),
			30
		)
	-- play a sound 
	trigger.action.outSoundForCoalition(mySide, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
	
	-- invoke callbacks
	cfxReconMode.invokeCallbacks("detected", mySide, theSout, theGroup)
end

function cfxReconMode.performReconForUnit(theScout)
	if not theScout then return end 
	if not theScout:isExist() then return end 
	-- get altitude above ground to calculate visual range 
	local alt = dcsCommon.getUnitAGL(theScout)
	local visRange = dcsCommon.lerp(cfxReconMode.detectionMinRange, cfxReconMode.detectionMaxRange, alt/cfxReconMode.maxAlt)
	local scoutPos = theScout:getPoint()
	-- figure out which groups we are looking for
	local myCoal = theScout:getCoalition()
	local enemyCoal = 1 
	if myCoal == 1 then enemyCoal = 2 end 
	
	-- iterate all enemy units until we find one 
	-- and then stop this iteration (can only detect one 
	-- group per pass)
	local enemyGroups = coalition.getGroups(enemyCoal)
	for idx, theGroup in pairs (enemyGroups) do 
		-- make sure it's a ground unit 
		local isGround = theGroup:getCategory() == 2
		if theGroup:isExist() and isGround then 
			local visible, location = cfxReconMode.canDetect(scoutPos, theGroup, visRange)
			if visible then 
				-- see if we already detected this one 
				
				if cfxReconMode.detectedGroups[theGroup:getName()] == nil then 
					-- visible and not yet seen 
					-- perhaps add some percent chance now 
					-- remember that we know this group 
					cfxReconMode.detectedGroups[theGroup:getName()] = theGroup
					cfxReconMode.detectedGroup(myCoal, theScout, theGroup, location)
					return -- stop, as we only detect one group per pass
				end
			end
		end
	end
end

function cfxReconMode.update()
	-- schedule next call 
	timer.scheduleFunction(cfxReconMode.update, {}, timer.getTime() + 1/cfxReconMode.ups)
	
	-- now process all scouts
	for idx, scout in pairs(cfxReconMode.scouts) do 
		cfxReconMode.performReconForUnit(scout)
	end
end

function cfxReconMode.start()
	-- start update cycle
	cfxReconMode.update()
	
	trigger.action.outText("cfx Recon version " .. cfxReconMode.version .. " started.", 30)
	return true
end

if not cfxReconMode.start() then 
	cfxReconMode = nil
end



 
 

 

  • Like 1
  • Thanks 2
Link to comment
Share on other sites

50 minutes ago, jackmckay said:

Is your "dcsCommon" script extrapolated from egg. Mist or just your own? Is it mandatory?

It's my own, completely independent of mist or other libraries. It's mandatory in that the recon scripts needs the three methods lerp(), getUnitAGL(), and distFlat() - I'm too lazy to copy them over individually, so I simply include the entire lib. Since it doesn't do anything by itself, it won't waste performance, and simply uses up some 10KB of memory.  You could, if you wanted to save a few bytes, delete all other methods from dcsCommon (except dist(), which is invoked by distFlat) and end up with a smaller lib. Not worth the effort, IMHO. 

  • Like 2
Link to comment
Share on other sites

  • 1 year later...
On 10/14/2021 at 6:12 PM, cfrag said:

It's my own, completely independent of mist or other libraries. It's mandatory in that the recon scripts needs the three methods lerp(), getUnitAGL(), and distFlat() - I'm too lazy to copy them over individually, so I simply include the entire lib. Since it doesn't do anything by itself, it won't waste performance, and simply uses up some 10KB of memory.  You could, if you wanted to save a few bytes, delete all other methods from dcsCommon (except dist(), which is invoked by distFlat) and end up with a smaller lib. Not worth the effort, IMHO. 

Hello,
I tested your script which works perfectly for naval units.
I wanted to use it for my MH-60R mod, helicopter for anti-submarine hunting, I encounter a problem, submarines on the surface are detected.
But as soon as they are in depth, they are no longer detectable by the script.
In my mod I immerse a dipping sonar with some range to simulate detection,
Is there a possibility to modify your script so that these specific units are detectable while diving?
Thanks

Link to comment
Share on other sites

On 10/14/2021 at 6:12 PM, cfrag said:

It's my own, completely independent of mist or other libraries. It's mandatory in that the recon scripts needs the three methods lerp(), getUnitAGL(), and distFlat() - I'm too lazy to copy them over individually, so I simply include the entire lib. Since it doesn't do anything by itself, it won't waste performance, and simply uses up some 10KB of memory.  You could, if you wanted to save a few bytes, delete all other methods from dcsCommon (except dist(), which is invoked by distFlat) and end up with a smaller lib. Not worth the effort, IMHO. 

I managed to detect submarines while diving, but you shouldn't put them too deep, just deep enough not to see them while flying. Could there be better settings?
I changed the line:
local visRange = dcsCommon.lerp(cfxReconMode.detectionMinRange, cfxReconMode.detectionMaxRange, 0) ...
and
cfxReconMode.detectionMinRange = 5000   -- meters
cfxReconMode.detectionMaxRange = 5000  -- meters
cfxReconMode.maxAlt = 30 -- meters

Do you authorize me to use your script in the few missions that I provide with my mod?

Link to comment
Share on other sites

9 hours ago, Tanuki44 said:

I wanted to use it for my MH-60R mod, helicopter for anti-submarine hunting, I encounter a problem, submarines on the surface are detected.
But as soon as they are in depth, they are no longer detectable by the script.

I haven't checked the code, and I assume that may be because of the visibility calculation the script does by invoking land.isVisible() from DCS. I have never experimented with submarines, what is their returned altitude? if it's negative, it should be relatively easy to make a quick (and faster than isVisible) decision in the code that should work on most map locations (well, with the possible exception of the dead sea and Jericho regions in the upcoming Sinai map, which are well below sea level (at least in RL). But subtracting a units altitude from land.getHeight should cover that as well - if it's negative, we have a unit below the surface and the amount tells us gy how much (so subs that are too deep can't be detected. Well, not an issue in the dead sea, but it's still a sound algorithm 🙂.

 

8 hours ago, Tanuki44 said:

Do you authorize me to use your script in the few missions that I provide with my mod?

Of course!

Cheers,

-ch

 


Edited by cfrag
Link to comment
Share on other sites

1 hour ago, Tanuki44 said:

if it remains under water, no detection

That's probably because the script does a isVisible() check in canDetect(). I don't know how DCS determines visibility, but I believe that there is a high probability that if the sub is submerged it returns false for isVisible. 

To detect if a sub is submerged, I still propose that you use the land.getHeight() check versus the unit's altitude (y-value from unit.getPosition). If the unit's altitude is less than land.getHeight I posit that the sub is submerged. I have never tried that, though.

If we want to make the recon script into an SSW script, we could modigfy the canDetect() method to first detect if the target is submerged, and if so, come up with our own algorithm to determine if it has been detected: see if there are buoys around, or other friendly vessels etc.

2 hours ago, Tanuki44 said:

Is there a script to dynamically add units to a group during a mission?

Not in current versions of DCS. But before you dynamically spawn a group you can add units to that group, no problem. Once the group is spawned into the mission, though, it's locked and you can only remove units. 

4 hours ago, Tanuki44 said:

The depth value is positive, I experienced a value of 100 feet which is low for a current submarine,

How did you obtain that value? And we need to remember that DCS works in sensible units. 100m is 300 silly feet depth

 

Link to comment
Share on other sites

the value of 100 is the depth assigned to the submarine unit

sub.png

👍 I found the function dcsCommon.getUnitAGL(theUnit), I will look at the returned values.

I created a naval unit 'sonobuoys' to attach the recon script to them,

It works fine, so I just need to get a good return from the script.
 

Link to comment
Share on other sites

  • 1 month later...
On 10/14/2021 at 3:13 PM, cfrag said:

And here's the raw script. Note that although I'm having ideas about blacklisting groups (so they are never reported) and prioListing (so they are marked different), they are not in the script because I ran out of time 🙂. Invocations starting with dcsCommon are to my library. For brevity I won't discuss them here, the calls are obvious, and it's included in the mission.

 

cfxReconMode = {}
cfxReconMode.version = "1.0.0"

--[[--
VERSION HISTORY
 1.0.0 - initial version 
 
 cfxReconMode is a script that allows units to perform reconnaissance
 missions and, after detecting units on, marks them on the map with 
 markers 
 
--]]--
cfxReconMode.ups = 1 -- updates per second
cfxReconMode.scouts = {} -- units that are performing scouting. 
cfxReconMode.detectedGroups = {} -- so we know which have been detected
cfxReconMode.marksFadeAfter = 600 -- after detection, marks disappear after
                     -- this amount of seconds. -1 means no fade
					 -- 600 is ten minutes
cfxReconMode.prioList = {} -- group names that are high prio
cfxReconMode.blackList = {} -- group names athat are never detected
cfxReconMode.detectionMinRange = 3000 -- meters at ground level
cfxReconMode.detectionMaxRange = 4000 -- meters at max alt (10'000m)
cfxReconMode.maxAlt = 10000 -- alt for maxrange

cfxReconMode.callbacks = {} -- sig: cb(reason, side, scout, group)
cfxReconMode.uuidCount = 0 -- for unique marks 

function cfxReconMode.uuid()
	cfxReconMode.uuidCount = cfxReconMode.uuidCount + 1
	return cfxReconMode.uuidCount
end

function cfxReconMode.addCallback(theCB)
	table.insert(cfxReconMode.callbacks, theCB)
end

function cfxReconMode.invokeCallbacks(reason, theSide, theSout, theGroup)
	for idx, theCB in pairs(cfxReconMode.callbacks) do 
		theCB(reason, theSide, theScout, theGroup)
	end
end

function cfxReconMode.addScout(theUnit)
	if not theUnit then 
		trigger.action.outText("+++cfxRecon: nil Unit on add", 30)
		return
	end
	if type(theUnit) == "string" then 
		trigger.action.outText("+++cfxRecon: will access vby name: " .. theUnit, 30)
		local u = Unit.getByName(theUnit) 
		theUnit = u
	end 
	
	if not theUnit then 
		trigger.action.outText("+++cfxRecon: did not find unit on add", 30)
		return 
	end	
	cfxReconMode.scouts[theUnit:getName()] = theUnit
end

function cfxReconMode.removeScout(theUnit)
	if type(theUnit) == "string" then 
		theUnit = Unit:getByName(theUnit) 
	end 
	if not theUnit then return end	
	cfxReconMode.scouts[theUnit:getName()] = nil
end

function cfxReconMode.canDetect(scoutPos, theGroup, visRange)
	-- determine if a member of theGroup can be seen from 
	-- scoutPos at visRange 
	-- returns true and pos when detected
	local allUnits = theGroup:getUnits()
	for idx, aUnit in pairs(allUnits) do
		if aUnit:isExist() and aUnit:getLife() >= 1 then 
			local uPos = aUnit:getPoint()
			uPos.y = uPos.y + 3 -- raise my 3 meters
			local d = dcsCommon.distFlat(scoutPos, uPos) 
			if d < visRange then 
				-- is in visual range. do we have LOS?
				if land.isVisible(scoutPos, uPos) then 
					-- group is visible, stop here, return true
					return true, uPos
				end
			end
		end		
	end
	return false, nil -- nothing visible
end

function cfxReconMode.placeMarkForUnit(location, theSide, theGroup) 
	local theID = cfxReconMode.uuid()
	trigger.action.markToCoalition(
					theID, 
					"Contact: "..theGroup:getName(), 
					location, 
					theSide, 
					false, 
					nil)
	return theID
end

function cfxReconMode.removeMarkForArgs(args)
	local theSide = args[1]
	local theScout = args[2]
	local theGroup = args[3]
	local theID = args[4]
	
	trigger.action.removeMark(theID)
	cfxReconMode.detectedGroups[theGroup:getName()] = nil 
	
	-- invoke callbacks
	cfxReconMode.invokeCallbacks("removed", theSide, theScout, theGroup)
end 


function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
	-- put a mark on the map 
	local theID = cfxReconMode.placeMarkForUnit(theLoc, mySide, theGroup)
	
	-- schedule removal if desired 
	if cfxReconMode.marksFadeAfter > 0 then 
		args = {mySide, theScout, theGroup, theID}
		timer.scheduleFunction(cfxReconMode.removeMarkForArgs, args, timer.getTime() + cfxReconMode.marksFadeAfter)
	end
	
	-- say something
	trigger.action.outTextForCoalition(
			mySide,
			theScout:getName() .. " reports new ground contact " .. theGroup:getName(),
			30
		)
	-- play a sound 
	trigger.action.outSoundForCoalition(mySide, "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav")
	
	-- invoke callbacks
	cfxReconMode.invokeCallbacks("detected", mySide, theSout, theGroup)
end

function cfxReconMode.performReconForUnit(theScout)
	if not theScout then return end 
	if not theScout:isExist() then return end 
	-- get altitude above ground to calculate visual range 
	local alt = dcsCommon.getUnitAGL(theScout)
	local visRange = dcsCommon.lerp(cfxReconMode.detectionMinRange, cfxReconMode.detectionMaxRange, alt/cfxReconMode.maxAlt)
	local scoutPos = theScout:getPoint()
	-- figure out which groups we are looking for
	local myCoal = theScout:getCoalition()
	local enemyCoal = 1 
	if myCoal == 1 then enemyCoal = 2 end 
	
	-- iterate all enemy units until we find one 
	-- and then stop this iteration (can only detect one 
	-- group per pass)
	local enemyGroups = coalition.getGroups(enemyCoal)
	for idx, theGroup in pairs (enemyGroups) do 
		-- make sure it's a ground unit 
		local isGround = theGroup:getCategory() == 2
		if theGroup:isExist() and isGround then 
			local visible, location = cfxReconMode.canDetect(scoutPos, theGroup, visRange)
			if visible then 
				-- see if we already detected this one 
				
				if cfxReconMode.detectedGroups[theGroup:getName()] == nil then 
					-- visible and not yet seen 
					-- perhaps add some percent chance now 
					-- remember that we know this group 
					cfxReconMode.detectedGroups[theGroup:getName()] = theGroup
					cfxReconMode.detectedGroup(myCoal, theScout, theGroup, location)
					return -- stop, as we only detect one group per pass
				end
			end
		end
	end
end

function cfxReconMode.update()
	-- schedule next call 
	timer.scheduleFunction(cfxReconMode.update, {}, timer.getTime() + 1/cfxReconMode.ups)
	
	-- now process all scouts
	for idx, scout in pairs(cfxReconMode.scouts) do 
		cfxReconMode.performReconForUnit(scout)
	end
end

function cfxReconMode.start()
	-- start update cycle
	cfxReconMode.update()
	
	trigger.action.outText("cfx Recon version " .. cfxReconMode.version .. " started.", 30)
	return true
end

if not cfxReconMode.start() then 
	cfxReconMode = nil
end



 
 

 

Does this work for MP? Been copy pasting the code into my groups mission but it gives a bunch of error codes. Appears to be searching for the recon unit but can’t find it. I’ve numbered all the client slots and copied them into the script replacing “recon”

Link to comment
Share on other sites

28 minutes ago, HE5405 said:

Does this work for MP?

Definitely. But the script shown here is quite old. The newest and most sparkling version of recon mode is available in DML, with more features than you can shake your mosue at.

 

Link to comment
Share on other sites

On 3/10/2023 at 5:38 PM, cfrag said:

Definitely. But the script shown here is quite old. The newest and most sparkling version of recon mode is available in DML, with more features than you can shake your mosue at.

 

DML? 

Link to comment
Share on other sites

See first stickied post 

System Specs:

Spoiler

 💻Processor:13th Gen Intel(R) Core(TM) i9-13900K - 🧠RAM: 64GB - 🎥Video Card: NVIDIA RTX 4090 - 🥽 Display: Pimax 8kx VR Headset - 🕹️Accessories:  VKB Gunfighter III MCG Ultimate, Thrustmaster TWCS (modified), Thrustmaster TPR Pedals, Simshaker JetPad, Predator HOTAS Mounts, 3D Printed Flight Button Box 

Thrustmaster TWCS Mod

 

Link to comment
Share on other sites

47 minutes ago, HE5405 said:

DML? 

Apologies, sometimes I'm too caught up im my own thoughts to not realize when I'm writing geek. DML is a mission creation toolbox that contains functional 'bricks' much like a LEGO set, and that allows you to use trigger zone attributes (that you edit in ME) to set up and control functions instead of messing with scripts. You can find a description here. One of the many bricks is Recon.

  • Thanks 1
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...