diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index a2c501e..3807a5c 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 80ba005..6146065 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/baseCaptured.lua b/modules/baseCaptured.lua index 44174ed..5128277 100644 --- a/modules/baseCaptured.lua +++ b/modules/baseCaptured.lua @@ -1,5 +1,5 @@ baseCaptured={} -baseCaptured.version = "1.0.1" +baseCaptured.version = "1.0.2" baseCaptured.verbose = false baseCaptured.ups = 1 baseCaptured.requiredLibs = { @@ -15,6 +15,8 @@ baseCaptured.handleContested = true -- 1.0.0 - Initial version based on cloose's code 1.0.1 - contested! flag - update and handleContested + 1.0.2 - expanded verbosity + - typo in defining redCaptured! corrected --]]-- @@ -47,7 +49,7 @@ function baseCaptured.createZone(theZone) end if cfxZones.hasProperty(theZone, "redCaptured!") then - theZone.redCap = cfxZones.getStringFromZoneProperty(theZone, "blueCaptured!", "*none") + theZone.redCap = cfxZones.getStringFromZoneProperty(theZone, "redCaptured!", "*none") end if cfxZones.hasProperty(theZone, "red!") then @@ -81,10 +83,16 @@ function baseCaptured.triggerZone(theZone) if newOwner == 1 then -- red if theZone.redCap then cfxZones.pollFlag(theZone.redCap, theZone.capturedMethod, theZone) + if baseCaptured.verbose or theZone.verbose then + trigger.action.outText("+++bCap: banging redCap! with <" .. theZone.redCap .. "> for zone <" .. theZone.name .. ">", 30) + end end elseif newOwner == 2 then if theZone.blueCap then cfxZones.pollFlag(theZone.blueCap, theZone.capturedMethod, theZone) + if baseCaptured.verbose or theZone.verbose then + trigger.action.outText("+++bCap: banging blueCap! with <" .. theZone.blueCap .. "> for zone <" .. theZone.name .. ">", 30) + end end else -- contested diff --git a/modules/cfxSpawnZones.lua b/modules/cfxSpawnZones.lua index 499e806..675782a 100644 --- a/modules/cfxSpawnZones.lua +++ b/modules/cfxSpawnZones.lua @@ -1,5 +1,5 @@ cfxSpawnZones = {} -cfxSpawnZones.version = "1.7.0" +cfxSpawnZones.version = "1.7.1" cfxSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -61,6 +61,8 @@ cfxSpawnZones.spawnedGroups = {} -- 1.5.3 - spawn?, spawnUnits? flags -- 1.6.0 - trackwith interface for group tracker -- 1.7.0 - persistence support +-- 1.7.1 - improved verbosity +-- - spelling check -- -- new version requires cfxGroundTroops, where they are -- @@ -230,6 +232,10 @@ function cfxSpawnZones.createSpawner(inZone) theSpawner.target = nil end + if cfxSpawnZones.verbose or inZone.verbose then + trigger.action.outText("+++spwn: created spawner for <" .. inZone.name .. ">", 30) + end + return theSpawner end @@ -326,6 +332,10 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner) if not aSpawner then return end local theZone = aSpawner.zone -- retrieve the zone that defined me + if cfxSpawnZones.verbose or theZone.verbose then + trigger.action.outText("+++spwn: started spawn with spawner for <" .. theZone.name .. ">", 30) + end + -- will NOT check if conditions are met. This forces a spawn local unitTypes = {} -- build type names --local p = aSpawner.zone.point diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index e55667c..6c22ecb 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -1,5 +1,5 @@ cfxZones = {} -cfxZones.version = "2.9.0" +cfxZones.version = "2.9.1" -- cf/x zone management module -- reads dcs zones and makes them accessible and mutable @@ -106,6 +106,9 @@ cfxZones.version = "2.9.0" - linkUnit works for late-activating units - linkUnit now also works for player / clients, dynamic (re-)linking - linkUnit uses zone's origin for all calculations +- 2.9.1 - new evalRemainder() + - pollFlag supports +/- for immediate numbers, flags, number flags in parantheses + - stronger guards in hasProperty --]]-- cfxZones.verbose = false @@ -1186,6 +1189,35 @@ function cfxZones.unPulseFlag(args) cfxZones.setFlagValue(theFlag, newVal, theZone) end +function cfxZones.evalRemainder(remainder) + local rNum = tonumber(remainder) + if not rNum then + -- we use remainder as name for flag + -- PROCESS ESCAPE SEQUENCES + local esc = string.sub(remainder, 1, 1) + local last = string.sub(remainder, -1) + if esc == "@" then + remainder = string.sub(remainder, 2) + remainder = dcsCommon.trim(remainder) + end + + if esc == "(" and last == ")" and string.len(remainder) > 2 then + -- note: iisues with startswith("(") ??? + remainder = string.sub(remainder, 2, -2) + remainder = dcsCommon.trim(remainder) + end + if esc == "\"" and last == "\"" and string.len(remainder) > 2 then + remainder = string.sub(remainder, 2, -2) + remainder = dcsCommon.trim(remainder) + end + if cfxZones.verbose then + trigger.action.outText("+++zne: accessing flag <" .. remainder .. ">", 30) + end + rNum = cfxZones.getFlagValue(remainder, theZone) + end + return rNum +end + function cfxZones.doPollFlag(theFlag, method, theZone) if cfxZones.verbose then trigger.action.outText("+++zones: polling flag " .. theFlag .. " with " .. method, 30) @@ -1198,14 +1230,19 @@ function cfxZones.doPollFlag(theFlag, method, theZone) method = method:lower() method = dcsCommon.trim(method) val = tonumber(method) - if val then - cfxZones.setFlagValue(theFlag, val, theZone) - if cfxZones.verbose or theZone.verbose then - trigger.action.outText("+++zones: flag <" .. theFlag .. "> changed to #" .. val, 30) + if dcsCommon.stringStartsWith(method, "+") or dcsCommon.stringStartsWith(method, "-") + then + -- skip this processing, a legal Lua val can start with "+" or "-" + -- but we interpret it as a method + else + if val then + cfxZones.setFlagValue(theFlag, val, theZone) + if cfxZones.verbose or theZone.verbose then + trigger.action.outText("+++zones: flag <" .. theFlag .. "> changed to #" .. val, 30) + end + return end - return - end - + end --trigger.action.outText("+++zones: polling " .. theZone.name .. " method " .. method .. " flag " .. theFlag, 30) local currVal = cfxZones.getFlagValue(theFlag, theZone) if method == "inc" or method == "f+1" then @@ -1233,6 +1270,21 @@ function cfxZones.doPollFlag(theFlag, method, theZone) elseif dcsCommon.stringStartsWith(method, "pulse") then cfxZones.pulseFlag(theFlag, method, theZone) + elseif dcsCommon.stringStartsWith(method, "+") then + -- we add whatever is to the right + local remainder = dcsCommon.removePrefix(method, "+") + local adder = cfxZones.evalRemainder(remainder) + cfxZones.setFlagValue(theFlag, currVal+adder, theZone) + if theZone.verbose then + trigger.action.outText("+++zones: (poll) updating with '+' flag <" .. theFlag .. "> in <" .. theZone.name .. "> by <" .. adder .. "> to <" .. adder + currVal .. ">", 30) + end + + elseif dcsCommon.stringStartsWith(method, "-") then + -- we subtract whatever is to the right + local remainder = dcsCommon.removePrefix(method, "-") + local adder = cfxZones.evalRemainder(remainder) + cfxZones.setFlagValue(theFlag, currVal-adder, theZone) + else if method ~= "on" and method ~= "f=1" then trigger.action.outText("+++zones: unknown method <" .. method .. "> - using 'on'", 30) @@ -1894,6 +1946,10 @@ function cfxZones.getPositiveRangeFromZoneProperty(theZone, theProperty, default end function cfxZones.hasProperty(theZone, theProperty) + if not theProperty then + trigger.action.outText("+++zne: WARNING - hasProperty called with nil theProperty for zone <" .. theZone.name .. ">", 30) + return false + end local foundIt = cfxZones.getZoneProperty(theZone, theProperty) if not foundIt then -- check for possible forgotten or exchanged IO flags diff --git a/modules/cfxmon.lua b/modules/cfxmon.lua index 5b349e0..1610d3d 100644 --- a/modules/cfxmon.lua +++ b/modules/cfxmon.lua @@ -1,9 +1,11 @@ cfxmon = {} -cfxmon.version = "1.0.0" +cfxmon.version = "1.0.1" cfxmon.delay = 30 -- seconds for display --[[-- Version History 1.0.0 - initial version + 1.0.1 - better guard for even.initiator to check if unit exists + - and handle static objcects and other non-grouped objects cfxmon is a monitor for all cfx events and callbacks use monConfig to tell cfxmon which events and callbacks @@ -36,17 +38,22 @@ function cfxmon.rejected(event) trigger.action.outText("***mon - dcsReject: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")", cfxmon.delay) end -function cfxmon.dcsCB(event) +function cfxmon.dcsCB(event) -- callback local initiatorStat = "" - if event.initiator then + if event.initiator and Unit.isExist(event.initiator) then local theUnit = event.initiator - local theGroup = theUnit:getGroup() + -- we assume it is unit, but it can be a static object, + -- and may not have getGroup implemented! + if theUnit.getGroup then + local theGroup = theUnit:getGroup() - local theGroupName = "" - if theGroup then theGroupName = theGroup:getName() end - - initiatorStat = ", for " .. theUnit:getName() - initiatorStat = initiatorStat .. " of " .. theGroupName + local theGroupName = "" + if theGroup then theGroupName = theGroup:getName() end + initiatorStat = ", for " .. theUnit:getName() + initiatorStat = initiatorStat .. " of " .. theGroupName + else + initiatorStat = ", non-unit (static?) " .. theUnit:getName() + end else initiatorStat = ", NO Initiator" end diff --git a/modules/changer.lua b/modules/changer.lua index 262b924..bd6201d 100644 --- a/modules/changer.lua +++ b/modules/changer.lua @@ -1,5 +1,5 @@ changer = {} -changer.version = "1.0.3" +changer.version = "1.0.4" changer.verbose = false changer.ups = 1 changer.requiredLibs = { @@ -13,6 +13,7 @@ changer.changers = {} 1.0.1 - Better guards in config to avoid Zone getter warning 1.0.2 - on/off: verbosity 1.0.3 - NOT on/off + 1.0.4 - a little bit more conversation Transmogrify an incoming signal to an output signal - not @@ -224,7 +225,7 @@ function changer.update() changer.process(aZone) else if changer.verbose or aZone.verbose then - trigger.action.outText("+++chgr: " .. aZone.name .. " gate closed.", 30) + trigger.action.outText("+++chgr: " .. aZone.name .. " gate closed [flag <" .. aZone.changerOnOff .. "> is 0].", 30) end end elseif aZone.changerOnOffINV then @@ -232,12 +233,16 @@ function changer.update() changer.process(aZone) else if changer.verbose or aZone.verbose then - trigger.action.outText("+++chgr: " .. aZone.name .. " gate closed.", 30) + trigger.action.outText("+++chgr: " .. aZone.name .. " gate closed [INVflag <" .. aZone.changerOnOffINV .. "> is 1].", 30) end end else changer.process(aZone) end + else + if aZone.verbose then + trigger.action.outText("+++chgr: <" .. aZone.name .. "> is paused.") + end end end end diff --git a/modules/counter.lua b/modules/counter.lua new file mode 100644 index 0000000..1b4441a --- /dev/null +++ b/modules/counter.lua @@ -0,0 +1,121 @@ +counter = {} +counter.version = "1.0.0" + +counter.verbose = false +counter.ups = 1 +counter.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +counter.counters = {} +--[[-- + Version History + 1.0.0 - Initial version + +--]]-- + +function counter.addCounter(theZone) + table.insert(counter.counters, theZone) +end + +function counter.getCounterByName(aName) + for idx, aZone in pairs(counter.counters) do + if aName == aZone.name then return aZone end + end + if counter.verbose then + trigger.action.outText("+++ctr: no counter with name <" .. aName ..">", 30) + end + + return nil +end + +function counter.createCounterWithZone(theZone) + theZone.counterInputFlag = cfxZones.getStringFromZoneProperty(theZone, "count?", "*") + theZone.lastCounterInputFlag = cfxZones.getFlagValue(theZone.counterInputFlag, theZone) + + -- triggerCounterMethod + theZone.triggerCounterMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") + if cfxZones.hasProperty(theZone, "triggerCountMethod") then + theZone.triggerCounterMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerChangeMethod", "change") + end + + theZone.countMethod = cfxZones.getStringFromZoneProperty(theZone, "countMethod", "+1") + if cfxZones.hasProperty(theZone, "method") then + theZone.countMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "+1") + end + + theZone.countOut = cfxZones.getStringFromZoneProperty(theZone, "out!", "") + if cfxZones.hasProperty(theZone, "countOut!") then + theZone.countOut = cfxZones.getStringFromZoneProperty(theZone, "countOut!", "") + end +end + +-- +-- Update +-- +function counter.update() + -- call me in a second to poll triggers + timer.scheduleFunction(counter.update, {}, timer.getTime() + 1/counter.ups) + + for idx, aZone in pairs(counter.counters) do + if cfxZones.testZoneFlag(aZone, aZone.counterInputFlag, aZone.triggerCounterMethod, "lastCounterInputFlag") then + cfxZones.pollFlag(aZone.countOut, aZone.countMethod, aZone) + end + + end +end + + +-- +-- Config & Start +-- +function counter.readConfigZone() + local theZone = cfxZones.getZoneByName("counterConfig") + if not theZone then + if counter.verbose then + trigger.action.outText("+++ctr: NO config zone!", 30) + end + theZone = cfxZones.createSimpleZone("counterConfig") -- temp only + end + + counter.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + + counter.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1) + + if counter.verbose then + trigger.action.outText("+++ctr: read config", 30) + end +end + +function counter.start() + -- lib check + if not dcsCommon.libCheck then + trigger.action.outText("cfx counter requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx counter", counter.requiredLibs) then + return false + end + + -- read config + counter.readConfigZone() + + -- process counter Zones + local attrZones = cfxZones.getZonesWithAttributeNamed("count?") + for k, aZone in pairs(attrZones) do + counter.createCounterWithZone(aZone) -- process attributes + counter.addCounter(aZone) -- add to list + end + + -- start update + counter.update() + + trigger.action.outText("cfx counter v" .. counter.version .. " started.", 30) + return true +end + +-- let's go! +if not counter.start() then + trigger.action.outText("cfx counter aborted: missing libraries", 30) + counter = nil +end \ No newline at end of file diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index 88fcd52..f685782 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.7.7" +dcsCommon.version = "2.7.9" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -109,6 +109,15 @@ dcsCommon.version = "2.7.7" - dumpVar2Str detects meta tables - rotateGroupData kills unit's psi value if it existed since it messes with heading - rotateGroupData - changes psi to -heading if it exists rather than nilling + 2.7.8 - new getGeneralDirection() + - new getNauticalDirection() + - more robust guards for getUnitSpeed + 2.7.9 - new bool2Num(theBool) + - new aspectByDirection() + - createGroundGroupWithUnits corrected spelling of minDist, crashed scattered formation + - randomPointInCircle fixed erroneous local for x, z + - "scattered" formation repaired + --]]-- -- dcsCommon is a library of common lua functions @@ -663,7 +672,9 @@ dcsCommon.version = "2.7.7" while direction < 0 do direction = direction + 360 end - + while direction >= 360 do + direction = direction - 360 + end if direction < 15 then -- special case 12 o'clock past 12 o'clock return 12 end @@ -673,6 +684,54 @@ dcsCommon.version = "2.7.7" end + function dcsCommon.getGeneralDirection(direction) -- inspired by cws, improvements my own + if not direction then return "unkown" end + direction = math.fmod (direction, 360) + while direction < 0 do + direction = direction + 360 + end + while direction >= 360 do + direction = direction - 360 + end + if direction < 45 then return "ahead" end + if direction < 135 then return "right" end + if direction < 225 then return "behind" end + if direction < 315 then return "left" end + return "ahead" + end + + function dcsCommon.getNauticalDirection(direction) -- inspired by cws, improvements my own + if not direction then return "unkown" end + direction = math.fmod (direction, 360) + while direction < 0 do + direction = direction + 360 + end + while direction >= 360 do + direction = direction - 360 + end + if direction < 45 then return "ahead" end + if direction < 135 then return "starboard" end + if direction < 225 then return "aft" end + if direction < 315 then return "port" end + return "ahead" + end + + function dcsCommon.aspectByDirection(direction) -- inspired by cws, improvements my own + if not direction then return "unkown" end + direction = math.fmod (direction, 360) + while direction < 0 do + direction = direction + 360 + end + while direction >= 360 do + direction = direction - 360 + end + + if direction < 45 then return "hot" end + if direction < 135 then return "beam" end + if direction < 225 then return "drag" end + if direction < 315 then return "beam" end + return "hot" + end function dcsCommon.randomDegrees() local degrees = math.random(360) * 3.14152 / 180 @@ -690,6 +749,8 @@ dcsCommon.version = "2.7.7" function dcsCommon.randomPointInCircle(sourceRadius, innerRadius, x, z) if not x then x = 0 end + if not z then z = 0 end + --local y = 0 if not innerRadius then innerRadius = 0 end if innerRadius < 0 then innerRadius = 0 end @@ -698,8 +759,8 @@ dcsCommon.version = "2.7.7" -- now lets get a random degree local degrees = dcsCommon.randomDegrees() -- math.random(360) * 3.14152 / 180 -- ok, it's actually radiants. local r = (sourceRadius-innerRadius) * percent - local x = x + (innerRadius + r) * math.cos(degrees) - local z = z + (innerRadius + r) * math.sin(degrees) + x = x + (innerRadius + r) * math.cos(degrees) + z = z + (innerRadius + r) * math.sin(degrees) local thePoint = {} thePoint.x = x @@ -1351,12 +1412,15 @@ dcsCommon.version = "2.7.7" local lowDist = 10000 local uPoint = {} local thePoint = {} - repeat -- get random point intil mindistance to all is kept or emergencybreak + repeat -- get random point until mindistance to all is kept or emergencybreak + thePoint = dcsCommon.randomPointInCircle(radius, innerRadius) -- returns x, 0, z + -- check if too close to others for idx, rUnit in pairs(processedUnits) do -- get min dist to all positioned units - thePoint = dcsCommon.randomPointInCircle(radius, innerRadius) -- returns x, 0, z + --trigger.action.outText("rPnt: thePoint = " .. dcsCommon.point2text(thePoint), 30) uPoint.x = rUnit.x uPoint.y = 0 uPoint.z = rUnit.y + --trigger.action.outText("rPnt: uPoint = " .. dcsCommon.point2text(uPoint), 30) local dist = dcsCommon.dist(thePoint, uPoint) -- measure distance to unit if (dist < lowDist) then lowDist = dist end end @@ -1509,7 +1573,7 @@ dcsCommon.version = "2.7.7" function dcsCommon.createGroundGroupWithUnits(name, theUnitTypes, radius, minDist, formation, innerRadius) - if not minDist then mindist = 4 end -- meters + if not minDist then minDist = 4 end -- meters if not formation then formation = "line" end if not radius then radius = 30 end -- meters if not innerRadius then innerRadius = 0 end @@ -1981,6 +2045,12 @@ end if theBool then return "yes" end return "no" end + + function dcsCommon.bool2Num(theBool) + if not theBool then theBool = false end + if theBool then return 1 end + return 0 + end function dcsCommon.point2text(p) if not p then return "" end @@ -2417,7 +2487,7 @@ end function dcsCommon.getUnitSpeed(theUnit) if not theUnit then return 0 end - if not theUnit:isExist() then return 0 end + if not Unit.isExist(theUnit) then return 0 end local v = theUnit:getVelocity() return dcsCommon.mag(v.x, v.y, v.z) end diff --git a/modules/guardianAngel.lua b/modules/guardianAngel.lua index cb18968..b5f0e1b 100644 --- a/modules/guardianAngel.lua +++ b/modules/guardianAngel.lua @@ -1,5 +1,5 @@ guardianAngel = {} -guardianAngel.version = "3.0.2" +guardianAngel.version = "3.0.4" guardianAngel.ups = 10 guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name guardianAngel.launchWarning = true -- detect launches and warn pilot @@ -59,6 +59,8 @@ guardianAngel.requiredLibs = { - removed legacy code 3.0.2 - added guardianAngel.name for those who use local flags on activate 3.0.3 - monitorItem() guards against loss of target (nil) + 3.0.4 - launchSound attribute + - interventionSound attribute This script detects missiles launched against protected aircraft an @@ -276,11 +278,20 @@ function guardianAngel.monitorItem(theItem) local desc = "Missile, missile, missile - now heading for " .. ctName .. "!" if guardianAngel.private then trigger.action.outTextForGroup(ID, desc, 30) + if guardianAngel.launchSound then + local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound + trigger.action.outSoundForGroup(ID, fileName) + end else - trigger.action.outText(desc, 30) + trigger.action.outText(desc, 30) + if guardianAngel.launchSound then + local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound + trigger.action.outSound(fileName) + end end end end + guardianAngel.retargetItem(theItem, currentTarget, isThreat) t = currentTarget else @@ -326,14 +337,20 @@ function guardianAngel.monitorItem(theItem) d <= lethalRange + 10 then desc = desc .. " ANGEL INTERVENTION" - --if theItem.lostTrack then desc = desc .. " (little sneak!)" end - --if theItem.missed then desc = desc .. " (missed you!)" end if guardianAngel.announcer then if guardianAngel.private then trigger.action.outTextForGroup(ID, desc, 30) + if guardianAngel.interventionSound then + local fileName = "l10n/DEFAULT/" .. guardianAngel.interventionSound + trigger.action.outSoundForGroup(ID, fileName) + end else trigger.action.outText(desc, 30) + if guardianAngel.interventionSound then + local fileName = "l10n/DEFAULT/" .. guardianAngel.interventionSound + trigger.action.outSound(fileName) + end end end guardianAngel.invokeCallbacks("intervention", theItem.targetName, theItem.weaponName) @@ -608,8 +625,16 @@ function guardianAngel.somethingHappened(event) -- can be moved to update() if guardianAngel.private then trigger.action.outTextForGroup(grpID, "Missile, missile, missile, " .. oclock .. " o clock" .. vbInfo, 30) + if guardianAngel.launchSound then + local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound + trigger.action.outSoundForGroup(grpID, fileName) + end else trigger.action.outText("Missile, missile, missile, " .. oclock .. " o clock" .. vbInfo, 30) + if guardianAngel.launchSound then + local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound + trigger.action.outSound(fileName) + end end theQItem.detected = true -- remember: we detected and warned already @@ -850,6 +875,14 @@ function guardianAngel.readConfigZone() guardianAngel.lastDeActivate = cfxZones.getFlagValue(guardianAngel.deactivate, theZone) end + if cfxZones.hasProperty(theZone, "launchSound") then + guardianAngel.launchSound = cfxZones.getStringFromZoneProperty(theZone, "launchSound", "nosound") + end + + if cfxZones.hasProperty(theZone, "interventionSound") then + guardianAngel.interventionSound = cfxZones.getStringFromZoneProperty(theZone, "interventionSound", "nosound") + end + guardianAngel.configZone = theZone if guardianAngel.verbose then trigger.action.outText("+++gA: processed config zone", 30) diff --git a/modules/messenger.lua b/modules/messenger.lua index 87b7f82..7050bf2 100644 --- a/modules/messenger.lua +++ b/modules/messenger.lua @@ -1,5 +1,5 @@ messenger = {} -messenger.version = "2.0.1" +messenger.version = "2.1.0" messenger.verbose = false messenger.requiredLibs = { "dcsCommon", -- always @@ -43,6 +43,24 @@ messenger.messengers = {} - unit - group 2.0.1 - config optimization + 2.1.0 - unit only: dynamicUnitProcessing for + - bearing to unit/zone + - response mapped by unit's heading + - bearing in clock position to unit/zone + - range to unit/zone + - bearing in left/right/ahead/behind + - bearing in starboard/port/ahead/aft + - added dynamicGroupProcessing to select unit 1 + - responses attribute + - + - response randomized + - respons mapped by unit's heading + - closing speed + - velocity (speed) + - aspect + - fix to messageMute + - + --]]-- @@ -137,6 +155,38 @@ function messenger.processDynamicValues(inMsg, theZone) outMsg = string.gsub(outMsg, pattern, val, 1) -- only one sub! end until not startLoc + + -- now process rsp + pattern = "" -- no list allowed but blanks and * and . and - and _ --> we fail on the other specials to keep this simple + + if theZone.msgResponses and (#theZone.msgResponses > 0) then -- only if this zone has an array + --trigger.action.outText("enter response proccing", 30) + repeat -- iterate all patterns one by one + local startLoc, endLoc = string.find(outMsg, pattern) + if startLoc then + --trigger.action.outText("response: found an occurence", 30) + local theValParam = string.sub(outMsg, startLoc, endLoc) + -- strip lead and trailer + local param = string.gsub(theValParam, "","") + + -- access flag + local val = cfxZones.getFlagValue(param, theZone) + if not val or (val < 1) then val = 1 end + if val > #theZone.msgResponses then val = #theZone.msgResponses end + + val = theZone.msgResponses[val] + val = dcsCommon.trim(val) + -- replace pattern in original with new val + outMsg = string.gsub(outMsg, pattern, val, 1) -- only one sub! + end + until not startLoc + + -- rnd response + local rndRsp = dcsCommon.pickRandom(theZone.msgResponses) + outMsg = outMsg:gsub ("", rndRsp) + end + return outMsg end @@ -167,8 +217,13 @@ end function messenger.processDynamicLoc(inMsg, theZone) -- replace all occurences of with their values - local locales = {"lat", "lon", "ele", "mgrs", "lle", "latlon"} +-- agl = angels +-- vel = velocity (speed) +-- hdg = heading +-- rhdg = heading, response-mapped + local locales = {"lat", "lon", "ele", "mgrs", "lle", "latlon", "alt", "vel", "hdg", "rhdg", "type"} 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 @@ -183,14 +238,32 @@ function messenger.processDynamicLoc(inMsg, theZone) local thePoint = nil local tZone = cfxZones.getZoneByName(param) local tUnit = Unit.getByName(param) - if tZone then + local spd = 0 + local angels = 0 + local theType = "" + if tZone then + theType = "Zone" thePoint = cfxZones.getPoint(tZone) - -- since zones always have elevation of 0, - -- now get the elevation from the map - thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + if tZone.linkedUnit and Unit.isExist(tZone.linkedUnit) then + local lU = tZone.linkedUnit + local masterPoint = lU:getPoint() + thePoint.y = masterPoint.y + spd = dcsCommon.getUnitSpeed(lU) + spd = math.floor(spd * 3.6) + uHead = math.floor(dcsCommon.getUnitHeading(tUnit) * 57.2958) -- to degrees. + else + -- since zones always have elevation of 0, + -- now get the elevation from the map + thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + end elseif tUnit then - if Unit.isExist(tUnit) then + if Unit.isExist(tUnit) then + theType = tUnit:getTypeName() thePoint = tUnit:getPoint() + spd = dcsCommon.getUnitSpeed(tUnit) + -- convert m/s to km/h + spd = math.floor(spd * 3.6) + uHead = math.floor(dcsCommon.getUnitHeading(tUnit) * 57.2958) -- to degrees. end else -- nothing to do, remove me. @@ -202,16 +275,31 @@ function messenger.processDynamicLoc(inMsg, theZone) -- processing. return result in locString local lat, lon, alt = coord.LOtoLL(thePoint) lat, lon = dcsCommon.latLon2Text(lat, lon) + angels = math.floor(thePoint.y) if theZone.imperialUnits then - alt = math.floor(alt * 3.28084) -- feet + alt = math.floor(alt * 3.28084) -- feet + spd = math.floor(spd * 0.539957) -- km/h to knots + angels = math.floor(angels * 3.28084) else alt = math.floor(alt) -- meters end + + if angels > 1000 then + angels = math.floor(angels / 100) * 100 + end + if aLocale == "lat" then locString = lat elseif aLocale == "lon" then locString = lon elseif aLocale == "ele" then locString = tostring(alt) elseif aLocale == "lle" then locString = lat .. " " .. lon .. " ele " .. tostring(alt) elseif aLocale == "latlon" then locString = lat .. " " .. lon + elseif aLocale == "alt" then locString = tostring(angels) -- don't confuse alt and angels, bad var naming here + elseif aLocale == "vel" then locString = tostring(spd) + elseif aLocale == "hdg" then locString = tostring(uHead) + elseif aLocale == "type" then locString = theType + elseif aLocale == "rhdg" and (theZone.msgResponses) then + local offset = messenger.rspMapper360(uHead, #theZone.msgResponses) + locString = dcsCommon.trim(theZone.msgResponses[offset]) else -- we have mgrs local grid = coord.LLtoMGRS(coord.LOtoLL(thePoint)) @@ -226,6 +314,161 @@ function messenger.processDynamicLoc(inMsg, theZone) return outMsg end + + +function messenger.rspMapper360(directionInDegrees, numResponses) + -- maps responses like clock. Clock has 12 'responses' (12, 1, .., 11), + -- with the first (12) also mapping to the last half arc + -- this method dynamically 'winds' the responses around + -- a clock and returns the index of the message to display + if numResponses < 1 then numResponses = 1 end + directionInDegrees = math.floor(directionInDegrees) + while directionInDegrees < 0 do directionInDegrees = directionInDegrees + 360 end + while directionInDegrees >= 360 do directionInDegrees = directionInDegrees - 360 end + -- now we have 0..360 + -- calculate arc per item + local arcPerItem = 360 / numResponses + local halfArc = arcPerItem / 2 + + -- we now map 0..360 to (0-halfArc..360-halfArc) by shifting + -- direction by half-arc and clipping back 0..360 + -- and now we can directly derive the index of the response + directionInDegrees = directionInDegrees + halfArc + if directionInDegrees >= 360 then directionInDegrees = directionInDegrees - 360 end + + local index = math.floor(directionInDegrees / arcPerItem) + 1 -- 1 .. numResponses + + return index +end + + +function messenger.dynamicGroupProcessing(msg, theZone, theGroup) + if not theGroup then return msg end + -- access first unit + local theUnit = theGroup:getUnit(1) + if not theUnit then return msg end + if not Unit.isExist(theUnit) then return msg end + -- we always use unit 1 as reference + return messenger.dynamicUnitProcessing(msg, theZone, theUnit) +end + +function messenger.dynamicUnitProcessing(inMsg, theZone, theUnit) +-- replace all occurences of with their values +-- bae = bearingInDegreesFromAtoB +-- rng = range + +-- asp = aspect (not yet implemented) +-- cls = closing velocity (not yet implemented) +-- clk = o'clock +-- hnd = handedness (left/right/ahead/behind +-- sde = side (starboard / port / ahead / aft) +-- rbea = responses mapped to bearing. maps all responses like clock, with "12" being the first response. requires msgResponses set + + local here = theUnit:getPoint() + local uHead = dcsCommon.getUnitHeading(theUnit) * 57.2958 -- to degrees. + local locales = {"bea", "rng", "clk", "hnd", "sde", "rbea", "cls", "pcls", "asp"} + local outMsg = inMsg + 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 thePoint = nil + local cls = 0 + local aspct = 0 + local tZone = cfxZones.getZoneByName(param) + local tUnit = Unit.getByName(param) + local aspect = 0 + local tHead = 0 + + if tZone then + thePoint = cfxZones.getPoint(tZone) + -- if this zone follows a unit, get the master units elevaltion + if tZone.linkedUnit and Unit.isExist(tZone.linkedUnit) then + local lU = tZone.linkedUnit + local masterPoint = lU:getPoint() + thePoint.y = masterPoint.y + cls = -dcsCommon.getClosingVelocity(theUnit, lU) + tHead = dcsCommon.getUnitHeading(lU) * 57.2958 + else + -- since zones always have elevation of 0, + -- now get the elevation from the map + thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z}) + end + elseif tUnit then + if Unit.isExist(tUnit) then + thePoint = tUnit:getPoint() + cls = -dcsCommon.getClosingVelocity(theUnit, tUnit) + tHead = dcsCommon.getUnitHeading(tUnit) * 57.2958 + end + else + -- nothing to do, remove me. + end + + local locString = theZone.errString + if thePoint then + -- now that we have a point, we can do locale-specific + -- processing. return result in locString + local pcls = cls + local r = dcsCommon.dist(here, thePoint) + --local alt = thePoint.y + local uSize = "m" + if theZone.imperialUnits then + r = math.floor(r * 3.28084) -- feet + uSize = "ft" + if r > 1000 then + -- convert to nautical mile + r = math.floor(r * 10 / 6076.12) / 10 + uSize = "nm" + end + cls = math.floor(cls * 1.9438452) -- m/s to knots + pcls = math.floor(pcls * 32.8084) / 10 -- ft/s + else + r = math.floor(r) -- meters + if r > 1000 then + r = math.floor (r / 100) / 10 + uSize = "km" + end + cls = math.floor(cls * 3.6) -- m/s to km/h + pcls = math.floor(pcls * 10) / 10 -- m/s + end + + local bea = dcsCommon.bearingInDegreesFromAtoB(here, thePoint) + local beaInv = 360 - bea -- from tUnit to player + local direction = bea - uHead -- tUnit as seen from player heading uHead + if direction < 0 then direction = direction + 360 end + aspect = beaInv - tHead + -- set up locale exchange string + if aLocale == "bea" then locString = tostring(bea) + elseif aLocale == "asp" then locString = dcsCommon.aspectByDirection(aspect) + elseif aLocale == "clk" then + locString = tostring(dcsCommon.getClockDirection(direction)) + elseif aLocale == "rng" then locString = tostring(r)..uSize + elseif aLocale == "cls" then locString = tostring(cls) + elseif aLocale == "pcls" then locString = tostring(pcls) + elseif aLocale == "hnd" then locString = dcsCommon.getGeneralDirection(direction) + elseif aLocale == "sde" then locString = dcsCommon.getNauticalDirection(direction) + elseif aLocale == "rbea" and (theZone.msgResponses) then + local offset = messenger.rspMapper360(direction, #theZone.msgResponses) + locString = dcsCommon.trim(theZone.msgResponses[offset]) + else locString = "" + end + end + -- replace pattern in original with new val + outMsg = string.gsub(outMsg, pattern, locString, 1) -- only one sub! + end -- if startloc + until not startLoc + end -- for all locales + return outMsg +end + + function messenger.dynamicFlagProcessing(inMsg, theZone) if not inMsg then return "No in message" end if not theZone then return "Nil zone" end @@ -296,10 +539,12 @@ function messenger.createMessengerWithZone(theZone) -- end theZone.messageOff = cfxZones.getBoolFromZoneProperty(theZone, "mute", false) --false - if cfxZones.hasProperty(theZone, "messageOff?") then + if cfxZones.hasProperty(theZone, "messageMute") then theZone.messageOff = cfxZones.getBoolFromZoneProperty(theZone, "messageMute", false) end + -- advisory: messageOff, messageOffFlag and lastMessageOff are all distinct + if cfxZones.hasProperty(theZone, "messageOff?") then theZone.messageOffFlag = cfxZones.getStringFromZoneProperty(theZone, "messageOff?", "*none") theZone.lastMessageOff = cfxZones.getFlagValue(theZone.messageOffFlag, theZone) @@ -313,23 +558,19 @@ function messenger.createMessengerWithZone(theZone) -- reveiver: coalition, group, unit if cfxZones.hasProperty(theZone, "coalition") then theZone.msgCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0) - end - - if cfxZones.hasProperty(theZone, "msgCoalition") then + elseif cfxZones.hasProperty(theZone, "msgCoalition") then theZone.msgCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "msgCoalition", 0) end if cfxZones.hasProperty(theZone, "group") then theZone.msgGroup = cfxZones.getStringFromZoneProperty(theZone, "group", "") - end - if cfxZones.hasProperty(theZone, "msgGroup") then + elseif cfxZones.hasProperty(theZone, "msgGroup") then theZone.msgGroup = cfxZones.getStringFromZoneProperty(theZone, "msgGroup", "") end if cfxZones.hasProperty(theZone, "unit") then theZone.msgUnit = cfxZones.getStringFromZoneProperty(theZone, "unit", "") - end - if cfxZones.hasProperty(theZone, "msgUnit") then + elseif cfxZones.hasProperty(theZone, "msgUnit") then theZone.msgUnit = cfxZones.getStringFromZoneProperty(theZone, "msgUnit", "") end @@ -358,6 +599,12 @@ function messenger.createMessengerWithZone(theZone) theZone.errString = cfxZones.getStringFromZoneProperty(theZone, "messageError", "") end + -- possible responses for mapping + if cfxZones.hasProperty(theZone, "responses") then + local resp = cfxZones.getStringFromZoneProperty(theZone, "responses", "none") + theZone.msgResponses = dcsCommon.string2Array(resp, ",") + end + if messenger.verbose or theZone.verbose then trigger.action.outText("+++Msg: new zone <".. theZone.name .."> will say <".. theZone.message .. ">", 30) end @@ -424,6 +671,7 @@ function messenger.isTriggered(theZone) local theGroup = Group.getByName(theZone.msgGroup) if theGroup and Group.isExist(theGroup) then local ID = theGroup:getID() + msg = messenger.dynamicGroupProcessing(msg, theZone, theGroup) trigger.action.outTextForGroup(ID, msg, theZone.duration, theZone.clearScreen) trigger.action.outSoundForGroup(ID, fileName) end @@ -431,6 +679,7 @@ function messenger.isTriggered(theZone) local theUnit = Unit.getByName(theZone.msgUnit) if theUnit and Unit.isExist(theUnit) then local ID = theUnit:getID() + msg = messenger.dynamicUnitProcessing(msg, theZone, theUnit) trigger.action.outTextForUnit(ID, msg, theZone.duration, theZone.clearScreen) trigger.action.outSoundForUnit(ID, fileName) end @@ -533,11 +782,6 @@ if not messenger.start() then end --[[-- -Wildcard extension: - - general flag access - - - - - - - + --]]-- \ No newline at end of file diff --git a/modules/unitZone.lua b/modules/unitZone.lua index eccc0b3..c318f33 100644 --- a/modules/unitZone.lua +++ b/modules/unitZone.lua @@ -1,5 +1,5 @@ unitZone={} -unitZone.version = "1.2.3" +unitZone.version = "1.2.4" unitZone.verbose = false unitZone.ups = 1 unitZone.requiredLibs = { @@ -16,6 +16,7 @@ unitZone.requiredLibs = { 1.2.2 - uzDirectInv 1.2.3 - better guards for enterZone!, exitZone!, changeZone! - better guards for uzOn? and uzOff? + 1.2.4 - more verbosity on uzDirect --]]-- @@ -282,7 +283,7 @@ function unitZone.update() -- scan all zones if not aZone.uzPaused then - local newState = unitZone.checkZoneStatus(aZone) + local newState = unitZone.checkZoneStatus(aZone) -- returns true if at least one unit in zone if newState ~= aZone.lastStatus then -- bang on change! @@ -292,6 +293,9 @@ function unitZone.update() -- output direct state suite if aZone.uzDirect then + if aZone.verbose or unitZone.verbose then + 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) else @@ -299,6 +303,10 @@ function unitZone.update() end end if aZone.uzDirectInv then + local invState = not newState + if aZone.verbose or unitZone.verbose then + 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) else @@ -363,5 +371,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 - Count Bases Blue.miz b/tutorial & demo missions/demo - Count Bases Blue.miz index 49d05ea..b8e2d2f 100644 Binary files a/tutorial & demo missions/demo - Count Bases Blue.miz and b/tutorial & demo missions/demo - Count Bases Blue.miz differ diff --git a/tutorial & demo missions/demo - Formation Trainer - Su25T.miz b/tutorial & demo missions/demo - Formation Trainer - Su25T.miz new file mode 100644 index 0000000..ede81c7 Binary files /dev/null and b/tutorial & demo missions/demo - Formation Trainer - Su25T.miz differ diff --git a/tutorial & demo missions/demo - Guardian Angel Reloaded.miz b/tutorial & demo missions/demo - Guardian Angel Reloaded.miz index f291987..bd11656 100644 Binary files a/tutorial & demo missions/demo - Guardian Angel Reloaded.miz and b/tutorial & demo missions/demo - Guardian Angel Reloaded.miz differ