diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 3bb325c..e637003 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 a822e9a..285ad2e 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index 437c8e5..e3cae67 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -1,5 +1,5 @@ cfxZones = {} -cfxZones.version = "4.0.3" +cfxZones.version = "4.0.5" -- cf/x zone management module -- reads dcs zones and makes them accessible and mutable @@ -153,6 +153,10 @@ cfxZones.version = "4.0.3" - 4.0.2 - removed verbosity from declutterZone (both versions) - 4.0.3 - new processDynamicVZU() - wildcard uses processDynamicVZU +- 4.0.4 - setFlagValue now supports multiple flags (OOP and classic) + - doSetFlagValue optimizations +- 4.0.5 - dynamicAB wildcard + - processDynamicValueVU --]]-- @@ -1781,6 +1785,14 @@ function cfxZones.expandFlagName(theFlag, theZone) return theFlag end +function dmlZone:setFlagValue(theFlag, theValue) + cfxZones.setFlagValueMult(theFlag, theValue, self) +end + +function cfxZones.setFlagValue(theFlag, theValue, theZone) + cfxZones.setFlagValueMult(theFlag, theValue, theZone) +end + function cfxZones.setFlagValueMult(theFlag, theValue, theZone) local allFlags = {} if dcsCommon.containsString(theFlag, ",") then @@ -1796,11 +1808,11 @@ function cfxZones.setFlagValueMult(theFlag, theValue, theZone) aFlag = dcsCommon.trim(aFlag) -- note: mey require range preprocessing, but that's not -- a priority - cfxZones.setFlagValue(aFlag, theValue, theZone) + cfxZones.doSetFlagValue(aFlag, theValue, theZone) end end -function cfxZones.setFlagValue(theFlag, theValue, theZone) +function cfxZones.doSetFlagValue(theFlag, theValue, theZone) local zoneName = "" if not theZone then trigger.action.outText("+++Zne: no zone on setFlagValue", 30) -- mod me for detector @@ -1809,19 +1821,13 @@ function cfxZones.setFlagValue(theFlag, theValue, theZone) end if type(theFlag) == "number" then - -- straight set, ME flag + -- straight set, oldschool ME flag trigger.action.setUserFlag(theFlag, theValue) return end -- we assume it's a string now - theFlag = dcsCommon.trim(theFlag) -- clear leading/trailing spaces - local nFlag = tonumber(theFlag) - if nFlag then - trigger.action.setUserFlag(theFlag, theValue) - return - end - + theFlag = dcsCommon.trim(theFlag) -- clear leading/trailing spaces -- some QoL: detect "" if dcsCommon.containsString(theFlag, "") then trigger.action.outText("+++Zone: warning - setFlag has '' flag name in zone <" .. zoneName .. ">", 30) -- if error, intended break @@ -1834,9 +1840,6 @@ function cfxZones.setFlagValue(theFlag, theValue, theZone) trigger.action.setUserFlag(theFlag, theValue) end -function dmlZone:setFlagValue(theFlag, theValue) - cfxZones.setFlagValue(theFlag, theValue, self) -end function cfxZones.getFlagValue(theFlag, theZone) @@ -3110,16 +3113,16 @@ local locales = {"coa",} local outMsg = inMsg local uHead = 0 for idx, aLocale in pairs(locales) do - local pattern = "<" .. aLocale .. ":%s*[%s%w%*%d%.%-_]+>" + local pattern = "<" .. aLocale .. ":%s*[%s%w%*%d%.%-_]+>" -- e.g. " repeat -- iterate all patterns one by one local startLoc, endLoc = string.find(outMsg, pattern) if startLoc then local theValParam = string.sub(outMsg, startLoc, endLoc) -- strip lead and trailer - local param = string.gsub(theValParam, "<" .. aLocale .. ":%s*", "") - param = string.gsub(param, ">","") + local param = string.gsub(theValParam, "<" .. aLocale .. ":%s*", "") -- remove "","") -- remove trailing ">" -- find zone or unit - param = dcsCommon.trim(param) + param = dcsCommon.trim(param) -- param = "flag Name" local tZone = cfxZones.getZoneByName(param) local tUnit = Unit.getByName(param) @@ -3127,7 +3130,7 @@ local locales = {"coa",} if aLocale == "coa" then coa = trigger.misc.getUserFlag(param) if tZone then coa = tZone.owner end - if zUnit then coa = tUnit:getCoalition() end + if tUnit and Unit:isExist(tUnit) then coa = tUnit:getCoalition() end locString = dcsCommon.coalition2Text(coa) end @@ -3138,6 +3141,83 @@ local locales = {"coa",} return outMsg end +-- process two-value vars that can be flag or unit and return interpreted value +-- i.e. +function cfxZones.processDynamicValueVU(inMsg) +local locales = {"yes", "true", "alive", "in"} + local outMsg = inMsg + local uHead = 0 + for idx, aLocale in pairs(locales) do + local pattern = "<" .. aLocale .. ":%s*[%s%w%*%d%.%-_]+>" -- e.g. " + repeat -- iterate all patterns one by one + local startLoc, endLoc = string.find(outMsg, pattern) + if startLoc then + local theValParam = string.sub(outMsg, startLoc, endLoc) + -- strip lead and trailer + local param = string.gsub(theValParam, "<" .. aLocale .. ":%s*", "") -- remove "","") -- remove trailing ">" + -- find zone or unit + param = dcsCommon.trim(param) -- param = "flagOrUnitName" + local tUnit = Unit.getByName(param) + local yesNo = trigger.misc.getUserFlag(param) ~= 0 + if tUnit then yesNo = Unit.isExist(tUnit) end + local locString = "err" + if aLocale == "yes" then + if yesNo then locString = "yes" else locString = "no" end + elseif aLocale == "true" then + if yesNo then locString = "true" else locString = "false" end + elseif aLocale == "alive" then + if yesNo then locString = "alive" else locString = "dead" end + elseif aLocale == "in" then + if yesNo then locString = "in" else locString = "out" end + end + + outMsg = string.gsub(outMsg, pattern, locString, 1) -- only one sub! + end -- if startloc + until not startLoc + end -- for all locales + return outMsg +end + +function cfxZones.processDynamicAB(inMsg, locale) + local outMsg = inMsg + if not locale then locale = "A/B" end + + -- + local replacerValPattern = "<".. locale .. ":%s*[%s%w%*%d%.%-_]+" .. "%[[%s%w]+|[%s%w]+%]"..">" + repeat + local startLoc, endLoc = string.find(outMsg, replacerValPattern) + if startLoc then + local rp = string.sub(outMsg, startLoc, endLoc) + -- get val/unit name + local valA, valB = string.find(rp, ":%s*[%s%w%*%d%.%-_]+%[") + local val = string.sub(rp, valA+1, valB-1) + val = dcsCommon.trim(val) + -- get left and right + local leftA, leftB = string.find(rp, "%[[%s%w]+|" ) -- from "[" to "|" + local rightA, rightB = string.find(rp, "|[%s%w]+%]") -- from "|" to "]" + left = string.sub(rp, leftA+1, leftB-1) + left = dcsCommon.trim(left) + right = string.sub(rp, rightA+1, rightB-1) + right = dcsCommon.trim(right) +-- trigger.action.outText("+++replacer pattern <" .. rp .. "> found, val = <" .. val .. ">, A = <" .. left .. ">, B = <" .. right .. ">", 30) + local yesno = false + -- see if unit exists + local theUnit = Unit.getByName(val) + if theUnit then + yesno = Unit:isExist(theUnit) + else + yesno = trigger.misc.getUserFlag(val) ~= 0 + end + + local locString = left + if yesno then locString = right end + outMsg = string.gsub(outMsg, replacerValPattern, locString, 1) + end + until not startLoc + return outMsg +end + function cfxZones.rspMapper360(directionInDegrees, numResponses) -- maps responses around a clock. Clock has 12 'responses' (12, 1, .., 11), -- with the first (12) also mapping to the last half arc @@ -3187,6 +3267,8 @@ function cfxZones.processStringWildcards(inMsg, theZone, timeFormat, imperialUni theMsg = cfxZones.processDynamicLoc(theMsg, imperialUnits, responses) -- process values that can be derived from flag (default), zone or unit theMsg = cfxZones.processDynamicVZU(theMsg) + theMsg = cfxZones.processDynamicAB(theMsg) + theMsg = cfxZones.processDynamicValueVU(theMsg) return theMsg end diff --git a/modules/duel.lua b/modules/duel.lua index 5f43d83..736c729 100644 --- a/modules/duel.lua +++ b/modules/duel.lua @@ -1,5 +1,5 @@ duel = {} -duel.version = "1.0.2" +duel.version = "1.1.0" duel.verbose = false duel.requiredLibs = { "dcsCommon", @@ -11,7 +11,9 @@ duel.requiredLibs = { 1.0.0 - Initial Version 1.0.1 - verbosity bug with SSB removed 1.0.2 - units are reserved for player when they disappear - + 1.1.0 - maxRed and maxBlue per zone to be able to create + 1v1, 2v2, XvY duel zones + --]]-- --[[-- @@ -25,8 +27,6 @@ duel.requiredLibs = { duel.duelZones = {} duel.activePlayers = {} -- by player name ---duel.activeUnits = {} -- as above, by unit name ---duel.missingPlayers = {} duel.allDuelists = {} -- all potential dualists as collected from zones -- -- reading attributes @@ -49,8 +49,8 @@ function duel.createDuelZone(theZone) local groupData = cfxMX.playerUnit2Group[unitName] duelist.groupName = groupData.name duelist.coa = cfxMX.groupCoalitionByName[duelist.groupName] - if duel.verbose then --- trigger.action.outText("Detected player unit <" .. duelist.name .. ">, type <" .. duelist.type .. "> of group <" .. duelist.groupName .. "> of coa <" .. duelist.coa .. "> in zone <" .. theZone.name .. "> as duelist", 30) + if duel.verbose or theZone.verbose then + trigger.action.outText("Detected player unit <" .. duelist.name .. ">, type <" .. duelist.type .. "> of group <" .. duelist.groupName .. "> of coa <" .. duelist.coa .. "> in zone <" .. theZone.name .. "> as duelist", 30) end duelist.active = false @@ -60,14 +60,13 @@ function duel.createDuelZone(theZone) -- enter into global table -- player can only be in at maximum one duelist zones if duel.allDuelists[unitName] then - trigger.action.outText("+++WARNING: overlapping duelists! Overwriting previous data", 30) + trigger.action.outText("+++WARNING: overlapping duelists for zone <" .. theZone.name .. ">! Overwriting previous data", 30) end duel.allDuelists[unitName] = duelist theZone.duelists[unitName] = duelist end end - theZone.state = "waiting" -- FSM, init to waiting state theZone.duelTriggerMethod = theZone:getStringFromZoneProperty("duelTriggerMethod", "change") if theZone:hasProperty("on?") then theZone.duelOnFlag = theZone:getStringFromZoneProperty("on?", "*none") @@ -83,6 +82,8 @@ function duel.createDuelZone(theZone) theZone.active = false end + theZone.maxRed = theZone:getNumberFromZoneProperty("maxRed", 1) + theZone.maxBlue = theZone:getNumberFromZoneProperty("maxBlue", 1) end -- @@ -102,12 +103,48 @@ function duel.closeSlotsForZoneAndCoaExceptGroupNamed(theZone, coa, groupName) end end +function duel.closeSlotsForZoneAndCoaExceptActive(theZone, coa) + -- iterate this zone's duelist groups and tell SSB to close them now + local allDuelists = theZone.duelists + for unitName, theDuelist in pairs(allDuelists) do + local theUnit = Unit.getByName(unitName) + if theUnit and Unit.isExist(theUnit) then + -- is unit exists already, do not close down + if theZone.verbose or duel.verbose then + trigger.action.outText("+++duel: leaving unit <" .. unitName .. "> in game", 30) + end + else + -- this unit is not live, close group down + local dgName = theDuelist.groupName + if (theDuelist.coa == coa) and (dgName ~= groupName) then + if duel.verbose or theZone.verbose then + trigger.action.outText("+++duel: closing SSB slot for group <" .. dgName .. ">, coa <" .. theDuelist.coa .. ">", 30) + end + trigger.action.setUserFlag(dgName,100) -- anything but 0 means closed + end + end + end +end + +function duel.countActiveUnitsForCoaInZone(theZone, coa) +local allDuelists = theZone.duelists + local activeCount = 0 + for unitName, theDuelist in pairs(allDuelists) do + if theDuelist.coa == coa then + local theUnit = Unit.getByName(unitName) + if theUnit and Unit.isExist(theUnit) then + activeCount = activeCount + 1 + end + end + end + return activeCount +end function duel.openSlotsForZoneAndCoa(theZone, coa) local allDuelists = theZone.duelists for unitName, theDuelist in pairs(allDuelists) do if (theDuelist.coa == coa) then - if duel.verbose then + if duel.verbose or theZone.verbose then trigger.action.outText("+++duel: opening SSB slot for group <" .. theDuelist.groupName .. ">, coa <" .. theDuelist.coa .. ">", 30) end trigger.action.setUserFlag(theDuelist.groupName, 0) -- 0 means OPEN @@ -117,28 +154,30 @@ end function duel.checkReopenSlotsForZoneAndCoa(theZone, coa) -- test if one side can reopen all slots to enter the duel - -- if so, will reset FSM for zone + -- + local maxForCoa = theZone.maxRed + if coa == 2 then maxForCoa = theZone.maxBlue end local allDuelists = theZone.duelists - local allUnengaged = true + local canReopen = true + local engageCount = 0 for unitName, theDuelist in pairs(allDuelists) do if (theDuelist.coa == coa) then local theUnit = Unit.getByName(unitName) if theUnit and Unit.isExist(theUnit) then - -- unit is still alive on this side, can't reopen - allUnengaged = false + -- unit is still alive on this side + engageCount = engageCount + 1 end end end - - if allUnengaged then + if engageCount < maxForCoa then canReopen = true end + if canReopen then if duel.verbose then trigger.action.outText("+++duel: will open all slots for <" .. theZone:getName() .. ">, coa <" .. coa .. ">", 30) end duel.openSlotsForZoneAndCoa(theZone, coa) - theZone.state = "waiting" else if duel.verbose then - trigger.action.outText("+++duel: unable to reopenslots for <" .. theZone:getName() .. ">, coa <" .. coa .. ">, still engaged", 30) + trigger.action.outText("+++duel: unable to reopenslots for <" .. theZone:getName() .. ">, coa <" .. coa .. ">, " .. engageCount .. " units are still engaged", 30) end end end @@ -149,6 +188,10 @@ function duel.duelistEnteredArena(theUnit, theDuelist) theDuelist.active = true local player = theUnit:getPlayerName() + if not player then + trigger.action.outText("+++Duel: WARNING: no player name for unit <" .. theUnit:getName() .. "> upon enter arena", 30) + return + end local unitName = theUnit:getName() local groupName = theDuelist.groupName local theZone = theDuelist.zone --duel.duelZones[theDuelist.arena] @@ -187,11 +230,22 @@ function duel.duelistEnteredArena(theUnit, theDuelist) duel.activePlayers[player] = playerData -- close all slots for this zone and coalition if it is active - if theZone.active then - if theZone.verbose or duel.verbose then - trigger.action.outText("+++duel: zone <" .. theZone:getName() .. ">, closing coa <" .. coa .. "> slots except for player's <" .. player .. "> group <" .. groupName .. ">", 30) + -- and we have reched the maximum of players for that coalition + local maxForCoa = theZone.maxRed + if coa == 2 then maxForCoa = theZone.maxBlue end + local activeInZone = duel.countActiveUnitsForCoaInZone(theZone, coa) + if theZone.active then + if(activeInZone >= maxForCoa) then + if theZone.verbose or duel.verbose then + trigger.action.outText("+++duel: zone <" .. theZone:getName() .. ">, closing coa <" .. coa .. "> slots except for player's <" .. player .. "> group <" .. groupName .. ">", 30) + end + --duel.closeSlotsForZoneAndCoaExceptGroupNamed(theZone, coa, groupName) + duel.closeSlotsForZoneAndCoaExceptActive(theZone, coa) + else + if duel.verbose or theZone.verbose then + trigger.action.outText("Zone <" .. theZone.name .. "> Coa <" .. coa .. "> remains open, have <" .. activeInZone .. "> participants, max is <" .. maxForCoa .. ">", 30) + end end - duel.closeSlotsForZoneAndCoaExceptGroupNamed(theZone, coa, groupName) else if theZone.verbose or duel.verbose then trigger.action.outText("+++duel: zone <" .. theZone:getName() .. "> currently not active, not closing slots", 30) @@ -234,30 +288,7 @@ function duel.update() -- call me in a second to poll triggers timer.scheduleFunction(duel.update, {}, timer.getTime() + 1/duel.ups) - -- find units that have disappeared, and react accordingly - --[[-- - for unitName, theDuelist in pairs (duel.allDuelists) do - local theZone = theDuelist.zone - if theDuelist.active then --- trigger.action.outText("+++duel: unit <" .. unitName .. "> is active in zone <" .. theZone:getName() .. ">, controlled by <" .. theDuelist.playerName .. ">", 30) - - local theUnit = Unit.getByName(unitName) - if theUnit and Unit.isExist(theUnit) then - -- all is well - else - if duel.verbose then - trigger.action.outText("+++duel: unit <" .. unitName .. "> controlled by <" .. theDuelist.playerName .. "> has disappeared, starting cleanup", 30) - end - - theDuelist.playerName = nil - theDuelist.active = false - duel.checkReopenSlotsForZoneAndCoa(theZone, theDuelist.coa) - end - end - end - --]]-- - - -- now check the active players and their units + -- check active players and their units local now = timer.getTime() local filtered = {} for playerName, playerData in pairs(duel.activePlayers) do diff --git a/modules/stopGaps standalone.lua b/modules/stopGaps standalone.lua index 56931c6..80a5ed5 100644 --- a/modules/stopGaps standalone.lua +++ b/modules/stopGaps standalone.lua @@ -1,10 +1,12 @@ stopGap = {} -stopGap.version = "1.0.6 STANDALONE" +stopGap.version = "1.0.9 STANDALONE" stopGap.verbose = false stopGap.ssbEnabled = true stopGap.ignoreMe = "-sg" stopGap.spIgnore = "-sp" -- only single-player ignored stopGap.isMP = false +stopGap.running = true +stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour --[[-- Written and (c) 2023 by Christian Franz @@ -29,6 +31,9 @@ stopGap.isMP = false 1.0.5 - (DML-only additions) 1.0.6 - can detect stopGapGUI active on server - supports "-sp" for single-player only suppress + 1.0.7 - (DML-only internal cool stuff) + 1.0.8 - added refreshInterval option as requested + 1.0.9 - optimization when turning on stopgap --]]-- stopGap.standInGroups ={} @@ -175,7 +180,7 @@ end function stopGap.initGaps() -- when we enter, all slots are emptry - -- and we populate all slots + -- and we populate all empty slots -- with their static representations for name, group in pairs (cfxMX.playerGroupByName) do -- check to see if this group is on the ground at parking @@ -194,13 +199,11 @@ function stopGap.initGaps() end else -- replace all groups entirely with static objects - ---local allUnits = group.units local theStaticGroup = stopGap.createStandInsForMXGroup(group) -- remember this static group by its real name stopGap.standInGroups[group.name] = theStaticGroup end end -- if groundtstart - end end @@ -212,13 +215,29 @@ function stopGap.turnOff() end end stopGap.standInGroups = {} + stopGap.running = false end function stopGap.turnOn() - -- populate all empty (non-taken) slots with stand-ins + -- populate all empty (un-occupied) slots with stand-ins stopGap.initGaps() + stopGap.running = true end +function stopGap.refreshAll() -- restore all statics + if stopGap.refreshInterval > 0 then + -- re-schedule invocation + timer.scheduleFunction(stopGap.refreshAll, {}, timer.getTime() + stopGap.refreshInterval) + if stopGap.running then + stopGap.turnOff() -- kill all statics + -- turn back on in half a second + timer.scheduleFunction(stopGap.turnOn, {}, timer.getTime() + 0.5) + end + if stopGap.verbose then + trigger.action.outText("+++stopG: refreshing all static", 30) + end + end +end -- -- event handling -- @@ -271,6 +290,7 @@ function stopGap.update() stopGap.turnOff() stopGap.isMP = true stopGap.turnOn() + return end end @@ -349,9 +369,14 @@ function stopGap.start() -- connect event handler world.addEventHandler(stopGap) - -- start update in 10 seconds + -- start update in 1 second timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1) + -- start refresh cycle if refresh (>0) + if stopGap.refreshInterval > 0 then + timer.scheduleFunction(stopGap.refreshAll, {}, timer.getTime() + stopGap.refreshInterval) + end + -- say hi! local mp = " (SP - <" .. sgDetect .. ">)" if sgDetect > 0 then mp = " -- MP GUI Detected (" .. sgDetect .. ")!" end diff --git a/modules/stopGaps.lua b/modules/stopGaps.lua index 2e2efc7..b7aba7e 100644 --- a/modules/stopGaps.lua +++ b/modules/stopGaps.lua @@ -1,10 +1,13 @@ stopGap = {} -stopGap.version = "1.0.7" +stopGap.version = "1.0.8" stopGap.verbose = false stopGap.ssbEnabled = true stopGap.ignoreMe = "-sg" stopGap.spIgnore = "-sp" -- only single-player ignored stopGap.isMP = false +stopGap.running = true +stopGap.refreshInterval = -1 -- seconds to refresh all statics. -1 = never, 3600 = once every hour + stopGap.requiredLibs = { "dcsCommon", @@ -42,6 +45,10 @@ stopGap.requiredLibs = { 1.0.6 - spIgnore '-sp' 1.0.7 - migrated to OOP zones - corrected ssbEnabled config from sbb to ssb + 1.0.8 - added refreshInterval option as requested + - refresh attribute config zone + 1.0.9 - in line with standalone (optimization not required for DML) + --]]-- stopGap.standInGroups = {} @@ -187,11 +194,28 @@ function stopGap.turnOff() end end stopGap.standInGroups = {} + stopGap.running = false end function stopGap.turnOn() -- populate all empty (non-taken) slots with stand-ins stopGap.initGaps() + stopGap.running = true +end + +function stopGap.refreshAll() -- restore all statics + if stopGap.refreshInterval > 0 then + -- re-schedule invocation + timer.scheduleFunction(stopGap.refreshAll, {}, timer.getTime() + stopGap.refreshInterval) + if stopGap.running then + stopGap.turnOff() -- kill all statics + -- turn back on in half a second + timer.scheduleFunction(stopGap.turnOn, {}, timer.getTime() + 0.5) + end + if stopGap.verbose then + trigger.action.outText("+++stopG: refreshing all static", 30) + end + end end -- -- event handling @@ -366,6 +390,8 @@ function stopGap.readConfigZone(theZone) trigger.action.outText("+++StopG: turned off", 30) end end + + stopGap.refreshInterval = theZone:getNumberFromZoneProperty("refresh", -1) -- default: no refresh end -- @@ -407,9 +433,14 @@ function stopGap.start() -- connect event handler world.addEventHandler(stopGap) - -- start update in 10 seconds + -- start update in 1 second timer.scheduleFunction(stopGap.update, {}, timer.getTime() + 1) + -- start refresh cycle if refresh (>0) + if stopGap.refreshInterval > 0 then + timer.scheduleFunction(stopGap.refreshAll, {}, timer.getTime() + stopGap.refreshInterval) + end + -- say hi! local mp = " (SP - <" .. sgDetect .. ">)" if sgDetect > 0 then mp = " -- MP GUI Detected (" .. sgDetect .. ")!" end diff --git a/modules/unitZone.lua b/modules/unitZone.lua index a82a87a..5ffc61e 100644 --- a/modules/unitZone.lua +++ b/modules/unitZone.lua @@ -1,5 +1,5 @@ unitZone={} -unitZone.version = "1.2.5" +unitZone.version = "2.0.0" unitZone.verbose = false unitZone.ups = 1 unitZone.requiredLibs = { @@ -18,7 +18,15 @@ unitZone.requiredLibs = { - better guards for uzOn? and uzOff? 1.2.4 - more verbosity on uzDirect 1.2.5 - reading config improvement - + 2.0.0 - matchAll option (internal, automatic look for "*" in names) + - lookFor defaults to "*" + - OOP dmlZones + - uzDirect correctly initialized at start + - synonyms uzDirect#, uzDirectInv# + - uzDirectInv better support + - unitZone now used to define the coalition, coalition DEPRECATED + - filter synonym + - direct#, directInv# synonyms --]]-- unitZone.unitZones = {} @@ -63,81 +71,117 @@ end function unitZone.createUnitZone(theZone) -- start val - a range - theZone.lookFor = cfxZones.getStringFromZoneProperty(theZone, "lookFor", "cfx no unit supplied") - if dcsCommon.stringEndsWith(theZone.lookFor, "*") then - theZone.lookForBeginsWith = true + theZone.lookFor = theZone:getStringFromZoneProperty("lookFor", "*") -- default to match all + if theZone.lookFor == "*" then + theZone.matchAll = true + if theZone.verbose or unitZone.verbose then + trigger.action.outText("+++uZne: zone <" .. theZone.name .. "> set up to matche all names", 30) + end + elseif dcsCommon.stringEndsWith(theZone.lookFor, "*") then + theZone.lookForBeginsWith = true + theZone.matchAll = false theZone.lookFor = dcsCommon.removeEnding(theZone.lookFor, "*") end - theZone.matching = cfxZones.getStringFromZoneProperty(theZone, "matching", "group") -- group, player [, name, type] + theZone.matching = theZone:getStringFromZoneProperty("matching", "group") -- group, player [, name, type] theZone.matching = dcsCommon.trim(theZone.matching:lower()) if theZone.matching == "groups" then theZone.matching = "group" end -- some simplification if theZone.matching == "players" then theZone.matching = "player" end -- some simplification -- coalition - theZone.uzCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0) -- 0 = all - if cfxZones.hasProperty(theZone, "uzCoalition") then - theZone.uzCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "uzCoalition", 0) + theZone.uzCoalition = theZone:getCoalitionFromZoneProperty("unitZone", 0) -- now with main attribute + -- DEPRECATED 2023 SEPT: provided for legacy compatibility + if theZone:hasProperty("coalition") then + theZone.uzCoalition = theZone:getCoalitionFromZoneProperty("coalition", 0) -- 0 = all + elseif theZone:hasProperty("uzCoalition") then + theZone.uzCoalition = theZone:getCoalitionFromZoneProperty("uzCoalition", 0) end - if unitZone.verbose or theZone.verbose then - trigger.action.outText("+++uZne: set coa " .. theZone.uzCoalition .. " for <" .. theZone.name .. ">", 30) - end - -- DML Method - theZone.uzMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") - if cfxZones.hasProperty(theZone, "uzMethod") then - theZone.uzMethod = cfxZones.getStringFromZoneProperty(theZone, "uzMethod", "inc") + theZone.uzMethod = theZone:getStringFromZoneProperty("method", "inc") + if theZone:hasProperty("uzMethod") then + theZone.uzMethod = theZone:getStringFromZoneProperty("uzMethod", "inc") end - if cfxZones.hasProperty(theZone, "enterZone!") then - theZone.enterZone = cfxZones.getStringFromZoneProperty(theZone, "enterZone!", "*") + if theZone:hasProperty("enterZone!") then + theZone.enterZone = theZone:getStringFromZoneProperty("enterZone!", "*") end - if cfxZones.hasProperty(theZone, "exitZone!") then - theZone.exitZone = cfxZones.getStringFromZoneProperty(theZone, "exitZone!", "*") + if theZone:hasProperty("exitZone!") then + theZone.exitZone = theZone:getStringFromZoneProperty("exitZone!", "*") end - if cfxZones.hasProperty(theZone, "changeZone!") then - theZone.changeZone = cfxZones.getStringFromZoneProperty(theZone, "changeZone!", "*") + if theZone:hasProperty("changeZone!") then + theZone.changeZone = theZone:getStringFromZoneProperty("changeZone!", "*") end - if cfxZones.hasProperty(theZone, "filterFor") then - local filterString = cfxZones.getStringFromZoneProperty(theZone, "filterFor", "1") -- ground + if theZone:hasProperty("filterFor") then + local filterString = theZone:getStringFromZoneProperty( "filterFor", "1") -- ground theZone.filterFor = unitZone.string2cat(filterString) if unitZone.verbose or theZone.verbose then trigger.action.outText("+++uZne: filtering " .. theZone.filterFor .. " in " .. theZone.name, 30) end + elseif theZone:hasProperty("filter") then + local filterString = theZone:getStringFromZoneProperty( "filter", "1") -- ground + theZone.filterFor = unitZone.string2cat(filterString) + if unitZone.verbose or theZone.verbose then + trigger.action.outText("+++uZne: filtering " .. theZone.filterFor .. " in " .. theZone.name, 30) + end end -- uzDirect - if cfxZones.hasProperty(theZone, "uzDirect") then - theZone.uzDirect = cfxZones.getStringFromZoneProperty(theZone, "uzDirect", "*") + if theZone:hasProperty("uzDirect") then + theZone.uzDirect = theZone:getStringFromZoneProperty("uzDirect", "*") + elseif + theZone:hasProperty("uzDirect#") then + theZone.uzDirect = theZone:getStringFromZoneProperty("uzDirect#", "*") + elseif + theZone:hasProperty("direct#") then + theZone.uzDirect = theZone:getStringFromZoneProperty("direct#", "*") end - if cfxZones.hasProperty(theZone, "uzDirectInv") then - theZone.uzDirectInv = cfxZones.getStringFromZoneProperty(theZone, "uzDirectInv", "*") + if theZone:hasProperty("uzDirectInv") then + theZone.uzDirectInv = theZone:getStringFromZoneProperty("uzDirectInv", "*") + elseif theZone:hasProperty("uzDirectInv#") then + theZone.uzDirectInv = theZone:getStringFromZoneProperty("uzDirectInv#", "*") + elseif theZone:hasProperty("directInv#") then + theZone.uzDirectInv = theZone:getStringFromZoneProperty("directInv#", "*") end -- on/off flags theZone.uzPaused = false -- we are turned on - if cfxZones.hasProperty(theZone, "uzOn?") then - theZone.triggerOnFlag = cfxZones.getStringFromZoneProperty(theZone, "uzOn?", "*") - theZone.lastTriggerOnValue = cfxZones.getFlagValue(theZone.triggerOnFlag, theZone) + if theZone:hasProperty("uzOn?") then + theZone.triggerOnFlag = theZone:getStringFromZoneProperty("uzOn?", "*") + theZone.lastTriggerOnValue = theZone:getFlagValue(theZone.triggerOnFlag) end - if cfxZones.hasProperty(theZone, "uzOff?") then - theZone.triggerOffFlag = cfxZones.getStringFromZoneProperty(theZone, "uzOff?", "*") - theZone.lastTriggerOffValue = cfxZones.getFlagValue(theZone.triggerOffFlag, theZone) + if theZone:hasProperty("uzOff?") then + theZone.triggerOffFlag = theZone:getStringFromZoneProperty("uzOff?", "*") + theZone.lastTriggerOffValue = theZone:getFlagValue(theZone.triggerOffFlag) end - theZone.uzTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") - if cfxZones.hasProperty(theZone, "uzTriggerMethod") then - theZone.uzTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "uzTriggerMethod", "change") + theZone.uzTriggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change") + if theZone:hasProperty("uzTriggerMethod") then + theZone.uzTriggerMethod = theZone:getStringFromZoneProperty("uzTriggerMethod", "change") end -- now get initial zone status ? theZone.lastStatus = unitZone.checkZoneStatus(theZone) + if theZone.uzDirect then + if newState then + theZone:setFlagValue(theZone.uzDirect, 1) + else + theZone:setFlagValue(theZone.uzDirect, 0) + end + end + if theZone.uzDirectInv then + if newState then + theZone:setFlagValue(theZone.uzDirectInv, 0) + else + theZone:setFlagValue(theZone.uzDirectInv, 1) + end + end + if unitZone.verbose or theZone.verbose then - trigger.action.outText("+++uZne: processsed unit zone " .. theZone.name, 30) + trigger.action.outText("+++uZne: processsed unit zone <" .. theZone.name .. "> with status = (" .. dcsCommon.bool2Text(theZone.lastStatus) .. ")", 30) end end @@ -148,7 +192,7 @@ end -- function unitZone.collectGroups(theZone) - local collector = {} + local collector = {} -- players: units, groups: groups if theZone.matching == "player" then -- collect all players matching coalition if theZone.uzCoalition == 1 or theZone.uzCoalition == 0 then @@ -166,16 +210,14 @@ function unitZone.collectGroups(theZone) elseif theZone.matching == "group" then if theZone.uzCoalition == 1 or theZone.uzCoalition == 0 then local allGroups = coalition.getGroups(1, theZone.filterFor) - - for idx, pUnit in pairs(allGroups) do - table.insert(collector, pUnit) + for idx, aGroup in pairs(allGroups) do + table.insert(collector, aGroup) end end if theZone.uzCoalition == 2 or theZone.uzCoalition == 0 then local allGroups = coalition.getGroups(2, theZone.filterFor) - - for idx, pUnit in pairs(allGroups) do - table.insert(collector, pUnit) + for idx, aGroup in pairs(allGroups) do + table.insert(collector, aGroup) end end else @@ -197,15 +239,17 @@ function unitZone.checkZoneStatus(theZone) local playerCheck = theZone.matching == "player" if playerCheck then -- we check the names for players only - -- collector holds units, not groups - for idx, pUnit in pairs(theGroups) do + -- collector holds units for players, not groups + for idx, pUnit in pairs(theGroups) do local puName = pUnit:getName() - local hasMatch = false - if theZone.lookForBeginsWith then - hasMatch = dcsCommon.stringStartsWith(puName, lookFor) - else - hasMatch = puName == lookFor - end + local hasMatch = theZone.matchAll + if not hasMatch then + if theZone.lookForBeginsWith then + hasMatch = dcsCommon.stringStartsWith(puName, lookFor) + else + hasMatch = puName == lookFor + end + end if hasMatch then if cfxZones.unitInZone(pUnit, theZone) then return true @@ -214,20 +258,21 @@ function unitZone.checkZoneStatus(theZone) end else - -- we perform group check + -- we perform group check. for idx, aGroup in pairs(theGroups) do local gName=aGroup:getName() - local hasMatch = false - if theZone.lookForBeginsWith then - hasMatch = dcsCommon.stringStartsWith(gName, lookFor) - else - hasMatch = gName == lookFor - end + local hasMatch = theZone.matchAll + if not hasMatch then + if theZone.lookForBeginsWith then + hasMatch = dcsCommon.stringStartsWith(gName, lookFor) + else + hasMatch = gName == lookFor + end + end if hasMatch and aGroup:isExist() then -- check all living units in zone local gUnits = aGroup:getUnits() for idy, aUnit in pairs (gUnits) do - --trigger.action.outText("trying " .. gName,10) if cfxZones.unitInZone(aUnit, theZone) then return true end @@ -244,18 +289,18 @@ end function unitZone.bangState(theZone, newState) if theZone.changeZone then - cfxZones.pollFlag(theZone.changeZone, theZone.uzMethod, theZone) + theZone:pollFlag(theZone.changeZone, theZone.uzMethod) end if newState then if theZone.enterZone then - cfxZones.pollFlag(theZone.enterZone, theZone.uzMethod, theZone) + theZone:pollFlag(theZone.enterZone, theZone.uzMethod) 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) + theZone:pollFlag(theZone.exitZone, theZone.uzMethod) if unitZone.verbose then trigger.action.outText("+++uZone: banging exit! with <" .. theZone.uzMethod .. "> on <" .. theZone.exitZone .. "> for " .. theZone.name, 30) end @@ -269,14 +314,16 @@ function unitZone.update() for idx, aZone in pairs(unitZone.unitZones) do -- check if we need to pause/unpause - if aZone.triggerOnFlag and cfxZones.testZoneFlag(aZone, aZone.triggerOnFlag, aZone.uzTriggerMethod, "lastTriggerOnValue") then + if aZone.triggerOnFlag and + aZone:testZoneFlag( 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 aZone.triggerOffFlag and cfxZones.testZoneFlag(aZone, aZone.triggerOffFlag, aZone.uzTriggerMethod, "lastTriggerOffValue") then + if aZone.triggerOffFlag and + aZone:testZoneFlag(aZone.triggerOffFlag, aZone.uzTriggerMethod, "lastTriggerOffValue") then if unitZone.verbose or aZone.verbose then trigger.action.outText("+++uZone: turning " .. aZone.name .. " OFF", 30) end @@ -299,9 +346,9 @@ function unitZone.update() trigger.action.outText("+++uZone: <" .. aZone.name .. "> setting uzDirect <" .. aZone.uzDirect .. "> to ".. dcsCommon.bool2Num(newState), 30) end if newState then - cfxZones.setFlagValueMult(aZone.uzDirect, 1, aZone) + aZone:setFlagValue(aZone.uzDirect, 1) else - cfxZones.setFlagValueMult(aZone.uzDirect, 0, aZone) + aZone:setFlagValue(aZone.uzDirect, 0) end end if aZone.uzDirectInv then @@ -310,9 +357,9 @@ function unitZone.update() trigger.action.outText("+++uZone: <" .. aZone.name .. "> setting INVuzDirect <" .. aZone.uzDirectInv .. "> to ".. dcsCommon.bool2Num(invState), 30) end if newState then - cfxZones.setFlagValueMult(aZone.uzDirectInv, 0, aZone) + aZone:setFlagValue(aZone.uzDirectInv, 0) else - cfxZones.setFlagValueMult(aZone.uzDirectInv, 1, aZone) + aZone:setFlagValue(aZone.uzDirectInv, 1) end end end @@ -323,6 +370,7 @@ end -- Config & Start -- function unitZone.readConfigZone() + unitZone.name = "unitZoneConfig" local theZone = cfxZones.getZoneByName("unitZoneConfig") if not theZone then theZone = cfxZones.createSimpleZone("unitZoneConfig") @@ -370,6 +418,6 @@ if not unitZone.start() then unitZone = nil end ---ToDo: matching: name, name wildcard, type + --ToDo: add 'neutral' support and add 'both' option --ToDo: add API \ No newline at end of file diff --git a/tutorial & demo missions/demo - my immortal.miz b/tutorial & demo missions/demo - my immortal.miz new file mode 100644 index 0000000..e648014 Binary files /dev/null and b/tutorial & demo missions/demo - my immortal.miz differ