diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index bb439de..3bb325c 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 0c21c42..a822e9a 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 71a31fb..069ffd7 100644 --- a/modules/RNDFlags.lua +++ b/modules/RNDFlags.lua @@ -109,7 +109,11 @@ function rndFlags.createRNDWithZone(theZone) if theZone.triggerFlag then - theZone.lastTriggerValue = cfxZones.getFlagValue(theZone.triggerFlag, theZone) --trigger.misc.getUserFlag(theZone.triggerFlag) -- save last value + theZone.lastTriggerValue = cfxZones.getFlagValue(theZone.triggerFlag, theZone) + if rndFlags.verbose or theZone.verbose then + trigger.action.outText("+++RND: randomizer in <" .. theZone:getName() .. "> triggers on flag <" .. theZone.triggerFlag .. ">", 30) + end + --trigger.misc.getUserFlag(theZone.triggerFlag) -- save last value end theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false) diff --git a/modules/airfield.lua b/modules/airfield.lua new file mode 100644 index 0000000..df02188 --- /dev/null +++ b/modules/airfield.lua @@ -0,0 +1,314 @@ +airfield = {} +airfield.version = "1.0.0" +airfield.requiredLibs = { + "dcsCommon", + "cfxZones", +} +airfield.verbose = false +airfield.myAirfields = {} -- indexed by name +airfield.farps = false + +--[[-- + This module generates signals when the nearest airfield changes hands, + can force the coalition of an airfield, and always provides the + current owner as a value + + Version History + 1.0.0 - initial release + +--]]-- + +-- +-- setting up airfield +-- +function airfield.createAirFieldFromZone(theZone) + local filterCat = 0 + if airfield.farps then filterCat = {0, 1} end -- bases and farps + local p = theZone:getPoint() + local theBase = dcsCommon.getClosestAirbaseTo(p, filterCat) + theZone.airfield = theBase + theZone.afName = theBase:getName() + if airfield.verbose then + trigger.action.outText("+++airF: zone <" .. theZone:getName() .. "> associates with <" .. theZone.afName .. ">", 30) + end + + -- methods + theZone.method = theZone:getStringFromZoneProperty("method", "inc") + theZone.triggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change") + + -- set zone's owner + theZone.owner = theBase:getCoalition() + + if theZone:hasProperty("red!") then + theZone.redCap = theZone:getStringFromZoneProperty("red!") + end + if theZone:hasProperty("blue!") then + theZone.blueCap = theZone:getStringFromZoneProperty("blue!") + end + + -- controlled capture? + if theZone:hasProperty("makeRed?") then + theZone.makeRed = theZone:getStringFromZoneProperty("makeRed?", "") + theZone.lastMakeRed = trigger.misc.getUserFlag(theZone.makeRed) + end + if theZone:hasProperty("makeBlue?") then + theZone.makeBlue = theZone:getStringFromZoneProperty("makeBlue?", "") + theZone.lastMakeBlue = trigger.misc.getUserFlag(theZone.makeBlue) + end + + if theZone:hasProperty("autoCap?") then + theZone.autoCap = theZone:getStringFromZoneProperty("autoCap?", "") + theZone.lastAutoCap = trigger.misc.getUserFlag(theZone.autoCap) + end + + theZone.directControl = theZone:getBoolFromZoneProperty("directControl", false) + + if theZone.directControl then + airfield.assumeControl(theZone) + end + + if theZone:hasProperty("ownedBy#") then + theZone.ownedBy = theZone:getStringFromZoneProperty("ownedBy#", "") + trigger.action.setUserFlag(theZone.ownedBy, theZone.owner) + end + + -- index by name, and warn if duplicate associate + if airfield.myAirfields[theZone.afName] then + trigger.action.outText("+++airF: WARNING - zone <" .. theZone:getName() .. "> redefines airfield <" .. theZone.afName .. ">, discarded!", 30) + else + airfield.myAirfields[theZone.afName] = theZone + end +end + +function airfield.assumeControl(theZone) + theBase = theZone.airfield + if airfield.verbose or theZone.verbose then + trigger.action.outText("+++airF: assuming direct control and turning off auto-capture for <" .. theZone.afName .. ">, now controlled by zone <" .. theZone:getName() .. ">", 30) + end + theBase:autoCapture(false) -- turn off autocap +end + +function airfield.relinquishControl(theZone) + theBase = theZone.airfield + if airfield.verbose or theZone.verbose then + trigger.action.outText("+++airF: zone <" .. theZone:getName() .. "> relinquishing ownership control over <" .. theZone.afName .. ">, can be captured normally", 30) + end + theBase:autoCapture(true) -- turn off autocap +end + +-- +-- event handling +-- + +function airfield.airfieldCaptured(theBase) + -- retrieve the zone that controls this airfield + local bName = theBase:getName() + local theZone = airfield.myAirfields[bName] + if not theZone then return end -- not handled + local newCoa = theBase:getCoalition() + theZone.owner = newCoa + + if theZone.verbose or airfield.verbose then + trigger.action.outText("+++airF: handling capture event/command for airfield <" .. bName .. "> with zone <" .. theZone:getName() .. ">", 30) + end + + -- outputs + if theZone.ownedBy then + trigger.action.setUserFlag(theZone.ownedBy, theZone.owner) + end + if theZone.redCap and newCoa == 1 then + theZone:pollFlag(theZone.redCap, theZone.method) + end + if theZone.blueCap and newCoa == 2 then + theZone:pollFlag(theZone.blueCap, theZone.method) + end + +end + +function airfield:onEvent(event) + if not event then return end + if event.id == 10 then -- S_EVENT_BASE_CAPTURED + local theBase = event.place + if not theBase then + trigger.action.outText("+++airF: error: cap event without base", 30) + return + end + -- get category + local desc = theBase:getDesc() + local bName = theBase:getName() + local cat = desc.category -- never get cat directly! + if cat == 1 then + if not airfield.farps then + if airfield.verbose then + trigger.action.outText("+++airF: ignored cap event for FARP <" .. bName .. ">", 30) + end + return + end + end + if airfield.verbose then + trigger.action.outText("+++airF: cap event for <" .. bName .. ">, cat = (" .. cat .. ")", 30) + end + airfield.airfieldCaptured(theBase) + end +end + +-- +-- update +-- + +function airfield.update() + timer.scheduleFunction(airfield.update, {}, timer.getTime() + 1) + for afName, theZone in pairs(airfield.myAirfields) do + local theAirfield = theZone.airfield + if theZone.makeRed and theZone:testZoneFlag(theZone.makeRed, theZone.triggerMethod, "lastMakeRed") then + if theZone.verbose or airfield.verbose then + trigger.action.outText("+++airF: 'makeRed' triggered for airfield <" .. afName .. "> in zone <" .. theZone:getName() .. ">", 30) + end + if theAirfield:autoCaptureIsOn() then + -- turn off autoCap + airfield.assumeControl(theZone) + end + theAirfield:setCoalition(1) -- make it red, doesn't trigger event + if theZone.owner ~= 1 then -- only send cap event when capped + airfield.airfieldCaptured(theAirfield) + end + end + + if theZone.makeBlue and theZone:testZoneFlag(theZone.makeBlue, theZone.triggerMethod, "lastMakeBlue") then + if theZone.verbose or airfield.verbose then + trigger.action.outText("+++airF: 'makeBlue' triggered for airfield <" .. afName .. "> in zone <" .. theZone:getName() .. ">", 30) + end + if theAirfield:autoCaptureIsOn() then + -- turn off autoCap + airfield.assumeControl(theZone) + end + theAirfield:setCoalition(2) -- make it blue + if theZone.owner ~= 2 then -- only send cap event when capped + airfield.airfieldCaptured(theAirfield) + end + end + + if theZone.autoCap and theZone:testZoneFlag(theZone.autoCap, theZone.triggerMethod, "lastAutoCap") then + if theAirfield:autoCaptureIsOn() then + -- do nothing + else + airfield.relinquishControl(theZone) + end + end + end +end + +-- +-- LOAD / SAVE +-- + +function airfield.saveData() + local theData = {} + local allAF = {} + for name, theZone in pairs(airfield.myAirfields) do + local theName = name + local theAirfield = theZone.airfield + local AFData = {} + AFData.autocapActive = theAirfield:autoCaptureIsOn() + AFData.owner = theZone.owner + allAF[theName] = AFData + end + theData.allAF = allAF + return theData +end + +function airfield.loadData() + if not persistence then return end + local theData = persistence.getSavedDataForModule("airfield") + if not theData then + if airfield.verbose then + trigger.action.outText("+++airF persistence: no save data received, skipping.", 30) + end + return + end + + local allAF = theData.allAF + if not allAF then + if airfield.verbose then + trigger.action.outText("+++airF persistence: no airfield data, skipping", 30) + end + return + end + + for theName, AFData in pairs(allAF) do + local theZone = airfield.myAirfields[theName] + if theZone then + -- synch airfield ownership, and auto-capture status + local theAirfield = theZone.airfield + -- set current owner + theAirfield:autoCapture(false) + theAirfield:setCoalition(AFData.owner) + theZone.owner = AFData.owner + -- set ownedBy# + if theZone.ownedBy then + trigger.action.setUserFlag(theZone.ownedBy, theZone.owner) + end + -- set owning mode: autocap or direct + theAirfield:autoCapture(AFData.autocapActive) + + else + trigger.action.outText("+++airF persistence: cannot synch airfield <" .. theName .. ">, skipping", 40) + end + end +end + + +-- +-- start up +-- +function airfield.readConfig() + local theZone = cfxZones.getZoneByName("airfieldConfig") + if not theZone then + theZone = cfxZones.createSimpleZone("airfieldConfig") + end + airfield.verbose = theZone.verbose + airfield.farps = theZone:getBoolFromZoneProperty("farps", false) + +end + +function airfield.start() + if not dcsCommon.libCheck("cfx airfield", airfield.requiredLibs) + then return false end + + -- read config + airfield.readConfig() + + -- read bases + local abZones = cfxZones.zonesWithProperty("airfield") + for idx, aZone in pairs(abZones) do + airfield.createAirFieldFromZone(aZone) + end + + -- connect event handler + world.addEventHandler(airfield) + + -- load any saved data + if persistence then + -- sign up for persistence + callbacks = {} + callbacks.persistData = airfield.saveData + persistence.registerModule("airfield", callbacks) + -- now load my data + airfield.loadData() + end + + -- start update in 1 second + timer.scheduleFunction(airfield.update, {}, timer.getTime() + 1) + + trigger.action.outText("cfx airfield v" .. airfield.version .. " loaded.", 30) + return true +end + +if not airfield.start() then + trigger.action.outText("+++ aborted airfield v" .. airfield.version .. " -- startup failed", 30) + airfield = nil +end + +--[[-- + ideas: what if we made this airfield movable? +--]]-- \ No newline at end of file diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index d25ad99..437c8e5 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -1,5 +1,5 @@ cfxZones = {} -cfxZones.version = "4.0.2" +cfxZones.version = "4.0.3" -- cf/x zone management module -- reads dcs zones and makes them accessible and mutable @@ -151,6 +151,8 @@ cfxZones.version = "4.0.2" negative numbers, backwards compatibility with old (dysfunctional) method - 4.0.1 - dmlZone:getName() - 4.0.2 - removed verbosity from declutterZone (both versions) +- 4.0.3 - new processDynamicVZU() + - wildcard uses processDynamicVZU --]]-- @@ -3101,6 +3103,41 @@ function cfxZones.processDynamicLoc(inMsg, imperialUnits, responses) return outMsg end +-- process reference that can be flag, Zone, or unit. +-- i.e. +function cfxZones.processDynamicVZU(inMsg) +local locales = {"coa",} + local outMsg = inMsg + local uHead = 0 + for idx, aLocale in pairs(locales) do + local pattern = "<" .. aLocale .. ":%s*[%s%w%*%d%.%-_]+>" + 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, ">","") + -- find zone or unit + param = dcsCommon.trim(param) + local tZone = cfxZones.getZoneByName(param) + local tUnit = Unit.getByName(param) + + local locString = "err" + if aLocale == "coa" then + coa = trigger.misc.getUserFlag(param) + if tZone then coa = tZone.owner end + if zUnit then coa = tUnit:getCoalition() end + locString = dcsCommon.coalition2Text(coa) + 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.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 @@ -3148,7 +3185,8 @@ function cfxZones.processStringWildcards(inMsg, theZone, timeFormat, imperialUni theMsg = cfxZones.processDynamicTime(theMsg, theZone, timeFormat) -- process theMsg = cfxZones.processDynamicLoc(theMsg, imperialUnits, responses) - + -- process values that can be derived from flag (default), zone or unit + theMsg = cfxZones.processDynamicVZU(theMsg) return theMsg end diff --git a/modules/cloneZone.lua b/modules/cloneZone.lua index c320804..e322b22 100644 --- a/modules/cloneZone.lua +++ b/modules/cloneZone.lua @@ -1,5 +1,5 @@ cloneZones = {} -cloneZones.version = "1.8.0" +cloneZones.version = "1.8.2" cloneZones.verbose = false cloneZones.requiredLibs = { "dcsCommon", -- always @@ -98,6 +98,8 @@ cloneZones.respawnOnGroupID = true 1.8.0 - OOP cfxZones - removed "empty+1" as planned - upgraded config zone parsing + 1.8.1 - clone zone definition now supports quads + 1.8.2 - on pre-wipe, delay respawn by 0.5s to avoid 'dropping' statics --]]-- @@ -166,8 +168,9 @@ function cloneZones.partOfGroupDataInZone(theZone, theUnits) uP.x = aUnit.x uP.y = 0 uP.z = aUnit.y -- !! y-z - local dist = dcsCommon.dist(uP, zP) - if dist <= theZone.radius then return true end + --local dist = dcsCommon.dist(uP, zP) + --if dist <= theZone.radius then return true end + if theZone:pointInZone(uP) then return true end end return false end @@ -314,14 +317,7 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" theZone.moveRoute = theZone:getBoolFromZoneProperty("moveRoute", false) theZone.preWipe = theZone:getBoolFromZoneProperty("preWipe", false) - - -- to be deprecated - --[[-- - if theZone:hasProperty("empty+1") then - theZone.emptyFlag = theZone:getStringFromZoneProperty("empty+1", "") -- note string on number default - end - --]]-- - + if theZone:hasProperty("empty!") then theZone.emptyBangFlag = theZone:getStringFromZoneProperty("empty!", "") -- note string on number default end @@ -1542,9 +1538,11 @@ function cloneZones.spawnWithCloner(theZone) end -- pre-Wipe? + local didPrewipe = false if theZone.preWipe then cloneZones.despawnAll(theZone) cloneZones.invokeCallbacks(theZone, "wiped", {}) + didPrewipe = true end -- declutter? @@ -1555,6 +1553,19 @@ function cloneZones.spawnWithCloner(theZone) end end + local args = {theZone = theZone, templateZone = templateZone} + if didPrewipe then -- delay spawning to allow revoval to take place + timer.scheduleFunction(cloneZones.doClone, args, timer.getTime() + 0.5) + else -- can do immediately + cloneZones.doClone(args) + end + +end + +-- deferrable clone method. +function cloneZones.doClone(args) + local theZone = args.theZone + local templateZone = args.templateZone local theClones, theStatics = cloneZones.spawnWithTemplateForZone(templateZone, theZone) -- reset hasClones so we know our spawns are full and we can -- detect complete destruction @@ -1569,7 +1580,7 @@ function cloneZones.spawnWithCloner(theZone) theZone.mySpawns = {} theZone.myStatics = {} end -end +end function cloneZones.countLiveUnits(theZone) if not theZone then return 0 end diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index b6344c6..5fb8c21 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.9.2" +dcsCommon.version = "2.9.3" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -171,8 +171,8 @@ dcsCommon.version = "2.9.2" - new bearingFromAtoBusingXY() - corrected verbosity for bearingFromAtoB - new getCountriesForCoalition() -2.9.2 - updated task2text - +2.9.2 - updated event2text +2.9.3 - getAirbasesWhoseNameContains now supports category tables for filtering --]]-- -- dcsCommon is a library of common lua functions @@ -495,7 +495,8 @@ dcsCommon.version = "2.9.2" -- getAirbasesWhoseNameContains - get all airbases containing -- a name. filterCat is optional and can be aerodrome (0), farp (1), ship (2) - -- filterCoalition is optional and can be 0 (neutral), 1 (red), 2 (blue) + -- filterCoalition is optional and can be 0 (neutral), 1 (red), 2 (blue) or + -- a table containing categories, e.g. {0, 2} = airfields and ships but not farps -- if no name given or aName = "*", then all bases are returned prior to filtering function dcsCommon.getAirbasesWhoseNameContains(aName, filterCat, filterCoalition) --trigger.action.outText("getAB(name): enter with " .. aName, 30) @@ -510,11 +511,20 @@ dcsCommon.version = "2.9.2" --if aName ~= "*" then -- trigger.action.outText("getAB(name): matched " .. airBaseName, 30) --end - local doAdd = true + local doAdd = true if filterCat then - -- make sure the airbase is of that category - local airCat = dcsCommon.getAirbaseCat(aBase) - doAdd = doAdd and airCat == filterCat + local aCat = dcsCommon.getAirbaseCat(aBase) + if type(filterCat) == "table" then + local hit = false + for idx, fCat in pairs(filterCat) do + if fCat == aCat then hit = true end + end + doAdd = doAdd and hit + else + -- make sure the airbase is of that category + local airCat = aCat + doAdd = doAdd and airCat == filterCat + end end if filterCoalition then diff --git a/modules/noGap.lua b/modules/noGap.lua index 43fb8ea..c4b5c7d 100644 --- a/modules/noGap.lua +++ b/modules/noGap.lua @@ -389,7 +389,7 @@ function noGap.start() -- connect event handler world.addEventHandler(noGap) - -- start update in 10 seconds + -- start update in 1 second timer.scheduleFunction(noGap.update, {}, timer.getTime() + 1) -- say hi! diff --git a/tutorial & demo missions/demo - No gap, no glory.miz b/tutorial & demo missions/demo - No gap, no glory.miz index 499f81b..33ef51a 100644 Binary files a/tutorial & demo missions/demo - No gap, no glory.miz and b/tutorial & demo missions/demo - No gap, no glory.miz differ diff --git a/tutorial & demo missions/demo - No gap, no problem.miz b/tutorial & demo missions/demo - No gap, no problem.miz new file mode 100644 index 0000000..17bdea6 Binary files /dev/null and b/tutorial & demo missions/demo - No gap, no problem.miz differ diff --git a/tutorial & demo missions/demo - airbase mine.miz b/tutorial & demo missions/demo - airbase mine.miz new file mode 100644 index 0000000..4f98756 Binary files /dev/null and b/tutorial & demo missions/demo - airbase mine.miz differ