-- theDebugger debugger = {} debugger.version = "1.1.2" debugDemon = {} debugDemon.version = "1.1.2" debugger.verbose = false debugger.ups = 4 -- every 0.25 second debugger.name = "DML Debugger" -- for compliance with cfxZones debugger.log = "" --[[-- Version History 1.0.0 - Initial version 1.0.1 - made ups available to config zone - changed 'on' to 'active' in config zone - merged debugger and debugDemon - QoL check for 'debug' attribute (no '?') 1.1.0 - logging - trigger.action --> debugger for outText - persistence of logs - save 1.1.1 - warning when trying to set a flag to a non-int 1.1.2 - remove command --]]-- debugger.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course } -- note: saving logs requires persistence module -- will auto-abort saving if not present debugger.debugZones = {} debugger.debugUnits = {} debugger.debugGroups = {} debugger.debugObjects = {} -- -- Logging & saving -- function debugger.outText(message, seconds, cls) if not message then message = "" end if not seconds then seconds = 20 end if not cls then cls = false end -- append message to log, and add a lf if not debugger.log then debugger.log = "" end debugger.log = debugger.log .. message .. "\n" -- now hand up to trigger trigger.action.outText(message, seconds, cls) end function debugger.saveLog(name) if not _G["persistence"] then debugger.outText("+++debug: persistence module required to save log") return end if not persistence.active then debugger.outText("+++debug: persistence module can't write. ensur you desanitize lfs and io") return end if persistence.saveText(debugger.log, name) then debugger.outText("+++debug: log saved to <" .. persistence.missionDir .. name .. ">") else debugger.outText("+++debug: unable to save log to <" .. persistence.missionDir .. name .. ">") end end -- -- tracking flags -- function debugger.addDebugger(theZone) table.insert(debugger.debugZones, theZone) end function debugger.getDebuggerByName(aName) for idx, aZone in pairs(debugger.debugZones) do if aName == aZone.name then return aZone end end if debugger.verbose then debugger.outText("+++debug: no debug zone with name <" .. aName ..">", 30) end return nil end function debugger.removeDebugger(theZone) local filtered = {} for idx, dZone in pairs(debugger.debugZones) do if dZone == theZone then else table.insert(filtered, dZone) end end debugger.debugZones = filtered end -- -- read zone -- function debugger.createDebuggerWithZone(theZone) -- watchflag input trigger theZone.debugInputMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") if cfxZones.hasProperty(theZone, "debugTriggerMethod") then theZone.debugInputMethod = cfxZones.getStringFromZoneProperty(theZone, "debugTriggerMethod", "change") elseif cfxZones.hasProperty(theZone, "inputMethod") then theZone.debugInputMethod = cfxZones.getStringFromZoneProperty(theZone, "inputMethod", "change") elseif cfxZones.hasProperty(theZone, "sayWhen") then theZone.debugInputMethod = cfxZones.getStringFromZoneProperty(theZone, "sayWhen", "change") end -- say who we are and what we are monitoring if debugger.verbose or theZone.verbose then debugger.outText("---debug: adding zone <".. theZone.name .."> to look for in flag(s):", 30) end -- read main debug array local theFlags = cfxZones.getStringFromZoneProperty(theZone, "debug?", "") -- now, create an array from that local flagArray = cfxZones.flagArrayFromString(theFlags) local valueArray = {} -- now establish current values for idx, aFlag in pairs(flagArray) do local fVal = cfxZones.getFlagValue(aFlag, theZone) if debugger.verbose or theZone.verbose then debugger.outText(" monitoring flag <" .. aFlag .. ">, inital value is <" .. fVal .. ">", 30) end valueArray[aFlag] = fVal end theZone.flagArray = flagArray theZone.valueArray = valueArray -- DML output method theZone.debugOutputMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") if cfxZones.hasProperty(theZone, "outputMethod") then theZone.debugOutputMethod = cfxZones.getStringFromZoneProperty(theZone, "outputMethod", "inc") end if cfxZones.hasProperty(theZone, "debugMethod") then theZone.debugOutputMethod = cfxZones.getStringFromZoneProperty(theZone, "debugMethod", "inc") end -- notify! if cfxZones.hasProperty(theZone, "notify!") then theZone.debugNotify = cfxZones.getStringFromZoneProperty(theZone, "notify!", "") end -- debug message, can use all messenger vals plus for flag name -- we use out own default -- with meaning flag name,

previous value, current value theZone.debugMsg = cfxZones.getStringFromZoneProperty(theZone, "debugMsg", "---debug: -- Flag changed from

to []") end -- -- Misc -- function debugger.addFlagToObserver(flagName, theZone) table.insert(theZone.flagArray, flagName) local fVal = cfxZones.getFlagValue(flagName, theZone) theZone.valueArray[flagName] = fVal end function debugger.removeFlagFromObserver(flagName, theZone) local filtered = {} for idy, aName in pairs(theZone.flagArray) do if aName == flagName then else table.insert(filtered, aName) end end theZone.flagArray = filtered -- no need to clean up values, they are name-indexed. do it anyway theZone.valueArray[flagName] = nil end function debugger.isObservingWithObserver(flagName, theZone) for idy, aName in pairs(theZone.flagArray) do if aName == flagName then local val = theZone.valueArray[flagName] return true, val end end end function debugger.isObserving(flagName) -- scan all zones for flag, and return -- zone, and flag value if observing local observers = {} for idx, theZone in pairs(debugger.debugZones) do for idy, aName in pairs(theZone.flagArray) do if aName == flagName then table.insert(observers, theZone) end end end return observers end -- -- Update -- function debugger.processDebugMsg(inMsg, theZone, theFlag, oldVal, currVal) if not inMsg then return "" end if not oldVal then oldVal = "" else oldVal = tostring(oldVal) end if not currVal then currVal = "" else currVal = tostring(currVal) end if not theFlag then theFlag = "" end local formerType = type(inMsg) if formerType ~= "string" then inMsg = tostring(inMsg) end if not inMsg then inMsg = "" end -- build message by relacing wildcards local outMsg = "" -- replace line feeds outMsg = inMsg:gsub("", "\n") if theZone then outMsg = outMsg:gsub("", theZone.name) end -- replace ,

, with currVal, oldVal, flag outMsg = outMsg:gsub("", currVal) outMsg = outMsg:gsub("

", oldVal) outMsg = outMsg:gsub("", oldVal) -- just for QoL outMsg = outMsg:gsub("", theFlag) -- replace 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("", timeString) -- replace with lat of zone point and with lon of zone point -- and with mgrs coords of zone point if theZone then local currPoint = cfxZones.getPoint(theZone) local lat, lon, alt = coord.LOtoLL(currPoint) lat, lon = dcsCommon.latLon2Text(lat, lon) outMsg = outMsg:gsub("", lat) outMsg = outMsg:gsub("", 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) end return outMsg end function debugger.debugZone(theZone) -- check every flag of this zone for idx, aFlag in pairs(theZone.flagArray) do local oldVal = theZone.valueArray[aFlag] local oldVal = theZone.valueArray[aFlag] theZone.debugLastVal = oldVal local hasChanged, newValue = cfxZones.testZoneFlag( theZone, aFlag, theZone.debugInputMethod, "debugLastVal") -- we ALWAYS transfer latch back theZone.valueArray[aFlag] = newValue if hasChanged then -- we are triggered -- generate the ouput message local msg = theZone.debugMsg msg = debugger.processDebugMsg(msg, theZone, aFlag, oldVal, newValue) debugger.outText(msg, 30) end end end -- -- reset debugger -- function debugger.resetObserver(theZone) for idf, aFlag in pairs(theZone.flagArray) do local fVal = cfxZones.getFlagValue(aFlag, theZone) if debugger.verbose or theZone.verbose then debugger.outText("---debug: resetting flag <" .. aFlag .. ">, to <" .. fVal .. "> for zone <" .. theZone.name .. ">", 30) end theZone.valueArray[aFlag] = fVal end end function debugger.reset() for idx, theZone in pairs(debugger.debugZones) do -- reset this zone debugger.resetObserver(theZone) end end function debugger.showObserverState(theZone) for idf, aFlag in pairs(theZone.flagArray) do local fVal = cfxZones.getFlagValue(aFlag, theZone) if debugger.verbose or theZone.verbose then debugger.outText(" state of flag <" .. aFlag .. ">: <" .. theZone.valueArray[aFlag] .. ">", 30) end theZone.valueArray[aFlag] = fVal end end function debugger.showState() debugger.outText("---debug: CURRENT STATE <" .. dcsCommon.nowString() .. "> --- ", 30) for idx, theZone in pairs(debugger.debugZones) do -- show this zone's state if #theZone.flagArray > 0 then debugger.outText(" state of observer <" .. theZone.name .. "> looking for :", 30) debugger.showObserverState(theZone) else if theZone.verbose or debugger.verbose then debugger.outText(" (empty observer <" .. theZone.name .. ">)", 30) end end end debugger.outText("---debug: end of state --- ", 30) end function debugger.doActivate() debugger.active = true if debugger.verbose or true then debugger.outText("+++ DM Debugger is now active", 30) end end function debugger.doDeactivate() debugger.active = false if debugger.verbose or true then debugger.outText("+++ debugger deactivated", 30) end end function debugger.update() -- call me in a second to poll triggers timer.scheduleFunction(debugger.update, {}, timer.getTime() + 1/debugger.ups) -- first check for switch on or off if debugger.onFlag then if cfxZones.testZoneFlag(debugger, debugger.onFlag, "change","lastOn") then debugger.doActivate() end end if debugger.offFlag then if cfxZones.testZoneFlag(debugger, debugger.offFlag, "change","lastOff") then debugger.doDeactivate() end end -- ALWAYS check for reset & state. if debugger.resetFlag then if cfxZones.testZoneFlag(debugger, debugger.resetFlag, "change","lastReset") then debugger.reset() end end if debugger.stateFlag then if cfxZones.testZoneFlag(debugger, debugger.stateFlag, "change","lastState") then debugger.showState() end end -- only progress if we are on if not debugger.active then return end for idx, aZone in pairs(debugger.debugZones) do -- see if we are triggered debugger.debugZone(aZone) end end -- -- Config & Start -- function debugger.readConfigZone() local theZone = cfxZones.getZoneByName("debuggerConfig") if not theZone then if debugger.verbose then debugger.outText("+++debug: NO config zone!", 30) end theZone = cfxZones.createSimpleZone("debuggerConfig") end debugger.configZone = theZone debugger.active = cfxZones.getBoolFromZoneProperty(theZone, "active", true) debugger.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) if cfxZones.hasProperty(theZone, "on?") then debugger.onFlag = cfxZones.getStringFromZoneProperty(theZone, "on?", "") debugger.lastOn = cfxZones.getFlagValue(debugger.onFlag, theZone) end if cfxZones.hasProperty(theZone, "off?") then debugger.offFlag = cfxZones.getStringFromZoneProperty(theZone, "off?", "") debugger.lastOff = cfxZones.getFlagValue(debugger.offFlag, theZone) end if cfxZones.hasProperty(theZone, "reset?") then debugger.resetFlag = cfxZones.getStringFromZoneProperty(theZone, "reset?", "") debugger.lastReset = cfxZones.getFlagValue(debugger.resetFlag, theZone) end if cfxZones.hasProperty(theZone, "state?") then debugger.stateFlag = cfxZones.getStringFromZoneProperty(theZone, "state?", "") debugger.lastState = cfxZones.getFlagValue(debugger.stateFlag, theZone) end debugger.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 4) if debugger.verbose then debugger.outText("+++debug: read config", 30) end end function debugger.start() -- lib check if not dcsCommon.libCheck then trigger.action.outText("cfx debugger requires dcsCommon", 30) return false end if not dcsCommon.libCheck("cfx debugger", debugger.requiredLibs) then return false end -- read config debugger.readConfigZone() -- process debugger Zones -- old style local attrZones = cfxZones.getZonesWithAttributeNamed("debug?") for k, aZone in pairs(attrZones) do debugger.createDebuggerWithZone(aZone) -- process attributes debugger.addDebugger(aZone) -- add to list end local attrZones = cfxZones.getZonesWithAttributeNamed("debug") for k, aZone in pairs(attrZones) do debugger.outText("***Warning: Zone <" .. aZone.name .. "> has a 'debug' flag. Are you perhaps missing a '?'", 30) end -- say if we are active if debugger.verbose then if debugger.active then debugger.outText("+++debugger loaded and active", 30) else debugger.outText("+++ debugger: standing by for activation", 30) end end -- start update debugger.update() debugger.outText("cfx debugger v" .. debugger.version .. " started.", 30) return true end -- let's go! if not debugger.start() then trigger.action.outText("cfx debugger aborted: missing libraries", 30) debugger = nil end -- -- DEBUG DEMON -- debugDemon.myObserverName = "+DML Debugger+" -- interactive interface for DML debugger debugDemon.verbose = false -- based on cfx stage demon --[[-- Version History 1.0.0 - initial version 1.1.0 - save command, requires persistence --]]-- debugDemon.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course "debugger", } debugDemon.markOfDemon = "-" -- all commands must start with this sequence debugDemon.splitDelimiter = " " debugDemon.commandTable = {} -- key, value pair for command processing per keyword debugDemon.keepOpen = false -- keep mark open after a successful command debugDemon.snapshot = {} function debugDemon.hasMark(theString) -- check if the string begins with the sequece to identify commands if not theString then return false end return theString:find(debugDemon.markOfDemon) == 1 end -- main hook into DCS. Called whenever a Mark-related event happens -- very simple: look if text begins with special sequence, and if so, -- call the command processor. function debugDemon:onEvent(theEvent) -- while we can hook into any of the three events, -- we curently only utilize CHANGE Mark if not (theEvent.id == world.event.S_EVENT_MARK_ADDED) and not (theEvent.id == world.event.S_EVENT_MARK_CHANGE) and not (theEvent.id == world.event.S_EVENT_MARK_REMOVED) then -- not of interest for us, bye bye return end -- when we get here, we have a mark event if theEvent.id == world.event.S_EVENT_MARK_ADDED then -- add mark is quite useless end if theEvent.id == world.event.S_EVENT_MARK_CHANGE then -- when changed, the mark's text is examined for a command -- if it starts with the 'mark' string ("-" by default) it is processed -- by the command processor -- if it is processed succesfully, the mark is immediately removed -- else an error is displayed and the mark remains. if debugDemon.hasMark(theEvent.text) then -- strip the mark local commandString = theEvent.text:sub(1+debugDemon.markOfDemon:len()) -- break remainder apart into ... local commands = dcsCommon.splitString(commandString, debugDemon.splitDelimiter) -- this is a command. process it and then remove it if it was executed successfully local success = debugDemon.executeCommand(commands, theEvent) -- remove this mark after successful execution if success then trigger.action.removeMark(theEvent.idx) else -- we could play some error sound end end end if theEvent.id == world.event.S_EVENT_MARK_REMOVED then end end -- -- add / remove commands to/from vocabulary -- function debugDemon.addCommndProcessor(command, processor) debugDemon.commandTable[command:upper()] = processor end function debugDemon.removeCommandProcessor(command) debugDemon.commandTable[command:upper()] = nil end -- -- process input arguments. Here we simply move them -- up by one. -- function debugDemon.getArgs(theCommands) local args = {} for i=2, #theCommands do table.insert(args, theCommands[i]) end return args end -- -- stage demon's main command interpreter. -- magic lies in using the keywords as keys into a -- function table that holds all processing functions -- I wish we had that back in the Oberon days. -- function debugDemon.executeCommand(theCommands, event) local cmd = theCommands[1] local arguments = debugDemon.getArgs(theCommands) if not cmd then return false end -- since we have a command in cmd, we remove this from -- the string, and pass the remainder back local remainder = event.text:sub(1 + debugDemon.markOfDemon:len()) remainder = dcsCommon.stripLF(remainder) remainder = dcsCommon.trim(remainder) remainder = remainder:sub(1+cmd:len()) remainder = dcsCommon.trim(remainder) event.remainder = remainder -- use the command as index into the table of functions -- that handle them. if debugDemon.commandTable[cmd:upper()] then local theInvoker = debugDemon.commandTable[cmd:upper()] local success = theInvoker(arguments, event) return success else debugger.outText("***error: unknown command <".. cmd .. ">", 30) return false end return true end -- -- Helpers -- function debugDemon.createObserver(aName) local observer = cfxZones.createSimpleZone(aName) observer.verbose = debugDemon.verbose observer.debugInputMethod = "change" observer.flagArray = {} observer.valueArray = {} observer.debugMsg = "---debug: -- Flag changed from

to []" return observer end -- -- COMMANDS -- function debugDemon.processHelpCommand(args, event) debugger.outText("*** debugger: commands are:" .. "\n " .. debugDemon.markOfDemon .. "show -- show current values for flag or observer" .. "\n " .. debugDemon.markOfDemon .. "set -- set flag to value " .. "\n " .. debugDemon.markOfDemon .. "inc -- increase flag by 1, changing it" .. "\n " .. debugDemon.markOfDemon .. "flip -- when flag's value is 0, set it to 1, else to 0" .. "\n\n " .. debugDemon.markOfDemon .. "observe [with ] -- observe a flag for change" .. "\n " .. debugDemon.markOfDemon .. "o [with ] -- observe a flag for change" .. "\n " .. debugDemon.markOfDemon .. "forget [with ] -- stop observing a flag" .. "\n " .. debugDemon.markOfDemon .. "new [[for] ] -- create observer for flags" .. "\n " .. debugDemon.markOfDemon .. "update [[to] ] -- change observer's condition" .. "\n " .. debugDemon.markOfDemon .. "drop -- remove observer from debugger" .. "\n " .. debugDemon.markOfDemon .. "list [] -- list observers [name contains ]" .. "\n " .. debugDemon.markOfDemon .. "who -- all who observe " .. "\n " .. debugDemon.markOfDemon .. "reset [] -- reset all or only the named observer" .. "\n\n " .. debugDemon.markOfDemon .. "snap [] -- create new snapshot of flags" .. "\n " .. debugDemon.markOfDemon .. "compare -- compare snapshot flag values with current" .. "\n " .. debugDemon.markOfDemon .. "note -- add to the text log" .. "\n\n " .. debugDemon.markOfDemon .. "remove -- remove named item from mission" .. "\n\n " .. debugDemon.markOfDemon .. "start -- starts debugger" .. "\n " .. debugDemon.markOfDemon .. "stop -- stop debugger" .. "\n\n " .. debugDemon.markOfDemon .. "save [] -- saves debugger log to storage" .. "\n\n " .. debugDemon.markOfDemon .. "? or -help -- this text", 30) return true end function debugDemon.processNewCommand(args, event) -- syntax new [[for] ] local observerName = args[1] if not observerName then debugger.outText("*** new: missing observer name.", 30) return false -- allows correction end -- see if this observer already existst local theObserver = debugger.getDebuggerByName(observerName) if theObserver then debugger.outText("*** new: observer <" .. observerName .. "> already exists.", 30) return false -- allows correction end -- little pitfall: what if the name contains blanks? -- also check against remainder! local remainderName = event.remainder local rObserver = debugger.getDebuggerByName(remainderName) if rObserver then debugger.outText("*** new: observer <" .. remainderName .. "> already exists.", 30) return false -- allows correction end local theZone = nil local condition = args[2] -- may need remainder instead if condition == "for" then -- we use arg[2] else observerName = remainderName -- we use entire rest of line end theZone = debugDemon.createObserver(observerName) if condition == "for" then condition = args[3] end if condition then if not cfxZones.verifyMethod(condition, theZone) then debugger.outText("*** new: illegal trigger condition <" .. condition .. "> for observer <" .. observerName .. ">", 30) return false end theZone.debugInputMethod = condition end debugger.addDebugger(theZone) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: new observer <" .. observerName .. "> for <" .. theZone.debugInputMethod .. ">", 30) return true end function debugDemon.processUpdateCommand(args, event) -- syntax update [[to] ] local observerName = args[1] if not observerName then debugger.outText("*** update: missing observer name.", 30) return false -- allows correction end -- see if this observer already existst local theZone = debugger.getDebuggerByName(observerName) if not theZone then debugger.outText("*** update: observer <" .. observerName .. "> does not exist exists.", 30) return false -- allows correction end local condition = args[2] -- may need remainder instead if condition == "to" then condition = args[3] end if condition then if not cfxZones.verifyMethod(condition, theZone) then debugger.outText("*** update: illegal trigger condition <" .. condition .. "> for observer <" .. observerName .. ">", 30) return false end theZone.debugInputMethod = condition end debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: updated observer <" .. observerName .. "> to <" .. theZone.debugInputMethod .. ">", 30) return true end function debugDemon.processDropCommand(args, event) -- syntax drop local observerName = event.remainder -- remainder if not observerName then debugger.outText("*** drop: missing observer name.", 30) return false -- allows correction end -- see if this observer already existst local theZone = debugger.getDebuggerByName(observerName) if not theZone then debugger.outText("*** drop: observer <" .. observerName .. "> does not exist exists.", 30) return false -- allows correction end -- now simply and irrevocable remove the observer, unless it's home, -- in which case it's simply reset if theZone == debugDemon.observer then debugger.outText("*** drop: <" .. observerName .. "> is MY PRECIOUS and WILL NOT be dropped.", 30) -- can't really happen since it contains blanks, but -- we've seen stranger things return false -- allows correction end debugger.removeDebugger(theZone) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: dropped observer <" .. observerName .. ">", 30) return true end -- observe command: add a new flag to observe function debugDemon.processObserveCommand(args, event) -- syntax: observe [with ] -- args[1] is the name of the flag local flagName = args[1] if not flagName then debugger.outText("*** observe: missing flag name.", 30) return false -- allows correction end local withTracker = nil if args[2] == "with" then local aName = args[3] if not aName then debugger.outText("*** observe: missing after 'with'.", 30) return false -- allows correction end aName = dcsCommon.stringRemainsStartingWith(event.remainder, aName) withTracker = debugger.getDebuggerByName(aName) if not withTracker then -- withTracker = debugDemon.createObserver(aName) -- debugger.addDebugger(withTracker) debugger.outText("*** observe: no observer <" .. aName .. "> exists", 30) return false -- allows correction end else -- not with as arg 2 if #args > 1 then debugger.outText("*** observe: unknown command after flag name '" .. flagName .. "'.", 30) return false -- allows correction end -- use own observer withTracker = debugDemon.observer end if debugger.isObservingWithObserver(flagName, withTracker) then debugger.outText("*** observe: already observing " .. flagName .. " with <" .. withTracker.name .. ">" , 30) return true end -- we add flag to tracker and init value debugger.addFlagToObserver(flagName, withTracker) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: now observing <" .. flagName .. "> for value " .. withTracker.debugInputMethod .. " with <" .. withTracker.name .. ">.", 30) return true end function debugDemon.processShowCommand(args, event) -- syntax -show with name either flag or observer -- observer has precendce over flag local theName = args[1] if not theName then debugger.outText("*** show: missing observer/flag name.", 30) return false -- allows correction end -- now see if we have an observer theName = dcsCommon.stringRemainsStartingWith(event.remainder, theName) local theObserver = debugger.getDebuggerByName(theName) if not theObserver then -- we directly use trigger.misc local fVal = trigger.misc.getUserFlag(theName) debugger.outText("[" .. dcsCommon.nowString() .. "] flag <" .. theName .. "> : value <".. fVal .. ">", 30) return true end -- if we get here, we want to show an entire observer debugger.outText("*** [" .. dcsCommon.nowString() .. "] flags observed by <" .. theName .. "> looking for :", 30) local flags = theObserver.flagArray local values = theObserver.valueArray for idx, flagName in pairs(flags) do local lastVal = values[flagName] local fVal = cfxZones.getFlagValue(flagName, theObserver) -- add code to detect if it would trigger here local hit = cfxZones.testFlagByMethodForZone(fVal, lastVal, theObserver.debugInputMethod, theObserver) local theMark = " " local trailer = "" if hit then theMark = " ! " trailer = ", HIT!" end debugger.outText(theMark .. "f:<" .. flagName .. "> = <".. fVal .. "> [current, state = <" .. values[flagName] .. ">" .. trailer .. "]", 30) end return true end function debugDemon.createSnapshot(allObservers) if not allObservers then allObservers = {debugDemon.observer} end local snapshot = {} for idx, theZone in pairs(allObservers) do -- iterate each observer for idy, flagName in pairs (theZone.flagArray) do local fullName = cfxZones.expandFlagName(flagName, theZone) local fVal = trigger.misc.getUserFlag(fullName) snapshot[fullName] = fVal end end return snapshot end function debugDemon.processSnapCommand(args, event) -- syntax snap [] local allObservers = debugger.debugZones -- default: all zones local theObserver = nil local theName = args[1] if theName then -- now see if we have an observer theName = dcsCommon.stringRemainsStartingWith(event.remainder, theName) theObserver = debugger.getDebuggerByName(theName) if not theObserver then debugger.outText("*** snap: unknown observer name <" .. theName .. ">.", 30) return false -- allows correction end end if theObserver then allObservers = {} table.insert(allObservers, theObserver) end -- set up snapshot local snapshot = debugDemon.createSnapshot(allObservers) --{} --[[-- for idx, theZone in pairs(allObservers) do -- iterate each observer for idy, flagName in pairs (theZone.flagArray) do local fullName = cfxZones.expandFlagName(flagName, theZone) local fVal = trigger.misc.getUserFlag(fullName) snapshot[fullName] = fVal end end --]]-- local sz = dcsCommon.getSizeOfTable(snapshot) debugDemon.snapshot = snapshot debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: new snapshot created, " .. sz .. " flags.", 30) return true end function debugDemon.processCompareCommand(args, event) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: comparing snapshot with current flag values", 30) for flagName, val in pairs (debugDemon.snapshot) do local cVal = trigger.misc.getUserFlag(flagName) local mark = ' ' if cVal ~= val then mark = ' ! ' end debugger.outText(mark .. "<" .. flagName .. "> snap = <" .. val .. ">, now = <" .. cVal .. "> " .. mark, 30) end debugger.outText("*** END", 30) return true end function debugDemon.processNoteCommand(args, event) local n = event.remainder debugger.outText("*** [" .. dcsCommon.nowString() .. "]: " .. n, 30) return true end function debugDemon.processSetCommand(args, event) -- syntax set local theName = args[1] if not theName then debugger.outText("*** set: missing flag name.", 30) return false -- allows correction end local theVal = args[2] if theVal and type(theVal) == "string" then theVal = theVal:upper() if theVal == "YES" or theVal == "TRUE" then theVal = "1" end if theVal == "NO" or theVal == "FALSE" then theVal = "0" end end if not theVal or not (tonumber(theVal)) then debugger.outText("*** set: missing or illegal value for flag <" .. theName .. ">.", 30) return false -- allows correction end theVal = tonumber(theVal) trigger.action.setUserFlag(theName, theVal) -- we set directly, no cfxZones proccing local note ="" -- flags are ints only? if theVal ~= math.floor(theVal) then note = " [int! " .. math.floor(theVal) .. "]" end debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: set flag <" .. theName .. "> to <" .. theVal .. ">" .. note, 30) return true end function debugDemon.processIncCommand(args, event) -- syntax inc local theName = args[1] if not theName then debugger.outText("*** inc: missing flag name.", 30) return false -- allows correction end local cVal = trigger.misc.getUserFlag(theName) local nVal = cVal + 1 -- we set directly, no cfxZones procing debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: inc flag <" .. theName .. "> from <" .. cVal .. "> to <" .. nVal .. ">", 30) trigger.action.setUserFlag(theName, nVal) return true end function debugDemon.processFlipCommand(args, event) -- syntax flip local theName = args[1] if not theName then debugger.outText("*** flip: missing flag name.", 30) return false -- allows correction end local cVal = trigger.misc.getUserFlag(theName) if cVal == 0 then nVal = 1 else nVal = 0 end -- we set directly, no cfxZones procing debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: flipped flag <" .. theName .. "> from <" .. cVal .. "> to <" .. nVal .. ">", 30) trigger.action.setUserFlag(theName, nVal) return true end function debugDemon.processListCommand(args, event) -- syntax list or list local prefix = nil prefix = args[1] if prefix then prefix = event.remainder -- dcsCommon.stringRemainsStartingWith(event.text, prefix) end if prefix then debugger.outText("*** [" .. dcsCommon.nowString() .. "] listing observers whose name contains <" .. prefix .. ">:", 30) else debugger.outText("*** [" .. dcsCommon.nowString() .. "] listing all observers:", 30) end local allObservers = debugger.debugZones for idx, theZone in pairs(allObservers) do local theName = theZone.name local doList = true if prefix then doList = dcsCommon.containsString(theName, prefix, false) end if doList then debugger.outText(" <" .. theName .. "> for (" .. #theZone.flagArray .. " flags)", 30) end end return true end function debugDemon.processWhoCommand(args, event) -- syntax: who local flagName = event.remainder -- args[1] if not flagName or flagName:len()<1 then debugger.outText("*** who: missing flag name.", 30) return false -- allows correction end local observers = debugger.isObserving(flagName) if not observers or #observers < 1 then debugger.outText("*** [" .. dcsCommon.nowString() .. "] flag <" .. flagName .. "> is currently not observed", 30) return false end debugger.outText("*** [" .. dcsCommon.nowString() .. "] flag <" .. flagName .. "> is currently observed by", 30) for idx, theZone in pairs(observers) do debugger.outText(" <" .. theZone.name .. "> looking for ", 30) end return true end function debugDemon.processForgetCommand(args, event) -- syntax: forget [with ] local flagName = args[1] if not flagName then debugger.outText("*** forget: missing flag name.", 30) return false -- allows correction end local withTracker = nil if args[2] == "with" or args[2] == "from" then -- we also allow 'from' local aName = args[3] if not aName then debugger.outText("*** forget: missing after 'with'.", 30) return false -- allows correction end aName = dcsCommon.stringRemainsStartingWith(event.remainder, aName) withTracker = debugger.getDebuggerByName(aName) if not withTracker then debugger.outText("*** forget: no observer named <" .. aName .. ">", 30) return false end else -- not with as arg 2 if #args > 1 then debugger.outText("*** forget: unknown command after flag name '" .. flagName .. "'.", 30) return false -- allows correction end -- use own observer withTracker = debugDemon.observer end if not debugger.isObservingWithObserver(flagName, withTracker) then debugger.outText("*** forget: observer <" .. withTracker.name .. "> does not observe flag <" .. flagName .. ">", 30) return false end -- we add flag to tracker and init value debugger.removeFlagFromObserver(flagName, withTracker) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger: no longer observing " .. flagName .. " with <" .. withTracker.name .. ">.", 30) return true end function debugDemon.processStartCommand(args, event) debugger.doActivate() return true end function debugDemon.processStopCommand(args, event) debugger.doDeactivate() return true end function debugDemon.processResetCommand(args, event) -- supports reset -- syntax: forget [with ] local obsName = args[1] if not obsName then debugger.reset() -- reset all debugger.outText("*** [" .. dcsCommon.nowString() .. "] debug: reset complete.", 30) return true -- allows correction end local withTracker = nil local aName = event.remainder withTracker = debugger.getDebuggerByName(aName) if not withTracker then debugger.outText("*** reset: no observer <" .. aName .. ">", 30) return false end debugger.resetObserver(withTracker) debugger.outText("*** [" .. dcsCommon.nowString() .. "] debugger:reset observer <" .. withTracker.name .. ">", 30) return true end function debugDemon.processSaveCommand(args, event) -- save log to file, requires persistence module -- syntax: -save [] local aName = event.remainder if not aName or aName:len() < 1 then aName = "DML Debugger Log" end if not dcsCommon.stringEndsWith(aName, ".txt") then aName = aName .. ".txt" end debugger.saveLog(aName) return true end function debugDemon.processRemoveCommand(args, event) -- remove a group, unit or object -- try group first local aName = event.remainder if not aName or aName:len() < 1 then debugger.outText("*** remove: no remove target", 30) return false end aName = dcsCommon.trim(aName) local theGroup = Group.getByName(aName) if theGroup and theGroup:isExist() then theGroup:destroy() debugger.outText("*** remove: removed group <" .. aName .. ">", 30) return true end local theUnit = Unit.getByName(aName) if theUnit and theUnit:isExist() then theUnit:destroy() debugger.outText("*** remove: removed unit <" .. aName .. ">", 30) return true end local theStatic = StaticObject.getByName(aName) if theStatic and theStatic:isExist() then theStatic:destroy() debugger.outText("*** remove: removed static object <" .. aName .. ">", 30) return true end debugger.outText("*** remove: did not find anything called <" .. aName .. "> to remove", 30) return true end -- -- init and start -- function debugDemon.readConfigZone() local theZone = cfxZones.getZoneByName("debugDemonConfig") if not theZone then if debugDemon.verbose then debugger.outText("+++debug (daemon): NO config zone!", 30) end theZone = cfxZones.createSimpleZone("debugDemonConfig") end debugDemon.configZone = theZone debugDemon.keepOpen = cfxZones.getBoolFromZoneProperty(theZone, "keepOpen", false) debugDemon.markOfDemon = cfxZones.getStringFromZoneProperty(theZone,"mark", "-") -- all commands must start with this sequence debugDemon.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) if debugger.verbose then debugger.outText("+++debug (deamon): read config", 30) end end function debugDemon.init() if not dcsCommon.libCheck then trigger.action.outText("cfx interactive debugger requires dcsCommon", 30) return false end if not dcsCommon.libCheck("cfx interactive debugger", debugDemon.requiredLibs) then return false end -- config debugDemon.readConfigZone() -- now add known commands to interpreter. debugDemon.addCommndProcessor("observe", debugDemon.processObserveCommand) debugDemon.addCommndProcessor("o", debugDemon.processObserveCommand) -- synonym debugDemon.addCommndProcessor("forget", debugDemon.processForgetCommand) debugDemon.addCommndProcessor("show", debugDemon.processShowCommand) debugDemon.addCommndProcessor("set", debugDemon.processSetCommand) debugDemon.addCommndProcessor("inc", debugDemon.processIncCommand) debugDemon.addCommndProcessor("flip", debugDemon.processFlipCommand) debugDemon.addCommndProcessor("list", debugDemon.processListCommand) debugDemon.addCommndProcessor("who", debugDemon.processWhoCommand) debugDemon.addCommndProcessor("new", debugDemon.processNewCommand) debugDemon.addCommndProcessor("update", debugDemon.processUpdateCommand) debugDemon.addCommndProcessor("drop", debugDemon.processDropCommand) debugDemon.addCommndProcessor("snap", debugDemon.processSnapCommand) debugDemon.addCommndProcessor("compare", debugDemon.processCompareCommand) debugDemon.addCommndProcessor("note", debugDemon.processNoteCommand) debugDemon.addCommndProcessor("start", debugDemon.processStartCommand) debugDemon.addCommndProcessor("stop", debugDemon.processStopCommand) debugDemon.addCommndProcessor("reset", debugDemon.processResetCommand) debugDemon.addCommndProcessor("save", debugDemon.processSaveCommand) debugDemon.addCommndProcessor("?", debugDemon.processHelpCommand) debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand) debugDemon.addCommndProcessor("remove", debugDemon.processRemoveCommand) return true end function debugDemon.start() -- add my own debug zones to debugger so it can -- track any changes local observer = debugDemon.createObserver(debugDemon.myObserverName) debugDemon.observer = observer debugger.addDebugger(observer) -- create initial snapshot debugDemon.snapshot = debugDemon.createSnapshot(debugger.debugZones) debugDemon.demonID = world.addEventHandler(debugDemon) debugger.outText("interactive debugDemon v" .. debugDemon.version .. " started" .. "\n enter " .. debugDemon.markOfDemon .. "? in a map mark for help", 30) if not _G["persistence"] then debugger.outText("\n note: '-save' disabled, no persistence module found", 30) end end if debugDemon.init() then debugDemon.start() else trigger.action.outText("*** interactive flag debugger failed to initialize.", 30) debugDemon = {} end --[[-- - track units/groups/objects: health changes - track players: unit change, enter, exit - inspect objects, dumping category, life, if it's tasking, latLon, alt, speed, direction - exec files. save all commands and then run them from script - query objects: -q persistence.active returns boolean, true -q x.y returns table, 12 elements -q a.b.x returns number 12 -q d.e.f returns string "asdasda..." -q sada reuturs - xref: which zones/attributes reference a flag, g.g. '-xref go' --]]--