diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index f36ed65..a0e337e 100644 Binary files a/Doc/DML Documentation.pdf and b/Doc/DML Documentation.pdf differ diff --git a/Doc/DML Quick Reference.pdf b/Doc/DML Quick Reference.pdf index 461433a..e0034ff 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/RNDFlags.lua b/modules/RNDFlags.lua index 310e38b..db50c62 100644 --- a/modules/RNDFlags.lua +++ b/modules/RNDFlags.lua @@ -1,5 +1,5 @@ rndFlags = {} -rndFlags.version = "1.3.0" +rndFlags.version = "1.3.1" rndFlags.verbose = false rndFlags.requiredLibs = { "dcsCommon", -- always @@ -25,6 +25,10 @@ rndFlags.requiredLibs = { 1.2.0 - Watchflag integration 1.3.0 - DML simplification: RND! zone-local verbosity + 1.3.1 - 'done+1' --> 'done!', using rndMethod instead of 'inc' + - added zonal verbosity + - added 'rndDone!' flag + - rndMethod defaults to "inc" --]] rndFlags.rndGen = {} @@ -147,12 +151,15 @@ function rndFlags.createRNDWithZone(theZone) theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false) if not theZone.onStart and not theZone.triggerFlag then - theZone.onStart = true + -- theZone.onStart = true + if true or theZone.verbose or rndFlags.verbose then + trigger.action.outText("+++RND - WARNING: no triggers and no onStart, RND in <" .. theZone.name .. "> can't be triggered.", 30) + end end - theZone.rndMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "on") + theZone.rndMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") if cfxZones.hasProperty(theZone, "rndMethod") then - theZone.rndMethod = cfxZones.getStringFromZoneProperty(theZone, "rndMethod", "on") + theZone.rndMethod = cfxZones.getStringFromZoneProperty(theZone, "rndMethod", "inc") end theZone.reshuffle = cfxZones.getBoolFromZoneProperty(theZone, "reshuffle", false) @@ -161,10 +168,17 @@ function rndFlags.createRNDWithZone(theZone) theZone.flagStore = dcsCommon.copyArray(theFlags) end - -- done flag + -- done flag OLD, to be deprecated if cfxZones.hasProperty(theZone, "done+1") then - theZone.doneFlag = cfxZones.getStringFromZoneProperty(theZone, "done+1", "none") + theZone.doneFlag = cfxZones.getStringFromZoneProperty(theZone, "done+1", "") + + -- now NEW replacements + elseif cfxZones.hasProperty(theZone, "done!") then + theZone.doneFlag = cfxZones.getStringFromZoneProperty(theZone, "done!", "") + elseif cfxZones.hasProperty(theZone, "rndDone!") then + theZone.doneFlag = cfxZones.getStringFromZoneProperty(theZone, "rndDone!", "") end + end function rndFlags.reshuffle(theZone) @@ -197,7 +211,7 @@ function rndFlags.fire(theZone) end if theZone.doneFlag then - cfxZones.pollFlag(theZone.doneFlag, "inc", theZone) + cfxZones.pollFlag(theZone.doneFlag, theZone.rndMethod, theZone) end return @@ -262,7 +276,7 @@ end function rndFlags.startCycle() for idx, theZone in pairs(rndFlags.rndGen) do if theZone.onStart then - if rndFlags.verbose then + if rndFlags.verbose or theZone.verbose then trigger.action.outText("+++RND: starting " .. theZone.name, 30) end rndFlags.fire(theZone) diff --git a/modules/baseCaptured.lua b/modules/baseCaptured.lua index 0562aba..44174ed 100644 --- a/modules/baseCaptured.lua +++ b/modules/baseCaptured.lua @@ -1,16 +1,20 @@ baseCaptured={} -baseCaptured.version = "1.0.0" +baseCaptured.version = "1.0.1" baseCaptured.verbose = false +baseCaptured.ups = 1 baseCaptured.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course } +baseCaptured.handleContested = true -- --[[-- baseCaptured - Detects when the assigned base has been captured, idea and first implementation by cloose Version History 1.0.0 - Initial version based on cloose's code + 1.0.1 - contested! flag + - update and handleContested --]]-- @@ -50,6 +54,10 @@ function baseCaptured.createZone(theZone) theZone.redCap = cfxZones.getStringFromZoneProperty(theZone, "red!", "*none") end + if cfxZones.hasProperty(theZone, "contested!") then + theZone.contested = cfxZones.getStringFromZoneProperty(theZone, "contested!", "*none") + end + if cfxZones.hasProperty(theZone, "baseOwner") then theZone.baseOwner = cfxZones.getStringFromZoneProperty(theZone, "baseOwner", "*none") cfxZones.setFlagValueMult(theZone.baseOwner, theZone.currentOwner, theZone) @@ -79,7 +87,10 @@ function baseCaptured.triggerZone(theZone) cfxZones.pollFlag(theZone.blueCap, theZone.capturedMethod, theZone) end else - -- possibly a new side? Neutral doesn't cap + -- contested + if theZone.contested then + cfxZones.pollFlag(theZone.contested, theZone.capturedMethod, theZone) + end end if baseCaptured.verbose or theZone.verbose then @@ -118,6 +129,31 @@ function baseCaptured:onEvent(event) end end +function baseCaptured.update() + -- call me in a second to poll triggers + timer.scheduleFunction(baseCaptured.update, {}, timer.getTime() + 1/baseCaptured.ups) + + -- look for contested event - it's not covered with capture event! + for idx, aZone in pairs(baseCaptured.zones) do + local newOwner = aZone.theBase:getCoalition() + + if (newOwner ~= aZone.currentOwner) and (newOwner == 3) then + if aZone.contested then + cfxZones.pollFlag(aZone.contested, aZone.capturedMethod, aZone) + end + + aZone.currentOwner = newOwner + if aZone.verbose or baseCaptured.verbose then + trigger.action.outText("+++bCap: zone <" .. aZone.name .. "> has become contested!", 30) + end + if aZone.baseOwner then + cfxZones.setFlagValueMult(aZone.baseOwner, newOwner, aZone) + end + end + end + +end + function baseCaptured.readConfigZone() -- search for configuration zone local theZone = cfxZones.getZoneByName("baseCapturedConfig") @@ -127,6 +163,8 @@ function baseCaptured.readConfigZone() baseCaptured.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + baseCaptured.handleContested = cfxZones.getBoolFromZoneProperty(theZone, "handleContested", true) + if baseCaptured.verbose then trigger.action.outText("+++bCap: read configuration from zone", 30) end @@ -155,6 +193,11 @@ function baseCaptured.start() -- listen for events world.addEventHandler(baseCaptured) + -- start update to look for contested + if baseCaptured.handleContested then + baseCaptured.update() + end + trigger.action.outText("baseCaptured v" .. baseCaptured.version .. " started.", 30) return true end diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index dfca4a5..63e91ab 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -1,11 +1,13 @@ +cfxZones = {} +cfxZones.version = "2.8.3" + -- cf/x zone management module -- reads dcs zones and makes them accessible and mutable -- by scripting. -- -- Copyright (c) 2021, 2022 by Christian Franz and cf/x AG -- -cfxZones = {} -cfxZones.version = "2.8.1" + --[[-- VERSION HISTORY - 2.2.4 - getCoalitionFromZoneProperty - getStringFromZoneProperty @@ -82,7 +84,11 @@ cfxZones.version = "2.8.1" - setFlagValue QoL for - 2.8.0 - new allGroupNamesInZone() - 2.8.1 - new zonesLinkedToUnit() - +- 2.8.2 - flagArrayFromString trims elements before range check +- 2.8.3 - new verifyMethod() + - changed extractPropertyFromDCS() to also match attributes with blanks like "the Attr" to "theAttr" + - new expandFlagName() + --]]-- cfxZones.verbose = false cfxZones.caseSensitiveProperties = false -- set to true to make property names case sensitive @@ -1222,6 +1228,32 @@ function cfxZones.pollFlag(theFlag, method, theZone) end +function cfxZones.expandFlagName(theFlag, theZone) + if not theFlag then return "!NIL" end + local zoneName = "" + if theZone then + zoneName = theZone.name -- for flag wildcards + end + + if type(theFlag) == "number" then + -- straight number, return + return theFlag + end + + -- we assume it's a string now + theFlag = dcsCommon.trim(theFlag) -- clear leading/trailing spaces + local nFlag = tonumber(theFlag) + if nFlag then -- a number, legal + return theFlag + end + + -- now do wildcard processing. we have alphanumeric + if dcsCommon.stringStartsWith(theFlag, "*") then + theFlag = zoneName .. theFlag + end + return theFlag +end + function cfxZones.setFlagValueMult(theFlag, theValue, theZone) local allFlags = {} if dcsCommon.containsString(theFlag, ",") then @@ -1297,7 +1329,7 @@ function cfxZones.getFlagValue(theFlag, theZone) -- some QoL: detect "" if dcsCommon.containsString(theFlag, "") then - trigger.action.outText("+++Zone: warning - getFlag has '' flag name in zone <" .. zoneName .. ">", 30) + trigger.action.outText("+++Zone: warning - getFlag has '' flag name in zone <" .. zoneName .. ">", 30) -- break here end -- now do wildcard processing. we have alphanumeric @@ -1316,6 +1348,68 @@ function cfxZones.isMEFlag(inFlag) -- return dcsCommon.stringIsPositiveNumber(inFlag) end +function cfxZones.verifyMethod(theMethod, theZone) + local lMethod = string.lower(theMethod) + if lMethod == "#" or lMethod == "change" then + return true + end + + if lMethod == "0" or lMethod == "no" or lMethod == "false" + or lMethod == "off" then + return true + end + + if lMethod == "1" or lMethod == "yes" or lMethod == "true" + or lMethod == "on" then + return true + end + + if lMethod == "inc" or lMethod == "+1" then + return true + end + + if lMethod == "dec" or lMethod == "-1" then + return true + end + + if lMethod == "lohi" or lMethod == "pulse" then + return true + end + + if lMethod == "hilo" then + return true + end + + -- number constraints + -- or flag constraints -- ONLY RETURN TRUE IF CHANGE AND CONSTRAINT MET + local op = string.sub(theMethod, 1, 1) + local remainder = string.sub(theMethod, 2) + remainder = dcsCommon.trim(remainder) -- remove all leading and trailing spaces +-- local rNum = tonumber(remainder) + + if true then + -- we have a comparison = ">", "=", "<" followed by a number + -- THEY TRIGGER EACH TIME lastVal <> currVal AND condition IS MET + if op == "=" then + return true + end + + if op == "#" or op == "~" then + return true + end + + if op == "<" then + return true + end + + if op == ">" then + return true + end + end + + return false +end + -- method-based flag testing function cfxZones.evalFlagMethodImmediate(currVal, theMethod, theZone) -- immediate eval - does not look at last val. @@ -1569,7 +1663,8 @@ function cfxZones.flagArrayFromString(inString) local rawElements = dcsCommon.splitString(inString, ",") -- go over all elements for idx, anElement in pairs(rawElements) do - if dcsCommon.stringStartsWithDigit(anElement) and dcsCommon.containsString(anElement, "-") then + anElement = dcsCommon.trim(anElement) + if dcsCommon.stringStartsWithDigit(anElement) and dcsCommon.containsString(anElement, "-") then -- interpret this as a range local theRange = dcsCommon.splitString(anElement, "-") local lowerBound = theRange[1] @@ -1649,6 +1744,12 @@ function cfxZones.extractPropertyFromDCS(theKey, theProperties) if existingKey == theKey then return theP.value end + + -- now check after removing all blanks + existingKey = dcsCommon.removeBlanks(existingKey) + if existingKey == theKey then + return theP.value + end end return nil end diff --git a/modules/changer.lua b/modules/changer.lua index e56451b..c2852c0 100644 --- a/modules/changer.lua +++ b/modules/changer.lua @@ -1,5 +1,5 @@ changer = {} -changer.version = "1.0.1" +changer.version = "1.0.2" changer.verbose = false changer.ups = 1 changer.requiredLibs = { @@ -11,6 +11,7 @@ changer.changers = {} Version History 1.0.0 - Initial version 1.0.1 - Better guards in config to avoid Zone getter warning + 1.0.2 - on/off: verbosity Transmogrify an incoming signal to an output signal - not @@ -210,6 +211,10 @@ function changer.update() if aZone.changerOnOff then if cfxZones.getFlagValue(aZone.changerOnOff, aZone) > 0 then changer.process(aZone) + else + if changer.verbose or aZone.verbose then + trigger.action.outText("+++chgr: " .. aZone.name .. " gate closed.", 30) + end end else changer.process(aZone) diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index 83d2fea..538ab7e 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.6.5" +dcsCommon.version = "2.6.6" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -77,6 +77,11 @@ dcsCommon.version = "2.6.5" 2.6.5 - new bearing2compass() - new bearingdegrees2compass() - new latLon2Text() - based on mist + 2.6.6 - new nowString() + - new str2num() + - new stringRemainsStartingWith() + - new stripLF() + - new removeBlanks() --]]-- @@ -1746,6 +1751,14 @@ dcsCommon.version = "2.6.5" return trimmedArray end + function dcsCommon.stripLF(theString) + return theString:gsub("[\r\n]", "") + end + + function dcsCommon.removeBlanks(theString) + return theString:gsub("%s", "") + end + function dcsCommon.stringIsPositiveNumber(theString) -- only full integer positive numbers supported if not theString then return false end @@ -1818,8 +1831,8 @@ dcsCommon.version = "2.6.5" if not p then return "" end local t = "[x=" if p.x then t = t .. p.x .. ", " else t = t .. ", " end - if p.y then t = t .. p.y .. ", " else t = t .. ", " end - if p.z then t = t .. p.z .. "]" else t = t .. "]" end + if p.y then t = t .. "y=" .. p.y .. ", " else t = t .. "y=, " end + if p.z then t = t .. "z=" .. p.z .. "]" else t = t .. "z=]" end return t end @@ -2047,6 +2060,33 @@ dcsCommon.version = "2.6.5" return msg end + function dcsCommon.nowString() + local absSecs = timer.getAbsTime()-- + env.mission.start_time + while absSecs > 86400 do + absSecs = absSecs - 86400 -- subtract out all days + end + return dcsCommon.processHMS("<:h>:<:m>:<:s>", absSecs) + end + + function dcsCommon.str2num(inVal, default) + if not default then default = 0 end + if not inVal then return default end + if type(inVal) == "number" then return inVal end + local num = nil + if type(inVal) == "string" then num = tonumber(inVal) end + if not num then return default end + return num + end + + function dcsCommon.stringRemainsStartingWith(theString, startingWith) + -- find the first position where startingWith starts + local pos = theString:find(startingWith) + if not pos then return theString end + -- now return the entire remainder of the string from pos + local nums = theString:len() - pos + 1 + return theString:sub(-nums) + end + -- -- -- V E C T O R M A T H diff --git a/modules/guardianAngel.lua b/modules/guardianAngel.lua index ce54d9c..8d62dc4 100644 --- a/modules/guardianAngel.lua +++ b/modules/guardianAngel.lua @@ -1,6 +1,7 @@ guardianAngel = {} -guardianAngel.version = "3.0.1" +guardianAngel.version = "3.0.2" guardianAngel.ups = 10 +guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name guardianAngel.launchWarning = true -- detect launches and warn pilot guardianAngel.intervention = true -- remove missiles just before hitting guardianAngel.explosion = -1 -- small poof when missile explodes. -1 = off. @@ -56,6 +57,7 @@ guardianAngel.requiredLibs = { 3.0.1 - corrected error on collateral (missing delay) - Supporst cloned units - removed legacy code + 3.0.2 - added guardianAngel.name for those who use local flags on activate This script detects missiles launched against protected aircraft an diff --git a/modules/impostors.lua b/modules/impostors.lua index 5ac854b..0a81d90 100644 --- a/modules/impostors.lua +++ b/modules/impostors.lua @@ -1,6 +1,6 @@ impostors={} -impostors.version = "1.0.0" +impostors.version = "1.0.1" impostors.verbose = false impostors.ups = 1 impostors.requiredLibs = { @@ -15,6 +15,11 @@ impostors.callbacks = {} impostors.uniqueCounter = 8200000 -- clones start at 9200000 --[[-- + Version History + 1.0.0 - initial version + 1.0.1 - added some verbosity + + LIMITATIONS: must be on ground (or would be very silly does not work with any units deployed on ships @@ -379,6 +384,12 @@ function impostors.delayedSpawn(args) local newGroup = coalition.addGroup(ctry, cat, rawData) impostors.relinkZonesForGroup(relinkZones, newGroup) + if newGroup then +-- trigger.action.outText("+++ipst: SUCCESS!!! Spawned group <" .. newGroup:getName() .. "> for impostor <" .. rawData.name .. ">", 30) + else + trigger.action.outText("+++ipst: failed to spawn group for impostor <" .. rawData.name .. ">", 30) + end + if theZone.trackWith and groupTracker.addGroupToTrackerNamed then -- add these groups to the group tracker if theZone.verbose or impostors.verbose then diff --git a/modules/pulseFlags.lua b/modules/pulseFlags.lua index 29dbc6d..9774788 100644 --- a/modules/pulseFlags.lua +++ b/modules/pulseFlags.lua @@ -1,5 +1,5 @@ pulseFlags = {} -pulseFlags.version = "1.2.1" +pulseFlags.version = "1.2.3" pulseFlags.verbose = false pulseFlags.requiredLibs = { "dcsCommon", -- always @@ -32,6 +32,9 @@ pulseFlags.requiredLibs = { - 1.2.1 pulseInterval synonym for time pulses now supports range zone-local verbosity + - 1.2.2 outputMethod synonym + - 1.2.3 deprecated paused/pulsePaused + returned onStart, defaulting to true --]]-- @@ -111,17 +114,27 @@ function pulseFlags.createPulseWithZone(theZone) theZone.lastPauseValue = cfxZones.getFlagValue(theZone.pausePulseFlag, theZone)-- trigger.misc.getUserFlag(theZone.pausePulseFlag) -- save last value end - theZone.pulsePaused = cfxZones.getBoolFromZoneProperty(theZone, "paused", false) + -- harmonizing on onStart, and converting to old pulsePaused + local onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", true) + theZone.pulsePaused = not (onStart) + -- old code, to be deprecated + if cfxZones.hasProperty(theZone, "paused") then + theZone.pulsePaused = cfxZones.getBoolFromZoneProperty(theZone, "paused", false) - if cfxZones.hasProperty(theZone, "pulseStopped") then + elseif cfxZones.hasProperty(theZone, "pulseStopped") then theZone.pulsePaused = cfxZones.getBoolFromZoneProperty(theZone, "pulseStopped", false) end + --]]-- theZone.pulseMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "flip") if cfxZones.hasProperty(theZone, "pulseMethod") then theZone.pulseMethod = cfxZones.getStringFromZoneProperty(theZone, "pulseMethod", "flip") end + + if cfxZones.hasProperty(theZone, "outputMethod") then + theZone.pulseMethod = cfxZones.getStringFromZoneProperty(theZone, "outputMethod", "flip") + end -- done flag if cfxZones.hasProperty(theZone, "done+1") then theZone.pulseDoneFlag = cfxZones.getStringFromZoneProperty(theZone, "done+1", "*none") diff --git a/modules/theDebugger.lua b/modules/theDebugger.lua new file mode 100644 index 0000000..7e5d497 --- /dev/null +++ b/modules/theDebugger.lua @@ -0,0 +1,1175 @@ +-- theDebugger +debugger = {} +debugger.version = "1.0.1" +debugDemon = {} +debugDemon.version = "1.0.0" + +debugger.verbose = false +debugger.ups = 4 -- every 0.25 second +debugger.name = "DML Debugger" -- for compliance with cfxZones + +--[[-- + 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.0.2 - contested! flag +--]]-- + +debugger.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +debugger.debugZones = {} + +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 + trigger.action.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 + trigger.action.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 + trigger.action.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) + trigger.action.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 + trigger.action.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 + trigger.action.outText(" state of flag <" .. aFlag .. ">: <" .. theZone.valueArray[aFlag] .. ">", 30) + end + theZone.valueArray[aFlag] = fVal + end +end + +function debugger.showState() + trigger.action.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 + trigger.action.outText(" state of observer <" .. theZone.name .. "> looking for :", 30) + debugger.showObserverState(theZone) + else + if theZone.verbose or debugger.verbose then + trigger.action.outText(" (empty observer <" .. theZone.name .. ">)", 30) + end + end + end + trigger.action.outText("---debug: end of state --- ", 30) +end + +function debugger.doActivate() + debugger.active = true + if debugger.verbose or true then + trigger.action.outText("+++ DM Debugger is now active", 30) + end +end + +function debugger.doDeactivate() + debugger.active = false + if debugger.verbose or true then + trigger.action.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 + trigger.action.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 + trigger.action.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 + trigger.action.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 + trigger.action.outText("+++debugger loaded and active", 30) + else + trigger.action.outText("+++ debugger: standing by for activation", 30) + end + end + + -- start update + debugger.update() + + trigger.action.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 on and off. globally, not per zone + +--]]-- + + +-- +-- 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 + +--]]-- + +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 + trigger.action.outText("***error: unknown command <".. cmd .. ">", 30) + return false + end + + return true +end + +-- +-- Helpers +-- +--[[-- +function debugDemon.isObserving(flagName) + -- for now, we simply scan out own + for idx, aName in pairs(debugDemon.observer.flagArray) do + if aName == flagName then return true end + end + return false +end +--]]-- + +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) +trigger.action.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 .. "start -- starts debugger" .. + "\n " .. debugDemon.markOfDemon .. "stop -- stop debugger" .. + + "\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 + trigger.action.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 + trigger.action.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 + trigger.action.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 + trigger.action.outText("*** new: illegal trigger condition <" .. condition .. "> for observer <" .. observerName .. ">", 30) + return false + end + theZone.debugInputMethod = condition + end + + debugger.addDebugger(theZone) + trigger.action.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 + trigger.action.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 + trigger.action.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 + trigger.action.outText("*** update: illegal trigger condition <" .. condition .. "> for observer <" .. observerName .. ">", 30) + return false + end + theZone.debugInputMethod = condition + end + + trigger.action.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 + trigger.action.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 + trigger.action.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 + trigger.action.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) + + trigger.action.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 + trigger.action.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 + trigger.action.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) + trigger.action.outText("*** observe: no observer <" .. aName .. "> exists", 30) + return false -- allows correction + end + else -- not with as arg 2 + if #args > 1 then + trigger.action.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 + trigger.action.outText("*** observe: already observing " .. flagName .. " with <" .. withTracker.name .. ">" , 30) + return true + end + + -- we add flag to tracker and init value + debugger.addFlagToObserver(flagName, withTracker) + trigger.action.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 + trigger.action.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) + trigger.action.outText("[" .. dcsCommon.nowString() .. "] flag <" .. theName .. "> : value <".. fVal .. ">", 30) + return true + end + + -- if we get here, we want to show an entire observer + trigger.action.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 + trigger.action.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 + trigger.action.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 + trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: new snapshot created, " .. sz .. " flags.", 30) + + return true +end + +function debugDemon.processCompareCommand(args, event) + trigger.action.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 + trigger.action.outText(mark .. "<" .. flagName .. "> snap = <" .. val .. ">, now = <" .. cVal .. "> " .. mark, 30) + end + trigger.action.outText("*** END", 30) + return true +end + +function debugDemon.processNoteCommand(args, event) + local n = event.remainder + trigger.action.outText("*** [" .. dcsCommon.nowString() .. "]: " .. n, 30) + return true +end + +function debugDemon.processSetCommand(args, event) + -- syntax set + local theName = args[1] + if not theName then + trigger.action.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 + trigger.action.outText("*** set: missing or illegal value for flag <" .. theName .. ">.", 30) + return false -- allows correction + end + + -- we set directly, no cfxZones procing + trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: set flag <" .. theName .. "> to <" .. theVal .. ">", 30) + trigger.action.setUserFlag(theName, theVal) + return true +end + +function debugDemon.processIncCommand(args, event) + -- syntax inc + local theName = args[1] + if not theName then + trigger.action.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 + trigger.action.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 + trigger.action.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 + trigger.action.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 + trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] listing observers whose name contains <" .. prefix .. ">:", 30) + else + trigger.action.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 + trigger.action.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 + trigger.action.outText("*** who: missing flag name.", 30) + return false -- allows correction + end + + local observers = debugger.isObserving(flagName) + + if not observers or #observers < 1 then + trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] flag <" .. flagName .. "> is currently not observed", 30) + return false + end + + trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] flag <" .. flagName .. "> is currently observed by", 30) + for idx, theZone in pairs(observers) do + trigger.action.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 + trigger.action.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 + trigger.action.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 + trigger.action.outText("*** forget: no observer named <" .. aName .. ">", 30) + return false + end + else -- not with as arg 2 + if #args > 1 then + trigger.action.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 + trigger.action.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) + trigger.action.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 + trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debug: reset complete.", 30) + return true -- allows correction + end + + local withTracker = nil + --if args[2] == "with" then + local aName = args[1] + aName = event.remainder -- dcsCommon.stringRemainsStartingWith(event.text, aName) + withTracker = debugger.getDebuggerByName(aName) + if not withTracker then + trigger.action.outText("*** reset: no observer <" .. aName .. ">", 30) + return false + end + + debugger.resetObserver(withTracker) + + trigger.action.outText("*** [" .. dcsCommon.nowString() .. "] debugger:reset observer <" .. withTracker.name .. ">", 30) + return true +end +-- +-- init and start +-- + +function debugDemon.readConfigZone() + local theZone = cfxZones.getZoneByName("debugDemonConfig") + if not theZone then + if debugDemon.verbose then + trigger.action.outText("+++debug: 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 + trigger.action.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("?", debugDemon.processHelpCommand) + debugDemon.addCommndProcessor("help", debugDemon.processHelpCommand) + + + 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) + + trigger.action.outText("interactive debugDemon v" .. debugDemon.version .. " started" .. "\n enter " .. debugDemon.markOfDemon .. "? in a map mark for help", 30) +end + +if debugDemon.init() then + debugDemon.start() +else + trigger.action.outText("*** interactive flag debugger failed to initialize.", 30) + debugDemon = {} +end + +--[[-- + - track units/groups: health changes + - track players: unit change +--]]-- diff --git a/modules/unitZone.lua b/modules/unitZone.lua index 3de7b2e..8685c1f 100644 --- a/modules/unitZone.lua +++ b/modules/unitZone.lua @@ -1,5 +1,5 @@ unitZone={} -unitZone.version = "1.2.2" +unitZone.version = "1.2.3" unitZone.verbose = false unitZone.ups = 1 unitZone.requiredLibs = { @@ -14,6 +14,8 @@ unitZone.requiredLibs = { 1.2.0 - uzOn?, uzOff?, triggerMethod 1.2.1 - uzDirect 1.2.2 - uzDirectInv + 1.2.3 - better guards for enterZone!, exitZone!, chsngeZone! + - better guards for uzOn? and uzOff? --]]-- @@ -86,9 +88,16 @@ function unitZone.createUnitZone(theZone) theZone.uzMethod = cfxZones.getStringFromZoneProperty(theZone, "uzMethod", "inc") end - theZone.enterZone = cfxZones.getStringFromZoneProperty(theZone, "enterZone!", "") - theZone.exitZone = cfxZones.getStringFromZoneProperty(theZone, "exitZone!", "") - theZone.changeZone = cfxZones.getStringFromZoneProperty(theZone, "changeZone!", "") + if cfxZones.hasProperty(theZone, "enterZone!") then + theZone.enterZone = cfxZones.getStringFromZoneProperty(theZone, "enterZone!", "*") + end + if cfxZones.hasProperty(theZone, "exitZone!") then + theZone.exitZone = cfxZones.getStringFromZoneProperty(theZone, "exitZone!", "*") + end + + if cfxZones.hasProperty(theZone, "changeZone!") then + theZone.changeZone = cfxZones.getStringFromZoneProperty(theZone, "changeZone!", "*") + end if cfxZones.hasProperty(theZone, "filterFor") then local filterString = cfxZones.getStringFromZoneProperty(theZone, "filterFor", "1") -- ground @@ -108,11 +117,15 @@ function unitZone.createUnitZone(theZone) -- on/off flags theZone.uzPaused = false -- we are turned on - theZone.triggerOnFlag = cfxZones.getStringFromZoneProperty(theZone, "uzOn?", "*") - theZone.lastTriggerOnValue = cfxZones.getFlagValue(theZone.triggerOnFlag, theZone) - - theZone.triggerOffFlag = cfxZones.getStringFromZoneProperty(theZone, "uzOff?", "*") - theZone.lastTriggerOffValue = cfxZones.getFlagValue(theZone.triggerOffFlag, theZone) + if cfxZones.hasProperty(theZone, "uzOn?") then + theZone.triggerOnFlag = cfxZones.getStringFromZoneProperty(theZone, "uzOn?", "*") + theZone.lastTriggerOnValue = cfxZones.getFlagValue(theZone.triggerOnFlag, theZone) + end + + if cfxZones.hasProperty(theZone, "uzOff?") then + theZone.triggerOffFlag = cfxZones.getStringFromZoneProperty(theZone, "uzOff?", "*") + theZone.lastTriggerOffValue = cfxZones.getFlagValue(theZone.triggerOffFlag, theZone) + end theZone.uzTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") if cfxZones.hasProperty(theZone, "uzTriggerMethod") then @@ -227,17 +240,23 @@ end -- function unitZone.bangState(theZone, newState) - cfxZones.pollFlag(theZone.changeZone, theZone.uzMethod, theZone) + if theZone.changeZone then + cfxZones.pollFlag(theZone.changeZone, theZone.uzMethod, theZone) + end if newState then - cfxZones.pollFlag(theZone.enterZone, theZone.uzMethod, theZone) - if unitZone.verbose then - trigger.action.outText("+++uZone: banging enter! with <" .. theZone.uzMethod .. "> on <" .. theZone.enterZone .. "> for " .. theZone.name, 30) - end - else - cfxZones.pollFlag(theZone.exitZone, theZone.uzMethod, theZone) - if unitZone.verbose then - trigger.action.outText("+++uZone: banging exit! with <" .. theZone.uzMethod .. "> on <" .. theZone.exitZone .. "> for " .. theZone.name, 30) + if theZone.enterZone then + cfxZones.pollFlag(theZone.enterZone, theZone.uzMethod, theZone) + if unitZone.verbose then + trigger.action.outText("+++uZone: banging enter! with <" .. theZone.uzMethod .. "> on <" .. theZone.enterZone .. "> for " .. theZone.name, 30) + end end + else + if theZone.exitZone then + cfxZones.pollFlag(theZone.exitZone, theZone.uzMethod, theZone) + if unitZone.verbose then + trigger.action.outText("+++uZone: banging exit! with <" .. theZone.uzMethod .. "> on <" .. theZone.exitZone .. "> for " .. theZone.name, 30) + end + end end end @@ -247,14 +266,14 @@ function unitZone.update() for idx, aZone in pairs(unitZone.unitZones) do -- check if we need to pause/unpause - if cfxZones.testZoneFlag(aZone, aZone.triggerOnFlag, aZone.uzTriggerMethod, "lastTriggerOnValue") then + if aZone.triggerOnFlag and cfxZones.testZoneFlag(aZone, aZone.triggerOnFlag, aZone.uzTriggerMethod, "lastTriggerOnValue") then if unitZone.verbose or aZone.verbose then trigger.action.outText("+++uZone: turning " .. aZone.name .. " on", 30) end aZone.uzPaused = false end - if cfxZones.testZoneFlag(aZone, aZone.triggerOffFlag, aZone.uzTriggerMethod, "lastTriggerOffValue") then + if aZone.triggerOffFlag and cfxZones.testZoneFlag(aZone, aZone.triggerOffFlag, aZone.uzTriggerMethod, "lastTriggerOffValue") then if unitZone.verbose or aZone.verbose then trigger.action.outText("+++uZone: turning " .. aZone.name .. " OFF", 30) end diff --git a/tutorial & demo missions/demo - Bug Hunt.miz b/tutorial & demo missions/demo - Bug Hunt.miz new file mode 100644 index 0000000..aa2c299 Binary files /dev/null and b/tutorial & demo missions/demo - Bug Hunt.miz differ