Version 1.4.4

stopGap refresh
interpreted wildcards
unitZone "*" match all
This commit is contained in:
Christian Franz 2023-09-21 15:48:56 +02:00
parent 0f1e814d22
commit d1d4af63a0
8 changed files with 360 additions and 143 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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 = "<dummy>"
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 "<none>"
if dcsCommon.containsString(theFlag, "<none>") then
trigger.action.outText("+++Zone: warning - setFlag has '<none>' 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. "<coa: flag Name>
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 "<coa:"
param = string.gsub(param, ">","") -- 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. <alive: Aerial-1-1>
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. "<yes: flagOrUnitName>
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 "<alive:"
param = string.gsub(param, ">","") -- 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
-- <A/B: flagOrUnitName [val A | val B]>
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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!", "*<none>")
if theZone:hasProperty("enterZone!") then
theZone.enterZone = theZone:getStringFromZoneProperty("enterZone!", "*<none>")
end
if cfxZones.hasProperty(theZone, "exitZone!") then
theZone.exitZone = cfxZones.getStringFromZoneProperty(theZone, "exitZone!", "*<none>")
if theZone:hasProperty("exitZone!") then
theZone.exitZone = theZone:getStringFromZoneProperty("exitZone!", "*<none>")
end
if cfxZones.hasProperty(theZone, "changeZone!") then
theZone.changeZone = cfxZones.getStringFromZoneProperty(theZone, "changeZone!", "*<none>")
if theZone:hasProperty("changeZone!") then
theZone.changeZone = theZone:getStringFromZoneProperty("changeZone!", "*<none>")
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", "*<none>")
if theZone:hasProperty("uzDirect") then
theZone.uzDirect = theZone:getStringFromZoneProperty("uzDirect", "*<none>")
elseif
theZone:hasProperty("uzDirect#") then
theZone.uzDirect = theZone:getStringFromZoneProperty("uzDirect#", "*<none>")
elseif
theZone:hasProperty("direct#") then
theZone.uzDirect = theZone:getStringFromZoneProperty("direct#", "*<none>")
end
if cfxZones.hasProperty(theZone, "uzDirectInv") then
theZone.uzDirectInv = cfxZones.getStringFromZoneProperty(theZone, "uzDirectInv", "*<none>")
if theZone:hasProperty("uzDirectInv") then
theZone.uzDirectInv = theZone:getStringFromZoneProperty("uzDirectInv", "*<none>")
elseif theZone:hasProperty("uzDirectInv#") then
theZone.uzDirectInv = theZone:getStringFromZoneProperty("uzDirectInv#", "*<none>")
elseif theZone:hasProperty("directInv#") then
theZone.uzDirectInv = theZone:getStringFromZoneProperty("directInv#", "*<none>")
end
-- on/off flags
theZone.uzPaused = false -- we are turned on
if cfxZones.hasProperty(theZone, "uzOn?") then
theZone.triggerOnFlag = cfxZones.getStringFromZoneProperty(theZone, "uzOn?", "*<none1>")
theZone.lastTriggerOnValue = cfxZones.getFlagValue(theZone.triggerOnFlag, theZone)
if theZone:hasProperty("uzOn?") then
theZone.triggerOnFlag = theZone:getStringFromZoneProperty("uzOn?", "*<none1>")
theZone.lastTriggerOnValue = theZone:getFlagValue(theZone.triggerOnFlag)
end
if cfxZones.hasProperty(theZone, "uzOff?") then
theZone.triggerOffFlag = cfxZones.getStringFromZoneProperty(theZone, "uzOff?", "*<none2>")
theZone.lastTriggerOffValue = cfxZones.getFlagValue(theZone.triggerOffFlag, theZone)
if theZone:hasProperty("uzOff?") then
theZone.triggerOffFlag = theZone:getStringFromZoneProperty("uzOff?", "*<none2>")
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

Binary file not shown.