Sinclair_76 Posted June 20, 2022 Posted June 20, 2022 Congralations on moving to 1.0! I am trying to get recon mode to run but when starting a mission I get this. I also included the scripts that have been loaded. What am I doing wrong? nullnull
cfrag Posted June 20, 2022 Author Posted June 20, 2022 (edited) 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 July 28, 2022 by cfrag 1
H0RN9T_ Posted June 21, 2022 Posted June 21, 2022 (edited) 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 June 21, 2022 by H0RN9T_
cfrag Posted June 21, 2022 Author Posted June 21, 2022 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.
H0RN9T_ Posted June 22, 2022 Posted June 22, 2022 (edited) 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 June 22, 2022 by H0RN9T_
cfrag Posted June 22, 2022 Author Posted June 22, 2022 (edited) 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 June 22, 2022 by cfrag
H0RN9T_ Posted June 22, 2022 Posted June 22, 2022 (edited) 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 June 22, 2022 by H0RN9T_
cfrag Posted June 22, 2022 Author Posted June 22, 2022 (edited) 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. 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 June 22, 2022 by cfrag
cfrag Posted June 22, 2022 Author Posted June 22, 2022 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.
H0RN9T_ Posted June 22, 2022 Posted June 22, 2022 (edited) 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 June 22, 2022 by H0RN9T_
H0RN9T_ Posted June 22, 2022 Posted June 22, 2022 (edited) 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. Maybe i did something wrong with adding scripts but everything works fine, even with that error. Edited June 22, 2022 by H0RN9T_
cfrag Posted June 22, 2022 Author Posted June 22, 2022 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.
H0RN9T_ Posted June 22, 2022 Posted June 22, 2022 17 minutes ago, cfrag said: 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. clonerTEST.miz
cfrag Posted June 22, 2022 Author Posted June 22, 2022 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 1
cfrag Posted June 23, 2022 Author Posted June 23, 2022 (edited) 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. 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 June 23, 2022 by cfrag
cfrag Posted June 30, 2022 Author Posted June 30, 2022 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) 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 2 1
crispy12 Posted July 12, 2022 Posted July 12, 2022 I'm having the same error message as well with getFlag and setFlag Occurs when the each Unit Zone is triggered BVR Trainer Timeline DML.miz
cfrag Posted July 12, 2022 Author Posted July 12, 2022 (edited) 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? I'll investigate. Edited July 12, 2022 by cfrag
cfrag Posted July 12, 2022 Author Posted July 12, 2022 (edited) Ok, unitZones was a bit over-eager when appropriating flags. Fixed. The change will be included with the next update. I've fixed it in your mission so you don't have to wait BVR Trainer Timeline DML.miz Edited July 12, 2022 by cfrag 1
cfrag Posted July 14, 2022 Author Posted July 14, 2022 (edited) Version 1.03 - 20220714 Featured Update: 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. 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. Enter the command into the text field, click outside, and the command is executed, returning the result as a text message: When in doubt, issue the '-help' command, and the debugger shows you a list of everything it understands: 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. 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 July 14, 2022 by cfrag
crispy12 Posted July 21, 2022 Posted July 21, 2022 (edited) 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 July 21, 2022 by crispy12
cfrag Posted July 21, 2022 Author Posted July 21, 2022 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 1
cfrag Posted July 28, 2022 Author Posted July 28, 2022 (edited) Version: 1.1.0 - 20220728 Major Update: Persistence 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. 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 July 28, 2022 by cfrag 3
Recommended Posts