diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 96cfea5..0265fd8 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 fe86afc..6e057af 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 069ffd7..f24788b 100644 --- a/modules/RNDFlags.lua +++ b/modules/RNDFlags.lua @@ -1,5 +1,5 @@ rndFlags = {} -rndFlags.version = "1.4.1" +rndFlags.version = "2.0.0" rndFlags.verbose = false rndFlags.requiredLibs = { "dcsCommon", -- always @@ -33,6 +33,7 @@ rndFlags.requiredLibs = { - minor clean-up 1.4.0 - persistence 1.4.1 - a little less verbosity + 2.0.0 - dmlZones, OOP --]] @@ -59,14 +60,14 @@ end -- function rndFlags.createRNDWithZone(theZone) local flags = "" - if cfxZones.hasProperty(theZone, "RND!") then - flags = cfxZones.getStringFromZoneProperty(theZone, "RND!", "") - elseif cfxZones.hasProperty(theZone, "flags!") then + if theZone:hasProperty("RND!") then + flags = theZone:getStringFromZoneProperty("RND!", "") + elseif theZone:hasProperty("flags!") then trigger.action.outText("+++RND: warning - zone <" .. theZone.name .. ">: deprecated 'flags!' usage, use 'RND!' instead.", 30) - flags = cfxZones.getStringFromZoneProperty(theZone, "flags!", "") - elseif cfxZones.hasProperty(theZone, "flags") then + flags = theZone:getStringFromZoneProperty("flags!", "") + elseif theZone:hasProperty("flags") then trigger.action.outText("+++RND: warning - zone <" .. theZone.name .. ">: deprecated 'flags' (no bang) usage, use 'RND!' instead.", 30) - flags = cfxZones.getStringFromZoneProperty(theZone, "flags", "") + flags = theZone:getStringFromZoneProperty("flags", "") else trigger.action.outText("+++RND: warning - zone <" .. theZone.name .. ">: no flags defined!", 30) end @@ -78,73 +79,65 @@ function rndFlags.createRNDWithZone(theZone) trigger.action.outText("+++RND: output set for <" .. theZone.name .. "> is <" .. flags .. ">",30) end - theZone.pollSizeMin, theZone.pollSize = cfxZones.getPositiveRangeFromZoneProperty(theZone, "pollSize", 1) + theZone.pollSizeMin, theZone.pollSize = theZone:getPositiveRangeFromZoneProperty("pollSize", 1) if rndFlags.verbose or theZone.verbose then trigger.action.outText("+++RND: pollSize is <" .. theZone.pollSizeMin .. ", " .. theZone.pollSize .. ">", 30) end - - theZone.remove = cfxZones.getBoolFromZoneProperty(theZone, "remove", false) + theZone.remove = theZone:getBoolFromZoneProperty("remove", false) + theZone.rndTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change") - -- watchflag: - -- triggerMethod - theZone.rndTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") - - if cfxZones.hasProperty(theZone, "rndTriggerMethod") then - theZone.rndTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "rndTriggerMethod", "change") + if theZone:hasProperty("rndTriggerMethod") then + theZone.rndTriggerMethod = theZone:getStringFromZoneProperty("rndTriggerMethod", "change") end -- trigger flag - if cfxZones.hasProperty(theZone, "f?") then - theZone.triggerFlag = cfxZones.getStringFromZoneProperty(theZone, "f?", "none") + if theZone:hasProperty("f?") then + theZone.triggerFlag = theZone:getStringFromZoneProperty("f?", "none") end - if cfxZones.hasProperty(theZone, "in?") then - theZone.triggerFlag = cfxZones.getStringFromZoneProperty(theZone, "in?", "none") + if theZone:hasProperty("in?") then + theZone.triggerFlag = theZone:getStringFromZoneProperty("in?", "none") end - if cfxZones.hasProperty(theZone, "rndPoll?") then - theZone.triggerFlag = cfxZones.getStringFromZoneProperty(theZone, "rndPoll?", "none") + if theZone:hasProperty("rndPoll?") then + theZone.triggerFlag = theZone:getStringFromZoneProperty("rndPoll?", "none") end - if theZone.triggerFlag then - theZone.lastTriggerValue = cfxZones.getFlagValue(theZone.triggerFlag, theZone) + theZone.lastTriggerValue = theZone:getFlagValue(theZone.triggerFlag) 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) + theZone.onStart = theZone:getBoolFromZoneProperty("onStart", false) if not theZone.onStart and not theZone.triggerFlag then - -- theZone.onStart = true - if true or theZone.verbose or rndFlags.verbose then - trigger.action.outText("+++RND - WARNING: no triggers and no onStart, RND in <" .. theZone.name .. "> can't be triggered.", 30) - end + trigger.action.outText("+++RND - WARNING: no triggers and no onStart, RND in <" .. theZone.name .. "> can't be triggered.", 30) end - theZone.rndMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") - if cfxZones.hasProperty(theZone, "rndMethod") then - theZone.rndMethod = cfxZones.getStringFromZoneProperty(theZone, "rndMethod", "inc") + theZone.rndMethod = theZone:getStringFromZoneProperty("method", "inc") + if theZone:hasProperty("rndMethod") then + theZone.rndMethod = theZone:getStringFromZoneProperty("rndMethod", "inc") end - theZone.reshuffle = cfxZones.getBoolFromZoneProperty(theZone, "reshuffle", false) + theZone.reshuffle = theZone:getBoolFromZoneProperty("reshuffle", false) if theZone.reshuffle then -- create a backup copy we can reshuffle from theZone.flagStore = dcsCommon.copyArray(theFlags) end -- done flag OLD, to be deprecated - if cfxZones.hasProperty(theZone, "done+1") then - theZone.doneFlag = cfxZones.getStringFromZoneProperty(theZone, "done+1", "") + if theZone:hasProperty("done+1") then + theZone.doneFlag = theZone:getStringFromZoneProperty("done+1", "") + trigger.action.outText("Warning: RND zone <" .. theZone.name .. "> uses depreceated 'done+1'.", 30) -- now NEW replacements - elseif cfxZones.hasProperty(theZone, "done!") then - theZone.doneFlag = cfxZones.getStringFromZoneProperty(theZone, "done!", "") - elseif cfxZones.hasProperty(theZone, "rndDone!") then - theZone.doneFlag = cfxZones.getStringFromZoneProperty(theZone, "rndDone!", "") + elseif theZone:hasProperty("done!") then + theZone.doneFlag = theZone:getStringFromZoneProperty("done!", "") + elseif theZone:hasProperty("rndDone!") then + theZone.doneFlag = theZone.getStringFromZoneProperty("rndDone!", "") end end @@ -210,7 +203,7 @@ function rndFlags.fire(theZone) trigger.action.outText("+++RND: polling " .. theFlag .. " with " .. theZone.rndMethod, 30) end - cfxZones.pollFlag(theFlag, theZone.rndMethod, theZone) + theZone:pollFlag(theFlag, theZone.rndMethod) end -- remove if requested @@ -317,9 +310,7 @@ function rndFlags.readConfigZone() end return end - - rndFlags.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) - + rndFlags.verbose = theZone.verbose if rndFlags.verbose then trigger.action.outText("***RND: read config", 30) end @@ -349,17 +340,15 @@ function rndFlags.start() -- now create an rnd gen for each one and add them -- to our watchlist for k, aZone in pairs(attrZones) do - rndFlags.createRNDWithZone(aZone) -- process attribute and add to zone - rndFlags.addRNDZone(aZone) -- remember it so we can smoke it + rndFlags.createRNDWithZone(aZone) + rndFlags.addRNDZone(aZone) end -- obsolete here attrZones = cfxZones.getZonesWithAttributeNamed("RND") - -- now create an rnd gen for each one and add them - -- to our watchlist for k, aZone in pairs(attrZones) do - rndFlags.createRNDWithZone(aZone) -- process attribute and add to zone - rndFlags.addRNDZone(aZone) -- remember it so we can smoke it + rndFlags.createRNDWithZone(aZone) + rndFlags.addRNDZone(aZone) end -- persistence diff --git a/modules/TDZ.lua b/modules/TDZ.lua index e5173a9..852854f 100644 --- a/modules/TDZ.lua +++ b/modules/TDZ.lua @@ -1,9 +1,15 @@ tdz = {} -tdz.version = "0.9.0dev" +tdz.version = "1.0.0" tdz.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course } +--[[-- +VERSION HISTORY + 1.0.0 - Initial version + +--]]-- + tdz.allTdz = {} tdz.watchlist = {} tdz.watching = false @@ -52,31 +58,7 @@ function tdz.translatePoly(thePoly, v) -- straight rot, translate to 0 first if aPoint.z then aPoint.z = aPoint.z + v.z end end end ---[[-- -function tdz.frameRwy(center, length, width, rads, a, b) -- bearing in rads - if not a then a = 0 end - if not b then b = 1 end - - -- create a 0-rotated centered poly - local poly = {} - local half = length / 2 - local leftEdge = -half - poly[4] = { x = leftEdge + a * length, z = width / 2, y = 0} - poly[3] = { x = leftEdge + b * length, z = width / 2, y = 0} - poly[2] = { x = leftEdge + b * length, z = -width / 2, y = 0} - poly[1] = { x = leftEdge + a * length, z = -width / 2, y = 0} - -- move it to center in map - tdz.translatePoly(poly, center) - - -- rotate it - tdz.rotateXZPolyAroundCenterInRads(poly, center, rads) - - -- frame it - local mId = dcsCommon.numberUUID() - trigger.action.quadToAll(-1, mId, poly[1], poly[2], poly[3], poly[4], {1, 0, 0, 1}, {1, 0, 0, .5}, 3) -- dotted line, red - -end ---]]-- + function tdz.calcTDZone(name, center, length, width, rads, a, b) if not a then a = 0 end if not b then b = 1 end @@ -94,7 +76,7 @@ function tdz.calcTDZone(name, center, length, width, rads, a, b) tdz.rotateXZPolyAroundCenterInRads(poly, center, rads) -- make it a dml zone local theNewZone = cfxZones.createSimplePolyZone(name, center, poly) - return theNewZone--, left, right + return theNewZone end -- @@ -181,7 +163,6 @@ function tdz.playerLanded(theUnit, playerName) -- make sure unit names match? local entry = tdz.watchlist[playerName] entry.hops = entry.hops + 1 -- uh oh. --- trigger.action.outText("Bump!") end -- we may want to filter helicopters @@ -226,7 +207,6 @@ function tdz.playerLanded(theUnit, playerName) if dOpHdg < dHdg then opposite = true dHdg = dOpHdg - trigger.action.outText("opposite rwy detected", 30) end if dHdg > math.pi * 1.5 then -- > 270+ dHdg = dHdg - math.pi * 1.5 diff --git a/modules/bombRange.lua b/modules/bombRange.lua index ab98e35..5cea9b3 100644 --- a/modules/bombRange.lua +++ b/modules/bombRange.lua @@ -1,18 +1,34 @@ bombRange = {} -bombRange.version = "1.0.0" +bombRange.version = "1.1.0" bombRange.dh = 1 -- meters above ground level burst bombRange.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course } - +--[[-- +VERSION HISTORY +1.0.0 - Initial version +1.1.0 - collector logic for collating hits + *after* impact on high-resolution scans (30fps) + set resolution to 30 ups by default + order of events: check kills against dropping projectiles + collecd dead, and compare against missing erdnance while they are fresh + GC + interpolate hits on dead when looking at kills and projectile does + not exist + also sampling kill events + +--]]-- bombRange.bombs = {} -- live tracking +bombRange.collector = {} -- post-impact collections for 0.5 secs bombRange.ranges = {} -- all bomb ranges bombRange.playerData = {} -- player accumulated data bombRange.unitComms = {} -- command interface per unit bombRange.tracking = false -- if true, we are tracking projectiles bombRange.myStatics = {} -- indexed by id +bombRange.killDist = 20 -- meters, if caught within that of kill event, this weapon was the culprit +bombRange.freshKills = {} -- at max 1 second old? function bombRange.addBomb(theBomb) table.insert(bombRange.bombs, theBomb) @@ -44,7 +60,7 @@ function bombRange.createRange(theZone) -- has bombRange attribte to mark it end theZone.details = theZone:getBoolFromZoneProperty("details", false) theZone.reporter = theZone:getBoolFromZoneProperty("reporter", true) - theZone.reportName = theZone:getBoolFromZoneProperty("reportName", true) + theZone.reportName = theZone:getBoolFromZoneProperty("reportName", false) theZone.smokeHits = theZone:getBoolFromZoneProperty("smokeHits", false) theZone.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "blue") theZone.flagHits = theZone:getBoolFromZoneProperty("flagHits", false) @@ -237,26 +253,51 @@ end -- Event Proccing -- function bombRange.suspectedHit(weapon, target) - if not bombRange.tracking then - return - end + local wType = weapon:getTypeName() if not target then return end - local theType = target:getTypeName() - + if target:getCategory() == 5 then -- scenery + return + end + + local theDesc = target:getDesc() + local theType = theDesc.typeName -- getTypeName gets display name +-- filter statics that we want to ignore for idx, aType in pairs(bombRange.filterTypes) do - if theType == aType then return end + if theType == aType then + return + end end -- try and match target to my known statics, exit if match - if not target.getID then return end -- units have no getID! - local theID = tonumber(target:getID()) - if bombRange.myStatics[theID] then - return + if target.getID then -- units have no getID, so skip for those + local theID = tonumber(target:getID()) + if bombRange.myStatics[theID] then + return + end end - -- look through the tracked weapons for a match - local filtered = {} + -- look through the collector (recent impacted) first local hasfound = false + local theID + for idx, b in pairs(bombRange.collector) do + if b.weapon == weapon then + b.pos = target:getPoint() + bombRange.impacted(b, target) -- use this for impact + theID = b.ID + hasfound = true +-- trigger.action.outText("susHit: filtering COLLECTED b <" .. b.name .. ">", 30) + end + end + if hasfound then + bombRange.collector[theID] = nil -- remove from collector + return + end + + -- look through the tracked weapons for a match next + if not bombRange.tracking then + return + end + local filtered = {} for idx, b in pairs (bombRange.bombs) do if b.weapon == weapon then hasfound = true @@ -264,6 +305,8 @@ function bombRange.suspectedHit(weapon, target) b.pos = weapon:getPoint() b.v = weapon:getVelocity() bombRange.impacted(b, target) + +-- trigger.action.outText("susHit: filtering live b <" .. b.name .. ">", 30) else table.insert(filtered, b) end @@ -273,16 +316,106 @@ function bombRange.suspectedHit(weapon, target) end end +function bombRange.suspectedKill(target) + -- some unit got killed, let's see if our munitions in the collector + -- phase are close by, i.e. they have disappeared + if not target then return end + + local theDesc = target:getDesc() + local theType = theDesc.typeName -- getTypeName gets display name + -- filter statics that we want to ignore + for idx, aType in pairs(bombRange.filterTypes) do + if theType == aType then return end + end + + local hasfound = nil + local theID + local pk = target:getPoint() + local now = timer.getTime() + + -- first, search all currently running projectiles, and check for proximity + local filtered = {} + for idx, b in pairs(bombRange.bombs) do + local wp + if Weapon.isExist(b.weapon) then + wp = b.weapon:getPoint() + else + local td = now - b.t -- time delta + -- calculate current loc from last velocity and + -- time + local moveV = dcsCommon.vMultScalar(b.v, td) + wp = dcsCommon.vAdd(b.pos, moveV) + end + local delta = dcsCommon.dist(wp, pk) + -- now use the line wp-wp+v and calculate distance + -- of pk to that line. + local wp2 = dcsCommon.vAdd(b.pos, b.v) + local delta2 = dcsCommon.distanceOfPointPToLineXZ(pk, b.pos, wp2) + + if delta < bombRange.killDist or delta2 < bombRange.killDist then + b.pos = pk + bombRange.impacted(b, target) + hasfound = true +-- trigger.action.outText("filtering b: <" .. b.name .. ">", 30) + else + table.insert(filtered, b) + end + end + bombRange.bombs = filtered + if hasfound then +-- trigger.action.outText("protocol: removed LIVING weapon from roster after impacted() invocation for non-nil target in suspectedKill", 30) + return + end + + -- now check the projectiles that have already impacted + for idx, b in pairs(bombRange.collector) do + local dist = dcsCommon.dist(b.pos, pk) + local wp2 = dcsCommon.vAdd(b.pos, b.v) + local delta2 = dcsCommon.distanceOfPointPToLineXZ(pk, b.pos, wp2) + + if dist < bombRange.killDist or delta2 < bombRange.killDist then + -- yeah, *you* killed them! + b.pos = pk + bombRange.impacted(b, target) -- use this for impact + theID = b.ID + hasfound = true + end + end + if hasfound then -- remove from collector, hit attributed + bombRange.collector[theID] = nil -- remove from collector +-- trigger.action.outText("protocol: removed COLL weapon from roster after impacted() invocation for non-nil target in suspectedKill", 30) + return + end +end + function bombRange:onEvent(event) if not event.initiator then return end local theUnit = event.initiator - if event.id == 2 then -- hit + if event.id == 2 then -- hit: weapon still exists if not event.weapon then return end bombRange.suspectedHit(event.weapon, event.target) return end + if event.id == 28 then -- kill: similar to hit, but due to new mechanics not reliable + if not event.weapon then return end + bombRange.suspectedHit(event.weapon, event.target) + return + end + + + if event.id == 8 then -- dead + -- these events can come *before* weapon disappears + local killDat = {} + killDat.victim = event.initiator + killDat.p = event.initiator:getPoint() + killDat.when = timer.getTime() + killDat.name = dcsCommon.uuid("vic") + bombRange.freshKills[killDat.name] = killDat + bombRange.suspectedKill(event.initiator) + end + local uName = nil local pName = nil if theUnit.getPlayerName and theUnit:getPlayerName() ~= nil then @@ -314,6 +447,7 @@ function bombRange:onEvent(event) b.weapon = w b.released = timer.getTime() b.relPos = b.pos + b.ID = dcsCommon.uuid("bomb") table.insert(bombRange.bombs, b) if not bombRange.tracking then timer.scheduleFunction(bombRange.updateBombs, {}, timer.getTime() + 1/bombRange.ups) @@ -336,14 +470,25 @@ end -- -- Update -- -function bombRange.impacted(weapon, target) +function bombRange.impacted(weapon, target, finalPass) local targetName = nil - local ipos = weapon.pos -- default to weapon location - if target then - ipos = target:getPoint() + if target then targetName = target:getDesc() if targetName then targetName = targetName.displayName end if not targetName then targetName = target:getTypeName() end + end + +-- local s = "Entering impacted() with weapon = <" .. weapon.name .. ">" +-- if target then +-- s = s .. " AND target = <" .. targetName .. ">" +-- end + +-- when we enter, weapon has ipacted target - if target is non-nil +-- what we need to determine is if that target is inside a zone + + local ipos = weapon.pos -- default to weapon location + if target then + ipos = target:getPoint() -- we make the target loc the impact point else -- not an object hit, interpolate the impact point on ground: -- calculate impact point. we use the linear equation @@ -375,6 +520,7 @@ function bombRange.impacted(weapon, target) trigger.action.outText("+++bRng: nil on eval. skipping.", 30) return end + if minDist > theRange.clipDist then -- no taget zone inside clip dist. disregard this one, too far off if bombRange.reportLongMisses then @@ -383,11 +529,11 @@ function bombRange.impacted(weapon, target) return end - if theRange.smokeHits then + if (not target) and theRange.smokeHits then trigger.action.smoke(ipos, theRange.smokeColor) end - if (not target) and theRange.flagHits then -- only ground imparts are flagged + if (not target) and theRange.flagHits then -- only ground impacts are flagged local cty = dcsCommon.getACountryForCoalition(0) -- some neutral county local p = {x=ipos.x, y=ipos.z} local theStaticData = dcsCommon.createStaticObjectData(dcsCommon.uuid(weapon.type .. " impact"), theRange.flagType) @@ -395,8 +541,37 @@ function bombRange.impacted(weapon, target) local theObject = coalition.addStaticObject(cty, theStaticData) end + local impactInside = theRange:pointInZone(ipos) +--[[-- + if target and (not impactInside) then + trigger.action.outText("Hit on target <" .. targetName .. "> outside of zone <" .. theRange.name .. ">. should exit unless final impact", 30) + -- find closest range to object that was hit + local closest = nil + local shortest = math.huge + local tp = target:getPoint() + for idx, aRange in pairs(bombRange.ranges) do + local zp = aRange:getPoint() + local zDist = dcsCommon.distFlat(zp, tp) + if zDist < shortest then + shortest = zDist + closest = aRange + end + end + + trigger.action.outText("re-check: closest range to target now is <" .. closest.name ..">", 30) + if closest:pointInZone(tp) then + trigger.action.outText("target <" .. targetName .. "> is INSIDE this range, d = <" .. math.floor(shortest) .. ">", 30) + else + trigger.action.outText("targed indeed outside, d = <" .. math.floor(shortest) .. ">", 30) + end + + if finalPass then trigger.action.outText("IS final pass.", 30) end + end +--]]-- if theRange.reporter and theRange.details then - local t = math.floor((timer.getTime() - weapon.released) * 10) / 10 + local ipc = weapon.impacted + if not ipc then ipc = timer.getTime() end + local t = math.floor((ipc - weapon.released) * 10) / 10 local v = math.floor(dcsCommon.vMag(weapon.v)) local tDist = dcsCommon.dist(ipos, weapon.relPos)/1000 tDist = math.floor(tDist*100) /100 @@ -404,7 +579,7 @@ function bombRange.impacted(weapon, target) end local msg = "" - if theRange:pointInZone(ipos) then + if impactInside then local percentage = 0 if theRange.isPoly then percentage = 100 @@ -430,29 +605,45 @@ function bombRange.impacted(weapon, target) bombRange.addImpactForWeapon(weapon, true, percentage) else msg = "Outside target area" +-- if target then msg = msg .. " (EVEN THOUGH TGT = " .. target:getName() .. ")" end if theRange.reportName then msg = msg .. " " .. theRange.name end - if theRange.details then msg = msg .. "(off-center by " .. math.floor(minDist *10)/10 .. " m)" end + if theRange.details then msg = msg .. " (off-center by " .. math.floor(minDist *10)/10 .. " m)" end msg = msg .. ", no hit." bombRange.addImpactForWeapon(weapon, false, 0) end if theRange.reporter then trigger.action.outTextForGroup(weapon.gID,msg , 30) end - +end + +function bombRange.uncollect(theID) + -- if this is still here, no hit was registered against the weapon + -- and we simply use the impact + local b = bombRange.collector[theID] + if b then + bombRange.collector[theID] = nil + bombRange.impacted(b, nil, true) -- final pass +-- trigger.action.outText("(final impact)", 30) + end end function bombRange.updateBombs() - + local now = timer.getTime() local filtered = {} for idx, theWeapon in pairs(bombRange.bombs) do if Weapon.isExist(theWeapon.weapon) then -- update pos and vel theWeapon.pos = theWeapon.weapon:getPoint() theWeapon.v = theWeapon.weapon:getVelocity() + theWeapon.t = now table.insert(filtered, theWeapon) else - -- interpolate the impact position from last position - bombRange.impacted(theWeapon) + -- put on collector to time out in 1 seconds to allow + -- asynch hits to still register for this weapon in MP +-- bombRange.impacted(theWeapon) + theWeapon.impacted = timer.getTime() + bombRange.collector[theWeapon.ID] = theWeapon -- + timer.scheduleFunction(bombRange.uncollect, theWeapon.ID, timer.getTime() + 1) end end @@ -468,6 +659,19 @@ function bombRange.updateBombs() end end +function bombRange.GC() + local cutOff = timer.getTime() + local filtered = {} + for name, killDat in pairs(bombRange.freshKills) do + if killDat.when + 2 < cutOff then + -- keep in set for two seconds after kill.when + filtered[name] = killDat + end + end + bombRange.freshKills = filtered + timer.scheduleFunction(bombRange.GC, {}, timer.getTime() + 10) +end + -- -- load & save data -- @@ -508,7 +712,7 @@ function bombRange.readConfigZone() bombRange.filterTypes = dcsCommon.trimArray(theSet) bombRange.reportLongMisses = theZone:getBoolFromZoneProperty("reportLongMisses", false) bombRange.mustCheckIn = theZone:getBoolFromZoneProperty("mustCheckIn", false) - bombRange.ups = theZone:getNumberFromZoneProperty("ups", 20) + bombRange.ups = theZone:getNumberFromZoneProperty("ups", 30) bombRange.menuTitle = theZone:getStringFromZoneProperty("menuTitle","Contact BOMB RANGE") if theZone:hasProperty("signIn!") then bombRange.signIn = theZone:getStringFromZoneProperty("signIn!", 30) @@ -551,6 +755,9 @@ function bombRange.start() -- add event handler world.addEventHandler(bombRange) + -- start GC + bombRange.GC() + return true end diff --git a/modules/cfxObjectDestructDetector.lua b/modules/cfxObjectDestructDetector.lua index 9fbcc0a..ac1619a 100644 --- a/modules/cfxObjectDestructDetector.lua +++ b/modules/cfxObjectDestructDetector.lua @@ -1,5 +1,5 @@ cfxObjectDestructDetector = {} -cfxObjectDestructDetector.version = "1.3.0" +cfxObjectDestructDetector.version = "2.0.0" cfxObjectDestructDetector.verbose = false cfxObjectDestructDetector.requiredLibs = { "dcsCommon", -- always @@ -12,11 +12,11 @@ cfxObjectDestructDetector.requiredLibs = { 1.1.0 added support for method, f! and destroyed! 1.2.0 DML / Watchflag support 1.3.0 Persistence support - - - Detect when an object with OBJECT ID as assigned in ME dies - *** EXTENDS ZONES - + 2.0.0 dmlZone OOP support + clean-up + re-wrote object determination to not be affected by + ID changes (happens with map updates) + fail addZone when name property is missing --]]-- cfxObjectDestructDetector.objectZones = {} @@ -31,7 +31,7 @@ end function cfxObjectDestructDetector.invokeCallbacksFor(zone) for idx, theCB in pairs (cfxObjectDestructDetector.callbacks) do - theCB(zone, zone.ID, zone.name) + theCB(zone, zone.ID, zone.name, zone.objName) end end @@ -55,104 +55,55 @@ end -- processing of zones -- function cfxObjectDestructDetector.processObjectDestructZone(aZone) - aZone.name = cfxZones.getStringFromZoneProperty(aZone, "NAME", aZone.name) --- aZone.coalition = cfxZones.getCoalitionFromZoneProperty(aZone, "coalition", 0) - aZone.ID = cfxZones.getNumberFromZoneProperty(aZone, "OBJECT ID", 1) -- THIS! + if aZone:hasProperty("name") then + aZone.objName = string.upper(aZone:getStringFromZoneProperty("NAME", "default")) + else + trigger.action.outText("+++OOD: Zone <" .. aZone.name .. "> lacks name attribute, ignored for destruct detection.") + return false + end + -- persistence interface aZone.isDestroyed = false - --[[-- old code, to be decom'd --]]-- - if cfxZones.hasProperty(aZone, "setFlag") then - aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "setFlag", "999") - end - if cfxZones.hasProperty(aZone, "f=1") then - aZone.setFlag = cfxZones.getStringFromZoneProperty(aZone, "f=1", "999") - end - if cfxZones.hasProperty(aZone, "clearFlag") then - aZone.clearFlag = cfxZones.getStringFromZoneProperty(aZone, "clearFlag", "999") - end - if cfxZones.hasProperty(aZone, "f=0") then - aZone.clearFlag = cfxZones.getStringFromZoneProperty(aZone, "f=0", "999") - end - if cfxZones.hasProperty(aZone, "increaseFlag") then - aZone.increaseFlag = cfxZones.getStringFromZoneProperty(aZone, "increaseFlag", "999") - end - if cfxZones.hasProperty(aZone, "f+1") then - aZone.increaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f+1", "999") - end - if cfxZones.hasProperty(aZone, "decreaseFlag") then - aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "decreaseFlag", "999") - end - if cfxZones.hasProperty(aZone, "f-1") then - aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999") + aZone.oddMethod = aZone:getStringFromZoneProperty("method", "inc") + if aZone:hasProperty("oddMethod") then + aZone.oddMethod = aZone:getStringFromZoneProperty("oddMethod", "inc") end - -- DML method support - aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "method", "inc") - if cfxZones.hasProperty(aZone, "oddMethod") then - aZone.oddMethod = cfxZones.getStringFromZoneProperty(aZone, "oddMethod", "inc") - end - - - -- we now always have that property - aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "f!", "*none") - - if cfxZones.hasProperty(aZone, "destroyed!") then - aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "destroyed!", "*none") - end - - if cfxZones.hasProperty(aZone, "objectDestroyed!") then - aZone.outDestroyFlag = cfxZones.getStringFromZoneProperty(aZone, "objectDestroyed!", "*none") + if aZone:hasProperty("f!") then + aZone.outDestroyFlag = aZone:getStringFromZoneProperty("f!", "*none") + elseif aZone:hasProperty("destroyed!") then + aZone.outDestroyFlag = aZone:getStringFromZoneProperty("destroyed!", "*none") + elseif aZone:hasProperty("objectDestroyed!") then + aZone.outDestroyFlag = aZone:getStringFromZoneProperty( "objectDestroyed!", "*none") end + return true end -- --- MAIN DETECTOR +-- ON EVENT -- --- invoke callbacks when an object was destroyed function cfxObjectDestructDetector:onEvent(event) if event.id == world.event.S_EVENT_DEAD then if not event.initiator then return end - local id = event.initiator:getName() - if not id then return end + local theObject = event.initiator + local desc = theObject:getDesc() + if not desc then return end + local matchMe = desc.typeName -- we home in on object's typeName + if not matchMe then return end + matchMe = string.upper(matchMe) for idx, aZone in pairs(cfxObjectDestructDetector.objectZones) do - if (not aZone.isDestroyed) and aZone.ID == id then - -- flag manipulation - -- OLD FLAG SUPPORT, SOON TO BE REMOVED - if aZone.setFlag then - trigger.action.setUserFlag(aZone.setFlag, 1) - end - if aZone.clearFlag then - trigger.action.setUserFlag(aZone.clearFlag, 0) - end - if aZone.increaseFlag then - local val = trigger.misc.getUserFlag(aZone.increaseFlag) + 1 - trigger.action.setUserFlag(aZone.increaseFlag, val) - end - if aZone.decreaseFlag then - local val = trigger.misc.getUserFlag(aZone.decreaseFlag) - 1 - trigger.action.setUserFlag(aZone.decreaseFlag, val) - end - -- END OF OLD CODE, TO BE REMOVED - - -- support for banging + if (not aZone.isDestroyed) and aZone.objName == matchMe then if aZone.outDestroyFlag then - cfxZones.pollFlag(aZone.outDestroyFlag, aZone.oddMethod, aZone) + aZone:pollFlag(aZone.outDestroyFlag, aZone.oddMethod) end - -- invoke callbacks cfxObjectDestructDetector.invokeCallbacksFor(aZone) if aZone.verbose or cfxObjectDestructDetector.verbose then trigger.action.outText("OBJECT KILL: " .. id, 30) end - - -- we could now remove the object from the list - -- for better performance since it cant - -- die twice - -- save state for persistence - aZone.isDestroyed = true - + aZone.isDestroyed = true return end end @@ -172,7 +123,7 @@ function cfxObjectDestructDetector.saveData() -- invoked by persistence -- the isDestroyed and flag info info info = {} info.isDestroyed = aZone.isDestroyed - info.outDestroyVal = cfxZones.getFlagValue(aZone.outDestroyFlag, aZone) + info.outDestroyVal = aZone:getFlagValue(aZone.outDestroyFlag) zoneInfo[aZone.name] = info end -- expasion proof: assign as own field @@ -202,7 +153,7 @@ function cfxObjectDestructDetector.loadMission() local theZone = cfxObjectDestructDetector.getObjectDetectZoneByName(zName) if theZone then theZone.isDestroyed = info.isDestroyed - cfxZones.setFlagValue(theZone.outDestroyFlag, info.outDestroyVal, theZone) + theZone:setFlagValue(theZone.outDestroyFlag, info.outDestroyVal) if cfxObjectDestructDetector.verbose or theZone.verbose then trigger.action.outText("+++oDDet: persistence setting flag <" .. theZone.outDestroyFlag .. "> to <" .. info.outDestroyVal .. ">",30) end @@ -246,14 +197,14 @@ function cfxObjectDestructDetector.start() return false end - -- collect all zones with 'OBJECT id' attribute - -- collect all spawn zones + -- collect all zones with 'OBJECT ID' attribute local attrZones = cfxZones.getZonesWithAttributeNamed("OBJECT ID") for k, aZone in pairs(attrZones) do - cfxObjectDestructDetector.processObjectDestructZone(aZone) -- process attribute and add to zone properties (extend zone) - cfxObjectDestructDetector.addObjectDetectZone(aZone) + if cfxObjectDestructDetector.processObjectDestructZone(aZone) then + cfxObjectDestructDetector.addObjectDetectZone(aZone) + end end -- add myself as event handler @@ -276,8 +227,6 @@ function cfxObjectDestructDetector.start() end end - - -- say hi trigger.action.outText("cfx Object Destruct Zones v" .. cfxObjectDestructDetector.version .. " started.", 30) return true diff --git a/modules/civAir.lua b/modules/civAir.lua index f50b4a4..7461c7a 100644 --- a/modules/civAir.lua +++ b/modules/civAir.lua @@ -1,5 +1,5 @@ civAir = {} -civAir.version = "2.0.0" +civAir.version = "3.0.0" --[[-- 1.0.0 initial version 1.1.0 exclude list for airfields @@ -32,43 +32,61 @@ civAir.version = "2.0.0" strenghtened guard on testing against free slots for other units flights are now of random neutral countries maxFlights synonym for maxTraffic - + 3.0.0 liveries support + default liveries for Yak-50 (main test case) + default liveries for C-130, c-17A, IL-76MD, An-30M, An-26B + default aircraft types dcs + support for CAM + default liveries for all CAM types + new DCS attribute + new CAM attribute + deafault to one Yak-40 if neither + support for 'civil_liveries' zone + --]]-- civAir.ups = 0.05 -- updates per second. 0.05 = once every 20 seconds civAir.initialAirSpawns = true -- when true has population spawn in-air at start civAir.verbose = false --- aircraftTypes contains the type names for the neutral air traffic --- each entry has the same chance to be chose, so to make an --- aircraft more probably to appear, add its type multiple times --- like here with the Yak-40 -civAir.aircraftTypes = {"Yak-40", "Yak-40", "C-130", "C-17A", "IL-76MD", "An-30M", "An-26B"} -- civilian planes type strings as described here https://github.com/mrSkortch/DCS-miscScripts/tree/master/ObjectDB +civAir.aircraftTypes = {} +civAir.dcsBuiltinTypes = {"Yak-40", "C-130", "C-17A", "IL-76MD", "An-30M", "An-26B"} +civAir.CAMTypes = { "A_320", "A_330", "A_380", "B_727", "B_737", "B_747", "B_757", "Cessna_210N", "DC_10",} --- maxTraffic is the number of neutral flights that are --- concurrently under way +civAir.liveries = { + +-- definitions for plain vanilla DCS +["Yak-40"] = {"Aeroflot", "Algeria GLAM", "Olympic Airways", "Ukranian", "Georgian Airlines", }, --"Georgian Airlines", +["C-130"] = {"Air Algerie L-382 White", "Algerian AF Green", "Algerian AF H30 White", "Belgian Air Force", "Canada's Air Force", "French Air Force", "HAF gray", "IRIAF 5-8503", "IRIAF 5-8518", "Israel Defence Force", "Royal Air Force", "Royal Danish Air Force", "Royal Netherlands Air Force", "Royal Norwegian Air Force", "Spanish Air Force", "Turkish Air Force", "US Air Force", }, +["C-17A"] = {"usaf standard", }, +["IL-76MD"] = {"Algerian AF IL-76MD", "China Air Force New", "China Air Force Old", "FSB aeroflot", "MVD aeroflot", "RF Air Force", "Ukrainian AF", "Ukrainian AF aeroflot", }, +["An-30M"] = {"15th Transport AB", "China CAAC", "RF Air Force"}, +["An-26B"] = {"Abkhazian AF", "Aeroflot", "China PLAAF", "Georgian AF", "RF Air Force", "RF Navy", "Ukraine AF", }, + +-- definitions for CAM mod +["A_320"] = {"Aeroflot", "Aeroflot 1", "Air Asia", "Air Berlin", "Air Berlin FFO", "Air Berlin OLT", "Air Berlin retro", "Air France", "Air Moldova", "Airbus Neo", "Al Maha", "Alitalia", "American Airlines", "British Airways", "Cebu Pacific", "Clean", "Condor", "Delta Airlines", "Easy Jet", "Easy Jet Berlin", "Easy Jet w", "Edelweiss", "Emirates", "Etihad", "Eurowings", "Eurowings BVB09", "Eurowings Europa Park", "Fly Georgia", "Fly Niki", "Frontier", "German Wings", "Gulf Air", "Iberia", "Iran Air", "Jet Blue NY", "JetBlue", "jetBlue FDNY", "Kish Air", "Kuwait Airways", "Lufthansa", "Lufthansa New", "MEA", "MRTT Canada", "MRTT Luftwaffe", "Qatar", "RAF MPA", "RAF VIP", "S7", "SAS", "Saudi Gulf", "Saudia", "Small Planet", "Star Alliance", "SWISS", "Thomas Cook", "Tunis Air", "Turkish Airlines", "United", "Ural Airlines", "US Airways", "Vietnam Airlines", "Virgin", "WiZZ", "WiZZ Budapest", "WOW", }, + +["A_330"] = {"Aer Lingus", "Aeroflot", "Air Canada", "Air China", "Air Tahiti Nui", "AirAsia", "Airbus", "BOURKHAN", "Brussels Airline", "Cathay Pacific", "CEBU Pacific", "China Eastern", "Clean", "DELTA", "DragonAir", "Edelweiss", "Egypt Air", "Emirates", "ETIHAD", "EVA", "FIJI", "FinnAir", "FrenchBlue", "Garude Indunesia", "GulfAir", "Hainan Airlines", "Iberia", "IRoI", "KLM", "LAN Airways", "Lion Air PK-LEG", "LTU", "Lufthansa", "NWA", "nwaold", "Olympic", "OmanAir", "Orbit", "Philipines", "Qantas", "Qatar", "RAF Voyager", "Singapore", "Skyteam", "Srilankan", "Star Aliance", "Swiss", "Thomas Cook", "Turkish Airlines", "US Airways", "Virgin Atlantic", "WorldTrave", }, + +["A_380"] = {"Air France", "BA", "China Southern", "Clean", "Emirates", "KA", "LH", "LHF", "Qantas Airways", "QTR", "SA", "TA", }, + +["B_727"] = {"AEROFLOT", "Air France", "Alaska", "Alitalia", "American Airlines", "Clean", "Delta Airlines", "Delta Airlines OLD", "FedEx", "Hapag Lloyd", "Lufthansa", "Lufthansa Oberhausen Old", "Northwest", "Pan Am", "Singapore Airlines", "Southwest", "UNITED", "UNITED Old", "ZERO G", }, + +["B_737"] = {"Air Algerie", "Air Berlin", "Air France", "airBaltic", "Airzena", "AM", "American_Airlines", "British Airways", "C40s", "Clean", "Disney", "EA", "easyJet", "FINNAIR", "HARIBO", "JA", "Jet2", "kulula", "LH", "Lufthansa BA", "Lufthansa KR", "OLD_BA", "OMAN AIR", "P8 RAF", "P8 USN", "PAN AM", "Polskie Linie Lotnicze LOT", "QANTAS", "RYANAIR", "SouthWest Lone Star", "ThomsonFly", "TNT", "Ukraine Airlines", "UPS", }, + +["B_747"] = {"AF", "AF-One", "AI", "CP", "IM", "KLM", "LH", "NW", "PA", "QA", "TA", }, + +["B_757"] = {"AA", "BA", "C-32", "Delta", "DHL", "easyJet", "Swiss", "Thomson", }, + +["Cessna_210N"] = {"Blank", "D-EKVW", "HellenicAF", "Muster", "N9572H", "SEagle blue", "SEagle red", "USAF-Academy", "V5-BUG", "VH-JGA", }, + +["DC_10"] = {"SWISSAIR HB-IHL", "SWISSAIR HB-IMC", "SWISSAIR HB-IPF", } +} civAir.maxTraffic = 10 -- number of flights at the same time civAir.maxIdle = 8 * 60 -- seconds of ide time before it is removed after landing - civAir.trafficCenters = {} --- place zones on the map and add a "civAir" attribute. --- If the attribute's value is anything --- but "exclude", the closest airfield to the zone --- is added to trafficCenters --- if you leave this list empty, and do not add airfields --- by zones, the list is automatically populated with all --- airfields in the map --- if name starts with "***" then it is not an airfield, but zone - civAir.excludeAirfields = {} --- list all airfields that must NOT be included in --- civilian activities. Will be used for neither landing --- nor departure. overrides any airfield that was included --- in trafficCenters. --- can be populated by zone on the map that have the --- 'civAir' attribute with value "exclude" - civAir.departOnly = {} -- use only to start from civAir.landingOnly = {} -- use only to land at civAir.inoutZones = {} -- off-map connector zones @@ -86,40 +104,106 @@ function civAir.readConfigZone() -- note: must match exactly!!!! local theZone = cfxZones.getZoneByName("civAirConfig") if not theZone then - trigger.action.outText("***civA: NO config zone!", 30) theZone = cfxZones.createSimpleZone("civAirConfig") end - - -- ok, for each property, load it if it exists - if theZone:hasProperty("aircraftTypes") then - local theTypes = theZone:getStringFromZoneProperty( "aircraftTypes", civAir.aircraftTypes) -- "Yak-40") - local typeArray = dcsCommon.splitString(theTypes, ",") - typeArray = dcsCommon.trimArray(typeArray) - civAir.aircraftTypes = typeArray - end + civAir.verbose = theZone.verbose + civAir.ups = theZone:getNumberFromZoneProperty("ups", 0.05) + if civAir.ups < .0001 then civAir.ups = 0.05 end --- if theZone:hasProperty("ups") then - civAir.ups = theZone:getNumberFromZoneProperty("ups", 0.05) - if civAir.ups < .0001 then civAir.ups = 0.05 end --- end if theZone:hasProperty("maxTraffic") then civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxTraffic", 10) - elseif theZone:hasProperty("maxFlights") then + else --if theZone:hasProperty("maxFlights") then civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxFlights", 10) end --- if theZone:hasProperty("maxIdle") then - civAir.maxIdle = theZone:getNumberFromZoneProperty("maxIdle", 8 * 60) --- end + civAir.maxIdle = theZone:getNumberFromZoneProperty("maxIdle", 8 * 60) --- if theZone:hasProperty("initialAirSpawns") then - civAir.initialAirSpawns = theZone:getBoolFromZoneProperty( "initialAirSpawns", true) --- end + civAir.initialAirSpawns = theZone:getBoolFromZoneProperty( "initialAirSpawns", true) + + civAir.owner = theZone:getNumberFromZoneProperty("owner", 82) -- default to UN peacekeepers + -- build my aircraft types list + local hasDCS = theZone:getBoolFromZoneProperty("dcs", true) + if hasDCS then + if civAir.verbose then trigger.action.outText("+++civA: adding DCS standard types", 30) end + for idx, aType in pairs(civAir.dcsBuiltinTypes) do + table.insert(civAir.aircraftTypes, aType) + end + end + local hasCAM = theZone:getBoolFromZoneProperty("cam", false) + if hasCAM then + if civAir.verbose then trigger.action.outText("+++civA: adding CAM add-on types", 30) end + for idx, aType in pairs(civAir.CAMTypes) do + table.insert(civAir.aircraftTypes, aType) + end + end + + -- now get types and liveries from 'civil_liveries' if present + local livZone = cfxZones.getZoneByName("civil_liveries") + if livZone then + if civAir.verbose then + trigger.action.outText("civA: found and processing 'civil_liveries' zone data.", 30) + end + + -- read all into my types registry, replacing whatever is there + local rawLiver = cfxZones.getAllZoneProperties(livZone) + local newTypes, newLiveries = civAir.addTypesAndLiveries(rawLiver) + -- now types to existing types if not already there + for idx, aType in pairs(newTypes) do + dcsCommon.addToTableIfNew(civAir.aircraftTypes, aType) + if civAir.verbose then + trigger.action.outText("+++civA: processed and added aircraft <" .. aType .. "> to civAir", 30) + end + end + -- now replace liveries or add if not already there + for aType, liveries in pairs(newLiveries) do + civAir.liveries[aType] = liveries + if civAir.verbose then + trigger.action.outText("+++civA: replaced/added liveries for aircraft <" .. aType .. ">", 30) + end + end + end + + if #civAir.aircraftTypes < 1 then + table.insert(civAir.aircraftTypes, "Yak-40") + if civAir.verbose then + trigger.action.outText("+++civA: adding singular Yak-40", 30) + end + end + + -- selective types, overwrites existing types when present + -- also provides legacy support + if theZone:hasProperty("aircraftTypes") then + local theTypes = theZone:getStringFromZoneProperty( "aircraftTypes", civAir.aircraftTypes) + local typeArray = dcsCommon.splitString(theTypes, ",") + typeArray = dcsCommon.trimArray(typeArray) + civAir.aircraftTypes = typeArray + if civAir.verbose then + trigger.action.outText("+++civA: setting aircraft types to <" .. theTypes .. ">", 30) + end + end - civAir.verbose = theZone.verbose end +function civAir.addTypesAndLiveries(rawIn) + local newTypes = {} + local newLiveries = {} + -- now iterate the input table, and generate new types and + -- liveries from it + for theType, liveries in pairs (rawIn) do + if civAir.verbose then + trigger.action.outText("+++civA: processing type <" .. theType .. ">:<" .. liveries .. ">", 30) + end + local livA = dcsCommon.splitString(liveries, ',') + livA = dcsCommon.trimArray(livA) + table.insert(newTypes, theType) + newLiveries[theType] = livA + end + + return newTypes, newLiveries +end + + function civAir.processZone(theZone) local value = theZone:getStringFromZoneProperty("civAir", "") local af = dcsCommon.getClosestAirbaseTo(theZone.point, 0) -- 0 = only airfields, not farp or ships @@ -137,16 +221,12 @@ function civAir.processZone(theZone) elseif dcsCommon.stringStartsWith(value, "inb") then table.insert(civAir.departOnly, inoutName) -- start in inbound zone civAir.inoutZones[inoutName] = theZone --- theZone.inbound = true elseif dcsCommon.stringStartsWith(value, "outb") then table.insert(civAir.landingOnly, inoutName) civAir.inoutZones[inoutName] = theZone --- theZone.outbound = true elseif dcsCommon.stringStartsWith(value, "in/out") then table.insert(civAir.trafficCenters, inoutName) civAir.inoutZones[inoutName] = theZone --- theZone.inbound = true --- theZone.outbound = true else table.insert(civAir.trafficCenters, afName) -- note that adding the same twice makes it more likely to be picked end @@ -231,8 +311,7 @@ function civAir.getTwoAirbases() sAB = dcsCommon.pickRandom(filteredAB) tries = tries + 1 -- only try 10 times until fAB ~= sAB or tries > 10 - - + local civA = {} if not (dcsCommon.stringStartsWith(fAB, '***')) then civA.AB = dcsCommon.getFirstAirbaseWhoseNameContains(fAB, 0) @@ -295,9 +374,15 @@ function civAir.createFlight(name, theTypeString, fromAirfield, toAirfield, inAi local theGroup = dcsCommon.createEmptyAircraftGroupData (name) local theAUnit = dcsCommon.createAircraftUnitData(name .. "-GA", theTypeString, false) + -- add livery capability for this aircraft + civAir.processLiveriesFor(theAUnit, theTypeString) + + -- enforce civ attribute + theAUnit.civil_plane = true + theAUnit.payload.fuel = 100000 dcsCommon.addUnitToGroupData(theAUnit, theGroup) - + local fromWP if fromAirfield.AB then fromWP = dcsCommon.createTakeOffFromParkingRoutePointData(fromAirfield.AB) @@ -384,13 +469,7 @@ function civAir.createFlight(name, theTypeString, fromAirfield, toAirfield, inAi dcsCommon.addRoutePointForGroupData(theGroup, toWP) -- spawn - local groupCat = Group.Category.AIRPLANE - local allNeutral = dcsCommon.getCountriesForCoalition(0) - local aRandomNeutral = dcsCommon.pickRandom(allNeutral) - if not aRandomNeutral then - trigger.action.outText("+++civA: WARNING: no neutral countries exist, flight is not neutral.", 30) - end - local theSpawnedGroup = coalition.addGroup(aRandomNeutral, groupCat, theGroup) -- 82 is UN peacekeepers + local theSpawnedGroup = coalition.addGroup(civAir.owner, groupCat, theGroup) -- 82 is UN peacekeepers if zoneApproach then -- track this flight to target zone civAir.outboundFlights[name] = zoneApproach @@ -436,6 +515,20 @@ function civAir.airStartPopulation() civAir.airStartSeparation = civAir.airStartSeparation + 200 civAir.createNewFlight(true) end + -- start update in 15 seconds + timer.scheduleFunction(civAir.update, {}, timer.getTime() + 15) +end + +-- +-- Livery handling +-- + +function civAir.processLiveriesFor(theData, theType) + if civAir.liveries[theType] then + local available = civAir.liveries[theType] + local chosen = dcsCommon.pickRandom(available) + theData.livery_id = chosen + end end -- @@ -609,7 +702,7 @@ function civAir.start() -- see if there is a config zone and load it civAir.readConfigZone() - + -- look for zones to add to air fields list civAir.collectHubs() @@ -632,12 +725,15 @@ function civAir.start() end -- air-start half population if allowed + -- allow mission 15 seconds to settle before we start populating to + -- allow better access to liveries if civAir.initialAirSpawns then - civAir.airStartPopulation() + timer.scheduleFunction(civAir.airStartPopulation, {}, timer.getTime() + 5) + else + -- start update in 15 seconds + timer.scheduleFunction(civAir.update, {}, timer.getTime() + 15) end - -- start the update loop - civAir.update() -- start outbound tracking civAir.trackOutbound() diff --git a/modules/cloneZone.lua b/modules/cloneZone.lua index 359590e..469ccd7 100644 --- a/modules/cloneZone.lua +++ b/modules/cloneZone.lua @@ -1,5 +1,5 @@ cloneZones = {} -cloneZones.version = "1.9.0" +cloneZones.version = "1.9.1" cloneZones.verbose = false cloneZones.requiredLibs = { "dcsCommon", -- always @@ -106,6 +106,7 @@ cloneZones.respawnOnGroupID = true - cooldown attribute - cloner collects all types used - groupScheme attribute + 1.9.1 - useAI attribute --]]-- -- @@ -404,6 +405,8 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" theZone.nameScheme = nil theZone.groupScheme = nil end + + theZone.useAI = theZone:getBoolFromZoneProperty("useAI", true) -- we end with clear plate end @@ -1256,6 +1259,8 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) -- see what country we spawn for ctry = cloneZones.resolveOwnership(spawnZone, ctry) rawData.CZctry = ctry -- save ctry + -- set AI on or off + rawData.useAI = spawnZone.useAI table.insert(dataToSpawn, rawData) end @@ -1295,6 +1300,11 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData) table.insert(spawnedGroups, theGroup) + -- turn off AI if disabled + if not rawData.useAI then + cloneZones.turnOffAI({theGroup}) + end + -- update groupXlate table from spawned group -- so we can later reference them with other clones local newGroupID = theGroup:getID() -- new ID assigned by DCS @@ -1515,6 +1525,13 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) return spawnedGroups, spawnedStatics end +function cloneZones.turnOffAI(args) + local theGroup = args[1] + local theController = theGroup:getController() + theController:setOnOff(false) +-- trigger.action.outText("turned off AI for group <" .. theGroup:getName() .. "> ", 30) +end + -- retro-fit for helo troops and others to provide 'requestable' support function cloneZones.spawnWithSpawner(theZone) -- analog to cfxSpawnZones.spawnWithSpawner(theSpawner) @@ -1950,6 +1967,10 @@ function cloneZones.loadData() local gdClone = dcsCommon.clone(gData) cloneZones.allClones[gName] = gdClone local theGroup = coalition.addGroup(cty, cat, gData) + -- turn off AI if disabled + if not gData.useAI then + cloneZones.turnOffAI({theGroup}) + end end -- spawn all static objects diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index e67eb2a..8054dc5 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.9.5" +dcsCommon.version = "2.9.6" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -175,6 +175,7 @@ dcsCommon.version = "2.9.5" 2.9.3 - getAirbasesWhoseNameContains now supports category tables for filtering 2.9.4 - new bearing2degrees() 2.9.5 - distanceOfPointPToLineXZ(p, p1, p2) +2.9.6 - new addToTableIfNew() --]]-- @@ -402,6 +403,13 @@ dcsCommon.version = "2.9.5" end return outTable end + + function dcsCommon.addToTableIfNew(theTable, theElement) + for idx, anElement in pairs(theTable) do + if anElement == theElement then return end + end + table.insert(theTable, theElement) + end -- -- A I R F I E L D S A N D F A R P S -- @@ -2404,38 +2412,9 @@ end theString = string.upper(theString) thePrefix = string.upper(theString) end - -- new code because old 'string.find' had some really - -- strange results with aircraft types. Prefix "A-10" did not - -- match string "A-10A" etc. - - -- superseded: string.find (s, pattern [, init [, plain]]) solves the problem - - --[[ - local pl = string.len(thePrefix) - if pl > string.len(theString) then return false end - if pl < 1 then return false end - for i=1, pl do - local left = string.sub(theString, i, i) - local right = string.sub(thePrefix, i, i) - if left ~= right then - return false - end - end - - return true ---]]-- trigger.action.outText("---- OK???", 30) - -- strange stuff happening with some strings, let's investigate - + -- superseded: string.find (s, pattern [, init [, plain]]) solves the problem local i, j = string.find(theString, thePrefix, 1, true) return (i == 1) ---[[-- - if res then - trigger.action.outText("startswith: <" .. theString .. "> pre <" .. thePrefix .. "> --> YES", 30) - else - trigger.action.outText("startswith: <" .. theString .. "> nojoy pre <" .. thePrefix .. ">", 30) - end - return res ---]]-- end function dcsCommon.removePrefix(theString, thePrefix) diff --git a/modules/fireFX.lua b/modules/fireFX.lua index 0e690ae..b708860 100644 --- a/modules/fireFX.lua +++ b/modules/fireFX.lua @@ -13,7 +13,9 @@ fireFX.fx = {} 1.0.0 - Initial version 1.1.0 - persistence 1.1.1 - agl attribute - + 2.0.0 - dmlZones OOP + - rndLoc + --]]-- function fireFX.addFX(theZone) @@ -35,7 +37,7 @@ end -- function fireFX.createFXWithZone(theZone) -- decode size and fire - local theSize = cfxZones.getStringFromZoneProperty(theZone, "fireFX", "none") + local theSize = theZone:getStringFromZoneProperty("fireFX", "none") theSize = dcsCommon.trim(theSize) theSize = string.upper(theSize) local fxCode = 1 @@ -59,22 +61,22 @@ function fireFX.createFXWithZone(theZone) trigger.action.outText("+++ffx: new FX with code = <" .. fxCode .. ">", 30) end - theZone.density = cfxZones.getNumberFromZoneProperty(theZone, "density", 0.5) + theZone.density = theZone:getNumberFromZoneProperty("density", 0.5) - theZone.agl = cfxZones.getNumberFromZoneProperty(theZone, "AGL", 0) + theZone.agl = theZone:getNumberFromZoneProperty("AGL", 0) + theZone.min, theZone.max = theZone:getPositiveRangeFromZoneProperty("num", 1, 1) - - if cfxZones.hasProperty(theZone, "start?") then - theZone.fxStart = cfxZones.getStringFromZoneProperty(theZone, "start?", "*") - theZone.fxLastStart = cfxZones.getFlagValue(theZone.fxStart, theZone) + if theZone:hasProperty("start?") then + theZone.fxStart = theZone:getStringFromZoneProperty("start?", "*") + theZone.fxLastStart = theZone:getFlagValue(theZone.fxStart) end - if cfxZones.hasProperty(theZone, "stop?") then - theZone.fxStop = cfxZones.getStringFromZoneProperty(theZone, "stop?", "*") - theZone.fxLastStop = cfxZones.getFlagValue(theZone.fxStop, theZone) + if theZone:hasProperty("stop?") then + theZone.fxStop = theZone:getStringFromZoneProperty("stop?", "*") + theZone.fxLastStop = theZone:getFlagValue(theZone.fxStop) end - theZone.fxOnStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false) + theZone.fxOnStart = theZone:getBoolFromZoneProperty("onStart", false) theZone.burning = false if not theZone.fxOnStart and not theZone.fxStart then @@ -84,14 +86,23 @@ function fireFX.createFXWithZone(theZone) -- output method (not needed) -- trigger method - theZone.fxTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "fxTriggerMethod", "change") - if cfxZones.hasProperty(theZone, "triggerMethod") then - theZone.fxTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") + theZone.fxTriggerMethod = theZone:getStringFromZoneProperty( "fxTriggerMethod", "change") + if theZone:hasProperty("triggerMethod") then + theZone.fxTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change") end + theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", false) + if theZone.max + 1 and (not theZone.rndLoc) then + if theZone.verbose or fireFX.verbose then + trigger.action.outText("+++ffx: more than 1 fires, will set to random loc") + end + theZone.rndLoc = true + end + if fireFX.verbose or theZone.verbose then trigger.action.outText("+++ffx: new FX <".. theZone.name ..">", 30) end + end -- @@ -99,18 +110,29 @@ end -- function fireFX.startTheFire(theZone) if not theZone.burning then - local p = cfxZones.getPoint(theZone) - p.y = land.getHeight({x = p.x, y = p.z}) + theZone.agl - local preset = theZone.fxCode - local density = theZone.density - trigger.action.effectSmokeBig(p, preset, density, theZone.name) + theZone.fireNames = {} + local num = cfxZones.randomInRange(theZone.min, theZone.max) + for i = 1, num do + local p = cfxZones.getPoint(theZone) + if theZone.rndLoc then + p = theZone:randomPointInZone() + end + p.y = land.getHeight({x = p.x, y = p.z}) + theZone.agl + local preset = theZone.fxCode + local density = theZone.density + local fireName = dcsCommon.uuid(theZone.name) + trigger.action.effectSmokeBig(p, preset, density, fireName) + theZone.fireNames[i] = fireName + end theZone.burning = true end end function fireFX.extinguishFire(theZone) if theZone.burning then - trigger.action.effectSmokeStop(theZone.name) + for idx, aFireName in pairs(theZone.fireNames) do + trigger.action.effectSmokeStop(aFireName) + end theZone.burning = false end end diff --git a/modules/groundExplosion.lua b/modules/groundExplosion.lua new file mode 100644 index 0000000..8e7ad51 --- /dev/null +++ b/modules/groundExplosion.lua @@ -0,0 +1,105 @@ +groundExplosion = {} +groundExplosion.version = "1.0.0" +groundExplosion.requiredLibs = { + "dcsCommon", + "cfxZones", +} +groundExplosion.zones = {} + +--[[-- +Version History + 1.0.0 - Initial version + +--]]-- + + +function groundExplosion.addExplosion(theZone) + theZone.powerMin, theZone.powerMax = theZone:getPositiveRangeFromZoneProperty("explosion", 1, 1) + theZone.triggerMethod = theZone:getStringFromZoneProperty("tiggerMethod", "change") + if theZone:hasProperty("boom?") then + theZone.boom = theZone:getStringFromZoneProperty("boom?", "none") + theZone.lastBoom = theZone:getFlagValue(theZone.boom) + end + theZone.numMin, theZone.numMax = theZone:getPositiveRangeFromZoneProperty("num", 1, 1) + theZone.rndLoc = theZone:getBoolFromZoneProperty("rndLoc", false) + if (theZone.numMax > 1) then + theZone.rndLoc = true + theZone.multi = true + end + theZone.duration = theZone:getNumberFromZoneProperty("duration", 0) + theZone.aglMin, theZone.aglMax = theZone:getPositiveRangeFromZoneProperty("AGL", 1,1) +end + +-- +-- go boom +-- +function groundExplosion.doBoom(args) + local loc = args[1] + local power = args[2] + local theZone = args[3] + trigger.action.explosion(loc, power) +end + +function groundExplosion.startBoom(theZone) + local now = timer.getTime() + local num = cfxZones.randomInRange(theZone.numMin, theZone.numMax) + local i = 1 + while i <= num do + local loc + if theZone.rndLoc then + loc = theZone:randomPointInZone() + else + loc = theZone:getPoint() + end + local h = land.getHeight({x = loc.x, y = loc.z}) + local agl = cfxZones.randomInRange(theZone.aglMin, theZone.aglMax) + loc.y = h + agl + local power = cfxZones.randomInRange(theZone.powerMin, theZone.powerMax) + if theZone.duration > 0 then -- deferred + local tplus = (i-1) * theZone.duration / num + timer.scheduleFunction(groundExplosion.doBoom, {loc, power, theZone}, now + tplus + 0.1) + else -- immediate + trigger.action.explosion(loc, power) + end + i = i + 1 + end +end + +-- +-- Update +-- +function groundExplosion.update() + for idx, theZone in pairs(groundExplosion.zones) do + + if theZone.boom then + if theZone:testZoneFlag(theZone.boom, theZone.triggerMethod, "lastBoom") then + groundExplosion.startBoom(theZone) + end + end + end + timer.scheduleFunction(groundExplosion.update, {}, timer.getTime() + 1) +end + +function groundExplosion.start() + if not dcsCommon.libCheck("cfx groundExplosion", + cfxObjectDestructDetector.requiredLibs) then + return false + end + + -- collect all zones with 'OBJECT ID' attribute + local attrZones = cfxZones.getZonesWithAttributeNamed("explosion") + for k, aZone in pairs(attrZones) do + groundExplosion.addExplosion(aZone) + table.insert(groundExplosion.zones, aZone) + end + + -- start update + timer.scheduleFunction(groundExplosion.update, {}, timer.getTime() + 1) + return true +end + +-- let's go +if not groundExplosion.start() then + trigger.action.outText("cf/x groundExplosion aborted: missing libraries", 30) + groundExplosion = nil +end \ No newline at end of file diff --git a/modules/pulseFlags.lua b/modules/pulseFlags.lua index 6b8b7ec..ab293f8 100644 --- a/modules/pulseFlags.lua +++ b/modules/pulseFlags.lua @@ -1,5 +1,5 @@ pulseFlags = {} -pulseFlags.version = "1.3.3" +pulseFlags.version = "2.0.0" pulseFlags.verbose = false pulseFlags.requiredLibs = { "dcsCommon", -- always @@ -39,6 +39,9 @@ pulseFlags.requiredLibs = { - 1.3.1 typos corrected - 1.3.2 removed last pulse's timeID upon entry in doPulse - 1.3.3 removed 'pulsing' when pausing, so we can restart + - 2.0.0 dmlZones / OOP + using method on all outputs + --]]-- @@ -59,106 +62,86 @@ end -- function pulseFlags.createPulseWithZone(theZone) - if cfxZones.hasProperty(theZone, "pulse") then - theZone.pulseFlag = cfxZones.getStringFromZoneProperty(theZone, "pulse", "*none") -- the flag to pulse - end - - if cfxZones.hasProperty(theZone, "pulse!") then - theZone.pulseFlag = cfxZones.getStringFromZoneProperty(theZone, "pulse!", "*none") -- the flag to pulse + if theZone:hasProperty("pulse") then + theZone.pulseFlag = theZone:getStringFromZoneProperty("pulse", "*none") -- the flag to pulse + trigger.action.outText("Warning: pulser in zone <" .. theZone.name .. "> uses deprecated attribuet 'pulse'.", 30) + elseif theZone:hasProperty("pulse!") then + theZone.pulseFlag = theZone:getStringFromZoneProperty("pulse!", "*none") -- the flag to pulse end -- time can be number, or number-number range - theZone.minTime, theZone.time = cfxZones.getPositiveRangeFromZoneProperty(theZone, "time", 1) - if cfxZones.hasProperty(theZone, "pulseInterval") then - theZone.minTime, theZone.time = cfxZones.getPositiveRangeFromZoneProperty(theZone, "pulseInterval", 1) + theZone.minTime = 1 + theZone.time = 1 + if theZone:hasProperty("time") then + theZone.minTime, theZone.time = theZone:getPositiveRangeFromZoneProperty("time", 1) + elseif theZone:hasProperty("pulseInterval") then + theZone.minTime, theZone.time = theZone:getPositiveRangeFromZoneProperty("pulseInterval", 1) end - if pulseFlags.verbose or theZone.verbose then trigger.action.outText("+++pulF: zone <" .. theZone.name .. "> time is <".. theZone.minTime ..", " .. theZone.time .. "!", 30) end theZone.pulses = -1 -- set to infinite - if cfxZones.hasProperty(theZone, "pulses") then - local minP - local maxP - minP, maxP = cfxZones.getPositiveRangeFromZoneProperty(theZone, "pulses", 1) + if theZone:hasProperty("pulses") then + local minP, maxP = theZone:getPositiveRangeFromZoneProperty("pulses", 1) if minP == maxP then theZone.pulses = minP else theZone.pulses = cfxZones.randomInRange(minP, maxP) end end - if pulseFlags.verbose or theZone.verbose then trigger.action.outText("+++pulF: zone <" .. theZone.name .. "> set to <" .. theZone.pulses .. "> pulses", 30) end - + theZone.pulsesLeft = 0 -- will start new cycle - -- watchflag: - -- triggerMethod - theZone.pulseTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") + theZone.pulseTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change") - if cfxZones.hasProperty(theZone, "pulseTriggerMethod") then - theZone.pulseTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "pulseTriggerMethod", "change") + if theZone:hasProperty("pulseTriggerMethod") then + theZone.pulseTriggerMethod = theZone:getStringFromZoneProperty("pulseTriggerMethod", "change") end -- trigger flags - if cfxZones.hasProperty(theZone, "activate?") then - theZone.activatePulseFlag = cfxZones.getStringFromZoneProperty(theZone, "activate?", "none") - theZone.lastActivateValue = cfxZones.getFlagValue(theZone.activatePulseFlag, theZone) -- trigger.misc.getUserFlag(theZone.activatePulseFlag) -- save last value + if theZone:hasProperty("activate?") then + theZone.activatePulseFlag = theZone:getStringFromZoneProperty("activate?", "none") + theZone.lastActivateValue = theZone:getFlagValue(theZone.activatePulseFlag) end - if cfxZones.hasProperty(theZone, "startPulse?") then - theZone.activatePulseFlag = cfxZones.getStringFromZoneProperty(theZone, "startPulse?", "none") - theZone.lastActivateValue = cfxZones.getFlagValue(theZone.activatePulseFlag, theZone) -- trigger.misc.getUserFlag(theZone.activatePulseFlag) -- save last value + if theZone:hasProperty("startPulse?") then + theZone.activatePulseFlag = theZone:getStringFromZoneProperty("startPulse?", "none") + theZone.lastActivateValue = theZone:getFlagValue(theZone.activatePulseFlag) end - if cfxZones.hasProperty(theZone, "pause?") then - theZone.pausePulseFlag = cfxZones.getStringFromZoneProperty(theZone, "pause?", "*none") - theZone.lastPauseValue = cfxZones.getFlagValue(theZone.pausePulseFlag, theZone)-- trigger.misc.getUserFlag(theZone.pausePulseFlag) -- save last value + if theZone:hasProperty("pause?") then + theZone.pausePulseFlag = theZone:getStringFromZoneProperty("pause?", "*none") + theZone.lastPauseValue = theZone:getFlagValue(theZone.pausePulseFlag) end - if cfxZones.hasProperty(theZone, "pausePulse?") then - theZone.pausePulseFlag = cfxZones.getStringFromZoneProperty(theZone, "pausePulse?", "*none") - theZone.lastPauseValue = cfxZones.getFlagValue(theZone.pausePulseFlag, theZone)-- trigger.misc.getUserFlag(theZone.pausePulseFlag) -- save last value + if theZone:hasProperty("pausePulse?") then + theZone.pausePulseFlag = theZone:getStringFromZoneProperty( "pausePulse?", "*none") + theZone.lastPauseValue = theZone:getFlagValue(theZone.pausePulseFlag) end -- harmonizing on onStart, and converting to old pulsePaused - local onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", true) + local onStart = theZone:getBoolFromZoneProperty("onStart", true) theZone.pulsePaused = not (onStart) - -- old code, to be deprecated - if cfxZones.hasProperty(theZone, "paused") then - theZone.pulsePaused = cfxZones.getBoolFromZoneProperty(theZone, "paused", false) - elseif cfxZones.hasProperty(theZone, "pulseStopped") then - theZone.pulsePaused = cfxZones.getBoolFromZoneProperty(theZone, "pulseStopped", false) - end - --]]-- - - theZone.pulseMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "flip") - - if cfxZones.hasProperty(theZone, "pulseMethod") then - theZone.pulseMethod = cfxZones.getStringFromZoneProperty(theZone, "pulseMethod", "flip") - end - - if cfxZones.hasProperty(theZone, "outputMethod") then - theZone.pulseMethod = cfxZones.getStringFromZoneProperty(theZone, "outputMethod", "flip") + theZone.pulseMethod = theZone:getStringFromZoneProperty("method", "inc") + + if theZone:hasProperty("outputMethod") then + theZone.pulseMethod = theZone:getStringFromZoneProperty( "outputMethod", "inc") end -- done flag - if cfxZones.hasProperty(theZone, "done+1") then - theZone.pulseDoneFlag = cfxZones.getStringFromZoneProperty(theZone, "done+1", "*none") + if theZone:hasProperty("pulsesDone!") then + theZone.pulseDoneFlag = theZone:getStringFromZoneProperty("pulsesDone!", "*none") end - if cfxZones.hasProperty(theZone, "pulsesDone!") then - theZone.pulseDoneFlag = cfxZones.getStringFromZoneProperty(theZone, "pulsesDone!", "*none") + if theZone:hasProperty("done!") then + theZone.pulseDoneFlag = theZone:getStringFromZoneProperty("done!", "*none") end - if cfxZones.hasProperty(theZone, "done!") then - theZone.pulseDoneFlag = cfxZones.getStringFromZoneProperty(theZone, "done!", "*none") - end - theZone.pulsing = false -- not running theZone.hasPulsed = false - theZone.zeroPulse = cfxZones.getBoolFromZoneProperty(theZone, "zeroPulse", true) + theZone.zeroPulse = theZone:getBoolFromZoneProperty("zeroPulse", true) end -- @@ -185,8 +168,7 @@ function pulseFlags.doPulse(args) trigger.action.outText("+++pulF: will bang " .. theZone.pulseFlag, 30); end - cfxZones.pollFlag(theZone.pulseFlag, theZone.pulseMethod, theZone) - + theZone:pollFlag(theZone.pulseFlag, theZone.pulseMethod) -- decrease count if theZone.pulses > 0 then -- only do this if ending @@ -196,11 +178,10 @@ function pulseFlags.doPulse(args) if theZone.pulsesLeft < 1 then -- increment done flag if set if theZone.pulseDoneFlag then - --local currVal = cfxZones.getFlagValue(theZone.pulseDoneFlag, theZone)-- trigger.misc.getUserFlag(theZone.pulseDoneFlag) - cfxZones.pollFlag(theZone.pulseDoneFlag, "inc", theZone) -- trigger.action.setUserFlag(theZone.pulseDoneFlag, currVal + 1) + theZone:pollFlag(theZone.pulseDoneFlag, theZone.pulseMethod) end if pulseFlags.verbose or theZone.verbose then - trigger.action.outText("+++pulF: pulse <" .. theZone.name .. "> ended!", 30) + trigger.action.outText("+++pulF: pulse <" .. theZone.name .. "> ended.", 30) end theZone.pulsing = false theZone.pulsePaused = true @@ -218,7 +199,6 @@ function pulseFlags.doPulse(args) -- if we get here, schedule next pulse local delay = cfxZones.randomDelayFromPositiveRange(theZone.minTime, theZone.time) - -- schedule in delay time theZone.scheduledTime = timer.getTime() + delay theZone.timerID = timer.scheduleFunction(pulseFlags.doPulse, args, theZone.scheduledTime) @@ -228,7 +208,6 @@ function pulseFlags.doPulse(args) end end - -- start new pulse, will reset function pulseFlags.startNewPulse(theZone) theZone.pulsesLeft = theZone.pulses @@ -241,7 +220,6 @@ function pulseFlags.startNewPulse(theZone) end function pulseFlags.update() - -- call me in a second to poll triggers timer.scheduleFunction(pulseFlags.update, {}, timer.getTime() + 1) for idx, aZone in pairs(pulseFlags.pulses) do @@ -249,10 +227,7 @@ function pulseFlags.update() if aZone.pulsing then -- this zone has a pulse and has scheduled -- a new pulse, nothing to do - else - -- this zone has not scheduled a new pulse - -- let's see why if aZone.pulsePaused then -- ok, zone is paused. all clear else @@ -263,7 +238,7 @@ function pulseFlags.update() -- see if we got a pause or activate command -- activatePulseFlag - if cfxZones.testZoneFlag(aZone, aZone.activatePulseFlag, aZone.pulseTriggerMethod, "lastActivateValue") then + if aZone:testZoneFlag(activatePulseFlag, aZone.pulseTriggerMethod, "lastActivateValue") then if pulseFlags.verbose or aZone.verbose then trigger.action.outText("+++pulF: activating <" .. aZone.name .. ">", 30) end @@ -271,7 +246,7 @@ function pulseFlags.update() end -- pausePulseFlag - if cfxZones.testZoneFlag(aZone, aZone.pausePulseFlag, aZone.pulseTriggerMethod, "lastPauseValue") then + if aZone:testZoneFlag(aZone.pausePulseFlag, aZone.pulseTriggerMethod, "lastPauseValue") then if pulseFlags.verbose or aZone.verbose then trigger.action.outText("+++pulF: pausing <" .. aZone.name .. ">", 30) end @@ -296,11 +271,9 @@ function pulseFlags.readConfigZone() if pulseFlags.verbose then trigger.action.outText("+++pulF: NO config zone!", 30) end - return + theZone = cfxZones.createSimpleZone("pulseFlagsConfig") end - - pulseFlags.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) - + pulseFlags.verbose = theZone.verbose if pulseFlags.verbose then trigger.action.outText("+++pulF: read config", 30) end @@ -385,25 +358,19 @@ function pulseFlags.start() -- read config pulseFlags.readConfigZone() - -- process RND Zones + -- process "pulse" Zones - deprecated!! local attrZones = cfxZones.getZonesWithAttributeNamed("pulse") --- local a = dcsCommon.getSizeOfTable(attrZones) --- trigger.action.outText("pulse zones: " .. a, 30) - -- now create a pulse gen for each one and add them - -- to our watchlist for k, aZone in pairs(attrZones) do - pulseFlags.createPulseWithZone(aZone) -- process attribute and add to zone - pulseFlags.addPulse(aZone) -- remember it so we can pulse it + pulseFlags.createPulseWithZone(aZone) + pulseFlags.addPulse(aZone) end attrZones = cfxZones.getZonesWithAttributeNamed("pulse!") a = dcsCommon.getSizeOfTable(attrZones) trigger.action.outText("pulse! zones: " .. a, 30) - -- now create a pulse gen for each one and add them - -- to our watchlist for k, aZone in pairs(attrZones) do - pulseFlags.createPulseWithZone(aZone) -- process attribute and add to zone - pulseFlags.addPulse(aZone) -- remember it so we can pulse it + pulseFlags.createPulseWithZone(aZone) + pulseFlags.addPulse(aZone) end -- load any saved data @@ -417,7 +384,6 @@ function pulseFlags.start() end -- start update in 1 second - --pulseFlags.update() timer.scheduleFunction(pulseFlags.update, {}, timer.getTime() + 1) trigger.action.outText("cfx Pulse Flags v" .. pulseFlags.version .. " started.", 30) diff --git a/modules/raiseFlag.lua b/modules/raiseFlag.lua index 63a0fbb..0224ade 100644 --- a/modules/raiseFlag.lua +++ b/modules/raiseFlag.lua @@ -1,5 +1,5 @@ raiseFlag = {} -raiseFlag.version = "1.2.1" +raiseFlag.version = "2.0.0" raiseFlag.verbose = false raiseFlag.requiredLibs = { "dcsCommon", -- always @@ -7,19 +7,18 @@ raiseFlag.requiredLibs = { } raiseFlag.flags = {} --[[-- - Raise A Flag module -- (c) 2022 by Christian Franz and cf/x AG - - Version History - 1.0.0 - initial release - 1.0.1 - synonym "raiseFlag!" - 1.1.0 - DML update - 1.2.0 - Watchflag update - 1.2.1 - support for 'inc', 'dec', 'flip' - 2.0.0 - dmlZones - - full method support - - full DML upgrade - - method attribute (synonym to 'value' +Raise A Flag module -- (c) 2022-23 by Christian Franz and cf/x AG +Version History + 1.0.0 - initial release + 1.0.1 - synonym "raiseFlag!" + 1.1.0 - DML update + 1.2.0 - Watchflag update + 1.2.1 - support for 'inc', 'dec', 'flip' + 2.0.0 - dmlZones + - full method support + - full DML upgrade + - method attribute (synonym to 'value') --]]-- function raiseFlag.addRaiseFlag(theZone) table.insert(raiseFlag.flags, theZone) @@ -32,7 +31,6 @@ function raiseFlag.getRaiseFlagByName(aName) if raiseFlag.verbose then trigger.action.outText("+++rFlg: no raiseFlag with name <" .. aName ..">", 30) end - return nil end @@ -41,10 +39,10 @@ end -- function raiseFlag.createRaiseFlagWithZone(theZone) -- get flag from faiseFlag itself - if cfxZones.hasProperty(theZone, "raiseFlag") then - theZone.raiseFlag = cfxZones.getStringFromZoneProperty(theZone, "raiseFlag", "") -- the flag to raise + if theZone:hasProperty("raiseFlag") then + theZone.raiseFlag = theZone:getStringFromZoneProperty("raiseFlag", "") -- the flag to raise else - theZone.raiseFlag = cfxZones.getStringFromZoneProperty(theZone, "raiseFlag!", "") -- the flag to raise + theZone.raiseFlag = theZone:getStringFromZoneProperty("raiseFlag!", "") -- the flag to raise end -- pre-method DML raiseFlag is now upgraded to method. @@ -55,12 +53,7 @@ function raiseFlag.createRaiseFlagWithZone(theZone) theZone.flagValue = theZone:getStringFromZoneProperty("method", "inc") end theZone.flagValue = theZone.flagValue:lower() - theZone.minAfterTime, theZone.maxAfterTime = theZone:getPositiveRangeFromZoneProperty("afterTime", -1) - - -- method for triggering - -- watchflag: - -- triggerMethod theZone.raiseTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change") if theZone:hasProperty("raiseTriggerMethod") then theZone.raiseTriggerMethod = theZone:getStringFromZoneProperty("raiseTriggerMethod", "change") @@ -70,7 +63,6 @@ function raiseFlag.createRaiseFlagWithZone(theZone) theZone.triggerStopFlag = theZone:getStringFromZoneProperty( "stopFlag?", "none") theZone.lastTriggerStopValue = theZone:getFlagValue(theZone.triggerStopFlag) -- save last value end - theZone.scheduleID = nil theZone.raiseStopped = false @@ -94,21 +86,6 @@ function raiseFlag.triggered(args) if raiseFlag.verbose or theZone.verbose then trigger.action.outText("+++rFlg - raising <" .. theZone.raiseFlag .. "> with method '" .. command .. "'" ,30) end - - --[[-- - command = dcsCommon.trim(command) - if command == "inc" or command == "dec" or command == "flip" then - cfxZones.pollFlag(theZone.raiseFlag, command, theZone) - if raiseFlag.verbose or theZone.verbose then - trigger.action.outText("+++rFlg - raising <" .. theZone.raiseFlag .. "> with method " .. command ,30) - end - else - cfxZones.setFlagValue(theZone.raiseFlag, theZone.flagValue, theZone) - if raiseFlag.verbose or theZone.verbose then - trigger.action.outText("+++rFlg - raising <" .. theZone.raiseFlag .. "> to value: " .. theZone.flagValue ,30) - end - end - --]]-- end -- @@ -117,28 +94,23 @@ end function raiseFlag.update() -- call me in a second to poll triggers timer.scheduleFunction(raiseFlag.update, {}, timer.getTime() + 1) - for idx, aZone in pairs(raiseFlag.flags) do -- make sure to re-start before reading time limit if aZone:testZoneFlag(aZone.triggerStopFlag, aZone.raiseTriggerMethod, "lastTriggerStopValue") then theZone.raiseStopped = true -- we are done, no flag! end - end end -- -- config & go! -- - function raiseFlag.readConfigZone() local theZone = cfxZones.getZoneByName("raiseFlagConfig") if not theZone then theZone = cfxZones.createSimpleZone("raiseFlagConfig") end - raiseFlag.verbose = theZone.verbose - if raiseFlag.verbose then trigger.action.outText("+++rFlg: read config", 30) end @@ -157,7 +129,7 @@ function raiseFlag.start() -- read config raiseFlag.readConfigZone() - -- process cloner Zones + -- process raise Flags Zones local attrZones = cfxZones.getZonesWithAttributeNamed("raiseFlag") for k, aZone in pairs(attrZones) do raiseFlag.createRaiseFlagWithZone(aZone) -- process attributes diff --git a/modules/stopGaps.lua b/modules/stopGaps.lua index 6946dbf..3c5b762 100644 --- a/modules/stopGaps.lua +++ b/modules/stopGaps.lua @@ -1,5 +1,5 @@ stopGap = {} -stopGap.version = "1.0.9" +stopGap.version = "1.0.10" stopGap.verbose = false stopGap.ssbEnabled = true stopGap.ignoreMe = "-sg" @@ -48,6 +48,7 @@ stopGap.requiredLibs = { 1.0.8 - added refreshInterval option as requested - refresh attribute config zone 1.0.9 - in line with standalone (optimization not required for DML) + 1.0.10 - some more verbosity for spIgnore and sgIgnore zones (DML only) --]]-- @@ -356,12 +357,22 @@ end -- function stopGap.createStopGapZone(theZone) local sg = theZone:getBoolFromZoneProperty("stopGap", true) - if sg then theZone.sgIgnore = false else theZone.sgIgnore = true end + if sg then theZone.sgIgnore = false else + if theZone.verbose or stopGap.verbose then + trigger.action.outText("++sg: Ignoring player craft in zone <" ..theZone.name .."> for all modes", 30) + end + theZone.sgIgnore = true + end end function stopGap.createStopGapSPZone(theZone) local sp = theZone:getBoolFromZoneProperty("stopGapSP", true) - if sp then theZone.spIgnore = false else theZone.spIgnore = true end + if sp then theZone.spIgnore = false else + if theZone.verbose or stopGap.verbose then + trigger.action.outText("++sg: Ignoring player craft in zone <" ..theZone.name .."> for single-player mode", 30) + end + theZone.spIgnore = true + end end -- diff --git a/tutorial & demo missions/demo - Air Caucasus II.miz b/tutorial & demo missions/demo - Air Caucasus II.miz index f3c3237..3df0e06 100644 Binary files a/tutorial & demo missions/demo - Air Caucasus II.miz and b/tutorial & demo missions/demo - Air Caucasus II.miz differ diff --git a/tutorial & demo missions/demo - Civ Air International.miz b/tutorial & demo missions/demo - Civ Air International.miz index da699b7..7532b59 100644 Binary files a/tutorial & demo missions/demo - Civ Air International.miz and b/tutorial & demo missions/demo - Civ Air International.miz differ diff --git a/tutorial & demo missions/demo - Pulsing Fun.miz b/tutorial & demo missions/demo - Pulsing Fun.miz index acc95a1..02fef4d 100644 Binary files a/tutorial & demo missions/demo - Pulsing Fun.miz and b/tutorial & demo missions/demo - Pulsing Fun.miz differ diff --git a/tutorial & demo missions/demo - Random Death.miz b/tutorial & demo missions/demo - Random Death.miz index 4dd82fc..d5933cc 100644 Binary files a/tutorial & demo missions/demo - Random Death.miz and b/tutorial & demo missions/demo - Random Death.miz differ diff --git a/tutorial & demo missions/demo - Random Glory.miz b/tutorial & demo missions/demo - Random Glory.miz index 45f5fc5..3cf0c2f 100644 Binary files a/tutorial & demo missions/demo - Random Glory.miz and b/tutorial & demo missions/demo - Random Glory.miz differ diff --git a/tutorial & demo missions/demo - Virgin (Civ) Air.miz b/tutorial & demo missions/demo - Virgin (Civ) Air.miz index b3b70ae..4293cc1 100644 Binary files a/tutorial & demo missions/demo - Virgin (Civ) Air.miz and b/tutorial & demo missions/demo - Virgin (Civ) Air.miz differ diff --git a/tutorial & demo missions/demo - boom boom.miz b/tutorial & demo missions/demo - boom boom.miz new file mode 100644 index 0000000..8f8685a Binary files /dev/null and b/tutorial & demo missions/demo - boom boom.miz differ diff --git a/tutorial & demo missions/demo - object destruct detection.miz b/tutorial & demo missions/demo - object destruct detection.miz index 558478f..4e8a5ca 100644 Binary files a/tutorial & demo missions/demo - object destruct detection.miz and b/tutorial & demo missions/demo - object destruct detection.miz differ diff --git a/tutorial & demo missions/demo - types and civil liveries.miz b/tutorial & demo missions/demo - types and civil liveries.miz new file mode 100644 index 0000000..b9e37b5 Binary files /dev/null and b/tutorial & demo missions/demo - types and civil liveries.miz differ