diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 6827ec1..3790ccd 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 43938be..bfd85ac 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 6be4f58..d08a111 100644 --- a/modules/RNDFlags.lua +++ b/modules/RNDFlags.lua @@ -202,6 +202,10 @@ function rndFlags.fire(theZone) local theFlag = table.remove(availableFlags,theFlagIndex) --rndFlags.pollFlag(theFlag, theZone.rndMethod) + if rndFlags.verbose then + trigger.action.outText("+++RND: polling " .. theFlag .. " with " .. theZone.rndMethod, 30) + end + cfxZones.pollFlag(theFlag, theZone.rndMethod, theZone) end @@ -309,3 +313,4 @@ if not rndFlags.start() then rndFlags = nil end +-- TODO: move flags to RND!, rename RND to RND!, deprecate flags! diff --git a/modules/cfxSSBClient.lua b/modules/cfxSSBClient.lua index 906eecb..8f62fcc 100644 --- a/modules/cfxSSBClient.lua +++ b/modules/cfxSSBClient.lua @@ -1,5 +1,5 @@ cfxSSBClient = {} -cfxSSBClient.version = "2.0.0" +cfxSSBClient.version = "2.0.2" cfxSSBClient.verbose = false cfxSSBClient.singleUse = false -- set to true to block crashed planes -- NOTE: singleUse (true) requires SSB to disable immediate respawn after kick @@ -36,6 +36,7 @@ Version History - reUseAfter option for single-use - dcsCommon, cfxZones import 2.0.1 - stricter verbosity: moved more comments to verbose only + 2.0.2 - health check code WHAT IT IS SSB Client is a small script that forms the client-side counterpart to @@ -232,7 +233,20 @@ function cfxSSBClient.openSlotForCrashedGroupNamed(gName) end end -function cfxSSBClient:onEvent(event) +function cfxSSBClient:onEvent(event) + if event.id == 21 then -- S_EVENT_PLAYER_LEAVE_UNIT + trigger.action.outText("+++SSB: Player leave unit", 30) + local theUnit = event.initiator + if not theUnit then + trigger.action.outText("+++SSB: No unit left, abort", 30) + return + end + local curH = theUnit:getLife() + local maxH = theUnit:getLife0() + trigger.action.outText("+++SSB: Health check: " .. curH .. " of " .. maxH, 30) + return + end + if event.id == 10 then -- S_EVENT_BASE_CAPTURED if cfxSSBClient.verbose then trigger.action.outText("+++SSB: CAPTURE EVENT -- RESETTING SLOTS", 30) diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index b7b2a61..c3fbadb 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -6,7 +6,7 @@ -- cfxZones = {} -cfxZones.version = "2.5.7" +cfxZones.version = "2.5.8" --[[-- VERSION HISTORY - 2.2.4 - getCoalitionFromZoneProperty - getStringFromZoneProperty @@ -55,6 +55,8 @@ cfxZones.version = "2.5.7" - randomDelayFromPositiveRange - isMEFlag - 2.5.7 - pollFlag supports dml flags + - 2.5.8 - flagArrayFromString + - getFlagNumber invokes tonumber() before returning result --]]-- cfxZones.verbose = false @@ -117,7 +119,7 @@ function cfxZones.readFromDCS(clearfirst) return; end - -- we only retrive the data we need. At this point it is name, location and radius + -- we only retrieve the data we need. At this point it is name, location and radius -- and put this in our own little structure. we also convert to all upper case name for index -- and assume that the name may also carry meaning, e.g. 'LZ:' defines a landing zone -- so we can quickly create other sets from this @@ -1149,30 +1151,85 @@ function cfxZones.getFlagValue(theFlag, theZone) if type(theFlag) == "number" then -- straight get, ME flag - return trigger.misc.getUserFlag(theFlag) + return tonumber(trigger.misc.getUserFlag(theFlag)) end -- we assume it's a string now theFlag = dcsCommon.trim(theFlag) -- clear leading/trailing spaces local nFlag = tonumber(theFlag) if nFlag then - return trigger.misc.getUserFlag(theFlag) + return tonumber(trigger.misc.getUserFlag(theFlag)) end -- now do wildcard processing. we have alphanumeric if dcsCommon.stringStartsWith(theFlag, "*") then theFlag = zoneName .. theFlag end - return trigger.misc.getUserFlag(theFlag) + return tonumber(trigger.misc.getUserFlag(theFlag)) end function cfxZones.isMEFlag(inFlag) + -- do NOT use me + trigger.action.outText("+++zne: warning: deprecated isMEFlag", 30) return true -- returns true if inFlag is a pure positive number -- inFlag = dcsCommon.trim(inFlag) -- return dcsCommon.stringIsPositiveNumber(inFlag) end +function cfxZones.flagArrayFromString(inString) +-- original code from RND flag + if string.len(inString) < 1 then + trigger.action.outText("+++zne: empty flags", 30) + return {} + end + if cfxZones.verbose then + trigger.action.outText("+++zne: processing <" .. inString .. ">", 30) + end + + local flags = {} + 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 + -- interpret this as a range + local theRange = dcsCommon.splitString(anElement, "-") + local lowerBound = theRange[1] + lowerBound = tonumber(lowerBound) + local upperBound = theRange[2] + upperBound = tonumber(upperBound) + if lowerBound and upperBound then + -- swap if wrong order + if lowerBound > upperBound then + local temp = upperBound + upperBound = lowerBound + lowerBound = temp + end + -- now add add numbers to flags + for f=lowerBound, upperBound do + table.insert(flags, tostring(f)) + end + else + -- bounds illegal + trigger.action.outText("+++zne: ignored range <" .. anElement .. "> (range)", 30) + end + else + -- single number + f = dcsCommon.trim(anElement) -- DML flag upgrade: accept strings tonumber(anElement) + if f then + table.insert(flags, f) + + else + trigger.action.outText("+++zne: ignored element <" .. anElement .. "> (single)", 30) + end + end + end + if cfxZones.verbose then + trigger.action.outText("+++zne: <" .. #flags .. "> flags total", 30) + end + return flags +end + -- -- PROPERTY PROCESSING -- diff --git a/modules/countDown.lua b/modules/countDown.lua index 2516fe7..d725600 100644 --- a/modules/countDown.lua +++ b/modules/countDown.lua @@ -20,6 +20,7 @@ countDown.requiredLibs = { 1.2.0 - DML Flags - counterOut! - ups config + 1.2.1 - disableCounter? --]]-- @@ -120,6 +121,13 @@ function countDown.createCountDownWithZone(theZone) if cfxZones.hasProperty(theZone, "counterOut!") then theZone.counterOut = cfxZones.getStringFromZoneProperty(theZone, "counterOut!", "") end + + -- disableFlag + theZone.counterDisabled = false + if cfxZones.hasProperty(theZone, "disableCounter?") then + theZone.disableCounterFlag = cfxZones.getStringFromZoneProperty(theZone, "disableCounter?", "") + theZone.disableCounterFlagVal = cfxZones.getFlagValue(theZone.disableCounterFlag, theZone) + end end -- @@ -195,7 +203,7 @@ function countDown.update() for idx, aZone in pairs(countDown.counters) do -- make sure to re-start before reading time limit - if aZone.triggerCountFlag then + if aZone.triggerCountFlag and not aZone.counterDisabled then local currTriggerVal = cfxZones.getFlagValue(aZone.triggerCountFlag, aZone) -- trigger.misc.getUserFlag(aZone.triggerCountFlag) if currTriggerVal ~= aZone.lastCountTriggerValue then @@ -206,6 +214,16 @@ function countDown.update() aZone.lastCountTriggerValue = cfxZones.getFlagValue(aZone.triggerCountFlag, aZone) -- trigger.misc.getUserFlag(aZone.triggerCountFlag) -- save last value end end + + if aZone.disableCounterFlag then + local currVal = cfxZones.getFlagValue(aZone.disableCounterFlag, aZone) + if currVal ~= aZone.disableCounterFlagVal then + if countDown.verbose then + trigger.action.outText("+++cntD: disabling counter " .. aZone.name, 30) + end + aZone.counterDisabled = true + end + end end end diff --git a/modules/csarManager2.lua b/modules/csarManager2.lua index 192360d..7bc8aa5 100644 --- a/modules/csarManager2.lua +++ b/modules/csarManager2.lua @@ -19,6 +19,7 @@ csarManager.version = "2.0.3" - 2.0.2 - use parametric csarManager.hoverAlt - use hoverDuration - 2.0.3 - corrected bug in hoverDuration + - 2.0.4 - guard in createCSARMission for cfxCommander --]]-- -- modules that need to be loaded BEFORE I run @@ -28,6 +29,7 @@ csarManager.requiredLibs = { "cfxPlayer", -- player monitoring and group monitoring "nameStats", -- generic data module for weight "cargoSuper", +-- "cfxCommander", -- needed if you want to hand-create CSAR missions } -- *** DOES NOT EXTEND ZONES *** BUT USES OWN STRUCT @@ -131,6 +133,11 @@ csarManager.csarCompleteCB = {} -- CREATING A CSAR -- function csarManager.createDownedPilot(theMission) + if not cfxCommander then + trigger.action.outText("+++CSAR: can't create mission, module cfxCommander is missing.", 30) + return + end + local aLocation = {} local aHeading = 0 -- in rads local newTargetZone = theMission.zone diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index 1fd5048..f2bfaa7 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.5.5" +dcsCommon.version = "2.5.6" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -65,6 +65,7 @@ dcsCommon.version = "2.5.5" 2.5.5 - stringStartsWithDigit() - stringStartsWithLetter() - stringIsPositiveNumber() + 2.5.6 - corrected stringEndsWith() bug with str --]]-- @@ -1740,7 +1741,7 @@ dcsCommon.version = "2.5.5" end function dcsCommon.stringEndsWith(theString, theEnding) - return theEnding == "" or str:sub(-#theEnding) == theEnding + return theEnding == "" or theString:sub(-#theEnding) == theEnding end function dcsCommon.removeEnding(theString, theEnding) diff --git a/modules/messenger.lua b/modules/messenger.lua index 6727a59..25aa5c3 100644 --- a/modules/messenger.lua +++ b/modules/messenger.lua @@ -1,5 +1,5 @@ messenger = {} -messenger.version = "1.1.0" +messenger.version = "1.1.1" messenger.verbose = false messenger.requiredLibs = { "dcsCommon", -- always @@ -15,7 +15,9 @@ messenger.messengers = {} - clearScreen option - inValue? - message preprocessor - + 1.1.1 - firewalled coalition to msgCoalition + - messageOn? + - messageOff? --]]-- function messenger.addMessenger(theZone) @@ -36,7 +38,7 @@ end -- -- read attributes -- -function messenger.createMessengerDownWithZone(theZone) +function messenger.createMessengerWithZone(theZone) -- start val - a range theZone.message = cfxZones.getStringFromZoneProperty(theZone, "message", "") @@ -68,9 +70,24 @@ function messenger.createMessengerDownWithZone(theZone) if theZone.triggerMessagerFlag then theZone.lastMessageTriggerValue = cfxZones.getFlagValue(theZone.triggerMessagerFlag, theZone)-- trigger.misc.getUserFlag(theZone.triggerMessagerFlag) -- save last value end + + theZone.messageOff = false + if cfxZones.hasProperty(theZone, "messageOff?") then + theZone.messageOffFlag = cfxZones.getStringFromZoneProperty(theZone, "messageOff?", "*none") + theZone.lastMessageOff = cfxZones.getFlagValue(theZone.messageOffFlag, theZone) + end + + if cfxZones.hasProperty(theZone, "messageOn?") then + theZone.messageOnFlag = cfxZones.getStringFromZoneProperty(theZone, "messageOn?", "*none") + theZone.lastMessageOn = cfxZones.getFlagValue(theZone.messageOnFlag, theZone) + end if cfxZones.hasProperty(theZone, "coalition") then - theZone.coalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0) + theZone.msgCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0) + end + + if cfxZones.hasProperty(theZone, "msgCoalition") then + theZone.msgCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "msgCoalition", 0) end -- flag whose value can be read @@ -78,6 +95,9 @@ function messenger.createMessengerDownWithZone(theZone) theZone.messageValue = cfxZones.getStringFromZoneProperty(theZone, "messageValue?", "") end + if messenger.verbose then + trigger.action.outText("+++Msg: new zone <".. theZone.name .."> will say <".. theZone.message .. ">", 30) + end end -- @@ -105,15 +125,25 @@ end function messenger.isTriggered(theZone) -- this module has triggered + if theZone.messageOff then + if messenger.verbose then + trigger.action.outFlag("msg: message for <".. theZone.name .."> is OFF",30) + end + return + end + local fileName = "l10n/DEFAULT/" .. theZone.soundFile local msg = messenger.getMessage(theZone) + if messenger.verbose then + trigger.action.outText("+++Msg: <".. theZone.name .."> will say <".. msg .. ">", 30) + end if theZone.spaceBefore then msg = "\n"..msg end if theZone.spaceAfter then msg = msg .. "\n" end - if theZone.coalition then - trigger.action.outTextForCoalition(theZone.coalition, msg, theZone.duration, theZone.clearScreen) - trigger.action.outSoundForCoalition(theZone.coalition, fileName) + if theZone.msgCoalition then + trigger.action.outTextForCoalition(theZone.msgCoalition, msg, theZone.duration, theZone.clearScreen) + trigger.action.outSoundForCoalition(theZone.msgCoalition, fileName) else -- out to all trigger.action.outText(msg, theZone.duration, theZone.clearScreen) @@ -132,12 +162,34 @@ function messenger.update() if currTriggerVal ~= aZone.lastMessageTriggerValue then if messenger.verbose then - trigger.action.outText("+++msgr: triggered on in?", 30) + trigger.action.outText("+++msgr: triggered on in? for <".. aZone.name ..">", 30) end messenger.isTriggered(aZone) aZone.lastMessageTriggerValue = cfxZones.getFlagValue(aZone.triggerMessagerFlag, aZone) -- trigger.misc.getUserFlag(aZone.triggerMessagerFlag) -- save last value end end + + if aZone.messageOffFlag then + local currVal = cfxZones.getFlagValue(aZone.messageOffFlag, aZone) + if currVal ~= aZone.lastMessageOff then + aZone.messageOff = true + aZone.lastMessageOff = currVal + if messenger.verbose then + trigger.action.outText("+++msg: messenger <" .. aZone.name .. "> turned ***OFF***", 30) + end + end + end + + if aZone.messageOnFlag then + local currVal = cfxZones.getFlagValue(aZone.messageOnFlag, aZone) + if currVal ~= aZone.lastMessageOn then + aZone.messageOff = false + aZone.lastMessageOn = currVal + if messenger.verbose then + trigger.action.outText("+++msg: messenger <" .. aZone.name .. "> turned ON", 30) + end + end + end end end @@ -176,7 +228,7 @@ function messenger.start() -- process cloner Zones local attrZones = cfxZones.getZonesWithAttributeNamed("messenger") for k, aZone in pairs(attrZones) do - messenger.createMessengerDownWithZone(aZone) -- process attributes + messenger.createMessengerWithZone(aZone) -- process attributes messenger.addMessenger(aZone) -- add to list end diff --git a/modules/unitZone.lua b/modules/unitZone.lua new file mode 100644 index 0000000..a616876 --- /dev/null +++ b/modules/unitZone.lua @@ -0,0 +1,272 @@ +unitZone={} +unitZone.version = "1.0.0" +unitZone.verbose = false +unitZone.ups = 1 +unitZone.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +unitZone.unitZones = {} + +function unitZone.addUnitZone(theZone) + table.insert(unitZone.unitZones, theZone) +end + +function unitZone.getUnitZoneByName(aName) + for idx, aZone in pairs(unitZone.unitZones) do + if aName == aZone.name then return aZone end + end + if unitZone.verbose then + trigger.action.outText("+++uZne: no unitZone with name <" .. aName ..">", 30) + end + + return nil +end + +function unitZone.string2cat(filterString) + + if not filterString then return 2 end -- default ground + filterString = filterString:lower() + filterString = dcsCommon.trim(filterString) + + local catNum = tonumber(filterString) + if catNum then + if catNum < 0 then catNum = 0 end + if catNum > 4 then catNum = 4 end + return catNum + end + + catNum = 2 -- ground default + if dcsCommon.stringStartsWith(filterString, "grou") then catNum = 2 end + if dcsCommon.stringStartsWith(filterString, "air") then catNum = 0 end + if dcsCommon.stringStartsWith(filterString, "hel") then catNum = 1 end + if dcsCommon.stringStartsWith(filterString, "shi") then catNum = 3 end + if dcsCommon.stringStartsWith(filterString, "trai") then catNum = 4 end + + return catNum +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 = dcsCommon.removeEnding(theZone.lookFor, "*") + end + + theZone.matching = cfxZones.getStringFromZoneProperty(theZone, "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 + cfxZones.uzCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "uzCoalition", 0) + end + + theZone.enterZone = cfxZones.getStringFromZoneProperty(theZone, "enterZone!", "") + theZone.exitZone = cfxZones.getStringFromZoneProperty(theZone, "exitZone!", "") + theZone.changeZone = cfxZones.getStringFromZoneProperty(theZone, "changeZone!", "") + + if cfxZones.hasProperty(theZone, "filterFor") then + local filterString = cfxZones.getStringFromZoneProperty(theZone, "filterFor", "1") -- ground + theZone.filterFor = unitZone.string2cat(filterString) + if unitZone.verbose then + trigger.action.outText("+++uZne: filtering " .. theZone.filterFor .. " in " .. theZone.name, 30) + end + end + + -- now get initial zone status ? + theZone.lastStatus = unitZone.checkZoneStatus(theZone) + if unitZone.verbose then + trigger.action.outText("+++uZne: processsed unit zone " .. theZone.name, 30) + end +end + + +-- +-- process zone +-- + +function unitZone.collectGroups(theZone) + local collector = {} + if theZone.matching == "player" then + -- collect all players matching coalition + if theZone.uzCoalition == 1 or theZone.uzCoalition == 0 then + local allPlayers = coalition.getPlayers(1) + for idx, pUnit in pairs(allPlayers) do + table.insert(collector, pUnit) + end + end + if theZone.uzCoalition == 2 or theZone.uzCoalition == 0 then + local allPlayers = coalition.getPlayers(2) + for idx, pUnit in pairs(allPlayers) do + table.insert(collector, pUnit) + end + end + 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) + 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) + end + end + else + trigger.action.outText("+++uZne: unknown matching: " .. theZone.matching, 30) + return {} + end + + return collector +end + +function unitZone.checkZoneStatus(theZone) + -- returns true (at least one unit found in zone) + -- or false (no unit found in zone) + + -- collect all groups to inspect + local theGroups = unitZone.collectGroups(theZone) + local lookFor = theZone.lookFor + -- now see if the groups match name and then check inside status for each + local playerCheck = theZone.matching == "player" + if playerCheck then + -- we check the names for players only + 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 + if hasMatch then + if cfxZones.unitInZone(pUnit, theZone) then + return true + end + end + end + + else + -- we perform group cehck + 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 + 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 + end + end + end + end + return false +end + +-- +-- update +-- +function unitZone.bangState(theZone, newState) + + cfxZones.pollFlag(theZone.changeZone, "inc", theZone) + if newState then + cfxZones.pollFlag(theZone.enterZone, "inc", theZone) + if unitZone.verbose then + trigger.action.outText("+++uZone: banging enter! on <" .. theZone.enterZone .. "> for " .. theZone.name, 30) + end + else + cfxZones.pollFlag(theZone.exitZone, "inc", theZone) + if unitZone.verbose then + trigger.action.outText("+++uZone: banging exit! on <" .. theZone.exitZone .. "> for " .. theZone.name, 30) + end + end +end + +function unitZone.update() + -- call me in a second to poll triggers + timer.scheduleFunction(unitZone.update, {}, timer.getTime() + 1/unitZone.ups) + + for idx, aZone in pairs(unitZone.unitZones) do + -- scan all zones + local newState = unitZone.checkZoneStatus(aZone) + + if newState ~= aZone.lastStatus then + -- bang on change! + unitZone.bangState(aZone, newState) + aZone.lastStatus = newState + end + end +end + +-- +-- Config & Start +-- +function unitZone.readConfigZone() + local theZone = cfxZones.getZoneByName("unitZoneConfig") + if not theZone then + if unitZone.verbose then + trigger.action.outText("+++uZne: NO config zone!", 30) + end + return + end + + unitZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + + unitZone.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1) + + if unitZone.verbose then + trigger.action.outText("+++uZne: read config", 30) + end +end + +function unitZone.start() + -- lib check + if not dcsCommon.libCheck then + trigger.action.outText("cfx Unit Zone requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx Unit Zone", unitZone.requiredLibs) then + return false + end + + -- read config + unitZone.readConfigZone() + + -- process cloner Zones + local attrZones = cfxZones.getZonesWithAttributeNamed("unitZone") + for k, aZone in pairs(attrZones) do + unitZone.createUnitZone(aZone) -- process attributes + unitZone.addUnitZone(aZone) -- add to list + end + + -- start update + unitZone.update() + + trigger.action.outText("cfx Unit Zone v" .. unitZone.version .. " started.", 30) + return true +end + +-- let's go! +if not unitZone.start() then + trigger.action.outText("cfx Unit Zone aborted: missing libraries", 30) + unitZone = nil +end + +--ToDo: add 'neutral' support and add 'both' option +--ToDo: add API \ No newline at end of file diff --git a/modules/xFlags.lua b/modules/xFlags.lua new file mode 100644 index 0000000..d20cc4c --- /dev/null +++ b/modules/xFlags.lua @@ -0,0 +1,306 @@ +xFlags = {} +xFlags.version = "1.0.0" +xFlags.verbose = false +xFlags.ups = 1 -- overwritten in get config! +xFlags.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +--[[-- + xFlags - flag array transmogrifier + + Version History + 1.0.0 - Initial version +--]]-- +xFlags.xFlagZones = {} + +function xFlags.addxFlags(theZone) + table.insert(xFlags.xFlagZones, theZone) +end +-- +-- create xFlag +-- +function xFlags.reset() + for i = 1, #theZone.flagNames do + -- since the checksum is order dependent, + -- we must preserve the order of the array + local flagName = theZone.flagNames[i] + theZone.startFlagValues[i] = cfxZones.getFlagValue(flagName, theZone) + theZone.flagResults[i] = false + theZone.flagChecksum = theZone.flagChecksum .. "0" + trigger.action.outText("+++xF: flag " .. flagName, 30) + end + theZone.xHasFired = false +end + +function xFlags.createXFlagsWithZone(theZone) + local theArray = "" + if cfxZones.hasProperty(theZone, "xFlags") then + theArray = cfxZones.getStringFromZoneProperty(theZone, "xFlags", "") + else + theArray = cfxZones.getStringFromZoneProperty(theZone, "xFlags?", "") + end + + -- now process the array and create the value arrays + theZone.flagNames = cfxZones.flagArrayFromString(theArray) + theZone.startFlagValues = {} -- reference/reset time we read these + theZone.flagResults = {} -- individual flag check result + theZone.flagChecksum = "" -- to detect change. is either '0' or 'X' + + for i = 1, #theZone.flagNames do + -- since the checksum is order dependent, + -- we must preserve the order of the array + local flagName = theZone.flagNames[i] + theZone.startFlagValues[i] = cfxZones.getFlagValue(flagName, theZone) + theZone.flagResults[i] = false + theZone.flagChecksum = theZone.flagChecksum .. "0" + trigger.action.outText("+++xF: flag " .. flagName, 30) + end + theZone.xHasFired = false + + theZone.xSuccess = cfxZones.getStringFromZoneProperty(theZone, "xSuccess!", "") + if cfxZones.hasProperty(theZone, "out!") then + theZone.xSuccess = cfxZones.getStringFromZoneProperty(theZone, "out!", "") + end + + if cfxZones.hasProperty(theZone, "xChange!") then + theZone.xChange = cfxZones.getStringFromZoneProperty(theZone, "xChange!", "") + end + theZone.inspect = cfxZones.getStringFromZoneProperty(theZone, "require", "or") -- same as any + -- supported any/or, all/and, moreThan, atLeast, exactly + theZone.inspect = string.lower(theZone.inspect) + theZone.inspect = dcsCommon.trim(theZone.inspect) + + theZone.matchNum = cfxZones.getNumberFromZoneProperty(theZone, "#hits", 0) + + theZone.lookFor = cfxZones.getStringFromZoneProperty(theZone, "lookFor", "change") -- (<>=[number or reference flag], off, on, yes, no, true, false, change + theZone.lookFor = string.lower(theZone.lookFor) + theZone.lookFor = dcsCommon.trim(theZone.lookFor) + + if cfxZones.hasProperty(theZone, "xReset?") then + theZone.xReset = cfxZones.getStringFromZoneProperty(theZone, "xReset?", "") + theZone.xLastReset = cfxZones.getFlagValue(theZone.xReset, theZone) + end + + theZone.xMethod = cfxZones.getStringFromZoneProperty(theZone, "xMethod", "flip") + if cfxZones.hasProperty(theZone, "method") then + theZone.xMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "flip") + end + + theZone.xOneShot = cfxZones.getBoolFromZoneProperty(theZone, "oneShot", true) + +end + +function xFlags.evaluateFlags(theZone) + local currVals = {} + -- read new values + for i = 1, #theZone.flagNames do + -- since the checksum is order dependent, + -- we must preserve the order of the array + local flagName = theZone.flagNames[i] + currVals[i] = cfxZones.getFlagValue(flagName, theZone) + + end + + -- now perform comparison flag by flag + local op = theZone.lookFor + local hits = 0 + local checkSum = "" + local firstChar = string.sub(op, 1, 1) + local remainder = string.sub(op, 2) + local rNum = tonumber(remainder) + + for i = 1, #theZone.flagNames do + local lastHits = hits + if op == "change" then + -- look for a change in flag line + if currVals[i] ~= theZone.startFlagValues[i] then + hits = hits + 1 + checkSum = checkSum .. "X" + else + checkSum = checkSum .. "0" + end + elseif op == "on" or op == "yes" or op == "true" then + if currVals[i] ~= 0 then + hits = hits + 1 + checkSum = checkSum .. "X" + else + checkSum = checkSum .. "0" + end + elseif op == "off" or op == "no" or op == "false" + then + if currVals[i] == 0 then + hits = hits + 1 + checkSum = checkSum .. "X" + else + checkSum = checkSum .. "0" + end + + elseif firstChar == "<" and rNum then + if currVals[i] < rNum then + hits = hits + 1 + checkSum = checkSum .. "X" + else + checkSum = checkSum .. "0" + end + + elseif firstChar == "=" and rNum then + if currVals[i] == rNum then + hits = hits + 1 + checkSum = checkSum .. "X" + else + checkSum = checkSum .. "0" + end + + elseif firstChar == ">" and rNum then + if currVals[i] > rNum then + hits = hits + 1 + checkSum = checkSum .. "X" + else + checkSum = checkSum .. "0" + end + + else + trigger.action.outText("+++xF: unknown lookFor: <" .. op .. ">", 30) + return 0, "" + end + if xFlags.verbose and lastHits ~= hits then + --trigger.action.outText("+++xF: hit detected for " .. theZone.flagNames[i] .. " in " .. theZone.name .. "(" .. op .. ")", 30) + end + end + return hits, checkSum +end + +function xFlags.evaluateZone(theZone) + + local hits, checkSum = xFlags.evaluateFlags(theZone) + -- depending on inspect see what the outcome is + -- supported any/or, all/and, moreThan, atLeast, exactly + local op = theZone.inspect + local evalResult = false + if (op == "or" or op == "any") and hits > 0 then + evalResult = true + elseif (op == "and" or op == "all") and hits == #theZone.flagNames then + evalResult = true + elseif (op == "morethan" or op == "more than") and hits > theZone.matchNum then + evalResult = true + elseif (op == "atleast" or op == "at lest") and hits >= theZone.matchNum then + evalResult = true + elseif op == "exactly" and hits == theZone.matchNum then + evalResult = true + end + + -- now check if changed and if result true + if checkSum ~= theZone.flagChecksum then + if xFlags.verbose then + trigger.action.outText("+++xFlag: change detected for " .. theZone.name .. ": " .. theZone.flagChecksum .. "-->" ..checkSum, 30) + end + + if theZone.xChange then + cfxZones.pollFlag(theZone.xChange, theZone.xMethod, theZone) + if xFlags.verbose then + trigger.action.outText("+++xFlag: change bang! on " .. theZone.xChange .. " for " .. theZone.name, 30) + end + end + theZone.flagChecksum = checkSum + end + + if theZone.xHasFired and theZone.xOneShot then return end + if evalResult then + if xFlags.verbose then + trigger.action.outText("+++xFlag: success bang! on " .. theZone.xSuccess .. " for " .. theZone.name, 30) + end + cfxZones.pollFlag(theZone.xSuccess, theZone.xMethod, theZone) + theZone.xHasFired = true + end +end + +-- +-- Update +-- +function xFlags.update() + timer.scheduleFunction(xFlags.update, {}, timer.getTime() + 1/xFlags.ups) + + for idx, theZone in pairs (xFlags.xFlagZones) do + -- see if they should fire + xFlags.evaluateZone(theZone) + + -- see if they should reset + if theZone.xReset then + local currVal = cfxZones.getFlagValue(theZone.xReset, theZone) + if currVal ~= theZone.xLastReset then + theZone.xLastReset = currVal + if xFlags.verbose then + trigger.action.outText("+++xF: reset command for " .. theZone.name, 30) + end + xFlags.reset(theZone) + end + end + end +end +-- +-- start +-- +function xFlags.readConfigZone() + -- note: must match exactly!!!! + local theZone = cfxZones.getZoneByName("xFlagsConfig") + if not theZone then + if xFlags.verbose then + trigger.action.outText("***xFlg: NO config zone!", 30) + end + return + end + + xFlags.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + xFlags.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1) + + if xFlags.verbose then + trigger.action.outText("***xFlg: read config", 30) + end +end + +function xFlags.start() + -- lib check + if not dcsCommon.libCheck then + trigger.action.outText("xFlags requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx xFlags", + xFlags.requiredLibs) then + return false + end + + -- read config + xFlags.readConfigZone() + + -- process RND Zones + local attrZones = cfxZones.getZonesWithAttributeNamed("xFlags") + + -- now create an rnd gen for each one and add them + -- to our watchlist + for k, aZone in pairs(attrZones) do + xFlags.createXFlagsWithZone(aZone) -- process attribute and add to zone + xFlags.addxFlags(aZone) -- remember it + end + + local attrZones = cfxZones.getZonesWithAttributeNamed("xFlags?") + + -- now create an rnd gen for each one and add them + -- to our watchlist + for k, aZone in pairs(attrZones) do + xFlags.createXFlagsWithZone(aZone) -- process attribute and add to zone + xFlags.addxFlags(aZone) -- remember it + end + + -- start update + timer.scheduleFunction(xFlags.update, {}, timer.getTime() + 1/xFlags.ups) + + trigger.action.outText("cfx xFlags v" .. xFlags.version .. " started.", 30) + return true +end + +-- let's go! +if not xFlags.start() then + trigger.action.outText("cf/x xFlags aborted: missing libraries", 30) + xFlags = nil +end \ No newline at end of file diff --git a/tutorial & demo missions/demo - follow me!.miz b/tutorial & demo missions/demo - follow me!.miz new file mode 100644 index 0000000..e8c0a11 Binary files /dev/null and b/tutorial & demo missions/demo - follow me!.miz differ diff --git a/tutorial & demo missions/full - Island Defender.miz b/tutorial & demo missions/full - Island Defender.miz new file mode 100644 index 0000000..5d930e4 Binary files /dev/null and b/tutorial & demo missions/full - Island Defender.miz differ