Jump to content

DML - Mission Creation Toolbox [no Lua required]


Recommended Posts

On 6/20/2022 at 4:52 PM, Sinclair_76 said:

What am I doing wrong?

Most likely that's an oversight on my part; I'm not entirely sure what has generated the "Strange Birth" event (are you cloning Objects? That's something I definitely overlooked in testing) which subsequently fails when the script tries to find out which group the object belongs to.

 

Anyway, please try if the script below resolves that issue (simply copy/replace over the existing version). If you have the time, check the new docs, as Recon has received missive updates to its ability.

Spoiler


cfxReconMode = {}
cfxReconMode.version = "2.0.1"
cfxReconMode.verbose = false -- set to true for debug info  
cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered

cfxReconMode.prioList = {} -- group names that are high prio and generate special event
cfxReconMode.blackList = {} -- group names that are NEVER detected. Comma separated strings, e.g. {"Always Hidden", "Invisible Group"}
cfxReconMode.dynamics = {} -- if a group name is dynamic
cfxReconMode.zoneInfo = {} -- additional zone info 

cfxReconMode.scoutZones = {} -- zones that define aircraft. used for late eval of players 
cfxReconMode.allowedScouts = {} -- when not using autoscouts 
cfxReconMode.blindScouts = {} -- to exclude aircraft from being scouts 
cfxReconMode.removeWhenDestroyed = true 
cfxReconMode.activeMarks = {} -- all marks and their groups, indexed by groupName 

cfxReconMode.requiredLibs = {
	"dcsCommon", -- always
	"cfxZones", -- Zones, of course 
}

--[[--
VERSION HISTORY
 1.0.0 - initial version 
 1.0.1 - removeScoutByName()
 1.0.2 - garbage collection 
 1.1.0 - autoRecon - any aircraft taking off immediately
         signs up, no message when signing up or closing down
		 standalone - copied common procs lerp, agl, dist, distflat
		 from dcsCommon
		 report numbers 
		 verbose flag 
 1.2.0 - queued recons. One scout per second for more even
         performance
		 removed gc since it's now integrated into 
		 update queue
		 removeScout optimization when directly passing name
		 playerOnlyRecon for autoRecon 
		 red, blue, grey side filtering on auto scout
 1.2.1 - parametrized report sound 
 1.3.0 - added black list, prio list functionality 
 1.3.1 - callbacks now also push name, as group can be dead
       - removed bug when removing dead groups from map
 1.4.0 - import dcsCommon, cfxZones etc 
       - added lib check 
	   - config zone 
	   - prio+
	   - detect+
 1.4.1 - invocation no longer happen twice for prio. 
	   - recon sound 
	   - read all flight groups at start to get rid of the 
	   - late activation work-around 
 1.5.0 - removeWhenDestroyed()
	   - autoRemove()
	   - readConfigZone creates default config zone so we get correct defaulting 
 2.0.0 - DML integration prio+-->prio! detect+ --> detect! 
         and method
	   - changed access to prio and blacklist to hash
	   - dynamic option for prio and black 
	   - trigger zones for designating prio and blacklist 
	   - reworked stringInList to also include dynamics 
	   - Report in SALT format: size, action, loc, time.
	   - Marks add size, action info
	   - LatLon or MGRS
	   - MGRS option in config 
	   - filter onEvent for helo and aircraft 
	   - allowedScouts and blind 
	   - stronger scout filtering at startup 
	   - better filtering on startup when autorecon and playeronly
	   - player lazy late checking, zone saving 
	   - correct checks when not autorecon 
	   - ability to add special flags to recon prio group 
	   - event guard in onEvent
	   - <t> wildcard 
	   - <lat>, <lon>, <mgrs> wildcards 
 2.0.1 - getGroup() guard for onEvent(). Objects now seem to birth. 
 
 cfxReconMode is a script that allows units to perform reconnaissance
 missions and, after detecting units, marks them on the map with 
 markers for their coalition and some text 
 Also, a callback is initiated for scouts as follows
   signature: (reason, theSide, theSout, theGroup) with  
   reason a string 
	 'detected' a group was detected
     'removed' a mark for a group timed out
	 'priority' a member of prio group was detected 
	 'start' a scout started scouting
	 'end' a scout stopped scouting
	 'dead' a scout has died and was removed from pool 
   theSide - side of the SCOUT that detected units
   theScout - the scout that detected the group 
   theGroup - the group that is detected  
   theName - the group's name    
--]]--

cfxReconMode.detectionMinRange = 3000 -- meters at ground level
cfxReconMode.detectionMaxRange = 12000 -- meters at max alt (10'000m)
cfxReconMode.maxAlt = 9000 -- alt for maxrange (9km = 27k feet)

cfxReconMode.autoRecon = true -- add all airborne units, unless 
cfxReconMode.redScouts = false -- set to false to prevent red scouts in auto mode
cfxReconMode.blueScouts = true -- set to false to prevent blue scouts in auto-mode
cfxReconMode.greyScouts = false -- set to false to prevent neutral scouts in auto mode
cfxReconMode.playerOnlyRecon = false -- only players can do recon 
cfxReconMode.reportNumbers = true -- also add unit count in report 
cfxReconMode.prioFlag = nil 
cfxReconMode.detectFlag = nil 
cfxReconMode.method = "inc"
cfxReconMode.applyMarks = true 
cfxReconMode.mgrs = false 

cfxReconMode.ups = 1 -- updates per second.
cfxReconMode.scouts = {} -- units that are performing scouting.
cfxReconMode.processedScouts = {} -- for managing performance: queue
cfxReconMode.detectedGroups = {} -- so we know which have been detected
cfxReconMode.marksFadeAfter = 30*60 -- after detection, marks disappear after
                     -- this amount of seconds. -1 means no fade
					 -- 60 is one minute

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


-- end standalone dcsCommon extract 

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, theScout, theGroup, theName)
	for idx, theCB in pairs(cfxReconMode.callbacks) do 
		theCB(reason, theSide, theScout, theGroup, theName)
	end
end

-- add a priority/blackList group name to prio list 
function cfxReconMode.addToPrioList(aGroup, dynamic)
	if not dynamic then dynamic = false end 
	if not aGroup then return end 
	if type(aGroup) == "table" and aGroup.getName then 
		aGroup = aGroup:getName()
	end
	if type(aGroup) == "string" then 
--		table.insert(cfxReconMode.prioList, aGroup)
		cfxReconMode.prioList[aGroup] = aGroup
		cfxReconMode.dynamics[aGroup] = dynamic 
	end
end

function cfxReconMode.addToBlackList(aGroup, dynamic)
	if not dynamic then dynamic = false end 
	if not aGroup then return end 
	if type(aGroup) == "table" and aGroup.getName then 
		aGroup = aGroup:getName()
	end
	if type(aGroup) == "string" then 
		--table.insert(cfxReconMode.blackList, aGroup)
		cfxReconMode.blackList[aGroup] = aGroup
		cfxReconMode.dynamics[aGroup] = dynamic
	end
end

function cfxReconMode.addToAllowedScoutList(aGroup, dynamic)
	if not dynamic then dynamic = false end 
	if not aGroup then return end 
	if type(aGroup) == "table" and aGroup.getName then 
		aGroup = aGroup:getName()
	end
	if type(aGroup) == "string" then 
		cfxReconMode.allowedScouts[aGroup] = aGroup
		cfxReconMode.dynamics[aGroup] = dynamic
	end
end

function cfxReconMode.addToBlindScoutList(aGroup, dynamic)
	if not dynamic then dynamic = false end 
	if not aGroup then return end 
	if type(aGroup) == "table" and aGroup.getName then 
		aGroup = aGroup:getName()
	end
	if type(aGroup) == "string" then 
		cfxReconMode.blindScouts[aGroup] = aGroup
		cfxReconMode.dynamics[aGroup] = dynamic
	end
end

function cfxReconMode.isStringInList(theString, theList)
	-- returns two values: inList, and original group name (if exist)
	if not theString then return false, nil end 
	if type(theString) ~= "string" then return false, nil end
	if not theList then return false, nil end 
	
	-- first, try a direct look-up. if this produces a hit
	-- we directly return true 
	if theList[theString] then return true, theString end 
	
	-- now try the more involved retrieval with string starts with 
	for idx, aName in pairs(theList) do 
		if dcsCommon.stringStartsWith(theString, aName) then 
			-- they start the same. are dynamics allowed?
			if cfxReconMode.dynamics[aName] then 
				return true, aName 
			end
		end
	end
	
	return false, nil
end


-- addScout directly adds a scout unit. Use from external 
-- to manually add a unit (e.g. via GUI when autoscout isExist
-- off, or to force a scout unit (e.g. when scouts for a side
-- are not allowed but you still want a unit from that side 
-- to scout
-- since we use a queue for scouts, also always check the 
-- processed queue before adding to make sure a scout isn't 
-- entered multiple times 

function cfxReconMode.addScout(theUnit)
	if not theUnit then 
		trigger.action.outText("+++cfxRecon: WARNING - nil Unit on add", 30)
		return
	end
	
	if type(theUnit) == "string" then 
		local u = Unit.getByName(theUnit) 
		theUnit = u
	end 
	
	if not theUnit then 
		trigger.action.outText("+++cfxRecon: WARNING - did not find unit on add", 30)
		return 
	end	
	if not theUnit:isExist() then return end 
	-- find out if this an update or a new scout 
	local thisID = tonumber(theUnit:getID())
	local theName = theUnit:getName() 
	local lastUnit = cfxReconMode.scouts[theName]
	local isProcced = false -- may also be in procced line 
	if not lastUnit then 
		lastUnit = cfxReconMode.processedScouts[theName]
		if lastUnit then isProcced = true end 
	end

	if lastUnit then 
		-- this is merely an overwrite 
		if cfxReconMode.verbose then trigger.action.outText("+++rcn: UPDATE scout " .. theName .. " -- no CB invoke", 30) end 
	else 
		if cfxReconMode.verbose then trigger.action.outText("+++rcn: new scout " .. theName .. " with ID " .. thisID, 30) end 
		-- a new scout! Invoke callbacks
		local scoutGroup = theUnit:getGroup()
		local theSide = scoutGroup:getCoalition()
		cfxReconMode.invokeCallbacks("start", theSide, theUnit, nil, "<none>")
	end 
	
	if isProcced then 
		-- overwrite exiting entry in procced queue
		cfxReconMode.processedScouts[theName] = theUnit
	else 
		-- add / overwrite into normal queue 
		cfxReconMode.scouts[theName] = theUnit
	end 
	
	if cfxReconMode.verbose then
		trigger.action.outText("+++rcn: addded scout " .. theUnit:getName(), 30)
	end
end


function cfxReconMode.removeScout(theUnit)
	if not theUnit then 
		trigger.action.outText("+++rcn: WARNING - nil Unit on remove", 30)
		return 
	end
	
	if type(theUnit) == "string" then 
		cfxReconMode.removeScoutByName(theUnit)
		return 
	end 
	
	if not theUnit then return end	
	if not theUnit:isExist() then return end 
	cfxReconMode.removeScoutByName(theUnit:getName())
	local scoutGroup = theUnit:getGroup()
	local theSide = scoutGroup:getCoalition()
	cfxReconMode.invokeCallbacks("end", theSide, theUnit, nil, "<none>")
end

-- warning: removeScoutByName does NOT invoke callbacks, always
-- use removeScout instead!
function cfxReconMode.removeScoutByName(aName)
	cfxReconMode.scouts[aName] = nil
	cfxReconMode.processedScouts[aName] = nil -- also remove from processed stack 
	if cfxReconMode.verbose then
		trigger.action.outText("+++rcn: removed scout " .. aName, 30)
	end
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
			else 
				-- OPTIMIZATION: if a unit is outside 
				-- detect range, we assume that entire group 
				-- is, since they are bunched together
				-- edge cases may get lucky tests
				return false, nil 
			end
		end		
	end
	return false, nil -- nothing visible
end

function cfxReconMode.placeMarkForUnit(location, theSide, theGroup) 
	local theID = cfxReconMode.uuid()
	local theDesc = "Contact: "..theGroup:getName()
	if cfxReconMode.reportNumbers then 
--		theDesc = theDesc .. " (" .. theGroup:getSize() .. " units)"
		theDesc = theDesc .. " - " .. cfxReconMode.getSit(theGroup) .. ", " .. cfxReconMode.getAction(theGroup) .. "."
	end
	trigger.action.markToCoalition(
					theID, 
					theDesc, 
					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]
	local theName = args[5]
	
	-- only remove if it wasn't already removed.
	-- this method is called async *and* sync!
	if cfxReconMode.activeMarks[theName] then 
		trigger.action.removeMark(theID)
		-- invoke callbacks
		cfxReconMode.invokeCallbacks("removed", theSide, theScout, theGroup, theName)
		cfxReconMode.activeMarks[theName] = nil -- also remove from list of groups being checked
	end 
	
	cfxReconMode.detectedGroups[theName] = nil -- some housekeeping. 
end 

function cfxReconMode.getSit(theGroup)
	local msg = ""
		-- analyse the group we just discovered. We know it's a ground troop, so simply differentiate between vehicles and infantry 
		local theUnits = theGroup:getUnits()
		local numInf = 0 
		local numVehicles = 0 
		for idx, aUnit in pairs(theUnits) do 
			if dcsCommon.unitIsInfantry(aUnit) then 
				numInf = numInf + 1
			else 
				numVehicles = numVehicles + 1
			end 
		end
		if numInf > 0 and numVehicles > 0 then 
			-- mixed infantry and vehicles 
			msg = numInf .. " infantry and " .. numVehicles .. " vehicles" 
		elseif numInf > 0 then
			-- only infantry
			msg = numInf .. " infantry"
		else 
			-- only vehicles
			msg = numVehicles .. " vehicles"
		end 
	return msg
end

function cfxReconMode.getAction(theGroup) 
	local msg = ""
	-- simply get the first unit and get velocity vector. 
	-- if it's smaller than 1 m/s (= 3.6 kmh), it's "Guarding", if it's faster, it's 
	-- moving with direction
	local theUnit = theGroup:getUnit(1)
	local vvel = theUnit:getVelocity()
	local vel = dcsCommon.vMag(vvel)
	if vel < 1 then 
		msg = "apparently guarding"
	else
		local speed = ""
		if vel < 3 then speed = "slowly"
		elseif vel < 6 then speed = "deliberately"
		else speed = "briskly" end 
		local heading = dcsCommon.getUnitHeading(theUnit) -- in rad 
		msg = speed .. " moving " .. dcsCommon.bearing2compass(heading)
	end
	return msg
end

function cfxReconMode.getLocation(theGroup)
	local msg = ""
	local theUnit = theGroup:getUnit(1)
	local currPoint = theUnit:getPoint()
	if cfxReconMode.mgrs then 
		local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
		msg = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
	else 
		local lat, lon, alt = coord.LOtoLL(currPoint)
		lat, lon = dcsCommon.latLon2Text(lat, lon)
		msg = "Lat " .. lat .. " Lon " .. lon
	end
	return msg
end

function cfxReconMode.getTimeData()
	local msg = ""
	local absSecs = timer.getAbsTime()-- + env.mission.start_time
	while absSecs > 86400 do 
		absSecs = absSecs - 86400 -- subtract out all days 
	end
	msg = dcsCommon.processHMS("<:h>:<:m>:<:s>", absSecs)
	return "at " .. msg
end

function cfxReconMode.generateSALT(theScout, theGroup)
	local msg = theScout:getName() .. " reports new ground contact " .. theGroup:getName() .. ":\n"
	-- SALT: S = Situation or number of units A = action they are doing L = Location T = Time 
	msg = msg .. cfxReconMode.getSit(theGroup) .. ", "-- S 
	msg = msg .. cfxReconMode.getAction(theGroup) .. ", " -- A 
	msg = msg .. cfxReconMode.getLocation(theGroup) .. ", " -- L 
	msg = msg .. cfxReconMode.getTimeData() -- T
	
	return msg
end

function cfxReconMode.processZoneMessage(inMsg, theZone) 
	if not inMsg then return "<nil inMsg>" end
	local formerType = type(inMsg)
	if formerType ~= "string" then inMsg = tostring(inMsg) end  
	if not inMsg then inMsg = "<inMsg is incompatible type " .. formerType .. ">" end 
	local outMsg = ""
	-- replace line feeds 
	outMsg = inMsg:gsub("<n>", "\n")
	if theZone then 
		outMsg = outMsg:gsub("<z>", theZone.name)
	end
	-- replace <t> with current mission time HMS
	local absSecs = timer.getAbsTime()-- + env.mission.start_time
	while absSecs > 86400 do 
		absSecs = absSecs - 86400 -- subtract out all days 
	end
	local timeString  = dcsCommon.processHMS("<:h>:<:m>:<:s>", absSecs)
	outMsg = outMsg:gsub("<t>", timeString)
	
	-- replace <lat> with lat of zone point and <lon> with lon of zone point 
	-- and <mgrs> with mgrs coords of zone point 
	local currPoint = cfxZones.getPoint(theZone)
	local lat, lon, alt = coord.LOtoLL(currPoint)
	lat, lon = dcsCommon.latLon2Text(lat, lon)
	outMsg = outMsg:gsub("<lat>", lat)
	outMsg = outMsg:gsub("<lon>", lon)
	currPoint = cfxZones.getPoint(theZone)
	local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
	local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
	outMsg = outMsg:gsub("<mgrs>", mgrs)
	return outMsg
end

function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
	-- put a mark on the map 
	if cfxReconMode.applyMarks then 
		local theID = cfxReconMode.placeMarkForUnit(theLoc, mySide, theGroup)
		local gName = theGroup:getName()
		local args = {mySide, theScout, theGroup, theID, gName}
		cfxReconMode.activeMarks[gName] = args
		-- schedule removal if desired 
		if cfxReconMode.marksFadeAfter > 0 then 	
			timer.scheduleFunction(cfxReconMode.removeMarkForArgs, args, timer.getTime() + cfxReconMode.marksFadeAfter)
		end
	end 
	
	-- say something
	if cfxReconMode.announcer then 
		local msg = cfxReconMode.generateSALT(theScout, theGroup)
		trigger.action.outTextForCoalition(mySide, msg, 30)
--		trigger.action.outTextForCoalition(mySide, theScout:getName() .. " reports new ground contact " .. theGroup:getName(), 30)
		if cfxReconMode.verbose then 
			trigger.action.outText("+++rcn: announced for side " .. mySide, 30)
		end 
		-- play a sound 
		trigger.action.outSoundForCoalition(mySide, cfxReconMode.reconSound)
	else 
	end 
	
	-- see if it was a prio target 
	local inList, gName = cfxReconMode.isStringInList(theGroup:getName(), cfxReconMode.prioList)
	if inList then 
--		if cfxReconMode.announcer then 
		if cfxReconMode.verbose then 
			trigger.action.outText("+++rcn: Priority target spotted",	30)
		end 
		-- invoke callbacks
		cfxReconMode.invokeCallbacks("priority", mySide, theScout, theGroup, theGroup:getName())
		
		-- increase prio flag 
		if cfxReconMode.prioFlag then 
			cfxZones.pollFlag(cfxReconMode.prioFlag, cfxReconMode.method, cfxReconMode.theZone)
		end
		
		-- see if we were passed additional info in zInfo 
		if gName and cfxReconMode.zoneInfo[gName] then 
			local zInfo = cfxReconMode.zoneInfo[gName]
			if zInfo.prioMessage then 
				-- prio message displays even when announcer is off
				local msg = zInfo.prioMessage
				msg = cfxReconMode.processZoneMessage(msg, zInfo.theZone) 
				trigger.action.outTextForCoalition(mySide, msg, 30)
				if cfxReconMode.verbose or zInfo.theZone.verbose then 
					trigger.action.outText("+++rcn: prio message sent  for prio target zone <" .. zInfo.theZone.name .. ">",30)
				end
			end
			
			if zInfo.theFlag then 
				cfxZones.pollFlag(zInfo.theFlag, cfxReconMode.method, zInfo.theZone)
				if cfxReconMode.verbose or zInfo.theZone.verbose then 
					trigger.action.outText("+++rcn: banging <" .. zInfo.theFlag .. "> for prio target zone <" .. zInfo.theZone.name .. ">",30)
				end
			end 
		end
	else 
		-- invoke callbacks
		cfxReconMode.invokeCallbacks("detected", mySide, theScout, theGroup, theGroup:getName())
	
		-- increase normal flag 
		if cfxReconMode.detectFlag then 
			cfxZones.pollFlag(cfxReconMode.detectFlag, cfxReconMode.method, cfxReconMode.theZone)
		end
	end
end

function cfxReconMode.performReconForUnit(theScout)
	if not theScout then return end 
	if not theScout:isExist() then return end -- will be gc'd soon
	-- 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 
				local groupName = theGroup:getName()
				if cfxReconMode.detectedGroups[groupName] == nil then 
					-- only now check against blackList
					local inList, gName = cfxReconMode.isStringInList(groupName, cfxReconMode.blackList) 
					if not inList then 
						-- visible and not yet seen 
						-- perhaps add some percent chance now 
						-- remember that we know this group 
						cfxReconMode.detectedGroups[groupName] = theGroup
						cfxReconMode.detectedGroup(myCoal, theScout, theGroup, location)
						return -- stop, as we only detect one group per pass
					end 
				end
			end
		end
	end
end



function cfxReconMode.updateQueues()
	-- schedule next call 
	timer.scheduleFunction(cfxReconMode.updateQueues, {}, timer.getTime() + 1/cfxReconMode.ups)
	
	-- we only process the first aircraft in 
	-- the scouts array, move it to processed and then shrink
	-- scouts table until it's empty. When empty, transfer all 
	-- back and start cycle anew

	local theFocusScoutName = nil 
	local procCount = 0 -- no iterations done yet
	for name, scout in pairs(cfxReconMode.scouts) do 
		theFocusScoutName = name -- remember so we can delete
		if not scout:isExist() then 
			-- we ignore the scout, and it's 
			-- forgotten since no longer transferred
			-- i.e. built-in GC
			if cfxReconMode.verbose then
				trigger.action.outText("+++rcn: GC - removing scout " .. name .. " because it no longer exists", 30)
			end
			-- invoke 'end' for this scout  
			cfxReconMode.invokeCallbacks("dead", -1, nil, nil, name)
		else
			-- scan for this scout
			cfxReconMode.performReconForUnit(scout)
			-- move it to processed table
			cfxReconMode.processedScouts[name] = scout
		end
		procCount = 1 -- remember we went through one iteration
		break -- always end after first iteration
	end

	-- remove processed scouts from scouts array
	if procCount > 0 then 
		-- we processed one scout (even if scout itself did not exist)
		-- remove that scout from active scouts table
		cfxReconMode.scouts[theFocusScoutName] = nil
	else 
		-- scouts is empty. copy processed table back to scouts
		-- restart scouts array, contains GC already 
		cfxReconMode.scouts = cfxReconMode.processedScouts
		cfxReconMode.processedScouts = {} -- start new empty processed queue
	end 
end

function cfxReconMode.isGroupStillAlive(gName)
		local theGroup = Group.getByName(gName)
		if not theGroup then return false end 
		if not theGroup:isExist() then return false end 
		local allUnits = theGroup:getUnits()
		for idx, aUnit in pairs (allUnits) do 
			if aUnit:getLife() >= 1 then return true end 
		end
		return false 
end

function cfxReconMode.autoRemove()
	-- schedule next call 
	timer.scheduleFunction(cfxReconMode.autoRemove, {}, timer.getTime() + 1/cfxReconMode.ups)
	
	local toRemove = {}
	-- scan all marked groups, and when they no longer exist, remove them 
	for idx, args in pairs (cfxReconMode.activeMarks) do
		-- args = {mySide, theScout, theGroup, theID, gName}
		local gName = args[5]
		if not cfxReconMode.isGroupStillAlive(gName) then 
			-- remove mark, remove group from set 
			table.insert(toRemove, args)
		end
	end 
	
	for idx, args in pairs(toRemove) do 
		cfxReconMode.removeMarkForArgs(args)
		trigger.action.outText("+++recn: removed mark: " .. args[5], 30)
	end
end

-- late eval player 
function cfxReconMode.lateEvalPlayerUnit(theUnit)
	-- check if a player is inside one of the scout zones 
	-- first: quick check if the player is already in a list 
	local aGroup = theUnit:getGroup() 
	local gName = aGroup:getName()
	if cfxReconMode.allowedScouts[gName] then return end 
	if cfxReconMode.blindScouts[gName] then return end 

	-- get location 
	local p = theUnit:getPoint()
	
	-- iterate all scoutZones
	for idx, theZone in pairs (cfxReconMode.scoutZones) do 
		local isScout = theZone.isScout
		local dynamic = theZone.dynamic
		local inZone = cfxZones.pointInZone(p, theZone)
		if inZone then 
			if isScout then 
				cfxReconMode.addToAllowedScoutList(aGroup, dynamic)
				if cfxReconMode.verbose or theZone.verbose then 
					if dynamic then 
						trigger.action.outText("+++rcn: added LATE DYNAMIC PLAYER" .. gName .. " to allowed scouts", 30)
					else 
						trigger.action.outText("+++rcn: added LATE PLAYER " .. gName .. " to allowed scouts", 30) 
					end
				end 
			else 
				cfxReconMode.addToBlindScoutList(aGroup, dynamic)
				if cfxReconMode.verbose or theZone.verbose then 
					if dynamic then 
						trigger.action.outText("+++rcn: added LATE DYNAMIC PLAYER" .. gName .. " to BLIND scouts list", 30)
					else 
						trigger.action.outText("+++rcn: added LATE PLAYER " .. gName .. " to BLIND scouts list", 30)
					end
				end
			end
			return -- we stop after first found 
		end
	end
end

-- event handler 
function cfxReconMode:onEvent(event) 
	if not event then return end 
	if not event.initiator then return end 
	if not (event.id == 15 or event.id == 3) then return end 
	
	local theUnit = event.initiator 
	if not theUnit:isExist() then return end 
	if not theUnit.getGroup then 
		-- strange, but seemingly can happen
		return 
	end 
	local theGroup = theUnit:getGroup() 
--	trigger.action.outText("+++rcn-ENTER onEvent: " .. event.id .. " for <" .. theUnit:getName() .. ">", 30)
	if not theGroup then return end 
	local gCat = theGroup:getCategory()
	-- only continue if cat = 0 (aircraft) or 1 (helo)
	if gCat > 1 then return end 
	
	-- we simply add scouts as they are garbage-collected 
	-- every so often when they do not exist 
	if event.id == 15 or -- birth
	   event.id == 3 -- take-off. should already have been taken 
	                 -- care of by birth, but you never know 
	then
		-- check if a side must not have scouts.
		-- this will prevent player units to auto-
		-- scout when they are on that side. in that case
		-- you must add manually
		local theSide = theUnit:getCoalition()
		
		local isPlayer = theUnit:getPlayerName()
		if isPlayer then  
			-- since players wake up late, we lazy-eval their group
			-- and add it to the blind/scout lists
			cfxReconMode.lateEvalPlayerUnit(theUnit)
			if cfxReconMode.verbose then 
				trigger.action.outText("+++rcn: late player check complete for <" .. theUnit:getName() .. ">", 30)
			end
		else 
			isPlayer = false -- safer than sorry
		end 
		
		if cfxReconMode.autoRecon then 
			if theSide == 0 and not cfxReconMode.greyScouts then 
				return -- grey scouts are not allowed
			end
			if theSide == 1 and not cfxReconMode.redScouts then 
				return -- grey scouts are not allowed
			end
			if theSide == 2 and not cfxReconMode.blueScouts then 
				return -- grey scouts are not allowed
			end
		
			if cfxReconMode.playerOnlyRecon then 
				if not isPlayer then 
					if cfxReconMode.verbose then 
						trigger.action.outText("+++rcn: <" .. theUnit:getName() .. "> filtered: no player unit", 30)
					end 
					return -- only players can do recon. this unit is AI
				end
			end
		end 
		
		-- check if cfxReconMode.autoRecon is enabled
		-- otherwise, abort the aircraft is not in 
		-- scourlist 
		local gName = theGroup:getName()
		if not cfxReconMode.autoRecon then 
			-- no auto-recon. plane must be in scouts list 
			local inList, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.allowedScouts)
			if not inList then 
				if cfxReconMode.verbose then 
					trigger.action.outText("+++rcn: <" .. theUnit:getName() .. "> filtered: not in scout list", 30)
				end
				return 
			end
		end
		
		-- check if aircraft is in blindlist 
		-- abort if so 
		local inList, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.blindScouts)
		if inList then 
			if cfxReconMode.verbose then 
				trigger.action.outText("+++rcn: <" .. theUnit:getName() .. "> filtered: unit cannot scout", 30)
			end
			return 
		end
		
		if cfxReconMode.verbose then 
			trigger.action.outText("+++rcn: event " .. event.id .. " for unit " .. theUnit:getName(), 30)
		end 
		cfxReconMode.addScout(theUnit)
	end
--	trigger.action.outText("+++rcn-onEvent: " .. event.id .. " for <" .. theUnit:getName() .. ">", 30)
end

--
-- read all existing planes 
-- 
function cfxReconMode.processScoutGroups(theGroups)
	for idx, aGroup in pairs(theGroups) do 
		-- process all planes in that group 
		-- we are very early in the mission, only few groups really 
		-- exist now, the rest of the units come in with 15 event
		if aGroup:isExist() then 
			-- see if we want to add these aircraft to the 
			-- active scout list 
			
			local gName = aGroup:getName()
			local isBlind, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.blindScouts)
			local isScout, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.allowedScouts)
			
			local doAdd = cfxReconMode.autoRecon
			if cfxReconMode.autoRecon then 
				local theSide = aGroup:getCoalition()
				if theSide == 0 and not cfxReconMode.greyScouts then
					doAdd = false 
				elseif theSide == 1 and not cfxReconMode.redScouts then 
					doAdd = false 
				elseif theSide == 2 and not cfxReconMode.blueScouts then 
					doAdd = false 
				end 
			end
			
			if isBlind then doAdd = false end 
			if isScout then doAdd = true end -- overrides all 
			
			if doAdd then 
				local allUnits = Group.getUnits(aGroup)
				for idy, aUnit in pairs (allUnits) do 
					if aUnit:isExist() then 
						if cfxReconMode.autoRecon and cfxReconMode.playerOnlyRecon and (aUnit:getPlayerName() == nil)
						then
							if cfxReconMode.verbose then
								trigger.action.outText("+++rcn: skipped unit " ..aUnit:getName() .. " because not player unit", 30)
							end
						else
							cfxReconMode.addScout(aUnit)
							if cfxReconMode.verbose then
								trigger.action.outText("+++rcn: added unit " ..aUnit:getName() .. " to pool at startup", 30)
							end
						end
					end
				end
			else 
				if cfxReconMode.verbose then 
					trigger.action.outText("+++rcn: filtered group " .. gName .. " from being entered into scout pool at startup", 30)
				end
			end 
		end
	end
end

function cfxReconMode.initScouts()
	-- get all groups of aircraft. Unrolled loop 0..2 
	-- added helicopters, removed check for grey/red/bluescouts,
	-- as that happens in processScoutGroups 
	local theAirGroups = {}  
	theAirGroups = coalition.getGroups(0, 0) -- 0 = aircraft
	cfxReconMode.processScoutGroups(theAirGroups)
	theAirGroups = coalition.getGroups(0, 1) -- 1 = helicopter
	cfxReconMode.processScoutGroups(theAirGroups)

	theAirGroups = coalition.getGroups(1, 0) -- 0 = aircraft
	cfxReconMode.processScoutGroups(theAirGroups)
	theAirGroups = coalition.getGroups(1, 1) -- 1 = helicopter
	cfxReconMode.processScoutGroups(theAirGroups)

	theAirGroups = coalition.getGroups(2, 0) -- 0 = aircraft
	cfxReconMode.processScoutGroups(theAirGroups)
	theAirGroups = coalition.getGroups(2, 1) -- 1 = helicopter
	cfxReconMode.processScoutGroups(theAirGroups)
end

--
-- read config 
--
function cfxReconMode.readConfigZone()
	-- note: must match exactly!!!!
	local theZone = cfxZones.getZoneByName("reconModeConfig") 
	if not theZone then 
		if cfxReconMode.verbose then
			trigger.action.outText("+++rcn: no config zone!", 30) 
		end 
		theZone = cfxZones.createSimpleZone("reconModeConfig")
	else  
		if cfxReconMode.verbose then 
			trigger.action.outText("+++rcn: found config zone!", 30) 
		end 
	end 
	
	cfxReconMode.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)

	cfxReconMode.autoRecon = cfxZones.getBoolFromZoneProperty(theZone, "autoRecon", true)
	cfxReconMode.redScouts = cfxZones.getBoolFromZoneProperty(theZone, "redScouts", false)
	cfxReconMode.blueScouts = cfxZones.getBoolFromZoneProperty(theZone, "blueScouts", true)	
	cfxReconMode.greyScouts = cfxZones.getBoolFromZoneProperty(theZone, "greyScouts", false)
	cfxReconMode.playerOnlyRecon = cfxZones.getBoolFromZoneProperty(theZone, "playerOnlyRecon", false)
	cfxReconMode.reportNumbers = cfxZones.getBoolFromZoneProperty(theZone, "reportNumbers", true)
		
	cfxReconMode.detectionMinRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMinRange", 3000)
	cfxReconMode.detectionMaxRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMaxRange", 12000)
	cfxReconMode.maxAlt = cfxZones.getNumberFromZoneProperty(theZone, "maxAlt", 9000)
	
	if cfxZones.hasProperty(theZone, "prio+") then 
		cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio+", "none")
	elseif cfxZones.hasProperty(theZone, "prio!") then 
		cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio!", "*<none>")
	end
	
	if cfxZones.hasProperty(theZone, "detect+") then 
		cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect+", "none")
	elseif cfxZones.hasProperty(theZone, "detect!") then 
		cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect!", "*<none>")
	end
	
	cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
	if cfxZones.hasProperty(theZone, "reconMethod") then 
		cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "reconMethod", "inc")
	end
	
	cfxReconMode.applyMarks = cfxZones.getBoolFromZoneProperty(theZone, "applyMarks", true)
	cfxReconMode.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
	-- trigger.action.outText("recon: announcer is " .. dcsCommon.bool2Text(cfxReconMode.announcer), 30) -- announced
	if cfxZones.hasProperty(theZone, "reconSound") then 
		cfxReconMode.reconSound = cfxZones.getStringFromZoneProperty(theZone, "reconSound", "<nosound>")
	end
	
	cfxReconMode.removeWhenDestroyed = cfxZones.getBoolFromZoneProperty(theZone, "autoRemove", true)
	
	cfxReconMode.mgrs = cfxZones.getBoolFromZoneProperty(theZone, "mgrs", false)
	
	cfxReconMode.theZone = theZone -- save this zone 
end

--
-- read blackList and prio list groups
--


function cfxReconMode.processReconZone(theZone) 
	local theList = cfxZones.getStringFromZoneProperty(theZone, "recon", "prio")
	theList = string.upper(theList)
	local isBlack = dcsCommon.stringStartsWith(theList, "BLACK")

	local zInfo = {}
	zInfo.theZone = theZone
	zInfo.isBlack = isBlack	
	if cfxZones.hasProperty(theZone, "spotted!") then 
		zInfo.theFlag = cfxZones.getStringFromZoneProperty(theZone, "spotted!", "*<none>")
	end
	
	if cfxZones.hasProperty(theZone, "prioMessage") then 
		zInfo.prioMessage = cfxZones.getStringFromZoneProperty(theZone, "prioMessage", "<none>")
	end
	
	local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic", false)
	zInfo.dynamic = dynamic 
	local categ = 2 -- ground troops only
	local allGroups = cfxZones.allGroupsInZone(theZone, categ)
	for idx, aGroup in pairs(allGroups) do 
		local gName = aGroup:getName()
		cfxReconMode.zoneInfo[gName] = zInfo 
		if isBlack then 
			cfxReconMode.addToBlackList(aGroup, dynamic)
			if cfxReconMode.verbose or theZone.verbose then 
				if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to blacklist", 30)
				else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to blacklist", 30) 
				end
			end 
		else 
			cfxReconMode.addToPrioList(aGroup, dynamic)
			if cfxReconMode.verbose or theZone.verbose then 
				if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to priority target list", 30)
				else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to priority target list", 30)
				end
			end
		end
	end
end

function cfxReconMode.processScoutZone(theZone) 
	local isScout = cfxZones.getBoolFromZoneProperty(theZone, "scout", true)
	local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic")
	theZone.dynamic = dynamic
	theZone.isScout = isScout
	
	local categ = 0 -- aircraft
	local allFixed = cfxZones.allGroupsInZone(theZone, categ)
	local categ = 1 -- helos
	local allRotor = cfxZones.allGroupsInZone(theZone, categ)
	local allGroups = dcsCommon.combineTables(allFixed, allRotor)
	for idx, aGroup in pairs(allGroups) do 
		if isScout then 
			cfxReconMode.addToAllowedScoutList(aGroup, dynamic)
			if cfxReconMode.verbose or theZone.verbose then 
				if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to allowed scouts", 30)
				else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to allowed scouts", 30) 
				end
			end 
		else 
			cfxReconMode.addToBlindScoutList(aGroup, dynamic)
			if cfxReconMode.verbose or theZone.verbose then 
				if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to BLIND scouts list", 30)
				else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to BLIND scouts list", 30)
				end
			end
		end
	end
	
	table.insert(cfxReconMode.scoutZones, theZone)
end

function cfxReconMode.readReconGroups()
	local attrZones = cfxZones.getZonesWithAttributeNamed("recon")
	for k, aZone in pairs(attrZones) do 
		cfxReconMode.processReconZone(aZone)
	end
end

function cfxReconMode.readScoutGroups()
	local attrZones = cfxZones.getZonesWithAttributeNamed("scout")
	for k, aZone in pairs(attrZones) do 
		cfxReconMode.processScoutZone(aZone)
	end
end

--
-- start 
--
function cfxReconMode.start()
	-- lib check 
	if not dcsCommon.libCheck("cfx Recon Mode", 
		cfxReconMode.requiredLibs) then
		return false 
	end
	
	-- read config 
	cfxReconMode.readConfigZone()
	
	-- gather prio and blacklist groups 
	cfxReconMode.readReconGroups() 
	
	-- gather allowed and forbidden scouts 
	cfxReconMode.readScoutGroups()
	
	-- gather exiting planes 
	cfxReconMode.initScouts()
	
	-- start update cycle
	cfxReconMode.updateQueues()
	
	-- if dead groups are removed from map,
	-- schedule housekeeping 
	if cfxReconMode.removeWhenDestroyed then 
		cfxReconMode.autoRemove()
	end
	
	if true or cfxReconMode.autoRecon then 
		-- install own event handler to detect 
		-- when a unit takes off and add it to scout
		-- roster 
		world.addEventHandler(cfxReconMode)
	end
	
	trigger.action.outText("cfx Recon version " .. cfxReconMode.version .. " started.", 30)
	return true
end

--
-- test callback 
--
function cfxReconMode.demoReconCB(reason, theSide, theScout, theGroup, theName)
	trigger.action.outText("recon CB: " .. reason .. " -- " .. theScout:getName() .. " spotted " .. theName, 30)
end

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

-- debug: wire up my own callback
-- cfxReconMode.addCallback(cfxReconMode.demoReconCB)


--[[--

ideas:
 
- renew lease. when already sighted, simply renew lease, maybe update location.
- update marks and renew lease 
TODO: red+ and blue+ - flags to increase when a plane of the other side is detected
TODO: recon: scout and blind for aircraft in group to add / remove scouts, maybe use scout keyword 
 
allow special bangs per priority group 
--]]--


 
 

 


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

How to stop spawning units (attacking humvees) in "Attack of ClonesZ"  after doing their "job" ? (after destroying all defenders, flag XXX is on)

I only know about deSpawn? but i dont like "instant despawning", can i somehow disable delayed spawn?  Like i want all humvees to stay in position after doing their job and if something destroy them (after destroying all defenders) i dont want them to spawn again cuz mission is done.

(i hope you undestand my english xd)


Edited by H0RN9T_
Link to comment
Share on other sites

4 hours ago, H0RN9T_ said:

can i somehow disable delayed spawn

Yes - use a ‚changer‘ module, which is a closable pass-through. Run the clone flag through the changer (direct, no change), and it passes all clone signals to the cloner.
When all defenders are destroyed and that sets xxx, feed that to the input that closes the changer’s pass-through. From that moment on, clone signals no longer pass, and no more clones can spawn.

Simply stack the changer on top of the cloner, and you don’t even need another zone. Perhaps take a look at the ‘gate and switch’ demo and demo doc for other tricks the changer has up its sleeve.

Link to comment
Share on other sites

Ah thanks you but still after 2h i did absolutely nothing 😕

Like i can create basic RND, Timers, Cloners and i how to connect them etc but i dont know how to create simple "changer" 😞

Demo "gate and switch"  is too much complicated for me, i just want to pass flag 202 through changer and change it to 203 if flag 999 (from ME trigger zones) is still OFF 😕 and i want new trigger zone cuz i dont want to add lines to my 10 cloners 😛  but every time i add something to change? it crashes.

 

EDIT: Hmm i did some more testing, i just add simple change? and changeout! in "gate and switch" mission and it works, but if i add that in "Attack of The CloneZ" it crashes (and yes i added scripts)

 

 


Edited by H0RN9T_
Link to comment
Share on other sites

4 hours ago, H0RN9T_ said:

Hmm i did some more testing, i just add simple change? and changeout! in "gate and switch" mission and it works, but if i add that in "Attack of The CloneZ" it crashes (and yes i added scripts)

That's easily explained: I'm lazy 🙂 - while each demo works by itself and to the extent that I want to show a feature, it's usually not using the most recent version of all modules (they are using the version from the time I created the demo - there currently are more than 50 demos in DML, and updating them for each release takes too much time for me). So when you add modules to an existing demo, there is a good chance that the module you add uses abilities that did not exist when the demo was created. Changer, for example, is a fairly recent module and requires very recent versions of dcsCommon and cfxZones.

The remedy is a bit annoying, but simple: update at least dcsCommon and cfxZones (better all modules that are in the mission) to the newest version that come with DML before you add other modules. That should remove the breaks.

Your own mission would work since you are already using the newest and brightest versions. 


Edited by cfrag
Link to comment
Share on other sites

Thank you!

My basic change? trigger  works now  😛  My last 2 questions:

1. Is there a way to "reverse"  On/Off?  (i just want it to start already ON, i dont want to make another trigger to enable it and then disable it)

2. +++Zone: warning - getFlag has '<noone>' flag name in zone <<ZONE000>>   how to disable this error? or what to do? (ZONE000 is a trigger with "change?" and this trigger works fine)


Edited by H0RN9T_
Link to comment
Share on other sites

17 minutes ago, H0RN9T_ said:

1. Is there a way to "reverse"  On/Off?

I'm not sure I correctly understand you question. What are you trying to achieve? When a changer starts up, by default it is set to direct, no evaluation, and it passes input to output unchanged (i.e. straight through). If you then send a signal into "changeOff?" the changer stops transmitting signals.  

image.png

Above shows an example cloner that is fully copy/pasteable and will clone any groups inside, and automatically respawn all groups inside when the previous clone was destroyed - UNTIL a signal is received on the (global) 'gameOver' flag (note that the trigger zone uses local flags to not pollute your flag space). This works by

  • start a clone cycle at the mission start
  • when all units are dead, (local) flag isDead is banged
  • this runs into the changer's 'change?' input and is passed on to changeOut!, which bangs on the local (local) clone flag
  • (local) clone is wired into the cloner's 'clone?' input and starts a new cycle.

This continues until at one point you change the value of the (global) flag "gameOver" that runs into the changer's 'changeOff?' input. Once the change is detected, change? and changeOut! are no longer connected, and no further clones can spawn because the signal on (local) isDead can't pass the changer.

Does this help?


Edited by cfrag
Link to comment
Share on other sites

19 minutes ago, H0RN9T_ said:

+++Zone: warning - getFlag has '<noone>' flag name in zone <<ZONE000>>

This is a warning that you are using a flag name that DML deems suspicious. Please look at the zone named 'ZONE000' and check if you are using a flag named <none>. You can use that flag name if you really want, but DML will keep warning you that you are using a name that it deeply dislikes. This happens most commonly if you provide an empty value when you should have provided a flag name. So please also check if you have added an attribute for an input or output flag to ZONE000 that has an empty value.

Link to comment
Share on other sites

Thank you, it will help even more but still even with copy-paste it shows +++Zone: warning - getFlag has '<noone>' ... error and it doesnt matter how i name trigger,  add "change?" to trigger zone and it gives me this spamming error.


Edited by H0RN9T_
Link to comment
Share on other sites

1 hour ago, cfrag said:

This is a warning that you are using a flag name that DML deems suspicious. Please look at the zone named 'ZONE000' and check if you are using a flag named <none>. You can use that flag name if you really want, but DML will keep warning you that you are using a name that it deeply dislikes. This happens most commonly if you provide an empty value when you should have provided a flag name. So please also check if you have added an attribute for an input or output flag to ZONE000 that has an empty value.

 And now i see where i had problems 😄   i wanted to disable timer by "changeOff?" by trigger in mission editor (after 10 seconds from mission start, flag 997 ON)  so it was  changeOff? 997 and it didnt work but when i change it to gameOver it started working 😄  

 

Thank you, it will help even more but still even with copy-paste it shows +++Zone: warning - getFlag has '<noone>' ... error and it doesnt matter how i name trigger,  add "change?" to trigger zone and it gives me this spamming error.

 

4.png

 

Maybe i did something wrong with adding scripts but everything works fine, even with that error.


Edited by H0RN9T_
Link to comment
Share on other sites

26 minutes ago, H0RN9T_ said:

Thank you, it will help even more but still even with copy-paste it shows +++Zone: warning - getFlag has '<noone>' ... error and it doesnt matter how i name trigger,  add "change?" to trigger zone and it gives me this spamming error.

I'm sure we can get to the root of that quickly. Why don't you post the mission here or PM it to me so I can have a look.

 

Link to comment
Share on other sites

OK, I see the issue. It's fixed in the included mission below. Root cause is a missing guard in the changer module. I added the new Changer version 1.0.1 so you don't have to wait until (tomorrows?) next DML drop. 

Other changes I've made (because I can't help myself 🙂

  • Made the trigger that loads DML an 'ONSTART' trigger, set color to green, and renamed it to 'Load DML'
  • Removed a duplicate copy of cfxPlayer as it was redundant
  • Replaced changer 1.0.0 with (BRAND SPANKING NEW) 1.0.1

Cheers,

-ch

 

clonerTEST.miz

  • Like 1
Link to comment
Share on other sites

Version 1.01 - 20220623

Featured Updates: Recon Mode / Guardian Angel

Now with 1.0 under the belt, I finally found the time to polish two modules that are great, but barely on a level with other DML modules: Recon Mode and Guardian Angel. Recon mode had a clunky interface and positively squandered the potential to trigger events when an aircraft detects a group. And Guardian Angel did not handle missiles well that are controlled by advanced SAM sites who can re-target their missiles in the air.

image.png

All that is in the past now. First, Recon Mode gained more features than you can shake a pair of binoculars at: 

  • MUCH better reporting inclding Lat/Lon or MGRS of discovered group
  • Selective inclusion / exclusion of aircraft with trigger zones
  • Trigger-Zone based Priority- and Blacklisting
  • Trigger-Zone based flags when groups are discovered (only priority targets)
  • Detections can now trigger game events
  • Custom priority messages per priority zone
  • Support for cloned groups
  • Ability to individually suppress reports and map marks so detection events can be triggered without generating a report
  • Turn Recon Mode on and off via flags

And Guardian Angel has gained the following abilities:

  • Correctly handles re-targeting missiles
  • Dynamically turn Guardian Angel on and off during the mission
  • Adding Aircraft to an ‘unprotected’ list using trigger zones
  • Adding Aircraft to a ‘protected’ list using trigger zones 

And of course, they each have their own demo with full discussion of the new features 

Details

Documentation
    - Added Guardian Angel Reloaded Documentation
    - Added Recon Mode Reloaded doc 
    
Demos
    - updated Guardian Angel Reloaded
    - Recon Mode Reloaded
    
Modules
    - changer 1.0.1
    - clone zone 1.4.8
    - guardian angel 3.0.1
    - recon mode 2.1.0

 

Enjoy,
-ch


Edited by cfrag
Link to comment
Share on other sites

Version 1.02 - 20220630

IMPOSTORS!

or: switch groups between being AI-controlled and static objects at will.

All AI units consume CPU - even if they have to wait until it's their time to become active. "Impostors" is an easy way to turn AI units into static objects while they wait, and then, when they should become active, turn them back into AI-controlled units (and yes, you can hot-spawn aircraft at parking, so they a ready to roll the second you activate them). Or, when they reach their destination and are supposed to become inactive eye candy, turn any AI group into a collection of static objects and take the load off your CPU.  

For example, you can place a group of ground units on the map, turn them into static objects, and only activate them if the player comes close enough - an ideal trap, so to speak (imagine an S-300 triggered to AI only when you are already inside the lethal range) 

image.png

So why call them 'imposters'? In many 3D game engines, developers use a technique called 'imposters' to conserve CPU. Impostors are simplified stand-ins (often nothing more than painted billboards) for more detailed game objects that are too far away for the player to notice the difference. Since unit AI is a drag on CPU, static objects can function as AI impostors and help reduce CPU demand when it is not required for these units to be controlled.

Changes

Documentation
    - Impostor module
    - Impossible Impostors demo 
    
Demos
    - Impossible Impostors 
    
Modules
    - impostors 1.0.0
    - cfxZones 2.8.1
    - groupTracker 1.1.3
 

 Enjoy,

-ch

 

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

  • 2 weeks later...
22 minutes ago, crispy12 said:

I'm having the same error message as well with getFlag and setFlag

I'll look into it ASAP. Do you perhaps have a screenshot of the error message, I'm not sure what you are referring to.

 

Ah. You mean this message?

image.png

I'll investigate. 


Edited by cfrag
Link to comment
Share on other sites

Version 1.03 - 20220714

Featured Update:

image.png

A few weeks ago, I was pushing the limits of my own patience with a particularly crafty mission that I was designing. There's one thing that really proliferates in DML-based missions: flags. While they are exceedingly useful to run signals between modules, they also tend to be difficult to wrangle, and I was getting the blunt end of the semaphore, so to speak. Getting annoyed with myself, I went ahead and wrote an honest-to-goodness DML-style flag debugger: trigger zones, attributes, user-defined messages, the works. State of the art. *Good* - even put it up for everyone to comment on. I was proud of my work and patting myself on the back.

image.png
Observers at work, de-mystifying flags

Then, week-end came. As an aside, and after a bit too much "Death in the Afternoon" (the cocktail, not the book), I mentioned the concept to a friend while we were watching the sun set, glasses in hand.
 
"So", she noted, "not interactive". It wasn't even a question. 

Mixing more Absinthe with Champagne, she proceeded to outline a superior concept, only to interrupt hersef (and save my dignity) and happily point  West, where Venus was sparkling above the horizon.

So. 
Yes. 

DML now has an interactive debugger. Easy to use in-mission. With an advanced observer concept to boot, and full DML integration. You can set up missions in ME and (if you want to) add flag debug zones there. And you can run the mission, and interactively, in real time, issue commands to the debugger: query flags, set flags, have it observe flags for you, and notify you when they change, or change to a certain value. So, yes, the interactive part does increase the debugger's usefulness by orders of magnitude, Hemingway be damned! 

So what can it do?
Even without relying on Trigger Zones, you can - at any time during a mission - give it commands to 

  • examine and 
  • set the value of any flag in the mission. 

Since DCS has no console - how? By appropriating the "Mark Label" feature of the F10 map.

image.png

Enter the command into the text field, click outside, and the command is executed, returning the result as a text message:

image.png

When in doubt, issue the '-help' command, and the debugger shows you a list of everything it understands:

image.png

But that's just the start. You can also tell it to 

  • observe individual flags, and alert you when they change value. 
  • create 'observers'. They look for a condition like 'value changed' or 'value is euqal to 6'
  • add flags to observers, so you can group flags to observers. A flag can be added to multiple observers at the same time
  • take a snapshot of flags and later compare their current values to the one in the snapshot.

Most importantly, you can use ME to set up observers. Simply add a "debug" attribute to your zone, list the flags you want to observe. That way you start the mission with your flags under observation. 

image.png

And in-mission, you can add flags, change trigger conditions, remove them, etc. 

Oh, and another thing: Since it's of great appeal to many mission designers, I've also made the debugger available as a stand-alone package. It's some older versions of modules dcsCommon, cfxZones and debugger packed into one text file. If you are using DML, use the DML-native one that comes with the distribution. It's more up to date, smaller, better integrated and maintained. 


Changes:

Documentation
    - Debugger full documentation
    - dcsCommon API update to 2.6.8
    - Quick Ref: debugger
    
Demos
    - Bug Hunt
    
Modules
    - debugger 1.0.0

    - baseCaptured 1.0.1 (support for contested zones)
    - cfxZones 2.8.3
    - changer 1.0.2
    - dcsCommon 2.6.8
    - guardianAngel 3.0.2
    - impostors 1.0.1
    - pulseFlags 1.2.3
    - rndFlags 1.3.1
    - unitZone 1.2.3

 

Enjoy,

-ch

 

 


Edited by cfrag
Link to comment
Share on other sites

Hi @cfrag, I am trying to create the following:

Using the unitZone module, when a player is in zone, announce a message "Player XX is in the zone"
Is the a way to call on callsign name to announce via the messenger module?


So far I have made a VFR navigation mission - it will announce when overflying a waypoint zone (trigger zone on waypoint) and announce a generic message "Fighter is over the waypoint zone" or "Bombs dropped on target zone" but i would like to link it to the player if possible

 


Edited by crispy12
Link to comment
Share on other sites

3 hours ago, crispy12 said:

Using the unitZone module, when a player is in zone, announce a message "Player XX is in the zone"
Is the a way to call on callsign name to announce via the messenger module?

Only indirectly: you'd have a create pairs of unitZone and Messengers for each callsign (i.e. player plane) that you want to detect. This will get boring and difficult to maintain fast. One of the reasons that is not possible is that flags can only carry number information, not strings (their names can now be strings, their values only numbers)

-ch

  • Like 1
Link to comment
Share on other sites

Version: 1.1.0 - 20220728

Major Update: Persistence

image.png

Well, actually it's already here, in a limited form. By adding persistence, a player now can, out-of-the-box, save and load 

  • ground units
  • static objects and cargo 
  • flags (yeah, kind of obvious, right?)

With the usual DCS-specific caveats:

  • only applicable to ground units placed with ME (saving DML spawns will come later)
  • ground units that are loaded from file will always move towards their first waypoint, so if you give them routes, make it single-point routes 
  • loading a mission will not restore date and time

This is a start to DML's persistence capability, I'm planning to add

  • automatic integration of other modules - they automatically detect persistence and play ball (e.g. RadioMenus save if they are visible or not, Randomizers save which flags they already used up, etc.)
  • include DML's dynamic spawners (clone zones, spawn zones, object spawners)
  • include ground forces DML tasking  (cfxCommander, cfxGroundForces Orders)

Persistence (the ability to save and load data) requires that the player who hosts the mission (be it single- or multiplayer) change some security settings, and therefore - although potentially a killer feature - may only have limited appeal to everyone. As a result, persistence will be rolled out to modules over time, and at a lower priority. 

At initial release release, the persistence module, when added it to your mission, automatically

  • checks for read/write ability and automatically becomes transparent when it can't read or write (so the mission will work on a standard (sanitized) DCS installation)
  • creates a mission-specific data directory (optional)
  • provides a service layer for all modules that want to save data 
  • saves any flags that you indicate 
  • handles load/save requests from the mission
  • handles mission versioning from ME
  • handles 'fresh start' requests (i.e. discard last save) so you don't have to delete the data manually (dedicated server admins, please rejoice now)
  • provides a method for sharing data between missions so multiple DML missions cac access the same data (e.g. share scores, or locations of destroyed units etc). How data is shared is up to the modules, the persistence module merely provides tha service

The first module to receive the ability to write data was the debugger (obviously a perfect target to test this ability), which now can save the session's log to file, making it an incredibly useful tool for testers (who can then send the log to the mission designer). 
Also, as a definite proof of concept, I added the unitPersistence module that follows the new persistence load/save protocol. It currently saves and restores all ME-placed ground units, static objects and cargo. 

image.png
Yes, unitPersistence can persist destroyed static objects

Due to its inherent complexity (and the fact that DCS missions where never designed to be saved and started again), persistence has to be highly engineered. The current design defines the basic persistence module as provider for a read/write service layer as a service to modules. Other modules can subscribe to this service when they choose to support persistence). This allows modules to support persistence individually, while sharing a common infrastructure.

Design isn't final yet, and some of the specifics will change. That being said, however, the design currently incorporates the following: 

  • mission-individual storage directory (done)
  • shared 'pool' storage for easy sharing of data among different missions (planned)
  • fail gracefully: if your DCS installation isn't set up to support persistence, saving or loading data must not break the mission (done)
  • abstraction: modules provide the data, while the persistence layer provides the container (and location) where the files are written/read (done)
  • update friendly: you can force a fresh mission restart when you update your mission and last version's save data isn't compatible (done)
  • server-provider friendly: stand-alone server providers can configure their servers in different ways. Mission designers/server admins should be able to change the location of the save files centrally with a config zone attribute in ME (partly done)
  • persistence will be rolled out module-by-module, and must not break compatibility with earlier versions

Obviously, there are still open questions. For the time being, basic persistence is now included with DML, and you can add basic misison load/save capability just by adding the modules.   

 

In other News...
there have been some scattered updates and other changes of which I'd only like to high-light two:

Delicates 
have become even more delictae by allowing a trigger to 'blown them all', simulating a remote detonator 

RadioMenus 
Have stepped up their game by adding the ability to be removed and added by simple flag input

 

Changes:

Documentation
    - Persistence full documentation
    - Persistence module documentation 
    - unitPersistence documentation 
    - Being Persistent demo documentation
    - Quick Ref: persistence & unitPersistence
    
Demos
    - Being Persistent
    
Modules
    - persistence 1.0.0 (new!) basic persistence layer for DML missions

    - unitPersistence 1.0.0 (new!) persists ground units and static objects placed in ME

    - debugger 1.1.1
        "-save" command saves log to file 
        warning when setting flag to non-int 
  
    - cfxCargoReceiver 1.2.2
        removed a bug when calculating the displayed distance to zone's center while reporting/talking down. 
  
    - objectSpawnZones 1.3.0
        Ability to integrate with the delicates module, making spawned objects (e.g. cargo) delicate
        Added synonyms

    - cfxZones 2.8.4
        Fixed a bug in setFlagValue

    - dcsCommon 2.6.8
        new menu2text() method (API)
        new GetMissionName() method
        new flagArrayFromString() method
  
    - delicates 1.1.0
        better synonym handling 
        new addStaticObjectInventoryForZone() method (API)
        blowAll? input 
        safetyMargin attribute to allow some damage 
  
    - radioMenu 1.1.0 -- add and remove menus during mission 
        removeMenu? input
        addMenu? input 
        menuVisible bool 
  
    - cfxMX 1.2.0
        added more data cross references 

    - rndFlags 1.3.2
        moved flagArrayFromString to dcsCommon
  

Cheers,

-ch

 


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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

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